---
title: 5 种 AI 对话数据格式全解析
description: 从 Claude Code、Codex、Cursor、Trae 和 Copilot 的实际适配器出发，理解 JSONL 消息、事件流、SQLite KV 与会话快照的解析边界。
image: "https://zengliangyi.com/og/blog/ai-conversation-data-formats.png"
---

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

[ChatCrystal](/projects/chatcrystal) 用不同适配器读取这些记录，再把它们交给共同的导入、笔记和检索流程。本文按 2026 年 9 月 23 日的实现修订，源码参照提交 [`7642768`](https://github.com/ZengLiangYi/ChatCrystal/tree/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/parser)。这里描述的是项目已处理的格式，不是各工具承诺长期稳定的公开接口，也不是无损备份规范。

## Claude Code：逐行读取，但不能逐行当成对话

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

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



```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/`，其他系统的位置不同。两个位置尤其重要：



```text
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` 的状态补丁。不能假设每行都是一份完整会话，然后简单保留最后一行。



```json
{  
    "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 补丁更新。

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

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

接下来可以阅读[本地向量数据库选型](/blog/local-vector-database-selection)，了解统一内容如何进入检索层；导出后的笔记管理见 [Obsidian 工作流](/blog/chatcrystal-to-obsidian)。

分享：

##  相关文章

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

从 Claude Code、Codex、Cursor、Trae 和 Copilot 的实际解析器建立验收矩阵，检查工具标记、思考文本、顺序与快照覆盖范围。          曾亮懿       2026年10月7日      阅读约 4 分钟

#ChatCrystal

#TypeScript

#Node.js](/blog/ai-conversation-parser-boundaries)    [##  AI SDK Provider 抽象：能力表与协议边界

核对 ChatCrystal 的七项 Provider 注册、AI SDK 7 模型工厂和结构化输出，用无网络 mock 验证 Responses、Chat Completions、Embedding 与 schema 失败。          曾亮懿       2026年10月7日      阅读约 4 分钟

#ChatCrystal

#Node.js

#TypeScript](/blog/ai-sdk-provider-abstraction)    [##  SourceAdapter 内部链路：身份、内容哈希与重新导入

对照 ChatCrystal 的注册表、导入器和远端载荷，验证会话身份、内容哈希、元数据捷径与 parserVersion 的实际边界。          曾亮懿       2026年10月7日      阅读约 4 分钟

#ChatCrystal

#TypeScript

#Node.js](/blog/source-adapter-internals)

##  从文章到代码

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

[访问我的 GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "5 种 AI 对话数据格式全解析",
  "description": "从 Claude Code、Codex、Cursor、Trae 和 Copilot 的实际适配器出发，理解 JSONL 消息、事件流、SQLite KV 与会话快照的解析边界。",
  "url": "https://zengliangyi.com/blog/ai-conversation-data-formats/",
  "image": "https://zengliangyi.com/images/blog/ai-conversation-data-formats.svg",
  "datePublished": "2026-06-09T00:45:00.000Z",
  "dateModified": "2026-09-24T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/blog/ai-conversation-data-formats/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "首页",
      "item": "https://zengliangyi.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "文章",
      "item": "https://zengliangyi.com/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "5 种 AI 对话数据格式全解析",
      "item": "https://zengliangyi.com/blog/ai-conversation-data-formats/"
    }
  ]
}
```
