Skip to main content
TypeScript SDK for Devin. Cloud and local sessions both speak the Agent Client Protocol — cloud over a WebSocket, local by running the Devin CLI on your machine — with the generated v3 API client available as an escape hatch.

Install

The SDK is beta-only for now: every release is a 0.0.1-beta.N prerelease on the beta dist-tag, and no version is published to latest, so the @beta suffix is required — a plain npm i @cognition-ai/sdk fails on purpose until the public launch.

Usage

createDevin(options) connects to the cloud over a WebSocket (CloudDevin) unless you pass a cwd, which spawns the CLI on this machine (LocalDevin); TypeScript narrows the return type accordingly. Both need a cog_… API key, from the apiKey option or DEVIN_API_KEY (locally the SDK hands it to the CLI for you). Both implement the shared DevinClient interface, and their sessions share the same send() / stream() / cancel() surface, so the same code drives Devin on your machine or in a cloud VM:
Clients are async-disposable — await using closes the connection (and the CLI process for local clients) at scope exit; without it, call .close() when done.

The v3 API escape hatch

ACP covers live interaction; everything else — session snapshots, structured output, pull requests, and platform resources (playbooks, secrets, organizations) — lives on the v3 API. The cloud client exposes the full generated v3 client as api:

Local sessions (Devin CLI)

LocalDevin spawns the Devin CLI as a subprocess and drives it over the Agent Client Protocol on stdio. Turns run on your machine: tool calls edit real files and run real commands in cwd. The CLI is bundled with the SDK as per-platform npm packages listed as optionalDependencies, so the binary lives in node_modules and is versioned by your lockfile — there is no path override; the SDK only runs a bundled, officially published CLI build. Authentication is the apiKey option (a cog_… personal access token, defaulting to $DEVIN_API_KEY, the same key cloud mode uses); the SDK never falls back to the CLI’s own devin auth login credential store. The CLI’s config, session store, and cache live under ~/.devin-sdk (override with stateDir), isolated from any interactive Devin CLI install on the same machine.
Cognition-specific events (_cognition.ai/* notifications — agent stop causes, context compaction, connection retries, …) are surfaced as a typed union via session.onExtension:

Models and Fusion

model (on LocalDevin.start, createSession, or session.setModel) takes a model id the CLI accepts for your account, or a Fusion model that pairs a leader model with a sidekick model. session.models() lists them, with the one currently in effect:
A Fusion model is sent as its id, and the CLI only accepts pairings it advertises (the fusion-* entries in session.models(), which is the session’s model config option). An unsupported model or leader/sidekick combination rejects setModel with a ModelRejectedError carrying the CLI’s reason (error.reason) and leaves the current model in place; the SDK does not validate ids itself, so the list stays in step with the CLI. The available pairings depend on the CLI version: the pairings above and fusion-adaptive need a recent CLI (tested on 3000.10.27) and are not in older builds such as 3000.6.11.

Cloud sessions over ACP

CloudDevin drives sessions running in Devin’s cloud over the same ACP protocol as LocalDevin, connected over the ACP WebSocket and authenticated with a personal access token (cog_…). Turns, streaming, permission handling, and _cognition.ai/* extension notifications work identically to local sessions; the generated v3 client stays available as the api escape hatch.

Tool calls, the same on both agents

The local CLI and the cloud describe tools differently on the wire (typed rawInput and cognition.ai/inferenceToolName locally; cognition.ai/eventType plus _meta details such as cognition.ai/command and cognition.ai/fileUpdates on the cloud). ToolCall.detail normalizes both into one discriminated union — exec, read, write, edit, list_directory, file_search, mcp, permission_request, ask_user_question, report_blocker, or other — so consumers narrow on detail.tool and never see the difference. The raw rawInput, meta, toolName and inferenceToolName stay on the call for anything the union does not cover.

Watching sessions you did not start

Every session exposes two views of its updates. run(), stream() and streamUpdates() are scoped to a turn this client sent. events() and updates() are scoped to the session: every live update for it, whoever started the turn, until the connection closes. Both typed flavours use the same SessionEventReducer, so a SessionEvent means the same thing wherever it came from.
The cloud pushes status transitions live but replays only the latest one, so a client that was offline cannot reconstruct what it missed: seed from listSessions() (each entry’s _meta carries statusEnum, userActionRequired, pendingRequest, sessionPRs, …) and stay attached for transitions. session.meta holds the latest session snapshot stamped on any update. Observers should leave onPermission unset — an answer from an observer is a real decision, and the protocol has no “abstain”.

Tracing sessions (OpenTelemetry, Langfuse)

@cognition-ai/sdk/tracing turns ACP sessions into OpenTelemetry spans, for local and cloud sessions alike. It reads the session’s updates and _cognition.ai/agent_stopped, so it needs no CLI flag. It only uses @opentelemetry/api (an optional peer dependency); your app owns the tracer provider, span processor and exporter. Spans carry the attributes Langfuse’s OTLP ingestion reads, plus gen_ai.* semantic-convention attributes for other backends. Cloud sessions currently send no usage_update, so their traces have turn and tool spans but no generation spans.
Configure tracing once on the client with traceSessions:
Every session the client creates, loads, attaches or launches is traced, and prompts are recorded from every prompt path (send, run, stream, streamUpdates, postMessage), including images and resources. A session’s tracing option merges over the client’s (metadata merged, tags appended); tracing: false opts it out. On cloud, messages other clients send (the web app, Slack, another SDK process) are traced as turns too, and this client’s own prompts are not counted twice. History replayed by loadSession or attach is not exported again; a turn already running when you attach is traced from then on. Tracing ends when the session is released, the client closes or the connection drops. To trace a single session you already hold, use traceSession. It cannot see outgoing prompts, so record each one yourself:
Without tracer, spans go to the global tracer provider (for example one registered by @opentelemetry/sdk-node). Each turn is its own trace unless parent (or the active context) holds a span; Langfuse groups turns by sessionId, which defaults to the ACP session id, so a session reloaded in a new process lands in the same Langfuse session:
  • turn N (agent): prompt, final message, stop cause.
  • generation <model> (generation), one per model request from usage_update: uncached input, output, cache_read_input_tokens and cache_creation_input_tokens. The last one in a turn also gets the request id, time to first token, and cost_details when the agent reports acuCost / creditCost.
  • <tool name> (tool), one per tool call: raw input and output, ERROR when it fails, WARNING when rejected or canceled.
Error stop causes (error, quota_exhausted, auth_required, content_filter) mark the turn and its last generation ERROR; other non-complete causes are WARNING. If the session stream fails, the open turn ends as stream_error and the session reports the failure to your code as usual. Export batching, retries and failures are handled by your span processor, so a collector outage never throws into the session.

Regenerating the API client

src/generated/ is produced by Fern from the committed OpenAPI spec (spec/v3-openapi.json). After changing the spec, run from the repo root (requires Docker):

Classes

Interfaces

Functions

Type Aliases

Variables