dsh-chat-import
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
从Claude Code、Codex、Reasonix等Agent工具导入历史消息,并在DSH中继续对话
DSH Chat Import
Bring your Claude Code, Codex, ChatGPT, Cursor, Gemini, Reasonix and opencode conversations into DeepSeek Harness — and keep talking exactly where you left off.
Listed in: Awesome DeepSeek Harness · Awesome DSH Plugin · Awesome DSH Plugins · npm
dsh-chat-import turns your external agent chat history into full-fidelity, resumable DeepSeek Harness sessions — tool calls, reasoning and all. It reads transcripts read-only (never rewrites your source files), never touches the DSH engine, and appends every import as a fresh, event-balanced session log through the public sessionPersistence service, grouped into the workspace of its cwd.
7 sources · Copy-only · Seamlessly resumable · Auto workspace grouping
✨ Features
- 📥 Import from 7 sources — Claude Code JSONL, Codex / ChatGPT CLI rollouts, ChatGPT web exports, Cursor agent transcripts, Gemini CLI sessions, Reasonix sessions, and opencode SQLite history. One plugin, one call per source.
- 🔍 Full fidelity — tool history becomes real
tool/call+tool/resultpairs (error flags andsourceEventSeqslinkage included), thinking blocks becomereasoning, multi-step assistant messages are preserved. - ▶️ Seamlessly resumable — every import synthesizes a balanced, loadable session (
turn/start→step/start→user/message→assistant/message→tool/call/tool/result→step/end→turn/end): open it and keep chatting. - 🗂 Auto workspace grouping — sessions land in the workspace of their source
cwd(no more "ungrouped"); session id, title, model and creation time are preserved where the source records them. - 🔁 Idempotent — re-importing skips sessions that already exist; malformed lines are counted and reported, never aborting the import.
- 📦 Batch import — point at a directory (or the whole opencode DB) and every file / conversation becomes its own session, with a per-file summary.
🚀 Quick start
# 1. Install (npm package)
dsh plugin --profile web add dsh-chat-import
# or from a local checkout
dsh plugin --profile web add -w link:/path/to/dsh-chat-import
- In any DSH session, import a single file or a whole directory:
import_claude({ path: "~/.claude/projects" })
- Refresh the session list once, open the imported session, and continue chatting — it resumes exactly where the source left off.
🗂 What can I import?
| Source | Storage location | Import tool |
|---|---|---|
| Claude Code | ~/.claude/projects/<slug>/<sessionId>.jsonl |
import_claude |
| Codex / ChatGPT CLI | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl |
import_codex |
| ChatGPT (web export) | exported ZIP → conversations.json |
import_chatgpt |
| Cursor | ~/.cursor/projects/<slug>/agent-transcripts/<id>/<id>.jsonl |
import_cursor |
| Gemini CLI | ~/.gemini/history/<slot>/chats/session-*.json |
import_gemini |
| Reasonix | ~/.reasonix/sessions/desktop-*.jsonl |
import_reasonix |
| opencode | ~/.local/share/opencode/opencode.db (SQLite) |
import_opencode |
Each import preserves what the source actually records — session id, cwd, title, model, creation time, tool calls & results, reasoning — and formats with less data (Cursor transcripts, ChatGPT exports) import what exists and clearly report what they don’t.
🛠 Usage
Note: imports persist to disk immediately, but the DSH session list does not auto-refresh — refresh the page (or the session list) after importing to see the new sessions.
import_claude({ path: "C:\Users\<you>\.claude\projects\<slug>\<sessionId>.jsonl" })
import_codex({ path: "C:\Users\<you>\.codex\sessions\2026\05\18\rollout-2026-05-18T21-14-16-xxxx.jsonl" })
import_chatgpt({ path: "C:\Users\<you>\Downloads\chatgpt-export\conversations.json" })
import_cursor({ path: "C:\Users\<you>\.cursor\projects\<slug>\agent-transcripts\<composer-id>\<composer-id>.jsonl" })
import_gemini({ path: "C:\Users\<you>\.gemini\history\<slot>\chats\session-2026-04-17T18-09-b26d7f99.json" })
import_reasonix({ path: "C:\Users\<you>\.reasonix\sessions\desktop-202606020721-1.jsonl" })
import_opencode({ path: "C:\Users\<you>\.local\share\opencode\opencode.db" })
import_claude / import_codex / import_cursor / import_gemini / import_reasonix behave alike:
pathcan be a single file or a directory (directories are scanned recursively; each file becomes its own session).- Optional
sessionIdoverrides the target DSH session id (defaultimport-<source sessionId>; Cursor uses the file-name composer id, Reasonix the file-name stem). - Returns
{ mode: 'single', sessionId, turns, messages, toolCalls, skipped, alreadyImported }.
import_chatgpt differs: one conversations.json holds all conversations, so even a single file returns the batch shape { mode: 'batch', total, imported, alreadyImported, skipped, failed, results: [...] } (each results entry is one conversation). ChatGPT exports carry no cwd, so imported sessions are not grouped into workspaces.
import_opencode also always returns the batch shape — one opencode.db holds all sessions. path may be the .db file or its data directory; optional sessionIds restricts the import to the listed sessions; optional fullHistory: true imports the full message history instead of respecting opencode’s conversation compaction (default false — compacted sessions import as their last summary plus the retained tail). Imported sessions keep their directory as cwd and are grouped into workspaces.
🧩 Data model
The importer cuts each transcript into turns on "direct human prompts" (a user record with string content), and synthesizes one closed DSH round per turn:
turn/start → step/start → user/message → assistant/message → (tool/call + tool/result) → step/end → turn/end
Messages carry stable ids and surfaceOp: 'append'; tool/result events link back to their tool/call via sourceEventSeqs. Assistant source is { kind: 'model', provider: 'claude-code', model: <source model> }; tool/result source is { kind: 'tool', callId }. The SessionHeader keeps version: 0, id: import-<source sessionId>, source createdAt and cwd.
Claude Code JSONL
Main transcript at ~/.claude/projects/<slug>/<sessionId>.jsonl; auxiliary subagent / workflow fragments under <sessionId>/subagents/** reuse the parent sessionId and are skipped (they can never shadow or merge into the main conversation).
| Claude Code JSONL | DSH SessionEvent |
|---|---|
{ type: "user", message.content: string } (direct prompt) |
turn/start + step/start + user/message |
{ type: "assistant", content: [{ type: "text", text }] } |
assistant/message |
{ type: "assistant", content: [{ type: "thinking", … }] } |
reasoning content block |
{ type: "assistant", content: [{ type: "tool_use", … }] } |
tool/call + tool-call content block |
{ type: "user", content: [{ type: "tool_result", … }] } |
tool/result (sourceEventSeqs links its tool/call) |
| turn ends | step/end + turn/end |
Codex / ChatGPT CLI rollout
Line envelope: { timestamp, type, payload }. event_msg user/agent messages duplicate response_item records and are ignored; user blocks starting with < (<environment_context>, <user_instructions>, …) are harness injections and never enter the prompt. Codex reasoning content is encrypted and skipped.
| Codex rollout | DSH SessionEvent |
|---|---|
session_meta / turn_context |
SessionHeader (id / cwd / createdAt / model) |
response_item message role=user (input_text) |
turn/start + step/start + user/message |
response_item message role=assistant (output_text) |
assistant/message |
response_item function_call / custom_tool_call |
tool/call |
response_item function_call_output / custom_tool_call_output |
tool/result (paired by call_id, sourceEventSeqs linkage) |
response_item reasoning |
skipped (encrypted, unreadable) |
| turn ends | step/end + turn/end |
ChatGPT web export (conversations.json)
Top level is a JSON array (one file, all conversations); each conversation has a mapping DAG. The active branch (last children entry) is rebuilt as the main thread; placeholder nodes with message: null and author.role === 'system' are skipped; timestamps are Unix seconds (×1000 → ms). No cwd exists, so sessions are not grouped.
| conversations.json | DSH SessionEvent |
|---|---|
conversation object (id / title / create_time) |
SessionHeader + session/title |
mapping node with author.role: "user" |
turn/start + step/start + user/message |
node with author.role: "assistant" |
assistant/message |
node with author.role: "tool" |
tool/result (attached to the latest step) |
author.role: "system" / message: null |
skipped |
| turn ends | step/end + turn/end |
Cursor agent transcript
Line structure: { role: "user" | "assistant", message: { content: [...] } }. First user message is wrapped in <user_query> (stripped); [REDACTED] sentinels are filtered. Transcripts contain no tool_result (results live only in the UI bubble store) and no timestamps / model — the session id comes from the file name, and there is no cwd.
| Cursor transcript | DSH SessionEvent |
|---|---|
role: "user" (text in <user_query>) |
turn/start + step/start + user/message |
role: "assistant" text blocks |
assistant/message |
role: "assistant" tool_use blocks |
tool/call (no result in transcript) |
[REDACTED] sentinels |
filtered |
| turn ends | step/end + turn/end |
Gemini CLI session JSON
One JSON object per file at ~/.gemini/history/<slot>/chats/session-*.json. Message types: user (parts array) opens a turn; gemini (string content, optional thoughts and toolCalls) is one assistant step; info (CLI notices) is skipped. Tool results are inline on the same object as the call.
| Gemini session JSON | DSH SessionEvent |
|---|---|
top level (sessionId / startTime / directories[0]) |
SessionHeader (id / createdAt / cwd) |
type: "user" (parts array) |
turn/start + step/start + user/message |
type: "gemini" string content |
assistant/message |
thoughts entries |
reasoning content blocks |
toolCalls[].args + inline result |
tool/call + tool/result (status: "error" → isError) |
type: "info" |
skipped |
| turn ends | step/end + turn/end |
Reasonix session JSONL
OpenAI-style messages without envelope at ~/.reasonix/sessions/<stem>.jsonl; both v1 (nested { id, type: "function", function: { name, arguments } }) and v2 (flat { id, name, arguments }) tool_calls are accepted. Tool results (role: "tool" with tool_call_id) pair by tool_calls[].id. A sibling <stem>.meta.json provides workspace → cwd and summary → pinned title; when neither the transcript nor the meta carries a timestamp, the creation time falls back to the one embedded in the file name. V2 WAL sidecars (.events.jsonl / .conflicts.jsonl / .guardian.jsonl) are excluded from directory scans.
| Reasonix JSONL | DSH SessionEvent |
|---|---|
role: "user" (string content) |
turn/start + step/start + user/message |
role: "assistant" string content |
assistant/message |
reasoning_content |
reasoning content block |
tool_calls[].function (v1 nested / v2 flat) |
tool/call |
role: "tool" with tool_call_id |
tool/result (paired by tool_call_id) |
<stem>.meta.json (workspace / summary) |
cwd / session/title |
| turn ends | step/end + turn/end |
opencode session database (SQLite)
Reads the session / message / part tables of ~/.local/share/opencode/opencode.db (the event table is only a partial mirror and session_message / session_input are empty — ignored). Tool results are inline in the tool part’s state, so tool/call + tool/result are emitted together; a tool part without output still emits an empty result so calls and results stay paired. opencode compaction is respected by default: only the last compaction summary (a leading reasoning block) plus the messages from tail_start_id onward are imported; fullHistory: true imports everything.
| opencode DB | DSH SessionEvent |
|---|---|
session row (id / title / directory / time_created / model) |
SessionHeader + session/title |
message with role: "user" (text parts) |
turn/start + step/start + user/message |
message with role: "assistant" |
assistant/message |
part type: "text" |
text content block |
part type: "reasoning" |
reasoning content block |
part type: "tool" |
tool/call + tool/result (state.status === "error" → isError) |
part type: "file" |
text block [image: <filename>] |
part type: "patch" |
text block [patch: <N> files] |
part type: "subtask" |
text block [subtask: <command> — <description>] |
part type: "compaction" (tail_start_id) |
drop pre-tail_start_id history; summary becomes leading reasoning |
| turn ends | step/end + turn/end |
⚙️ Compatibility
- Consumes only public host plugin APIs (
sessionPersistence/fs/tools/workspaceRegistry) and@deepseek-ai/dsh-tools(declaredpeerDependencies, tested against0.1.0-rc.6).
| Source format | Import tool | Verified |
|---|---|---|
| Claude Code | import_claude |
✅ 44 tool/call + 44 tool/result persisted, load OK |
| Codex / ChatGPT CLI | import_codex |
✅ unit + mock integration (npm test) |
| ChatGPT web export | import_chatgpt |
✅ unit + mock integration (npm test) |
| Cursor | import_cursor |
✅ unit + mock integration (npm test) |
| Gemini CLI | import_gemini |
✅ unit + mock integration (npm test) |
| Reasonix | import_reasonix |
✅ unit + mock integration (npm test); dry-run on 55 real sessions |
| opencode | import_opencode |
✅ unit + mock integration (npm test) |
- Verified: 2026-08 on
dsh 0.1.0-rc.6(web profile) — full "import → resume → workspace attach" run;npm test(79 cases) covers the pure conversion logic and mock integration paths for all seven source formats.
🔒 Safety & boundaries
- Source transcripts are read-only, never rewritten; DSH history events are append-only (deep-frozen) — new events are added, existing ones are never modified.
- The plugin never modifies the DSH engine, apiproxy, or official UI packages; it publishes no services, so no isolate realm is needed.
- Reading transcripts outside the workspace requires the session sandbox to allow access to that path.
- Known boundaries:
permission/summaryauxiliary records are not imported;tool_resultwithis_errorkeeps the error flag but drops fields beyondmessage.content; Claude subagent / workflow fragment transcripts are skipped (only the main<sessionId>.jsonlbecomes a session); Codexreasoningis encrypted and skipped; ChatGPT exports rebuild only the main thread (branch = last child) and tool messages attach to the nearest step as text; Cursor transcripts have notool_resultand[REDACTED]text is filtered; Gemini follows the format observed 2026-04 (no stable official schema); Reasonix reads the JSONL checkpoint (the V2 WAL is excluded); opencodepatchparts carry no diff (placeholder[patch: <N> files]only) and tool output may keep ANSI escapes verbatim.
🧪 Tests
npm test
test/convert.test.mjs covers the pure conversion logic for all seven source formats (turn balance, tool linkage, titles, malformed lines, injection filtering, dedup, mapping branches, REDACTED filtering, inline tool results, v1/v2 tool-call shapes, opencode part mapping and model fallback); test/index.test.mjs runs the full apply → execute path with mock fs / sessionPersistence / tools / workspaceRegistry (and a real SQLite temp DB for import_opencode) and validates the return value against the output schema.
📦 Install & uninstall
dsh plugin --profile web add dsh-chat-import # npm package
dsh plugin --profile web add -w link:/path/to/dsh-chat-import # local checkout (symlink, recommended for development)
dsh plugin is a pnpm forwarder: after add it reads the dsh.bundle declaration, folds the cordis.patch.yml insert lines into the profile’s bundles, and the plugin is active after restarting dsh.
To uninstall, remove the import-claude insert line from the profile’s bundles and restart dsh. Already-imported sessions stay in the DSH data directory and are unaffected.
📄 License
MIT — see LICENSE.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi