TypeScript SDKGithubEdit on GitHub

Sessions

The session handle — lifecycle, the session-owned runtime (health & previews), and the typed opencode runtime.

A session is one agent run, in its own disposable sandbox, on its own git branch. kortix.session(projectId, sessionId) binds both ids and is the single handle for everything a session does.

const s = kortix.session(projectId, sessionId);

Session-scoped, by design. A session owns its runtime, so you ask the session about health and previews — never a global "sandbox." The sandbox is plumbing the SDK resolves for you.

Lifecycle

await s.get();                 // session detail
await s.update({ /* … */ });   // rename, settings
await s.start();               // provision + boot the runtime
await s.restart();             // restart the runtime (may re-provision a new sandbox)
await s.stop();                // stop the runtime without deleting the session
await s.commit();              // commit the agent's work
await s.setSharing(intent);    // sharing / visibility
await s.delete();

restart() and delete() both forget this handle's cached runtime (a restart may land on a different sandbox); stop() does the same without deleting the session itself.

Previews & public shares

await s.previews();            // candidate preview ports the runtime exposes
await s.publicShares.list();
await s.publicShares.create(input);
await s.publicShares.revoke(shareId);

Audit & transcript

await s.audit(limit?);         // per-session audit trail of executor-gated agent actions
await s.transcript(options?);  // compact server-side transcript (text + tool calls, no tool inputs/outputs)

transcript() is callable with project-scoped session tokens, so it's the right read for a scoped/embedded host that only has a session token.

The runtime

The session owns its runtime, so these resolve the active sandbox for you — you pass a port or a URL, never a sandbox id.

methodreturnswhat
s.health(init?){ status, ok, health, body }runtime liveness + whether OpenCode is ready
s.previewUrl(port, path?)stringproxy/preview URL for a port the agent exposed
s.proxyUrl(url?)string | undefinedrewrite a localhost URL the agent printed into a reachable proxy URL
const { ok, health } = await s.health();
// health?.runtimeReady · health?.status · health?.version …

const url = s.previewUrl(3000, '/docs');       // → the live preview URL
const fixed = s.proxyUrl('http://localhost:8080');

s.health() never throws SessionNotReadyError — it's safe to poll before the session has ever resolved a runtime. s.previewUrl() and s.proxyUrl() do require a resolved runtime; call s.ensureReady() first (or s.send() / s.abort(), which call it internally).

Stateless URL helpers — detecting localhost URLs in agent output, parsing preview URLs — live at @kortix/sdk/session. The session handle wraps them with the sandbox context already resolved.

Talking to the agent

These are the opinionated wrappers over the runtime — the right entry points for a script, server, worker, or any non-React host. Each auto-provisions the runtime via ensureReady() internally, resolves the OpenCode session id for you, and always acts against this handle's own resolved runtime — never whatever sandbox happens to be globally "active" — so two handles on two different sessions never cross wires.

await s.ensureReady();                 // provision/resume the runtime; idempotent
s.setModel({ providerID, modelID });   // sticky model for subsequent send()s
s.setAgent('build');                   // sticky agent for subsequent send()s

await s.send('Refactor the auth module');           // prompt the agent
await s.send('One-off task', { model, agent });     // per-call override
await s.abort();                                     // abort the current run

Call s.ensureReady() (or send/abort, which call it internally) before .runtime, .previewUrl(), or .proxyUrl() — those throw SessionNotReadyError if this handle hasn't resolved a runtime yet. .health() is the exception: it never throws and is safe to call anytime.

Streaming events — s.stream()

For live message / part / event streaming from a non-React host, use s.stream() — a framework-free facade over the same primitive useSession uses internally. It handles connect/reconnect/backoff and a 15s heartbeat watchdog.

const handle = await s.stream({
  onEvent: (event) => console.log(event),
  onGapRehydrate: (gapMs) => console.log('reconnected, gap:', gapMs),
});
// later
handle.close();

In a React app, prefer useSession(projectId, sessionId) instead — the hook that owns the whole runtime (start, SSE, readiness) and returns the thread, send, and the boot phase.

The typed runtime — s.runtime

s.runtime is the typed OpenCode v2 client, scoped to this session and reached only through the SDK — the escape hatch for anything not covered by send/abort/stream. It requires a resolved runtime (call s.ensureReady() first, or use it after send/abort/stream have run).

await s.ensureReady();

// send a prompt (equivalent to s.send(), shown for the raw client)
await s.runtime.session.prompt({
  sessionID: (await s.ensureReady()).opencodeSessionId,
  parts: [{ type: 'text', text: 'Refactor the auth module' }],
});

The OpenCode sessionID the runtime expects is not the Kortix sessionId passed to kortix.session(projectId, sessionId) — it's resolved server-side at /start and cached on the handle. Prefer s.send() / s.abort(), which resolve it for you; only reach for raw s.runtime calls when you need something those wrappers don't cover.

Never import @opencode-ai/sdk directly. s.runtime is the same client, owned by the SDK, with the full opencode type surface re-exported from @kortix/sdk/opencode-client.

Files — s.files

s.files is the session-scoped equivalent of the top-level @kortix/sdk files export: the same 12-op workspace surface, but every call auto-provisions via ensureReady() and always targets this handle's own resolved runtime — never the module-global "active" sandbox. This fixes a cross-session bleed bug: a host juggling multiple open sessions that called the global files.list() could silently read/write the wrong sandbox.

await s.files.list(dirPath);
await s.files.read(filePath);
await s.files.readBlob(filePath);
await s.files.status();
await s.files.findFiles(query, { type: 'file', limit });
await s.files.findText(pattern);
await s.files.upload(file, targetPath?, filename?);
await s.files.create(filePath);
await s.files.copy(sourcePath, destPath);
await s.files.remove(filePath);
await s.files.mkdir(dirPath);
await s.files.rename(from, to);
Sessions – Kortix Docs