Codex CLI / Trae / Copilot 数据源接入

0 阅读5分钟

本文面向:使用 Codex CLI、Trae 或 GitHub Copilot 的开发者,想把对话数据导入 ChatCrystal。 预计阅读时间:8 分钟


三个数据源,三种格式

ChatCrystal 支持 5 种 AI 编程工具的对话导入。Claude Code 和 Cursor 在前两篇已经讲过,这篇覆盖剩下的三个:

数据源存储格式数据位置
Codex CLIJSONL 事件流~/.codex/sessions/
TraeSQLite (state.vscdb)Trae workspaceStorage
GitHub CopilotJSONL / JSONVS Code workspaceStorage + globalStorage

它们的格式完全不同,但 ChatCrystal 的插件架构屏蔽了这些差异——导入命令一样:

crystal import

自动检测你装了哪些工具,有数据就导入。

Codex CLI

数据在哪

~/.codex/sessions/
├── <session-hash>/
│   └── rollout-2026-05-10T14-30-00-abc123.jsonl
├── <session-hash>/
│   └── rollout-2026-05-10T15-00-00-def456.jsonl
└── session_index.jsonl          ← 会话名称索引

每个 rollout-*.jsonl 是一次对话,文件名里嵌入了时间戳和会话 ID。

JSONL 事件流格式

Codex CLI 的 JSONL 不是简单的一行一条消息,而是事件流:

{"type": "session_meta", "payload": {"id": "abc123", "cwd": "/home/user/project", "git": {"branch": "main"}}}
{"type": "event_msg", "timestamp": "2026-05-10T14:30:01Z", "payload": {"type": "user_message", "message": "帮我优化这个查询"}}
{"type": "event_msg", "timestamp": "2026-05-10T14:30:05Z", "payload": {"type": "agent_message", "message": "分析了你的查询..."}}
{"type": "response_item", "payload": {"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "分析了你的查询..."}]}}

ChatCrystal 需要从这些事件里重建对话:

事件类型用途ChatCrystal 处理
session_meta会话元数据提取 sessionId、cwd、git branch
event_msg + user_message用户输入作为 user 消息
event_msg + agent_message助手回复作为 assistant 消息
response_item + message完整消息对象检查是否与 agent_message 重复,去重后保留
response_item + function_call工具调用标记 lastAssistantMsg.hasToolUse = true

特殊处理:VS Code 上下文剥离

Codex VS Code 插件会在用户消息前面加上 IDE 上下文信息:

## Environment
- OS: linux
- ...

## My request for Codex:
帮我优化这个查询

ChatCrystal 会自动找到 ## My request for Codex: 标记,只保留后面的实际请求。这样摘要不会被环境信息污染。

session_index.jsonl

Codex CLI 会维护一个会话名称索引:

{"id": "abc123", "thread_name": "优化数据库查询"}

ChatCrystal 读取这个文件,把 thread_name 作为对话的 slug(标题),这样导入后更容易识别。

Trae

数据在哪

系统路径
Windows%APPDATA%\Trae\User\workspaceStorage\
macOS~/Library/Application Support/Trae/User/workspaceStorage/
Linux~/.config/Trae/User/workspaceStorage/

和 Cursor 一样,数据存在 SQLite 的 state.vscdb 里,但存储结构不同。

存储 key

Trae 把所有对话存在一个 key 下:

SELECT value FROM ItemTable WHERE [key] = 'memento/icube-ai-agent-storage'

这个 value 是一个 JSON 对象:

{
  "list": [
    {
      "sessionId": "session-001",
      "createdAt": 1715340600000,
      "updatedAt": 1715340900000,
      "messages": [
        {
          "role": "user",
          "content": "帮我搭一个 React 项目",
          "turnIndex": 0
        },
        {
          "role": "assistant",
          "content": "",
          "agentTaskContent": {
            "proposal": "我来帮你创建一个 React 项目...",
            "guideline": {
              "planItems": [
                {"toolName": "create_file", "thought": "创建 package.json"},
                {"toolName": "finish", "thought": "项目创建完成,已配置好..."}
              ]
            }
          }
        }
      ]
    }
  ],
  "currentSessionId": "session-001"
}

助手消息提取

Trae 的 Agent(SOLO Builder)回复比较特殊。助手消息的 content 字段经常是空的,实际内容藏在 agentTaskContent 里。ChatCrystal 的提取优先级:

  1. msg.content — 直接内容,优先使用
  2. agentTaskContent.proposal — 提案文本
  3. agentTaskContent.guideline.planItemstoolName === "finish"thought — 最终总结

如果 content 为空,ChatCrystal 会从 agentTaskContent 里拼接出完整回复。

思考过程提取

Trae 的思考过程分散在多个地方:

  • agentTaskContent.proposalReasoningContent — 顶层推理
  • agentTaskContent.guideline.planItems[].reasoningContent — 每个步骤的推理

ChatCrystal 会把所有推理内容合并为 thinking 字段。

GitHub Copilot

数据在哪

Copilot 的对话数据分布在两个位置:

工作区对话(有项目上下文):

~/.config/Code/User/workspaceStorage/<hash>/chatSessions/
├── session-001.jsonl
├── session-002.json
└── ...

全局对话(无项目,如新窗口聊天):

~/.config/Code/User/globalStorage/emptyWindowChatSessions/
├── session-003.jsonl
└── ...
系统VS Code 路径
Windows%APPDATA%\Code\User\
macOS~/Library/Application Support/Code/User/
Linux~/.config/Code/User/

两种文件格式

Copilot 同时使用 .jsonl.json 两种格式:

.jsonl 格式(第一行是快照,后续行是 UI 状态补丁):

1: {"kind": 0, "v": {"sessionId": "...", "requests": [...]}}   ← 只读这一行
行2: {"kind": 1, ...}                                             ← UI 补丁,跳过
行3: {"kind": 1, ...}

.json 格式(旧版,整个文件是一个 JSON 对象):

{
  "sessionId": "abc123",
  "creationDate": 1715340600000,
  "customTitle": "React 项目搭建",
  "requests": [...]
}

ChatCrystal 对 .jsonl 只读第一行(kind: 0 的快照),跳过后续的 UI 状态补丁。

请求-响应结构

每个 request 包含用户消息和助手回复:

{
  "requestId": "req-001",
  "timestamp": 1715340600000,
  "message": {
    "text": "怎么用 React Query 做缓存"
  },
  "response": [
    {"kind": "thinking", "value": "用户想了解 React Query 的缓存机制..."},
    {"kind": "text", "value": "React Query 的缓存基于 stale-while-revalidate 策略..."},
    {"kind": "toolInvocationSerialized", ...}
  ]
}

response 数组里每项的 kind 决定类型:

kind说明ChatCrystal 处理
text文本回复提取为 content
thinking思考过程提取为 thinking
toolInvocationSerialized工具调用标记 hasToolUse
mcpServersStartingMCP 启动信息跳过
inlineReference内联引用跳过
undefined无 kind 的文本提取为 content(兼容旧版)

自定义标题

Copilot 允许用户给对话设置标题(customTitle),ChatCrystal 会把它作为 slug 导入,方便在界面上识别。

自定义数据目录

如果某个工具的数据不在默认位置,通过环境变量指定:

# Codex CLI
CODEX_SESSIONS_DIR=/path/to/codex/sessions crystal import

# Trae
TRAE_DATA_DIR=/path/to/trae/User crystal import

# GitHub Copilot
COPILOT_DATA_DIR=/path/to/code/User crystal import

或在 ChatCrystal 设置页面修改对应配置。

导入验证

导入完成后,检查各数据源的状态:

crystal status

输出示例:

ChatCrystal Status
  Server       : running
  Database     : connected
  Conversations: 147
  Sources      : claude-code(52), cursor(38), codex(21), copilot(19), trae(17)
  Notes        : 89
  Tags         : 23

如果某个数据源显示 0,检查:

  1. 对应工具是否有历史对话
  2. 数据目录路径是否正确
  3. 运行 crystal import --source <数据源名> 单独触发某个数据源

下一步


项目地址:github.com/ZengLiangYi…