Add approval mode selection
CI / Rust Checks (push) Successful in 5m35s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Successful in 3m28s
CI / Deploy (push) Successful in 1m38s

This commit is contained in:
github-ops
2026-06-27 07:55:38 +00:00
parent 2b2ff92146
commit 700a684257
15 changed files with 425 additions and 31 deletions
+94 -3
View File
@@ -15,7 +15,7 @@ use axum::{
use crank_core::{
ApprovalRequest, ApprovalRequestId, ApprovalRequestStatus, AuthProfile, CoordinationStateStore,
InvocationLevel, InvocationLog, InvocationLogId, InvocationSource, InvocationStatus,
PlatformApiKeyScope, SecretId,
OperationApprovalMode, PlatformApiKeyScope, SecretId,
};
use crank_registry::{
ApprovalRequestRecord, CreateApprovalRequest, CreateInvocationLogRequest,
@@ -79,6 +79,8 @@ pub(super) struct AppState {
struct InitializeParams {
#[serde(rename = "protocolVersion")]
protocol_version: String,
#[serde(default)]
capabilities: Value,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -955,7 +957,7 @@ async fn handle_base_tool_call(
let tool = execution.tool;
let arguments = execution.arguments;
let operation = runtime_operation(&tool);
if let Some(response) = maybe_create_pending_approval(
if let Some(response) = maybe_handle_approval_policy(
&state,
session,
message,
@@ -1052,7 +1054,7 @@ async fn handle_base_tool_call(
}
}
async fn maybe_create_pending_approval(
async fn maybe_handle_approval_policy(
state: &Arc<AppState>,
session: &SessionState,
message: &Value,
@@ -1066,6 +1068,42 @@ async fn maybe_create_pending_approval(
return None;
}
match policy.mode {
OperationApprovalMode::Custom => {
maybe_create_custom_pending_approval(
state,
session,
message,
response_mode,
tool,
arguments,
transport_request_id,
)
.await
}
OperationApprovalMode::Elicitation => Some(handle_elicitation_approval(
session,
message,
response_mode,
tool,
arguments,
policy.elicitation_message.as_deref(),
transport_request_id,
)),
}
}
async fn maybe_create_custom_pending_approval(
state: &Arc<AppState>,
session: &SessionState,
message: &Value,
response_mode: ResponseMode,
tool: &PublishedAgentTool,
arguments: &Value,
transport_request_id: &str,
) -> Option<Response> {
let policy = tool.operation.execution_config.approval_policy.as_ref()?;
let approval_id = ApprovalRequestId::new(format!("approval_{}", uuid::Uuid::now_v7().simple()));
let now = OffsetDateTime::now_utc();
let expires_at = now + time::Duration::seconds(i64::from(policy.ttl_seconds));
@@ -1145,6 +1183,54 @@ async fn maybe_create_pending_approval(
))
}
fn handle_elicitation_approval(
session: &SessionState,
message: &Value,
response_mode: ResponseMode,
tool: &PublishedAgentTool,
arguments: &Value,
elicitation_message: Option<&str>,
transport_request_id: &str,
) -> Response {
if !session.supports_elicitation {
return tool_error_response(
message,
response_mode,
&session.protocol_version,
generic_tool_error_contract(
"approval_elicitation_not_supported",
"operation requires MCP Elicitation, but the MCP client did not advertise elicitation capability",
transport_request_id,
false,
Some(
"Выберите Custom MCP Approval или подключите MCP-клиент с поддержкой elicitation.",
),
),
);
}
let payload_preview = tool
.operation
.execution_config
.approval_policy
.as_ref()
.and_then(|policy| policy.show_payload_preview.then(|| arguments.clone()))
.unwrap_or(Value::Null);
success_tool_response(
message,
response_mode,
&session.protocol_version,
json!({
"status": "elicitation_required",
"message": elicitation_message.unwrap_or("Confirm operation execution."),
"tool": tool.tool_name,
"payload_preview": payload_preview,
"note": "This MCP client advertised elicitation support. Full elicitation/create continuation is handled by compatible client integrations.",
}),
)
}
fn approval_url_for(tool: &PublishedAgentTool, approval_id: &ApprovalRequestId) -> String {
format!(
"/v1/{}/{}/approvals/{}",
@@ -1191,12 +1277,17 @@ async fn handle_initialize(
};
let now = OffsetDateTime::now_utc();
let expires_at = add_millis(now, TRANSPORT_SESSION_TTL_MS);
let supports_elicitation = initialize_params
.capabilities
.get("elicitation")
.is_some_and(Value::is_object);
let session_id = match state
.sessions
.create(
protocol_version,
&path.workspace_slug,
&path.agent_slug,
supports_elicitation,
now,
Some(expires_at),
)