跳到正文
曾亮懿 ZengLiangYi Blog

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

从 Claude Code、Codex、Cursor、Trae 和 Copilot 的实际适配器出发,理解 JSONL 消息、事件流、SQLite KV 与会话快照的解析边界。

曾亮懿

曾亮懿

阅读约 7 分钟
目录 展开 收起

当你在 Claude Code 里讨论架构、在 Cursor 里排查问题、在 Copilot 里继续实现时,相关记录会散落在不同存储中。统一检索之前,需要先回答:什么算一条消息,什么算一次会话,哪些内容值得留下?

ChatCrystal 用不同适配器读取这些记录,再把它们交给共同的导入、笔记和检索流程。本文按 2026 年 9 月 23 日的实现修订,源码参照提交 7642768。这里描述的是项目已处理的格式,不是各工具承诺长期稳定的公开接口,也不是无损备份规范。

Claude Code:逐行读取,但不能逐行当成对话

默认扫描位置是 ~/.claude/projects/。JSONL 的每一行可以独立解析,却不意味着每行都能直接展示给读者:同一文件里还会出现系统记录、进度、文件历史快照以及工具相关内容。

下面是用于说明字段关系的最小示例,不是完整日志:

{"type":"user","uuid":"u1","message":{"role":"user","content":"解释这个错误"}}
{"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"先检查输入类型。"}]}}

当前适配器要求记录具有 uuid,筛选 user 和 assistant,并保留 parentUuid。这是一种面向知识提炼的过滤策略:被跳过的记录不一定毫无价值,只是不属于当前导入结果希望保留的对话正文。

解析时要区分两层类型:外层记录的 type 和 message.content 数组内的块类型。content 可以是字符串,也可以同时包含文本和工具块。不能简单调用 String(content),也不能把所有工具结果直接当成人类输入。

系统附加文本的清理也应有明确范围。例如清理特定的系统标签,不代表可以删除所有尖括号内容;用户可能正在讨论 HTML。会话路径、记录字段和父子关系都可能提供元数据,不能假设项目和时间只能从文件名推断。

Codex:从事件流中选择消息来源

默认扫描位置是 ~/.codex/sessions/。这里同样是 JSONL,但记录围绕事件组织。需要同时检查顶层事件类型和 payload.type。

记录 当前适配器的用途
session_meta 提取会话元数据
event_msg + user_message 提取实际用户输入
event_msg + agent_message 提取助手回复
response_item + message 处理完整消息对象
response_item + function_call / custom_tool_call 标记助手消息使用了工具

function_call 在这里是载荷类型,不能把它与 session_meta 并列理解为同一层字段。另一个常见问题是重复:同一次输入可能在多个事件表示中出现。当前适配器优先使用 event_msg/user_message,跳过 response_item 中的用户消息,并对助手文本做重复处理。

这里的去重有顺序限制:处理 response_item/message 时,会将文本与最近一条助手消息比较;处理 event_msg/agent_message 时则直接追加。因此,如果相同文本先出现在 response_item、后出现在 event_msg,仍可能重复。它并不是按事件 ID 进行的全局去重。

实现最后会按时间戳排序,但排序本身不等于完整恢复因果关系。缺失时间戳、相同时间戳以及格式升级,都需要独立的测试样本。导入后的可读消息也不等于可完整回放的工具执行日志。

Cursor:SQLite 只是容器,KV 才是要理解的模型

Cursor 使用 VS Code 风格的数据目录。Linux 常见根目录为 ~/.config/Cursor/User/,其他系统的位置不同。两个位置尤其重要:

workspaceStorage/<workspace>/state.vscdb
  └─ 工作区与 Composer 元数据

globalStorage/state.vscdb
  └─ 全局会话与 bubble 数据

常见访问入口包括 ItemTable 中的 composer.composerData,以及 cursorDiskKV 中形如 bubbleId:<composerId>:<bubbleId> 的键。SQLite 提供查询能力,但业务结构仍藏在 JSON 值和键的命名规则里。两个库、两张表不能简单互换。

需要处理的边界包括:

  • 元数据和消息内容分离,必须正确关联 ID。
  • 空助手消息可能只是中间状态,不应生成空笔记。
  • 已存在于全局库中的会话,不一定还能从工作区元数据找到。
  • 值的类型、嵌套字段和版本标记可能变化,应记录异常而不是默默丢失数据。

ChatCrystal 通过 sql.js 读取数据库文件的内存副本,不是直接连接正在运行的 Cursor 数据库。这样避免了直接写入源库,但不能据此保证读取的是最新一致快照:正在写入的数据库及其 WAL 需要考虑一致性。排障时应先确认源文件状态;不要修改原始数据库来迁就解析器。

内存副本也有成本。读入文件和实例化数据库都会消耗内存,大量工作区应逐个处理并及时关闭实例,而不是把所有库常驻内存。

Trae:同是 SQLite,消息模型却不同

当前适配器读取工作区 state.vscdb 的 ItemTable,查找 memento/icube-ai-agent-storage。不能因为文件扩展名相同,就复用 Cursor 的 bubble 查询。

当前 TypeScript 模型包含具有 sessionId 和 messages 的会话。助手消息可能直接包含 content,也可能在 agentTaskContent 中包含提案和计划步骤。因此,把整个存储描述成一个统一的“任务消息数组”会遗漏实际结构。

提取顺序是先看直接文本,再按已知结构读取嵌套内容。计划步骤、工具名和推理相关字段各有意义,不能通过把对象序列化为 JSON 来代替正文提取。适配器需要区分“结构存在但没有可读内容”和“解析失败”,否则后续摘要很难判断输入为什么为空。

GitHub Copilot:快照与补丁不能混为一谈

当前扫描范围包括工作区的 chatSessions,以及全局的 emptyWindowChatSessions,并兼容 .json 和 .jsonl 文件。

在当前适配器处理的 JSONL 格式中,首行是 kind: 0 的快照,后续可能是 kind: 1 的状态补丁。不能假设每行都是一份完整会话,然后简单保留最后一行。

{
  "kind": 0,
  "v": {
    "sessionId": "example-session",
    "requests": [
      {"message": {"text": "解释这个函数"}, "response": [{"kind": "text", "value": "函数负责读取配置。"}]}
    ]
  }
}

这是简化示意。response 属于每个 request,而不是独立的顶层数组。响应条目还可能包含工具调用和其他类型,需要分类提取。

当前实现读取 JSONL 首行快照,并没有重放后续全部补丁。这是重要的能力边界:如果更新只存在于补丁中,导入结果可能不能反映最终状态。需要完整还原时,应先补足补丁处理和回归样本,再声称能够恢复整个会话。

统一接口,保留差异

数据源 主要容器 核心解析工作
Claude Code JSONL 消息记录 过滤记录、提取内容块、保留消息关联
Codex JSONL 事件流 选择事件来源、处理重复、组装消息
Cursor SQLite KV 关联工作区元数据与全局消息
Trae SQLite KV 读取会话和嵌套任务内容
Copilot JSON / JSONL 快照与补丁 识别快照结构,明确未重放补丁的边界

ChatCrystal 的 SourceAdapter 将工作拆成 detect()、scan() 和 parse():检测来源、扫描元数据、解析成统一结果。这样上层导入流程不必知道 bubble 或任务字段的细节。

统一输出不意味着统一丢弃信息。至少应保留来源、原始 ID、时间及可追溯线索;解析失败和确实没有正文也应能够区分。扫描阶段的文件大小和修改时间有助于增量导入,但一个文件变化不等于所有内容都是新的。

验证解析器时,应先覆盖哪些情况

最小测试集不应该只有一段正常问答,还应包含:坏 JSON 行、空文件、只有工具结果的消息、重复事件、缺失时间戳、孤立 Composer、未知字段、Windows 与 Unix 路径,以及 Copilot 补丁更新。

对每个样本明确预期:导入几条消息、哪些内容保留、哪些内容跳过、是否给出诊断。使用脱敏或人工构造的数据,避免把真实项目内容带进测试仓库。

这些边界决定了知识库的输入质量。检索再好,也无法补回导入时悄悄丢掉的关键信息。

接下来可以阅读本地向量数据库选型,了解统一内容如何进入检索层;导出后的笔记管理见 Obsidian 工作流。

分享:

相关文章

五种 AI 对话解析器:字段保留与损失边界

从 Claude Code、Codex、Cursor、Trae 和 Copilot 的实际解析器建立验收矩阵,检查工具标记、思考文本、顺序与快照覆盖范围。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #TypeScript #Node.js

AI SDK Provider 抽象:能力表与协议边界

核对 ChatCrystal 的七项 Provider 注册、AI SDK 7 模型工厂和结构化输出,用无网络 mock 验证 Responses、Chat Completions、Embedding 与 schema 失败。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #TypeScript

SourceAdapter 内部链路:身份、内容哈希与重新导入

对照 ChatCrystal 的注册表、导入器和远端载荷,验证会话身份、内容哈希、元数据捷径与 parserVersion 的实际边界。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #TypeScript #Node.js

从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录,欢迎交流实现细节。