Types

Every request and response is typed from the OpenAPI spec. Field names are the API's wire names, so a response object matches the JSON exactly.

Request and response types are generated from the same OpenAPI spec the API reference is built from, and re-exported from the package root:

import type { Session, SessionRecord } from '@ellipsis-dev/sdk';

function summarize(session: Session): string {
  return `${session.id} ${session.status}`;
}

Field names are the API's wire names, so a response object matches the JSON on the wire exactly: session_state, not sessionState. Only the SDK's own surface uses camelCase, which is why handle.send() takes idempotencyKey while sessions.sendMessage() takes idempotency_key.

Responses are envelopes

A response type wraps the resource rather than being it. sessions.get() returns a SessionResponse whose session is the Session:

const response = await client.sessions.get('session_7Hq2mX4p');
const session = response.session;

Destructuring at the call site is usually shorter:

const { session } = await client.sessions.get('session_7Hq2mX4p');

The session handle unwraps this for you: handle.session is already a Session. Each operation's exact response type is named on its API reference page.

Optional means possibly undefined or null

A field the API may omit is typed optional, and a nullable one includes null. With strictNullChecks on, the compiler makes you handle it:

if (session.surface) {
  console.log(session.surface.status);
}

Open vocabularies are strings

Fields whose value set grows without a contract change are typed string, even where the known values are documented: a record's source, an error code, a delta's kind. New values ship on a routine release, so a union type would turn an additive server change into a broken build. Handle the values you know and fall through on the rest.

Closed vocabularies keep their union. A new value in one of those is a real contract change and should redden your build, which is the point.

Frames and store types

Stream frame types live in the same public surface, so a frame handler can be typed without importing from a subpath:

import type { StreamFrame, DeltaFrame } from '@ellipsis-dev/sdk';

StreamFrame is the open union: the seven known frames plus an unknown-shape escape hatch. Narrow it on frame.type.