dsh-chat-import

agent
Guvenlik Denetimi
Uyari
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.

SUMMARY

从Claude Code、Codex、Reasonix等Agent工具导入历史消息,并在DSH中继续对话

README.md

English | 简体中文

dsh-chat-import

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.

npm version
license: MIT
GitHub stars
Awesome DSH Plugin
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/result pairs (error flags and sourceEventSeqs linkage included), thinking blocks become reasoning, multi-step assistant messages are preserved.
  • ▶️ Seamlessly resumable — every import synthesizes a balanced, loadable session (turn/startstep/startuser/messageassistant/messagetool/call/tool/resultstep/endturn/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
  1. In any DSH session, import a single file or a whole directory:
import_claude({ path: "~/.claude/projects" })
  1. 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:

  • path can be a single file or a directory (directories are scanned recursively; each file becomes its own session).
  • Optional sessionId overrides the target DSH session id (default import-<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 workspacecwd 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 (declared peerDependencies, tested against 0.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 / summary auxiliary records are not imported; tool_result with is_error keeps the error flag but drops fields beyond message.content; Claude subagent / workflow fragment transcripts are skipped (only the main <sessionId>.jsonl becomes a session); Codex reasoning is 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 no tool_result and [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); opencode patch parts 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)

Sonuc bulunamadi