Skip to content

feat(core): 实现 CodeModeOnly 风格的 Code Mode 程序化工具调用 #10377

Description

@LaZzyMan

What would you like to be added?

在 Qwen Code 中实现与 OpenAI Codex CodeModeOnly 等价的 Code Mode / Programmatic Tool Calling 能力:模型顶层主要只看到 exec、wait 和少量必须直调的控制面/hosted tools;普通 Qwen Code tools 被编译为 tools.* JavaScript API,由模型在受限 JavaScript runtime 中进行条件、循环、并发、过滤、聚合和输出投影。

交付顺序

本期在 Hybrid CodeMode 与 CodeModeOnly 之间选择 CodeModeOnly 先行;Hybrid CodeMode 作为后续模式。这个顺序调整只改变模型顶层工具暴露策略,不改变 exec/wait、cell/session、store/yield、多模态、provider capability、独立 runtime 或统一 ToolCallRuntime 等共同能力的需求与验收范围。

目标调用形态:

const [status, changedFiles] = await Promise.all([
  tools.exec_command({ cmd: 'git status --short' }),
  tools.exec_command({ cmd: 'git diff --name-only' }),
]);

text({
  status: status.output,
  files: changedFiles.output.trim().split('\n').filter(Boolean),
});

exec 不是 shell alias,也不是持久 Node REPL。它是一层临时、受限的工具编排 runtime:每个 cell 使用新的 JavaScript isolate;JavaScript 本身没有 Node、文件系统、网络或进程权限,只能通过 tools.* 回调 Qwen Code 已注册且允许的工具。

最终产品模式应包含:

模式 模型顶层工具面 目的
Direct 现有直接工具 完整兼容和回滚基线
CodeMode exec、wait 与直接工具并存 灰度、调试和模型适配
CodeModeOnly exec、wait、最小 direct-only allowlist 收敛 schema、强制普通工具走程序化编排

CodeModeOnly 不能只靠 system prompt 中的一句“请使用 exec”实现。必须在每轮 provider request 构建时隐藏 code-mode eligible 顶层 schemas,同时保留真实 runtime registry,用于嵌套调用的参数校验、权限、审批、hooks 和执行。

需求与实现要求

1. 模型可见协议

  • 新增 exec 和 wait 两个控制工具。

  • provider 支持 custom/freeform tool 时,exec 接受 raw JavaScript;第一行可选严格 pragma:

    // @exec: {"yield_time_ms": 30000, "max_output_tokens": 1000}
  • 只支持普通 function calling 的 provider 使用严格兼容入口 exec({source: string});两种入口进入同一个内部 CodeExecRequest 和 runtime,不维护两套执行语义。

  • wait 使用普通 JSON schema,至少包含 cell_id、yield_time_ms?、max_tokens? 和 terminate?。

  • exec、wait 不得再次出现在 tools.* 中,禁止递归控制面。

  • tool name 必须确定性规范化为 JavaScript identifier;namespace 使用稳定分隔规则;碰撞 first-wins 并产生可观察 warning,不能静默覆盖。

  • 在 CodeMode 中,普通直接工具保留顶层 schema,并在 description 中增加对应 tools.<name> TypeScript declaration。

  • 在 CodeModeOnly 中,code-mode eligible 顶层 schemas 被隐藏,其 description、input/output schema 被编译为 exec.description 内的 TypeScript declarations。

  • DirectModelOnly、交互/审批控制面、协作工具、provider hosted tools 和显式 direct-only namespaces 使用最小 allowlist 保持直调,并且不进入 tools.*。

2. 独立安全 runtime

  • 生产实现使用独立 qwen-code-mode-host 进程;每个 cell 使用 fresh isolate。
  • JavaScript runtime 不提供 Node、process、require、Buffer、文件系统、网络、console、静态/动态 import、Atomics、SharedArrayBuffer 或 WebAssembly。
  • 提供受控 helpers:text、image、audio、generatedImage、store/load、notify、setTimeout/clearTimeout、yield_control、exit 和只读 ALL_TOOLS。
  • module 顶层 promise 决定 cell 生命周期;未 await promise、timer 和 nested tool call 不得保持 cell 存活。
  • 必须支持同步死循环终止、内存/CPU/输出/并发/frame hard limits,以及 host crash 后的确定性撤销。
  • 现有 workflow-sandbox / node:vm 只能用于 local protocol double,不能作为生产安全边界或静默 fallback。
  • host 缺失、版本不匹配、握手失败或安全 capability 不满足时 fail closed。

3. 统一、可重入的工具执行链

当前 CoreToolScheduler 在已有工具执行期间会排队新的 schedule;如果父 exec 等待子调用,而子调用再次进入同一个 scheduler,会形成死锁。实现前必须抽取统一、可重入的单工具 ToolCallRuntime。

顶层直接调用和 tools.* 嵌套调用必须共享同一执行政策:

  1. canonical name/exposure 校验;
  2. registry lookup;
  3. JSON/freeform input 校验;
  4. permission policy;
  5. approval mode 和用户确认;
  6. PreToolUse hook;
  7. 实际 tool invocation;
  8. PostToolUse success/failure hook;
  9. output shaping/truncation;
  10. transcript、telemetry 和 TUI/ACP lifecycle events。

不得从 exec 直接调用 tool.build(...).execute(...),也不得复制一套简化权限链。Core、headless、ACP daemon 和 subagent 必须通过同一 runtime 获得一致行为。

4. session、cell 与控制面

  • 一个逻辑 Qwen chat session 持有一个 code-mode session;多个 cell 不共享 JS heap,但通过 store/load 共享 JSON serializable state。
  • cell 启动时读取 store 快照;只有成功完成才原子提交 pending writes;failed、cancelled、terminated cell 不提交。
  • exec 达到初始等待上限后返回 running cell ID;wait 只返回上次 yield 之后的增量输出。
  • 一个 cell 同时只允许一个 active observer;第二个 wait 返回 busy。
  • terminal result 被领取后 cell 关闭;旧 cell 在 host restart 后明确返回 missing。
  • parent turn cancel 必须终止 isolate、取消所有 child calls,并清理 process/thread/timer 资源。
  • chat compaction 不应重建运行中的 code-mode session;session dispose、/clear、daemon detach 和进程退出必须有明确 shutdown 语义。

5. nested tool dispatch 与 UI

  • 每个 nested call 携带 parentCallId、cellId 和 runtimeToolCallId。
  • parent exec 保持 executing;需要审批的 child 单独进入 awaiting approval,用户决策只作用于 child。
  • TUI、headless stream 和 ACP 都能观察 parent/child 生命周期,不生成第二套互不兼容的 nesting metadata。
  • nested tools 可以并发,但必须受全局 tool concurrency 与 code-mode per-cell cap 的共同限制。
  • parent module 完成后,未 await children 被取消;迟到 callback 不能污染后续 cell。
  • wait 是 control-plane,不触发普通工具的 pre/post hooks;真正 nested tool 仍完整触发。

6. 输出、多模态与 transcript

  • nested tool result 先经过现有 per-tool truncation/spill;JavaScript 选择 text/image/audio 最终输出后,再应用 max_output_tokens 的 token-aware budget。
  • 支持多段文本、图片、音频和 MCP CallToolResult 内容提升;禁止用远程 HTTP URL 绕过媒体边界。
  • 输出状态明确区分 completed、failed、running 和 terminated;Script completed 不能被解释为用户任务成功。
  • transcript 保存本地 exec source、parent/child 关联和 terminal status;默认 telemetry 不发送 raw source、tool args/output、stored values 或环境信息。
  • running cell 无法跨进程恢复时,resume/replay 必须显示 terminated/missing,不能重新执行或伪装继续运行。

7. provider、配置与兼容

  • provider adapter 显式声明 structured exec、custom/freeform exec 和 hosted tool capabilities,禁止根据模型名猜测 wire support。
  • modelInfo.toolMode 可以声明 direct、code_mode 或 code_mode_only;没有声明时才读取 feature/config。
  • 首次交付默认关闭,并先提供 CodeModeOnly;Hybrid CodeMode 作为后续模式。CodeModeOnly 默认候选仍需模型质量、安全、原生包和回滚门槛全部通过后再评估。
  • Direct 模式必须始终可作为显式 rollback;不得在 host/runtime 失败时静默回退到同进程 eval。
  • custom/freeform capability 必须通过真实 provider request/response 验证,HTTP 200 但未产生可执行 tool call 不算通过。

技术方案

Provider adapter
  -> CodeExec request normalizer
  -> exec / wait control tools
  -> CodeModeSessionManager
  -> CodeModeHostClient
  -> framed stdio IPC
  -> qwen-code-mode-host
  -> fresh sandboxed JavaScript isolate
  -> nested callback
  -> NestedToolDispatcher
  -> unified ToolCallRuntime
  -> registry / permission / approval / hooks
  -> built-in, MCP and extension tools
  -> output / transcript / TUI / ACP

建议组件:

  • CodeExecRequestNormalizer:统一 raw custom input 与 {source} function input,解析 pragma 并施加 hard limits。
  • ToolExposurePlanner:从 ToolRegistry 编译 direct、deferred、code-mode、control surfaces,不改变真实注册。
  • ToolCallRuntime:抽取无 UI 状态的单调用政策,供 Core、ACP、headless 和 nested dispatch 共用。
  • NestedToolDispatcher:处理 host callback、并发、取消、审批和 parent/child metadata。
  • CodeModeSessionManager:管理 session/cell/store 快照和 terminal commit。
  • CodeModeHostClient:懒启动一个匹配版本的 host,完成 handshake、framing、limits 和 crash generation。
  • qwen-code-mode-host:独立原生进程,负责 isolate、helpers、cell actor 和 terminate。

协议第一版建议使用 length-prefixed JSON frame,至少包含 versioned handshake、request ID、session ID、cell ID、nested callback、yield、result、terminate 和 structured error。禁止使用 newline JSON 承载任意 source/output。

Roadmap

Phase 0 — 决策门与 threat model

  • 批准 runtime 选型、平台矩阵、包体成本、license/NOTICE/SBOM、签名和分发路径。
  • 固化攻击面、信任边界、资源上限和 fail-closed 行为。
  • 明确首期 CodeModeOnly,Hybrid CodeMode 作为后续 milestone。

Phase 1 — 统一 ToolCallRuntime

  • 从 CoreToolScheduler 和 ACP Session.runTool 抽取 behavior-preserving 单调用 runtime。
  • Core、ACP、headless、subagent golden matrix 全绿。
  • 本阶段不增加 exec,避免把权限重构与新 runtime 回归混在一起。

Phase 2 — protocol 与 local double

  • 完成 exec parser、tool exposure compiler、cell/store/output contracts 和 TS/Rust protocol fixtures。
  • 用 fake host 或现有 workflow runtime 做协议测试,明确标记为 local double,不宣称生产安全。

Phase 3 — native host

  • 实现 framed IPC、handshake、session/cell、fresh isolate、helpers、callbacks、terminate 和资源上限。
  • 完成 escape corpus、DoS、IPC fuzz、crash/restart 和跨 session 隔离测试。

Phase 4 — CodeModeOnly

  • 隐藏 code-mode eligible 顶层 schemas,将其声明集中编译进 exec.description。
  • 保留经过审计的最小 direct-only allowlist;普通工具通过共享的 tools.* runtime 调用。
  • 接通 TUI、headless、ACP、subagent、permissions、approvals、hooks、MCP、extensions、多模态、cell/session helpers 和 transcript。
  • provider 先使用 structured exec({source});feature 默认关闭,内部灰度。

Phase 5 — native freeform provider

  • 只为经过真实探针确认的 provider 启用 raw custom/freeform exec。
  • 记录 route、model、tool type、response type、nested outcome、HTTP status 和 CLI exit code。

Phase 6 — Hybrid CodeMode

  • 增加 direct tools 与 exec/wait 并存的 Hybrid CodeMode,复用与 CodeModeOnly 相同的 runtime、cell/session、helpers、输出和安全语义。
  • 在固定模型/平台灰度,对比 Direct、Hybrid、CodeModeOnly 的任务质量、token、往返、审批和 runtime failure。
  • 门槛通过后才考虑 CodeModeOnly 默认候选;默认迁移和旧工具面移除需要独立批准。

