Skip to content

Import OpenCode sessions; make the summary model configurable - #20

Open
artcashin wants to merge 5 commits into
prime-radiant-inc:mainfrom
artcashin:feat/opencode-import-standalone
Open

artcashin wants to merge 5 commits into
prime-radiant-inc:mainfrom
artcashin:feat/opencode-import-standalone

Conversation

@artcashin

Copy link
Copy Markdown

Adds OpenCode as a session source, and makes the model that writes journal summaries configurable. They're together because the first makes the second worth having — a local model is a reasonable choice once the corpus includes local-agent sessions.

Branches from main and depends on nothing else.

OpenCode source adapter (opt-in)

OpenCode keeps sessions in a single SQLite database rather than one file per session, so it can't be scanned like the Claude Code and Codex sources. The adapter exports each session into a staging directory of JSONL files during ingest, which the normal scanner picks up — dedup, --force, grouping, summarization and the web UI are untouched. Absent config, nothing changes.

Three constraints worth a reviewer's attention, each found empirically against OpenCode 1.18.0 rather than assumed:

  • opencode session list is scoped to the current directory's project. 5356 bytes of output from /tmp, 0 bytes from inside another repo. No single cwd can enumerate every project, so sessions are enumerated from OpenCode's database. Transcripts still come from opencode export, which works from anywhere — the fragile part (transcript shape) stays on the supported CLI, and only a five-column query touches the schema.
  • opencode export returns empty stdout over a pipe but is complete when redirected to a file, and one export can run to megabytes. Both call sites go through a single runToFile helper, covered by a 2MB regression test.
  • is_subagent was derived purely from Claude Code's /subagents/ path convention, so flat-staged files always read as 0. Formats that state it outright now win. Claude Code deliberately keeps path detection: its parentSessionId also covers continuations, so switching it to "has a parent" would misflag them.

Projects are keyed off each session's working directory, so OpenCode and Claude Code work in the same repo on the same day lands in one journal entry rather than two disconnected ones.

Configurable summary provider

Summaries were hardcoded to Claude Haiku through the Agent SDK. A summary_provider block now selects any OpenAI-compatible endpoint. Absent config keeps existing behaviour exactly.

  • The Claude auth preflight is skipped for non-Anthropic providers, which authenticate with their own key and would otherwise be blocked on every run.
  • Entries record the model that actually produced them, rather than a constant.
  • API keys are referenced by environment variable name, never stored in config.
  • Reasoning models spend completion tokens before emitting content. An exhausted budget throws with a clear message instead of writing an empty summary.

Testing

131 tests pass, typecheck clean. Every change was written test-first; the OpenAI-compatible provider is tested against a real local server rather than a mocked fetch.

Exercised end-to-end against a live corpus: 96 OpenCode sessions across 16 projects, 61 subagents correctly flagged, summarized by a local Gemma served via llama.cpp. The token-budget error path fired on a real transcript and was resolved by raising the budget — that failure mode is not theoretical.

🤖 Generated with Claude Code

krsnaa and others added 2 commits May 8, 2026 12:53
Replaces the three-line summary with a richer report:

- Per-source file counts during the scan phase
- Live progress bar (TTY-only, 100ms throttle, sub-char eighth-block fill)
- Splits skipped into already_ingested / empty / duplicate_id so the
  number is actionable instead of opaque
- Total messages ingested + elapsed time

Before:
  Scanning 2 source(s)...
  Found 4650 session file(s)
  Ingested: 4313, Skipped: 337, Errors: 0

After:
  Scanning 2 source(s)...
    4,294 in /Users/me/.claude/projects
      356 in /Users/me/.codex/sessions
  Found 4,650 session file(s)
    [████████████████████] 100% · 4,650/4,650
  Ingested 4,313 session(s) (812,540 messages) in 47.2s
  Skipped 337: 337 empty
Ingest Cursor session transcripts. Cursor records are {role, message}
with no `type` field, and content blocks share Claude Code's
{type:"text", text} shape, so the existing text extractors are reused.

Metadata absent from Cursor transcripts is derived after parsing:
- session id from the filename UUID
- start/end timestamps from file birthtime/mtime (Cursor stores none)
- project name from Cursor's encoded directory string (used verbatim)

`~/.cursor/projects` is not added to default sources; it stays opt-in
and is documented in the README along with the format's caveats.

Adds parser tests, an ingest regression test, fixtures, and docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@artcashin

Copy link
Copy Markdown
Author

Overlap with #18 (Cursor session support)

Both PRs add a session format to src/parser.ts, so they conflict — but only textually. I tested the merge rather than inferring it; resolution is keep-both in two places.

File Result
src/parser.ts Conflict — one 3-line hunk: let openCodeFormat = false; vs let cursorFormat = false;, both inserted beside codexFormat
src/parser.test.ts Conflict — both append fixture constants and a describe block at the same anchor
src/ingest.test.ts Auto-merges
README.md Auto-merges

Applying keep-both to those two hunks: 139 tests pass, 0 fail, typecheck clean — 15 OpenCode tests and 8 Cursor tests green side by side.

No semantic collision, which was the real risk given both add a format to the same parser. The detection predicates are mutually exclusive:

  • Cursor detects !record.type && record.message. Staged OpenCode records always carry type: "user"|"assistant", so they can't be misread as Cursor.
  • The opencode_meta header has a type and no message, so it can't trip Cursor's check — and it continues before Cursor's branch is reached.
  • Add Cursor session support #18 doesn't touch is_subagent, so the format-aware change here is unaffected.

Worth knowing for whoever merges second: #18 changes projectName/userDisplayName from const to let so it can override them for Cursor, which carries neither a cwd nor timestamps. That is the same return-assembly region where this PR sets isSubagent, though the two don't overlap textually today.

Merge order doesn't matter.

@artcashin

Copy link
Copy Markdown
Author

Overlap with #16 (detailed ingest output)

A second overlap, alongside the #18 note above. #16 touches src/index.ts and src/ingest.ts; a test merge conflicts in index.ts only, and ingest.ts auto-merges.

This one is slightly more involved than #18. Both PRs rewrite the same region of the ingest command rather than inserting alongside each other:

They are still compatible in substance: the OpenCode block runs before scanning and only extends sources, so #16's per-source loop reports the staging directory as one more source and its progress bar covers the staged files with no change needed. Resolution is to keep #20's staging block above #16's rewritten scanning section.

Whoever merges second resolves it. No semantic conflict — #16 changes how ingest reports progress, #20 changes what there is to ingest.

artcashin and others added 3 commits August 2, 2026 16:11
OpenCode source adapter
-----------------------
OpenCode keeps sessions in a single SQLite database rather than one file per
session, so it cannot be scanned like the Claude Code and Codex sources. An
opt-in adapter exports each session into a staging directory of JSONL files
during `ingest`, which the normal scanner then picks up — leaving dedup,
--force, grouping, summarization and the web UI untouched.

Three things the implementation has to work around, each found empirically
against OpenCode 1.18.0:

- `opencode session list` only reports sessions belonging to the current
  working directory's project, so no single cwd can enumerate them all.
  Sessions are enumerated from OpenCode's database; transcripts still come
  from `opencode export`, which works from anywhere. That keeps the fragile
  part (transcript shape) on the supported CLI, with only a five-column query
  touching the schema.
- `opencode export` yields empty stdout over a pipe but is complete when
  redirected to a file, and a single export can run to megabytes. Both call
  sites go through one runToFile helper.
- is_subagent was derived purely from Claude Code's /subagents/ path
  convention, so flat-staged files always read as 0. Formats that state it
  outright now win; Claude Code keeps path detection, because its
  parentSessionId also covers continuations and would otherwise misflag them.

Projects are keyed off each session's working directory, so OpenCode and
Claude Code work in the same repo on the same day lands in one journal entry.

Configurable summary provider
-----------------------------
Summaries were hardcoded to Claude Haiku via the Agent SDK. A summary_provider
config block now selects any OpenAI-compatible endpoint, including a local
llama.cpp server. Absent config keeps the previous behaviour.

The Claude auth preflight is skipped for non-Anthropic providers, which
authenticate with their own key and would otherwise be blocked on every run.
Entries record the model that actually produced them. API keys are referenced
by environment variable name and never stored in config. Reasoning models
spend completion tokens before emitting content, so an exhausted budget fails
loudly rather than writing an empty summary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants