如何解析 5 种完全不同格式的 AI 对话

本文面向:想了解 5 种 AI 对话格式各自解析策略的开发者。

预计阅读时间:12 分钟

最终效果:理解 JSONL 流式解析、SQLite KV 查询、Event Stream 重建、快照读取的完整策略,以及各格式的特殊处理技巧。

五种格式,五种世界

AI 编程工具没有统一的对话存储标准。每家都按自己的理解设计数据格式,有的追加写日志,有的用数据库,有的存快照。ChatCrystal 需要同时支持 5 种数据源,每种的解析逻辑都截然不同。本文逐个拆解每种格式的特点和对应的解析策略。

一、Claude Code:JSONL 流式日志 + 噪音过滤

Claude Code 的对话存储在 ~/.claude/projects/ 下,按项目目录组织。每个会话是一个 .jsonl 文件,每行一个 JSON 对象。

数据特点: 这是流式写入的日志。每一行代表一个事件------包括流式 delta(文本片段、工具调用片段、思考片段)、完整消息、系统事件等。一个用户提问可能产生几十行日志。

核心难点:噪音过滤。 解析器定义了一个 SKIP_TYPES 集合:

javascript 复制代码
const SKIP_TYPES = new Set([
  'file-history-snapshot', 'last-prompt', 'progress',
  'agent_progress', 'hook_progress', 'queue-operation',
  'message',      // 流式 delta
  'tool_use',     // 流式工具 delta
  'tool_result',  // 流式工具结果 delta
  'thinking',     // 流式思考 delta
  'text',         // 流式文本 delta
  'tool_reference',
]);

过滤逻辑有两层:第一层检查 uuid 是否存在(流式 delta 没有 uuid),第二层检查 type 是否在黑名单中。所有 system 类型也被跳过(包括 turn_durationapi_error 等)。最终只保留 userassistant 类型的完整消息。

内容提取: 消息的 content 字段可能是纯字符串,也可能是 ContentBlock 数组。解析器遍历数组,按 block type 分别处理:text 提取文本,thinking 提取思考过程,tool_use 标记工具调用,tool_result 跳过。

内容清洗: sanitizeContent() 函数用正则移除 Claude Code 注入的 XML 标签:

ini 复制代码
result = result.replace(/<system-reminder>[\s\S]*?</system-reminder>/g, '');
result = result.replace(/<command-name>[^<]*</command-name>/g, '');
result = result.replace(/<command-message>[^<]*</command-message>/g, '');
result = result.replace(/<command-args>[^<]*</command-args>/g, '');
result = result.replace(/<local-command-stdout>[^<]*</local-command-stdout>/g, '');
result = result.replace(/<local-command-caveat>[\s\S]*?</local-command-caveat>/g, '');

这些标签是 Claude Code 的运行时注入,对知识提取没有价值。

二、Codex CLI:事件流 + 多源去重

Codex CLI 的会话记录在 ~/.codex/sessions/ 下,文件名格式为 rollout-{ISO时间}-{sessionId}.jsonl。与 Claude Code 不同,Codex 的 JSONL 不是简单的消息追加,而是一个结构化的事件流。

事件类型路由: 每行 JSON 有 typepayload 两个顶层字段。解析器按 type 分支处理:

  • session_meta --- 会话元数据(ID、工作目录、Git 分支)。只提取一次。
  • event_msg + payload.type === 'user_message' --- 用户消息。需要调用 extractUserPrompt() 去除 IDE 上下文前缀(Codex VS Code 会注入 "## My request for Codex:" 标记)。
  • event_msg + payload.type === 'agent_message' --- 助手文本回复。
  • response_item + payload.type === 'message' --- 完整消息对象(含 output_text content blocks)。
  • response_item + payload.type === 'function_call''custom_tool_call' --- 工具调用,标记到上一条助手消息的 hasToolUse

去重问题: Codex 会同时通过 event_msg/agent_messageresponse_item/message 两种路径输出助手回复。解析器用 lastAssistantMsg 指针检测重复:如果 response_item 的文本与上一条 agent_message 完全相同,就跳过。

Slug 来源: Codex 有一个 ~/.codex/session_index.jsonl 文件,存储会话 ID 到线程名称的映射。解析器在 parse() 结束时加载这个索引来填充 slug 字段。

三、Cursor:跨两个 SQLite 数据库

Cursor 基于 VS Code,对话数据存在 SQLite 的 KV 表里。但它的数据分布在两个位置:

  1. Workspace DB (workspaceStorage/{hash}/state.vscdb) --- 存储 composer.composerData,包含该工作区所有 composer 会话的元数据列表。
  2. Global DB (globalStorage/state.vscdb) --- 存储 cursorDiskKV 表,包含所有 bubble(消息气泡)的实际内容。

扫描流程: 先遍历 workspaceStorage/ 下所有子目录,读取 workspace.json 获取项目路径,打开 state.vscdb 查询 composer.composerData。这是一个 JSON 字符串,解析后得到 allComposers 数组,每个元素有 composerIdcreatedAtname 等字段。

孤儿发现: 有些 composer 的 bubble 数据存在于 global DB 中,但在任何 workspace DB 的 composerData 里都找不到(比如工作区被删除了)。findOrphanBubbleComposers() 用 SQL 的 SUBSTR + INSTRcursorDiskKV 的 key 中提取 composerId,过滤掉已知的,再验证是否有实际文本内容。

