如何解析 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 或私信交流,很乐意解答。

相关推荐
不吃辣4905 分钟前
vibe coding | 如何做一个 AI 音乐生成工具?
java·人工智能·后端·ai·ai编程
文人sec8 分钟前
从接口压测到全链路质量保障:AI智能客服系统的软件测试实践
人工智能
声讯电子9 分钟前
实测A-59F音频模组:降噪稳、不啸叫、通话清晰
人工智能·语音识别·ai降噪·usb接口·回音消除
神奇霸王龙14 分钟前
2026旗舰六阶段MCP流水线实战指南
人工智能·ai·ai作画·prompt·aigc·mcp
山河不见老16 分钟前
【Cursor 、Qoder安装问题】Cursor 、Qoder安装卡在“正在准备安装”问题排查及解决
人工智能·windows·编辑器
新知图书16 分钟前
2.3 开发工作流
人工智能·agent·ai agent·智能体
tuanxiang18 分钟前
用免费 AI 改写工具处理批量文案的踩坑记录,附完整降噪脚本
人工智能
小邓的技术笔记19 分钟前
DeepSeek 大模型落地应用与场景实战指南
大数据·人工智能
wuminyu24 分钟前
JUC组件逐层剥离与深度剖析
java·linux·c语言·jvm·c++·算法
石榴25 分钟前
从 Codex + Obsidian 到本地 RAG:我的私人知识库实践
人工智能