5 种 AI 对话数据格式全解析

0 阅读8分钟

本文面向:想统一管理多个 AI 编程工具对话数据的开发者。
预计阅读时间:10 分钟
最终效果:理解 Claude Code、Codex、Cursor、Trae、Copilot 五种对话格式的结构、优劣与解析陷阱,明白为什么需要统一抽象层。

当你用 Claude Code 写了一下午代码、用 Cursor 调了半天 bug、用 Copilot 补全了一堆函数之后,你的对话数据散落在三种完全不同的文件格式里。如果你想把这些对话统一管理——导入、搜索、总结——你需要先理解每种格式的结构、优缺点和解析陷阱。

本文深入分析 ChatCrystal 支持的 5 种 AI 对话数据格式。

一、Claude Code:JSONL 文件

文件位置: ~/.claude/projects/ 目录下,按项目路径组织

文件格式: JSONL(JSON Lines),每行一个 JSON 对象

Claude Code 的对话记录是最"朴素"的格式。每个 JSONL 文件代表一次会话,每行是一个消息对象,包含 role(user/assistant)、content(文本内容)、type 等字段。

结构示例:

{"type":"user","message":{"role":"user","content":"帮我写一个 Fastify 插件..."}}
{"type":"assistant","message":{"role":"assistant","content":"好的,这是一个 Fastify 插件的模板..."}}
{"type":"tool_use","message":{"role":"assistant","content":[{"type":"tool_use","name":"write_file","input":{...}}]}}
{"type":"tool_result","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"...","content":"文件已写入"}]}}

解析难点:

  1. 系统标签污染。 Claude Code 的消息内容中经常混入 <system-reminder><command-name> 等 XML 标签。这些标签是 UI 渲染用的,对内容理解没有帮助,反而会干扰 LLM 的摘要生成。ChatCrystal 的 sanitizeContent() 函数会正则匹配并移除这些标签。
  2. 工具调用的嵌套结构。 assistant 消息的 content 有时不是纯字符串,而是一个数组,包含 tool_usetext 类型的混合。解析时需要递归处理。
  3. 连续工具消息的合并。 一个工具调用通常涉及三条消息:assistant 发起 tool_use、user 返回 tool_result、assistant 继续回复。这三条在语义上是一个整体,解析时需要把它们合并为一个"工具调用组"。

优势: 格式简单、可读性好、文件天然按会话分割、不需要额外依赖即可解析。

劣势: 没有元数据头(项目名、开始时间等需要从文件路径推断)、没有内置的索引机制。

二、Codex CLI:事件流 JSONL

文件位置: ~/.codex/sessions/ 目录下

文件格式: JSONL 事件流,每个 JSON 对象是一个事件

Codex CLI 的对话格式和 Claude Code 的 JSONL 有本质区别。它不是直接存储消息,而是存储事件流——一系列带时间戳的操作事件。

核心事件类型:

  • session_meta:会话创建,包含初始配置
  • event_msg:用户输入消息
  • response_item:AI 响应片段(可以是文本、工具调用、推理过程)
  • function_call:工具执行的调用和返回

解析难点:

  1. 对话需要"重建"。 和 Claude Code 的直接消息列表不同,Codex 的对话需要从事件流中重建。你需要遍历所有事件,把 event_msgresponse_item 按时间顺序组装成对话流。
  2. 响应碎片化。 AI 的回复不是一个完整的消息,而是多个 response_item 事件的拼接。有些是文本片段,有些是工具调用,有些是推理过程(chain of thought)。你需要把它们按类型分组,再组装成可读的对话。
  3. 事件顺序依赖时间戳。 如果时间戳精度不够,或者存在时钟回退,事件的顺序可能错乱。ChatCrystal 的解析器会做容错处理。

优势: 事件流保留了完整的操作历史(包括工具调用的输入输出),适合做审计和回放。

劣势: 解析复杂度高、文件体积大(每个事件都带完整元数据)、对话重建需要额外逻辑。

三、Cursor:SQLite 数据库

文件位置: Cursor 的 workspaceStorageglobalStorage 目录下的 state.vscdb

文件格式: SQLite 数据库,键值对存储

Cursor 没有使用文件系统来存储对话,而是用了 VS Code 的状态存储机制——一个 SQLite 数据库文件 state.vscdb。对话数据以键值对的形式存在数据库中。

数据结构:

  • Composer 元数据: 键名类似 composer.composerData,存储所有对话的 ID、标题、创建时间等元信息
  • Bubble 数据: 键名格式为 bubbleId:{composerId}:{bubbleId},存储每条消息的内容,包括用户输入和 AI 回复
  • 工具调用数据: 工具调用的输入输出单独存储在特定的键下

解析难点:

  1. 键值对的间接引用。 对话元数据和消息内容分开存储。你需要先读取元数据获取对话列表和消息 ID,再逐个查询消息内容。这种间接引用模式在数据量大时会产生大量查询。
  2. Blob 数据的编码。 某些值可能是二进制大对象(Blob)而非纯文本,需要判断类型并做相应解码。
  3. 数据库锁定。 如果 Cursor 正在运行,数据库文件可能被锁定。ChatCrystal 的 Cursor 适配器会在检测到锁定时给出提示,建议关闭 Cursor 后再导入。
  4. 数据量膨胀。 Cursor 会在同一个 state.vscdb 里存储远超对话的数据——设置、扩展状态、工作区配置等。一个典型的工作区数据库可能有数百 MB,但对话数据只占很小的比例。

优势: 结构化存储、支持复杂查询(SQL)、单文件管理所有状态。

劣势: 解析依赖 SQLite 库(ChatCrystal 用 sql.js WASM 避免原生依赖)、需要处理数据库锁定、数据模型复杂。

四、Trae:SQLite 数据库(变体)

文件位置: Trae 的 workspaceStorage 目录下的 state.vscdb

文件格式: SQLite 数据库,键值对存储

Trae 基于 VS Code 架构,数据存储方式和 Cursor 类似,但对话的数据模型有显著差异。

数据结构:

Trae 的对话数据存储在 memento/icube-ai-agent-storage 这个键下。每条记录代表一个 Agent 任务(AgentTask),包含:

  • taskId:任务唯一标识
  • taskTitle:任务标题
  • agentTaskContent:一个 JSON 数组,包含任务中所有消息
  • 每条消息有 rolecontentcontentType 等字段

解析难点:

  1. Agent 任务 vs 普通对话。 Trae 的对话模型是"任务制"而非"会话制"。一个任务可能包含多轮对话,但所有消息打包在 agentTaskContent 字段中。这和 Claude Code 的"一个文件一次会话"有本质区别。
  2. 内容类型的多样性。 contentType 不只有纯文本——还有代码块、工具输出、图片描述等。解析时需要按类型分别处理。
  3. 响应内容的提取。 Agent 的回复不是直接存储在 content 里,而是嵌套在特定的响应结构中。ChatCrystal 的 Trae 适配器会提取 content 字段中的文本内容,过滤掉结构化的元数据。

优势: 和 Cursor 类似,结构化存储、SQL 可查询。

劣势: 数据模型更复杂(嵌套 JSON 数组)、Agent 任务的粒度比会话更粗、某些字段的文档缺失需要逆向工程。

五、GitHub Copilot:JSONL 会话快照

文件位置: VS Code 的 workspaceStorage/<id>/chatSessionsglobalStorage/emptyWindowChatSessions

文件格式: JSONL,每行一个会话快照

Copilot 的数据格式相对规整。每个 JSONL 文件包含多个会话快照,每个快照是一个完整的对话记录。

数据结构:

每个快照以 kind:0 标记为完整会话快照,包含:

  • requests:用户请求列表,每条包含 message(用户输入)、timestampresponse(AI 响应数组)等。注意 response 是嵌套在每个 request 对象内部的字段,而非独立的顶层数组。

解析难点:

  1. 响应类型的多样性。 每个 request 内的 response 数组包含多种类型的条目(textthinkingtoolInvocationSerialized 等),需要按 kind 字段分别处理,提取有意义的文本内容。
  2. 会话快照的增量更新。 一个 JSONL 文件中可能包含同一会话的多个版本(每次保存都是完整快照)。解析时需要去重,只保留最新版本。
  3. 多窗口会话的分离。 Copilot 分 workspaceStorageglobalStorage 两个位置存储对话。工作区对话在各自的工作区目录下,全局对话(没有打开工作区时的对话)在 emptyWindowChatSessions 中。完整的导入需要扫描两个位置。

优势: 格式规整、数据结构清晰、JSONL 易于逐行解析。

劣势: 快照式存储导致文件体积冗余(同一个会话的多个版本)、响应类型多样需要分类处理。

格式对比总结

特性Claude CodeCodex CLICursorTraeCopilot
文件格式JSONLJSONLSQLite KVSQLite KVJSONL
对话粒度会话事件流Composer 对话Agent 任务会话快照
解析复杂度中高中低
外部依赖sql.jssql.js
元数据丰富度
工具调用记录
增量更新支持天然支持天然支持需查询需查询需去重

为什么格式统一很重要

5 种格式、5 种解析逻辑、5 种数据模型。如果每个工具都只能管理自己的对话,你最终会得到 5 个孤岛。

ChatCrystal 的 SourceAdapter 插件架构正是为了解决这个问题。每个适配器负责一种格式的解析,输出标准化的 ConversationMessage 类型。上层的导入服务、摘要服务、搜索服务不需要关心数据来自哪个工具——对它们来说,所有对话都是一样的。

这种抽象的价值在搜索时最为明显。当你搜"数据库连接池"时,搜索结果横跨所有 5 种工具的对话。你在 Claude Code 里讨论的架构设计、在 Cursor 里调试的连接泄漏、在 Copilot 里写的连接池代码,都被统一索引和检索。

这才是个人知识管理应有的样子——不被工具割裂


项目地址:github.com/ZengLiangYi…

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