Bubble 解析: 每个 bubble 的 key 格式为 bubbleId:{composerId}:{bubbleId},value 是 JSON。BubbleData 用 type 字段区分用户(1)和助手(2),还有 toolResultscodeBlocksallThinkingBlocks 等结构化字段。Cursor 的 bubble schema 有版本号(_v),解析器会对未知版本发出警告。

四、Trae:单 key 存储 + agentTaskContent 嵌套

Trae 同样基于 VS Code,数据也存在 state.vscdb 里,但存储方式与 Cursor 完全不同。

单一 KV 结构: Trae 把所有会话数据存在一个 key 下:memento/icube-ai-agent-storage。这个 value 是一个 JSON 字符串,解析后是 { list: TraeSession[], currentSessionId: string }。每个 TraeSession 包含 sessionIdcreatedAtupdatedAt 和一个 messages 数组。

消息结构: TraeMessage 有 rolecontentturnIndextimestamp 等字段。排序时用 turnIndex 而不是 timestamp,因为助手消息的时间戳有时不可靠。

agentTaskContent 提取: Trae 的 SOLO Builder agent 不直接把回复写在 content 里,而是存在 agentTaskContent 中。这个结构包含:

  • proposal --- 提案文本
  • proposalReasoningContent --- 顶层推理过程
  • guideline.planItems[] --- 计划步骤列表,每个步骤有 toolNamethoughtreasoningContent

解析策略是:优先用 content(直接内容),如果为空则回退到 agentTaskContent。在 agentTaskContent 中,toolName === "finish" 的步骤的 thought 通常是完整回复。工具使用检测则看是否存在 toolName 不是 "finish" 的 planItem。

时间戳合成: Trae 的 assistant 消息时间戳可能早于 user 消息。解析器用一个单调递增的 orderMs 计数器:如果真实时间戳大于当前计数器就用真实的,否则用计数器值并递增 1ms。

五、Copilot:JSONL 快照 + 只读首行

GitHub Copilot 的对话存在 VS Code 的 workspaceStorage/{hash}/chatSessions/globalStorage/emptyWindowChatSessions/ 目录下,文件格式为 .jsonl.json

快照机制: .jsonl 文件的第一行是完整会话快照(kind:0),后续行是 UI 状态 patch(kind:1)。ChatCrystal 只读第一行------用 readFirstLine() 函数打开流,读一行就关闭。.json 文件则是单一 JSON 对象(旧格式),直接用 readFile() 读取整个文件内容:

typescript 复制代码
async function readFirstLine(filePath: string): Promise<string | null> {
  const stream = createReadStream(filePath, { encoding: 'utf-8' });
  const rl = createInterface({ input: stream, crlfDelay: Infinity });
  for await (const line of rl) {
    rl.close();
    stream.destroy();
    return line;
  }
  return null;
}

格式归一化: .jsonl 的快照包裹在 {kind:0, v:{...}} 中,.json 的数据直接在顶层。解析器用 snapshot.v ?? snapshot 统一处理。

Request-Response 结构: 会话数据的核心是 requests 数组。每个 request 有 message(用户消息)和 response(助手回复数组)。response 数组的每个元素有 kind 字段:text 是文本、thinking 是思考过程、toolInvocationSerialized 是工具调用、mcpServersStartinginlineReference 是噪音。

时间戳处理: 助手回复的时间戳设为 request.timestamp + 1(加 1 毫秒),保证在同一 request 内助手消息排在用户消息之后。

总结对比

维度 Claude Code Codex Cursor Trae Copilot
文件格式 JSONL JSONL SQLite KV SQLite KV JSONL/JSON
存储粒度 每会话一文件 每会话一文件 跨两个 DB 单 key 全量 每会话一文件
噪音程度 高(流式 delta) 中(事件类型多) 低(结构化) 低(但嵌套深) 低(快照模式)
工具调用标记 content block type function_call event toolResults + isAgentic planItems toolInvocationSerialized
思考过程 thinking block 加密(不可用) allThinkingBlocks proposalReasoningContent thinking kind
特殊处理 XML 标签清洗 IDE 上下文剥离 孤儿发现 时间戳合成 只读首行

五种格式,五种解析策略,但最终都归一化成同一个 ParsedConversation。这就是 SourceAdapter 插件架构的价值------格式差异被隔离在适配器内部,下游的导入、存储、搜索逻辑完全不需要关心数据从哪来。


项目地址:github.com/ZengLiangYi...

如有疑问欢迎在 GitHub Issues 或私信交流,很乐意解答。

相关推荐
仔仔 v1.01 小时前
第四章: AI图像生成与视频制作实战指南
人工智能
写做四月一日的四月一日1 小时前
在安卓手机上安装小龙虾openclaw并配置QQ机器人接入
android·人工智能
@小阿宝1 小时前
PPF(Point-Pair Feature,点对特征)
人工智能·机器学习
计算机安禾1 小时前
【算法设计与分析】第29篇:启发式与元启发式搜索方法综述
java·数据库·算法
我叫袁小陌1 小时前
数据结构详解与算法关联指南
算法
IronMurphy1 小时前
AI Agent学习day6 从 MCP 到 RAG 记忆:AI Agent 项目中的三块核心基础
人工智能·学习
sleven fung1 小时前
llama-cpp-python 本地部署入门
开发语言·python·算法·llama
cxr8281 小时前
高分子复合材料 AI 逆向设计合——生态级专业补充与产业部署框架
人工智能·材料逆向设计合成
想你依然心痛1 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“律界智脑“——PC端AI智能体沉浸式法律文档智能审查工作台
人工智能·华为·ar·harmonyos·智能体