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.* 嵌套调用必须共享同一执行政策:
- canonical name/exposure 校验;
- registry lookup;
- JSON/freeform input 校验;
- permission policy;
- approval mode 和用户确认;
- PreToolUse hook;
- 实际 tool invocation;
- PostToolUse success/failure hook;
- output shaping/truncation;
- 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 默认候选;默认迁移和旧工具面移除需要独立批准。
验收标准
模型工具面
权限与正确性
安全与原生分发
真实 E2E 与指标
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
详细设计与证据
- Codex CodeModeOnly 逆向分析与 Qwen Code 技术实现设计(Know How,可能需要 Alibaba 内网访问)
- 上游参考基线:OpenAI Codex
0.150.1 / rust-v0.150.1 / commit 90854393966b21e9ebfd21b122334eb09a20c93d
- Qwen Code 分析基线:
0.20.0 / commit 4af784d2bf221e263c9d66a94b9fa17fc6efdc05
非目标
- 不把本 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 需要单独确认。
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 等共同能力的需求与验收范围。目标调用形态:
exec不是 shell alias,也不是持久 Node REPL。它是一层临时、受限的工具编排 runtime:每个 cell 使用新的 JavaScript isolate;JavaScript 本身没有 Node、文件系统、网络或进程权限,只能通过tools.*回调 Qwen Code 已注册且允许的工具。最终产品模式应包含:
exec、wait与直接工具并存exec、wait、最小 direct-only allowlistCodeModeOnly不能只靠 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。process、require、Buffer、文件系统、网络、console、静态/动态 import、Atomics、SharedArrayBuffer或WebAssembly。text、image、audio、generatedImage、store/load、notify、setTimeout/clearTimeout、yield_control、exit和只读ALL_TOOLS。workflow-sandbox/node:vm只能用于 local protocol double,不能作为生产安全边界或静默 fallback。3. 统一、可重入的工具执行链
当前
CoreToolScheduler在已有工具执行期间会排队新的schedule;如果父exec等待子调用,而子调用再次进入同一个 scheduler,会形成死锁。实现前必须抽取统一、可重入的单工具ToolCallRuntime。顶层直接调用和
tools.*嵌套调用必须共享同一执行政策:不得从
exec直接调用tool.build(...).execute(...),也不得复制一套简化权限链。Core、headless、ACP daemon 和 subagent 必须通过同一 runtime 获得一致行为。4. session、cell 与控制面
store/load共享 JSON serializable state。exec达到初始等待上限后返回 running cell ID;wait只返回上次 yield 之后的增量输出。/clear、daemon detach 和进程退出必须有明确 shutdown 语义。5. nested tool dispatch 与 UI
parentCallId、cellId和runtimeToolCallId。exec保持 executing;需要审批的 child 单独进入 awaiting approval,用户决策只作用于 child。wait是 control-plane,不触发普通工具的 pre/post hooks;真正 nested tool 仍完整触发。6. 输出、多模态与 transcript
text/image/audio最终输出后,再应用max_output_tokens的 token-aware budget。CallToolResult内容提升;禁止用远程 HTTP URL 绕过媒体边界。Script completed不能被解释为用户任务成功。7. provider、配置与兼容
structured exec、custom/freeform exec和 hosted tool capabilities,禁止根据模型名猜测 wire support。modelInfo.toolMode可以声明direct、code_mode或code_mode_only;没有声明时才读取 feature/config。技术方案
建议组件:
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
Phase 1 — 统一 ToolCallRuntime
CoreToolScheduler和 ACPSession.runTool抽取 behavior-preserving 单调用 runtime。exec,避免把权限重构与新 runtime 回归混在一起。Phase 2 — protocol 与 local double
Phase 3 — native host
Phase 4 — CodeModeOnly
exec.description。tools.*runtime 调用。exec({source});feature 默认关闭,内部灰度。Phase 5 — native freeform provider
exec。Phase 6 — Hybrid CodeMode
exec/wait并存的 Hybrid CodeMode,复用与 CodeModeOnly 相同的 runtime、cell/session、helpers、输出和安全语义。验收标准
模型工具面
tools.*正常调用。exec.description包含确定性 TypeScript declarations;schema 更新和 deferred reveal 不产生竞态或 prompt cache 随机漂移。exec/wait不可递归;direct-only/hosted allowlist 最小且有测试。exec/wait并存 golden tests 在 Phase 6 交付;只有 Hybrid 工具暴露模式后移,其他共同能力不随之延期。权限与正确性
安全与原生分发
node:vmproduction fallback。真实 E2E 与指标
Script completed与 task success 分开统计。Why is this needed?
Qwen Code 当前把大量普通工具 schema 直接发送给模型,复杂任务中的循环、分支、过滤和聚合也需要多轮模型—工具往返。Code Mode 可以把确定性编排留在本地,只把必要结果投影回模型上下文,从而:
tools.*API 被程序化组合;这与 #9333/#9335 的持久 Node REPL 路线互补:Node REPL 用于持久对象和领域 SDK;本 issue 的 Code Mode 用于临时程序编排 Qwen Code 已注册工具。首版不合并两条 runtime,也不允许
execimport Node packages。Additional context
详细设计与证据
0.150.1/rust-v0.150.1/ commit90854393966b21e9ebfd21b122334eb09a20c93d0.20.0/ commit4af784d2bf221e263c9d66a94b9fa17fc6efdc05非目标
eval、new Function或生产node:vm。globalThis.qwenSession;这些属于不同 runtime。