use anyhow::Result;
use serde_json::{Value, json};
use crate::bridge_protocol::{
PendingServerRequestOption, PendingServerRequestQuestion, PendingServerRequestRecord,
now_millis,
};
use crate::state::BridgeState;
use crate::state::helpers::{optional_string, request_key};
pub(super) async fn handle_server_request(
state: &BridgeState,
runtime_id: &str,
id: Value,
method: &str,
params: Value,
) -> Result<()> {
match method {
"item/commandExecution/requestApproval"
| "item/fileChange/requestApproval"
| "item/tool/requestUserInput"
| "mcpServer/elicitation/request"
| "item/permissions/requestApproval"
| "item/tool/call"
| "applyPatchApproval"
| "execCommandApproval"
| "account/chatgptAuthTokens/refresh" => {
let request = pending_server_request(runtime_id, id, method, params)?;
state.storage.put_pending_request(&request)?;
state.emit_event(
method,
Some(runtime_id),
request.thread_id.as_deref(),
json!({ "request": request }),
)?;
}
_ => {
let runtime = state.require_runtime(Some(runtime_id)).await?;
runtime
.app_server
.respond_error(id, -32000, "bridge 不支持的 server request")
.await?;
}
}
Ok(())
}
fn pending_server_request(
runtime_id: &str,
rpc_request_id: Value,
request_type: &str,
params: Value,
) -> Result<PendingServerRequestRecord> {
let available_decisions = params
.get("availableDecisions")
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(Value::as_str)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
})
.unwrap_or_else(|| default_available_decisions(request_type));
Ok(PendingServerRequestRecord {
request_id: request_key(&rpc_request_id),
runtime_id: runtime_id.to_string(),
rpc_request_id,
request_type: request_type.to_string(),
request_kind: pending_request_kind(request_type).to_string(),
thread_id: optional_string(¶ms, "threadId")
.or_else(|| optional_string(¶ms, "conversationId")),
turn_id: optional_string(¶ms, "turnId"),
item_id: optional_string(¶ms, "itemId"),
call_id: optional_string(¶ms, "callId"),
title: pending_request_title(request_type),
reason: optional_string(¶ms, "reason")
.or_else(|| optional_string(¶ms, "message"))
.or_else(|| optional_string(¶ms, "title")),
command: pending_request_command(¶ms),
cwd: optional_string(¶ms, "cwd"),
grant_root: optional_string(¶ms, "grantRoot"),
tool_name: optional_string(¶ms, "tool")
.or_else(|| optional_string(¶ms, "name"))
.or_else(|| optional_string(¶ms, "serverName")),
arguments: params.get("arguments").cloned(),
questions: pending_request_questions(¶ms),
proposed_execpolicy_amendment: params.get("proposedExecpolicyAmendment").cloned(),
network_approval_context: params.get("networkApprovalContext").cloned(),
permissions: params.get("permissions").cloned(),
schema: params
.get("schema")
.cloned()
.or_else(|| params.get("requestedSchema").cloned())
.or_else(|| params.get("inputSchema").cloned()),
available_decisions,
raw_payload: params,
created_at_ms: now_millis(),
})
}
fn pending_request_title(request_type: &str) -> Option<String> {
Some(match request_type {
"item/commandExecution/requestApproval" => "命令执行确认".to_string(),
"item/fileChange/requestApproval" => "文件改动确认".to_string(),
"item/tool/requestUserInput" => "需要补充输入".to_string(),
"item/permissions/requestApproval" => "权限确认".to_string(),
"item/tool/call" => "动态工具调用".to_string(),
"mcpServer/elicitation/request" => "MCP 交互请求".to_string(),
"applyPatchApproval" => "补丁应用确认".to_string(),
"execCommandApproval" => "命令执行确认".to_string(),
"account/chatgptAuthTokens/refresh" => "刷新 ChatGPT 认证".to_string(),
_ => return None,
})
}
fn pending_request_kind(request_type: &str) -> &'static str {
match request_type {
"item/commandExecution/requestApproval" => "commandExecutionApproval",
"item/fileChange/requestApproval" => "fileChangeApproval",
"item/tool/requestUserInput" => "toolRequestUserInput",
"mcpServer/elicitation/request" => "mcpElicitationRequest",
"item/permissions/requestApproval" => "permissionsApproval",
"item/tool/call" => "dynamicToolCall",
"applyPatchApproval" => "applyPatchApproval",
"execCommandApproval" => "execCommandApproval",
"account/chatgptAuthTokens/refresh" => "chatgptAuthTokensRefresh",
_ => "unknown",
}
}
fn default_available_decisions(request_type: &str) -> Vec<String> {
match request_type {
"item/commandExecution/requestApproval"
| "item/fileChange/requestApproval"
| "item/permissions/requestApproval" => vec![
"accept".to_string(),
"acceptForSession".to_string(),
"decline".to_string(),
"cancel".to_string(),
],
"applyPatchApproval" | "execCommandApproval" => vec![
"approved".to_string(),
"approved_for_session".to_string(),
"denied".to_string(),
"abort".to_string(),
],
_ => Vec::new(),
}
}
fn pending_request_command(params: &Value) -> Option<String> {
optional_string(params, "command").or_else(|| {
params
.get("command")
.and_then(Value::as_array)
.map(|parts| {
parts
.iter()
.filter_map(Value::as_str)
.collect::<Vec<_>>()
.join(" ")
})
.filter(|command| !command.trim().is_empty())
})
}
fn pending_request_questions(params: &Value) -> Vec<PendingServerRequestQuestion> {
params
.get("questions")
.and_then(Value::as_array)
.into_iter()
.flatten()
.map(|question| PendingServerRequestQuestion {
id: optional_string(question, "id").unwrap_or_default(),
header: optional_string(question, "header"),
question: optional_string(question, "question")
.or_else(|| optional_string(question, "prompt"))
.or_else(|| optional_string(question, "label")),
required: question
.get("required")
.and_then(Value::as_bool)
.unwrap_or(false),
options: question
.get("options")
.and_then(Value::as_array)
.into_iter()
.flatten()
.map(|option| PendingServerRequestOption {
label: optional_string(option, "label")
.or_else(|| optional_string(option, "value"))
.unwrap_or_default(),
description: optional_string(option, "description"),
value: option.get("value").cloned(),
is_other: option
.get("isOther")
.and_then(Value::as_bool)
.unwrap_or(false),
raw: option.clone(),
})
.collect(),
raw: question.clone(),
})
.collect()
}