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 same runtime in place
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() preserves the session's established sandbox identity and refreshes the handle's cached readiness state. If the original provider object is unavailable, restart fails explicitly instead of attaching an empty replacement. delete() removes the runtime; stop() clears readiness 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