Skip to content

feat(cli): add a client-only migrate API to bring Claude Code and Codex sessions into Kilo - #13294

Merged
kirillk merged 7 commits into
mainfrom
feat/claude-code-session-import-endpoint
Aug 31, 2026
Merged

feat(cli): add a client-only migrate API to bring Claude Code and Codex sessions into Kilo#13294
kirillk merged 7 commits into
mainfrom
feat/claude-code-session-import-endpoint

Conversation

@kirillk

@kirillk kirillk commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

What

Adds a CLI server HTTP API for migrating sessions into Kilo from another coding agent, starting with Claude Code and OpenAI Codex JSONL transcripts.

This is API-only, for clients — not a user-facing feature by itself. It adds no CLI flag, no slash command, and no TUI screen. The routes exist so a client (VS Code extension, JetBrains plugin, or anything else driving kilo serve through @kilocode/sdk) can build its own "bring your Claude Code session into Kilo" UI. The only end-user-visible surfaces remain the pre-existing /resume-claude and /resume-codex TUI slash commands, which this PR did not add — it just moved their write path into a module these routes also call.

Route Purpose
POST /kilocode/migrate/sessions/discover Preview the transcripts available for a directory, marking ones already migrated. Read-only.
POST /kilocode/migrate/sessions Discover and migrate transcripts into new Kilo sessions, one per transcript. Skips sources already migrated.

SDK surface is client.migrate.discover(...) and client.migrate.sessions(...).

Why

Transcript import already existed, but only as the /resume-claude and /resume-codex TUI slash commands, with the mapping and write logic inside the slash-command handler. That made it unreachable for any other client: VS Code and JetBrains had no way to offer "bring your Claude Code session into Kilo" without reimplementing the parser and the message/part write path.

The map + write step now lives in a shared module that both the slash commands and these endpoints call, so every client gets identical behavior from one implementation.

Client flow

// Optional: show a picker. Entries already migrated carry a sessionID.
const { sessions } = await client.migrate.discover({ cwd })

// Migrate. Omit ids to take everything, or pass specific source ids.
const result = await client.migrate.sessions({ cwd, ids: [picked.id] })
//    -> { sessions: [{ id, format, sessionID, messageID, messages, skipped, error, dropped }], migrated, skipped, dropped }

Calling migrate.sessions again is a no-op, so it is safe behind a "check again" button or on startup.

Design notes

The server does the discovery, not the client. An earlier revision of this PR had migrate.sessions accept raw transcript content, which was wrong: discover hands back a path on the CLI host's filesystem, and nothing bridged the two. GET /file/content refuses paths outside the project directory, and transcripts live under $HOME, so a client had to read the file itself — fine for the VS Code extension, broken for any remote, containerized, or split-mode backend. Re-discovering server-side also avoids round-tripping multi-MB JSONL to a server that just read the file to build the preview.

Idempotency is stored, not inferred. write mints fresh ascending IDs, so nothing about a migrated session says where its content came from and a second run would silently duplicate it. Each created session now records metadata.migrate = { format, id, path, time }, and migration reads that back to skip sources that already landed. This follows the v5 session import's contract (skipped: true per source, force to override) but keys off an explicit marker rather than v5's hashed row ID, since Kilo session IDs must stay time-sortable. The marker lookup queries the session table directly rather than through Session.list(), which pages to the 100 most recently updated sessions — that page limit would otherwise let old markers fall out of view and cause re-migration.

Failures are scoped by blast radius. Unknown agent, unloadable model, and unknown requested IDs fail the whole request (422) because they apply to every source. A single unreadable transcript is reported on its own entry so it cannot block the rest of the batch. If a write fails after the session was created, that session is removed — otherwise the next run would see a marked-but-empty session and skip the source forever.

discover keeps a flat identifier (kilocode.migrate.discover). Nesting it under sessions made the SDK generator emit both a sessions method and a sessions2 namespace getter for the same name.

Naming

The group is migrate, not session-resume, which collided conceptually with two existing things: the legacy session-import group (raw DB row import) and session resume meaning resuming an interrupted turn. Internal module names (SessionResume, SessionResumeImport) are unchanged since they're shared with the slash commands.

Testing

test/kilocode/session-resume-integration.test.ts covers the migrate path against real fixtures through the ResumeRoots seam: one session created per transcript with the marker persisted, second call skipping, force re-migrating, discover reporting already-migrated sources, id filtering, unknown-id rejection, per-entry failure leaving no stray session, the empty no-op, and the marker surviving past the 100-session page limit. The HttpApi exerciser covers route/auth/shape — it asserts the no-op contract, since it can't plant transcripts in the host's home directory.

…ume endpoint

Expose the existing SessionResume transcript importer (Claude Code / OpenAI
Codex JSONL, Anthropic message format) through a new CLI HTTP endpoint,
POST /kilocode/session-resume, so every client can import an external
transcript into an empty Kilo session without reimplementing the write path.

The map + write logic is extracted into a shared SessionResumeImport module,
which both the /resume-claude and /resume-codex slash commands and the new
endpoint call into. Regenerate the SDK and add integration tests covering the
raw-content import path and its error cases.
Comment thread packages/opencode/src/kilocode/session-resume/import.ts
Comment thread packages/opencode/src/kilocode/session-resume/import.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/opencode/src/kilocode/session-resume/import.ts
  • packages/opencode/test/kilocode/session-resume-integration.test.ts
Previous Review Summaries (5 snapshots, latest commit 6cc21af)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 6cc21af)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/session-resume/import.ts 279 sessions.list() only returns the 100 most recently updated sessions, so already-migrated sources can be remigrated
Files Reviewed (9 files)
  • .changeset/claude-code-session-import-endpoint.md
  • packages/opencode/src/kilocode/server/httpapi/groups/migrate.ts
  • packages/opencode/src/kilocode/server/httpapi/handlers/migrate.ts
  • packages/opencode/src/kilocode/session-resume/import.ts - 1 issue
  • packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts
  • packages/opencode/test/kilocode/session-resume-integration.test.ts
  • packages/sdk/js/src/v2/gen/sdk.gen.ts
  • packages/sdk/js/src/v2/gen/types.gen.ts
  • packages/sdk/openapi.json

Fix these issues in Kilo Cloud

Previous review (commit 8d05e8a)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (11 files)
  • .changeset/claude-code-session-import-endpoint.md
  • packages/opencode/src/kilocode/server/httpapi/groups/migrate.ts
  • packages/opencode/src/kilocode/server/httpapi/handlers/migrate.ts
  • packages/opencode/src/kilocode/server/httpapi/server.ts
  • packages/opencode/src/kilocode/session-resume/import.ts
  • packages/opencode/src/server/routes/instance/httpapi/api.ts
  • packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts
  • packages/opencode/test/kilocode/session-resume-integration.test.ts
  • packages/sdk/js/src/v2/gen/sdk.gen.ts
  • packages/sdk/js/src/v2/gen/types.gen.ts
  • packages/sdk/openapi.json

Previous review (commit 0bfc479)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/opencode/src/kilocode/server/httpapi/handlers/session-resume.ts
  • packages/opencode/src/kilocode/session-resume/import.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts
  • packages/opencode/test/kilocode/session-resume-integration.test.ts

Previous review (commit 19ceae0)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/session-resume/import.ts 164 Unknown agent is not rejected and will crash the import
packages/opencode/src/kilocode/session-resume/import.ts 158 A missing target session is treated as empty and still imported
Files Reviewed (8 files)
  • .changeset/claude-code-session-import-endpoint.md
  • packages/opencode/src/kilocode/server/httpapi/groups/session-resume.ts
  • packages/opencode/src/kilocode/server/httpapi/handlers/session-resume.ts
  • packages/opencode/src/kilocode/session-resume/import.ts - 2 issues
  • packages/opencode/src/kilocode/session-resume/index.ts
  • packages/opencode/test/kilocode/session-resume-integration.test.ts
  • packages/sdk/js/src/v2/gen/sdk.gen.ts
  • packages/sdk/js/src/v2/gen/types.gen.ts

Fix these issues in Kilo Cloud

Previous review (commit b59ebd7)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/session-resume/import.ts 162 Unknown agent is not rejected and will crash the import
packages/opencode/src/kilocode/session-resume/import.ts 156 A missing target session is treated as empty and still imported
Files Reviewed (10 files)
  • .changeset/claude-code-session-import-endpoint.md
  • packages/opencode/src/kilocode/server/httpapi/groups/session-resume.ts
  • packages/opencode/src/kilocode/server/httpapi/handlers/session-resume.ts
  • packages/opencode/src/kilocode/server/httpapi/server.ts
  • packages/opencode/src/kilocode/session-resume/import.ts - 2 issues
  • packages/opencode/src/server/routes/instance/httpapi/api.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/kilocode/session-resume-integration.test.ts
  • packages/sdk/js/src/v2/gen/sdk.gen.ts
  • packages/sdk/js/src/v2/gen/types.gen.ts

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 94.5K · Output: 9.4K · Cached: 449.9K

Review guidance: REVIEW.md from base branch main

kirillk and others added 2 commits August 20, 2026 23:12
…on-resume endpoint

Add POST /kilocode/session-resume/discover, a read-only companion to the
session-resume import endpoint. It scans the Claude Code / Codex JSONL
transcript locations for a directory (reusing SessionResume.discover*) and
previews each transcript (title, format, version, message count, model) so
clients can list importable sessions before importing.

Discovery is its own SessionResumeImport.discover handler; it never writes
and does not fold into the import endpoint. Adds a pure SessionResume.preview
helper, wires the new endpoint into the session-resume HttpApi group, and
regenerates the SDK. No client code changes.
Address review feedback on the session-resume import endpoint.

`fromContent` resolved the agent without checking that `agents.get`
actually found one, so an unknown `agent` crashed on `agent.name` as a
defect the HTTP handler could not map to 422. It also skipped a session
existence check, so a bad `sessionID` died inside `messages` instead of
reporting a user-actionable failure. Both now fail with
`NamedError.Unknown` before anything is written.

Also fix the CI failures on this branch: the discover handler mapped an
error channel that is `never` (discovery reports problems through
`dropped`), the discover result union needed an explicit annotation, the
slash-command path leaked a `Session.Service` requirement into
`SessionPrompt.command`, and both new routes had no HttpApi exerciser
scenarios.
@kirillk kirillk closed this Aug 30, 2026
@kirillk kirillk reopened this Aug 30, 2026
@kirillk

kirillk commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 0bfc479 addressing both review comments, plus the CI failures that were red on this branch:

  • typecheck / typecheck-js: the discover handler mapped an error channel that is never (discovery reports problems via dropped), the discover result union needed an explicit annotation, and the slash-command path leaked a Session.Service requirement into SessionPrompt.command.
  • HttpApi exerciser / test (linux): both new routes had no scenarios (missing=2). Added import happy path, import-into-unknown-session (422), and discover.

GitHub did not create pull_request workflow runs for this SHA (close/reopen did not help either), so I verified via workflow_dispatch on the branch at 0bfc479:

  • typecheck: success (typecheck-js, typecheck-jetbrains)
  • test: HttpApi exerciser success, unit linux + all 6 windows shards success, jetbrains success
  • unit (macos) failed twice with two different 10s/30s timeouts in files this PR does not touch (packages/client test/contract-identity.test.ts, then test/server/httpapi-v2-pty.test.ts). Both pass locally (1.1s and 12.5s respectively), so they look like macOS runner flakes rather than regressions.

…ion-import-endpoint

# Conflicts:
#	packages/opencode/src/session/prompt.ts
#	packages/sdk/js/src/v2/gen/sdk.gen.ts
Rename the HTTP surface from `session-resume` to `migrate` so the route says
what it is for: bringing existing work into Kilo from another coding agent.

- `POST /kilocode/session-resume`          -> `POST /kilocode/migrate/sessions`
- `POST /kilocode/session-resume/discover` -> `POST /kilocode/migrate/sessions/discover`

The old name collided conceptually with two unrelated things: the legacy
`session-import` group (raw DB row import) and session *resume* as in
resuming an interrupted turn. `migrate` leaves room for future
"bring your existing X into Kilo" routes.

Generated SDK is now `client.migrate.sessions(...)` and
`client.migrate.discover(...)`. Discover deliberately keeps a flat
identifier so the generator does not emit a `sessions` method plus a
`sessions2` namespace getter for the same name.

Internal module names (`SessionResume`, `SessionResumeImport`) are unchanged;
they are shared with the `/resume-claude` and `/resume-codex` slash commands.
@kirillk kirillk changed the title feat(cli): import Claude Code and Codex transcripts via a session-resume endpoint feat(cli): migrate Claude Code and Codex sessions into Kilo via a migrate API group Aug 30, 2026
`POST /kilocode/migrate/sessions` no longer takes transcript bytes and a
target session. It re-discovers Claude Code / Codex transcripts server-side
and migrates each one into a session it creates itself, so clients only pass
filters. This removes the asymmetry where `discover` returned a path on the
CLI host that the import endpoint could not accept, which made the pair
unusable for any client that does not share the backend's filesystem.

Migration is now idempotent. Each created session records its source in
`session.metadata.migrate` (format + source UUID + path + time), and a
migration reads that back and skips sources that already landed, so calling
the endpoint repeatedly is a no-op once everything has been migrated. This
mirrors how the v5 session import decides to skip -- `skipped: true` per
source with a `force` escape hatch -- but keyed off an explicit stored marker
instead of a hashed row ID, because Kilo session IDs must stay time-sortable.

`discover` now reports `sessionID` for sources it has already migrated so
clients can mark them done in a picker.

Failure handling is split by blast radius: unknown agent, unloadable model
and unknown requested IDs fail the request, while one unreadable transcript
is reported against its own entry so it cannot block the rest. A write that
fails after the session was created removes that session, otherwise the next
run would see a marked-but-empty session and skip the source forever.
Comment thread packages/opencode/src/kilocode/session-resume/import.ts Outdated
…imit

`migrated()` built the skip map from `Session.list()`, which pages to the 100
most recently updated sessions in the project. Once a project accumulated 100
sessions newer than a migrated one, its `metadata.migrate` marker fell out of
that window, so `discover` stopped reporting the source as migrated and
`migrate` imported it again as a duplicate. That defeated the whole point of
the marker.

Query the session table directly instead, scoped to the current project and
narrowed to rows that carry metadata at all. Scoping matches what
`Session.list()` applied for this call (`KiloSession.filters` with no
directory reduces to `project_id`), so only the row cap is gone.

Adds a regression test that pushes the migrated session past the 100-row page
and asserts the source is still skipped; it fails against the previous
implementation.
@kirillk kirillk changed the title feat(cli): migrate Claude Code and Codex sessions into Kilo via a migrate API group feat(cli): add a client-only migrate API to bring Claude Code and Codex sessions into Kilo Aug 31, 2026
@kirillk
kirillk merged commit 67a8264 into main Aug 31, 2026
33 checks passed
@kirillk
kirillk deleted the feat/claude-code-session-import-endpoint branch August 31, 2026 16:51
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.

2 participants