跳到主要内容

第 9 章:Agent 后端与协议适配

9.1 Multica 为什么不自己统一模型 API

Claude Code、Codex、Cursor、Pi 等工具已经各自实现了:

  • 模型调用与 Provider 鉴权;
  • Agent Loop;
  • 文件、Shell、搜索等工具;
  • Prompt/Context 发现;
  • Session 与 Resume;
  • 权限和 Sandbox;
  • 流式事件与用量。

Multica 选择适配它们的 CLI 协议,而不是在 Server 重建所有能力。这样用户可继续使用本机登录态和工具生态,但 Multica 必须把 17 套启动参数、事件格式和失败语义压到一个接口。

9.2 统一接口

事实源在 server/pkg/agent/agent.go

type Backend interface {
Execute(ctx context.Context, prompt string, opts ExecOptions) (*Session, error)
}

type Session struct {
Messages <-chan Message
Result <-chan Result
}

设计含义:

  • Execute 的同步错误只表示“无法开始 Session”;
  • 运行期事件走 Messages
  • 最终恰好一个 Result
  • Message Channel 先关闭,Result 后送达;
  • Daemon 不需要知道 Provider 如何读取 stdout、JSON-RPC 或文件 Transcript。

9.3 统一消息模型

七种消息类型:

类型主要字段用途
textContent用户可见增量/文本
thinkingContent推理摘要或 Provider 暴露的思考
tool-useTool / CallID / Input工具开始
tool-resultTool / CallID / Output工具结束
statusStatus / SessionID生命周期、早期 Session Pin
errorContent结构化运行错误
logLevel / Content诊断,不一定给普通用户

统一不等于“每个 Provider 都能填满全部字段”。Adapter 尽力映射,缺失能力保留为空,而不是伪造事件。

9.4 统一终态

Result 包含:

  • Status:completed/failed/aborted/timeout/cancelled;
  • Output:Adapter 选择的最终用户可见答案;
  • Error:失败说明;
  • DurationMs
  • SessionID
  • 按 Model 汇总的 Token Usage;
  • ResumeRejected:是否有正面证据表明续接被拒绝。

Token Usage 统一为 Input、Output、Cache Read、Cache Write;Grok 等 Provider 还可直接报告精确 Cost Ticks,避免用聚合 Token 重新估价时丢失请求级计费规则。

9.5 ExecOptions 是跨 Provider 的能力上限

主要选项:

  • Cwd / Model / SystemPrompt / ThreadName
  • MaxTurns / Timeout
  • SemanticInactivityTimeout / IdleWatchdogTimeout / HandshakeTimeout
  • ResumeSessionID / ResumeExpected
  • ExtraArgs / CustomArgs
  • McpConfig
  • ThinkingLevel / ServiceTier
  • OpenclawMode
  • ClaudeSettingsPath

并非所有 Provider 都支持每项。接口采用“支持者消费,不支持者忽略”的渐进演进策略,避免新增一个 Codex Tier 让所有 Adapter 同时改签名。

9.6 当前 17 个 Provider

SupportedTypes 是当前快照的权威白名单:

claude, codebuddy, codex, copilot, opencode, deveco,
openclaw, hermes, pi, cursor, kimi, kiro, antigravity,
qoder, traecli, grok, qwen

它还必须与 runtime_profile.protocol_family 的数据库 CHECK 同步。仅在 Factory 增加 Case,而不扩迁移,会导致自定义 Runtime Profile 无法保存。

9.7 协议家族

可以按运输协议分组:

家族Provider启动/事件形状
Stream JSONClaude、CodeBuddy、Qwen、CursorCLI 输出 JSONL/stream-json,逐行映射消息
App Server JSON-RPCCodexcodex app-server --listen stdio://,初始化、Thread、Turn、通知
ACPHermes、Kimi、Kiro、Qoder、Trae、Grokstdio JSON-RPC,session/newsession/loadsession/prompt
Run JSONOpenCode、DevEcorun --format json 等 NDJSON Event
独立 JSONCopilot、OpenClaw、Pi各自 JSON/JSONL 模式
Transcript/非交互Antigravity启动非交互命令并读取 Conversation Transcript

Provider 同属一族也不代表参数完全相同。ACP Client 可复用,但认证、Model 设置、MCP Capability、Tool 名和 Resume 行为仍由各 Adapter 包装。

9.8 Stream JSON Adapter 的通用模式

以 Claude/CodeBuddy/Qwen 为例:

  1. 构造受控参数,指定非交互与机器可读输出;
  2. 启动子进程并获取 stdout/stderr;
  3. Scanner 逐行 JSON Decode;
  4. 按 Event Type 映射 Text/Thinking/Tool/Usage;
  5. 尽早发带 Session ID 的 Status;
  6. 解析最终 Result;
  7. Wait 子进程并区分 Exit Code、Context Cancel 与协议失败。

常见防线:

  • 单行可能很大,需要扩大 Scanner Buffer;
  • 坏 JSON 行记录受限诊断,不应泄漏整个 Prompt/凭据;
  • stdout 是协议,不把普通日志混进去;
  • stderr 进入 Log/Error,但不直接当最终用户输出;
  • Channel 必须在所有返回分支关闭。

9.9 Codex App Server

Codex Adapter 不是简单 codex -p

  • 启动 codex app-server
  • 在 stdio 上做 JSON-RPC Handshake;
  • 创建或 Resume Thread;
  • 启动 Turn;
  • 读取 Item/Turn Notifications;
  • 归并 Text、Reasoning、Command/Tool 与 Usage;
  • 支持独立 Handshake Timeout、First-turn Semantic Progress Watchdog;
  • 明确识别 Resume Rejection;
  • 若预期续接却被迫 Fresh,向用户发 Continuity Notice。

