code-mode 协议契约类型
这一阶段是幕后打地基的公共协议,不直接跑代码,而是先把“大家怎么说话”定好。lib.rs 像总入口,把常用东西摆到门口。description.rs 给工具写说明,并检查 exec 的特殊配置。response.rs 规定结果里文字、图片怎么装。runtime.rs 规定运行代码、拿输出的消息格式。session.rs 则定下会话从创建、执行、等待到停止的整套规矩,让本地或远程执行器都能按同一份合同配合。
Crate 接口
crate 根建立公共 API 和规范工具名称,用于组织协议契约的其余部分。
code-mode-protocol/src/lib.rs源码 ↗
可以把这个文件理解成一家商店的前台。真正的货物放在 description、response、runtime、session 这些后屋里,但外人不需要自己跑进每个房间找东西,只要从这里拿就行。它先声明了几个内部模块,然后用 pub use 把常用的工具说明、运行请求、等待结果、会话类型、响应内容等公开出来。这样做的好处是:外部使用者只需要依赖这个库的统一入口,不会被内部目录结构绑死。文件最后还定义了两个公开工具名:PUBLIC_TOOL_NAME 是 "exec",表示执行工具;WAIT_TOOL_NAME 是 "wait",表示等待工具。这些名字像约定好的按钮标签,其他系统按这个名字调用对应能力,避免大家各叫各的导致对不上。
工具描述
这些定义描述代码模式工具本身,包括人类可读的元数据、嵌套声明,以及可执行源码的 pragma 解析。
code-mode-protocol/src/description.rs源码 ↗
代码模式允许模型写一小段 JavaScript 来调用其他工具。这个文件就像工具箱里的“标签打印机”:它把每个工具叫什么、怎么传参数、会返回什么,整理成清楚的文字说明和 TypeScript 类型提示(TypeScript 是给 JavaScript 加类型说明的一种写法)。这样模型不容易把工具名写错,也不容易传错参数。它还认识 exec 输入第一行的 // @exec: 配置,用来控制脚本多久先交还结果、最多输出多少内容。文件里也有一套 JSON Schema 到 TypeScript 的简化翻译器:JSON Schema 是描述数据长什么样的规则,它会被翻译成更适合写代码时阅读的类型。最后,测试覆盖了说明生成、工具名规范化、MCP 工具返回值识别等关键行为,防止这些“说明书”悄悄变错。
parse_exec_source163–245 ↗
fn parse_exec_source(input: &str) -> Result<ParsedExecSource, String>
作用:解析传给 exec 的 JavaScript 源码,顺便读出第一行可选的 // @exec: 配置。它用来保证输入不是空的,配置是合法 JSON,并且时间和输出上限不是 JavaScript 不能安全表示的超大整数。
数据流:进去的是一整段用户输入的文字。它先检查是不是空;再看第一行有没有 // @exec:;如果没有,就原样把代码交出去;如果有,就把后面的 JSON 配置读成 yield_time_ms 和 max_output_tokens,并把真正的 JavaScript 代码改成剩余行。出来的是 ParsedExecSource,里面有代码和两个可选限制;遇到格式不对则出来一段给人看的错误消息。
调用关系:这是 exec 工具真正运行脚本前的门卫。它自己只调用 JSON 解析和格式化错误消息,不负责执行代码;后续执行器会拿它产出的代码和限制去启动脚本。测试里分别验证了有配置和没配置两种入口。
调用图:外部调用 3 个(format!, from_str, from_value)。
is_code_mode_nested_tool247–249 ↗
fn is_code_mode_nested_tool(tool_name: &str) -> bool
作用:判断一个工具名是不是代码模式里的“嵌套工具”。嵌套工具就是 exec 脚本里面可以通过 tools.xxx(...) 再调用的工具,而不是公开的 exec 工具本身,也不是 wait 工具。
数据流:进去的是一个工具名字符串。它把这个名字和公开工具名、wait 工具名各比较一次。出来的是布尔值:不是这两个特殊工具就返回 true,否则返回 false。
调用关系:它是筛选工具清单的小判断函数。其他模块可以用它决定哪些工具应该暴露到 JavaScript 的全局 tools 对象上。
build_exec_tool_description251–318 ↗
fn build_exec_tool_description(
enabled_tools: &[ToolDefinition],
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
code_mode_only: bool,
deferred_tools_available: b
作用:生成 exec 工具展示给模型看的完整说明。它会说明 exec 怎么运行 JavaScript,也会在需要时把可用的嵌套工具逐个列出来。
数据流:进去的是已启用工具列表、命名空间说明、是否只处于代码模式、以及是否有延迟加载工具。它先放入固定的 exec 使用说明;如果有延迟工具,就追加提醒;如果需要列出嵌套工具,就按工具顺序生成每个工具的标题和类型示例,还会把同一命名空间的说明只插入一次。出来的是一整段 Markdown 风格的说明文字。
调用关系:它是说明书的总装配工。它会调用 render_code_mode_sample_for_definition 生成每个工具的调用示例,调用 normalize_code_mode_identifier 把工具名变成 JavaScript 合法名字,调用 render_tool_heading 做标题。多组测试会直接调用它,确认嵌套工具、命名空间、MCP 类型和延迟工具提示都按预期出现。
调用图:调用 3 个内部函数(normalize_code_mode_identifier, render_code_mode_sample_for_definition, render_tool_heading);被 6 处调用(code_mode_only_description_groups_namespace_instructions_once, code_mode_only_description_includes_nested_tools, code_mode_only_description_omits_empty_namespace_sections, code_mode_only_description_renders_shared_mcp_types_once, exec_description_mentions_deferred_nested_tools_when_available, exec_description_mentions_timeout_helpers);外部调用 6 个(new, with_capacity, is_empty, iter, len, format!)。
build_wait_tool_description320–322 ↗
fn build_wait_tool_description() -> &'static str
作用:返回 wait 工具的固定说明。wait 用来继续等待一个还没跑完的 exec 脚本。
数据流:它不需要输入。它直接把文件里写好的 wait 使用说明返回出去,没有额外计算,也不会改动任何状态。
调用关系:它是一个简单的说明出口。工具注册或展示说明时会用它拿到 wait 的文字;复杂的拼装工作不在这里做。
normalize_code_mode_identifier324–346 ↗
fn normalize_code_mode_identifier(tool_key: &str) -> String
作用:把任意工具名改成 JavaScript 里合法的变量名。比如带横线的名字不能直接当函数名用,它会把不合规字符换成下划线。
数据流:进去的是原始工具名。它逐个看字符:开头只能是字母、下划线或美元符号,后面还允许数字;不合法的字符一律变成 _。如果最后一个字符都没有,就返回 _。
调用关系:这是很多说明生成步骤都会用到的基础工具。build_exec_tool_description、enabled_tool_metadata、render_code_mode_tool_declaration 和 render_json_schema_property_name 都靠它保证生成出来的名字能在 JavaScript/TypeScript 中正常使用。
调用图:被 4 处调用(build_exec_tool_description, enabled_tool_metadata, render_code_mode_tool_declaration, render_json_schema_property_name);外部调用 1 个(new)。
augment_tool_definition348–353 ↗
fn augment_tool_definition(mut definition: ToolDefinition) -> ToolDefinition
作用:给工具定义补上一段代码模式专用的调用示例。这样工具说明不只是自然语言,还包含“应该怎么调用”的类型提示。
数据流:进去的是一个 ToolDefinition。如果它不是公开的 exec 工具,就用 render_code_mode_sample_for_definition 根据输入输出 schema 重新生成说明;如果是公开工具,就保持原样。出来的是更新后的工具定义。
调用关系:它常用于工具列表准备阶段。测试会调用它,确认生成的说明里确实追加了 declare const tools 这样的 TypeScript 声明,并且属性描述会变成注释。
调用图:调用 1 个内部函数(render_code_mode_sample_for_definition);被 3 处调用(augment_tool_definition_appends_typed_declaration, augment_tool_definition_includes_property_descriptions_as_comments, code_mode_only_description_renders_shared_mcp_types_once)。
enabled_tool_metadata355–362 ↗
fn enabled_tool_metadata(definition: &ToolDefinition) -> EnabledToolMetadata
作用:把完整工具定义压缩成运行时更轻的元数据。元数据里保留原工具名、JavaScript 全局名、说明和工具类型。
数据流:进去的是一个 ToolDefinition。它复制工具真实名称和说明,调用 normalize_code_mode_identifier 生成可在脚本中使用的全局函数名,并带上工具种类。出来的是 EnabledToolMetadata。
调用关系:它适合在 exec 运行环境准备 ALL_TOOLS 或 tools 对象时使用。它不生成长说明,只整理调用时需要快速查询的信息。
调用图:调用 1 个内部函数(normalize_code_mode_identifier)。
render_code_mode_sample372–384 ↗
fn render_code_mode_sample(
description: &str,
tool_name: &str,
input_name: &str,
input_type: String,
output_type: String,
) -> String
作用:把一段工具说明和一个 TypeScript 调用声明拼在一起。它让读者既能看懂工具用途,也能看到准确的调用格式。
数据流:进去的是原始描述、工具名、参数名、输入类型和输出类型。它先生成 declare const tools: { ... } 形式的声明,再把它放进代码块,接在原始描述后面。出来的是完整说明文字。
调用关系:它是 render_code_mode_sample_for_definition 的最后排版步骤。上游负责判断输入输出类型,这里只把这些信息包装成清楚的文档片段。
调用图:被 1 处调用(render_code_mode_sample_for_definition);外部调用 1 个(format!)。
render_code_mode_sample_for_definition386–422 ↗
fn render_code_mode_sample_for_definition(definition: &ToolDefinition) -> String
作用:根据一个工具定义自动生成代码模式调用示例。它会判断工具是普通函数参数还是自由文本输入,并把输入输出 schema 翻译成 TypeScript。
数据流:进去的是 ToolDefinition。它先按工具种类决定参数名是 args 还是 input;再把输入 schema 转成 TypeScript 类型;如果输出看起来像 MCP 调用结果,就包成 CallToolResult<...>;否则直接翻译输出 schema。最后交给 render_code_mode_sample 生成文字。出来的是带类型声明的说明。
调用关系:它被 augment_tool_definition 和 build_exec_tool_description 使用,是单个工具说明的核心生成器。它会调用 mcp_structured_content_schema 识别 MCP 返回格式,也会调用 render_json_schema_to_typescript 做类型翻译。
调用图:调用 3 个内部函数(mcp_structured_content_schema, render_code_mode_sample, render_json_schema_to_typescript);被 2 处调用(augment_tool_definition, build_exec_tool_description);外部调用 1 个(format!)。
render_code_mode_tool_declaration424–432 ↗
fn render_code_mode_tool_declaration(
tool_name: &str,
input_name: &str,
input_type: String,
output_type: String,
) -> String
作用:生成某个工具在 TypeScript 里的函数签名。函数签名就是告诉人和机器:这个工具函数叫什么、收什么参数、返回什么结果。
数据流:进去的是工具名、参数名、输入类型和输出类型。它先把工具名规范化成 JavaScript 合法标识符,再拼出类似 tool(args: Input): Promise<Output>; 的文本。出来的是这一行声明。
调用关系:它是代码示例排版中的小零件,服务于 render_code_mode_sample 这一类说明生成流程。它依赖 normalize_code_mode_identifier,避免声明里出现 JavaScript 不接受的名字。
调用图:调用 1 个内部函数(normalize_code_mode_identifier);外部调用 1 个(format!)。
render_tool_heading434–440 ↗
fn render_tool_heading(global_name: &str, raw_name: &str) -> String
作用:生成嵌套工具说明里的标题。如果工具的 JavaScript 名和原始名不同,它会同时展示两个名字,避免读者混淆。
数据流:进去的是规范化后的全局名和原始工具名。两者一样时,输出一个简单标题;不一样时,输出包含两个名字的标题,比如“可调用名”和“原始名”。
调用关系:它被 build_exec_tool_description 调用,用来给每个嵌套工具开一个清楚的小节。真正的工具说明和类型示例由其他函数生成。
调用图:被 1 处调用(build_exec_tool_description);外部调用 1 个(format!)。
render_json_schema_to_typescript442–444 ↗
fn render_json_schema_to_typescript(schema: &JsonValue) -> String
作用:把 JSON Schema 转成 TypeScript 类型文本。JSON Schema 是机器常用的数据格式说明,TypeScript 类型更适合写 JavaScript 时阅读。
数据流:进去的是一段 JSON Schema。它直接交给内部递归函数处理。出来的是一个字符串,比如 string、Array<number> 或 { name: string; }。
调用关系:它是对外暴露的简洁入口。render_code_mode_sample_for_definition 用它给工具的输入和输出生成类型提示,具体细节由 render_json_schema_to_typescript_inner 完成。
调用图:调用 1 个内部函数(render_json_schema_to_typescript_inner);被 1 处调用(render_code_mode_sample_for_definition)。
mcp_structured_content_schema446–485 ↗
fn mcp_structured_content_schema(output_schema: Option<&JsonValue>) -> Option<&JsonValue>
作用:判断一个输出 schema 是否像 MCP 工具调用结果,并取出其中真正有业务含义的 structuredContent。MCP 是一种让模型和外部工具通信的协议。
数据流:进去的是可选的输出 schema。它检查里面是否有 content 数组、isError 布尔值、_meta 对象等 MCP 调用结果常见字段。符合时,出来的是 structuredContent 的 schema;没有这个字段时用 true 代表未知结构;不符合时返回空。
调用关系:它被 render_code_mode_sample_for_definition 调用。作用是让 MCP 工具的返回类型显示成 CallToolResult<具体结构>,而不是一坨普通对象。
调用图:被 1 处调用(render_code_mode_sample_for_definition);外部调用 1 个(Bool)。
render_json_schema_to_typescript_inner487–560 ↗
fn render_json_schema_to_typescript_inner(schema: &JsonValue) -> String
作用:递归翻译 JSON Schema 的主力函数。它会处理常量、枚举、联合类型、交叉类型、对象、数组和基础类型。
数据流:进去的是任意 JSON 值。它先看布尔 schema:true 代表 unknown,false 代表 never;对象 schema 里优先处理 const、enum、anyOf、oneOf、allOf 和 type;遇到对象或数组再交给专门函数。出来的是 TypeScript 类型字符串,无法识别时用 unknown 保底。
调用关系:它是 schema 翻译器的核心。render_json_schema_to_typescript 从这里开始,数组、对象、额外属性和对象字段也会反过来调用它来翻译更小的子 schema。
调用图:调用 4 个内部函数(render_json_schema_array, render_json_schema_literal, render_json_schema_object, render_json_schema_type_keyword);被 4 处调用(append_additional_properties_line, render_json_schema_array, render_json_schema_object_property, render_json_schema_to_typescript)。
render_json_schema_type_keyword562–575 ↗
fn render_json_schema_type_keyword(
map: &serde_json::Map<String, JsonValue>,
schema_type: &str,
) -> String
作用:把 JSON Schema 的 type 关键词翻译成 TypeScript 类型。比如 integer 在 TypeScript 里也只能写成 number。
数据流:进去的是整个 schema 对象和一个类型关键词。它按关键词分流:字符串、数字、布尔、空值直接变成对应类型;数组交给数组渲染;对象交给对象渲染;不认识的类型返回 unknown。
调用关系:它由 render_json_schema_to_typescript_inner 调用,专门处理 type 字段这一条路。遇到复杂结构时,它再把活交给 render_json_schema_array 或 render_json_schema_object。
调用图:调用 2 个内部函数(render_json_schema_array, render_json_schema_object);被 1 处调用(render_json_schema_to_typescript_inner)。
render_json_schema_array577–594 ↗
fn render_json_schema_array(map: &serde_json::Map<String, JsonValue>) -> String
作用:把数组 schema 翻译成 TypeScript 的数组或元组类型。普通数组是很多同类元素,元组则像固定长度的清单。
数据流:进去的是 schema 对象。它先看 items,有的话把元素类型翻译出来,输出 Array<元素类型>;如果没有,再看 prefixItems,有的话输出 [A, B] 这种固定位置类型;都没有则输出 unknown[]。
调用关系:它被 render_json_schema_to_typescript_inner 和 render_json_schema_type_keyword 调用。它自己会继续调用内部翻译函数处理数组元素的 schema。
调用图:调用 1 个内部函数(render_json_schema_to_typescript_inner);被 2 处调用(render_json_schema_to_typescript_inner, render_json_schema_type_keyword);外部调用 2 个(get, format!)。
append_additional_properties_line596–615 ↗
fn append_additional_properties_line(
lines: &mut Vec<String>,
map: &serde_json::Map<String, JsonValue>,
properties: &serde_json::Map<String, JsonValue>,
line_prefix: &str,
)
作用:给对象类型补上“还允许其他任意字段”的说明。这个相当于告诉读者:除了明面列出的字段,还能不能有别的键。
数据流:进去的是正在组装的文本行、整个对象 schema、已知属性表和每行前缀。它检查 additionalProperties:true 表示任意额外字段,false 表示不加;如果是一个 schema,就翻译成额外字段的类型;如果没有写任何属性,也默认补一个未知字段索引。它会直接修改传入的行列表。
调用关系:它只被 render_json_schema_object 调用,是对象类型排版的收尾步骤。需要翻译额外字段类型时,它会调用 render_json_schema_to_typescript_inner。
调用图:调用 1 个内部函数(render_json_schema_to_typescript_inner);被 1 处调用(render_json_schema_object);外部调用 3 个(get, is_empty, format!)。
has_property_description617–622 ↗
fn has_property_description(value: &JsonValue) -> bool
作用:判断某个对象字段 schema 里有没有非空的描述文字。字段有描述时,生成的 TypeScript 会用注释保留下来。
数据流:进去的是字段对应的 JSON 值。它查找 description 字段,并确认它是非空字符串。出来的是 true 或 false。
调用关系:它服务于 render_json_schema_object 的排版决策。只要有字段带描述,对象类型就会改成多行格式,方便插入注释。
调用图:外部调用 1 个(get)。
render_json_schema_object_property624–633 ↗
fn render_json_schema_object_property(name: &str, value: &JsonValue, required: &[&str]) -> String
作用:把对象里的一个字段渲染成 TypeScript 属性行。它会标出这个字段是必填还是可选。
数据流:进去的是字段名、字段 schema 和必填字段名单。它判断字段名是否在必填名单里,不在就加 ?;再把字段名转成安全写法,把字段类型翻译出来。出来的是类似 name?: string; 的一行文字。
调用关系:它被 render_json_schema_object 用来逐个生成对象字段。它会调用 render_json_schema_property_name 处理字段名,也会调用内部 schema 翻译函数处理字段类型。
调用图:调用 2 个内部函数(render_json_schema_property_name, render_json_schema_to_typescript_inner);外部调用 1 个(format!)。
render_json_schema_object635–693 ↗
fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>) -> String
作用:把对象 schema 翻译成 TypeScript 对象类型。它会处理必填字段、字段排序、字段注释和额外字段规则。
数据流:进去的是对象 schema。它先取出 required 必填名单和 properties 字段表,并按字段名排序,让输出稳定;如果发现字段有描述,就生成带注释的多行对象;否则生成紧凑的一行对象。最后补上额外字段规则。出来的是对象类型字符串,比如 { id: string; count?: number; }。
调用关系:它被 render_json_schema_to_typescript_inner 和 render_json_schema_type_keyword 调用。它会进一步调用 render_json_schema_object_property 生成字段行,并调用 append_additional_properties_line 处理额外字段。
调用图:调用 1 个内部函数(append_additional_properties_line);被 2 处调用(render_json_schema_to_typescript_inner, render_json_schema_type_keyword);外部调用 3 个(get, format!, vec!)。
render_json_schema_property_name695–701 ↗
fn render_json_schema_property_name(name: &str) -> String
作用:决定 TypeScript 对象字段名应该直接写,还是加引号写。比如 foo-bar 不能裸写成属性名,就必须变成字符串形式。
数据流:进去的是字段名。它用 normalize_code_mode_identifier 判断这个名字是否已经是合法标识符;合法就原样输出,不合法就用 JSON 字符串格式包起来。出来的是可安全放进 TypeScript 类型里的字段名。
调用关系:它被 render_json_schema_object_property 调用。它复用工具名规范化逻辑,保证对象字段名也不会生成非法 TypeScript。
调用图:调用 1 个内部函数(normalize_code_mode_identifier);被 1 处调用(render_json_schema_object_property);外部调用 1 个(to_string)。
render_json_schema_literal703–705 ↗
fn render_json_schema_literal(value: &JsonValue) -> String
作用:把 JSON 字面值变成 TypeScript 字面量类型。比如字符串常量会变成带引号的字符串,数字常量会变成数字文本。
数据流:进去的是一个 JSON 值。它尝试用 JSON 序列化把它转成字符串;如果失败,就返回 unknown。出来的是可嵌入 TypeScript 类型的字面量文本。
调用关系:它被 render_json_schema_to_typescript_inner 用于处理 const 和 enum。这样枚举值能显示成 "a" | "b" 这类更精确的类型。
调用图:被 1 处调用(render_json_schema_to_typescript_inner);外部调用 1 个(to_string)。
tests::mcp_call_tool_result_schema723–740 ↗
fn mcp_call_tool_result_schema(structured_content_schema: JsonValue) -> JsonValue
作用:在测试里快速造一个像 MCP 调用结果的 JSON Schema。这样多个测试不用重复写同一大段结构。
数据流:进去的是 structuredContent 的 schema。它把这个 schema 塞进固定的 MCP 结果外壳里,包括 content、isError、_meta 等字段。出来的是一份完整的测试用 JSON Schema。
调用关系:这是测试辅助函数。命名空间分组、空命名空间和 MCP 类型渲染相关测试会用它制造输入数据。
调用图:外部调用 1 个(json!)。
tests::parse_exec_source_without_pragma743–752 ↗
fn parse_exec_source_without_pragma()
作用:确认没有 // @exec: 配置时,exec 源码会原样保留下来。这个测试防止普通 JavaScript 被误解析。
数据流:进去的是测试写死的 text('hi')。它调用 parse_exec_source,期望出来的代码仍是原文,两个限制字段都是空。测试只做断言,不改运行状态。
调用关系:它直接覆盖 parse_exec_source 的无配置路径。和带配置的测试一起保证解析器的两条常见路都正常。
调用图:外部调用 1 个(assert_eq!)。
tests::parse_exec_source_with_pragma755–764 ↗
fn parse_exec_source_with_pragma()
作用:确认第一行带 // @exec: 配置时,解析器能读出配置并移除这一行。这样运行器拿到的代码不会包含配置注释。
数据流:进去的是一段第一行带 yield_time_ms 的输入。它调用 parse_exec_source,期望输出代码只剩 text('hi'),等待时间为 10,输出上限为空。
调用关系:它直接测试 parse_exec_source 的配置解析路径,防止 JSON 配置读取或代码截取逻辑退化。
调用图:外部调用 1 个(assert_eq!)。
tests::normalize_identifier_rewrites_invalid_characters767–776 ↗
fn normalize_identifier_rewrites_invalid_characters()
作用:确认工具名规范化会保留合法名字,并把非法字符换成下划线。尤其是带横线的工具名要能变成 JavaScript 可调用名。
数据流:进去的是两个固定工具名。测试调用 normalize_code_mode_identifier,分别检查合法名不变、hidden-dynamic-tool 变成 hidden_dynamic_tool。
调用关系:它覆盖 normalize_code_mode_identifier。这个函数被很多说明生成流程依赖,所以这个测试能间接保护工具声明和字段名输出。
调用图:外部调用 1 个(assert_eq!)。
tests::augment_tool_definition_appends_typed_declaration779–805 ↗
fn augment_tool_definition_appends_typed_declaration()
作用:确认增强工具定义时,会把输入输出类型声明追加进说明。没有这个测试,说明可能只剩文字,模型就更容易传错参数。
数据流:测试先构造一个函数型工具,输入需要 city 字符串,输出有 ok 布尔值。它调用 augment_tool_definition,再检查生成说明里包含 declare const tools 和正确的函数签名。
调用关系:它直接调用 augment_tool_definition,而该函数会继续走到 render_code_mode_sample_for_definition 等类型渲染流程。
调用图:调用 2 个内部函数(augment_tool_definition, plain);外部调用 2 个(assert!, json!)。
tests::augment_tool_definition_includes_property_descriptions_as_comments808–853 ↗
fn augment_tool_definition_includes_property_descriptions_as_comments()
作用:确认 JSON Schema 里的字段描述会变成 TypeScript 注释。这样工具说明不会丢掉字段的口头解释。
数据流:测试构造一个天气工具,输入和输出字段都带 description。它调用 augment_tool_definition,然后检查生成的声明里有多行注释和对应字段类型。
调用关系:它覆盖从 augment_tool_definition 到对象 schema 渲染的完整链路,尤其保护 render_json_schema_object 对字段描述的处理。
调用图:调用 2 个内部函数(augment_tool_definition, plain);外部调用 2 个(assert!, json!)。
tests::code_mode_only_description_includes_nested_tools856–875 ↗
fn code_mode_only_description_includes_nested_tools()
作用:确认代码模式专用说明会列出可用的嵌套工具。这样 exec 说明里不只讲规则,也告诉模型具体能调用什么。
数据流:测试传入一个名叫 foo 的工具,并把 code_mode_only 设为 true。它调用 build_exec_tool_description,检查结果包含 foo 的小节和描述。
调用关系:它直接测试 build_exec_tool_description 的嵌套工具列表路径。这个路径会用到工具标题和单工具说明生成。
调用图:调用 2 个内部函数(build_exec_tool_description, plain);外部调用 2 个(new, assert!)。
tests::exec_description_mentions_timeout_helpers878–887 ↗
fn exec_description_mentions_timeout_helpers()
作用:确认 exec 的基础说明里提到了 setTimeout 和 clearTimeout。这些是脚本里安排延迟任务和取消延迟任务的辅助函数。
数据流:测试不传任何工具,并把代码模式专用列表关闭。它调用 build_exec_tool_description,检查返回说明中包含两个超时辅助函数的文字。
调用关系:它保护固定说明模板 EXEC_DESCRIPTION_TEMPLATE。虽然调用的是 build_exec_tool_description,重点是防止基础帮助文字被误删。
调用图:调用 1 个内部函数(build_exec_tool_description);外部调用 2 个(new, assert!)。
tests::code_mode_only_description_groups_namespace_instructions_once890–945 ↗
fn code_mode_only_description_groups_namespace_instructions_once()
作用:确认同一命名空间的说明只出现一次。命名空间可以理解成一组相关工具的共同分类说明,重复出现会让文档又长又乱。
数据流:测试构造两个同属 mcp__sample__ 的工具和一段共享命名空间说明。它调用 build_exec_tool_description,检查命名空间标题只出现一次,同时两个工具都有各自的 CallToolResult 声明。
调用关系:它直接覆盖 build_exec_tool_description 的命名空间分组逻辑,并借助 tests::mcp_call_tool_result_schema 生成 MCP 风格返回 schema。
调用图:调用 2 个内部函数(build_exec_tool_description, namespaced);外部调用 5 个(from, assert!, assert_eq!, mcp_call_tool_result_schema, json!)。
tests::code_mode_only_description_omits_empty_namespace_sections948–980 ↗
fn code_mode_only_description_omits_empty_namespace_sections()
作用:确认命名空间说明为空时,不生成空标题。这样最终说明不会出现没有内容的章节。
数据流:测试构造一个带空命名空间描述的 MCP 工具。它调用 build_exec_tool_description,检查没有 ## mcp__sample 章节,但工具自己的标题仍然存在。
调用关系:它测试 build_exec_tool_description 里对空说明的跳过逻辑,同时继续保证工具本身不会被误删。
调用图:调用 2 个内部函数(build_exec_tool_description, namespaced);外部调用 5 个(from, new, assert!, mcp_call_tool_result_schema, json!)。
tests::exec_description_mentions_deferred_nested_tools_when_available1087–1098 ↗
fn exec_description_mentions_deferred_nested_tools_when_available()
作用:确认有延迟加载的嵌套工具时,exec 说明会提醒读者有些工具可能没有列在正文里。延迟加载就是工具可用,但说明里可能先不全部展开。
数据流:测试不传具体工具,但把 deferred_tools_available 设为 true。它调用 build_exec_tool_description,检查返回文字包含“部分延迟工具可能省略”和“可按名字描述过滤 ALL_TOOLS”的提示。
调用关系:它保护 build_exec_tool_description 对延迟工具提示的拼接逻辑,确保模型知道还能通过 ALL_TOOLS 找工具。
调用图:调用 1 个内部函数(build_exec_tool_description);外部调用 2 个(new, assert!)。
运行时负载 schema
这些数据模型定义代码模式工具在执行时发送和接收的内容,从内容负载到运行时请求和响应封装。
code-mode-protocol/src/response.rs源码 ↗
这个文件本身不做计算,更像是一张“表格模板”或“快递面单规范”。它规定了图片清晰度可以有哪些选择:自动、低、高、原图,并把默认值定为高。它还规定了函数调用输出内容的两种形态:一种是输入文字,里面有一段 text;另一种是输入图片,里面有 image_url,还可以额外带 detail 来说明要用什么清晰度。这里用到的 serde 是 Rust 里常见的序列化工具,意思是把内存里的数据变成 JSON 这类可传输文本,或再读回来。几个 serde 标记很重要:图片清晰度会用小写字符串表示;内容项会用 type 字段区分种类;图片的 detail 如果没有,就不写进输出里。这样既保证格式清楚,也避免传很多没必要的空字段。
code-mode-protocol/src/runtime.rs源码 ↗
可以把这个文件理解成一套“表格模板”或“快递单格式”。当系统要执行一段代码时,会用 ExecuteRequest 写清楚:要跑什么代码、允许用哪些工具、最多等多久、最多收多少输出。代码跑着跑着可能还没结束,于是 WaitRequest、WaitToPendingRequest 用来继续等待某个已经开始的单元。返回结果也被分成几类:可能只是暂时让出执行权,可能已经终止,也可能拿到了最终结果,还可能发现要等的单元不存在。这里的结构都支持序列化和反序列化,也就是能在程序内部数据和 JSON 这类传输格式之间来回转换,方便网络、进程间通信或日志记录。重要的是,它不真正执行代码,而是规定“请求和回复长什么样”。这样运行时、调用方、工具系统才能按同一种语言配合。
RuntimeResponse::from58–62 ↗
fn from(outcome: WaitOutcome) -> Self
作用:这个函数把 WaitOutcome 这种“等待后的结果包装”拆开,变成真正的 RuntimeResponse。别人如果只关心最终回复内容,而不关心它来自“活着的单元”还是“丢失的单元”,就会用它。
数据流:进去的是一个 WaitOutcome,里面无论是 LiveCell 还是 MissingCell,都包着一个 RuntimeResponse。函数检查是哪一种情况,然后把里面那份 RuntimeResponse 原样取出来。出来的是更简单的 RuntimeResponse;它不改动别的状态,也不生成新输出。
调用关系:它位于等待流程的收尾处:上游等待某个代码单元后,会得到 WaitOutcome 这种带上下文的结果;当后续流程只需要统一处理运行时回复时,这个转换就把额外外壳去掉。它没有再把工作交给其他函数,只做一个安全、明确的拆包。
会话契约
这些抽象定义调用方与代码模式运行时之间更长生命周期的会话边界,包括标识符、包装器以及提供方/会话 trait。
code-mode-protocol/src/session.rs源码 ↗
这份文件不负责真正跑代码,而是规定“跑代码这件事要怎么对外说清楚”。在代码模式里,一次会话可以连续执行多个代码单元,这些单元会共享之前保存下来的值;不同会话之间又必须隔开,不能串味。这里的 CellId 就是每个代码单元的编号,StartedCell 表示一个已经启动、但第一份运行反馈可能还没到的单元。CodeModeSession 是核心接口,规定会话必须能执行、等待、终止和关闭;CodeModeSessionDelegate 是反方向的回调,让运行中的代码可以请求宿主调用工具或发送通知;CodeModeSessionProvider 则像“会话工厂”,负责造出新会话。文件里还大量使用 Future(可以理解成“以后才会给结果的任务”),因为执行代码、等结果、远程通信都不是立刻完成的事。
CellId::new30–32 ↗
fn new(value: String) -> Self
作用:把一段普通字符串包成 CellId,也就是一个代码单元的正式编号。这样做的好处是,代码里一看类型就知道这是“单元编号”,而不是随便什么文本。
数据流:进去的是一个 String 字符串 → 它把这个字符串放进 CellId 这个小包装里 → 出来的是一个 CellId,之后可以被执行、等待、通知等流程当作单元身份使用。
调用关系:当系统分配新单元编号、处理调用,或者测试需要造一个单元编号时会用到它。它不再把工作交给别的项目函数,只是完成最基础的“给字符串穿上 CellId 外衣”。
调用图:被 5 处调用(started_cell_preserves_remote_initial_response_errors, allocate_cell_id, cell_id, cell_id, handle_call)。
CellId::as_str34–36 ↗
CellId::as_ref40–42 ↗
fn as_ref(&self) -> &str
作用:让 CellId 可以在需要字符串引用的地方自然使用。简单说,就是让“单元编号”能自动当成一段只读文本传给别的代码。
数据流:进去的是一个 CellId 的引用 → 它调用 CellId::as_str 取出内部文字 → 出来的是同一段编号文字的 &str 引用,不产生新编号,也不修改原值。
调用关系:这是 Rust 的 AsRef<str> 接口实现,方便通用代码接收 CellId。它自己不直接知道内部怎么存字符串,而是把取字符串这件事交给 CellId::as_str。
调用图:调用 1 个内部函数(as_str)。
CellId::fmt46–48 ↗
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result
作用:规定 CellId 被打印或拼进文字时应该显示什么。它让日志、错误信息、调试输出里能看到真正的单元编号,而不是一坨内部结构。
数据流:进去的是一个 CellId 和一个格式化输出器 → 它先调用 CellId::as_str 拿到编号文字,再把这段文字写进输出器 → 出来的是格式化是否成功的结果,CellId 本身不变。
调用关系:这是 Display 显示接口的一部分。任何地方用普通打印方式显示 CellId 时都会走到这里;它把取编号交给 CellId::as_str,把写文字交给标准格式化工具。
StartedCell::new57–66 ↗
fn new(cell_id: CellId, initial_response_rx: oneshot::Receiver<RuntimeResponse>) -> Self
作用:创建一个 StartedCell,表示某个代码单元已经开始执行,并且以后会收到第一条运行反馈。它适合那种第一条反馈只会是成功的 RuntimeResponse,通道断了才算错误的场景。
数据流:进去的是单元编号 CellId,以及一个 oneshot 接收器;oneshot 可以理解成“一次性传话筒”,只传一次结果 → 它把等待接收器的过程包成一个 Future,也就是以后完成的任务,并保存起来 → 出来的是 StartedCell;如果传话筒另一头提前消失,之后等待时会得到“运行时意外结束”的错误。
调用关系:当执行系统启动一个单元后,需要把“编号”和“稍后到来的初始反馈”一起交给调用者,就会用它。它内部主要借助异步任务包装工具 pin,把等待结果这件事固定成 StartedCell 可保存的形式。
调用图:外部调用 1 个(pin)。
StartedCell::from_result_receiver68–80 ↗
fn from_result_receiver(
cell_id: CellId,
initial_response_rx: oneshot::Receiver<Result<RuntimeResponse, String>>,
) -> Self
作用:创建一个 StartedCell,但它接收的第一条反馈本身就可能是成功或失败。它常用于远程执行一类场景,因为远端可能明确返回“启动失败”或“运行错误”。
数据流:进去的是 CellId,以及一个一次性接收器,里面会传来 Result<RuntimeResponse, String>,也就是“成功响应或错误文字” → 它把等待过程包成 Future:如果通道断了,转成“运行时意外结束”;如果通道正常,就保留远端传来的成功或失败 → 出来的是 StartedCell,里面保存了这个将来才能知道的初始结果。
调用关系:执行流程创建 StartedCell 时会用到它,测试也会用它确认远程传回的错误不会被吞掉。它和 StartedCell::new 很像,但多保留了一层“远端主动报告失败”的信息。
调用图:被 2 处调用(started_cell_preserves_remote_initial_response_errors, execute);外部调用 1 个(pin)。
StartedCell::initial_response82–84 ↗
async fn initial_response(self) -> Result<RuntimeResponse, String>
作用:等待这个已启动代码单元的第一条运行反馈。调用它的人想知道:这个单元刚开始执行后,运行时给出的第一份回应到底是什么,或者是不是出错了。
数据流:进去的是一个 StartedCell 本身 → 它取出里面保存的 Future 并等待它完成 → 出来的是 RuntimeResponse,或者一段错误文字;因为它会消耗掉 StartedCell,所以这份初始反馈只能领取一次。
调用关系:在单元启动之后,后续流程需要拿第一条响应时会调用它。它不再调用本文件里的其他函数,而是等待 StartedCell::new 或 StartedCell::from_result_receiver 早先放进去的异步结果。