Codex 系统手册

指令、skill、插件、memory 与评审提示词贡献者

stage-12.329 个文件

这一阶段像开工前给模型递“任务夹”,属于幕后支撑,不直接干活,但决定模型看见哪些规则和线索。它先收集用户家目录、项目 AGENTS.md、IDE 当前文件、权限边界、协作方式和说话风格;再把技能、插件、外部 App 的可用能力整理好,用户点名时还会把对应说明塞进去;同时补上图片保存位置、终端画图规则、记忆摘要、当前目标和预算。最后,评审相关文件把“审哪段代码”和结束语变成稳定提示,让模型按同一套规则行动。

本阶段的文件29

项目和用户指令来源

这些文件从用户和项目来源加载基线指令文本,并将可选的 developer 风格覆盖层包装为提示词片段。

codex-home/src/instructions/mod.rs源码 ↗
configconfig load

这个文件像一个“说明书读取员”。Codex 启动或准备生成提示内容时,需要知道用户有没有写过全局规则,比如代码风格、回答习惯、项目约定。这里定义的 CodexHomeUserInstructionsProvider 会到指定的 Codex home 目录里找两个文件:先找 AGENTS.override.md,再找 AGENTS.md。前者优先级更高,像临时覆盖版;如果没有或不是正常文件,就继续找默认版。它读取文件时很小心:文件不存在不算错误,直接跳过;如果遇到权限、磁盘等读取问题,会把警告记下来,而不是让整个程序崩掉。读到内容后,它会去掉首尾空白;如果里面确实有文字,就把文字和来源路径一起返回。整个设计的重点是:尽量找到用户指令,但找不到也能正常运行,同时把真正值得提醒的问题带回去。

函数细节3
CodexHomeUserInstructionsProvider::new20–22 ↗
fn new(codex_home: AbsolutePathBuf) -> Self

作用:这个函数创建一个“从哪个 Codex 主目录读取指令”的读取器。别人只要把一个绝对路径交给它,后面就可以用这个读取器去找用户的 AGENTS.md 文件。

数据流:进去的是一个 Codex home 的绝对路径 → 函数把这个路径保存到 CodexHomeUserInstructionsProvider 结构里 → 出来的是一个新的读取器对象,之后读取文件时都会以这个目录为起点。

调用关系:它是这个读取器的入口。命令运行、主程序启动、测试场景和提示构建流程都会先调用它,拿到一个配置好的 provider,然后再在需要用户指令时调用后续加载函数。

调用图:被 7 处调用(run_debug_prompt_input_command, provider, loads_user_instructions_without_a_primary_environment, multi_environment_thread_loads_every_project_and_keeps_creation_snapshot, build_prompt_input_includes_context_and_user_message, new, run_main)。

CodexHomeUserInstructionsProvider::load_from_codex_home24–67 ↗
async fn load_from_codex_home(&self) -> LoadedUserInstructions

作用:这个函数真正去磁盘上找并读取用户指令文件。它会按优先级检查 AGENTS.override.md 和 AGENTS.md,找到第一个有内容的文件就返回。

数据流:进去的是读取器里保存的 Codex home 路径 → 它依次拼出候选文件路径,检查文件是否存在、是否是普通文件,再异步读取内容;读取失败但文件不存在就跳过,其他读取错误会变成警告;读到内容后转成文字并去掉首尾空白 → 出来的是 LoadedUserInstructions:可能包含一段用户指令和来源文件路径,也可能没有指令,但会带上过程中收集到的警告。

调用关系:它是核心干活的函数,但不直接暴露给外部接口使用。load_user_instructions 会把它包装成一个异步任务来调用。它自己会用路径拼接、文件信息读取、文件内容读取和字节转文字这些底层能力,完成从“目录”到“可用指令”的转换。

调用图:调用 1 个内部函数(join);被 1 处调用(load_user_instructions);外部调用 5 个(from_utf8_lossy, new, format!, metadata, read)。

CodexHomeUserInstructionsProvider::load_user_instructions71–73 ↗
fn load_user_instructions(&self) -> LoadUserInstructionsFuture<'_>

作用:这个函数把读取用户指令的能力接到统一接口 UserInstructionsProvider 上。外部代码不需要关心具体怎么读文件,只要通过这个接口请求加载即可。

数据流:进去的是当前读取器对象 → 它调用 load_from_codex_home,并把这个异步读取过程装进一个可等待的盒子里 → 出来的是一个 future,也就是“稍后会完成的读取任务”,调用方等待它就能拿到加载结果。

调用关系:它是对外的标准接口实现。其他模块只认 UserInstructionsProvider 这个统一约定时,会调用它;它再把实际工作交给 load_from_codex_home。这样以后即使换一种指令来源,外部流程也不用跟着改。

调用图:调用 1 个内部函数(load_from_codex_home);外部调用 1 个(pin)。

core/src/agents_md.rs源码 ↗
domain_logic每次准备一次模型请求或一次任务回合时活跃,用来收集并渲染用户和项目说明

这个文件做的事很像进办公室前先看墙上的规章制度。它会从当前工作目录往上找项目根目录,比如看到 .git 就认为到了项目根;然后从项目根一路往下,把每一层目录里的 AGENTS.override.mdAGENTS.md 或配置里的备用文件找出来,按从大到小的顺序读进去。这样,上层的通用规则和下层的局部规则都会被模型看到。它还会合并宿主程序传进来的用户说明,并记录每段说明来自哪里。为了防止说明文件太大,它会按配置限制总字节数,超出就截断。遇到多个运行环境时,它会给不同环境的说明加标签,避免模型把一个目录的规则误用到另一个目录。

函数细节17
load_project_instructions48–88 ↗
async fn load_project_instructions(
    config: &Config,
    user_instructions: Option<UserInstructions>,
    environments: &TurnEnvironmentSnapshot,
) -> Option<LoadedAgentsMd>

作用:这是本文件的总入口,用来把外部传来的用户说明和各个工作环境里的项目说明合在一起。别人调用它,是为了得到一份最终可以交给模型看的规则文本。

数据流:进去的是配置、可选的用户说明、以及当前回合的环境列表。它先保留非空的用户说明,再逐个环境拿到文件系统和当前目录,调用 read_agents_md 去读项目说明;读到的内容会追加进同一个结果里。如果配置打开了子代理说明功能,它还会额外塞入一段内部说明。最后,如果什么有效说明都没有,就返回空;否则返回装好的 LoadedAgentsMd

调用关系:它站在最外层,负责串起整个流程。它先用 LoadedAgentsMd::from_user_instructions 建一个容器,再把每个环境交给 read_agents_md。如果读文件时出错,它不会让整个流程崩掉,而是记录错误后继续处理其他环境。

调用图:调用 2 个内部函数(from_user_instructions, read_agents_md);外部调用 1 个(error!)。

read_agents_md96–166 ↗
async fn read_agents_md(
    config: &Config,
    fs: &dyn ExecutorFileSystem,
    environment_id: &str,
    cwd: &AbsolutePathBuf,
) -> io::Result<Option<LoadedAgentsMd>>

作用:这个函数负责真正读取找到的项目说明文件。它会控制总大小,跳过不存在或不是普通文件的路径,避免把目录或坏路径当成说明读进去。

数据流:进去的是配置、一个抽象文件系统、环境编号和当前目录。它先看允许读取的最大字节数;如果是 0,就直接表示不读。然后调用 agents_md_paths 找候选文件。接着逐个检查文件是否存在、是否真的是文件,再读内容;如果剩余额度不够,就截断内容。非空文本会被包装成一条带来源信息的说明。最后,有内容就返回 LoadedAgentsMd,没内容就返回空。

调用关系:它由 load_project_instructions 调用,是“找到路径”和“生成最终说明对象”之间的中间工人。它把找路径的活交给 agents_md_paths,自己主要管读文件、限大小、记录来源和处理文件读取错误。

调用图:调用 2 个内部函数(agents_md_paths, from_abs_path);被 1 处调用(load_project_instructions);外部调用 6 个(from_utf8_lossy, default, get_metadata, read_file, warn!, clone)。

agents_md_paths170–256 ↗
async fn agents_md_paths(
    config: &Config,
    cwd: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
) -> io::Result<Vec<AbsolutePathBuf>>

作用:这个函数负责找出应该读取哪些说明文件,以及读取顺序。它保证从项目根目录到当前目录一路收集,让通用规则先出现,局部规则后出现。

数据流:进去的是配置、当前目录和文件系统。它先合并配置层里的项目根标记设置,比如默认的 .git;然后从当前目录往父目录检查这些标记,找到项目根。如果找到了根目录,就列出根目录到当前目录之间的所有目录;如果没找到,就只看当前目录。随后它拿到候选文件名列表,在每个目录里按优先级查找第一个存在的说明文件,最后返回这些文件的绝对路径列表。

调用关系:它由 read_agents_md 调用,专门处理“去哪里找”的问题。它会调用 candidate_filenames 决定每个目录里优先找哪些文件名,并通过文件系统的元数据查询来确认文件是否存在。

调用图:调用 2 个内部函数(candidate_filenames, from_abs_path);被 1 处调用(read_agents_md);外部调用 11 个(Table, new, default_project_root_markers, merge_toml_values, project_root_markers_from_config, get_metadata, matches!, new, warn!, clone (+1 more))。

candidate_filenames258–272 ↗
fn candidate_filenames(config: &Config) -> Vec<&str>

作用:这个函数给出每个目录里要尝试的说明文件名顺序。这样系统可以先尊重本地覆盖文件,再看标准的 AGENTS.md,最后看配置里指定的备用名字。

数据流:进去的是配置。它先放入 AGENTS.override.md,再放入 AGENTS.md,然后把配置里的备用文件名逐个加入;空名字会被跳过,重复名字也不会加入。出来的是一个按优先级排好的文件名列表。

调用关系:它被 agents_md_paths 使用。agents_md_paths 每到一个目录,就按这个列表逐个检查,找到第一个真实文件后就采用它。

调用图:被 1 处调用(agents_md_paths);外部调用 1 个(with_capacity)。

LoadedAgentsMd::new_user287–298 ↗
fn new_user(contents: String, path: AbsolutePathBuf) -> Self

作用:这个构造函数用来创建一份只有用户级说明的 LoadedAgentsMd。如果用户说明是空白,它会返回空对象,避免把没有意义的文本传给模型。

数据流:进去的是说明文本和它来自的文件路径。函数先去掉首尾空白来判断是否真的有内容;如果没有,就返回默认空结果。如果有,就把文本和来源路径装进 UserInstructions,项目说明列表保持为空。

调用关系:它通常用于把一个用户级说明文件变成统一的数据结构。后续可以像处理项目说明一样,对它调用 textrendersources

调用图:外部调用 2 个(default, new)。

LoadedAgentsMd::from_user_instructions300–306 ↗
fn from_user_instructions(user_instructions: Option<UserInstructions>) -> Self

作用:这个函数把外部已经传进来的用户说明转换成内部统一格式。它会自动丢掉空白说明,避免空内容影响后续拼接。

数据流:进去的是一个可选的 UserInstructions。如果没有,或者文本全是空白,就得到没有用户说明的容器;如果有有效文本,就保存起来,并初始化一个空的项目说明列表。

调用关系:它被 load_project_instructions 在流程开头调用。也就是说,所有项目文件说明都会追加到它创建的这个基础容器后面。

调用图:被 1 处调用(load_project_instructions);外部调用 1 个(new)。

LoadedAgentsMd::from_text_for_testing312–324 ↗
fn from_text_for_testing(contents: impl Into<String>) -> Self

作用:这个函数主要给测试用,可以快速造一份没有真实文件来源的说明内容。它方便测试后续拼接和渲染逻辑,而不必真的在磁盘上建文件。

数据流:进去的是任意可转成字符串的内容。它先转成字符串并检查是否为空白;空白就返回默认空对象。非空就创建一条内部来源的说明记录,放进 entries 里。

调用关系:它不依赖实际文件系统,主要服务测试代码。造出来的对象仍然可以走 textrender 等正常路径。

调用图:外部调用 4 个(into, trim, default, vec!)。

LoadedAgentsMd::is_empty326–332 ↗
fn is_empty(&self) -> bool

作用:这个函数判断当前对象里有没有真正可用的说明。它不只看列表是否为空,还会把全是空白的说明当作没有内容。

数据流:它读取对象里的用户说明和所有说明条目。如果用户说明不存在,并且每条说明的文本去掉空白后都是空的,就返回真;否则返回假。它不修改任何数据。

调用关系:它被本文件内部用来决定是否应该返回 None 或保留结果。比如 load_project_instructionsread_agents_md 都需要避免把空说明继续传下去。

LoadedAgentsMd::text335–341 ↗
fn text(&self) -> String

作用:这个函数生成模型最终能看到的纯说明文本。它会根据是否涉及多个项目环境,选择普通拼接方式或带环境标签的拼接方式。

数据流:进去的是已经收集好的说明对象。它先调用 has_multiple_project_environments 判断是否有多个环境;如果有,就调用 environment_labeled_text,给每个环境的说明加标签;如果没有,就调用 legacy_text,保持旧的普通格式。出来的是一整段字符串。

调用关系:它被 LoadedAgentsMd::render 调用,是渲染完整用户片段前的核心一步。它把底层的一条条说明变成模型真正阅读的一段话。

调用图:调用 3 个内部函数(environment_labeled_text, has_multiple_project_environments, legacy_text);被 1 处调用(render)。

LoadedAgentsMd::legacy_text343–369 ↗
fn legacy_text(&self) -> String

作用:这个函数按传统方式拼接说明文本,适合只有一个项目环境或没有项目环境的情况。它会在用户说明和项目说明之间插入一个明显分隔符。

数据流:它从用户说明开始写入输出字符串,然后依次追加 entries。遇到从用户或内部说明切换到项目说明时,会插入 --- project-doc --- 这样的分隔标记;普通相邻段落之间则用空行隔开。最后返回拼好的文本。

调用关系:它由 LoadedAgentsMd::text 在单环境情况下调用。它负责保持老格式,避免只处理一个项目时输出样式突然变化。

调用图:被 1 处调用(text);外部调用 2 个(new, matches!)。

LoadedAgentsMd::environment_labeled_text371–414 ↗
fn environment_labeled_text(&self) -> String

作用:这个函数在有多个项目环境时拼接说明,并给不同环境加上标签。这样模型能知道哪段规则属于哪个环境和哪个根目录。

数据流:它先写入用户说明。之后逐条查看说明来源:如果是项目说明,就在环境第一次出现时写入类似“for 某环境 with root 某目录”的标签,再写说明内容;同一个环境连续多条说明不会重复贴标签。如果是内部说明,就直接追加并重置环境跟踪。最后输出完整字符串。

调用关系:它由 LoadedAgentsMd::text 在多环境情况下调用。它解决的是多工作区同时参与时的歧义问题,防止项目 A 的规则被误认为项目 B 的规则。

调用图:被 1 处调用(text);外部调用 2 个(new, format!)。

LoadedAgentsMd::render417–431 ↗
fn render(&self) -> String

作用:这个函数把说明包装成完整的上下文用户片段,也就是最终要放进模型上下文里的形式。它不仅给出文本,还会在合适时附上当前目录信息。

数据流:它先判断是否有多个项目环境。多个环境时,正文内部已经有环境标签,所以外层目录留空;单个环境时,它用 single_project_cwd 找到项目当前目录作为外层目录。然后调用 text 得到说明正文,交给 ContextUserInstructions 渲染成最终字符串。

调用关系:它通常是外部真正取最终结果时会调用的方法。它把 text 生成的正文再包一层上下文格式,让下游模型请求可以直接使用。

调用图:调用 3 个内部函数(has_multiple_project_environments, single_project_cwd, text)。

LoadedAgentsMd::user_instructions434–436 ↗
fn user_instructions(&self) -> Option<&UserInstructions>

作用:这个函数返回宿主程序传进来的用户说明。外部如果需要单独查看用户级说明,而不是整段合并后的文本,就会用它。

数据流:它读取对象内部的 user_instructions 字段。如果存在,就返回一个只读引用;如果不存在,就返回空。它不拼接文本,也不修改对象。

调用关系:它是一个查询口,服务其他需要检查原始用户说明的代码。它不参与文件发现流程,只提供已加载数据的访问方式。

LoadedAgentsMd::sources439–448 ↗
fn sources(&self) -> impl Iterator<Item = &AbsolutePathBuf>

作用:这个函数列出所有有文件来源的说明路径。它常用于展示、调试或记录:这次模型到底参考了哪些说明文件。

数据流:它先看用户说明是否有来源路径,再遍历项目说明条目,并通过来源信息取出项目文件路径。内部生成的说明没有文件路径,所以不会出现在结果里。出来的是一个路径迭代器,可以逐个取用。

调用关系:它依赖 InstructionProvenance::path 来从项目说明来源里取路径。它不生成说明文本,而是帮助外部追踪说明从哪里来。

LoadedAgentsMd::has_multiple_project_environments450–464 ↗
fn has_multiple_project_environments(&self) -> bool

作用:这个函数判断项目说明是否来自不止一个环境。这个判断会影响输出格式,因为多个环境需要更清楚的标签。

数据流:它遍历所有说明条目,只关注项目来源的条目。它记住看到的第一个环境编号;如果后面发现另一个不同编号,就返回真。没有项目说明或都来自同一个环境,就返回假。

调用关系:它被 LoadedAgentsMd::textLoadedAgentsMd::render 调用。前者用它选择拼接格式,后者用它决定外层是否还要写单一目录。

调用图:被 2 处调用(render, text)。

LoadedAgentsMd::single_project_cwd466–473 ↗
fn single_project_cwd(&self) -> Option<&AbsolutePathBuf>

作用:这个函数找出单环境情况下的项目当前目录。它用于给最终上下文加一个目录提示,让模型知道这些规则对应哪个工作目录。

数据流:它遍历说明条目,找到第一条项目来源说明,并返回其中记录的当前目录。内部说明会被跳过。如果没有项目说明,就返回空。它不修改数据。

调用关系:它被 LoadedAgentsMd::render 调用。只有当说明不是来自多个环境时,这个目录才适合作为外层目录显示。

调用图:被 1 处调用(render)。

InstructionProvenance::path501–506 ↗
fn path(&self) -> Option<&AbsolutePathBuf>

作用:这个函数从一条说明的来源信息里取出真实文件路径。项目说明有路径,内部说明没有路径。

数据流:进去的是一个来源枚举值。如果它是项目来源,就返回 source_path 的只读引用;如果是内部来源,就返回空。它只是读取信息,不改变任何东西。

调用关系:它主要被 LoadedAgentsMd::sources 使用。sources 靠它把不同类型的来源统一过滤成“哪些说明来自文件”这个结果。

core/src/context/collaboration_mode_instructions.rs源码 ↗
domain_logiccontext building / startup and collaboration mode update

协作模式可以理解成给助手换一种工作方式,比如更主动、更多解释、或按某种规则配合用户。这个文件专门处理协作模式里的“开发者说明”:如果配置里真的有说明文字,它就把这段文字保存下来;如果没有或是空的,就什么也不生成,避免往上下文里塞无意义内容。它还实现了 ContextualUserFragment,也就是“一块会被拼进上下文的文字片段”。这块片段会声明自己的身份是 developer(开发者指令,比普通用户话语更像规则),并用固定的开始和结束标记包起来,方便后续系统准确识别边界。最后,body 会把真正的说明文字交出去。整体上,它像一个信封:把协作模式说明装进去,贴上“开发者指令”的标签,再加上封口标记,送进模型上下文。

函数细节5
CollaborationModeInstructions::from_collaboration_mode12–21 ↗
fn from_collaboration_mode(collaboration_mode: &CollaborationMode) -> Option<Self>

作用:从一个协作模式配置里取出“开发者说明”,并把它变成可放入上下文的指令片段。有人会用它来判断:当前协作模式到底有没有需要告诉模型的额外规则。

数据流:进去的是一个 CollaborationMode,也就是协作模式配置;函数读取其中 settings.developer_instructions 这段可选文字。如果这段文字不存在或为空,就返回 None,表示不用加入任何内容;如果有非空文字,就复制出来,放进 CollaborationModeInstructions 这个小容器里并返回 Some。它不会改动原来的配置。

调用关系:它会在构建初始上下文 build_initial_context 时被用到,也会在协作模式更新 build_collaboration_mode_update_item 时被用到。也就是说,不管是一开始启动对话,还是中途切换协作模式,都靠它把配置里的说明变成模型能看到的上下文片段。

调用图:被 2 处调用(build_collaboration_mode_update_item, build_initial_context)。

CollaborationModeInstructions::role25–27 ↗
fn role(&self) -> &'static str

作用:告诉上下文系统,这段协作模式说明应该以 developer,也就是“开发者指令”的身份出现。这样模型会把它看成规则性更强的说明,而不是普通聊天内容。

数据流:进去的是这个指令片段本身;函数不读取复杂数据,只固定返回字符串 developer。它不产生副作用,也不修改任何东西。

调用关系:当上下文系统准备把这个片段拼进最终消息时,会询问它的角色。这个函数提供角色标签,配合 markers 和 body 一起组成完整的上下文片段。

CollaborationModeInstructions::markers29–31 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:给这段说明提供一对边界标记,也就是“从这里开始”和“到这里结束”。这样后续系统或模型能清楚知道哪一段文字属于协作模式说明。

数据流:进去的是这个指令片段本身;函数把工作交给 type_markers,拿到固定的开始标签和结束标签,然后原样返回。它不改动指令内容。

调用关系:它是 ContextualUserFragment 接口的一部分,通常在拼接上下文时被调用。它内部调用 type_markers,让实例方法和类型级方法使用同一套标记,避免两边写出不一致的标签。

调用图:外部调用 1 个(type_markers)。

CollaborationModeInstructions::type_markers33–35 ↗
fn type_markers() -> (&'static str, &'static str)

作用:返回协作模式说明专用的固定开始标签和结束标签。这个函数不依赖某个具体对象,只是在统一地方说明“这种片段应该用什么标签包起来”。

数据流:没有业务输入;函数直接返回两个常量:COLLABORATION_MODE_OPEN_TAG 和 COLLABORATION_MODE_CLOSE_TAG。结果是一对字符串标记,不会修改任何状态。

调用关系:markers 会调用它来拿边界标记。把标记集中在这里,可以让所有协作模式说明都使用同一种包装方式,方便构建上下文和后续识别。

CollaborationModeInstructions::body37–39 ↗
fn body(&self) -> String

作用:取出真正要给模型看的协作模式说明文字。它提供的是信封里的正文,而不是角色或边界标签。

数据流:进去的是保存了说明文字的 CollaborationModeInstructions;函数复制内部的 instructions 字符串并返回。原对象里的文字仍然保留,不会被拿走或修改。

调用关系:当上下文系统组装完整片段时,会用 role 决定身份,用 markers 决定边界,再用 body 拿正文。这个函数就是最后交出实际指令内容的那一步。

core/src/context/personality_spec_instructions.rs源码 ↗
domain_logic构建初始上下文时

这个文件定义了一个小组件,专门保存并输出“个性/语气要求”。可以把它想成一张贴在对话最前面的便签:上面写着“以后请按这种风格说话”。它保存一段文字 spec,也就是具体的风格说明;然后通过 ContextualUserFragment 这个接口,把这段说明变成系统内部统一能识别的上下文片段。这里有几个关键点:它声明自己的角色是 developer,也就是开发者层面的提示,通常比普通用户一句话更稳定;它还给内容加上 <personality_spec> 这样的开始和结束标记,方便系统或模型分清这块内容的边界;最后 body 会生成真正要放进上下文里的说明文字。没有这个文件,用户要求的“沟通风格变化”就可能只是普通聊天内容,后续生成回复时更容易被忽略或混在别的文本里。

函数细节5
PersonalitySpecInstructions::new9–11 ↗
fn new(spec: impl Into<String>) -> Self

作用:创建一个新的“个性说明”对象,把外面传进来的风格要求保存起来。别人想把用户指定的说话风格放进上下文时,会先用它造出这个对象。

数据流:进去的是一段可转换成字符串的 spec,也就是风格说明文本;函数把它转成真正的 String 并存到结构体里;出来的是一个 PersonalitySpecInstructions 对象,里面已经带着这段说明,没有改动别的东西。

调用关系:它通常在 build_initial_context 构建初始上下文时被调用,也会在 sample_rollout 这类示例流程里用到。它只负责把原始风格文字装好,后面真正把文字展示成上下文片段的工作,会交给 role、markers 和 body 这些接口方法。

调用图:被 2 处调用(build_initial_context, sample_rollout);外部调用 1 个(into)。

PersonalitySpecInstructions::role15–17 ↗
fn role(&self) -> &'static str

作用:告诉系统,这段个性说明应该以什么身份放进上下文里。这里固定返回 developer,意思是把它当作开发者给模型的提示,而不是普通聊天内容。

数据流:它不需要额外输入,只读取当前对象的类型信息;它直接返回字符串 developer;不会修改对象里的风格说明。

调用关系:当系统把这个对象当作 ContextualUserFragment 使用时,会调用它来确定这段内容的身份。它和 markers、body 一起,组成一段完整、带身份和边界的上下文片段。

PersonalitySpecInstructions::markers19–21 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:给这段个性说明提供一对边界标记,告诉系统“这里开始是个性说明,这里结束”。这像给文件夹贴标签,避免内容和别的提示混在一起。

数据流:它不接收额外输入,也不读取 spec 的具体内容;它调用 type_markers 拿到固定的开始标记和结束标记;返回这两个标记,不改动任何状态。

调用关系:它是在系统需要包装这个上下文片段时被调用的。它把具体标记的决定交给 type_markers,这样实例方法和类型级别的固定标记保持一致。

调用图:外部调用 1 个(type_markers)。

PersonalitySpecInstructions::type_markers23–25 ↗
fn type_markers() -> (&'static str, &'static str)

作用:返回这个片段类型固定使用的开始和结束标签:<personality_spec> 和 </personality_spec>。这些标签让后续读取上下文的人或模型知道,中间那段就是个性风格要求。

数据流:它没有输入,也不依赖某个具体对象;它直接产出一对固定字符串;不会产生副作用,也不会修改数据。

调用关系:markers 会调用它来拿标记。把固定标记集中放在这里,可以保证所有 PersonalitySpecInstructions 使用同一套边界格式。

PersonalitySpecInstructions::body27–32 ↗
fn body(&self) -> String

作用:生成真正要放进上下文里的正文,明确告诉模型:用户请求了新的沟通风格,后续消息要遵守这段 personality 说明。

数据流:它读取对象里保存的 spec,也就是具体风格要求;然后用固定说明文字把 spec 包起来,拼成一段更清楚的提示;返回拼好的字符串,不修改原来的 spec。

调用关系:当上下文系统需要拿到这段片段的实际内容时,会调用它。它和 role 提供的身份、markers 提供的边界一起,组成一段完整的“个性说明”上下文。

调用图:外部调用 1 个(format!)。

core/src/context/plugin_instructions.rs源码 ↗
data_modelcross-cutting

这个文件很小,但位置很关键。它定义了一个叫 PluginInstructions 的小盒子,里面只装一段文字 text。项目里可能会把很多种内容拼成一次发给模型的上下文,比如用户消息、系统提示、工具说明等。这个文件负责其中一种:插件提供的额外说明。它实现了 ContextualUserFragment 这个接口,可以理解成“所有能放进上下文里的文字片段都要遵守的格式”。这里规定了三件事:这段文字的角色是 developer;它前后不额外加标记;真正的正文就是保存的 text。类比一下,它像给一张便签贴上固定标签:“这是开发者说明”,然后交给统一装订的人去排版。

函数细节5
PluginInstructions::new9–11 ↗
fn new(text: impl Into<String>) -> Self

作用:创建一个新的插件说明对象。调用者给它一段文字,它把文字收进 PluginInstructions 这个小盒子里,方便后面按统一格式使用。

数据流:进去的是一段可以变成字符串的内容 → 函数调用 into 把它转成真正的 String → 出来的是一个 PluginInstructions,对象内部的 text 保存了这段说明文字。

调用关系:这是这类对象的入口。外部代码想把插件说明放进上下文时,会先用它生成 PluginInstructions;之后上下文拼装流程会通过 role、markers、body 这些接口读取它。

调用图:外部调用 1 个(into)。

PluginInstructions::role15–17 ↗
fn role(&self) -> &'static str

作用:告诉外部:这段插件说明应该用什么身份呈现。这里固定返回 developer,意思是把它当成开发者给模型的指示。

数据流:进去的是当前 PluginInstructions 对象,但它不需要读取 text → 直接返回固定字符串 developer → 不改动任何数据。

调用关系:当统一的上下文拼装代码需要知道这段文字属于谁时,会调用它。它把插件说明放在“开发者指令”这个层级上,避免被误当成普通用户内容。

PluginInstructions::markers19–21 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:告诉外部这段文字前后要不要加特殊边界标记。这里它把工作交给 type_markers,结果是前后都不加任何标记。

数据流:进去的是当前对象 → 函数调用 type_markers 取得这一类片段统一使用的前后标记 → 出来是一对空字符串,表示正文前后不用包额外符号。

调用关系:上下文拼装代码想给片段加外壳时会问它要 markers。它再转去调用 type_markers,这样同一类片段的标记规则集中放在一个地方。

调用图:外部调用 1 个(type_markers)。

PluginInstructions::type_markers23–25 ↗
fn type_markers() -> (&'static str, &'static str)

作用:定义 PluginInstructions 这一类片段的统一边界标记。这里返回两个空字符串,意思是插件说明原样放进去,不额外包起来。

数据流:没有输入对象内容,也不读取 text → 直接给出一对固定值:空开头、空结尾 → 不产生副作用,只返回规则。

调用关系:markers 会调用它来拿标记规则。把规则单独放在这里,方便以后如果要给插件说明加统一前缀或后缀,只改这一处。

PluginInstructions::body27–29 ↗
fn body(&self) -> String

作用:取出插件说明的正文。调用者用它拿到真正要放进上下文、发给模型看的那段文字。

数据流:进去的是当前 PluginInstructions 对象 → 函数读取内部的 text,并复制一份 → 出来的是一个新的 String;原对象里的 text 不变。

调用关系:上下文拼装代码在真正生成消息内容时会调用它。role 决定这段话的身份,markers 决定外壳,body 则提供核心正文。

core/src/context/image_generation_instructions.rs源码 ↗
domain_logiccontext building / request handling

当系统生成图片后,模型需要知道默认保存目录和文件路径,否则后续如果用户说“用刚才那张图”,模型可能找不到它。这个文件就像给模型贴一张便签:图片默认存在某个目录、某个路径;如果要放到别的地方,应该复制一份,除非用户明确要求删除原图。它有两种用法:一种是直接生成一段给扩展端看的提示,并且会检查长度,太长就不放进去,避免提示内容撑得过大;另一种是把这些信息包装成 ImageGenerationInstructions,作为上下文片段塞进对话上下文里。这里的 ContextualUserFragment 可以理解成“要附加给模型看的上下文小纸条”。这个文件的重要点是:它不生成图片,也不保存图片,只负责把保存位置用稳定、清楚的文字告诉模型。

函数细节7
extension_image_generation_output_hint8–14 ↗
fn extension_image_generation_output_hint(
    image_output_dir: impl Display,
    image_output_path: impl Display,
) -> Option<String>

作用:生成一段给模型看的图片保存位置提示,但会先检查这段话会不会太长。有人会用它来安全地把图片路径信息放进模型上下文,避免路径过长导致提示内容膨胀。

数据流:输入是图片输出目录和图片输出路径,这两个值只要求能显示成文字。函数先把它们交给 image_generation_hint 拼成完整提示;然后看提示字节长度是否不超过 1024。长度合格就返回这段提示,太长就返回空值,表示干脆不附加这段提示。

调用关系:它是对外使用的安全入口,先调用 image_generation_hint 做正文,再自己负责“太长就不要”的把关。这样调用方不需要知道提示怎么写,也不需要自己记住长度限制。

调用图:调用 1 个内部函数(image_generation_hint)。

image_generation_hint16–23 ↗
fn image_generation_hint(
    image_output_dir: impl Display,
    image_output_path: impl Display,
) -> String

作用:把图片保存目录和默认图片路径拼成一段固定格式的说明文字。它是这个文件里真正产出提示正文的地方。

数据流:输入是图片输出目录和图片输出路径。函数用格式化字符串把它们插进一段人能读懂的话里,说明生成图片默认保存在哪里,以及如果要换路径应该复制而不是随便移动或删除原图。输出是一整个字符串,不会改动外部状态。

调用关系:extension_image_generation_output_hint 会调用它生成扩展端提示,ImageGenerationInstructions::body 也会调用它生成上下文片段正文。也就是说,它是两条使用路线共用的“写提示文案”的小工厂。

调用图:被 2 处调用(body, extension_image_generation_output_hint);外部调用 1 个(format!)。

ImageGenerationInstructions::new32–37 ↗
fn new(image_output_dir: impl Display, image_output_path: impl Display) -> Self

作用:创建一个 ImageGenerationInstructions 对象,把图片输出目录和路径保存起来,等之后需要放进上下文时再生成提示文字。

数据流:输入是输出目录和输出路径。函数把它们都转成 String,也就是拥有自己内容的普通字符串,然后存进新对象里。输出是这个新对象;它不写文件,也不直接告诉模型,只是先把信息装好。

调用关系:它会在记录图片生成指令时被调用,比如 record_image_generation_instructions 和 handle_output_item_done_records_image_save_history_message 这些流程需要把图片保存信息加入历史或上下文时,会先用它造出这个上下文片段。

调用图:被 2 处调用(handle_output_item_done_records_image_save_history_message, record_image_generation_instructions);外部调用 1 个(to_string)。

ImageGenerationInstructions::role41–43 ↗
fn role(&self) -> &'static str

作用:说明这段上下文在对话里扮演什么身份。这里返回 developer,意思是它像开发者给模型的指令,而不是用户随口说的话。

数据流:函数不需要输入,也不读取对象里的路径。调用后直接返回固定字符串 developer。它不会改动任何数据。

调用关系:这是实现 ContextualUserFragment 这套接口的一部分。外层组装上下文时会问每个片段“你是什么角色”,这个函数就回答它应该作为开发者提示放进去。

ImageGenerationInstructions::markers45–47 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:返回这段上下文前后要不要加特殊标记。这里实际返回空标记,意思是不额外包一层标签。

数据流:函数不使用图片目录或路径。它调用 type_markers 拿到固定的一对标记字符串,然后返回给调用方。结果是两个空字符串,不改动对象。

调用关系:它也是 ContextualUserFragment 接口需要的一个方法。上下文组装器如果想给片段加开始和结束标记,会通过它询问;这里把工作交给 type_markers,保持实例方法和类型级方法一致。

调用图:外部调用 1 个(type_markers)。

ImageGenerationInstructions::type_markers49–51 ↗
fn type_markers() -> (&'static str, &'static str)

作用:给 ImageGenerationInstructions 这个类型定义默认的上下文标记。这里选择不用任何标记,所以返回两个空字符串。

数据流:函数没有输入,也不读取任何状态。它直接输出一对固定值:开始标记为空、结束标记为空。不会产生副作用。

调用关系:ImageGenerationInstructions::markers 会调用它。这样无论是从某个具体对象询问,还是从类型本身询问,得到的标记规则都是同一套。

ImageGenerationInstructions::body53–55 ↗
fn body(&self) -> String

作用:生成这段上下文真正要给模型看的正文,也就是“图片默认保存在哪里、要移动时该怎么做”的说明。

数据流:输入是当前 ImageGenerationInstructions 对象。函数读取对象里保存的 image_output_dir 和 image_output_path,然后交给 image_generation_hint 拼成完整提示字符串。输出是这段提示文字,不修改对象本身。

调用关系:这是 ContextualUserFragment 接口里最核心的方法之一。外层上下文组装流程需要正文时会调用它;它再把具体文案生成工作交给 image_generation_hint,保证和扩展端提示使用同一套说法。

调用图:调用 1 个内部函数(image_generation_hint)。

tui/src/terminal_visualization_instructions.rs源码 ↗
configconfig load / thread setup

这个文件解决的是“终端里怎么更清楚地展示复杂信息”的问题。终端不像网页那样能随便放图片,所以这里准备了一段固定说明,要求回答时如果需要视觉化,就用纯 ASCII 字符画紧凑的表格、树、时间线或流程图。核心函数会先看配置里的功能开关是否启用;没启用,就完全不动原来的控制说明。启用后,它会把这段终端可视化说明追加到已有说明后面;如果原来没有说明,就直接使用这段说明。可以把它理解成:在发给模型的“工作要求”最后贴一张小纸条,提醒它“这里是终端,请用终端友好的方式画图”。

函数细节1
with_terminal_visualization_instructions10–29 ↗
fn with_terminal_visualization_instructions(
    config: &Config,
    control_instructions: Option<String>,
) -> Option<String>

作用:这个函数根据配置决定要不要给模型的控制说明追加“终端可视化规则”。有人创建或恢复一次对话线程时会用它,确保模型知道当前界面是终端,必要时应该用 ASCII 图表表达。

数据流:进去的是一份配置 config,以及一段可能已经存在的 control_instructions。函数先读取配置里的功能开关,看 TerminalVisualizationInstructions 是否启用;如果没启用,就原样返回传入的说明。若启用,它会优先使用传入的说明;如果没有传入,就尝试使用配置里的 developer_instructions。然后它检查这段说明是不是空的:不空就把终端可视化规则接在后面,中间留空行;为空或不存在就只返回终端可视化规则。出来的是 Option<String>,也就是可能存在的一整段最终说明。

调用关系:它不主动启动流程,而是在准备线程参数时被调用:thread_start_params_from_config、thread_resume_params_from_config 和 thread_fork_params_from_config 会在新开对话、恢复对话或分叉对话时请它加工说明文本。它唯一额外做的“交给别人”的事是使用 format! 把已有说明和固定规则拼成一段完整文字。

调用图:被 3 处调用(thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config);外部调用 1 个(format!)。

tui/src/ide_context/prompt.rs源码 ↗
domain_logicrequest handling

这个文件解决的是“聊天窗口和 IDE 状态脱节”的问题。用户在 TUI 里提问时,真正有用的信息往往在编辑器里:当前打开的文件、选中了哪段代码、还有哪些标签页。这里会把这些信息整理成一段固定格式的提示词,放到用户请求前面,并用“## My request for Codex:”当分隔线。这个分隔线很重要,因为桌面端和 IDE 插件也用同一个标记,后面回放历史或只显示用户原始问题时,才能准确切回来。文件还会做两件保护工作:选中的内容太长会截断,打开的标签页太多也会省略,避免提示词被撑爆。若原本的输入里有图片和文字,它只给文字加前缀,尽量不打乱用户提交内容的顺序。

函数细节13
apply_ide_context_to_user_input18–59 ↗
fn apply_ide_context_to_user_input(
    context: &IdeContext,
    items: &mut Vec<UserInput>,
) -> bool

作用:把 IDE 上下文真正加到用户输入里。有人提交问题时会用它,让 Codex 在看到用户问题前,先看到当前文件、选区、打开标签页这些背景。

数据流:进去的是一个 IDE 上下文和一组用户输入项。它先请 render_prompt_context 把上下文变成文字;如果没有任何上下文,就什么也不改并返回 false。若有上下文,它生成前缀和固定分隔线,再找到第一段文字输入:有文字就用 prefixed_text_input 把前缀接到文字前面,并修正文字元素的位置;没有文字就新插入一段只有前缀的文字。最后返回 true,表示输入已经被加料。

调用关系:这是把 IDE 信息接入用户请求的主入口。它先依赖 render_prompt_context 判断有没有内容可塞;如果原输入已有文字,就把细活交给 prefixed_text_input,保证文字里的特殊占位范围不会错位。

调用图:调用 2 个内部函数(prefixed_text_input, render_prompt_context);外部调用 5 个(new, new, format!, replace, unreachable!)。

has_prompt_context61–63 ↗
fn has_prompt_context(context: &IdeContext) -> bool

作用:快速判断当前 IDE 状态是否能生成有用的提示上下文。调用者可以用它决定要不要显示或启用“带 IDE 上下文”的行为。

数据流:进去的是 IDE 上下文。它调用 render_prompt_context 尝试渲染;如果能得到一段文字,就返回 true;如果上下文是空的,就返回 false。它不修改任何东西。

调用关系:它是一个轻量检查口,把真正的判断逻辑交给 render_prompt_context,避免外面重复写一套“有没有当前文件、有没有标签页”的判断。

调用图:调用 1 个内部函数(render_prompt_context)。

extract_prompt_request_with_offset65–74 ↗
fn extract_prompt_request_with_offset(message: &str) -> (&str, usize)

作用:从一整段带 IDE 上下文的消息里,取回真正的用户请求。它还会告诉调用者这段请求在原文里的起始位置,方便后续定位。

数据流:进去的是一整段消息文本。它从最后一个“## My request for Codex:”分隔线开始切,因为消息里可能出现多个同名标记;切到后会去掉请求前后的空白,并计算去掉前导空白后的真实字节位置。出来的是“干净的用户请求”和它在原消息中的偏移量;如果找不到分隔线,就原样返回整段文本和 0。

调用关系:它和 apply_ide_context_to_user_input 使用同一个分隔线。前者负责加上下文,后者负责在需要展示、回放或分析时,把上下文前缀剥掉,只留下用户真正问的话。

prefixed_text_input76–94 ↗
fn prefixed_text_input(prefix: String, text: String, text_elements: Vec<TextElement>) -> UserInput

作用:给一段文字输入加前缀,同时修正里面那些“特殊文字片段”的位置。比如用户写了一个占位符,前面多插了一大段上下文后,占位符的起止位置也必须跟着往后挪。

数据流:进去的是前缀、原文字、以及文字中的 TextElement 列表;TextElement 可以理解成文字里的特殊标记,并带有字节范围。它把前缀拼到文字前面,然后把每个元素的 start 和 end 都加上前缀长度。出来是一条新的 UserInput::Text,内容变长了,内部标记位置也保持正确。

调用关系:它只被 apply_ide_context_to_user_input 调用。主函数负责决定要不要加上下文;这个函数负责最容易出错的细节:别让文字内容和文字标记的位置对不上。

调用图:被 1 处调用(apply_ide_context_to_user_input);外部调用 1 个(format!)。

render_prompt_context96–185 ↗
fn render_prompt_context(context: &IdeContext) -> Option<String>

作用:把 IDE 上下文整理成 Codex 能读懂的一段提示文字。它决定哪些信息写进去、用什么标题、太长时怎么截断。

数据流:进去的是 IdeContext。它依次查看当前文件、选区范围、选中的实际文本、打开的标签页;有内容就按固定标题写成一段文本。选区行列号会从程序常用的 0 起始改成普通人习惯的 1 起始;选中文本超过 40000 个字符会截断;标签页最多写 100 个,总字符数也有限制。出来是 Some(上下文文本),如果完全没东西可写就返回 None。

调用关系:这是本文件的核心格式化机器。apply_ide_context_to_user_input 用它生成要插入的前缀,has_prompt_context 用它判断是否存在上下文,多个测试也直接调用它来确认格式、截断和省略规则没有变。

调用图:被 4 处调用(apply_ide_context_to_user_input, has_prompt_context, render_prompt_context_omits_excess_open_tabs, render_prompt_context_truncates_large_selection);外部调用 3 个(new, format!, from_ref)。

tests::descriptor198–203 ↗
fn descriptor(label: &str, path: &str) -> FileDescriptor

作用:给测试快速造一个文件描述对象。它让测试不用每次手写 label 和 path 的结构体拼装。

数据流:进去的是文件显示名 label 和路径 path。它把两个字符串转成 FileDescriptor 需要的 owned 字符串,出来是一个可放进测试上下文里的文件描述。

调用关系:它只服务测试代码。后面的多个测试用它搭建当前文件和打开标签页,让测试重点放在提示词结果,而不是样板数据创建上。

tests::render_prompt_context_matches_app_format206–236 ↗
fn render_prompt_context_matches_app_format()

作用:检查渲染出来的 IDE 上下文格式是否和桌面应用约定一致。这个测试防止有人无意中改坏跨端共享的提示词格式。

数据流:它先构造一个包含当前文件、选中代码、两个打开标签页的上下文。然后调用 render_prompt_context,并把结果和预期的完整字符串逐字比较。结果一致测试通过,不一致就失败。

调用关系:测试运行器会调用它。它通过 descriptor 准备文件信息,并直接验证 render_prompt_context 这个核心函数的输出格式。

调用图:外部调用 4 个(new, assert_eq!, descriptor, vec!)。

tests::render_prompt_context_omits_empty_context239–246 ↗
fn render_prompt_context_omits_empty_context()

作用:确认没有当前文件、也没有打开标签页时,不会硬塞一段空上下文。这样用户的请求不会被无意义内容污染。

数据流:它创建一个空 IdeContext,然后调用 render_prompt_context。预期结果是 None,表示没有可渲染内容。

调用关系:测试运行器会调用它。它专门覆盖 render_prompt_context 的“空输入”分支,保证 apply_ide_context_to_user_input 遇到空上下文时能正确选择不修改输入。

调用图:外部调用 2 个(new, assert_eq!)。

tests::apply_ide_context_uses_desktop_prompt_request_delimiter249–306 ↗
fn apply_ide_context_uses_desktop_prompt_request_delimiter()

作用:检查给用户输入加上下文时,使用的分隔线是否正是桌面端那条。这个约定关系到不同界面之间能不能正确回放和剥离上下文。

数据流:它准备一个有当前文件的上下文,以及一组用户输入:先是一张本地图片,再是一段文字,文字里还有一个特殊 TextElement。调用 apply_ide_context_to_user_input 后,它检查图片仍在原位置、文字前面加了上下文和分隔线、TextElement 的字节范围也按前缀长度后移。

调用关系:测试运行器会调用它。它间接覆盖 apply_ide_context_to_user_input、render_prompt_context 和 prefixed_text_input 的配合,尤其验证“加前缀但不打乱图片和文字顺序”的行为。

调用图:外部调用 6 个(new, new, assert!, assert_eq!, descriptor, vec!)。

tests::extract_prompt_request_returns_text_after_last_delimiter309–317 ↗
fn extract_prompt_request_returns_text_after_last_delimiter()

作用:确认提取用户请求时,会使用最后一个分隔线后面的内容。这样即使前面上下文或历史里也出现过同样标记,也不会切错。

数据流:它准备一段包含两个“## My request for Codex:”的消息。调用 extract_prompt_request_with_offset 后,预期拿到的是最后一个标记后的“Second”,并且偏移量等于这个词在原文里的位置。

调用关系:测试运行器会调用它。它直接保护 extract_prompt_request_with_offset 的关键规则:从最后一个分隔线切,而不是第一个。

调用图:外部调用 1 个(assert_eq!)。

tests::render_prompt_context_includes_selection_ranges_without_content320–358 ↗
fn render_prompt_context_includes_selection_ranges_without_content()

作用:检查当 IDE 只知道选区位置、没有拿到选中内容时,提示词仍会写出选区范围。这样 Codex 至少知道用户关注文件里的哪几行哪几列。

数据流:它构造两个非空选区范围,但把 active_selection_content 设为空。调用 render_prompt_context 后,预期输出包含“Active selection ranges”,并把两个范围用从 1 开始的行列号写出来。

调用关系:测试运行器会调用它。它验证 render_prompt_context 在没有选中文本时的备用表达方式,防止上下文完全丢失。

调用图:外部调用 5 个(new, new, assert_eq!, descriptor, vec!)。

tests::render_prompt_context_truncates_large_selection361–386 ↗
fn render_prompt_context_truncates_large_selection()

作用:检查超长选中文本会被截断。这个测试防止一次选中太多内容导致提示词巨大、拖慢或超过模型可接受的长度。

数据流:它构造一个选中文本,长度超过 MAX_ACTIVE_SELECTION_CHARS,并在末尾加上 tail。调用 render_prompt_context 后,它检查输出里有截断提示,并确认 tail 没有出现,说明超出部分确实被丢掉了。

调用关系:测试运行器会调用它。它直接测试 render_prompt_context 的长度保护规则,确保大选区不会完整塞进用户请求。

调用图:调用 1 个内部函数(render_prompt_context);外部调用 4 个(new, assert!, format!, descriptor)。

tests::render_prompt_context_omits_excess_open_tabs389–402 ↗
fn render_prompt_context_omits_excess_open_tabs()

作用:检查打开标签页太多时,只写前面允许数量的标签,并提示省略了多少个。这样 Codex 能知道大致环境,但提示词不会被标签页列表占满。

数据流:它创建超过 MAX_OPEN_TABS 数量的打开标签页,然后调用 render_prompt_context。结果应该包含第 99 个标签,不包含第 100 个标签,并出现“2 open tabs omitted”的省略提示。

调用关系:测试运行器会调用它。它直接保护 render_prompt_context 对打开标签页数量的限制,防止未来改动让提示词无限增长。

调用图:调用 1 个内部函数(render_prompt_context);外部调用 1 个(assert!)。

prompts/src/permissions_instructions.rs源码 ↗
domain_logicstartup, context update, cross-cutting

这份文件像一张“操作规矩说明书”的生成器。系统里真正的权限配置通常是程序能看懂的结构,比如沙盒模式、网络开关、哪些目录可写、哪些路径禁止读。外行可以把“沙盒”理解成一个安全围栏:程序只能在围栏允许的地方活动。这个文件的工作,就是把这些冷冰冰的配置,拼成清楚的文字,放进上下文里提醒模型。它先看文件系统权限,判断是完全开放、只能写工作区,还是只读;再看网络是否允许;然后根据审批策略写明什么时候要申请批准。它还会补充可写目录、禁止读取的路径,以及已经批准过的命令前缀。最后生成的 PermissionsInstructions 会作为 developer 角色的上下文片段出现,也就是一段比普通用户消息更像“系统规矩”的说明。

函数细节18
PermissionsInstructions::from_permission_profile65–91 ↗
fn from_permission_profile(
        permission_profile: &PermissionProfile,
        approval_policy: AskForApproval,
        approvals_reviewer: ApprovalsReviewer,
        exec_policy: &Policy,

作用:从完整的权限档案生成一段权限说明文字。别人通常不用自己拼各种权限描述,直接把当前权限档案交给它,它会产出模型能读懂的说明。

数据流:输入是权限档案、审批策略、命令执行策略、当前工作目录,以及两个功能开关。它先从权限档案里取出文件系统沙盒和网络沙盒,再判断沙盒模式、网络是否可用、哪些目录可写、哪些内容禁止读,最后交给内部拼装函数生成 PermissionsInstructions。

调用关系:它是外部流程最常用的入口之一,会被初始上下文构建、权限更新消息、相关测试调用。它自己不直接写大段文字,而是把判断任务交给 sandbox_prompt_from_policy、network_access_from_policy、denied_reads_text,再把结果交给 PermissionsInstructions::from_permissions_with_network_and_denied_reads 统一组装。

调用图:调用 5 个内部函数(denied_reads_text, network_access_from_policy, sandbox_prompt_from_policy, file_system_sandbox_policy, network_sandbox_policy);被 5 处调用(build_permissions_update_item, build_initial_context, permissions_message_includes_writable_roots, builds_permissions_from_profile, builds_permissions_from_profile_with_denied_reads);外部调用 1 个(from_permissions_with_network_and_denied_reads)。

PermissionsInstructions::from_permissions_with_network98–111 ↗
fn from_permissions_with_network(
        sandbox_mode: SandboxMode,
        network_access: NetworkAccess,
        config: PermissionsPromptConfig<'_>,
        writable_roots: Option<Vec<WritableRoot

作用:这是测试用的简化入口,用指定的沙盒模式和网络状态生成权限说明。它不处理“禁止读取”的额外说明,方便测试某个单独场景。

数据流:输入是沙盒模式、网络访问状态、审批配置和可写目录。它把这些原样转交给更完整的拼装函数,并把禁止读取内容设为没有,输出一份 PermissionsInstructions。

调用关系:它主要被测试代码调用,用来验证不同审批策略、联网开关、request_permissions 工具提示是否写对。真正干活的是 PermissionsInstructions::from_permissions_with_network_and_denied_reads。

调用图:被 7 处调用(builds_permissions_with_network_access_override, includes_request_permission_rule_instructions_for_on_request_when_enabled, includes_request_permissions_tool_instructions_for_on_failure_when_enabled, includes_request_permissions_tool_instructions_for_on_request_when_tool_is_enabled, includes_request_permissions_tool_instructions_for_unless_trusted_when_enabled, includes_request_rule_instructions_for_on_request, on_request_includes_tool_guidance_alongside_inline_permission_guidance_when_both_exist);外部调用 1 个(from_permissions_with_network_and_denied_reads)。

PermissionsInstructions::from_permissions_with_network_and_denied_reads113–142 ↗
fn from_permissions_with_network_and_denied_reads(
        sandbox_mode: SandboxMode,
        network_access: NetworkAccess,
        config: PermissionsPromptConfig<'_>,
        writable_roots: Option

作用:把沙盒说明、审批说明、可写目录说明、禁止读取说明拼成最终文本。它是这个文件里真正的“总装工位”。

数据流:输入是沙盒模式、网络状态、审批配置、可写目录列表,以及可选的禁止读取说明。它依次生成每一段文字,用 append_section 保证段落之间有换行,最后确保整段文字以换行结尾,并放进 PermissionsInstructions 里返回。

调用关系:它被 from_permission_profile 和测试辅助入口调用。它会调用 sandbox_text 生成沙盒段落,调用 approval_text 生成审批段落,调用 writable_roots_text 生成可写目录段落,并用 append_section 把它们安全地接起来。

调用图:调用 4 个内部函数(append_section, approval_text, sandbox_text, writable_roots_text);外部调用 1 个(new)。

PermissionsInstructions::role146–148 ↗
fn role(&self) -> &'static str

作用:告诉上下文系统,这段权限说明应该以 developer 角色出现。简单说,它把这段话标成“开发者级别的规矩”,而不是普通聊天内容。

数据流:它不需要输入,也不读取复杂状态。调用后固定返回字符串 developer,不改动任何东西。

调用关系:它来自 ContextualUserFragment 接口的实现。上下文系统在插入这段权限说明时会问它“你是什么角色”,它就回答 developer。

PermissionsInstructions::markers150–152 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:给这段权限说明提供开头和结尾标记。这样上下文里的人和程序都能看出:这一块是专门讲权限的。

数据流:它不接收业务输入。它调用 type_markers,返回一对固定标记:开始标记和结束标记。

调用关系:它也是 ContextualUserFragment 接口的一部分。上下文系统需要包住正文时会调用它,而具体标记由 PermissionsInstructions::type_markers 提供。

调用图:外部调用 1 个(type_markers)。

PermissionsInstructions::type_markers154–156 ↗
fn type_markers() -> (&'static str, &'static str)

作用:定义权限说明块的固定边界标记。可以把它理解成文件夹标签,告诉读者“这里开始和结束都是权限说明”。

数据流:它没有输入,固定输出两个字符串:<permissions instructions> 和 </permissions instructions>。它不修改任何状态。

调用关系:PermissionsInstructions::markers 会使用它。这样所有权限说明都用同一套标记,不会每个地方各写一份导致不一致。

PermissionsInstructions::body158–160 ↗
fn body(&self) -> String

作用:取出最终生成好的权限说明正文。调用者需要把这段说明放进上下文时,就用它拿到文字。

数据流:输入是当前 PermissionsInstructions 对象。它读取里面保存的 text,并返回一份字符串副本,不改变原对象。

调用关系:这是 ContextualUserFragment 接口中的正文出口。上下文系统通过它拿到实际要展示给模型看的权限说明。

sandbox_prompt_from_policy163–177 ↗
fn sandbox_prompt_from_policy(
    file_system_policy: &FileSystemSandboxPolicy,
    cwd: &Path,
) -> (SandboxMode, Option<Vec<WritableRoot>>)

作用:把文件系统权限策略翻译成更容易写进提示词的沙盒模式。它会判断是完全开放、工作区可写,还是只读。

数据流:输入是文件系统沙盒策略和当前工作目录。它先检查是否拥有全磁盘写权限;如果有,就返回 danger-full-access。否则它取出相对当前目录计算后的可写根目录;没有可写目录就返回 read-only,有则返回 workspace-write 和这些目录。

调用关系:PermissionsInstructions::from_permission_profile 会调用它来决定沙盒说明该写哪一种。它依赖权限策略对象提供的 has_full_disk_write_access 和 get_writable_roots_with_cwd 来做判断。

调用图:调用 2 个内部函数(get_writable_roots_with_cwd, has_full_disk_write_access);被 1 处调用(from_permission_profile)。

network_access_from_policy179–185 ↗
fn network_access_from_policy(network_policy: NetworkSandboxPolicy) -> NetworkAccess

作用:把网络沙盒策略翻译成“网络可用”或“网络受限”这两个简单结果。这样后面写提示词时不用理解底层策略细节。

数据流:输入是网络沙盒策略。它检查策略是否启用网络访问,启用就输出 Enabled,否则输出 Restricted。

调用关系:PermissionsInstructions::from_permission_profile 会调用它,然后把结果交给 sandbox_text,让沙盒说明里能明确写出当前能不能联网。

调用图:调用 1 个内部函数(is_enabled);被 1 处调用(from_permission_profile)。

append_section187–192 ↗
fn append_section(text: &mut String, section: &str)

作用:安全地把一段新文字接到已有文本后面,避免两段粘在一起。它是一个小工具,专门处理段落换行。

数据流:输入是一段可修改的总文本和一段要追加的内容。它先检查总文本末尾有没有换行,没有就补一个,再把新段落接上;输出体现在原来的总文本被改长了。

调用关系:PermissionsInstructions::from_permissions_with_network_and_denied_reads 会反复调用它来拼装各个说明段落。它让主流程不用到处写换行处理。

调用图:被 1 处调用(from_permissions_with_network_and_denied_reads)。

approval_text194–247 ↗
fn approval_text(
    approval_policy: AskForApproval,
    approvals_reviewer: ApprovalsReviewer,
    exec_policy: &Policy,
    exec_permission_approvals_enabled: bool,
    request_permissions_tool_en

作用:根据审批策略生成“什么时候要请求批准”的说明。审批就是在模型要做敏感操作前,是否需要让用户或审查器点头。

数据流:输入是审批模式、审批审查方式、命令执行策略,以及两个权限申请相关开关。它按不同模式选择对应模板:从不申请、非可信时申请、失败时申请、按请求申请,或更细粒度的 granular 模式;必要时加入 request_permissions 工具说明、已批准命令前缀、自动审查提示,最后输出一段审批说明文字。

调用关系:它被总装函数 PermissionsInstructions::from_permissions_with_network_and_denied_reads 调用。遇到 granular 细粒度审批时,它会把具体分类说明交给 granular_instructions 生成。

调用图:调用 1 个内部函数(granular_instructions);被 1 处调用(from_permissions_with_network_and_denied_reads);外部调用 1 个(format!)。

sandbox_text249–259 ↗
fn sandbox_text(mode: SandboxMode, network_access: NetworkAccess) -> String

作用:生成沙盒模式说明文字,并把网络状态填进模板。模板可以理解成预先写好的说明卡片,这里只负责选卡片和填空。

数据流:输入是沙盒模式和网络访问状态。它根据模式选择对应模板,把网络状态转成字符串并填入模板里的 network_access 位置,输出完整的沙盒说明文字。

调用关系:它被 PermissionsInstructions::from_permissions_with_network_and_denied_reads 调用,是最终权限说明的第一大块内容来源。模板在文件顶部用 LazyLock 延迟解析,也就是第一次用到时才准备好。

调用图:被 1 处调用(from_permissions_with_network_and_denied_reads);外部调用 2 个(as_str, to_string)。

writable_roots_text261–277 ↗
fn writable_roots_text(writable_roots: Option<Vec<WritableRoot>>) -> Option<String>

作用:把可写目录列表写成一句人能看懂的话。这样模型知道哪些地方可以改文件,哪些地方不要乱碰。

数据流:输入是一个可能为空的可写根目录列表。没有列表或列表为空就返回空结果;有内容时先按路径排序,再把每个路径包成反引号格式,最后输出“可写根目录是……”或“可写根目录有……”这样的句子。

调用关系:它被总装函数 PermissionsInstructions::from_permissions_with_network_and_denied_reads 调用。只有 workspace-write 这类存在可写目录的场景,才会把它生成的文字追加进最终说明。

调用图:被 1 处调用(from_permissions_with_network_and_denied_reads);外部调用 1 个(format!)。

denied_reads_text279–299 ↗
fn denied_reads_text(file_system_policy: &FileSystemSandboxPolicy, cwd: &Path) -> Option<String>

作用:生成“这些文件或匹配规则禁止读取”的说明。它提醒模型:这些不是临时缺权限,而是政策禁止,不要申请升级去读。

数据流:输入是文件系统权限策略和当前工作目录。它取出不可读的根路径和不可读的 glob 规则;glob 是一种用通配符匹配文件名的规则,比如匹配一批文件。它把这些条目整理成项目列表;如果没有禁止项就返回空结果,否则输出一段禁止读取说明。

调用关系:PermissionsInstructions::from_permission_profile 会调用它,把生成的文字交给总装函数追加。它依赖权限策略对象提供 get_unreadable_roots_with_cwd 和 get_unreadable_globs_with_cwd。

调用图:调用 2 个内部函数(get_unreadable_globs_with_cwd, get_unreadable_roots_with_cwd);被 1 处调用(from_permission_profile);外部调用 1 个(format!)。

approved_command_prefixes_text301–304 ↗
fn approved_command_prefixes_text(exec_policy: &Policy) -> Option<String>

作用:把已经批准过的命令前缀整理成可展示的文字。命令前缀可以理解成“以这些开头的命令已经被允许”。

数据流:输入是命令执行策略。它读取其中已允许的前缀列表,交给 format_allow_prefixes 格式化;如果格式化结果为空,就返回空结果,否则返回这段前缀文字。

调用关系:granular_instructions 会调用它,用来在细粒度审批说明里补充“这些命令规则已经批准”。approval_text 的 on-request 分支里也会加入同类信息。

调用图:调用 1 个内部函数(format_allow_prefixes);被 1 处调用(granular_instructions);外部调用 1 个(get_allowed_prefixes)。

granular_prompt_intro_text306–308 ↗
fn granular_prompt_intro_text() -> &'static str

作用:提供 granular 细粒度审批说明的固定开头。granular 的意思是把审批拆成多个类别,分别允许或拒绝。

数据流:它没有输入,固定返回一段标题和解释文字,说明哪些类别设为 false 会被自动拒绝,而不是弹出询问用户。

调用关系:granular_instructions 会把它作为第一段内容。这样细粒度审批说明每次都用同一个清楚的开场。

request_permissions_tool_prompt_section310–312 ↗
fn request_permissions_tool_prompt_section() -> &'static str

作用:提供 request_permissions 工具的说明文字。这个工具让模型可以在之后运行命令前,先申请具体的网络或文件系统权限。

数据流:它没有输入,固定返回一段说明:当前会话里有 request_permissions 工具,应该只申请任务真正需要的具体权限。

调用关系:granular_instructions 会在允许该工具时调用它。approval_text 的部分审批模式也会把这段说明加入最终审批文字。

调用图:被 1 处调用(granular_instructions)。

granular_instructions314–384 ↗
fn granular_instructions(
    granular_config: GranularApprovalConfig,
    exec_policy: &Policy,
    exec_permission_approvals_enabled: bool,
    request_permissions_tool_enabled: bool,
) -> String

作用:为 granular 细粒度审批模式生成完整说明。它会清楚列出哪些审批类别还能询问用户,哪些会被自动拒绝。

数据流:输入是细粒度审批配置、命令执行策略,以及两个权限申请功能开关。它逐项检查 sandbox_approval、rules、skill_approval、request_permissions、mcp_elicitations 等类别是否允许,把允许的放进一组列表,把拒绝的放进另一组列表;如果命令权限申请可用,就加入相关规则;如果 request_permissions 工具可用且允许,就加入工具说明;最后可能补上已批准命令前缀,并把所有段落合成一段文字输出。

调用关系:approval_text 在遇到 AskForApproval::Granular 时会调用它。它又会调用 granular_prompt_intro_text、request_permissions_tool_prompt_section 和 approved_command_prefixes_text,把细粒度审批这一整块说明拼完整。

调用图:调用 7 个内部函数(approved_command_prefixes_text, request_permissions_tool_prompt_section, allows_mcp_elicitations, allows_request_permissions, allows_rules_approval, allows_sandbox_approval, allows_skill_approval);被 1 处调用(approval_text);外部调用 2 个(format!, vec!)。

技能发现和注入

此组选取被引用的技能,注入显式技能内容,并将可用技能目录渲染到提示词可见上下文中。

ext/skills/src/selection.rs源码 ↗
domain_logicrequest handling

用户可能用很多方式指定技能:直接上传一个技能、在消息里提到一个技能路径,或者在普通文字里写出技能名。这个文件就像一个“点名登记员”:它先看哪些输入本身就是技能或技能路径,再扫描文字里的工具/技能提及,最后去技能目录里找对应的、已经启用的技能。它还会做两件重要的小保护:第一,同一个技能不会被重复加入,因为它用“来源 authority + 包 id”当唯一身份证;第二,如果某个名字已经跟一个明确路径绑定过,就不会再用这个普通名字去误选另一个同名技能。这里的 skill:// 前缀会被去掉再比较,指向 SKILL.md 的文件路径也会被认作技能路径。最终输出的是一组真正选中的技能条目,供后续流程把这些技能注入到对话或任务里。

函数细节7
collect_explicit_skill_mentions13–68 ↗
fn collect_explicit_skill_mentions(
    inputs: &[UserInput],
    catalog: &SkillCatalog,
) -> Vec<SkillCatalogEntry>

作用:这是本文件的主入口,用来从一批用户输入中挑出用户明确提到的技能。别人调用它时,只需要给它用户输入和技能目录,它就返回应当启用的技能列表。

数据流:进去的是用户输入列表和技能目录。它先检查结构化输入里有没有技能或技能路径,把这些路径拿去匹配目录;然后再扫描普通文字,用 extract_tool_mentions 找出文字里像工具/技能提及的内容;路径类提及会按路径匹配,普通名字会按技能名匹配。过程中它记录已经选过的技能,避免重复;也记录已经被路径占用的名字,避免同名误选。出来的是去重后的 SkillCatalogEntry 列表,原目录本身不会被改动。

调用关系:它由上层的 contribute 调用,通常是在系统准备把技能内容加入当前请求时发生。它自己负责统筹整个挑选流程:遇到路径就交给 select_by_path,遇到文字提及时会用 path_is_skill 判断是不是技能路径,用 normalize_skill_path 统一路径写法,用 push_selected 把确认的技能安全加入结果。

调用图:调用 5 个内部函数(extract_tool_mentions, normalize_skill_path, path_is_skill, push_selected, select_by_path);被 1 处调用(contribute);外部调用 2 个(new, new)。

select_by_path70–82 ↗
fn select_by_path(
    catalog: &SkillCatalog,
    path: &str,
    seen: &mut HashSet<SkillCatalogEntryKey>,
    selected: &mut Vec<SkillCatalogEntry>,
)

作用:这个函数按路径来选技能。用户给了一个技能路径时,它负责在技能目录里找所有启用且路径匹配的技能。

数据流:进去的是技能目录、一个路径、已见过的技能集合、以及当前已选列表。它先把路径里的 skill:// 前缀去掉,得到统一格式;然后逐个查看目录里已启用的技能,请 entry_matches_path 判断这个技能是不是对得上;匹配上的就交给 push_selected 加入结果。出来时,已选列表可能增加了技能,已见集合也会更新。

调用关系:它只被 collect_explicit_skill_mentions 调用,是主流程里“按路径找技能”的小工。它不直接决定如何去重,而是把加入结果这件事交给 push_selected,把具体是否匹配交给 entry_matches_path

调用图:调用 3 个内部函数(entry_matches_path, normalize_skill_path, push_selected);被 1 处调用(collect_explicit_skill_mentions)。

push_selected84–93 ↗
fn push_selected(
    entry: &SkillCatalogEntry,
    seen: &mut HashSet<SkillCatalogEntryKey>,
    selected: &mut Vec<SkillCatalogEntry>,
)

作用:这个函数负责把一个技能加入已选列表,但前提是它之前没有被加入过。它像门口盖章的人,防止同一个技能重复进场。

数据流:进去的是一个技能条目、记录已选技能身份的集合、以及结果列表。它先用 SkillCatalogEntryKey::from 从技能里取出唯一身份,也就是技能来源和包 id;如果这个身份还没出现过,就把技能复制一份放进结果列表,并把身份记下来。出来时,列表可能多一个技能,也可能因为重复而不变。

调用关系collect_explicit_skill_mentionsselect_by_path 都会用它来添加技能。它把“去重”这件事集中到一个地方,避免主流程和路径匹配流程各自写一套重复判断。

调用图:调用 1 个内部函数(from);被 2 处调用(collect_explicit_skill_mentions, select_by_path);外部调用 1 个(clone)。

entry_matches_path95–102 ↗
fn entry_matches_path(entry: &SkillCatalogEntry, path: &str) -> bool

作用:这个函数判断某个技能条目是否和给定路径指向的是同一个技能。它解决的是“同一个技能可能有多种地址写法”的问题。

数据流:进去的是一个技能条目和一个已经标准化过的路径。它会比较三个可能的地址:技能的主提示文件路径、技能包 id、以及展示用路径;展示用路径如果带有 skill://,也会先统一格式再比较。出来的是一个布尔值:匹配就是 true,不匹配就是 false

调用关系:它被 select_by_path 调用,用来做每个目录条目的具体判断。select_by_path 负责遍历目录,它负责回答“这一项是不是目标”。

调用图:被 1 处调用(select_by_path)。

path_is_skill104–110 ↗
fn path_is_skill(path: &str) -> bool

作用:这个函数判断一个路径看起来是不是技能路径。它让代码能识别两类常见写法:skill://... 形式,或者指向名叫 SKILL.md 的文件。

数据流:进去的是一段路径字符串。它先看路径是否以 skill:// 开头;如果不是,就取最后的文件名,看它是不是 SKILL.md,大小写不敏感。出来的是 truefalse,表示这段路径是否应该按技能来处理。

调用关系:它被 collect_explicit_skill_mentions 调用,用在两处:一是判断结构化提及里的路径是不是技能,二是判断普通文字里解析出的路径是不是技能。只有通过这个判断的路径,后面才会继续拿去匹配技能目录。

调用图:被 1 处调用(collect_explicit_skill_mentions)。

normalize_skill_path112–114 ↗
fn normalize_skill_path(path: &str) -> &str

作用:这个函数把技能路径统一成便于比较的样子。具体来说,它会去掉开头的 skill://,让 skill://abcabc 可以按同一种写法比较。

数据流:进去的是一段路径字符串。它检查开头有没有 skill://;有就返回去掉前缀后的部分,没有就原样返回。它不创建新的路径对象,只返回原字符串中的一段或原字符串本身。

调用关系collect_explicit_skill_mentions 在处理文字里的技能路径时会用它,select_by_path 在按路径匹配前也会用它。它保证后面的 entry_matches_path 不会因为一个路径多了协议前缀就匹配失败。

调用图:被 2 处调用(collect_explicit_skill_mentions, select_by_path)。

SkillCatalogEntryKey::from123–128 ↗
fn from(entry: &SkillCatalogEntry) -> Self

作用:这个函数从一个技能条目里提取“唯一身份证”。这个身份证只看技能来源和技能包 id,用来判断两个条目是不是同一个技能。

数据流:进去的是一个技能目录条目。它复制条目里的 authority,也就是技能来自哪里;再复制条目里的 id,也就是技能包标识;然后组成一个 SkillCatalogEntryKey 返回。它不改动原技能条目。

调用关系:它由 push_selected 调用。push_selected 需要先拿到这个身份证,才能把它放进 HashSet,也就是一种专门用来快速判断“见没见过”的集合,从而完成去重。

调用图:被 1 处调用(push_selected)。

core-skills/src/injection.rs源码 ↗
domain_logicrequest handling

可以把这个文件理解成“技能点名和上菜”的流程。用户输入里可能有结构化选择,也可能只是写了类似 $skill-name 的文字,甚至是带链接的 [$skill](path)。这里先从这些输入里找出真正被点名的技能,再排除被禁用的、重复的、名字有歧义的技能。选好后,它会去读对应的 SKILL.md 文件,把文件内容包装成可注入的对象;如果读取失败,就留下警告,同时记录遥测和分析事件。它还特别处理路径格式,比如 skill:// 前缀、Windows 反斜杠,防止同一个技能因为写法不同被当成两个。

函数细节22
InjectedHostSkillPrompts::insert_path43–47 ↗
fn insert_path(&mut self, path: impl Into<String>)

作用:把一个已经由扩展注入过的宿主技能路径记下来,后面就不会再重复发送同一份 SKILL.md 内容。它同时保存原始写法和标准化写法,避免路径格式不同导致漏判。

数据流:进去的是一个路径字符串 → 函数先把它转成自己的字符串,再调用路径标准化,把 skill:// 这类前缀和反斜杠差异处理掉 → 出来没有返回值,但内部的路径集合多了原始路径和标准化路径两条记录。

调用关系:它会调用 normalize_host_skill_path 做路径统一。后续 InjectedHostSkillPrompts::contains_path 会拿待检查路径来和这里存下的记录比对,用来阻止重复注入。

调用图:调用 1 个内部函数(normalize_host_skill_path);外部调用 1 个(into)。

InjectedHostSkillPrompts::is_empty49–51 ↗
fn is_empty(&self) -> bool

作用:检查当前有没有记录任何已经注入过的宿主技能路径。调用者可以用它快速判断是否需要做去重检查。

数据流:进去不需要额外输入,只读取对象内部的路径集合 → 判断集合里有没有元素 → 返回 true 或 false,不改动任何数据。

调用关系:它是 InjectedHostSkillPrompts 这个小登记簿的状态查询函数,通常在外层流程想知道“扩展是否已经注入过技能”时使用。

InjectedHostSkillPrompts::contains_path53–55 ↗
fn contains_path(&self, path: &str) -> bool

作用:判断某个路径是不是已经登记为注入过。它会同时看原始路径和标准化路径,防止因为斜杠或 skill:// 前缀不同而误以为没注入过。

数据流:进去的是一个待检查路径 → 函数先直接查一次,再把路径标准化后查一次 → 返回是否命中,不修改内部集合。

调用关系:它调用 normalize_host_skill_path 来统一路径写法。它和 insert_path 配合,一个负责登记,一个负责查重。

调用图:调用 1 个内部函数(normalize_host_skill_path)。

build_skill_injections58–111 ↗
async fn build_skill_injections(
    mentioned_skills: &[SkillMetadata],
    loaded_skills: Option<&SkillLoadOutcome>,
    otel: Option<&SessionTelemetry>,
    analytics_client: &AnalyticsEventsClient

作用:把已经选中的技能真正变成“可注入内容”:读取每个技能的 SKILL.md 文件,成功就收集内容,失败就收集警告。它还顺手记录统计事件,让系统知道哪些技能被显式使用了。

数据流:进去的是被点名的技能列表、已加载技能信息、遥测对象、分析客户端和追踪上下文 → 如果列表为空就直接返回空结果;否则逐个找到该技能该用哪个文件系统,读取 SKILL.md,成功时生成 SkillInjection 并记录一次成功指标,失败时写入警告并记录失败指标 → 出来是 SkillInjections,里面有可注入的内容和警告,同时分析客户端收到一批技能调用记录。

调用关系:它是“选中技能之后”的核心步骤。它会调用 emit_skill_injected_metric 记录遥测,也会调用分析客户端的 track_skill_invocations 上报使用情况;路径会通过 PathUri::from_abs_path 转成文件系统能读的形式。

调用图:调用 3 个内部函数(track_skill_invocations, emit_skill_injected_metric, from_abs_path);外部调用 6 个(new, with_capacity, is_empty, len, default, format!)。

normalize_host_skill_path113–115 ↗
fn normalize_host_skill_path(path: &str) -> String

作用:把宿主技能路径整理成更容易比较的统一格式。主要处理 skill:// 前缀和 Windows 风格反斜杠。

数据流:进去的是路径文本 → 先交给 normalize_skill_path 去掉可能存在的 skill:// 前缀,再把反斜杠换成正斜杠 → 返回一个新的标准化字符串。

调用关系:它被 InjectedHostSkillPrompts::insert_path 和 InjectedHostSkillPrompts::contains_path 使用,保证登记和查询时用的是同一套路径口径。

调用图:调用 1 个内部函数(normalize_skill_path);被 2 处调用(contains_path, insert_path)。

emit_skill_injected_metric117–131 ↗
fn emit_skill_injected_metric(
    otel: Option<&SessionTelemetry>,
    skill: &SkillMetadata,
    status: &str,
)

作用:记录一次“某个技能注入成功或失败”的遥测指标。遥测可以理解成系统运行时的仪表盘数据,方便之后排查和统计。

数据流:进去的是可选的遥测对象、技能信息和状态文字 → 如果没有遥测对象就什么也不做;如果有,就给计数器加 1,并带上状态和技能名标签 → 没有返回值,只影响外部遥测系统。

调用关系:它只被 build_skill_injections 调用。每次读取技能文件成功或失败时,build_skill_injections 都会让它记一笔。

调用图:被 1 处调用(build_skill_injections)。

collect_explicit_skill_mentions144–201 ↗
fn collect_explicit_skill_mentions(
    inputs: &[UserInput],
    skills: &[SkillMetadata],
    disabled_paths: &HashSet<AbsolutePathBuf>,
    connector_slug_counts: &HashMap<String, usize>,
) -> Vec<

作用:从用户输入中找出用户明确点名的技能。它既看结构化的技能选择,也扫描普通文本里的 $技能名 或带链接写法。

数据流:进去的是用户输入列表、当前可用技能、被禁用的路径、连接器名称数量 → 先计算技能名是否重名,再处理结构化 Skill 输入,按路径精确匹配;然后处理文本输入,抽出提及内容并选择技能,同时跳过禁用、重复和有歧义的名字 → 返回按既定顺序排列的技能列表。

调用关系:它是“用户说要用哪些技能”的入口函数。它调用 extract_tool_mentions 从文本里找提及,再调用 select_skills_from_mentions 把提及转换成真正的 SkillMetadata。

调用图:调用 3 个内部函数(extract_tool_mentions, select_skills_from_mentions, relative_to_current_dir);外部调用 3 个(new, new, build_skill_name_counts)。

ToolMentions::is_empty217–219 ↗
fn is_empty(&self) -> bool

作用:判断一次文本扫描有没有找到任何工具或技能提及。这样后续选择流程可以在没有提及时立刻跳过。

数据流:进去不需要额外输入,只查看 ToolMentions 里的名字集合和路径集合 → 两者都空就返回 true,否则返回 false → 不改动数据。

调用关系:它被 select_skills_from_mentions 调用,用作快速出口,避免在没有提及时还遍历所有技能。

调用图:被 1 处调用(select_skills_from_mentions)。

ToolMentions::plain_names221–223 ↗
fn plain_names(&self) -> impl Iterator<Item = &'a str> + '_

作用:拿到普通 $name 形式提到的名字。这里的“普通”是指没有附带资源路径链接的写法。

数据流:进去不需要额外输入,只读取内部 plain_names 集合 → 把里面的名字一个个作为迭代结果交出去 → 不修改集合。

调用关系:它给外部代码提供只读访问。虽然本文件里的选择逻辑直接访问了集合字段,但这个函数让其他地方可以用更安全的方式读取普通名字。

ToolMentions::paths225–227 ↗
fn paths(&self) -> impl Iterator<Item = &'a str> + '_

作用:拿到文本里链接形式提到的资源路径,比如 [$skill](skill://...) 里的括号路径。路径可以用来做比名字更准确的匹配。

数据流:进去不需要额外输入,只读取内部 paths 集合 → 把路径逐个作为迭代结果交出去 → 不修改数据。

调用关系:它被 select_skills_from_mentions 调用。选择技能时会先用这些路径做精确匹配,再考虑普通名字匹配。

调用图:被 1 处调用(select_skills_from_mentions)。

tool_kind_for_path245–257 ↗
fn tool_kind_for_path(path: &str) -> ToolMentionKind

作用:判断一个资源路径指的是哪类工具:应用、MCP、插件、技能,还是别的东西。MCP 是一种外部工具连接协议,这里只需要知道它不是技能。

数据流:进去的是路径字符串 → 按前缀检查 app://mcp://plugin://skill://,如果没有前缀再看文件名是不是 SKILL.md → 返回对应的 ToolMentionKind 枚举值。

调用关系:它会调用 is_skill_filename 辅助判断普通文件路径是否像技能文件。extract_tool_mentions_with_sigil 和 select_skills_from_mentions 会借它排除应用、MCP、插件路径,避免把它们误当技能。

调用图:调用 1 个内部函数(is_skill_filename)。

is_skill_filename259–262 ↗
fn is_skill_filename(path: &str) -> bool

作用:判断路径最后一段文件名是不是 SKILL.md。这样即使没有 skill:// 前缀,也能识别常见的技能说明文件路径。

数据流:进去的是路径字符串 → 按正斜杠或反斜杠切出最后的文件名 → 忽略大小写比较它是不是 SKILL.md → 返回 true 或 false。

调用关系:它只被 tool_kind_for_path 调用,是判断“这个路径是不是技能”的补充规则。

调用图:被 1 处调用(tool_kind_for_path)。

app_id_from_path264–267 ↗
fn app_id_from_path(path: &str) -> Option<&str>

作用:从 app://... 形式的路径里取出应用 ID。空的应用 ID 会被当成无效。

数据流:进去的是路径字符串 → 尝试去掉 app:// 前缀,并检查剩下内容不是空字符串 → 成功返回应用 ID,失败返回 None。

调用关系:它是给其他流程使用的小工具,用来把应用资源路径拆出真正的名字;本文件里没有进一步调用它。

plugin_config_name_from_path269–272 ↗
fn plugin_config_name_from_path(path: &str) -> Option<&str>

作用:从 plugin://... 形式的路径里取出插件配置名。这样别的代码可以知道链接指向哪个插件配置。

数据流:进去的是路径字符串 → 尝试去掉 plugin:// 前缀,并确认剩余内容不为空 → 成功返回配置名,失败返回 None。

调用关系:它是插件路径解析的小工具,供外部流程使用;本文件中的提及扫描会识别插件路径,但不会在这里展开插件配置。

normalize_skill_path274–276 ↗
fn normalize_skill_path(path: &str) -> &str

作用:去掉技能路径开头的 skill:// 前缀。这样带前缀和不带前缀的同一条路径可以放在一起比较。

数据流:进去的是路径字符串 → 如果以 skill:// 开头就返回去掉前缀后的部分,否则返回原字符串切片 → 不分配新的路径对象。

调用关系:它被 normalize_host_skill_path 调用,也在选择技能时用于把链接路径变成普通文件路径再比较。

调用图:被 1 处调用(normalize_host_skill_path)。

extract_tool_mentions283–285 ↗
fn extract_tool_mentions(text: &str) -> ToolMentions<'_>

作用:从一段普通文本里找出默认写法的工具提及,也就是用项目规定的符号(通常是 $)开头的名字。它是更通用扫描函数的默认入口。

数据流:进去的是一段文本 → 把文本和默认提及符号交给 extract_tool_mentions_with_sigil → 返回 ToolMentions,里面有名字、路径和普通名字。

调用关系:它被 collect_explicit_skill_mentions 调用,用来扫描用户文本输入。真正的逐字节解析工作交给 extract_tool_mentions_with_sigil。

调用图:调用 1 个内部函数(extract_tool_mentions_with_sigil);被 2 处调用(collect_explicit_skill_mentions, collect_explicit_skill_mentions)。

extract_tool_mentions_with_sigil287–348 ↗
fn extract_tool_mentions_with_sigil(text: &str, sigil: char) -> ToolMentions<'_>

作用:按指定的起始符号扫描文本,找出 $name[$name](path) 这两类工具提及。它还会故意忽略常见环境变量,比如 $PATH,避免误判。

数据流:进去的是文本和一个提及符号 → 从头扫描字节;遇到链接写法就调用 parse_linked_tool_mention,遇到普通符号就读取后面的合法名字;对环境变量名会跳过;对应用、MCP、插件链接会记录路径但不把名字当技能名 → 返回 ToolMentions,包含去重后的名字、路径和普通名字集合。

调用关系:它被 extract_tool_mentions 调用,也会被外部的 collect_tool_mentions_from_messages_with_sigil 使用。它依赖 parse_linked_tool_mention 解析链接,依赖 is_mention_name_char 判断名字字符,依赖 is_common_env_var 过滤环境变量。

调用图:调用 3 个内部函数(is_common_env_var, is_mention_name_char, parse_linked_tool_mention);被 2 处调用(extract_tool_mentions, collect_tool_mentions_from_messages_with_sigil);外部调用 2 个(new, matches!)。

select_skills_from_mentions351–426 ↗
fn select_skills_from_mentions(
    selection_context: &SkillSelectionContext<'_>,
    blocked_plain_names: &HashSet<String>,
    mentions: &ToolMentions<'_>,
    seen_names: &mut HashSet<String>,

作用:把文本里找到的提及转换成真正的技能对象。它先按路径精确匹配,再按名字匹配,并且只接受没有歧义的名字。

数据流:进去的是选择上下文、被结构化选择挡住的普通名字、提及结果、已见过的名字和路径、输出列表 → 如果没有提及就退出;先整理链接里的技能路径并遍历技能列表做路径匹配;再遍历技能列表做普通名字匹配,同时排除禁用、重复、重名、和连接器同名的情况 → 输出列表 selected 会增加新选中的技能,seen_names 和 seen_paths 也会同步更新。

调用关系:它被 collect_explicit_skill_mentions 调用,是文本提及到 SkillMetadata 的转换器。它调用 ToolMentions::is_empty 和 ToolMentions::paths,并使用 tool_kind_for_path、normalize_skill_path 等规则来避免误选。

调用图:调用 2 个内部函数(is_empty, paths);被 1 处调用(collect_explicit_skill_mentions)。

parse_linked_tool_mention428–483 ↗
fn parse_linked_tool_mention(
    text: &'a str,
    text_bytes: &[u8],
    start: usize,
    sigil: char,
) -> Option<(&'a str, &'a str, usize)>

作用:解析 Markdown 风格的工具链接,比如 [$tool](some/path)。它只负责判断这一小段是不是合法链接,并拆出名字和路径。

数据流:进去的是完整文本、字节数组、起始位置和提及符号 → 检查是否从 [ 后紧跟符号开始,读取合法名字,确认有 ]、可选空白、(、非空路径、) → 成功返回名字、修剪后的路径和链接结束位置;不符合格式就返回 None。

调用关系:它被 extract_tool_mentions_with_sigil 在扫描到 [ 时调用。名字字符是否合法由 is_mention_name_char 判断。

调用图:调用 1 个内部函数(is_mention_name_char);被 1 处调用(extract_tool_mentions_with_sigil)。

is_common_env_var485–501 ↗
fn is_common_env_var(name: &str) -> bool

作用:判断一个名字是不是常见环境变量名,比如 PATH、HOME、USER。这样用户写 $PATH 时,系统不会误以为他在点名一个技能。

数据流:进去的是名字字符串 → 转成大写 → 和一组常见环境变量名比较 → 返回是否命中。

调用关系:它被 extract_tool_mentions_with_sigil 调用。每次扫描到普通提及或链接提及时,都会用它先排除环境变量。

调用图:被 1 处调用(extract_tool_mentions_with_sigil);外部调用 1 个(matches!)。

text_mentions_skill504–533 ↗
fn text_mentions_skill(text: &str, skill_name: &str) -> bool

作用:这是测试用的小函数,用来判断一段文字是否准确提到了某个技能名。它只在测试编译时存在。

数据流:进去的是文本和技能名 → 如果技能名为空直接返回 false;否则寻找 $ 后面是否紧跟完整技能名,并确认后面不是名字字符的一部分 → 返回是否找到了完整提及。

调用关系:它受 #[cfg(test)] 限制,只服务测试代码。它复用 is_mention_name_char 来避免把 $foo 错认成 $foobar 的一部分。

is_mention_name_char535–537 ↗
fn is_mention_name_char(byte: u8) -> bool

作用:判断一个字节能不能作为提及名字的一部分。允许英文字母、数字、下划线、短横线和冒号。

数据流:进去的是一个字节 → 和允许的字符范围逐一匹配 → 返回 true 或 false。

调用关系:它是文本解析的基础规则,被 extract_tool_mentions_with_sigil 和 parse_linked_tool_mention 调用;测试函数 text_mentions_skill 也用它确认名字边界。

调用图:被 2 处调用(extract_tool_mentions_with_sigil, parse_linked_tool_mention);外部调用 1 个(matches!)。

core/src/skills.rs源码 ↗
orchestrationconfig load / request handling

这里的“技能”可以理解成一组可插拔的小能力,像工具箱里的专用工具。这个文件自己不负责加载、渲染、筛选技能的复杂细节,而是把 codex_core_skills 里的主要类型和函数统一转出口,方便 core 其他地方从一个入口拿到它们。除此之外,它做两件很实际的事:第一,把当前配置整理成加载技能需要的输入;第二,当用户输入的命令看起来“暗中触发了某个技能”时,记录一次遥测和分析事件。它还会记住本轮已经上报过的隐式技能,避免同一个技能被重复统计。这样既能让技能系统接上配置和会话,又能让产品知道技能是否真的被用上了。

函数细节2
skills_load_input_from_config36–46 ↗
fn skills_load_input_from_config(
    config: &Config,
    effective_skill_roots: Vec<PluginSkillRoot>,
) -> SkillsLoadInput

作用:这个函数把全局配置转换成“加载技能”这一步真正需要的材料。调用方不用自己到配置里东拼西凑,只要把配置和有效的技能目录交给它即可。

数据流:进去的是一份 Config 配置,以及已经算好的技能根目录列表。函数从配置里取出当前工作目录、配置层级栈,以及是否启用内置技能这个开关,然后调用 SkillsLoadInput::new 组装成一个 SkillsLoadInput。出来的是一份干净的加载输入,后续加载器可以直接照着它找技能、读规则。

调用关系:它处在配置和技能加载器之间,像把采购清单整理成仓库能看懂的入库单。内部会调用 SkillsLoadInput::new 来创建结果,也会询问 config.bundled_skills_enabled() 判断内置技能是否该参与加载。

调用图:调用 1 个内部函数(new);外部调用 1 个(bundled_skills_enabled)。

maybe_emit_implicit_skill_invocation48–108 ↗
async fn maybe_emit_implicit_skill_invocation(
    sess: &Session,
    turn_context: &TurnContext,
    command: &str,
    workdir: &AbsolutePathBuf,
)

作用:这个函数负责在系统判断“某条命令可能自动用到了某个技能”时,上报一次统计。它的重点是“可能就报,但同一轮同一个技能只报一次”,避免数据被刷重。

数据流:进去的是当前会话、这一轮对话上下文、用户命令文本,以及命令所在工作目录。函数先让技能系统检查这条命令是否像是在隐式触发某个技能;如果没有,就直接结束。如果有,它把技能名、范围、路径、插件编号等打包成一次 SkillInvocation。接着用互斥锁(一把锁,防止多个异步任务同时改同一份记录)打开本轮已见过的隐式技能集合,检查这次是否已经报过。没报过才会增加一个 telemetry 计数器,并把这次技能调用事件送到 analytics_events_client。结果没有返回值,但会改变“已上报集合”,并可能产生一条遥测和一条分析事件。

调用关系:它通常在处理用户命令时被调用,位置在“识别技能是否被自动触发”和“把使用情况发给统计系统”之间。它先把判断工作交给 detect_implicit_skill_invocation_for_command;确认需要上报后,再用 build_track_events_context 组装分析事件所需的上下文,最后交给会话里的 analytics_events_client 发送。

调用图:外部调用 4 个(build_track_events_context, detect_implicit_skill_invocation_for_command, format!, vec!)。

core-skills/src/render.rs源码 ↗
domain_logicthread startup / prompt building

这里解决的是一个很实际的问题:技能可能很多,每个技能还有说明和文件路径,但模型一次能看的内容有限。这个文件就像给技能列表做“排版和压缩”的编辑。它先按优先级排序,把技能写成“名字、描述、位置”的短行;如果放得下,就完整显示;如果放不下,就先公平地缩短每个技能的描述;还不够,就只保留名字和路径;再不够,才按优先级省略部分技能。它还会尝试把很长的绝对路径换成 r0、r1 这种短别名,好比地图上用“地铁 1 号线”代替一长串站点地址。最后,它会生成警告和遥测数据(运行统计),告诉系统技能列表是否被裁剪过。

函数细节71
render_available_skills_body62–84 ↗
fn render_available_skills_body(skill_root_lines: &[String], skill_lines: &[String]) -> String

作用:把技能根目录表、技能列表和“怎么使用技能”的说明拼成一整段文本。模型最终看到的技能说明正文就是靠它组织出骨架。

数据流:输入是根目录别名行和技能行 → 它决定使用“绝对路径版”还是“别名版”的介绍和使用规则 → 输出一段带标题、列表和说明的 Markdown 文本。

调用关系:它被 aliased_metadata_overhead_cost 用来估算有无别名时正文会多占多少空间,因此不只是展示,也参与预算计算。

调用图:被 1 处调用(aliased_metadata_overhead_cost);外部调用 2 个(new, format!)。

SkillMetadataBudget::limit93–97 ↗
fn limit(self) -> usize

作用:取出预算的数字上限。预算可能按 token(模型大致按词块计数)算,也可能按字符算,这个函数统一拿到那个限制值。

数据流:输入是一个预算对象 → 它看预算是哪一种类型 → 输出里面保存的上限数字。

调用关系:渲染技能时,build_aliased_available_skills、render_skill_lines_from_lines 和 render_minimum_skill_lines_until_budget 都用它判断还能不能继续塞内容。

调用图:被 3 处调用(build_aliased_available_skills, render_minimum_skill_lines_until_budget, render_skill_lines_from_lines)。

SkillMetadataBudget::cost99–104 ↗
fn cost(self, text: &str) -> usize

作用:计算一段文字会花掉多少预算。按 token 预算时会估算 token 数,按字符预算时直接数字符。

数据流:输入是一段文本 → 它按预算类型选择估算 token 或数字符 → 输出这段文本的花费。

调用关系:line_cost 和 aliased_metadata_overhead_cost 会调用它,用来判断一行技能或别名说明到底占多少空间。

调用图:被 2 处调用(aliased_metadata_overhead_cost, line_cost);外部调用 1 个(approx_token_count)。

SkillMetadataBudget::cost_from_counts106–111 ↗
fn cost_from_counts(self, chars: usize, bytes: usize) -> usize

作用:在已经知道字符数和字节数时,快速算预算花费。这样不用每次重新拼字符串。

数据流:输入是字符数和字节数 → 按预算类型选择字符数或用字节数粗估 token → 输出花费。

调用关系:DescriptionBudgetLine::new 用它逐字计算描述越写越长时会多花多少预算。

调用图:调用 1 个内部函数(approx_token_count_from_bytes);被 1 处调用(new)。

approx_token_count_from_bytes114–116 ↗
fn approx_token_count_from_bytes(bytes: usize) -> usize

作用:用字节数粗略估算 token 数。这里采用大约 4 个字节算 1 个 token 的简单规则。

数据流:输入是字节数 → 加上取整补偿后除以每 token 的估算字节数 → 输出大致 token 数。

调用关系:它只服务于 SkillMetadataBudget::cost_from_counts,是描述预算分配时的低成本估算工具。

调用图:被 1 处调用(cost_from_counts)。

default_skill_metadata_budget143–158 ↗
fn default_skill_metadata_budget(context_window: Option<i64>) -> SkillMetadataBudget

作用:给技能元数据设置默认空间。若知道模型上下文窗口大小,就拿 2% 给技能;不知道时就退回到固定 8000 字符。

数据流:输入是可选的上下文窗口大小 → 合法且大于 0 时换算成 token 预算,否则使用字符预算 → 输出 SkillMetadataBudget。

调用关系:这是外部构建技能列表前会用的默认规则,保证技能列表既有空间,又不会吃掉太多对话上下文。

调用图:外部调用 1 个(Characters)。

build_available_skills160–200 ↗
fn build_available_skills(
    outcome: &SkillLoadOutcome,
    budget: SkillMetadataBudget,
    side_effects: SkillRenderSideEffects<'_>,
) -> Option<AvailableSkills>

作用:这是生成“可用技能列表”的主入口。它从加载结果里挑出允许隐式使用的技能,并决定用完整路径还是短别名来展示。

数据流:输入是技能加载结果、预算和是否记录副作用的选项 → 先取可用技能,再生成绝对路径版本;如果空间紧张,再尝试别名版本并比较哪个更好 → 输出 AvailableSkills,或在没有技能时输出空。

调用关系:它串起 ordered_absolute_skill_lines、build_available_skills_from_lines、build_aliased_available_skills、aliased_render_is_better 和记录统计的函数,是本文件面向外部的核心流程。

调用图:调用 7 个内部函数(allowed_skills_for_implicit_invocation, aliased_render_is_better, build_aliased_available_skills, build_available_skills_from_lines, ordered_absolute_skill_lines, record_available_skills_side_effects, record_skill_render_side_effects);被 2 处调用(outcome_rendering_omits_aliases_when_absolute_plan_has_no_budget_pressure, outcome_rendering_uses_aliases_when_they_allow_more_skills_to_fit);外部调用 1 个(default)。

build_available_skills_from_lines202–251 ↗
fn build_available_skills_from_lines(
    skill_lines: Vec<SkillLine<'_>>,
    total_count: usize,
    budget: SkillMetadataBudget,
    path_aliases: SkillPathAliases,
) -> Option<AvailableSkills>

作用:把已经准备好的技能行真正压进预算里,并生成报告和警告。它不关心路径怎么来的,只关心这些行能不能放下。

数据流:输入是技能行、总技能数、预算和路径别名表 → 调用渲染算法决定完整显示、缩短描述或省略 → 输出 AvailableSkills,包括最终行、报告和可能的警告。

调用关系:build_available_skills 和 build_aliased_available_skills 都把整理好的行交给它;测试里的 build_available_skills_from_metadata 也用它验证预算行为。

调用图:调用 1 个内部函数(render_skill_lines_from_lines);被 3 处调用(build_aliased_available_skills, build_available_skills, build_available_skills_from_metadata);外部调用 1 个(format!)。

record_available_skills_side_effects253–277 ↗
fn record_available_skills_side_effects(
    available: &AvailableSkills,
    budget: SkillMetadataBudget,
    side_effects: SkillRenderSideEffects<'_>,
)

作用:把本次技能渲染的结果写入统计系统,并在发生裁剪时打日志。这样运行者能知道技能列表是不是被压缩过。

数据流:输入是最终 AvailableSkills、预算和副作用选项 → 记录总数、保留数、省略数、被截短字符数 → 如有省略或截短,再写一条日志。

调用关系:build_available_skills 在选定最终版本后调用它;它内部把指标记录交给 record_skill_render_side_effects。

调用图:调用 1 个内部函数(record_skill_render_side_effects);被 1 处调用(build_available_skills);外部调用 1 个(info!)。

budget_warning_prefix279–288 ↗
fn budget_warning_prefix(budget: SkillMetadataBudget, prefix: &str) -> String

作用:根据预算类型调整警告开头。token 预算时会明确说这是 2% 技能上下文预算。

数据流:输入是预算类型和原始警告前缀 → 若是 token 预算就替换句子中的预算说明,否则原样返回 → 输出最终前缀字符串。

调用关系:它用于 build_available_skills_from_lines 生成“技能被省略”的用户可见警告。

record_skill_render_side_effects290–322 ↗
fn record_skill_render_side_effects(
    side_effects: SkillRenderSideEffects<'_>,
    total_count: usize,
    included_count: usize,
    omitted_count: usize,
    truncated_description_chars: usize,

作用:真正把技能数量和截短情况写进遥测系统。遥测就是运行统计,方便后续观察系统表现。

数据流:输入是副作用模式和几个计数 → 如果模式是 None 就什么也不做;如果是线程开始,则写入多个直方图指标 → 输出为空,但会改动外部统计记录。

调用关系:build_available_skills 在无技能时直接调用它;record_available_skills_side_effects 在有技能时也会调用它。

调用图:被 2 处调用(build_available_skills, record_available_skills_side_effects);外部调用 1 个(try_from)。

render_skill_lines_from_lines324–379 ↗
fn render_skill_lines_from_lines(
    skill_lines: Vec<SkillLine<'_>>,
    total_count: usize,
    budget: SkillMetadataBudget,
) -> (Vec<String>, SkillRenderReport)

作用:决定技能行怎么在预算里显示。它的策略是:能完整就完整,不能完整就缩短描述,还不行就只保留最小行。

数据流:输入是技能行、总数和预算 → 先算完整花费,再算最小花费;按情况选择完整渲染、按描述预算渲染,或只保留能放下的最小行 → 输出最终字符串列表和渲染报告。

调用关系:build_available_skills_from_lines 把核心压缩工作交给它;它又会调用 render_lines_with_description_budget、render_minimum_skill_lines_until_budget 和统计辅助函数。

调用图:调用 5 个内部函数(limit, render_lines_with_description_budget, render_minimum_skill_lines_until_budget, skill_render_report, sum_description_truncation);被 1 处调用(build_available_skills_from_lines)。

render_minimum_skill_lines_until_budget381–417 ↗
fn render_minimum_skill_lines_until_budget(
    budget: SkillMetadataBudget,
    skill_lines: Vec<SkillLine<'_>>,
    total_count: usize,
) -> (Vec<String>, SkillRenderReport)

作用:当连所有技能的最小形式都放不下时,它负责尽量多放一些。最小形式就是只放名字和文件位置,不放描述。

数据流:输入是预算、技能行和总数 → 按顺序检查每个技能的最小行是否还能放下;放得下就加入,放不下就计为省略 → 输出保留下来的行和报告。

调用关系:render_skill_lines_from_lines 在最紧张的预算情况下调用它,用来做最后一档降级。

调用图:调用 2 个内部函数(limit, skill_render_report);被 1 处调用(render_skill_lines_from_lines);外部调用 1 个(new)。

skill_render_report419–433 ↗
fn skill_render_report(
    total_count: usize,
    included_count: usize,
    omitted_count: usize,
    truncated_description_chars: usize,
    truncated_description_count: usize,
) -> SkillRenderRep

作用:创建一份技能渲染报告。报告记录总数、保留数、省略数、描述被截短的字符数和技能数。

数据流:输入是五个计数 → 直接装进 SkillRenderReport 结构 → 输出这份报告。

调用关系:render_skill_lines_from_lines 和 render_minimum_skill_lines_until_budget 都用它统一生成报告。

调用图:被 2 处调用(render_minimum_skill_lines_until_budget, render_skill_lines_from_lines)。

SkillRenderReport::average_truncated_description_chars436–444 ↗
fn average_truncated_description_chars(&self) -> usize

作用:计算平均每个技能被截掉多少描述字符。这个平均值用来判断要不要提醒用户。

数据流:输入是报告自身 → 如果没有技能或没有截短就返回 0,否则把总截短字符数按技能总数向上取整 → 输出平均值。

调用关系:build_available_skills_from_lines 用它决定是否显示“描述被缩短”的警告;record_available_skills_side_effects 也用它写日志。

sum_description_truncation464–477 ↗
fn sum_description_truncation(rendered: &[RenderedSkillLine]) -> (usize, usize)

作用:汇总每条技能行的描述被截掉了多少。它把零散的截短情况变成报告里的总数。

数据流:输入是已渲染的技能行列表 → 遍历每一行,累加被截字符数,并统计哪些技能确实被截短 → 输出总字符数和技能数量。

调用关系:render_skill_lines_from_lines 在按描述预算缩短之后调用它,为 SkillRenderReport 准备数据。

调用图:被 1 处调用(render_skill_lines_from_lines);外部调用 1 个(iter)。

SkillLine::new480–485 ↗
fn new(skill: &'a SkillMetadata) -> Self

作用:从一个技能元数据创建普通的技能显示行,路径使用真实的 SKILL.md 路径。它是“绝对路径版”技能行的构造器。

数据流:输入是 SkillMetadata → 取出名字、描述和文件路径,并把 Windows 反斜杠换成斜杠 → 输出 SkillLine。

调用关系:测试里的 expected_skill_line 和多个预算测试会用它;生产流程中的绝对路径技能行也依赖这种构造方式。

调用图:被 6 处调用(budgeted_rendering_does_not_warn_when_average_description_truncation_is_within_threshold, budgeted_rendering_redistributes_unused_description_budget, budgeted_rendering_token_budget_truncation_warning_mentions_two_percent, budgeted_rendering_truncates_descriptions_equally_before_omitting_skills, budgeted_rendering_warns_when_average_description_truncation_exceeds_threshold, expected_skill_line);外部调用 1 个(with_path)。

SkillLine::with_path487–493 ↗
fn with_path(skill: &'a SkillMetadata, path: String) -> Self

作用:用指定路径创建技能显示行。这个路径可以是真实绝对路径,也可以是 r0/xxx 这种别名路径。

数据流:输入是技能元数据和一个已经准备好的路径字符串 → 取名字、描述和该路径 → 输出 SkillLine。

调用关系:SkillLine::new 会把普通路径交给它;build_aliased_available_skills 也用它生成别名路径版本。

SkillLine::full_cost495–497 ↗
fn full_cost(&self, budget: SkillMetadataBudget) -> usize

作用:计算完整技能行需要多少预算。完整技能行包含名字、完整描述和文件路径。

数据流:输入是技能行和预算 → 先渲染完整行,再计算带换行的花费 → 输出成本数字。

调用关系:render_skill_lines_from_lines 用它判断所有技能完整显示是否放得下。

调用图:调用 2 个内部函数(render_full, line_cost)。

SkillLine::minimum_cost499–501 ↗
fn minimum_cost(&self, budget: SkillMetadataBudget) -> usize

作用:计算最小技能行需要多少预算。最小行只保留名字和路径,不放描述。

数据流:输入是技能行和预算 → 渲染最小行,再计算带换行的花费 → 输出成本数字。

调用关系:render_skill_lines_from_lines 和 render_minimum_skill_lines_until_budget 用它判断降级后的技能列表能放多少。

调用图:调用 2 个内部函数(render_minimum, line_cost)。

SkillLine::description_char_count503–505 ↗
fn description_char_count(&self) -> usize

作用:数一条技能描述有多少字符。这里按字符数而不是字节数,避免中文、表情等多字节字符被切坏。

数据流:输入是技能行 → 遍历描述的字符 → 输出字符数量。

调用关系:DescriptionBudgetLine::new 用它知道一条描述最多能分配多少字符;最小渲染阶段也用这个数字统计被删掉的描述长度。

调用图:被 1 处调用(new)。

SkillLine::render_full507–509 ↗
fn render_full(&self) -> String

作用:把技能渲染成完整显示形式。完整形式包含名字、描述和文件位置。

数据流:输入是技能行 → 把完整描述交给通用渲染函数 → 输出一行字符串。

调用关系:SkillLine::full_cost 用它先得到文本,再计算预算花费。

调用图:调用 1 个内部函数(render_with_description);被 1 处调用(full_cost)。

SkillLine::render_minimum511–513 ↗
fn render_minimum(&self) -> String

作用:把技能渲染成最省空间的形式。描述为空,只留下名字和文件路径。

数据流:输入是技能行 → 把空描述交给通用渲染函数 → 输出一行最短字符串。

调用关系:SkillLine::minimum_cost 和 DescriptionBudgetLine::new 都依赖它来知道底线成本。

调用图:调用 1 个内部函数(render_with_description);被 2 处调用(new, minimum_cost)。

SkillLine::rendered_description_prefix_len515–520 ↗
fn rendered_description_prefix_len(&self, description_chars: usize) -> usize

作用:找出描述前 N 个字符在原字符串里的字节位置。这样截断时不会切到半个中文字符或半个 emoji。

数据流:输入是想保留的描述字符数 → 在描述里按字符定位对应字节下标;如果超过长度就取全文末尾 → 输出字节位置。

调用关系:SkillLine::render_with_description_chars 调用它来安全截取描述。

调用图:被 1 处调用(render_with_description_chars)。

SkillLine::render_with_description_chars522–530 ↗
fn render_with_description_chars(&self, description_chars: usize) -> String

作用:用描述的前几个字符渲染技能行。它专门用于“描述被预算截短”的情况。

数据流:输入是要保留的描述字符数 → 为 0 时输出无描述行;否则安全截取描述前缀并拼上路径 → 输出技能行字符串。

调用关系:render_lines_with_description_budget 在给每个技能分配完描述字符数后,用它生成最终行。

调用图:调用 1 个内部函数(rendered_description_prefix_len);外部调用 1 个(format!)。

SkillLine::render_with_description532–538 ↗
fn render_with_description(&self, description: &str) -> String

作用:按给定描述文本拼出一条技能行。它是完整渲染和最小渲染共用的格式化函数。

数据流:输入是一段描述 → 描述为空就输出“- 名字: (file: 路径)”,否则输出“- 名字: 描述 (file: 路径)” → 返回字符串。

调用关系:SkillLine::render_full 和 SkillLine::render_minimum 都把具体格式工作交给它。

调用图:被 2 处调用(render_full, render_minimum);外部调用 1 个(format!)。

DescriptionBudgetLine::new542–570 ↗
fn new(line: &'a SkillLine<'a>, budget: SkillMetadataBudget) -> Self

作用:为一条技能预先算好:描述保留 0 个、1 个、2 个字符时分别多花多少预算。这样后面能公平分配描述空间。

数据流:输入是技能行和预算 → 先算最小行成本,再逐字符累积描述的字符数和字节数,算出每个长度的额外成本 → 输出 DescriptionBudgetLine。

调用关系:render_lines_with_description_budget 会为每条技能创建它,然后按这些成本逐字符分配剩余空间。

调用图:调用 3 个内部函数(description_char_count, render_minimum, cost_from_counts);外部调用 1 个(with_capacity)。

line_cost573–575 ↗
fn line_cost(budget: SkillMetadataBudget, line: &str) -> usize

作用:计算单行文本加上换行后占多少预算。列表里每条技能实际都会换行,所以这里把换行也算进去。

数据流:输入是预算和一行文本 → 在文本末尾加换行 → 调用预算的 cost 算花费 → 输出成本。

调用关系:SkillLine::full_cost 和 SkillLine::minimum_cost 都通过它得到准确的行成本。

调用图:调用 1 个内部函数(cost);被 2 处调用(full_cost, minimum_cost);外部调用 1 个(format!)。

lines_cost577–581 ↗
fn lines_cost(budget: SkillMetadataBudget, lines: &[String]) -> usize

作用:计算多行技能文本一共占多少预算。它是多条 line_cost 的累加版。

数据流:输入是预算和字符串列表 → 逐行计算成本并累加,使用饱和加法避免数字溢出 → 输出总成本。

调用关系:available_skills_cost 用它比较绝对路径版和别名版哪个更省空间。

调用图:被 1 处调用(available_skills_cost)。

render_lines_with_description_budget583–636 ↗
fn render_lines_with_description_budget(
    budget: SkillMetadataBudget,
    skill_lines: &[SkillLine<'_>],
    limit: usize,
) -> Vec<RenderedSkillLine>

作用:在有限的剩余空间里,尽量公平地给每个技能分配描述字符。它不是一刀切配额,而是一轮一轮每个技能加一个字符。

数据流:输入是预算、技能行列表和可用于描述的额度 → 为每行预计算额外成本;循环给还没写完描述的技能各加字符,只要预算够就继续 → 输出每条最终行和被截掉的字符数。

调用关系:render_skill_lines_from_lines 在“所有技能能放下,但描述放不全”时调用它。

调用图:被 1 处调用(render_skill_lines_from_lines);外部调用 2 个(iter, vec!)。

build_aliased_available_skills638–659 ↗
fn build_aliased_available_skills(
    outcome: &SkillLoadOutcome,
    skills: &[SkillMetadata],
    budget: SkillMetadataBudget,
) -> Option<AvailableSkills>

作用:尝试用短别名路径生成技能列表。它的目标是把很长的公共路径提到表里,让每个技能行更短。

数据流:输入是加载结果、技能列表和预算 → 先建立别名计划;若别名表本身太贵就放弃;否则扣掉别名表成本,用剩余预算渲染别名路径技能行 → 输出 AvailableSkills 或空。

调用关系:build_available_skills 只有在绝对路径版本被截短或省略时才尝试它;相关测试也直接调用它验证插件路径场景。

调用图:调用 4 个内部函数(limit, build_alias_plan, build_available_skills_from_lines, ordered_skills_for_budget);被 2 处调用(build_available_skills, outcome_rendering_counts_plugin_version_skills_before_budget_omission);外部调用 3 个(len, Characters, Tokens)。

build_alias_plan673–736 ↗
fn build_alias_plan(
    outcome: &SkillLoadOutcome,
    skills: &[SkillMetadata],
    budget: SkillMetadataBudget,
) -> Option<AliasPlan>

作用:制定路径别名方案。它决定哪些根目录变成 r0、r1,以及每个技能路径应该对应哪个根。

数据流:输入是加载结果、当前技能和预算 → 找出这些技能实际用到的根目录;根据插件缓存路径特点选择更合适的别名根;生成根目录表并计算它的额外成本 → 输出 AliasPlan。

调用关系:build_aliased_available_skills 依赖它;多组测试直接检查它对单插件、多插件、多版本路径的选择是否合理。

调用图:调用 4 个内部函数(aliased_metadata_overhead_cost, build_skill_root_lines, ordered_alias_roots, plugin_version_skill_counts_for_skill_roots);被 8 处调用(build_aliased_available_skills, outcome_rendering_counts_plugin_version_skills_before_budget_omission, outcome_rendering_extracts_plugin_marketplace_root_for_multiple_plugins, outcome_rendering_uses_aliases_when_they_allow_more_skills_to_fit, outcome_rendering_uses_each_skill_root_for_multiple_roots_in_one_plugin_version, outcome_rendering_uses_marketplace_root_for_single_skill_plugin_versions, outcome_rendering_uses_one_marketplace_root_for_multiple_plugin_versions, outcome_rendering_uses_skill_root_for_multiple_skills_in_one_plugin_version);外部调用 1 个(iter)。

ordered_alias_roots738–751 ↗
fn ordered_alias_roots(
    used_roots: &[AbsolutePathBuf],
    alias_root_by_skill_root: &HashMap<AbsolutePathBuf, AbsolutePathBuf>,
) -> Option<Vec<AbsolutePathBuf>>

作用:按原始根目录出现顺序整理别名根,并去重。这样 r0、r1 的顺序稳定可预测。

数据流:输入是已使用根目录和“根目录到别名根”的映射 → 逐个取别名根,没见过就加入结果 → 输出有序且去重的别名根列表。

调用关系:build_alias_plan 用它生成最终别名表;如果映射缺失,它会返回空,表示无法建计划。

调用图:被 1 处调用(build_alias_plan);外部调用 2 个(new, new)。

alias_root_for_skill_root753–769 ↗
fn alias_root_for_skill_root(
    root: &AbsolutePathBuf,
    plugin_version_skill_counts: &HashMap<AbsolutePathBuf, usize>,
) -> AbsolutePathBuf

作用:为一个技能根目录选择最合适的别名根。单个插件版本只有一个技能时,可以用更高层的 marketplace 根;多个技能时保留更具体的技能根,通常更短。

数据流:输入是技能根目录和插件版本技能数量 → 识别它是否属于插件缓存路径;若不是就用原根;若是则根据同版本技能数量选择原根或 marketplace 根 → 输出别名根目录。

调用关系:build_alias_plan 在为每个使用到的根目录制定别名时调用它;它依赖 plugin_version_base 和 plugin_marketplace_base 识别插件路径。

调用图:调用 3 个内部函数(plugin_marketplace_base, plugin_version_base, as_path);外部调用 1 个(clone)。

plugin_version_skill_counts_for_skill_roots771–782 ↗
fn plugin_version_skill_counts_for_skill_roots(
    skill_roots: impl Iterator<Item = &'a AbsolutePathBuf>,
) -> HashMap<AbsolutePathBuf, usize>

作用:统计每个插件版本下面有多少个技能根。这个数量会影响别名根选得多高或多低。

数据流:输入是一批技能根目录 → 对每个目录尝试找出插件版本路径,并计数 → 输出“插件版本路径到技能数”的表。

调用关系:build_alias_plan 先调用它,再让 alias_root_for_skill_root 根据这些计数做选择。

调用图:调用 1 个内部函数(plugin_version_base);被 1 处调用(build_alias_plan);外部调用 1 个(new)。

aliased_metadata_overhead_cost784–794 ↗
fn aliased_metadata_overhead_cost(
    budget: SkillMetadataBudget,
    skill_root_lines: &[String],
) -> usize

作用:计算启用路径别名本身会多占多少预算。别名能缩短技能行,但也要额外展示“r0 等于哪个目录”的表。

数据流:输入是预算和根目录别名行 → 分别渲染无别名和有别名的空技能正文 → 用两者成本差算出额外开销 → 输出成本差。

调用关系:build_alias_plan 用它确认别名表成本;available_skills_cost 也用它比较别名版和绝对路径版的总成本。

调用图:调用 2 个内部函数(cost, render_available_skills_body);被 2 处调用(available_skills_cost, build_alias_plan)。

build_skill_root_lines796–805 ↗
fn build_skill_root_lines(roots: &[AbsolutePathBuf]) -> Vec<String>

作用:把根目录列表写成给模型看的别名表。比如把某个长路径写成“- r0 = /long/path”。

数据流:输入是根目录列表 → 按顺序编号 r0、r1,并统一使用斜杠路径 → 输出多行字符串。

调用关系:build_alias_plan 用它生成 AvailableSkills 里的 skill_root_lines。

调用图:被 1 处调用(build_alias_plan);外部调用 1 个(iter)。

plugin_marketplace_base807–818 ↗
fn plugin_marketplace_base(path: &Path) -> Option<AbsolutePathBuf>

作用:从一个插件缓存路径里找出 marketplace 根目录。简单说,就是找到 plugins/cache 后面的那个市场目录层级。

数据流:输入是路径 → 从当前路径一路向父目录查找形如 plugins/cache 的位置 → 找到后输出对应的绝对路径,否则输出空。

调用关系:alias_root_for_skill_root 和 plugin_version_base 都用它理解插件安装路径结构。

调用图:调用 1 个内部函数(from_absolute_path);被 2 处调用(alias_root_for_skill_root, plugin_version_base)。

plugin_version_base820–835 ↗
fn plugin_version_base(path: &Path) -> Option<AbsolutePathBuf>

作用:从插件缓存路径里找出具体插件版本目录。比如 marketplace/github/hash123 这一层。

数据流:输入是路径 → 先找 marketplace 根,再取其后两个路径段作为插件名和版本号 → 输出对应绝对路径,失败则输出空。

调用关系:alias_root_for_skill_root 用它判断路径是否属于插件版本;plugin_version_skill_counts_for_skill_roots 用它做计数分组。

调用图:调用 2 个内部函数(plugin_marketplace_base, from_absolute_path);被 2 处调用(alias_root_for_skill_root, plugin_version_skill_counts_for_skill_roots);外部调用 1 个(strip_prefix)。

render_skill_path_with_aliases837–840 ↗
fn render_skill_path_with_aliases(skill: &SkillMetadata, plan: &AliasPlan) -> String

作用:把技能的真实路径换成别名路径。若无法换,就退回真实路径。

数据流:输入是技能和别名计划 → 先尝试算相对别名路径;失败时把原 SKILL.md 路径转成字符串 → 输出用于显示的路径。

调用关系:build_aliased_available_skills 用它为每条技能生成短路径;测试也用它检查别名结果。

调用图:调用 1 个内部函数(outcome_relative_skill_path)。

outcome_relative_skill_path842–852 ↗
fn outcome_relative_skill_path(skill: &SkillMetadata, plan: &AliasPlan) -> Option<String>

作用:真正计算 r0/xxx 这种相对别名路径。它要求技能路径能落在某个别名根下面。

数据流:输入是技能和别名计划 → 找到该技能对应的别名根和别名名;把真实路径减去根目录前缀;拼成“别名/相对路径” → 输出字符串或空。

调用关系:render_skill_path_with_aliases 把具体计算交给它,并在失败时负责兜底。

调用图:被 1 处调用(render_skill_path_with_aliases);外部调用 1 个(format!)。

aliased_render_is_better854–867 ↗
fn aliased_render_is_better(
    aliased: &AvailableSkills,
    absolute: &AvailableSkills,
    budget: SkillMetadataBudget,
) -> bool

作用:比较别名版和绝对路径版哪个更值得用。优先看能多保留多少技能,再看少截多少描述,最后看总成本。

数据流:输入是两个 AvailableSkills 和预算 → 比较保留数量、截短字符数和总成本 → 输出是否别名版更好。

调用关系:build_available_skills 在同时拥有绝对路径版和别名版时调用它做最终选择。

调用图:调用 1 个内部函数(available_skills_cost);被 1 处调用(build_available_skills)。

available_skills_cost869–876 ↗
fn available_skills_cost(budget: SkillMetadataBudget, available: &AvailableSkills) -> usize

作用:估算一个技能列表最终占多少预算。它会把技能行成本和别名表额外成本一起算进去。

数据流:输入是预算和 AvailableSkills → 若有根别名表,先算别名额外开销;再累加技能行成本 → 输出总成本。

调用关系:aliased_render_is_better 用它在前两个指标打平时做最后比较。

调用图:调用 2 个内部函数(aliased_metadata_overhead_cost, lines_cost);被 1 处调用(aliased_render_is_better)。

ordered_absolute_skill_lines878–883 ↗
fn ordered_absolute_skill_lines(skills: &[SkillMetadata]) -> Vec<SkillLine<'_>>

作用:把技能按预算优先级排好,并创建使用绝对路径的技能行。它是普通显示方案的准备步骤。

数据流:输入是技能元数据列表 → 先排序,再把每个技能转成 SkillLine → 输出有序技能行。

调用关系:build_available_skills 用它生成绝对路径版本;测试辅助 build_available_skills_from_metadata 也用它。

调用图:调用 1 个内部函数(ordered_skills_for_budget);被 2 处调用(build_available_skills, build_available_skills_from_metadata)。

ordered_skills_for_budget885–894 ↗
fn ordered_skills_for_budget(skills: &[SkillMetadata]) -> Vec<&SkillMetadata>

作用:给技能排序,决定预算不够时谁先被保留。系统级优先,其次管理员、仓库、用户,同级再按名字和路径排。

数据流:输入是技能列表 → 复制引用并按作用域、名字、路径排序 → 输出排好序的技能引用列表。

调用关系:ordered_absolute_skill_lines 和 build_aliased_available_skills 都用它保证不同显示方案排序一致。

调用图:被 2 处调用(build_aliased_available_skills, ordered_absolute_skill_lines);外部调用 1 个(iter)。

prompt_scope_rank896–903 ↗
fn prompt_scope_rank(scope: SkillScope) -> u8

作用:把技能作用域换成排序用的数字。数字越小,预算紧张时越优先显示。

数据流:输入是 SkillScope → System 转 0,Admin 转 1,Repo 转 2,User 转 3 → 输出排序等级。

调用关系:ordered_skills_for_budget 用它实现技能优先级排序。

tests::make_skill915–927 ↗
fn make_skill(name: &str, scope: SkillScope) -> SkillMetadata

作用:测试用的小工厂,快速造一个技能元数据。这样每个测试不用重复写一大坨字段。

数据流:输入是技能名和作用域 → 填入默认描述、默认路径和空的可选字段 → 输出 SkillMetadata。

调用关系:多个测试和 tests::make_skill_with_description、tests::skill_with_path 都用它搭建测试数据。

调用图:外部调用 2 个(test_path_buf, format!)。

tests::make_skill_with_description929–937 ↗
fn make_skill_with_description(
        name: &str,
        scope: SkillScope,
        description: &str,
    ) -> SkillMetadata

作用:测试用来创建带指定描述的技能。它方便测试长描述、空描述和截短行为。

数据流:输入是名字、作用域和描述 → 先用 make_skill 创建默认技能,再替换描述 → 输出 SkillMetadata。

调用关系:预算截短相关测试大量调用它来构造不同长度的技能描述。

调用图:外部调用 1 个(make_skill)。

tests::expected_skill_line939–941 ↗
fn expected_skill_line(skill: &SkillMetadata, description: &str) -> String

作用:测试里生成期望的技能行文本。它避免手写格式字符串导致测试和真实格式不一致。

数据流:输入是技能和期望描述 → 创建 SkillLine,再按给定描述渲染 → 输出期望字符串。

调用关系:描述截短和预算分配测试用它和实际输出做 assert_eq 比较。

调用图:调用 1 个内部函数(new)。

tests::normalized_path943–945 ↗
fn normalized_path(path: &AbsolutePathBuf) -> String

作用:测试里把路径转成统一斜杠格式。这样 Windows 和 Unix 风格路径差异不会影响断言。

数据流:输入是绝对路径 → 转成字符串并把反斜杠换成斜杠 → 输出标准化路径字符串。

调用关系:路径别名相关测试用它构造期望的 r0 根目录行。

调用图:调用 1 个内部函数(to_string_lossy)。

tests::outcome_with_roots947–971 ↗
fn outcome_with_roots(
        skills: Vec<SkillMetadata>,
        roots: Vec<AbsolutePathBuf>,
    ) -> SkillLoadOutcome

作用:测试用来造一个带技能根目录映射的加载结果。它模拟真实加载器告诉渲染器“每个技能来自哪个根”。

数据流:输入是技能列表和根目录列表 → 找出每个技能路径属于哪个根,并填入 skill_root_by_path → 输出 SkillLoadOutcome。

调用关系:别名路径相关测试用它搭出不同插件根目录场景。

调用图:外部调用 2 个(new, default)。

tests::build_available_skills_from_metadata973–983 ↗
fn build_available_skills_from_metadata(
        skills: &[SkillMetadata],
        budget: SkillMetadataBudget,
    ) -> Option<AvailableSkills>

作用:测试辅助函数,直接从技能元数据生成 AvailableSkills。它绕过完整加载结果,专注测试预算渲染。

数据流:输入是技能列表和预算 → 创建绝对路径技能行,再调用 build_available_skills_from_lines → 输出 AvailableSkills 或空。

调用关系:多项预算截短、省略和排序测试都用它驱动核心渲染逻辑。

调用图:调用 2 个内部函数(build_available_skills_from_lines, ordered_absolute_skill_lines);外部调用 2 个(len, default)。

tests::skill_usage_instructions_require_complete_main_agent_reads986–1007 ↗
fn skill_usage_instructions_require_complete_main_agent_reads()

作用:确认技能使用说明明确要求主代理完整读取 SKILL.md。这个测试防止说明被改成只读一部分或交给子代理解释。

数据流:输入是两套说明常量 → 检查它们包含完整读取、读到 EOF、不要委托解释等关键句子,并不包含错误提示 → 测试通过或失败。

调用关系:它保护 render_available_skills_body 会放进正文的使用规则不被无意削弱。

调用图:外部调用 1 个(assert!)。

tests::default_budget_uses_two_percent_of_full_context_window1010–1019 ↗
fn default_budget_uses_two_percent_of_full_context_window()

作用:验证已知上下文窗口时,默认技能预算确实取 2%。这保证技能不会无限占用对话空间。

数据流:输入几个上下文窗口例子 → 调用 default_skill_metadata_budget → 断言输出 token 预算符合预期。

调用关系:它直接覆盖 default_skill_metadata_budget 的主要分支。

调用图:外部调用 1 个(assert_eq!)。

tests::default_budget_falls_back_to_characters_without_context_window1022–1031 ↗
fn default_budget_falls_back_to_characters_without_context_window()

作用:验证没有合法上下文窗口时,会退回固定字符预算。这样外部没提供窗口大小也能稳定工作。

数据流:输入 None 和负数窗口 → 调用 default_skill_metadata_budget → 断言输出 8000 字符预算。

调用关系:它覆盖 default_skill_metadata_budget 的兜底分支。

调用图:外部调用 1 个(assert_eq!)。

tests::budgeted_rendering_truncates_descriptions_equally_before_omitting_skills1034–1056 ↗
fn budgeted_rendering_truncates_descriptions_equally_before_omitting_skills()

作用:验证预算不够完整描述时,会先公平缩短描述,而不是直接丢技能。两个技能应各保留相近数量的描述字符。

数据流:输入两个描述长度相同的技能和刚好多一点的预算 → 构建可用技能列表 → 断言两个技能都保留、描述都被截短且没有警告。

调用关系:它主要验证 render_lines_with_description_budget 和 render_skill_lines_from_lines 的配合。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, Characters, build_available_skills_from_metadata, make_skill_with_description)。

tests::budgeted_rendering_does_not_warn_when_average_description_truncation_is_within_threshold1059–1075 ↗
fn budgeted_rendering_does_not_warn_when_average_description_truncation_is_within_threshold()

作用:验证描述虽然被截短,但平均截短不严重时不会打扰用户。小幅压缩属于正常行为。

数据流:输入两个较短描述技能和有限预算 → 渲染技能列表 → 断言有截短统计但 warning_message 为空。

调用关系:它保护 build_available_skills_from_lines 里的警告阈值逻辑。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, Characters, build_available_skills_from_metadata, make_skill_with_description)。

tests::budgeted_rendering_warns_when_average_description_truncation_exceeds_threshold1078–1104 ↗
fn budgeted_rendering_warns_when_average_description_truncation_exceeds_threshold()

作用:验证描述被大量截短时会给出警告。这样用户知道模型看到的技能说明变短了。

数据流:输入一个很长描述技能和一个空描述技能 → 用有限预算渲染 → 断言截短统计和警告文字符合预期。

调用关系:它覆盖 SkillRenderReport::average_truncated_description_chars 与 warning_message 的联动。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, Characters, build_available_skills_from_metadata, make_skill_with_description)。

tests::budgeted_rendering_token_budget_truncation_warning_mentions_two_percent1107–1122 ↗
fn budgeted_rendering_token_budget_truncation_warning_mentions_two_percent()

作用:验证 token 预算下的截短警告会提到 2% 技能预算。这样提示和默认预算策略一致。

数据流:输入一个长描述技能和 token 预算 → 渲染技能列表 → 断言警告使用带 2% 的版本。

调用关系:它检查 build_available_skills_from_lines 在 SkillMetadataBudget::Tokens 分支的警告选择。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, Tokens, build_available_skills_from_metadata, make_skill_with_description)。

tests::budgeted_rendering_redistributes_unused_description_budget1125–1146 ↗
fn budgeted_rendering_redistributes_unused_description_budget()

作用:验证短描述没用完的空间会让给长描述。不会因为固定平均分配而浪费预算。

数据流:输入一个短描述和一个长描述技能 → 设置能多放一些描述的预算 → 断言短描述完整保留,长描述拿到剩余空间。

调用关系:它专门保护 render_lines_with_description_budget 的逐轮分配策略。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, Characters, build_available_skills_from_metadata, make_skill_with_description)。

tests::budgeted_rendering_preserves_prompt_priority_when_minimum_lines_exceed_budget1149–1178 ↗
fn budgeted_rendering_preserves_prompt_priority_when_minimum_lines_exceed_budget()

作用:验证预算连最小行都放不下时,会按作用域优先级保留技能。系统和管理员技能应优先于仓库和用户技能。

数据流:输入四个不同作用域的技能和只能放两个最小行的预算 → 渲染 → 断言只保留高优先级技能,并给出省略警告。

调用关系:它覆盖 ordered_skills_for_budget、prompt_scope_rank 和 render_minimum_skill_lines_until_budget 的组合行为。

调用图:外部调用 6 个(assert!, assert_eq!, Characters, build_available_skills_from_metadata, make_skill, format!)。

tests::budgeted_rendering_keeps_scanning_after_oversized_entry1181–1204 ↗
fn budgeted_rendering_keeps_scanning_after_oversized_entry()

作用:验证遇到一个太大的技能时,不会直接停止扫描后面的技能。后面能放下的技能仍应被保留。

数据流:输入一个超大系统技能和一个较小仓库技能 → 预算只够小技能 → 渲染 → 断言大技能省略、小技能保留。

调用关系:它保护 render_minimum_skill_lines_until_budget 的“跳过放不下的项,继续看后面”的行为。

调用图:外部调用 6 个(assert!, assert_eq!, Characters, build_available_skills_from_metadata, make_skill, format!)。

tests::outcome_rendering_omits_aliases_when_absolute_plan_has_no_budget_pressure1207–1228 ↗
fn outcome_rendering_omits_aliases_when_absolute_plan_has_no_budget_pressure()

作用:验证没有预算压力时不使用路径别名。因为别名表本身会增加说明复杂度,没必要就不用。

数据流:输入两个同根技能和无限预算 → 调用 build_available_skills → 断言没有 skill_root_lines,两个技能都保留。

调用关系:它检查 build_available_skills 对绝对路径版本和别名版本的选择策略。

调用图:调用 1 个内部函数(build_available_skills);外部调用 6 个(assert!, assert_eq!, test_path_buf, Characters, outcome_with_roots, vec!)。

tests::outcome_rendering_uses_aliases_when_they_allow_more_skills_to_fit1231–1289 ↗
fn outcome_rendering_uses_aliases_when_they_allow_more_skills_to_fit()

作用:验证长公共路径导致预算紧张时,会使用别名让更多技能放进列表。别名在这里能明显省空间。

数据流:输入一组路径前缀很长的技能 → 先确认别名版本更便宜,再调用 build_available_skills → 断言所有技能都保留且路径使用 r0。

调用关系:它覆盖 build_alias_plan、build_aliased_available_skills 和 aliased_render_is_better 的完整协作。

调用图:调用 2 个内部函数(build_alias_plan, build_available_skills);外部调用 6 个(assert!, assert_eq!, test_path_buf, Characters, outcome_with_roots, vec!)。

tests::outcome_rendering_uses_marketplace_root_for_single_skill_plugin_versions1292–1317 ↗
fn outcome_rendering_uses_marketplace_root_for_single_skill_plugin_versions()

作用:验证单个插件版本只有一个技能时,别名根会提到 marketplace 层。这样整体路径表达通常更划算。

数据流:输入一个插件缓存路径下的技能 → 建立别名计划 → 断言 r0 指向 marketplace 根,技能路径包含插件名和版本。

调用关系:它测试 alias_root_for_skill_root、plugin_marketplace_base 和 plugin_version_base 的单技能版本逻辑。

调用图:调用 1 个内部函数(build_alias_plan);外部调用 6 个(assert_eq!, test_path_buf, Characters, outcome_with_roots, skill_with_path, vec!)。

tests::outcome_rendering_uses_skill_root_for_multiple_skills_in_one_plugin_version1320–1355 ↗
fn outcome_rendering_uses_skill_root_for_multiple_skills_in_one_plugin_version()

作用:验证同一个插件版本里有多个技能时,别名根会使用更具体的技能根。这样每条技能行更短。

数据流:输入同一插件版本下两个技能 → 建立别名计划 → 断言 r0 指向 skills 根,路径只剩技能目录和 SKILL.md。

调用关系:它检查 alias_root_for_skill_root 根据插件版本技能数量做不同选择。

调用图:调用 1 个内部函数(build_alias_plan);外部调用 6 个(assert_eq!, test_path_buf, Characters, outcome_with_roots, skill_with_path, vec!)。

tests::outcome_rendering_counts_plugin_version_skills_before_budget_omission1358–1393 ↗
fn outcome_rendering_counts_plugin_version_skills_before_budget_omission()

作用:验证计算插件版本技能数量时,要在预算省略之前统计。即使后来只放得下一个技能,别名根也应按原本两个技能来选。

数据流:输入同一插件版本两个技能和只够显示一个的预算 → 建别名计划并渲染别名版 → 断言别名根仍是原技能根,最终只保留一个技能。

调用关系:它保护 build_alias_plan 在预算裁剪前运行,并配合 build_aliased_available_skills 检查结果。

调用图:调用 2 个内部函数(build_alias_plan, build_aliased_available_skills);外部调用 7 个(assert_eq!, test_path_buf, Characters, outcome_with_roots, skill_with_path, format!, vec!)。

tests::outcome_rendering_uses_each_skill_root_for_multiple_roots_in_one_plugin_version1396–1438 ↗
fn outcome_rendering_uses_each_skill_root_for_multiple_roots_in_one_plugin_version()

作用:验证同一插件版本下有多个不同技能根时,会分别给它们别名。这样不会错误地把一个根套到另一个根上。

数据流:输入同一插件版本的 skills 根和 extra-skills 根 → 建立别名计划 → 断言生成 r0、r1,并各自渲染正确相对路径。

调用关系:它测试 build_alias_plan、ordered_alias_roots 和 outcome_relative_skill_path 对多根场景的处理。

调用图:调用 1 个内部函数(build_alias_plan);外部调用 6 个(assert_eq!, test_path_buf, Characters, outcome_with_roots, skill_with_path, vec!)。

tests::outcome_rendering_extracts_plugin_marketplace_root_for_multiple_plugins1441–1486 ↗
fn outcome_rendering_extracts_plugin_marketplace_root_for_multiple_plugins()

作用:验证多个插件各一个技能时,可以共享同一个 marketplace 根别名。这样只需要一条 r0 表项。

数据流:输入 github 和 slack 两个插件路径 → 建立别名计划 → 断言 r0 是 marketplace 根,两个技能路径都从 r0 展开。

调用关系:它覆盖 plugin_marketplace_base 和 alias_root_for_skill_root 在多插件场景下的合并效果。

调用图:调用 1 个内部函数(build_alias_plan);外部调用 6 个(assert_eq!, test_path_buf, Characters, outcome_with_roots, skill_with_path, vec!)。

tests::outcome_rendering_uses_one_marketplace_root_for_multiple_plugin_versions1489–1529 ↗
fn outcome_rendering_uses_one_marketplace_root_for_multiple_plugin_versions()

作用:验证同一 marketplace 下多个插件版本也能共用一个 marketplace 根别名。这样避免生成太多根表项。

数据流:输入同一插件不同版本下的两个技能 → 建立别名计划 → 断言只有 r0,路径中保留插件名和不同版本号。

调用关系:它检查 build_alias_plan 对多个版本路径的去重和路径相对化逻辑。

调用图:调用 1 个内部函数(build_alias_plan);外部调用 6 个(assert_eq!, test_path_buf, Characters, outcome_with_roots, skill_with_path, vec!)。

tests::skill_with_path1531–1535 ↗
fn skill_with_path(name: &str, path: &AbsolutePathBuf) -> SkillMetadata

作用:测试辅助函数,用指定路径创建技能。它方便构造插件缓存、长路径、多根目录等场景。

数据流:输入技能名和路径 → 先创建默认用户技能,再替换 SKILL.md 路径 → 输出 SkillMetadata。

调用关系:多个路径别名测试用它快速搭建不同目录结构下的技能。

调用图:外部调用 2 个(make_skill, clone)。

core/src/context/available_skills_instructions.rs源码 ↗
domain_logiccontext assembly

可以把这个文件理解成一张“工具清单说明书”的生成器。系统里可能已经扫描到一些可用技能,比如技能所在目录、技能名称和说明;但模型不能直接读这些内部数据,它需要一段格式固定、角色明确的文字。AvailableSkillsInstructions 就是这个包装盒:它保存技能根目录信息和技能列表,并实现 ContextualUserFragment,也就是“能放进对话上下文的一小段内容”。它会声明这段内容的角色是 developer,表示这是给模型看的开发者级提示;还会提供开始和结束标记,方便系统之后识别、替换或移除这段技能说明。真正拼正文时,它交给 render_available_skills_body 来把列表排成可读文本。

函数细节6
AvailableSkillsInstructions::from_skill_lines17–22 ↗
fn from_skill_lines(skill_lines: Vec<String>) -> Self

作用:用一批已经整理好的技能文字行,直接做出一个技能说明片段。适合外面已经把技能清单准备好,只需要把它塞进模型上下文的时候使用。

数据流:输入是一组技能说明行。函数新建一个 AvailableSkillsInstructions,把技能根目录行设为空列表,把传进来的技能行保存进去。输出是这个可放进上下文的技能说明对象,不会修改外部数据。

调用关系:available_skills_fragment 会调用它,把已经准备好的技能行包装成上下文片段。它自己只做装盒这一步,不负责把正文排版;后面真正需要文字时,会由 body 再去生成。

调用图:被 1 处调用(available_skills_fragment);外部调用 1 个(new)。

AvailableSkillsInstructions::from26–31 ↗
fn from(available_skills: AvailableSkills) -> Self

作用:把 AvailableSkills 这种“系统收集到的技能数据”转换成 AvailableSkillsInstructions 这种“能给模型看的上下文片段”。这是从内部数据到提示文字包装之间的桥。

数据流:输入是一个 AvailableSkills,里面带着技能根目录行和技能列表行。函数把这两份数据取出来,放进新的 AvailableSkillsInstructions。输出是包装后的对象,原来的 AvailableSkills 被消费掉。

调用关系:build_initial_context 会在搭建初始上下文时调用它。也就是说,系统一开始准备给模型的背景信息时,会通过这个转换把技能清单加入进去。

调用图:被 1 处调用(build_initial_context)。

AvailableSkillsInstructions::role35–37 ↗
fn role(&self) -> &'static str

作用:告诉上下文系统:这段技能说明应该以什么身份放进对话里。这里固定返回 developer,意思是它属于开发者给模型的指导信息。

数据流:不需要输入,也不读取技能内容。它直接返回字符串 developer。它不改动对象,只提供一个标签式的信息。

调用关系:当上下文系统收集各种片段、准备发给模型时,会询问每个片段的 role。这个函数让技能说明被放在正确的提示层级里,而不是被当成普通用户聊天内容。

AvailableSkillsInstructions::markers39–41 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:给这段技能说明提供一对边界标记,就像文章前后的“开始”和“结束”标签。这样系统以后能准确找到这段内容。

数据流:输入是当前对象,但它不需要读取对象里的技能列表。它调用 type_markers,拿到固定的开始标记和结束标记,然后返回这两个字符串。

调用关系:上下文系统需要给片段加边界时会用到它。它把具体工作交给 type_markers,保证实例方法和类型级方法用的是同一套标记。

调用图:外部调用 1 个(type_markers)。

AvailableSkillsInstructions::type_markers43–45 ↗
fn type_markers() -> (&'static str, &'static str)

作用:返回技能说明专用的固定开始标签和结束标签。它像统一印章,保证所有技能说明片段都用同一种边界格式。

数据流:没有输入,也不依赖对象状态。它直接返回 SKILLS_INSTRUCTIONS_OPEN_TAG 和 SKILLS_INSTRUCTIONS_CLOSE_TAG 这两个常量。不会产生副作用。

调用关系:markers 会调用它来取得边界标记。其他地方如果只知道类型、不拿具体对象,也可以用它来识别这类上下文片段。

AvailableSkillsInstructions::body47–49 ↗
fn body(&self) -> String

作用:生成真正给模型看的技能说明正文。它把内部保存的技能根目录和技能行,整理成一段可读的文字。

数据流:输入是当前对象里的 skill_root_lines 和 skill_lines。函数把这两份列表交给 render_available_skills_body 排版。输出是一整个字符串,也就是技能说明正文;对象本身不被修改。

调用关系:当上下文系统最终要把片段写进模型输入时,会调用它拿正文。它不自己决定排版细节,而是交给 render_available_skills_body,保证技能说明的文本格式集中在一个地方维护。

调用图:外部调用 1 个(render_available_skills_body)。

ext/skills/src/render.rs源码 ↗
domain_logic提示词组装阶段

这个文件像一个“菜单排版员”。系统里可能有很多技能,但不是每个技能都应该展示给模型看,也不能无限长地塞进提示词里。它先从技能目录里挑出已启用、允许出现在提示词里的技能,把每个技能写成一行:名字、说明、来源位置。整份“可用技能列表”最多控制在 8000 字节左右,超过的技能会被省略,并补一句“还有多少个没列出来”。另外,它还负责把技能自己的主提示内容截到 8000 字节以内。这里特别注意 UTF-8(常见文字编码,中文一个字可能占多个字节):截断时不会把一个中文字符从中间切坏。这样既保护提示词长度,又保证生成出来的文字仍然是合法、可读的。

函数细节4
available_skills_fragment13–50 ↗
fn available_skills_fragment(
    catalog: &SkillCatalog,
) -> Option<AvailableSkillsInstructions>

作用:把技能目录变成一段“可用技能说明”。只有已启用、并且允许显示给模型看的技能才会被写进去,太多时会自动省略后面的内容。

数据流:进去的是一个 SkillCatalog,也就是技能清单;函数逐个查看里面的条目,跳过未启用或不该展示的技能,为每个可展示技能调用 render_skill_line 写成一行,并累计字节数;如果超过上限,就不再加入那一行,只记录被省略的数量。出来的是一个 AvailableSkillsInstructions;如果没有任何可展示行,就返回 None。

调用关系:它在 contribute 组装提示词时被调用,负责提供“有哪些技能可用”这块文字。它把单个技能的排版工作交给 render_skill_line,最后再用 from_skill_lines 把多行文字包装成系统后续能使用的提示片段。

调用图:调用 2 个内部函数(from_skill_lines, render_skill_line);被 1 处调用(contribute);外部调用 2 个(new, format!)。

render_skill_line52–66 ↗
fn render_skill_line(entry: &SkillCatalogEntry, description: &str) -> String

作用:把一个技能条目写成一行人和模型都能读懂的说明。它会说明技能名、技能描述,以及这个技能来自哪里。

数据流:进去的是一个 SkillCatalogEntry 和一段描述文字;函数先根据技能来源判断该写成“文件”“环境资源”“编排器资源”还是“自定义资源”,再取技能名和渲染后的路径;如果描述为空,就只写名字和位置,如果有描述,就把描述也放进去。出来的是一行以短横线开头的文本。

调用关系:它只在 available_skills_fragment 里被使用,是技能列表里的“小排版工”。路径字符串不由它自己拼,它会调用 rendered_path 拿到适合展示的路径,然后用格式化字符串生成最终一行。

调用图:调用 1 个内部函数(rendered_path);被 1 处调用(available_skills_fragment);外部调用 1 个(format!)。

truncate_main_prompt_contents68–70 ↗
fn truncate_main_prompt_contents(contents: &str) -> (String, bool)

作用:把技能的主提示内容限制在固定大小以内。这样可以防止单个技能的说明太长,占掉太多模型上下文空间。

数据流:进去的是一整段提示词内容;函数使用本文件固定的 8000 字节上限,把内容交给 truncate_utf8_to_bytes;出来的是截断后的字符串,以及一个布尔值,表示是否真的发生了截断。

调用关系:它在 contribute 组装技能提示时被调用,是一个带默认上限的便捷入口。真正的安全截断动作由 truncate_utf8_to_bytes 完成。

调用图:调用 1 个内部函数(truncate_utf8_to_bytes);被 1 处调用(contribute)。

truncate_utf8_to_bytes72–75 ↗
fn truncate_utf8_to_bytes(contents: &str, max_bytes: usize) -> (String, bool)

作用:按字节数截断字符串,同时保证不会把 UTF-8 字符切坏。对中文、emoji 这类多字节字符尤其重要。

数据流:进去的是原始字符串和最大字节数;函数调用 take_bytes_at_char_boundary,找到不超过上限、且正好落在字符边界上的切点;出来的是安全截断后的新字符串,以及一个布尔值,表示结果是否比原文短。

调用关系:它既可以被 contribute 直接调用,也被 truncate_main_prompt_contents 当作底层工具使用。它把最容易出错的“按字节截断但不能切坏字符”交给外部工具 take_bytes_at_char_boundary 来完成。

调用图:被 2 处调用(contribute, truncate_main_prompt_contents);外部调用 1 个(take_bytes_at_char_boundary)。

插件和应用能力提示词

这些文件定义并渲染插件、工具和应用连接器的提示词指引,包括显式插件提及注入。

core/src/apps/render.rs源码 ↗
domain_logicrequest handling / prompt construction

这个文件解决的是“要不要把 Apps 信息写进上下文”的问题。这里的 Apps 可以理解成外部应用连接器,比如日历之类的服务。系统在跟模型对话前,可能需要告诉模型:现在有哪些应用可以配合使用。但如果某个应用用户没权限访问,或者没有启用,就不该写进去,否则模型可能会以为自己能用,最后给出错误建议。核心函数 render_apps_section 会接收一组应用信息,交给 AppsInstructions::from_connectors 先筛选和整理;如果没有合格应用,就返回空;如果有,就渲染成一段带固定开始和结束标签的文字。文件里的测试像验收清单一样,确认“没有可用应用时不输出”,以及“有可用应用时输出格式正确”。

函数细节4
render_apps_section7–9 ↗
fn render_apps_section(connectors: &[AppInfo]) -> Option<String>

作用:把一组应用连接器信息转换成一段可以放进模型上下文的说明文字。只有存在既能访问又已启用的应用时,它才会返回内容;否则返回没有内容。

数据流:输入是一串 AppInfo,也就是每个应用的基本资料和状态。函数把这些资料交给 AppsInstructions::from_connectors 做筛选和整理;如果整理结果存在,就调用它的 render 生成最终字符串;如果不存在,就返回 None,表示这一节不用放进提示里。它不修改原来的应用列表,只产出可选的文本结果。

调用关系:它是这个文件的正式对外小入口。构建模型提示时,别的代码可以调用它来决定是否加入 Apps 说明。测试 tests::renders_apps_section_with_an_accessible_and_enabled_app 会调用它,验证有合格应用时确实能生成带固定标签的内容。

调用图:调用 1 个内部函数(from_connectors);被 1 处调用(renders_apps_section_with_an_accessible_and_enabled_app)。

tests::connector15–31 ↗
fn connector(id: &str, is_accessible: bool, is_enabled: bool) -> AppInfo

作用:这是测试用的小帮手,用来快速造一个假的应用连接器。测试不关心的字段都填成空值,只重点控制应用是否可访问、是否启用。

数据流:输入是应用 id,以及两个布尔值:is_accessible 表示用户能不能访问,is_enabled 表示有没有启用。函数用这些值组装出一个 AppInfo;名字直接复用 id,其他图标、描述、安装地址等信息都留空或设成默认空列表。输出是一个测试用的应用对象。

调用关系:它只服务于本文件的测试,让测试代码不用每次手写一大堆无关字段。测试函数用它造出不同状态的应用,再交给 render_apps_section 检查筛选行为是否正确。

调用图:外部调用 1 个(new)。

tests::omits_apps_section_without_accessible_and_enabled_apps34–48 ↗
fn omits_apps_section_without_accessible_and_enabled_apps()

作用:这个测试确认:没有真正可用的应用时,系统不会生成 Apps 说明段。也就是说,空列表、没启用、没权限访问,这几种情况都应该被省略。

数据流:它先准备三种输入:没有应用、应用可访问但没启用、应用启用但不可访问。每种输入都会送进 render_apps_section,然后用断言检查结果是不是 None。测试本身不产生业务数据,只验证函数行为符合预期。

调用关系:它保护的是最重要的安全边界之一:不能把不可用的应用写进模型提示里。它间接依赖 tests::connector 生成测试对象,并通过断言确认 render_apps_section 的省略逻辑没有出错。

调用图:外部调用 1 个(assert_eq!)。

tests::renders_apps_section_with_an_accessible_and_enabled_app51–60 ↗
fn renders_apps_section_with_an_accessible_and_enabled_app()

作用:这个测试确认:只要有一个既可访问又已启用的应用,系统就会生成 Apps 说明段,而且格式里有规定的开始标签、标题和结束标签。

数据流:它先用测试帮手造出一个名为 calendar 的应用,并把它设为可访问且已启用。然后把这个应用列表传给 render_apps_section,拿到生成的字符串。最后检查这段文字是否以 Apps 说明的开始标签开头、包含 ## Apps (Connectors) 标题,并以结束标签结尾。

调用关系:它直接调用 render_apps_section,验证正常成功路径。这个测试还会使用协议里定义的固定标签,确保渲染出来的内容能被系统其他部分准确识别和包裹。

调用图:调用 1 个内部函数(render_apps_section);外部调用 2 个(assert!, connector)。

core/src/context/apps_instructions.rs源码 ↗
domain_logic构建对话上下文时,尤其是启动或准备首轮上下文时

可以把这个文件理解成一张“工具使用小纸条”。系统发现用户装了并且能用的 App 后,就需要告诉模型:用户消息里可能会用 [$app-name](app://...) 这种写法点名某个 App;App 本质上是一组 MCP 工具,MCP 可以简单理解成“模型调用外部工具的一套接口规矩”。这个文件不真正调用 App,也不查工具列表,它只负责决定“要不要放说明”和“说明写什么”。AppsInstructions 是这张小纸条本身;from_connectors 看连接器列表里有没有可用 App;实现 ContextualUserFragment 后,系统就能知道这段内容用什么身份发给模型、用什么标签包起来、正文是什么。一个重要细节是:如果没有任何既可访问又已启用的 App,这段说明不会出现,避免给模型无用提示。

函数细节5
AppsInstructions::from_connectors12–17 ↗
fn from_connectors(connectors: &[AppInfo]) -> Option<Self>

作用:检查当前有没有真正可用的 App 连接器。如果有,就创建一份 App 使用说明;如果没有,就什么也不放。

数据流:输入是一组 AppInfo,也就是 App 连接器的信息。它逐个查看每个连接器是否“可访问”并且“已启用”;只要找到一个符合条件的,就返回 Some(AppsInstructions);一个都没有,就返回 None。它不修改连接器,只根据它们的状态做判断。

调用关系:在构建上下文时,render_apps_sectionbuild_initial_context 会调用它,先决定这段 App 说明该不该出现。它内部只是遍历列表,不把工作交给其他项目内函数。

调用图:被 2 处调用(render_apps_section, build_initial_context);外部调用 1 个(iter)。

AppsInstructions::role21–23 ↗
fn role(&self) -> &'static str

作用:告诉上下文系统:这段说明应该以 developer 身份出现。这样模型会把它当成开发者给的规则或提示,而不是普通用户随口说的话。

数据流:它不需要输入,也不读取外部状态。调用后直接返回固定字符串 developer,不改动任何东西。

调用关系:这是 ContextualUserFragment 这套接口的一部分。上下文渲染器在拼接提示内容时会询问每个片段的身份,随后用这个身份包装或发送这段 App 说明。

AppsInstructions::markers25–27 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:给这段 App 说明提供开始和结束标记。标记像文件夹标签一样,方便系统和模型知道哪一段是 Apps 说明。

数据流:它不接收业务数据。调用后转去拿 type_markers 返回的一对固定标签,然后把这对标签交回调用方;没有副作用。

调用关系:它也是 ContextualUserFragment 接口的一部分。上下文系统需要给片段加边界时会调用它,而它把具体标签来源交给 AppsInstructions::type_markers

调用图:外部调用 1 个(type_markers)。

AppsInstructions::type_markers29–31 ↗
fn type_markers() -> (&'static str, &'static str)

作用:返回 Apps 说明专用的起始标签和结束标签。这样不同类型的上下文片段不会混在一起。

数据流:它没有输入。它读取两个协议常量:APPS_INSTRUCTIONS_OPEN_TAGAPPS_INSTRUCTIONS_CLOSE_TAG,然后把它们作为一对结果返回;不修改任何状态。

调用关系AppsInstructions::markers 会调用它来拿标签。因为标签来自协议层常量,其他地方只要用同一套协议,就能稳定识别这段内容。

AppsInstructions::body33–37 ↗
fn body(&self) -> String

作用:生成真正写给模型看的 Apps 使用说明正文。它说明 App 可以怎样被触发、App 和 MCP 工具是什么关系,以及哪些查询动作不该额外做。

数据流:它不接收外部输入,只把固定说明文字和 CODEX_APPS_MCP_SERVER_NAME 这个服务器名拼成一段字符串。输出是一整段说明文本;不会调用工具,也不会改变 App 状态。

调用关系:当上下文系统决定要渲染 AppsInstructions 时,会调用这个函数拿正文。它内部只用格式化字符串生成文本,然后交给外层上下文拼接流程使用。

调用图:外部调用 1 个(format!)。

core/src/plugins/render.rs源码 ↗
domain_logicprompt construction / request handling

这个文件做的事很像给工具箱贴标签:工具箱里可能有技能、外部服务、应用,但光有东西不够,还得把“有哪些、怎么叫、什么时候能用”说清楚。主要函数会接收一个插件的能力摘要,然后按情况拼出几行说明文字。比如插件有技能,就提醒这些技能名前面会带插件名作为前缀;有可用的 MCP 服务器,就列出服务器名字;有可用应用,也列出来。MCP 可以理解成一种让系统连接外部工具或服务的协议。这里还有一个测试时才用的辅助函数,用来把多个插件的总体说明渲染出来。一个重要细节是:如果插件没有任何需要说明的能力,这个文件不会硬凑一段空话,而是返回“没有内容”,这样上层就不会把无用提示塞进上下文里。

函数细节2
render_plugins_section8–10 ↗
fn render_plugins_section(plugins: &[PluginCapabilitySummary]) -> Option<String>

作用:这是测试环境里用的辅助函数,用来把一组插件的能力说明渲染成一整段文字。有人写测试时可以用它检查:插件说明最终会不会按预期出现在提示内容里。

数据流:进去的是一组插件能力摘要。它先把这些插件交给 AvailablePluginsInstructions::from_plugins,让它判断是否能组成“可用插件说明”;如果能组成,就调用 render 把说明变成字符串;如果没有可说明的内容,就得到 None。它不改动插件本身,只产出可选的文字结果。

调用关系:它只在测试编译时存在,像一个测试用入口。它把真正判断和组织说明的工作交给 from_plugins,自己只负责把结果渲染出来,方便测试代码验证整体效果。

调用图:调用 1 个内部函数(from_plugins)。

render_explicit_plugin_instructions12–58 ↗
fn render_explicit_plugin_instructions(
    plugin: &PluginCapabilitySummary,
    available_mcp_servers: &[String],
    available_apps: &[String],
) -> Option<String>

作用:这个函数给单个插件生成一段明确的使用说明。它会告诉读者:这个插件有哪些技能前缀、这次会话里有哪些 MCP 服务器和应用可用,以及应该用这些能力来完成任务。

数据流:进去的是一个插件能力摘要、一串当前可用的 MCP 服务器名字、一串当前可用的应用名字。函数先写下标题,再根据实际情况逐条追加说明:有技能就说明技能名前缀,有服务器就把服务器名用反引号包起来列出,有应用也同样列出。如果最后只有标题、没有任何实际能力说明,它返回 None;否则把所有行用换行符连起来,返回一段完整文字。

调用关系:它是在构建会话提示或插件说明时会被调用的小零件。上层决定某个插件在当前会话中有哪些可用能力后,把这些信息交给它;它不再调用项目里的复杂逻辑,只用格式化和列表拼接把信息变成用户或模型能读懂的说明。

调用图:外部调用 2 个(format!, vec!)。

core/src/context/available_plugins_instructions.rs源码 ↗
domain_logiccontext building

这个文件解决的是一个很实际的问题:系统里可能装了插件,但模型如果不知道插件该怎么理解、怎么使用,就容易把“插件”当成一个可以直接调用的按钮。这里定义了 AvailablePluginsInstructions,它像一张给模型看的便签:只要当前确实有插件,就生成一段开发者身份的提示文字,告诉模型插件是什么、技能名字怎么带插件前缀、MCP 工具(一类外部工具调用接口)怎么识别来源、用户点名插件时该优先考虑什么,以及插件缺能力时该怎么回答。它还用固定的开始和结束标记把这段说明包起来,方便系统把不同上下文片段安全地拼在一起。一个重要细节是:如果没有插件,这个文件不会硬塞一段空说明进去,避免上下文里出现没用的信息。

函数细节5
AvailablePluginsInstructions::from_plugins13–21 ↗
fn from_plugins(plugins: &[PluginCapabilitySummary]) -> Option<Self>

作用:这个函数根据插件列表决定要不要创建一段“插件使用说明”。如果列表是空的,它直接返回没有内容,避免给模型看无意义的插件提示。

数据流:进去的是一组插件能力摘要,也就是系统知道的插件信息。函数先检查这组信息是不是空的;如果空,就输出 None,表示不用生成这段上下文;如果不空,就把插件列表复制进 AvailablePluginsInstructions 里,输出一个可用于后续渲染的说明对象。

调用关系:它通常在组装上下文时被调用,比如 render_plugins_section 或 build_initial_context 需要决定是否加入插件说明时会用它。它不负责写出最终文字,只负责先判断“有没有必要放这一块”。

调用图:被 2 处调用(render_plugins_section, build_initial_context);外部调用 2 个(is_empty, to_vec)。

AvailablePluginsInstructions::role25–27 ↗
fn role(&self) -> &'static str

作用:这个函数告诉系统,这段插件说明应该用什么身份放进对话上下文。这里返回的是 developer,意思是把它当成开发者给模型的规则提示。

数据流:它不需要额外输入,只读取当前对象本身的类型含义。调用后直接输出固定字符串 developer,不改变任何数据。

调用关系:它是 ContextualUserFragment 这套上下文片段接口的一部分。系统在拼接上下文时会问每个片段“你是什么角色”,这个函数就回答插件说明这一段应该放在开发者提示里。

AvailablePluginsInstructions::markers29–31 ↗
fn markers(&self) -> (&'static str, &'static str)

作用:这个函数给这段插件说明提供一对边界标记,也就是“从这里开始”和“到这里结束”。这样系统和模型都更容易分清这段文字的范围。

数据流:它不接收外部数据,只调用同类型的 type_markers,拿到固定的开始标记和结束标记,然后原样返回。它不会修改插件信息。

调用关系:它也是上下文片段接口的一部分。拼上下文的代码需要给每段内容加边框时,会调用它;它把具体标记的来源交给 type_markers,避免两处写重复常量。

调用图:外部调用 1 个(type_markers)。

AvailablePluginsInstructions::type_markers33–38 ↗
fn type_markers() -> (&'static str, &'static str)

作用:这个函数集中定义插件说明这类片段使用的固定边界标记。以后只要需要识别“插件说明块”,就可以用同一组标记。

数据流:它没有输入,也不看对象里的插件列表。它直接输出协议里预先定义好的插件说明开始标签和结束标签。

调用关系:markers 会调用它来拿标记。把标记集中在这里的好处是,别的地方不用猜这段文本该用什么标签包起来。

AvailablePluginsInstructions::body40–58 ↗
fn body(&self) -> String

作用:这个函数真正生成要给模型看的插件说明正文。它用普通文字列出插件的含义和使用规则,帮助模型正确选择插件带来的能力。

数据流:它从当前对象出发生成一串文本,但这里并不会逐个展开插件列表本身,而是写出通用规则:插件是什么、技能命名方式、MCP 工具命名方式、用户点名插件时怎么处理、插件缺能力时怎么说。最后它把这些行合成一个带前后换行的字符串返回,不修改对象。

调用关系:当上下文系统确定要放入插件说明后,会调用它拿到正文。它生成的内容会和 role、markers 提供的信息一起,被包装成完整的上下文片段交给模型阅读。

调用图:外部调用 2 个(format!, vec!)。

core/src/plugins/injection.rs源码 ↗
orchestrationrequest handling

可以把这个文件想成一个“插件使用说明的小纸条生成器”。当用户在对话里点名某个插件后,系统会检查这个插件目前能看到哪些 MCP 服务器(MCP 可以理解成让模型调用外部工具的一套接口)、哪些已启用的应用连接器,然后把这些信息整理成一段开发者提示,塞进要发给模型的消息里。它不会凭空生成所有插件的信息,只处理用户明确提到的插件;如果一个插件没被提到,就什么也不加。它还会去重并排序服务器名和应用名,避免提示里出现重复、混乱的列表。最后生成的是 ResponseItem,也就是对话上下文里的一项内容,模型后续回答或调用工具时会看到它。

函数细节1
build_plugin_injections14–59 ↗
fn build_plugin_injections(
    mentioned_plugins: &[PluginCapabilitySummary],
    mcp_tools: &[ToolInfo],
    available_connectors: &[connectors::AppInfo],
) -> Vec<ResponseItem>

作用:根据用户提到的插件,生成要注入到模型上下文里的插件使用提示。有人会用它,是为了让模型在回答时知道:这个插件现在有哪些可用工具和应用,不要乱猜。

数据流:进去的是三份信息:用户提到的插件列表、当前可用的 MCP 工具列表、当前可用的应用连接器列表。它先检查插件列表是不是空的;如果空,就直接返回空列表。否则它逐个看插件,找出属于这个插件的可见 MCP 服务器,再找出属于这个插件且已经启用的应用连接器,把名字去重、排序后交给提示渲染函数生成说明文字。最后这些说明会被包成上下文片段,再变成 ResponseItem 返回;原来的输入数据不会被改动。

调用关系:它通常在一次请求准备发给模型之前被调用,属于“补上下文”的步骤。它自己不真正调用外部工具,只是遍历传进来的插件、工具和连接器信息;代码里会先用空列表检查避免无谓工作,再用迭代把每个插件变成一条提示,并通过 PluginInstructions::new 这类构造步骤把渲染好的文字包装成模型能接收的内容。

调用图:外部调用 3 个(new, is_empty, iter)。

tools/src/code_mode.rs源码 ↗
domain_logiccross-cutting

可以把这里想成一个“工具说明翻译器”。系统里原本有各种工具:普通函数工具、自由文本工具、带命名空间的一组工具,还有一些代码模式不支持的工具。这个文件会筛选出代码模式能用的那部分,把它们转换成 code-mode 的统一格式,并给描述文字加上代码模式专用的执行示例。命名空间工具还会被改名,比如把“某个工具箱里的某个工具”拼成一个不会混淆的名字。它还会收集一批工具定义,排序、去重,避免同名工具重复出现。没有它,代码模式可能拿到普通模式的说明,名字对不上、示例不合适,模型就更容易调用错工具或不知道该怎么传参数。

函数细节8
augment_tool_spec_for_code_mode8–51 ↗
fn augment_tool_spec_for_code_mode(spec: ToolSpec) -> ToolSpec

作用:这个函数给一个工具说明补上代码模式专用的描述文字。简单说,就是把“给普通人看的工具说明”加工成“给代码模式看的工具说明”。

数据流:进去的是一个 ToolSpec,也就是一份工具规格。它先看这份规格是哪一种:普通函数、自由文本、命名空间工具,还是别的类型;能增强的就生成代码模式版本的描述,再把原来的 description 换掉。出来的是一份新的 ToolSpec;工具本身基本不变,主要是说明文字变得更适合代码模式。

调用关系:它是外部在准备工具列表时会用的入口之一。处理普通函数和自由文本工具时,它会交给 augmented_description_for_spec 去拿增强后的描述;处理命名空间工具时,它会用 namespaced 生成带命名空间的工具名,再用 code_mode_name_for_tool_name 拼出代码模式里的名字,并调用外部的 augment_tool_definition 做最终润色。

调用图:调用 3 个内部函数(namespaced, augmented_description_for_spec, code_mode_name_for_tool_name);外部调用 5 个(augment_tool_definition, to_value, Freeform, Function, Namespace)。

tool_spec_to_code_mode_tool_definition55–59 ↗
fn tool_spec_to_code_mode_tool_definition(spec: &ToolSpec) -> Option<CodeModeToolDefinition>

作用:这个函数把单个工具规格转换成代码模式运行时需要的工具定义。它还会检查这个工具是不是代码模式真正支持的嵌套工具,不支持就不给结果。

数据流:进去的是一个 ToolSpec 的引用。它先尝试从里面取出一份 CodeModeToolDefinition;然后用外部检查函数确认这个工具名是不是代码模式认可的嵌套工具;如果认可,就返回增强后的工具定义,否则返回空值。

调用关系:它通常用在只想处理一个工具的场景。它先把工作交给 code_mode_tool_definition_for_spec 做基础转换,再用外部的 is_code_mode_nested_tool 当门卫,最后由外部的增强逻辑补齐代码模式描述。

调用图:调用 1 个内部函数(code_mode_tool_definition_for_spec);外部调用 1 个(is_code_mode_nested_tool)。

collect_code_mode_tool_definitions61–85 ↗
fn collect_code_mode_tool_definitions(
    specs: impl IntoIterator<Item = &'a ToolSpec>,
) -> Vec<CodeModeToolDefinition>

作用:这个函数从一批工具规格里收集出所有代码模式可用的工具定义,并把描述文字补强、排序、去重。它适合用来生成代码模式正式可见的工具清单。

数据流:进去的是一组 ToolSpec。它逐个展开成代码模式工具定义;如果工具来自命名空间,并且命名空间本身有说明文字,就把这段总说明加到每个子工具描述前面;接着过滤掉代码模式不支持的工具,给剩下的工具补上代码模式示例,最后按名字排序并去掉重名项。出来的是一个干净、有序、没有重复的 CodeModeToolDefinition 列表。

调用关系:它位于“批量准备工具清单”的阶段。外部把所有可用工具交给它,它在内部完成展开、筛选、增强和去重,然后把结果交给后续代码模式使用。

调用图:外部调用 1 个(into_iter)。

collect_code_mode_exec_prompt_tool_definitions87–98 ↗
fn collect_code_mode_exec_prompt_tool_definitions(
    specs: impl IntoIterator<Item = &'a ToolSpec>,
) -> Vec<CodeModeToolDefinition>

作用:这个函数也会从一批工具规格里收集代码模式可用的工具定义,但它不额外增强描述。它更像是给执行提示词准备一份原始但规范的工具清单。

数据流:进去的是一组 ToolSpec。它把每个规格展开成代码模式工具定义,过滤掉代码模式不支持的名字,然后排序并去掉同名重复项。出来的是 CodeModeToolDefinition 列表;和 collect_code_mode_tool_definitions 不同,它不会再加代码模式专用示例。

调用关系:它用于需要工具定义、但不想改写描述文字的流程。外部在构造执行提示词时可以调用它,拿到一份稳定、去重后的工具定义列表。

调用图:外部调用 1 个(into_iter)。

augmented_description_for_spec100–104 ↗
fn augmented_description_for_spec(spec: &ToolSpec) -> Option<String>

作用:这个小函数只做一件事:根据某个工具规格,拿到代码模式增强后的描述文字。它把“转换工具定义”和“取出描述”这两步包成一个方便调用的小帮手。

数据流:进去的是一个 ToolSpec。它先调用 code_mode_tool_definition_for_spec 尝试生成代码模式工具定义;如果成功,就用外部的增强函数加上代码模式说明,再取出里面的 description。出来的是一段描述文字;如果这个规格没法转换,就出来空值。

调用关系:它被 augment_tool_spec_for_code_mode 调用。augment_tool_spec_for_code_mode 想修改原工具描述时,不自己做转换细节,而是把这一步交给它来完成。

调用图:调用 1 个内部函数(code_mode_tool_definition_for_spec);被 1 处调用(augment_tool_spec_for_code_mode)。

code_mode_tool_definition_for_spec106–108 ↗
fn code_mode_tool_definition_for_spec(spec: &ToolSpec) -> Option<CodeModeToolDefinition>

作用:这个函数从一个工具规格里取出第一份代码模式工具定义。它适合处理那些预期只对应一个工具定义的规格。

数据流:进去的是一个 ToolSpec。它调用 code_mode_tool_definitions_for_spec,把规格可能对应的工具定义全部展开成列表;然后取列表里的第一个。出来的是一份 CodeModeToolDefinition,或者在没有可用定义时返回空值。

调用关系:它是单个工具转换流程里的中间层。augmented_description_for_spec 和 tool_spec_to_code_mode_tool_definition 都会用它,避免各自重复写“展开后取第一个”的逻辑。

调用图:调用 1 个内部函数(code_mode_tool_definitions_for_spec);被 2 处调用(augmented_description_for_spec, tool_spec_to_code_mode_tool_definition)。

code_mode_tool_definitions_for_spec110–155 ↗
fn code_mode_tool_definitions_for_spec(spec: &ToolSpec) -> Vec<CodeModeToolDefinition>

作用:这个函数是真正把 ToolSpec 翻译成 CodeModeToolDefinition 的地方。它知道不同类型的工具该怎么变成代码模式需要的形状。

数据流:进去的是一个 ToolSpec。普通函数工具会带上名字、描述、函数类型、输入参数结构和输出结构;自由文本工具会带上名字、描述和自由文本类型,但没有参数结构;命名空间工具会把每个子工具变成单独定义,并生成带命名空间的名字;图片生成、工具搜索、网页搜索这几类不支持的工具会变成空列表。出来的是零个、一个或多个 CodeModeToolDefinition。

调用关系:它是本文件的基础转换器。code_mode_tool_definition_for_spec 会调用它来拿第一份定义;批量收集函数也依赖同样的转换思路,把各种工具规格先统一展开,再继续筛选和整理。

调用图:被 1 处调用(code_mode_tool_definition_for_spec);外部调用 2 个(new, vec!)。

code_mode_name_for_tool_name157–165 ↗
fn code_mode_name_for_tool_name(tool_name: &ToolName) -> String

作用:这个函数把带命名空间的工具名拼成代码模式使用的实际名字。它的目标是既避免重名,又避免多加奇怪的下划线。

数据流:进去的是一个 ToolName,里面可能有 namespace(可以理解成工具箱名字)和 name(具体工具名字)。如果有命名空间,通常会拼成“命名空间__工具名”;但如果命名空间已经以下划线结尾,或者工具名以下划线开头,就直接连起来,避免出现多余分隔符。没有命名空间时,直接返回原工具名。

调用关系:它在处理命名空间工具时被 augment_tool_spec_for_code_mode 使用。这样代码模式看到的是稳定、可区分的工具名,不会把不同工具箱里的同名工具混在一起。

调用图:被 1 处调用(augment_tool_spec_for_code_mode);外部调用 1 个(format!)。

ext/extension-api/examples/enabled_extensions/shared_state_extension.rs源码 ↗
domain_logicstartup and prompt contribution

这个文件像一个小教程扩展。它安装了两个“提示贡献者”:一个告诉模型回答要短一点,另一个告诉模型这个扩展有某种能力。这里的“贡献者”可以理解成给大提示词递小纸条的人。每次递纸条时,它还会在两处存储里各记一次数:一处是会话级存储,另一处是线程级存储。为了安全计数,它用 AtomicU64(原子数字,多个任务同时加一也不容易乱)保存次数;用 Arc(共享指针,让多处代码安全共用同一个对象)把计数器放进扩展数据里。外部还能通过两个读取函数查看已经记录了多少次风格贡献和使用说明贡献。这个例子的重要点是:扩展不只是返回提示词,也可以保留自己的小状态。

函数细节10
install11–14 ↗
fn install(registry: &mut ExtensionRegistryBuilder<()>)

作用:把这个示例扩展里的两个提示贡献者登记到扩展注册表里。宿主程序启动时调用它,之后系统才知道要请这两个贡献者来加提示内容。

数据流:进去的是一个可修改的扩展注册表 → 它创建并包装 StyleContributor 和 UsageContributor,然后把它们登记成提示贡献者 → 出来没有返回值,但注册表被改好了,后续生成提示时会用到这两个贡献者。

调用关系:它由 main 在启动阶段调用,是这个文件接入宿主程序的入口。它把具体工作交给注册表的 prompt_contributor,并用共享指针包装贡献者,后面的真正贡献动作由 StyleContributor::contribute 和 UsageContributor::contribute 完成。

调用图:调用 1 个内部函数(prompt_contributor);被 1 处调用(main);外部调用 1 个(new)。

StyleContributor::contribute20–33 ↗
fn contribute(
        &'a self,
        session_store: &'a ExtensionData,
        thread_store: &'a ExtensionData,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Vec<PromptFragment>> + S

作用:在需要组装提示词时,贡献一条“回答尽量简短”的开发者规则。同时它会记录自己被调用了多少次。

数据流:进去的是会话级存储和线程级存储 → 它从两处存储里取出或创建计数器,并把“风格贡献”次数各加一 → 出来是一组提示片段,其中包含一句让模型默认短答的规则。

调用关系:这个函数是在宿主程序向已注册贡献者索要提示片段时被调用的。它会调用 contribution_counts 找到计数器,再返回 PromptFragment;它的贡献者身份来自 install 的注册。

调用图:调用 1 个内部函数(contribution_counts);外部调用 2 个(pin, vec!)。

UsageContributor::contribute40–53 ↗
fn contribute(
        &'a self,
        session_store: &'a ExtensionData,
        thread_store: &'a ExtensionData,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Vec<PromptFragment>> + S

作用:在需要组装提示词时,贡献一条说明扩展能力的提示片段。同时它会记录自己被调用了多少次。

数据流:进去的是会话级存储和线程级存储 → 它从两处存储里取出或创建计数器,并把“使用说明贡献”次数各加一 → 出来是一组提示片段,其中说明这个扩展可以贡献多个提示片段。

调用关系:这个函数同样是在宿主程序向贡献者索要提示片段时运行。它和 StyleContributor::contribute 走的是同一套记账机制,都通过 contribution_counts 取得共享计数器。

调用图:调用 1 个内部函数(contribution_counts);外部调用 2 个(pin, vec!)。

recorded_style_contributions57–62 ↗
fn recorded_style_contributions(store: &ExtensionData) -> u64

作用:读取某个扩展存储里已经记录了多少次“风格提示”贡献。外部代码可以用它检查或展示这个扩展被触发了几次。

数据流:进去的是一个扩展存储 → 它尝试从里面取出 ContributionCounts 计数器,如果有就读取 style 数字,如果没有就当作 0 → 出来是一个普通数字,表示风格贡献次数。

调用关系:它通常在贡献发生之后被调用,用来观察 StyleContributor::contribute 留下的记录。它不创建计数器,只读已有数据,所以不会改变存储内容。

recorded_usage_contributions65–70 ↗
fn recorded_usage_contributions(store: &ExtensionData) -> u64

作用:读取某个扩展存储里已经记录了多少次“使用说明提示”贡献。它让调用方能看到 UsageContributor 被触发的次数。

数据流:进去的是一个扩展存储 → 它尝试找到 ContributionCounts 计数器,如果找到就读取 usage 数字,找不到就返回 0 → 出来是使用说明贡献的次数。

调用关系:它用于查看 UsageContributor::contribute 的记账结果。和 recorded_style_contributions 一样,它只是读数,不参与注册,也不生成提示片段。

ContributionCounts::record_style79–81 ↗
fn record_style(&self)

作用:把“风格贡献”的计数加一。它是 StyleContributor 每次成功参与提示组装时用来记账的小动作。

数据流:进去的是一个 ContributionCounts 对象本身 → 它对 style 这个原子数字执行加一 → 没有返回值,但内部的风格次数变大了。

调用关系:它被 StyleContributor::contribute 间接使用:贡献者先通过 contribution_counts 拿到计数器,再调用它加一。底层使用 fetch_add,这是原子加法,适合可能并发发生的调用。

调用图:外部调用 1 个(fetch_add)。

ContributionCounts::record_usage83–85 ↗
fn record_usage(&self)

作用:把“使用说明贡献”的计数加一。它是 UsageContributor 每次参与提示组装时用来记账的小动作。

数据流:进去的是一个 ContributionCounts 对象本身 → 它对 usage 这个原子数字执行加一 → 没有返回值,但内部的使用说明次数变大了。

调用关系:它被 UsageContributor::contribute 间接使用:贡献者拿到共享计数器后调用它。它和 record_style 是一对,只是记录的栏目不同。

调用图:外部调用 1 个(fetch_add)。

ContributionCounts::style87–89 ↗
fn style(&self) -> u64

作用:读取当前累计的“风格贡献”次数。它把内部的原子计数转换成外部容易使用的普通数字。

数据流:进去的是一个 ContributionCounts 对象本身 → 它读取 style 这个原子数字的当前值 → 出来是一个 u64 数字,表示风格贡献次数。

调用关系:recorded_style_contributions 会用它拿到最终读数。它只读不改,是计数器对外展示风格次数的出口。

调用图:外部调用 1 个(load)。

ContributionCounts::usage91–93 ↗
fn usage(&self) -> u64

作用:读取当前累计的“使用说明贡献”次数。它让外部不用关心内部怎么存,只拿到一个数字。

数据流:进去的是一个 ContributionCounts 对象本身 → 它读取 usage 这个原子数字的当前值 → 出来是一个 u64 数字,表示使用说明贡献次数。

调用关系:recorded_usage_contributions 会用它拿到最终读数。它只负责读 usage 这一栏,不影响计数器状态。

调用图:外部调用 1 个(load)。

contribution_counts96–98 ↗
fn contribution_counts(store: &ExtensionData) -> Arc<ContributionCounts>

作用:从扩展存储里取出共享计数器;如果还没有,就新建一个放进去。它保证贡献者每次记账都记到同一个地方。

数据流:进去的是一个扩展存储 → 它按 ContributionCounts 这个类型查找已有计数器,找不到就用默认值创建一个 → 出来是一个可共享的计数器 Arc,之后调用方可以拿它加次数或读次数。

调用关系:StyleContributor::contribute 和 UsageContributor::contribute 都会调用它。它是两类贡献者和底层 ExtensionData 存储之间的桥梁,让两个贡献者不用自己操心计数器是否已经存在。

调用图:被 2 处调用(contribute, contribute)。

记忆和目标引导

此组将基于记忆的提示词贡献接入扩展系统,并添加面向目标的引导和记忆写入提示词模板。

ext/memories/src/extension.rs源码 ↗
orchestrationstartup、thread start、config change、prompt/tool collection

可以把这个文件想成“记忆功能的插线板”。记忆功能本身不全在这里实现,但这里负责把它插到 Codex 的几个关键时刻:新对话开始时,先从总配置里读出记忆功能是否开启、工具是否单独启用、Codex 主目录在哪里;配置变化时,再更新这份线程内的记忆配置;模型准备提示词时,如果记忆启用了,就生成一段开发者说明,告诉模型怎么使用记忆;系统收集工具时,如果允许专门工具,就创建读写本地记忆的工具。这里还带着一个可选的 MetricsClient,也就是“指标上报客户端”,用来把工具使用情况之类的数据交给监控系统。没有这个文件,记忆后端和工具可能都存在,但 Codex 不知道该在什么时候启用它们、把它们放到哪里。

函数细节7
MemoriesExtension::new28–30 ↗
fn new(metrics_client: Option<MetricsClient>) -> Self

作用:创建一个记忆扩展对象,并把可选的指标上报客户端保存进去。以后创建记忆工具时,可以继续把这个客户端传下去。

数据流:进去的是一个可能存在、也可能为空的 MetricsClient;函数把它放进 MemoriesExtension 结构里;出来的是一个新的 MemoriesExtension,不会读取或改动外部状态。

调用关系:install 在安装扩展时调用它。这个新建出来的扩展随后会被注册成多个贡献者,分别参与线程启动、配置变化、提示词生成和工具提供。

调用图:被 1 处调用(install)。

MemoriesExtensionConfig::from_config41–47 ↗
fn from_config(config: &Config) -> Self

作用:从 Codex 的总配置里摘出记忆功能真正需要的几项设置。它把复杂的大配置变成一份小而明确的“记忆配置”。

数据流:进去的是 Config;函数检查 MemoryTool 功能开关和 memories.use_memories,合起来决定 enabled;读取 dedicated_tools;复制 codex_home;出来的是 MemoriesExtensionConfig。

调用关系:on_thread_start 和 on_config_changed 都会调用它。也就是说,新对话开始时会生成一次,配置改了以后也会重新生成一次,保证线程里保存的记忆设置是新的。

调用图:被 2 处调用(on_config_changed, on_thread_start)。

MemoriesExtension::contribute51–70 ↗
fn contribute(
        &'a self,
        _session_store: &'a ExtensionData,
        thread_store: &'a ExtensionData,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Vec<PromptFragment>> +

作用:在模型要拿到提示词上下文时,按需加入“记忆工具该怎么用”的开发者说明。简单说,它决定要不要给模型发一张记忆功能的使用说明书。

数据流:进去的是扩展自身、会话数据和当前线程数据;它从线程数据里取 MemoriesExtensionConfig;如果没有配置或记忆没启用,就返回空列表;如果启用了,就根据 codex_home 调用 build_memory_tool_developer_instructions 生成说明文字,再包装成 PromptFragment;出来的是一组要加入提示词的片段。

调用关系:它是 ContextContributor 接口的一部分,会在系统收集提示词上下文时被调用。真正生成说明文字的工作交给 build_memory_tool_developer_instructions;这里负责判断开关,并把结果包装成 Codex 扩展系统认识的提示词片段。

调用图:调用 1 个内部函数(build_memory_tool_developer_instructions);外部调用 2 个(pin, new)。

MemoriesExtension::on_thread_start74–83 ↗
fn on_thread_start(
        &'a self,
        input: ThreadStartInput<'a, Config>,
    ) -> ExtensionFuture<'a, ()>

作用:在一条新线程或新对话开始时,把当前配置里的记忆设置存进线程自己的小仓库里。这样后面的提示词和工具收集就不用反复翻总配置。

数据流:进去的是 ThreadStartInput,里面有当前 Config 和 thread_store;函数用 MemoriesExtensionConfig::from_config 从 Config 生成记忆配置;然后把这份配置插入 thread_store;出来没有业务结果,但线程数据被更新了。

调用关系:它是 ThreadLifecycleContributor 的回调,由扩展系统在线程启动时调用。它把配置准备好,供后面的 contribute 和 tools 读取。

调用图:调用 1 个内部函数(from_config);外部调用 1 个(pin)。

MemoriesExtension::on_config_changed87–95 ↗
fn on_config_changed(
        &self,
        _session_store: &ExtensionData,
        thread_store: &ExtensionData,
        _previous_config: &Config,
        new_config: &Config,
    )

作用:当配置被修改时,同步更新线程里保存的记忆设置。这样用户开关记忆功能或切换工具模式后,不需要旧配置一直生效。

数据流:进去的是会话数据、线程数据、旧配置和新配置;函数不使用旧配置,而是从 new_config 重新生成 MemoriesExtensionConfig;然后插入 thread_store,覆盖或更新原来的记忆配置;出来没有返回值,但线程内配置变成新的。

调用关系:它是 ConfigContributor 的回调,由扩展系统在配置变化时调用。它和 on_thread_start 做的是同一类准备工作,只是触发时机不同:一个在开局,一个在配置更新后。

调用图:调用 2 个内部函数(insert, from_config)。

MemoriesExtension::tools99–115 ↗
fn tools(
        &self,
        _session_store: &ExtensionData,
        thread_store: &ExtensionData,
    ) -> Vec<Arc<dyn codex_extension_api::ToolExecutor<codex_extension_api::ToolCall>>>

作用:在系统询问“现在有哪些工具可用”时,按配置决定要不要提供专门的记忆工具。只有记忆启用并且 dedicated_tools 也启用时,它才会把这些工具拿出来。

数据流:进去的是扩展自身、会话数据和线程数据;函数从 thread_store 读取 MemoriesExtensionConfig;如果没配置、记忆关闭、或专门工具关闭,就返回空列表;否则用 codex_home 创建 LocalMemoriesBackend,再调用 memory_tools 生成工具,并把 metrics_client 一起传进去;出来的是一组可执行工具。

调用关系:它是 ToolContributor 接口的一部分,会在 Codex 收集工具列表时被调用。它不直接读写记忆,而是把本地记忆后端交给 tools::memory_tools,让后者生成真正可执行的工具。

调用图:调用 2 个内部函数(from_codex_home, memory_tools);外部调用 1 个(new)。

install119–128 ↗
fn install(
    registry: &mut ExtensionRegistryBuilder<Config>,
    metrics_client: Option<MetricsClient>,
)

作用:把记忆扩展正式安装到 Codex 的扩展注册表里。没有这一步,前面那些线程启动、配置更新、提示词贡献、工具贡献的能力都不会被系统调用。

数据流:进去的是 ExtensionRegistryBuilder 和一个可选的 MetricsClient;函数先创建 MemoriesExtension,并用 Arc 包起来,Arc 可以理解成“可被多处安全共享的一份对象引用”;然后把同一个扩展分别注册成线程生命周期贡献者、配置贡献者、提示词贡献者和工具贡献者;出来没有返回值,但注册表被添加了记忆扩展的各个入口。

调用关系:它通常在程序启动或扩展初始化阶段被调用。它先调用 MemoriesExtension::new 创建扩展,然后把扩展交给 registry 的 thread_lifecycle_contributor、config_contributor、prompt_contributor 和 tool_contributor,让 Codex 在对应时机回调这个文件里的方法。

调用图:调用 5 个内部函数(config_contributor, prompt_contributor, thread_lifecycle_contributor, tool_contributor, new);外部调用 1 个(new)。

ext/memories/src/prompts.rs源码 ↗
domain_logic生成开发者指令时

这个文件解决的是“模型怎么知道用户以前保存过哪些记忆”的问题。它会到 Codex 主目录下面的 memories 文件夹里找 memory_summary.md。这个文件可以理解成一本记忆簿的目录。如果目录不存在、读不到,或者内容为空,这里就什么也不生成,避免给模型塞一段没用的信息。读到内容后,它会先去掉前后空白,再按令牌上限截断。令牌可以粗略理解成模型读文本时的一小块单位,这样做是为了防止记忆摘要太长,把提示词撑爆。最后,它把“记忆文件夹路径”和“记忆摘要”填进内置模板,生成一段开发者指令。模板本身在程序编译时就嵌进来了,并且只在第一次使用时解析;如果模板写坏了,程序会直接报错,因为这是开发者打包时就应该保证正确的东西。

函数细节2
parse_embedded_template16–21 ↗
fn parse_embedded_template(source: &'static str, template_name: &str) -> Template

作用:这个函数把程序里内置的模板文字解析成可填空的模板对象。它主要是给固定模板做一次安全检查:模板如果有问题,就立刻让程序报错,而不是带着坏模板继续运行。

数据流:进去的是一段模板源码文字和模板名字;它把源码交给模板解析器检查并转换。如果解析成功,就出来一个可以 later 填变量的 Template;如果解析失败,就用模板名字和错误信息触发 panic,也就是直接中止当前程序流程并报出明确原因。

调用关系:它在静态的 MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_TEMPLATE 第一次被使用时运行。它把真正的解析工作交给 Template::parse;失败时交给 panic! 把问题暴露出来。后面的 build_memory_tool_developer_instructions 会使用这个已经解析好的模板来生成最终说明。

调用图:调用 1 个内部函数(parse);外部调用 1 个(panic!)。

build_memory_tool_developer_instructions27–51 ↗
async fn build_memory_tool_developer_instructions(
    codex_home: &AbsolutePathBuf,
) -> Option<String>

作用:这个函数负责真正生成要加到开发者指令里的记忆说明。别人调用它时,只要给出 Codex 的主目录,它就会尝试读取记忆摘要,并返回一段可给模型看的提示文字。

数据流:进去的是 codex_home,也就是 Codex 的主目录绝对路径;它在下面拼出 memories/memory_summary.md,异步读取这个文件,把内容去掉前后空白,再按配置的令牌上限截短。如果文件读不到、内容为空,或者模板渲染失败,就出来 None,表示不添加这段说明;如果一切顺利,就把记忆目录路径和摘要填进模板,出来 Some(String),也就是最终的开发者指令文本。

调用关系:它会在 contribute 需要贡献记忆工具相关说明时被调用。它自己负责路径拼接和读文件,把截断交给 truncate_text,把“按令牌数限制长度”的规则交给 TruncationPolicy::Tokens,最后使用前面准备好的 MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_TEMPLATE 渲染成完整文本。

调用图:调用 1 个内部函数(join);被 1 处调用(contribute);外部调用 3 个(truncate_text, read_to_string, Tokens)。

memories/write/src/prompts.rs源码 ↗
domain_logicmemory write prompt construction

这份文件像一个“提示词装配工”。记忆系统在写入和整理记忆时,需要把固定说明模板、当前记忆目录、扩展目录、工作区差异文件、回放内容等信息塞进一段给模型看的文字里。这里先把内置模板解析好;如果模板本身写错,程序会直接报错,因为这种错误属于发布前就必须发现的问题。生成整理提示词时,它会检查有没有记忆扩展目录;有的话就把扩展目录结构和主要输入也加进提示词,没有就留空。生成第一阶段输入消息时,它还会根据当前模型能接收多少内容,先把很长的回放文本截短,并尽量保留开头和结尾的上下文。这样模型既能看到关键信息,又不会因为输入太长而失败。

函数细节4
parse_embedded_template35–40 ↗
fn parse_embedded_template(source: &'static str, template_name: &str) -> Template

作用:这个函数负责把写在程序里的提示词模板变成可以填空使用的模板对象。它主要用来在程序启动后第一次用到模板时,提前确认模板没有语法错误。

数据流:进去的是一段固定模板文字和模板名字;它调用模板解析器检查并转换这段文字;如果成功,就出来一个可渲染的 Template,如果失败,就直接让程序崩溃并说明是哪一个内置模板坏了。

调用关系:它被几个全局懒加载模板间接使用:这些模板第一次被用到时,会通过它完成解析。它把真正的模板解析工作交给 Template::parse;如果解析失败,就用 panic! 明确暴露问题,因为内置模板坏了不是运行时可以悄悄忽略的事。

调用图:调用 1 个内部函数(parse);外部调用 1 个(panic!)。

build_consolidation_prompt43–87 ↗
fn build_consolidation_prompt(memory_root: &Path) -> String

作用:这个函数生成“第二阶段整理记忆”时给子模型看的完整提示词。它告诉模型记忆目录在哪里、差异文件叫什么,以及如果存在记忆扩展内容,也一并告诉模型该怎么看。

数据流:进去的是一个记忆根目录路径;它先推算出扩展记忆目录的位置,并检查这个目录是否真的存在;然后把路径转成可放进文字里的形式,把可选的扩展说明块渲染出来,最后填进整理阶段的大模板。出来的是一整段提示词字符串;如果模板渲染失败,它会记录警告,并返回一段简化但还能用的备用提示词。

调用关系:在记忆整理阶段,外层流程会调用它来准备发给整理子任务的说明。它自己会在需要插入扩展目录说明时调用 render_memory_extensions_block;也会借助 memory_extensions_root 计算扩展目录位置。它处在“把系统状态翻译成模型能读懂的话”的关键位置。

调用图:调用 1 个内部函数(render_memory_extensions_block);外部调用 4 个(as_str, display, new, memory_extensions_root)。

render_memory_extensions_block89–96 ↗
fn render_memory_extensions_block(template: &Template, memory_extensions_root: &str) -> String

作用:这个函数专门渲染一小块和“记忆扩展目录”有关的提示词。它让主提示词不用关心这块文字的具体格式,只要给出目录路径即可。

数据流:进去的是一个模板和扩展记忆目录的字符串路径;它把路径填进模板;成功时出来一段可插入主提示词的文字,失败时记录警告并返回空字符串,等于跳过这块附加说明。

调用关系:它由 build_consolidation_prompt 在发现扩展目录存在时调用。它把实际填模板的活儿交给 Template::render;如果这块附加提示生成失败,不会拖垮整个整理提示词,只是少放一段扩展说明。

调用图:调用 1 个内部函数(render);被 1 处调用(build_consolidation_prompt)。

build_stage_one_input_message102–127 ↗
fn build_stage_one_input_message(
    model_info: &ModelInfo,
    rollout_path: &Path,
    rollout_cwd: &Path,
    rollout_contents: &str,
) -> anyhow::Result<String>

作用:这个函数生成“第一阶段”要发给模型的用户消息,里面包含回放文件路径、当时的工作目录和回放内容。它还会控制内容长度,防止太大的回放文本塞不进模型。

数据流:进去的是模型信息、回放文件路径、回放发生时的工作目录,以及完整回放内容;它先根据模型的上下文窗口,也就是模型一次最多能看多少文字,算出本次最多保留多少 token(token 可以理解成模型眼里的文字小块);再用截断工具压缩回放内容,保留头尾重点;最后把路径、目录和截断后的内容填入第一阶段模板。出来的是一段完整消息;如果模板渲染出错,就返回错误给调用方。

调用关系:它在记忆写入的第一阶段被调用,用来把原始回放材料整理成模型能处理的输入。它会询问 ModelInfo 的 resolved_context_window 来估算模型容量,并把截断工作交给 truncate_text 和 Tokens 这种截断策略。它不像整理阶段那样返回备用文本;这里渲染失败会作为错误交回外层流程处理。

调用图:调用 1 个内部函数(resolved_context_window);外部调用 4 个(as_str, display, truncate_text, Tokens)。

ext/goal/src/steering.rs源码 ↗
domain_logicmain loop / request handling:在目标继续、目标更新、工具结束检查预算时活跃

这份文件专门生成和“目标”有关的引导消息。系统在运行时可能遇到几种情况:任务还没做完要继续、token 预算快到上限、用户或外部流程更新了目标。每种情况都需要给模型一段清楚、格式固定的说明,否则模型可能不知道现在该收尾、该继续,还是该按新目标调整方向。文件里先把几个 Markdown 模板嵌进程序,并用 LazyLock(第一次用到时才初始化的静态对象)延迟解析,避免启动时多做事。真正使用时,它会从 ThreadGoal 里取出目标文字、已用 token、预算、剩余量等数据,填进模板。目标文字会先做 XML 转义,也就是把 <、>、& 这类容易破坏格式的字符换成安全写法。最后,这些提示会被包装成 ResponseItem,作为一条“内部上下文”交给模型看。

函数细节9
parse_embedded_template30–35 ↗
fn parse_embedded_template(source: &'static str, template_name: &str) -> Template

作用:这个函数负责把写在项目里的模板文本解析成可填空的模板对象。它像检查一张表格模板有没有写坏;如果模板本身有问题,程序会直接报错停下来,因为内置模板不应该在运行时才悄悄失败。

数据流:进去的是一段固定模板文本和它的名字 → 函数调用模板库去解析这段文本 → 如果成功,就返回可用于渲染的 Template;如果失败,就用 panic! 让程序立刻暴露这个开发期错误。

调用关系:它被几个静态模板初始化时使用。CONTINUATION_PROMPT_TEMPLATE、BUDGET_LIMIT_PROMPT_TEMPLATE 和 OBJECTIVE_UPDATED_PROMPT_TEMPLATE 第一次被用到时,会通过它检查并生成模板。它把真正的解析工作交给 Template::parse。

调用图:调用 1 个内部函数(parse);外部调用 1 个(panic!)。

budget_limit_steering_item37–39 ↗
fn budget_limit_steering_item(goal: &ThreadGoal) -> ResponseItem

作用:这个函数生成“预算到限或接近到限时”要给模型看的引导消息。有人会在工具运行结束后用它,提醒模型当前目标、耗时和 token 使用情况,让模型知道该谨慎收尾或调整行为。

数据流:进去的是一个 ThreadGoal,也就是当前目标和预算状态 → 它先调用 budget_limit_prompt 生成一段具体提示文字 → 再调用 goal_context_input_item 把这段文字包装成模型能接收的 ResponseItem → 出来的是一条可塞进对话上下文的内部提示。

调用关系:它会在 on_tool_finish 之后被调用,也就是某个工具执行完后系统检查目标预算时使用。它自己不拼底层上下文格式,而是把提示文字交给 goal_context_input_item 包装。

调用图:调用 2 个内部函数(budget_limit_prompt, goal_context_input_item);被 1 处调用(on_tool_finish)。

objective_updated_steering_item41–43 ↗
fn objective_updated_steering_item(goal: &ThreadGoal) -> ResponseItem

作用:这个函数生成“目标刚被更新了”时给模型看的引导消息。它帮助模型立刻知道新目标是什么,以及当前已经花掉了多少预算,避免继续按旧目标干活。

数据流:进去的是更新后的 ThreadGoal → 它调用 objective_updated_prompt,把新目标、已用 token、预算和剩余量写进模板 → 再交给 goal_context_input_item 包装 → 出来的是一条代表新目标说明的 ResponseItem。

调用关系:它由 apply_external_goal_set 调用,说明外部流程设置或更改目标后,会用它把变化同步给模型。它依赖 objective_updated_prompt 负责写内容,依赖 goal_context_input_item 负责包装成上下文条目。

调用图:调用 2 个内部函数(goal_context_input_item, objective_updated_prompt);被 1 处调用(apply_external_goal_set)。

continuation_steering_item45–47 ↗
fn continuation_steering_item(goal: &ThreadGoal) -> ResponseItem

作用:这个函数生成“任务还要继续做”时给模型看的提示。它让模型在空闲或需要续跑时,记住原来的目标和剩余预算,不至于跑偏。

数据流:进去的是当前 ThreadGoal → 它调用 continuation_prompt 生成继续执行用的提示文字 → 再调用 goal_context_input_item 包成 ResponseItem → 出来的是一条可以加入模型上下文的继续任务提示。

调用关系:它由 continue_if_idle 调用,也就是系统发现当前可以继续推进目标时使用。它是“继续执行”这条路径上的入口,把生成文字和包装上下文这两步串起来。

调用图:调用 2 个内部函数(continuation_prompt, goal_context_input_item);被 1 处调用(continue_if_idle)。

goal_context_input_item49–54 ↗
fn goal_context_input_item(prompt: String) -> ResponseItem

作用:这个函数把一段普通提示文字包装成模型上下文里的标准条目。简单说,它给提示贴上“这是 goal 目标系统提供的内部信息”这个标签,再交给模型。

数据流:进去的是已经写好的 prompt 字符串 → 它用 InternalContextSource::from_static("goal") 标明来源是目标系统 → 再创建 InternalModelContextFragment → 最后转换成 ResponseItem → 出来的是模型协议里可传递的上下文消息。

调用关系:budget_limit_steering_item、continuation_steering_item 和 objective_updated_steering_item 都会调用它。上游函数负责决定写什么内容,它负责统一包装,避免每个入口都重复写同一套转换代码。

调用图:调用 3 个内部函数(into, from_static, new);被 3 处调用(budget_limit_steering_item, continuation_steering_item, objective_updated_steering_item)。

continuation_prompt56–78 ↗
fn continuation_prompt(goal: &ThreadGoal) -> String

作用:这个函数负责写出“继续完成当前目标”的具体提示文字。它会告诉模型目标是什么、已经用了多少 token、总预算是多少、还剩多少。

数据流:进去的是 ThreadGoal → 它先把目标文字做 escape_xml_text,防止特殊字符破坏提示格式 → 再把已用 token、预算、剩余 token 转成文字;如果没有预算,就写成 none 或 unbounded → 然后把这些值填进 continuation 模板 → 出来的是一段完整的继续执行提示。

调用关系:它只被 continuation_steering_item 调用。它专心负责“提示内容怎么写”,写完后由 continuation_steering_item 交给 goal_context_input_item 包装。它会调用 escape_xml_text 来保护目标文字。

调用图:调用 1 个内部函数(escape_xml_text);被 1 处调用(continuation_steering_item)。

budget_limit_prompt80–99 ↗
fn budget_limit_prompt(goal: &ThreadGoal) -> String

作用:这个函数负责写出“预算限制相关”的具体提示文字。它把目标、已用时间、已用 token 和 token 预算放进固定模板,让模型知道资源已经用到什么程度。

数据流:进去的是 ThreadGoal → 它先安全处理目标文字 → 再取出已用秒数、已用 token 和预算;如果没有预算,就写成 none → 接着填入 budget_limit 模板 → 出来的是一段用于预算提醒的提示文字。

调用关系:它只被 budget_limit_steering_item 调用。on_tool_finish 触发预算检查时,会通过 budget_limit_steering_item 间接用到它。它把安全处理目标文字的工作交给 escape_xml_text。

调用图:调用 1 个内部函数(escape_xml_text);被 1 处调用(budget_limit_steering_item)。

objective_updated_prompt101–122 ↗
fn objective_updated_prompt(goal: &ThreadGoal) -> String

作用:这个函数负责写出“目标已更新”的具体提示文字。它让模型看到新的目标,以及当前预算使用状态,方便模型从旧方向切到新方向。

数据流:进去的是 ThreadGoal → 它先转义目标文字 → 再读取已用 token;如果有 token 预算,就算出剩余量,且不会让剩余量变成负数;如果没有预算,就写成 none 和 unknown → 然后把这些信息填入 objective_updated 模板 → 出来的是一段目标更新提示。

调用关系:它只被 objective_updated_steering_item 调用。apply_external_goal_set 更新目标后,会通过 objective_updated_steering_item 间接用到它。它依赖 escape_xml_text 保证新目标文字不会破坏模板结构。

调用图:调用 1 个内部函数(escape_xml_text);被 1 处调用(objective_updated_steering_item)。

escape_xml_text124–129 ↗
fn escape_xml_text(input: &str) -> String

作用:这个函数把目标文字里的特殊字符变安全,主要处理 &、<、>。这样做是为了防止用户目标里刚好带了这些符号时,把提示里的 XML 风格结构弄乱。

数据流:进去的是一段普通文本 → 它依次把 & 换成 &amp;,把 < 换成 &lt;,把 > 换成 &gt; → 出来的是更适合嵌进模板的安全文本;它不改原字符串,而是返回新字符串。

调用关系:continuation_prompt、budget_limit_prompt 和 objective_updated_prompt 都会先调用它再填模板。它是三个提示生成函数共用的小工具,保证无论哪种目标提示,都不会被特殊字符意外打断格式。

调用图:被 3 处调用(budget_limit_prompt, continuation_prompt, objective_updated_prompt)。

prompts/src/goals.rs源码 ↗
domain_logicrequest handling / goal continuation

这个文件解决的是“模型怎么记住并延续一个目标”的问题。用户可能开了一个较长的目标,模型一轮说不完,或者目标中途被改了,或者用量快到上限了。这时系统不能只靠聊天记录猜,而是要塞一段隐藏提示词,把目标、已用 token 数、剩余额度等信息讲清楚。token 可以理解成模型读写文字时消耗的“小单位”。文件里先把三个 Markdown 模板嵌进程序,并用 LazyLock(第一次用到时才初始化的一种安全懒加载工具)解析好。真正生成提示词时,会从 ThreadGoal 里取目标文本和用量数字,先把目标里的 XML 特殊字符转义,避免用户输入的尖括号等字符破坏提示词结构,然后填进模板。若内置模板坏了,程序会直接 panic,也就是立刻报错停止,因为这是开发阶段就必须修好的内部资源问题。

函数细节4
continuation_prompt32–53 ↗
fn continuation_prompt(goal: &ThreadGoal) -> String

作用:生成一段隐藏提示词,用来告诉模型:上一个回合结束了,但这个目标还没完成,请接着做。它会把目标、已用额度、总额度和剩余额度都写进去。

数据流:输入是一份 ThreadGoal,也就是当前目标的记录,里面有目标文字、已用 token 数、可选的 token 上限。函数先把额度数字转成文字;如果没有上限,就写成“none”和“unbounded”。然后把目标文字交给 escape_xml_text 做安全处理,再把这些值填进 continuation 模板。输出是一整段可直接放进上下文的隐藏提示词;如果模板填充失败,就触发 panic 停止程序。

调用关系:它是目标继续执行时会被上层流程调用的造句工具。它自己不决定什么时候继续目标,只负责把上层给的 ThreadGoal 翻译成模型能读懂的提示词。为了避免目标文字破坏模板结构,它会先调用 escape_xml_text。

调用图:调用 1 个内部函数(escape_xml_text);外部调用 1 个(panic!)。

budget_limit_prompt57–75 ↗
fn budget_limit_prompt(goal: &ThreadGoal) -> String

作用:生成一段隐藏提示词,用来告诉模型:目标的额度已经用完或到限制了,请收尾。它重点提醒模型不要再无限展开,而是总结当前结果。

数据流:输入是一份 ThreadGoal,函数读取目标文字、已用 token 数、用掉的时间秒数,以及可选的 token 上限。它把这些数字转成文字,把目标文字用 escape_xml_text 转义,然后填入 budget_limit 模板。输出是一段要求模型收尾的隐藏提示词;如果内置模板无法渲染,就 panic 报错。

调用关系:它通常在目标耗尽预算时由外层控制流程调用。外层负责判断“预算是不是到了”,这个函数只负责把这个状态包装成清楚的提示词。它会把目标文本交给 escape_xml_text,确保用户写的特殊符号不会干扰模板。

调用图:调用 1 个内部函数(escape_xml_text);外部调用 1 个(panic!)。

objective_updated_prompt78–99 ↗
fn objective_updated_prompt(goal: &ThreadGoal) -> String

作用:生成一段隐藏提示词,用来告诉模型:用户刚刚修改了正在进行的目标,请按新目标继续。它帮助模型从旧方向平稳切换到新方向。

数据流:输入是一份已经更新过的 ThreadGoal。函数读取新的目标文字、已用 token 数、可选总预算,并计算剩余 token;没有预算时就写成无上限。它先用 escape_xml_text 处理目标文字,再把目标和额度信息填进 objective_updated 模板。输出是一段说明“目标已更新”的隐藏提示词;模板渲染失败时会 panic。

调用关系:它位于“用户编辑目标”之后、“模型继续响应”之前。上层流程负责保存新目标并调用它,它则把新状态说给模型听。和另外两个提示词函数一样,它依赖 escape_xml_text 来保护模板内容。

调用图:调用 1 个内部函数(escape_xml_text);外部调用 1 个(panic!)。

escape_xml_text101–106 ↗
fn escape_xml_text(input: &str) -> String

作用:把目标文字里的 XML 特殊字符变成安全写法,防止用户输入的符号被误当成提示词结构。简单说,它是在把文字放进模板前先做一次“防串味”的清洗。

数据流:输入是一段普通字符串。函数把其中的 & 替换成 &amp;,把 < 替换成 &lt;,把 > 替换成 &gt;。输出是一段内容意思不变、但放进类似 XML 标记环境里更安全的字符串;它不改原始输入,只返回新字符串。

调用关系:它是三个提示词生成函数共同使用的小工具。continuation_prompt、budget_limit_prompt 和 objective_updated_prompt 在填模板前都会先调用它,这样无论目标文字来自用户还是系统,都不容易破坏隐藏提示词的格式。

调用图:被 3 处调用(budget_limit_prompt, continuation_prompt, objective_updated_prompt)。

评审流程提示词

这些文件将评审请求解析为具体提示词,并渲染评审流程完成或停止时使用的退出片段。

prompts/src/review_request.rs源码 ↗
domain_logicrequest handling

代码审查不是只说一句“帮我看看代码”就够了,模型需要知道看哪里、怎么比较、要输出什么。这个文件就是审查请求的“翻译员”。它先看请求目标:是当前没提交的改动、相对某个基础分支的改动、某个提交,还是用户自定义说明。然后拼出一段具体提示词。遇到基础分支时,它会尽量用 Git 找到当前分支和目标分支共同的起点,也就是 merge base(可以理解成两条分叉路共同出发的路口),这样审查范围更准确;找不到时就给一个备用做法。文件里还准备了给用户看的短提示,比如“current changes”或“commit abc1234”,方便界面显示。模板用 LazyLock(第一次用时才初始化的一次性缓存)保存,避免每次都重新解析。

函数细节5
resolve_review_request42–57 ↗
fn resolve_review_request(
    request: ReviewRequest,
    cwd: &AbsolutePathBuf,
) -> anyhow::Result<ResolvedReviewRequest>

作用:把一份原始审查请求整理成“已解析”的请求,里面包含审查目标、给模型看的完整提示词,以及给用户看的简短说明。别人调用它,是为了在真正开始审查前,把模糊输入变成可执行的明确指令。

数据流:进去的是一个 ReviewRequest 和当前工作目录 cwd。它取出请求里的目标,交给 review_prompt 生成模型要读的提示词;如果请求本身没有给用户看的提示,就补一个默认提示。出来的是 ResolvedReviewRequest;如果生成提示时遇到问题,比如自定义提示为空或 Git 查询失败,就返回错误。

调用关系:它处在审查流程的前置整理阶段。上层拿到用户请求后会先调用它;它把最关键的提示词生成工作交给 review_prompt,自己负责把结果打包成后续流程更好使用的形状。

调用图:调用 1 个内部函数(review_prompt)。

review_prompt59–99 ↗
fn review_prompt(target: &ReviewTarget, cwd: &AbsolutePathBuf) -> anyhow::Result<String>

作用:根据审查目标生成给模型看的具体指令。它决定模型到底该审当前改动、某个分支差异、某个提交,还是照用户自定义文字执行。

数据流:进去的是 ReviewTarget 和当前工作目录 cwd。它按目标类型分支处理:未提交改动直接返回固定说明;基础分支会调用 merge_base_with_head 去找共同起点,再把分支名和提交号填进模板;某个提交会把提交号和可选标题填进模板;自定义说明会先去掉首尾空白,空的话就报错。出来的是一段完整 prompt 字符串,或者一个错误。

调用关系:它是 resolve_review_request 里生成核心提示词的主要工人。需要拼模板时,它会把实际填充动作交给 render_review_prompt;需要知道分支比较起点时,它会借助外部的 merge_base_with_head;自定义说明为空时,它用错误机制中断流程。

调用图:调用 1 个内部函数(render_review_prompt);被 1 处调用(resolve_review_request);外部调用 2 个(bail!, merge_base_with_head)。

render_review_prompt101–108 ↗
fn render_review_prompt(
    template: &Template,
    variables: [(&'a str, &'a str); N],
) -> String

作用:把提前写好的提示词模板和具体变量合成最终文字。比如模板里有“{{sha}}”,它会替换成真实提交号。

数据流:进去的是一个 Template 和一组变量名、变量值。它调用模板的 render 方法完成替换。出来的是渲染后的字符串;如果模板渲染失败,它会直接 panic,也就是认为这是程序员写错模板的严重问题,而不是普通用户输入错误。

调用关系:它是 review_prompt 的小帮手,只负责“填空”。review_prompt 决定用哪个模板、填哪些值;render_review_prompt 负责把这些值真正塞进模板里。

调用图:调用 1 个内部函数(render);被 1 处调用(review_prompt)。

user_facing_hint110–124 ↗
fn user_facing_hint(target: &ReviewTarget) -> String

作用:生成一段给人看的短说明,用来概括这次审查对象。它不是给模型执行的长指令,而是界面或日志里更容易读懂的标签。

数据流:进去的是 ReviewTarget。它按目标类型生成短文字:当前改动会变成“current changes”;基础分支会写成相对某分支的改动;提交会截取提交号前 7 位,必要时加上标题;自定义说明则去掉首尾空白后原样作为提示。出来的是一个字符串,不会改动外部状态。

调用关系:它和提示词生成是配套的展示层辅助。审查流程需要一段面向用户的简短描述时会用到它;它自己不调用复杂逻辑,只用格式化字符串把目标说成人能快速看懂的话。

调用图:外部调用 1 个(format!)。

ReviewRequest::from127–132 ↗
fn from(resolved: ResolvedReviewRequest) -> Self

作用:把已经解析好的审查请求再转换回普通 ReviewRequest。这样需要传输或保存时,可以保留审查目标和已经算好的用户提示。

数据流:进去的是 ResolvedReviewRequest。它取出里面的 target,并把 user_facing_hint 包成 Some 放回 ReviewRequest;模型用的完整 prompt 不会放进去。出来的是一个新的 ReviewRequest。

调用关系:它是类型转换的小桥梁。当前面流程已经得到 ResolvedReviewRequest,但后续接口只认 ReviewRequest 时,就可以通过这个转换把必要信息带回去。

prompts/src/review_exit.rs源码 ↗
domain_logicreview exit rendering

这个文件解决的是“审查流程结束后该说什么”的问题。审查成功时,它会读取一个 XML 模板,把实际的审查结果填进模板里的位置,再输出完整文本;审查被打断时,它直接返回另一份固定模板。这里还有一个小但重要的清理步骤:统一换行符。不同系统保存文件时,换行可能是 Windows 风格的“回车加换行”,也可能是 Unix 风格的“换行”。如果不统一,生成出来的提示可能格式怪异,甚至测试结果不稳定。文件里用 LazyLock(一种“第一次用到时才初始化”的保险箱)缓存成功模板,避免每次都重新解析模板,提高效率,也保证模板有问题时尽早暴露。

函数细节3
render_review_exit_success16–20 ↗
fn render_review_exit_success(results: &str) -> String

作用:生成“审查成功结束”时要显示的完整文字。调用者把审查结果传进来,它负责把结果嵌进预先写好的模板里。

数据流:进去的是一段 results 文本,也就是审查得到的结果;函数使用已经准备好的成功模板,把 results 填到模板中名为 results 的位置;出来的是一整段可以直接展示给用户的字符串。如果模板无法渲染,它会直接报错停止,因为这代表程序自带模板本身有问题。

调用关系:它通常在审查流程正常完成后被调用,负责最后一步“把结果包装成用户能读的提示”。它依赖文件顶部那个延迟初始化的模板;模板第一次被用到时会被解析好,之后重复使用。

render_review_exit_interrupted22–24 ↗
fn render_review_exit_interrupted() -> String

作用:生成“审查被中断”时要显示的文字。它不需要填入动态结果,只要把固定模板整理好后返回。

数据流:进去没有额外参数;它读取内置的中断提示模板文本,然后交给 normalize_review_template_line_endings 统一换行格式;出来的是一段清理过换行、可以直接显示的字符串。

调用关系:它在审查流程没有正常走完、被用户或外部条件打断时使用。它把换行清理这件小活交给 normalize_review_template_line_endings,这样自己只关心“拿到可展示文本”。

调用图:调用 1 个内部函数(normalize_review_template_line_endings)。

normalize_review_template_line_endings26–32 ↗
fn normalize_review_template_line_endings(template: &str) -> Cow<'_, str>

作用:把模板里的换行符统一成一种格式,防止不同操作系统带来的文本差异。简单说,就是把参差不齐的换行整理成整齐的一种换行。

数据流:进去的是一段模板文本;它先检查里面有没有回车符,如果有,就把 Windows 风格的换行和单独的回车都替换成普通换行,并返回一份新的文本;如果没有,就直接借用原来的文本,不额外复制。出来的是一个 Cow 类型的文本结果,Cow 可以理解成“能借用就借用,必须改才复制”的省事包装。

调用关系:它是一个小工具函数,目前由 render_review_exit_interrupted 调用,用来保证中断模板的输出格式稳定。它内部根据是否需要改动,选择返回 Borrowed(借用原文本)或 Owned(新建一份文本),这样既省内存又能保证格式正确。

调用图:被 1 处调用(render_review_exit_interrupted);外部调用 2 个(Borrowed, Owned)。