MCP 通过任务级 Codex Config Block 管理,而不是盲目把任意 --mcp-config 参数拼到 App Server 后。

Codex 还要保证进程树被完整回收,尤其在 Windows 上,父进程退出不代表继承 stdout 的子进程关闭。

9.10 ACP 家族

ACP Adapter 大体遵循:

关键不是方法名,而是 Capability Negotiation:某 Provider 不支持的 Transport、Model 或 MCP 字段不能硬塞,否则 session/new 直接失败。

Resume 时若 session/loadset_modelprompt 明确返回 Session Not Found,Adapter 设置 ResumeRejected=true,Daemon 才有理由 Fresh Retry。

9.11 Resume Rejection 为什么必须是“正面证据”

网络断开、限流、鉴权失败、Provider 5xx 都不应该自动清除 Session;Fresh Session 不会治好这些问题,还会丢失对话连续性。

所以 ResumeRejected 的语义是:

Provider 明确证明指定 Session 不存在、不可加载或属于不兼容身份。

false 对部分 Provider 只表示“无法判断”。当前显式列为不可检测的有:

  • Antigravity;
  • Copilot;
  • Cursor;
  • DevEco;
  • OpenCode。

新 Provider 默认按“应该可检测”处理:若不实现,就不会自动 Fresh Retry。这是一种 Fail Closed,避免猜测性重跑。

9.12 Custom Args 的信任边界

用户可给 Agent 配 custom_args,但不能覆盖 Multica 管理的协议参数:

  • 输出格式;
  • Prompt 输入;
  • Resume Session;
  • Permission/Approval Mode;
  • App Server Transport;
  • MCP Config;
  • Cwd/Model 等关键字段。

每个 Adapter 定义 Blocked Standalone/Blocked With Value 参数并过滤冲突。否则用户一个 --output-format text 就会让 Daemon 的 JSON Parser 全部失效,或用另一个 --resume 绕过 Session 选择。

LaunchHeader 给 UI 展示“自定义参数追加在哪个命令骨架后”,但不会泄露完整内部参数或环境。

9.13 Process 生命周期

Adapter 的 Context 同时承载:

  • 用户取消;
  • Daemon Shutdown;
  • 可选 Hard Timeout;
  • Semantic/Idle Watchdog 触发。

runContext 的规则是:Timeout <= 0 不设墙钟 Deadline,让持续有事件的长任务由 Watchdog 管理。否则一个健康长任务会仅因耗时被杀。

进程停止要考虑:

  • 关闭 stdin;
  • 终止子进程组/Job Object;
  • WaitDelay 防 Pipe 被后代进程永久持有;
  • Drain 已到达的 stdout;
  • 区分主动 Cancel 与进程异常退出。

9.14 Idle 与 Semantic Inactivity

“没有任何 stdout”与“有日志但没有语义进展”不同:

  • Idle Watchdog:一段时间没有 Message;
  • Tool Watchdog:工具仍在运行时使用更宽窗口;
  • Semantic Inactivity:Codex 等可能持续发心跳/低价值事件,却没有 Text/Tool/Turn 进展;
  • Handshake Timeout:协议还没建立;
  • Hard Timeout:明确配置的总时限。

失败分类不同,重试和 Session Poison 策略也不同。

9.15 最终输出选择

Provider Stream 可能包含:

  • 多个 Assistant Text Chunk;
  • Tool Narration;
  • 临时 Status;
  • 最终 Result Field;
  • stderr Warning;
  • Fallback Message。

Adapter 要选择稳定的用户输出,而不是简单“最后一行 stdout”。例如:

  • 优先结构化 Final Result;
  • 若缺失,按 Provider 规则收集最后 Assistant Message;
  • Tool Trace 不自动变成 Comment;
  • Fallback 输出可能被标为会话毒化;
  • 超大输出在 Server 端用固定安全提示替代,而不是截取可能包含执行细节的开头。

9.16 用量归一化

不同 Provider 报告:

  • 单次或累计 Token;
  • Model 名或 Alias;
  • Cache Read/Write;
  • Premium Request;
  • Provider 直接成本。

Adapter 先归一为 map[model]TokenUsage,Daemon 再汇报 Server。Server 结合模型价格表和 Authoritative Cost 写 task_usage,并生成 Prometheus Counter。

不能假设所有 Provider 都有 Token;缺失应显示为未知,而不是 0 成本的确定事实。

9.17 怎样增加一个 Provider

至少要完成:

  1. 新建 Backend,实现 Channel 关闭和唯一 Result 约定;
  2. SupportedTypesNew()LaunchHeader 注册;
  3. 扩 Runtime Profile 数据库 CHECK Migration;
  4. Daemon 探测默认命令与版本;
  5. execenv 选择 Brief/Skill/MCP 注入方式;
  6. 前端 Provider 元数据、图标、Model/Thinking Capability;
  7. 明确 Resume、MCP、Usage、取消、Windows 行为;
  8. 加入 Stream Fixture 与协议测试;
  9. 验证 Custom Args 不能覆盖托管参数;
  10. 更新文档与发布资产。

只让 Factory 能创建对象,不代表 Provider 已完整接入产品。

9.18 本章结论

Multica 的统一 Agent 层不是抹平差异,而是建立最小公共协议:

  • Prompt 和 Options 进入;
  • 统一 Message Stream 与单一 Result 出来;
  • Session、Usage、Resume Rejection 作为显式能力;
  • 进程、参数和协议解析留在 Adapter;
  • Daemon 只处理跨 Provider 一致的生命周期。

下一章继续回答:Prompt 与几百行任务上下文究竟怎样到达这些不同 Provider,并在后续 Task 中保持会话连续性。


上一章:执行环境、仓库、本地目录与 GC
下一章:Prompt、上下文与会话连续性