一、sessions_send 工具
1.1 工具概述
功能:发送消息到其他会话核心特性:
• 支持 sessionKey 或 label 定位目标 • 支持指定 agentId(跨 agent 通信) • Agent-to-Agent 策略检查 • 会话可见性检查 • 超时控制
1.2 Schema 定义
位置:第 109348 行
constSessionsSendToolSchema = Type.Object({sessionKey: Type.Optional(Type.String()),label: Type.Optional(Type.String({minLength: 1,maxLength: 512 })),agentId: Type.Optional(Type.String({minLength: 1,maxLength: 64 })),message: Type.String(),timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 }))});1.3 完整执行代码
位置:第 109358 行
functioncreateSessionsSendTool(opts) {return {label: "Session Send",name: "sessions_send",description: "Send a message into another session. Use sessionKey or label to identify the target.",parameters: SessionsSendToolSchema,execute: async (_toolCallId, args) => {const params = args;const gatewayCall = opts?.callGateway ?? callGateway;// 1. 解析消息(必填)const message = readStringParam$1(params, "message", { required: true });// 2. 解析会话上下文const { cfg, mainKey, alias, effectiveRequesterKey, restrictToSpawned } = resolveSessionToolContext(opts);const a2aPolicy = createAgentToAgentPolicy(cfg);const sessionVisibility = resolveEffectiveSessionToolsVisibility({ cfg,sandboxed: opts?.sandboxed === true });// 3. 解析目标参数const sessionKeyParam = readStringParam$1(params, "sessionKey");const labelParam = readStringParam$1(params, "label")?.trim() || void0;const labelAgentIdParam = readStringParam$1(params, "agentId")?.trim() || void0;// 4. 检查参数冲突if (sessionKeyParam && labelParam) {returnjsonResult({runId: crypto$1.randomUUID(),status: "error",error: "Provide either sessionKey or label (not both)." }); }let sessionKey = sessionKeyParam;// 5. 通过 label 解析 sessionKeyif (!sessionKey && labelParam) {const requesterAgentId = resolveAgentIdFromSessionKey(effectiveRequesterKey);const requestedAgentId = labelAgentIdParam ? normalizeAgentId(labelAgentIdParam) : void0;// 沙盒限制if (restrictToSpawned && requestedAgentId && requestedAgentId !== requesterAgentId) {returnjsonResult({runId: crypto$1.randomUUID(),status: "forbidden",error: "Sandboxed sessions_send label lookup is limited to this agent" }); }// Agent-to-Agent 策略检查if (requesterAgentId && requestedAgentId && requestedAgentId !== requesterAgentId) {if (!a2aPolicy.enabled) {returnjsonResult({runId: crypto$1.randomUUID(),status: "forbidden",error: "Agent-to-agent messaging is disabled. Set tools.agentToAgent.enabled=true to allow cross-agent sends." }); }if (!a2aPolicy.isAllowed(requesterAgentId, requestedAgentId)) {returnjsonResult({runId: crypto$1.randomUUID(),status: "forbidden",error: "Agent-to-agent messaging denied by tools.agentToAgent.allow." }); } }// 解析 labelconst resolveParams = {label: labelParam, ...requestedAgentId ? { agentId: requestedAgentId } : {}, ...restrictToSpawned ? { spawnedBy: effectiveRequesterKey } : {} };let resolvedKey = "";try {const resolved = awaitgatewayCall({method: "sessions.resolve",params: resolveParams,timeoutMs: 1e4 }); resolvedKey = typeof resolved?.key === "string" ? resolved.key.trim() : ""; } catch (err) {const msg = err instanceofError ? err.message : String(err);if (restrictToSpawned) {returnjsonResult({runId: crypto$1.randomUUID(),status: "forbidden",error: "Session not visible from this sandboxed agent session." }); }returnjsonResult({runId: crypto$1.randomUUID(),status: "error",error: msg || `No session found with label: ${labelParam}` }); }if (!resolvedKey) {if (restrictToSpawned) {returnjsonResult({runId: crypto$1.randomUUID(),status: "forbidden",error: "Session not visible from this sandboxed agent session." }); }returnjsonResult({runId: crypto$1.randomUUID(),status: "error",error: `No session found with label: ${labelParam}` }); } sessionKey = resolvedKey; }// 6. 检查 sessionKey 是否存在if (!sessionKey) {returnjsonResult({runId: crypto$1.randomUUID(),status: "error",error: "Either sessionKey or label is required" }); }// 7. 解析会话引用const resolvedSession = awaitresolveSessionReference({ sessionKey, alias, mainKey,requesterInternalKey: effectiveRequesterKey, restrictToSpawned });if (!resolvedSession.ok) {returnjsonResult({runId: crypto$1.randomUUID(),status: resolvedSession.status,error: resolvedSession.error }); }// 8. 检查可见性const visibleSession = awaitresolveVisibleSessionReference({ resolvedSession,requesterSessionKey: effectiveRequesterKey, restrictToSpawned,visibilitySessionKey: sessionKey });if (!visibleSession.ok) {returnjsonResult({runId: crypto$1.randomUUID(),status: visibleSession.status,error: visibleSession.error,sessionKey: visibleSession.displayKey }); }const resolvedKey = visibleSession.key;const displayKey = visibleSession.displayKey;// 9. 解析超时const timeoutSeconds = typeof params.timeoutSeconds === "number" && Number.isFinite(params.timeoutSeconds) ? Math.max(0, Math.floor(params.timeoutSeconds)) : 30;const timeoutMs = timeoutSeconds * 1e3;const announceTimeoutMs = timeoutSeconds === 0 ? 3e4 : timeoutMs;// 10. 生成幂等键const idempotencyKey = crypto$1.randomUUID();let runId = idempotencyKey;// 11. 可见性检查const access = (awaitcreateSessionVisibilityGuard({action: "send",requesterSessionKey: effectiveRequesterKey,visibility: sessionVisibility, a2aPolicy })).check(resolvedKey);if (!access.allowed) {returnjsonResult({runId: crypto$1.randomUUID(),status: access.status,error: access.error,sessionKey: displayKey }); }// 12. 构建发送参数const sendParams = { message,sessionKey: resolvedKey, idempotencyKey,deliver: false, // 不直接发送到渠道channel: INTERNAL_MESSAGE_CHANNEL,lane: AGENT_LANE_NESTED,extraSystemPrompt: buildAgentToAgentMessageContext({requesterSessionKey: opts?.agentSessionKey,requesterChannel: opts?.agentChannel,targetSessionKey: displayKey }),inputProvenance: {kind: "inter_session",sourceSessionKey: opts?.agentSessionKey,sourceChannel: opts?.agentChannel,sourceTool: "sessions_send" } };// 13. 启动 A2A 流程const requesterSessionKey = opts?.agentSessionKey;const requesterChannel = opts?.agentChannel;const maxPingPongTurns = resolvePingPongTurns(cfg);const delivery = {status: "pending",mode: "announce" };conststartA2AFlow = (roundOneReply, waitRunId) => {runSessionsSendA2AFlow({targetSessionKey: resolvedKey, displayKey, roundOneReply, waitRunId, requesterSessionKey, requesterChannel, maxPingPongTurns, gatewayCall, delivery }); };// 14. 发送消息try {const result = awaitstartAgentRun({callGateway: gatewayCall, sendParams, runId,sessionKey: resolvedKey });if (!result.ok) return result.result; runId = result.runId;returnjsonResult({ runId,status: "sent",sessionKey: displayKey, delivery }); } catch (err) {const msg = err instanceofError ? err.message : String(err);returnjsonResult({runId: crypto$1.randomUUID(),status: "error",error: msg,sessionKey: displayKey }); } } };}1.4 Agent-to-Agent 策略检查
// 1. 检查是否启用if (!a2aPolicy.enabled) {returnjsonResult({status: "forbidden",error: "Agent-to-agent messaging is disabled." });}// 2. 检查是否允许if (!a2aPolicy.isAllowed(requesterAgentId, requestedAgentId)) {returnjsonResult({status: "forbidden",error: "Agent-to-agent messaging denied by policy." });}// 策略配置示例// tools.agentToAgent:// enabled: true// allow:// - "agent-a" -> "agent-b"// - "agent-a" -> "agent-c"1.5 执行流程图
sessions_send 工具调用 ↓1. 解析消息(必填) ↓2. 解析会话上下文 ↓3. 解析目标参数 ├─ sessionKey(直接指定) └─ label(通过标签查找) ↓4. 检查参数冲突(不能同时使用) ↓5. 通过 label 解析 sessionKey ├─ 沙盒限制检查 ├─ Agent-to-Agent 策略检查 └─ 调用 Gateway 解析 ↓6. 检查 sessionKey 是否存在 ↓7. 解析会话引用 ↓8. 检查可见性 ↓9. 解析超时 ↓10. 生成幂等键 ↓11. 可见性检查 ↓12. 构建发送参数 ↓13. 启动 A2A 流程 ↓14. 发送消息 ↓15. 返回结果1.6 返回结果格式
成功:
{"runId":"abc123","status":"sent","sessionKey":"main","delivery":{"status":"pending","mode":"announce"}}失败(参数冲突):
{"runId":"abc123","status":"error","error":"Provide either sessionKey or label (not both)."}失败(A2A 策略禁止):
{"runId":"abc123","status":"forbidden","error":"Agent-to-agent messaging is disabled. Set tools.agentToAgent.enabled=true to allow cross-agent sends."}二、sessions_spawn 工具
2.1 工具概述
功能:创建子 agent 会话核心特性:
• 支持两种运行时(subagent / acp) • 支持两种模式(run 单次 / session 持久) • 支持恢复已有会话(仅 ACP) • 支持附件(仅 subagent) • 支持线程绑定(仅 ACP) • 自动继承工作目录
2.2 Schema 定义
位置:第 112084 行
constSessionsSpawnToolSchema = Type.Object({task: Type.String(),label: Type.Optional(Type.String()),runtime: optionalStringEnum$3(SESSIONS_SPAWN_RUNTIMES), // "subagent" | "acp"agentId: Type.Optional(Type.String()),resumeSessionId: Type.Optional(Type.String({ description: "Resume an existing agent session by its ID (e.g. a Codex session UUID). Requires runtime=\"acp\"." })),model: Type.Optional(Type.String()),thinking: Type.Optional(Type.String()),cwd: Type.Optional(Type.String()),runTimeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),thread: Type.Optional(Type.Boolean()),mode: optionalStringEnum$3(SUBAGENT_SPAWN_MODES), // "run" | "session"cleanup: optionalStringEnum$3(["delete", "keep"]),sandbox: optionalStringEnum$3(SESSIONS_SPAWN_SANDBOX_MODES), // "inherit" | "require"streamTo: optionalStringEnum$3(ACP_SPAWN_STREAM_TARGETS), // "parent"attachments: Type.Optional(Type.Array(Type.Object({name: Type.String(),content: Type.String(),encoding: Type.Optional(optionalStringEnum$3(["utf8", "base64"])),mimeType: Type.Optional(Type.String()) }), { maxItems: 50 })),attachAs: Type.Optional(Type.Object({ mountPath: Type.Optional(Type.String()) }))});2.3 完整执行代码
位置:第 112110 行
functioncreateSessionsSpawnTool(opts) {return {label: "Sessions",name: "sessions_spawn",description: "Spawn an isolated session (runtime=\"subagent\" or runtime=\"acp\"). mode=\"run\" is one-shot and mode=\"session\" is persistent/thread-bound. Subagents inherit the parent workspace directory automatically.",parameters: SessionsSpawnToolSchema,execute: async (_toolCallId, args) => {const params = args;// 1. 检查不支持的参数const unsupportedParam = UNSUPPORTED_SESSIONS_SPAWN_PARAM_KEYS.find((key) =>Object.hasOwn(params, key) );if (unsupportedParam) {thrownewToolInputError(`sessions_spawn does not support "${unsupportedParam}". Use "message" or "sessions_send" for channel delivery.` ); }// 2. 解析必填参数const task = readStringParam$1(params, "task", { required: true });const label = typeof params.label === "string" ? params.label.trim() : "";// 3. 解析运行时const runtime = params.runtime === "acp" ? "acp" : "subagent";const requestedAgentId = readStringParam$1(params, "agentId");const resumeSessionId = readStringParam$1(params, "resumeSessionId");// 4. 解析模型和思考级别const modelOverride = readStringParam$1(params, "model");const thinkingOverrideRaw = readStringParam$1(params, "thinking");const cwd = readStringParam$1(params, "cwd");// 5. 解析模式const mode = params.mode === "run" || params.mode === "session" ? params.mode : void0;const cleanup = params.cleanup === "keep" || params.cleanup === "delete" ? params.cleanup : "keep";const sandbox = params.sandbox === "require" ? "require" : "inherit";const streamTo = params.streamTo === "parent" ? "parent" : void0;// 6. 解析超时const timeoutSecondsCandidate = typeof params.runTimeoutSeconds === "number" ? params.runTimeoutSeconds : typeof params.timeoutSeconds === "number" ? params.timeoutSeconds : void0;const runTimeoutSeconds = typeof timeoutSecondsCandidate === "number" && Number.isFinite(timeoutSecondsCandidate) ? Math.max(0, Math.floor(timeoutSecondsCandidate)) : void0;// 7. 解析线程和附件const thread = params.thread === true;const attachments = Array.isArray(params.attachments) ? params.attachments : void0;// 8. 检查运行时特定限制if (streamTo && runtime !== "acp") {returnjsonResult({status: "error",error: `streamTo is only supported for runtime=acp; got runtime=${runtime}` }); }if (resumeSessionId && runtime !== "acp") {returnjsonResult({status: "error",error: `resumeSessionId is only supported for runtime=acp; got runtime=${runtime}` }); }// 9. ACP 运行时if (runtime === "acp") {if (Array.isArray(attachments) && attachments.length > 0) {returnjsonResult({status: "error",error: "attachments are currently unsupported for runtime=acp; use runtime=subagent or remove attachments" }); }returnjsonResult(awaitspawnAcpDirect({ task,label: label || void0,agentId: requestedAgentId, resumeSessionId, cwd,mode: mode && ACP_SPAWN_MODES.includes(mode) ? mode : void0, thread, sandbox, streamTo }, {agentSessionKey: opts?.agentSessionKey,agentChannel: opts?.agentChannel,agentAccountId: opts?.agentAccountId,agentTo: opts?.agentTo,agentThreadId: opts?.agentThreadId,sandboxed: opts?.sandboxed })); }// 10. Subagent 运行时returnjsonResult(awaitspawnSubagentDirect({ task,label: label || void0,agentId: requestedAgentId,model: modelOverride,thinking: thinkingOverrideRaw, runTimeoutSeconds, thread, mode, cleanup, sandbox,expectsCompletionMessage: true, attachments,attachMountPath: params.attachAs && typeof params.attachAs === "object" ? readStringParam$1(params.attachAs, "mountPath") : void0 }, {agentSessionKey: opts?.agentSessionKey,agentChannel: opts?.agentChannel,agentAccountId: opts?.agentAccountId,agentTo: opts?.agentTo,agentThreadId: opts?.agentThreadId,agentGroupId: opts?.agentGroupId,agentGroupChannel: opts?.agentGroupChannel,agentGroupSpace: opts?.agentGroupSpace,requesterAgentIdOverride: opts?.requesterAgentIdOverride,workspaceDir: opts?.workspaceDir })); } };}2.4 运行时对比
| 运行时类型 | ||
| 附件支持 | ||
| 恢复会话 | ||
| 流式输出 | ||
| 线程绑定 | ||
| 模式 |
2.5 参数限制检查
// 1. streamTo 仅支持 ACPif (streamTo && runtime !== "acp") {returnjsonResult({status: "error",error: `streamTo is only supported for runtime=acp` });}// 2. resumeSessionId 仅支持 ACPif (resumeSessionId && runtime !== "acp") {returnjsonResult({status: "error",error: `resumeSessionId is only supported for runtime=acp` });}// 3. attachments 不支持 ACPif (runtime === "acp" && Array.isArray(attachments) && attachments.length > 0) {returnjsonResult({status: "error",error: "attachments are currently unsupported for runtime=acp" });}2.6 执行流程图
sessions_spawn 工具调用 ↓1. 检查不支持的参数 ↓2. 解析必填参数(task) ↓3. 解析运行时(subagent / acp) ↓4. 解析模型和思考级别 ↓5. 解析模式(run / session) ↓6. 解析超时 ↓7. 解析线程和附件 ↓8. 检查运行时特定限制 ├─ streamTo 仅支持 ACP ├─ resumeSessionId 仅支持 ACP └─ attachments 不支持 ACP ↓9. 根据运行时选择创建方式 ├─ ACP → spawnAcpDirect └─ Subagent → spawnSubagentDirect ↓10. 返回结果2.7 返回结果格式
成功(Subagent):
{"status":"ok","runId":"abc123","childSessionKey":"subagent:abc123","label":"My Task"}成功(ACP):
{"status":"ok","runId":"abc123","threadId":"thread_123","label":"My Task"}失败(参数错误):
{"status":"error","error":"streamTo is only supported for runtime=acp; got runtime=subagent"}三、关键机制对比
3.1 目标定位
| 目标 | ||
| 定位方式 | ||
| A2A 策略 |
3.2 运行时支持
| 运行时 | ||
| 附件 | ||
| 恢复会话 |
3.3 安全限制
| 可见性检查 | ||
| A2A 策略 | ||
| 沙盒限制 |
四、使用示例
4.1 sessions_send 工具调用
用户:发送消息到主会话
大模型返回:
{"tool_call":{"name":"sessions_send","arguments":{"sessionKey":"main","message":"请处理这个任务"}}}执行结果:
{"runId":"abc123","status":"sent","sessionKey":"main","delivery":{"status":"pending","mode":"announce"}}4.2 sessions_spawn 工具调用
用户:创建一个子 agent 来分析股票数据
大模型返回:
{"tool_call":{"name":"sessions_spawn","arguments":{"task":"分析 AAPL 股票数据","label":"股票分析","runtime":"subagent","mode":"run"}}}执行结果:
{"status":"ok","runId":"abc123","childSessionKey":"subagent:abc123","label":"股票分析"}五、相关工具
5.1 sessions_yield
位置:第 112197 行
constSessionsYieldToolSchema = Type.Object({ message: Type.Optional(Type.String()) });functioncreateSessionsYieldTool(opts) {return {label: "Yield",name: "sessions_yield",description: "End your current turn. Use after spawning subagents to receive their results as the next message.",parameters: SessionsYieldToolSchema,execute: async (_toolCallId, args) => {const message = readStringParam$1(args, "message") || "Turn yielded.";if (!opts?.sessionId) {returnjsonResult({ status: "error", error: "No session context" }); }if (!opts?.onYield) {returnjsonResult({ status: "error", error: "Yield not supported in this context" }); }await opts.onYield(message);returnjsonResult({status: "yielded", message }); } };}用途:在 spawn 子 agent 后使用,结束当前回合,接收子 agent 结果作为下一条消息。
夜雨聆风