5 种 AI 对话数据格式全解析
本文面向想统一管理多个 AI 编程工具对话数据的开发者。预计阅读时间10 分钟最终效果理解 Claude Code、Codex、Cursor、Trae、Copilot 五种对话格式的结构、优劣与解析陷阱明白为什么需要统一抽象层。当你用 Claude Code 写了一下午代码、用 Cursor 调了半天 bug、用 Copilot 补全了一堆函数之后你的对话数据散落在三种完全不同的文件格式里。如果你想把这些对话统一管理——导入、搜索、总结——你需要先理解每种格式的结构、优缺点和解析陷阱。本文深入分析 ChatCrystal 支持的 5 种 AI 对话数据格式。一、Claude CodeJSONL 文件文件位置~/.claude/projects/目录下按项目路径组织文件格式JSONLJSON Lines每行一个 JSON 对象Claude Code 的对话记录是最朴素的格式。每个 JSONL 文件代表一次会话每行是一个消息对象包含roleuser/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:文件已写入}]}}解析难点系统标签污染。Claude Code 的消息内容中经常混入system-reminder、command-name等 XML 标签。这些标签是 UI 渲染用的对内容理解没有帮助反而会干扰 LLM 的摘要生成。ChatCrystal 的sanitizeContent()函数会正则匹配并移除这些标签。工具调用的嵌套结构。assistant 消息的content有时不是纯字符串而是一个数组包含tool_use和text类型的混合。解析时需要递归处理。连续工具消息的合并。一个工具调用通常涉及三条消息assistant 发起 tool_use、user 返回 tool_result、assistant 继续回复。这三条在语义上是一个整体解析时需要把它们合并为一个工具调用组。优势格式简单、可读性好、文件天然按会话分割、不需要额外依赖即可解析。劣势没有元数据头项目名、开始时间等需要从文件路径推断、没有内置的索引机制。二、Codex CLI事件流 JSONL文件位置~/.codex/sessions/目录下文件格式JSONL 事件流每个 JSON 对象是一个事件Codex CLI 的对话格式和 Claude Code 的 JSONL 有本质区别。它不是直接存储消息而是存储事件流——一系列带时间戳的操作事件。核心事件类型session_meta会话创建包含初始配置event_msg用户输入消息response_itemAI 响应片段可以是文本、工具调用、推理过程function_call工具执行的调用和返回解析难点对话需要重建。和 Claude Code 的直接消息列表不同Codex 的对话需要从事件流中重建。你需要遍历所有事件把event_msg和response_item按时间顺序组装成对话流。响应碎片化。AI 的回复不是一个完整的消息而是多个response_item事件的拼接。有些是文本片段有些是工具调用有些是推理过程chain of thought。你需要把它们按类型分组再组装成可读的对话。事件顺序依赖时间戳。如果时间戳精度不够或者存在时钟回退事件的顺序可能错乱。ChatCrystal 的解析器会做容错处理。优势事件流保留了完整的操作历史包括工具调用的输入输出适合做审计和回放。劣势解析复杂度高、文件体积大每个事件都带完整元数据、对话重建需要额外逻辑。三、CursorSQLite 数据库文件位置Cursor 的workspaceStorage或globalStorage目录下的state.vscdb文件格式SQLite 数据库键值对存储Cursor 没有使用文件系统来存储对话而是用了 VS Code 的状态存储机制——一个 SQLite 数据库文件state.vscdb。对话数据以键值对的形式存在数据库中。数据结构Composer 元数据键名类似composer.composerData存储所有对话的 ID、标题、创建时间等元信息Bubble 数据键名格式为bubbleId:{composerId}:{bubbleId}存储每条消息的内容包括用户输入和 AI 回复工具调用数据工具调用的输入输出单独存储在特定的键下解析难点键值对的间接引用。对话元数据和消息内容分开存储。你需要先读取元数据获取对话列表和消息 ID再逐个查询消息内容。这种间接引用模式在数据量大时会产生大量查询。Blob 数据的编码。某些值可能是二进制大对象Blob而非纯文本需要判断类型并做相应解码。数据库锁定。如果 Cursor 正在运行数据库文件可能被锁定。ChatCrystal 的 Cursor 适配器会在检测到锁定时给出提示建议关闭 Cursor 后再导入。数据量膨胀。Cursor 会在同一个state.vscdb里存储远超对话的数据——设置、扩展状态、工作区配置等。一个典型的工作区数据库可能有数百 MB但对话数据只占很小的比例。优势结构化存储、支持复杂查询SQL、单文件管理所有状态。劣势解析依赖 SQLite 库ChatCrystal 用 sql.js WASM 避免原生依赖、需要处理数据库锁定、数据模型复杂。四、TraeSQLite 数据库变体文件位置Trae 的workspaceStorage目录下的state.vscdb文件格式SQLite 数据库键值对存储Trae 基于 VS Code 架构数据存储方式和 Cursor 类似但对话的数据模型有显著差异。数据结构Trae 的对话数据存储在memento/icube-ai-agent-storage这个键下。每条记录代表一个 Agent 任务AgentTask包含taskId任务唯一标识taskTitle任务标题agentTaskContent一个 JSON 数组包含任务中所有消息每条消息有role、content、contentType等字段解析难点Agent 任务 vs 普通对话。Trae 的对话模型是任务制而非会话制。一个任务可能包含多轮对话但所有消息打包在agentTaskContent字段中。这和 Claude Code 的一个文件一次会话有本质区别。内容类型的多样性。contentType不只有纯文本——还有代码块、工具输出、图片描述等。解析时需要按类型分别处理。响应内容的提取。Agent 的回复不是直接存储在content里而是嵌套在特定的响应结构中。ChatCrystal 的 Trae 适配器会提取content字段中的文本内容过滤掉结构化的元数据。优势和 Cursor 类似结构化存储、SQL 可查询。劣势数据模型更复杂嵌套 JSON 数组、Agent 任务的粒度比会话更粗、某些字段的文档缺失需要逆向工程。五、GitHub CopilotJSONL 会话快照文件位置VS Code 的workspaceStorage/id/chatSessions和globalStorage/emptyWindowChatSessions文件格式JSONL每行一个会话快照Copilot 的数据格式相对规整。每个 JSONL 文件包含多个会话快照每个快照是一个完整的对话记录。数据结构每个快照以kind:0标记为完整会话快照包含requests用户请求列表每条包含message用户输入、timestamp、responseAI 响应数组等。注意response是嵌套在每个 request 对象内部的字段而非独立的顶层数组。解析难点响应类型的多样性。每个 request 内的response数组包含多种类型的条目text、thinking、toolInvocationSerialized等需要按kind字段分别处理提取有意义的文本内容。会话快照的增量更新。一个 JSONL 文件中可能包含同一会话的多个版本每次保存都是完整快照。解析时需要去重只保留最新版本。多窗口会话的分离。Copilot 分workspaceStorage和globalStorage两个位置存储对话。工作区对话在各自的工作区目录下全局对话没有打开工作区时的对话在emptyWindowChatSessions中。完整的导入需要扫描两个位置。优势格式规整、数据结构清晰、JSONL 易于逐行解析。劣势快照式存储导致文件体积冗余同一个会话的多个版本、响应类型多样需要分类处理。格式对比总结特性Claude CodeCodex CLICursorTraeCopilot文件格式JSONLJSONLSQLite KVSQLite KVJSONL对话粒度会话事件流Composer 对话Agent 任务会话快照解析复杂度低中高中高中低外部依赖无无sql.jssql.js无元数据丰富度低高中中中工具调用记录有有有有有增量更新支持天然支持天然支持需查询需查询需去重为什么格式统一很重要5 种格式、5 种解析逻辑、5 种数据模型。如果每个工具都只能管理自己的对话你最终会得到 5 个孤岛。ChatCrystal 的SourceAdapter插件架构正是为了解决这个问题。每个适配器负责一种格式的解析输出标准化的Conversation和Message类型。上层的导入服务、摘要服务、搜索服务不需要关心数据来自哪个工具——对它们来说所有对话都是一样的。这种抽象的价值在搜索时最为明显。当你搜数据库连接池时搜索结果横跨所有 5 种工具的对话。你在 Claude Code 里讨论的架构设计、在 Cursor 里调试的连接泄漏、在 Copilot 里写的连接池代码都被统一索引和检索。这才是个人知识管理应有的样子——不被工具割裂。项目地址github.com/ZengLiangYi/ChatCrystal如有疑问欢迎在 GitHub Issues 或私信交流很乐意解答。