Execute approved tool calls
CI / Rust Checks (push) Successful in 1h31m49s
CI / UI Checks (push) Successful in 5s
CI / Frontend E2E (push) Has been cancelled
CI / Deployment Manifests (push) Has been cancelled
CI / Deploy (push) Has been cancelled

This commit is contained in:
github-ops
2026-06-24 12:49:57 +00:00
parent 78d3052a61
commit 267061e226
9 changed files with 282 additions and 26 deletions
+145 -2
View File
@@ -18,8 +18,8 @@ use crank_core::{
PlatformApiKeyScope, SecretId,
};
use crank_registry::{
CreateApprovalRequest, CreateInvocationLogRequest, DecideApprovalRequest, PostgresRegistry,
PublishedAgentTool,
ApprovalRequestRecord, CreateApprovalRequest, CreateInvocationLogRequest,
DecideApprovalRequest, FinishApprovalRequest, PostgresRegistry, PublishedAgentTool,
};
use crank_runtime::{
RequestRateLimiter, ResolvedAuth, RuntimeError, RuntimeExecutionRequest, RuntimeExecutor,
@@ -155,6 +155,10 @@ pub fn build_app(
"/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/approve",
post(approve_request),
)
.route(
"/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}",
get(get_approval_request),
)
.route(
"/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/deny",
post(deny_request),
@@ -213,6 +217,42 @@ async fn approve_request(
.await
}
async fn get_approval_request(
Path(path): Path<ApprovalRoutePath>,
State(state): State<Arc<AppState>>,
headers: HeaderMap,
) -> Response {
let agent_path = AgentRoutePath {
workspace_slug: path.workspace_slug,
agent_slug: path.agent_slug,
};
let key = match require_approval_access(
&state,
&agent_path,
&headers,
PlatformApiKeyScope::ReadPending,
)
.await
{
Ok(key) => key,
Err(status) => return status.into_response(),
};
let Some(agent_id) = key.api_key.agent_id.as_ref() else {
return StatusCode::FORBIDDEN.into_response();
};
let approval_id = ApprovalRequestId::new(path.approval_id);
match state
.registry
.get_approval_request_for_agent(&key.api_key.workspace_id, agent_id, &approval_id)
.await
{
Ok(Some(record)) => Json(json!(record)).into_response(),
Ok(None) => StatusCode::NOT_FOUND.into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
async fn deny_request(
Path(path): Path<ApprovalRoutePath>,
State(state): State<Arc<AppState>>,
@@ -279,12 +319,115 @@ async fn decide_approval_request(
})
.await
{
Ok(Some(record)) if status == ApprovalRequestStatus::Approved => {
match execute_approved_request(&state, &agent_path, record).await {
Ok(record) => Json(json!(record)).into_response(),
Err(response) => response,
}
}
Ok(Some(record)) => Json(json!(record)).into_response(),
Ok(None) => StatusCode::CONFLICT.into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
async fn execute_approved_request(
state: &Arc<AppState>,
path: &AgentRoutePath,
approval: ApprovalRequestRecord,
) -> Result<ApprovalRequestRecord, Response> {
let tools = state
.catalog
.list_tools(&path.workspace_slug, &path.agent_slug)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())?;
let Some(tool) = tools.into_iter().find(|tool| {
tool.operation.id == approval.approval.operation_id
&& tool.operation.version == approval.approval.operation_version
}) else {
return Err(StatusCode::NOT_FOUND.into_response());
};
let operation = runtime_operation(&tool);
let request_preview = build_request_preview(
&state.runtime,
&operation,
&approval.approval.request_payload,
);
let started_at = Instant::now();
let resolved_auth =
resolve_operation_auth(state, &tool.workspace_id, &operation.execution_config).await;
let result = match resolved_auth {
Ok(resolved_auth) => {
state
.runtime
.execute_request(
RuntimeExecutionRequest::new(&operation, &approval.approval.request_payload)
.with_optional_auth(resolved_auth.as_ref()),
)
.await
}
Err(error) => Err(error),
};
let (status, response_payload, invocation_status, invocation_level, message, error_kind) =
match result {
Ok(output) => (
ApprovalRequestStatus::Completed,
output,
InvocationStatus::Ok,
InvocationLevel::Info,
"approved tool call completed",
None,
),
Err(error) => (
ApprovalRequestStatus::Failed,
json!({
"error": {
"code": runtime_error_code(&error),
"message": error.to_string(),
}
}),
InvocationStatus::Error,
InvocationLevel::Error,
"approved tool call failed",
Some(runtime_error_code(&error)),
),
};
let _ = persist_invocation(
state,
&tool,
InvocationRecord {
request_id: Some(approval.approval.id.as_str()),
tool_name: &tool.tool_name,
status: invocation_status,
level: invocation_level,
message,
status_code: None,
error_kind,
duration: started_at.elapsed(),
request_preview,
response_preview: response_payload.clone(),
},
)
.await;
state
.registry
.finish_approval_request(FinishApprovalRequest {
workspace_id: &approval.approval.workspace_id,
agent_id: &approval.approval.agent_id,
approval_id: &approval.approval.id,
status,
response_payload: Some(response_payload),
decision_note: None,
})
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())?
.ok_or_else(|| StatusCode::CONFLICT.into_response())
}
async fn mcp_get(
Path(path): Path<AgentRoutePath>,
State(state): State<Arc<AppState>>,