验收标准

模型工具面

  • 本期 Direct 与 CodeModeOnly 的 model-visible specs 有精确 golden tests。
  • CodeModeOnly 中普通 code-mode eligible schemas 不再顶层可见,但能通过 tools.* 正常调用。
  • exec.description 包含确定性 TypeScript declarations;schema 更新和 deferred reveal 不产生竞态或 prompt cache 随机漂移。
  • exec/wait 不可递归;direct-only/hosted allowlist 最小且有测试。
  • Hybrid CodeMode 的 model-visible spec 与 direct + exec/wait 并存 golden tests 在 Phase 6 交付;只有 Hybrid 工具暴露模式后移,其他共同能力不随之延期。

权限与正确性

  • TUI、headless、ACP、subagent 的 allow/deny/ask、PreToolUse/PostToolUse、MCP 和 extension 行为一致。
  • parent exec 等待 nested call 不死锁,child 并发和取消上限生效。
  • denied child 不执行;审批只出现一次;用户取消不提交 store。
  • output、multimodal、notify、yield/wait、terminate、busy/missing cell 和 transcript replay 语义通过。

安全与原生分发

  • 生产 runtime 是独立、无 Node capability 的 sandboxed host;没有 node:vm production fallback。
  • JS escape corpus、CPU/memory/output flood、oversized/invalid frame、host crash 和跨 session isolation 测试通过。
  • host missing、wrong version、handshake failure、crash 和 unsupported capability 全部 fail closed。
  • npm、standalone、Desktop/VSIX 实际支持矩阵的 install、launch、handshake、terminate、签名和 hash 检查通过。

真实 E2E 与指标

  • 至少一个真实 provider 的 structured exec E2E 通过。
  • 若声明 raw custom/freeform 支持,至少一个真实 provider 的 freeform E2E 通过。
  • bundled CLI 的同步完成、yield/wait、approval、deny、cancel、host failure 场景通过。
  • 对固定任务集分别报告 task score、model requests、tool calls、wall time、tokens、approval count、invalid exec rate 和 host/runtime failure rate。
  • Script completed 与 task success 分开统计。
  • 有单开关 rollback 到 Direct,且不需要修复 transcript 或卸载 host。

Why is this needed?

Qwen Code 当前把大量普通工具 schema 直接发送给模型,复杂任务中的循环、分支、过滤和聚合也需要多轮模型—工具往返。Code Mode 可以把确定性编排留在本地,只把必要结果投影回模型上下文,从而:

  • 收敛首轮工具 schema 和 prompt cache 表面;
  • 减少模型与工具之间的往返次数和中间 token;
  • 允许动态 fan-out、并发和条件调用,而不需要为每个流程新增专用工具;
  • 让内置工具、MCP、extensions 通过统一 tools.* API 被程序化组合;
  • 保持权限、审批、hooks 和 sandbox 的宿主控制权,而不是给 JavaScript 直接系统权限;
  • 为针对 Programmatic Tool Calling 训练或优化的 Qwen 模型提供稳定 Harness 接口。

这与 #9333/#9335 的持久 Node REPL 路线互补:Node REPL 用于持久对象和领域 SDK;本 issue 的 Code Mode 用于临时程序编排 Qwen Code 已注册工具。首版不合并两条 runtime,也不允许 exec import Node packages。

Additional context

详细设计与证据

非目标

  • 不把本 issue 实现为 shell alias、同进程 eval、new Function 或生产 node:vm。
  • 不实现持久 Node REPL、npm package import、SDK 宿主或 globalThis.qwenSession;这些属于不同 runtime。
  • 不让 JavaScript 直接访问 cwd、环境变量、文件系统或网络。
  • 不在首个实现阶段默认启用 CodeModeOnly,也不静默删除现有 direct tools。
  • 不在 runtime 失败时静默回退到不安全执行路径。
  • 不复制或分发 Codex 平台二进制;实现需完成独立 license/NOTICE 和供应链评审。
  • 本 issue 定义路线图和最终验收,不授权自动创建、拆分或合并任何 PR;每个实际 PR 的 repository、base、head 和 scope 需要单独确认。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions