Codex 系统手册

外部会话导入持久化

stage-21.52 个文件

这个阶段是幕后支撑导入功能的一层,专门帮系统把“别人家的聊天记录”安全搬进来。ledger.rs 像一本登记簿,记下哪些外部会话文件已经导入过,并给内容做版本识别,避免同一份文件反复搬;如果文件改了,也能当成新版本处理。records.rs 像翻译员,负责打开外部会话日志,挑出真正有用的聊天消息、标题、目录和时间,再整理成 Codex 能看懂的格式。两者配合,一个管“有没有导过、是不是新版”,一个管“内容怎么读、怎么转”,让后续导入流程能稳稳接着用。

本阶段的文件2

导入状态跟踪

该账本会持久化并刷新对先前导入的外部会话文件版本的了解,使后续导入工作可以快速判断哪些是新内容。

external-agent-sessions/src/ledger.rs源码 ↗
domain_logicimport detection and import recording

外部代理会话从别的地方导入进来时,系统需要记住:这个源文件在哪里、当时文件内容的指纹是什么、导入到了哪个线程、什么时候导入的。否则用户可能会一遍遍导入同一个文件,或者文件改了系统却误以为已经处理过。这个文件把这些记录保存到 codex home 目录下的 external_agent_session_imports.json。它用 SHA-256(一种把文件内容算成固定“指纹”的算法)判断文件内容是否一样,而不是只看文件名。主要流程是:读取账本;把源路径变成标准路径,避免同一个文件因为写法不同被当成两个;必要时计算内容指纹;新增或更新记录;最后把账本写回磁盘。它还保存源文件的修改时间,用来帮助后续扫描判断哪些文件值得重新检查。

函数细节12
has_current_session_been_imported46–51 ↗
fn has_current_session_been_imported(
    codex_home: &Path,
    source_path: &Path,
) -> io::Result<bool>

作用:检查某个外部会话文件“当前这个内容版本”是不是已经导入过。有人准备导入前会先问它,避免重复导入。

数据流:输入 codex_home 和源文件路径 → 它先从磁盘读入导入账本 → 再让账本自己判断这个路径和当前文件内容指纹是否已经出现过 → 输出 true 或 false;如果读文件或算指纹失败,就返回错误。

调用关系:它是导入前的门卫。prepare_validated_session_import 会调用它来决定是否继续导入;它把具体查账的工作交给 load_import_ledger 读账本,再交给账本对象里的 contains_current_source 做细查。

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

record_imported_session54–68 ↗
fn record_imported_session(
    codex_home: &Path,
    source_path: &Path,
    imported_thread_id: ThreadId,
) -> io::Result<()>

作用:这是测试用的便捷函数,用来假装“某个会话已经导入过”,方便测试重复导入和文件变化的情况。它只在测试代码里启用。

数据流:输入 codex_home、源文件路径和导入后的线程 ID → 它先把源路径变成系统认可的真实路径 → 计算源文件内容指纹 → 包成一条已完成导入记录 → 交给 record_completed_session_imports 写入账本 → 输出成功或错误。

调用关系:它服务于多个测试场景,比如检测批量会话、跳过已导入会话、源内容变化后重新检测等。它本身不直接写文件,而是把标准化后的记录交给 record_completed_session_imports。

调用图:调用 2 个内部函数(canonical_source_path, record_completed_session_imports);被 4 处调用(detects_sessions_in_batches, redetects_sessions_when_source_contents_change_after_import, skips_already_imported_current_session_versions, skips_session_that_was_already_imported);外部调用 1 个(vec!)。

record_completed_session_imports70–101 ↗
fn record_completed_session_imports(
    codex_home: &Path,
    imports: Vec<CompletedExternalAgentSessionImport>,
) -> io::Result<()>

作用:把一批已经成功导入的会话写进登记簿。它会更新旧记录或追加新记录,让账本始终知道最新导入情况。

数据流:输入 codex_home 和一批完成导入的信息 → 如果列表为空就什么也不做 → 读取现有账本 → 取当前时间作为导入时间 → 对每条导入记录,尝试读取源文件修改时间;如果同一路径同一内容指纹已存在,就把旧记录移到最后并更新时间和线程 ID;否则新增一条记录 → 最后把账本保存回磁盘。

调用关系:它是“导入完成后落账”的核心步骤。测试辅助函数 record_imported_session 会调用它;它自己会调用 load_import_ledger 读旧账本,调用 session_modified_at 取源文件修改时间,最后调用 save_import_ledger 写回去。

调用图:调用 3 个内部函数(load_import_ledger, save_import_ledger, session_modified_at);被 1 处调用(record_imported_session);外部调用 1 个(now_unix_seconds)。

ImportedExternalAgentSessionLedger::source_states104–116 ↗
fn source_states(&self) -> HashMap<&Path, ImportedSourceState>

作用:把账本里的记录整理成“每个源文件当前有什么状态”的表。这样扫描器可以快速知道某个文件上次导入和修改的大致情况。

数据流:输入一个账本对象本身 → 它遍历所有导入记录 → 以源文件路径为键,保存源文件修改时间和导入时间 → 输出一个查找表;如果同一路径有多条记录,后面的记录会覆盖前面的状态。

调用关系:它是给后续检测流程看账本摘要用的工具。它不读写磁盘,只把内存里的 records 转成更容易查询的 HashMap(一张按路径查状态的表)。

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

ImportedExternalAgentSessionLedger::contains_current_source118–134 ↗
fn contains_current_source(&self, source_path: &Path) -> io::Result<bool>

作用:判断账本里是否已经有“这个源文件的当前内容”。重点是当前内容,不只是同名文件。

数据流:输入账本和源文件路径 → 如果账本为空,直接说没有 → 把路径标准化 → 先看账本里有没有这个真实路径 → 如果有,再计算当前文件的 SHA-256 内容指纹 → 检查是否存在同一路径且同一指纹的记录 → 输出 true 或 false。

调用关系:它是 has_current_session_been_imported 的真正判断者。它会调用 canonical_source_path 避免路径写法差异造成误判,也会调用 session_content_sha256 用文件内容确认是不是同一版本。

调用图:调用 2 个内部函数(canonical_source_path, session_content_sha256)。

ImportedExternalAgentSessionLedger::refresh_current_source136–160 ↗
fn refresh_current_source(
        &mut self,
        source_path: &Path,
        source_modified_at: i64,
    ) -> io::Result<bool>

作用:当系统发现某个源文件仍然是已经导入过的当前版本时,用它刷新这条记录的时间信息。可以理解为给旧登记盖一个“最近确认过”的章。

数据流:输入可修改的账本、源文件路径和源文件修改时间 → 标准化路径 → 如果账本从没见过这个路径,返回 false → 计算当前内容指纹 → 找到同一路径同一指纹的最后一条记录 → 更新时间为当前时间,并保存传入的修改时间 → 把记录移到最后 → 输出 true;找不到匹配内容则输出 false。

调用关系:它用于会话检测时更新账本里的已知文件状态。它依赖 canonical_source_path 统一路径,依赖 session_content_sha256 确认内容没变,并用 now_unix_seconds 记录刷新发生的时间。

调用图:调用 2 个内部函数(canonical_source_path, session_content_sha256);外部调用 1 个(now_unix_seconds)。

load_import_ledger163–180 ↗
fn load_import_ledger(
    codex_home: &Path,
) -> io::Result<ImportedExternalAgentSessionLedger>

作用:从磁盘读取导入登记簿。没有账本文件时,它不会报错,而是返回一本空账本。

数据流:输入 codex_home → 拼出账本文件路径 → 读取 JSON 文本 → 如果文件不存在,输出默认的空账本 → 如果文件存在,就把 JSON 解析成账本对象 → 如果 JSON 坏了,输出“无效数据”错误。

调用关系:它是所有查账和记账动作的入口。detect_recent_sessions、has_current_session_been_imported、record_completed_session_imports 都会先靠它拿到账本;它把路径拼接交给 import_ledger_path,把 JSON 解析交给 serde_json。

调用图:调用 1 个内部函数(import_ledger_path);被 3 处调用(detect_recent_sessions, has_current_session_been_imported, record_completed_session_imports);外部调用 3 个(default, read_to_string, from_str)。

save_import_ledger182–190 ↗
fn save_import_ledger(
    codex_home: &Path,
    ledger: &ImportedExternalAgentSessionLedger,
) -> io::Result<()>

作用:把内存里的导入账本保存到磁盘。这样程序下次启动时还能记得以前导入过什么。

数据流:输入 codex_home 和账本对象 → 确保 codex_home 目录存在 → 拼出账本文件路径 → 把账本转成漂亮缩进的 JSON 字节 → 写入 external_agent_session_imports.json → 输出成功或写入错误。

调用关系:它是记账动作的收尾步骤。detect_recent_sessions 和 record_completed_session_imports 会在账本变化后调用它;它使用 import_ledger_path 找到固定文件名,然后通过文件系统写入。

调用图:调用 1 个内部函数(import_ledger_path);被 2 处调用(detect_recent_sessions, record_completed_session_imports);外部调用 3 个(create_dir_all, write, to_vec_pretty)。

import_ledger_path192–194 ↗
fn import_ledger_path(codex_home: &Path) -> PathBuf

作用:算出导入账本文件应该放在哪里。它把用户的 codex home 目录和固定文件名拼在一起。

数据流:输入 codex_home 路径 → 在后面接上 external_agent_session_imports.json → 输出完整文件路径;它不检查文件是否存在,也不读写磁盘。

调用关系:它是 load_import_ledger 和 save_import_ledger 共用的小工具,保证读和写都找同一个账本文件,避免一个地方写到 A、另一个地方读 B。

调用图:被 2 处调用(load_import_ledger, save_import_ledger);外部调用 1 个(join)。

canonical_source_path196–198 ↗
fn canonical_source_path(path: &Path) -> io::Result<PathBuf>

作用:把源文件路径转换成系统确认过的真实路径。这样同一个文件不会因为相对路径、符号链接等不同写法,被误认为是不同文件。

数据流:输入一个路径 → 调用操作系统的路径规范化能力 → 输出真实的绝对路径;如果文件不存在或无法访问,就返回错误。

调用关系:它被 contains_current_source、refresh_current_source 和测试辅助 record_imported_session 使用。凡是要把源文件路径写进账本或拿来比较前,都会先靠它统一口径。

调用图:被 3 处调用(contains_current_source, refresh_current_source, record_imported_session);外部调用 1 个(canonicalize)。

session_content_sha256200–213 ↗
fn session_content_sha256(path: &Path) -> io::Result<String>

作用:给会话文件算一个内容指纹。只要文件内容有一点变化,算出的 SHA-256 字符串通常就会完全不同。

数据流:输入文件路径 → 打开文件 → 每次读取 64KB 内容,分块喂给 SHA-256 计算器,避免一次性把大文件全读进内存 → 读到文件末尾后生成十六进制字符串 → 输出这个内容指纹。

调用关系:它是判断“当前版本是否已导入”的关键工具。contains_current_source 和 refresh_current_source 会调用它;这样系统比较的是文件内容,而不是只相信文件名或修改时间。

调用图:被 2 处调用(contains_current_source, refresh_current_source);外部调用 3 个(open, new, format!)。

session_modified_at215–221 ↗
fn session_modified_at(path: &Path) -> io::Result<Option<i64>>

作用:读取源会话文件的最后修改时间,用来辅助判断文件是否近期变过。它返回的是从 1970 年 1 月 1 日以来的纳秒数,放不下或时间异常时就给 None。

数据流:输入文件路径 → 读取文件元数据 → 取最后修改时间 → 转成距离 UNIX_EPOCH(很多系统用的时间起点,1970 年 1 月 1 日)的时长 → 再转成 i64 数字 → 输出 Some(时间);如果时间早于起点或数字太大无法转换,就输出 None;读取失败则返回错误。

调用关系:它在 record_completed_session_imports 里被调用,用来给新写入或更新的导入记录补充源文件修改时间。这个信息之后可被 source_states 提供给检测流程参考。

调用图:被 1 处调用(record_completed_session_imports);外部调用 1 个(metadata)。

会话记录规范化

记录读取器将外部代理 JSONL 会话解析为摘要或完整消息流,并将其内容规范化为 Codex 可用的形式。

external-agent-sessions/src/records.rs源码 ↗
domain_logicsession import / session discovery

外部智能体的会话文件通常是一行一条 JSON 记录,也就是每行都是一小段结构化文本。这里要解决的问题是:这些记录里有用户消息、助手消息、标题、工具调用、工具结果、无用的元信息,格式还可能不完全一致。如果不先整理,系统就很难导入旧会话,也没法给会话列表显示标题和最后更新时间。这个文件会逐行读文件,跳过空行和坏掉的 JSON,找到工作目录、标题和真正的聊天消息。普通文字会直接保留;工具调用和工具结果会被包成明确的标记,像给聊天记录贴上“这里调用了工具”“这里是工具返回”的标签。为了避免一大坨工具输出撑爆记录,它还会截断过长内容。读完整个文件时,它还能算出内容的 SHA-256 哈希值(像文件内容的指纹),用来判断这份导入内容是否变过。

函数细节16
summarize_session33–94 ↗
fn summarize_session(path: &Path) -> io::Result<Option<SessionSummary>>

作用:快速扫一遍会话文件,提取给会话列表用的摘要信息,比如最后时间、工作目录和标题。它不导入完整聊天,只回答“这个文件像不像一个有效会话,能显示成什么样”。

数据流:输入是一个会话文件路径。它打开文件,逐行读取 JSON,找 cwd 工作目录、手写标题、AI 生成标题和聊天消息;遇到第一条用户消息时,会把用户文字压缩成一个备用标题;同时记录最新时间。最后,如果缺少工作目录、没有消息或没有时间,就返回空;否则返回一个 SessionSummary,里面带着可用于迁移的路径、目录、标题和最新时间。

调用关系:它是会话扫描阶段的轻量入口。它自己负责读文件,并把标题识别交给 custom_title_from_record 和 ai_title_from_record,把消息识别交给 conversation_message_from_owned_record,把备用标题生成交给外部的 summarize_for_label。

调用图:调用 3 个内部函数(ai_title_from_record, conversation_message_from_owned_record, custom_title_from_record);外部调用 4 个(new, open, to_path_buf, summarize_for_label)。

read_session_import96–140 ↗
fn read_session_import(path: &Path) -> io::Result<ParsedSessionImport>

作用:完整读取一个会话文件,准备真正导入。它不仅收集所有可用聊天消息,还会算出整份文件内容的 SHA-256 指纹,方便后面确认导入内容是否重复或被改过。

数据流:输入是会话文件路径。它打开文件,用缓冲读取器一行行读;每读一行就把原始字节喂给哈希计算器;能解析成 JSON 的行会继续提取工作目录、标题和聊天消息。最后输出 ParsedSessionImport,里面有可能存在的 cwd、来源标题、消息列表,以及整份文件内容的十六进制哈希字符串。

调用关系:它是正式导入流程里的核心读取步骤,会被 load_session_for_import_with_content_sha256 调用;测试 reads_session_import_in_one_pass 也会直接调用它验证“一边读一边解析一边算指纹”的行为。它把标题提取交给 custom_title_from_record 和 ai_title_from_record,把消息转换交给 conversation_message_from_owned_record。

调用图:调用 3 个内部函数(ai_title_from_record, conversation_message_from_owned_record, custom_title_from_record);被 2 处调用(load_session_for_import_with_content_sha256, reads_session_import_in_one_pass);外部调用 6 个(new, open, new, new, new, format!)。

custom_title_from_record142–144 ↗
fn custom_title_from_record(record: &JsonValue) -> Option<&str>

作用:从一条记录里找用户手动设置的标题。手动标题通常比机器生成标题更可靠,所以导入时会优先使用它。

数据流:输入是一条 JSON 记录。它只检查这条记录是不是 custom-title 类型,并从 customTitle 字段里取出非空标题;如果不是这种记录,或者标题为空,就返回空。

调用关系:它是标题提取的小帮手,被 summarize_session 和 read_session_import 调用。它不自己判断细节,而是把通用检查交给 title_from_record。

调用图:调用 1 个内部函数(title_from_record);被 2 处调用(read_session_import, summarize_session)。

ai_title_from_record146–148 ↗
fn ai_title_from_record(record: &JsonValue) -> Option<&str>

作用:从一条记录里找 AI 自动生成的标题。没有用户手动标题时,这个标题可以作为会话名称。

数据流:输入是一条 JSON 记录。它检查记录类型是不是 ai-title,并从 aiTitle 字段拿到去掉首尾空白后的非空字符串;拿不到就返回空。

调用关系:它被 summarize_session 和 read_session_import 用来补充标题信息。具体的取字段和过滤空标题工作交给 title_from_record。

调用图:调用 1 个内部函数(title_from_record);被 2 处调用(read_session_import, summarize_session)。

title_from_record150–156 ↗
fn title_from_record(record: &'a JsonValue, record_type: &str, field: &str) -> Option<&'a str>

作用:这是提取标题的通用工具。给它一个期望的记录类型和字段名,它就判断这条 JSON 里有没有对应标题。

数据流:输入是一条 JSON 记录、期望的 type 值、标题字段名。它先看 record.type 是否等于指定类型,再取指定字段作为字符串,去掉首尾空白,并过滤掉空标题。输出是可用标题文本,或者空。

调用关系:它是 custom_title_from_record 和 ai_title_from_record 背后的共用零件,避免两种标题各写一遍同样的检查逻辑。

调用图:被 2 处调用(ai_title_from_record, custom_title_from_record);外部调用 1 个(get)。

conversation_message_from_owned_record158–196 ↗
fn conversation_message_from_owned_record(record: &mut JsonValue) -> Option<ConversationMessage>

作用:把外部日志里的一条原始记录,转换成本项目统一使用的 ConversationMessage。它会过滤掉不是正常对话的记录,比如元信息和旁路消息。

数据流:输入是一条可修改的 JSON 记录。它先确认 type 是 user 或 assistant,再跳过 isMeta、isSidechain 这类不该进入主聊天的内容;然后解析 timestamp 时间字段,取出 message.content。如果 content 是普通字符串,就直接变成文本;如果是分块内容,就交给 extract_message_text 拼成可读文本。最后输出带角色、文本和时间的 ConversationMessage;如果没有有效内容就返回空。

调用关系:它是文件里最关键的“格式转换器”,被 summarize_session 和 read_session_import 共同使用。复杂内容的拆解交给 extract_message_text,时间字符串会通过 parse_timestamp 变成 Unix 时间戳。

调用图:调用 1 个内部函数(extract_message_text);被 2 处调用(read_session_import, summarize_session);外部调用 2 个(get, get_mut)。

extract_message_text203–248 ↗
fn extract_message_text(content: &JsonValue) -> Option<ExtractedMessage>

作用:把一条消息里的复杂内容块,整理成一段人能读的文本。它能识别普通文字、工具调用、工具结果,也会给不支持的块留下提示。

数据流:输入是 JSON 形式的 content,可能是字符串,也可能是数组块。它先用 content_blocks 统一变成一组块;遇到 text 就拿文字,遇到 tool_use 就用 tool_call_note 做成“工具调用说明”,遇到 tool_result 就用 tool_result_note 做成“工具结果说明”,thinking 思考块会被忽略。最后把非空片段用空行拼起来,输出 ExtractedMessage,并标记它是否只有工具结果。

调用关系:它由 conversation_message_from_owned_record 调用,专门处理那些不是简单字符串的消息内容。它把工具调用的格式化交给 tool_call_note,把工具结果的格式化交给 tool_result_note。

调用图:调用 3 个内部函数(content_blocks, tool_call_note, tool_result_note);被 1 处调用(conversation_message_from_owned_record);外部调用 2 个(new, format!)。

content_blocks250–267 ↗
fn content_blocks(content: &JsonValue) -> Vec<JsonValue>

作用:把不同形状的消息内容统一成“内容块列表”。这样后面的代码不用关心原始内容到底是一个字符串还是一个数组。

数据流:输入是 JSON content。如果它本来就是字符串,就包装成一个 type 为 text 的块;如果它是数组,就只保留里面确实是对象的项目并复制出来;其他情况输出空列表。

调用关系:它被 extract_message_text 调用,是复杂消息解析的第一步,作用像把散装材料先摆成同一种托盘。

调用图:被 1 处调用(extract_message_text);外部调用 3 个(as_array, as_str, vec!)。

tool_call_note269–303 ↗
fn tool_call_note(block: &JsonValue) -> String

作用:把外部智能体的一次工具调用,改写成一段清楚的文字标签。这样导入后的聊天记录里还能看出当时调用了什么工具、带了什么参数。

数据流:输入是一个 tool_use 内容块。它读取工具名 name,以及 input 里的 description、command、file_path 或 file;如果没有这些常见字段,就把整个 input 转成字符串并截短。最后输出一段用 external_agent_tool_call 开头和结尾包起来的文本。

调用关系:它由 extract_message_text 在遇到 tool_use 块时调用。它还会使用 truncate 截断过长输入,避免工具参数太大导致聊天记录难读或占空间过多。

调用图:被 1 处调用(extract_message_text);外部调用 3 个(get, format!, vec!)。

tool_result_note305–320 ↗
fn tool_result_note(block: &JsonValue) -> String

作用:把工具返回结果改写成带标签的聊天文本。成功和错误会用不同标签标出来,读历史记录时一眼能看出工具是否失败。

数据流:输入是一个 tool_result 内容块。它先看 is_error 是否为 true,决定标签里要不要写 error;再用 tool_result_text 提取结果正文;正文为空就只输出空标签,正文不为空就截断到上限后放进标签中间。

调用关系:它由 extract_message_text 在遇到 tool_result 块时调用,并把真正提取正文的工作交给 tool_result_text。它也会使用 truncate 控制过长工具输出。

调用图:调用 1 个内部函数(tool_result_text);被 1 处调用(extract_message_text);外部调用 2 个(get, format!)。

tool_result_text322–333 ↗
fn tool_result_text(content: Option<&JsonValue>) -> String

作用:从工具结果块的 content 字段里拿出可显示的文字。它兼容两种常见写法:直接字符串,或者数组里每项带 text 字段。

数据流:输入是可选的 JSON content。如果是字符串,就原样返回;如果是数组,就收集每个项目里的非空 text 并用换行拼起来;如果没有内容或格式不认识,就返回空字符串。

调用关系:它只被 tool_result_note 调用,是工具结果格式化前的“取正文”步骤。

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

parse_timestamp335–339 ↗
fn parse_timestamp(timestamp: &str) -> Option<i64>

作用:把外部记录里的时间字符串转换成机器更好比较的数字时间。这里接受的是 RFC3339 格式,也就是类似 2026-06-03T12:00:00Z 这样的标准时间写法。

数据流:输入是时间字符串。它尝试按 RFC3339 标准解析;成功后输出 Unix 时间戳,也就是从 1970 年开始经过的秒数;解析失败就返回空。

调用关系:它服务于消息转换过程,让 summarize_session 可以比较哪条消息最新。底层解析交给 chrono 库的 parse_from_rfc3339。

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

tests::reads_session_import_in_one_pass347–383 ↗
fn reads_session_import_in_one_pass()

作用:这个测试确认 read_session_import 能一次读完文件,同时拿到目录、标题、消息和正确的内容指纹。它也确认坏掉的 JSON 行不会把导入流程搞崩。

数据流:测试先创建临时目录和 session.jsonl 文件,写入用户消息、无效文本、AI 标题和手动标题。然后调用 read_session_import,检查输出的 cwd、标题、消息数量、消息文本和 SHA-256 哈希都符合预期。

调用关系:它直接验证 read_session_import 这个正式导入入口,确保标题优先级、坏行跳过、消息提取和哈希计算这些关键行为没有被改坏。

调用图:调用 1 个内部函数(read_session_import);外部调用 4 个(new, assert_eq!, json!, write)。

tests::converts_tool_use_blocks_to_bounded_external_agent_tags386–403 ↗
fn converts_tool_use_blocks_to_bounded_external_agent_tags()

作用:这个测试确认工具调用块会被转换成固定格式的 external_agent_tool_call 标签文本。这样导入后的记录不会丢失“调用了什么工具”的信息。

数据流:测试构造一个 Bash 工具调用块,里面有 description 和 command。然后调用 tool_call_note,检查生成文本是否包含正确的开头标签、描述、命令和结束标签。

调用关系:它覆盖 extract_message_text 处理 tool_use 时依赖的格式化函数 tool_call_note,防止标签格式被无意改动。

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

tests::converts_tool_result_blocks_to_bounded_external_agent_tags406–418 ↗
fn converts_tool_result_blocks_to_bounded_external_agent_tags()

作用:这个测试确认普通工具结果会被转换成 external_agent_tool_result 标签文本。它保证工具输出在导入后仍然能被读者看见。

数据流:测试构造一个带字符串 content 的 tool_result 块。然后调用 tool_result_note,检查输出是否是正常结果标签加正文加结束标签。

调用关系:它验证 tool_result_note 的成功结果路径,也间接覆盖 tool_result_text 对字符串结果的提取。

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

tests::converts_error_tool_result_blocks_to_bounded_external_agent_tags421–434 ↗
fn converts_error_tool_result_blocks_to_bounded_external_agent_tags()

作用:这个测试确认出错的工具结果会被明确标成 error。这样看历史记录时,不会把失败输出误以为正常结果。

数据流:测试构造一个 is_error 为 true、content 为 command failed 的工具结果块。然后调用 tool_result_note,检查输出标签里带有 error,并且正文保留下来。

调用关系:它验证 tool_result_note 的错误结果路径,确保 extract_message_text 在导入工具失败记录时能保留失败状态。

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