docs
TypeScript SDKReference

Types

Shared TypeScript type definitions exported from @urun-sh/core

The public types exported from @urun-sh/core (and re-exported by @urun-sh/react).

TransportState

Low-level connection state. reconnecting means the SDK lost the transport and is actively re-dialing; renegotiating means the session is still admitted but its media path went dead (a recycled backend) and the SDK is re-establishing onto the rebound backend. Render both as recoverable, not dead.

type TransportState =
  | 'connecting' | 'connected' | 'reconnecting' | 'renegotiating' | 'disconnected' | 'failed'

SessionPhaseName

High-level, app-facing session lifecycle — nine honest phases.

type SessionPhaseName =
  | 'idle'          // created, not yet admitted/connected
  | 'queued'        // waiting in the admission queue (phase.queue)
  | 'unavailable'   // no runtime exists to serve this function — non-terminal, recovers
  | 'provisioning'  // admitted; compute is being allocated/warmed
  | 'connecting'    // transport is establishing the media session
  | 'live'          // first media track has arrived
  | 'error'         // admission or transport failed (phase.error)
  | 'ended'         // intentionally disconnected / closed
  | 'expired'       // reached its declared maximum length — terminal, honest, not an error

RuntimeAvailability

Honest runtime readiness reported by the control plane while a session is waiting, carried on phase.runtime — so you can render "starting model…" instead of a dead spinner.

interface RuntimeAvailability {
  state: 'unavailable' | 'starting' | 'busy'
  /** Human-readable, frontend-safe explanation from the control plane. */
  reason?: string
}

SessionFailureKind

Machine-branchable category for why a session failed — carried on phase.error.kind and on thrown SessionFailedErrors.

type SessionFailureKind =
  | 'create-failed'     // control plane could not create the session
  | 'never-live'        // created/admitted but no runtime picked it up in time
  | 'transport-failed'  // WebSocket/ICE/media transport failed (often the network)
  | 'media-permission'  // client-side mic/camera permission or device failure
  | 'ended'             // ended before (or instead of) going live
  | 'runtime-failed'    // the session function crashed at runtime
  | 'expired'           // reached its declared maximum length — expected, not a failure
  | 'produce-timeout'   // the server never acked a browser publish in time

SessionPhase

A snapshot of the current phase.

interface SessionPhase {
  name: SessionPhaseName
  /** Present while name === 'queued' */
  queue?: { position: number; depth: number }
  /** Honest runtime readiness while waiting */
  runtime?: RuntimeAvailability
  /**
   * Epoch ms when the CURRENT uninterrupted pre-live wait began. Present on the
   * waiting phases (queued/unavailable/provisioning — see isWakingPhase) and
   * continuous across transitions between them, so one honest "waking (Ns)"
   * counter spans the whole wake.
   */
  wakingSince?: number
  requestId?: string
  /** Present from 'connecting' onward */
  sessionId?: string
  /** The session's deadline, when the app declared a maximum session length */
  endsAt?: Date
  /** Present while name === 'error' or 'expired' */
  error?: SessionPhaseError
}

interface SessionPhaseError {
  reason: string
  code?: string
  kind?: SessionFailureKind
  /** HTTP status of the failed create call, when kind === 'create-failed' */
  httpStatus?: number
}

SessionStatus

The always-available, diagnosable snapshot behind session.status: phase + transport state + the raw last control-plane admission status + elapsed connect time, so a failure or a stall is explainable without digging through console logs.

interface SessionStatus {
  phase: SessionPhaseName
  transportState: TransportState
  sessionId?: string
  requestId?: string
  /** The raw last admission status string the control plane sent, unmapped */
  lastServerStatus?: string
  /** Milliseconds since this handle first started connecting */
  elapsedMs?: number
  queue?: { position: number; depth: number }
  runtime?: RuntimeAvailability
  endsAt?: Date
  wakingSince?: number
  error?: SessionPhaseError
}

SessionStream

A named bidirectional media stream owned by a running function call.

interface SessionStream {
  /** Latest inbound track, or null while not attached */
  readonly track: MediaStreamTrack | null
  /** Publish a local browser AUDIO (mic) track upstream */
  attach(track: MediaStreamTrack): Promise<void>
  /** Publish a local browser VIDEO (camera) track upstream; a later call swaps the source via replaceTrack */
  attachVideo(track: MediaStreamTrack): Promise<void>
  detach(): Promise<void>
  detachVideo(): Promise<void>
  /** Seek to seconds from start, or back to the live edge */
  seek(target: number | 'live'): Promise<void>
  /** Read the media as a lazy, cancelable byte stream — same surface live and post-seek */
  chunks(options?: StreamChunkOptions): ChunkReadable
  /** Subscribe to seek completions (number = seconds from start, or back to 'live') */
  onSeeked(handler: (target: number | 'live') => void): () => void
  /** Subscribe to track lifecycle */
  on(event: 'track', handler: (track: MediaStreamTrack | null) => void): () => void
  /** Consume this stream as DATA messages (subscribing opt-in) */
  messages(): AsyncIterable<unknown>
  /** Produce a DATA payload on this named stream (mirror of ctx.stream(name).emit) */
  emit(payload: unknown, options?: { to?: string }): Promise<void>
}

SessionDocument

A CRDT document (vanilla Yjs underneath) synced via the session transport.

interface SessionDocument {
  /** Deep-merge a patch and notify listeners */
  set(patch: Record<string, unknown>): void
  /** Read state; pass a dotted path for a nested value */
  get(path?: string, defaultValue?: unknown): unknown
  /** Subscribe to changes */
  on(event: 'change', handler: (snapshot: Record<string, unknown>) => void): () => void
  /** Append-only text value at `field` — efficient incremental (token) streaming */
  text(field: string): SessionText
  /** Whether the server's initial state has been applied; pre-sync writes are buffered */
  readonly synced: boolean
  /** One-shot: invoke once the doc first becomes synced */
  onSynced(handler: () => void): () => void
}

Session

Returned by app.<function>().

interface Session {
  readonly id: string
  readonly phase: SessionPhase
  /** The session's deadline, or null when the app declared no maximum length */
  readonly endsAt: Date | null
  /** Structured, diagnosable status snapshot (see SessionStatus) */
  readonly status: SessionStatus
  /** Which transport carries media: 'webtransport' | 'webrtc' | null while undecided */
  readonly mediaTransport: 'webtransport' | 'webrtc' | null
  /** Ephemeral who's-here presence over the session's Yjs awareness */
  readonly presence: Presence

  stream(name: string): SessionStream
  doc(key: string): SessionDocument

  /** Correlated request/response riding a session doc — survives reconnects */
  request(payload: unknown, options?: RequestOptions): Promise<unknown>
  /** Streamed response: yields incremental token deltas on an addressed llm-resp:<id> stream */
  requestStream(payload: unknown, options?: RequestStreamOptions): AsyncIterable<string>
  /** Non-stream convenience over the same lane: await the terminal body */
  complete(payload: unknown, options?: RequestStreamOptions): Promise<unknown>

  /** Fires immediately with the current phase, then on each transition. Returns unsubscribe. */
  onPhase(handler: (phase: SessionPhase) => void): () => void
  /** Structured diagnostics ({ level, kind, message, detail? }) emitted when the SDK detects a notable condition and takes recovery action */
  onDiagnostic(handler: (diagnostic: SessionDiagnostic) => void): () => void
  /** First-media activation watchdog events (activating → cold-boot → … → first-media), per named stream */
  onActivation?(handler: (event: ActivationEvent) => void): () => void
  /** Resolve at 'live'; reject with a typed SessionFailedError (default timeout 45s) */
  whenLive(options?: { timeout?: number; signal?: AbortSignal }): Promise<void>
  /** Record client activity now (idle-timeout primitive); throttled internally */
  touch(): void

  /** Org-scoped recordings for this session (DVR lifecycle & retention) */
  readonly recordings: RecordingsAccessor
  /** Session-scoped runtime artifacts (list + short-lived download URLs) */
  readonly artifacts: RuntimeArtifactsAccessor

  /** One level-triggered page-foreground recovery pass (auto-wired to visibility/focus; safe no-op when healthy) */
  recover(): void
  /** Register a level-triggered recovery hook for surfaces the SDK does not own (mic capture, playback). Returns unsubscribe */
  onRecovery(hook: () => void): () => void

  /** Terminally end this session and await the control-plane release (idempotent) */
  end(): Promise<SessionEndResult>
  /** Detach this handle's transport WITHOUT ending the server session (reconnectable) */
  disconnect(): void
}

App

Returned by App(appId, options). Each property is a deployed Python function; calling it returns a Session.

interface App {
  [fnName: string]: (args?: Record<string, unknown>) => Session
}

args map 1:1 to the Python function's args/kwargs. They are invocation arguments only — decorator config (GPU units, compute shape, scheduling) is fixed at deploy and is never overridable here. Reserved-looking names (e.g. gpus) are passed through untouched.

AppOptions

interface AppOptions {
  baseUrl: string
  orgId: string
  /** Optional regional fallbacks; most apps pass only the global URL. */
  fallbackUrls?: readonly string[]
  /** Advanced/manual JWT; React WorkOS apps let the bridge resolve this. */
  jwt?: string
  /** Provider for fresh browser-safe JWTs at session allocation. */
  getAccessToken?: (options?: { forceRefresh?: boolean; reason?: 'initial' | 'unauthorized' | 'background' }) => string | null | undefined | Promise<string | null | undefined>
  authProvider?: string
  /** Functions base URL; required before session.recordings resolves. */
  functionsUrl?: string
  /** QUIC/WebTransport dial negotiation kill switch (default true). */
  allowWebTransport?: boolean
}

AttachOptions

Options for Session.attach(sessionId, options) — same auth shape as AppOptions minus the function dispatch (a viewer references an existing session, it does not create one).

Store / StoreOptions

interface StoreOptions {
  baseUrl: string
  orgId: string
  jwt?: string
  authProvider?: string
  /** Advanced server-side access only — never in a browser. */
  apiKey?: string
}

interface Store {
  get(key: string): Promise<unknown>
  has(key: string): Promise<boolean>
  /** Subscribe to a global stream. Returns unsubscribe. */
  on(name: string, handler: (data: unknown) => void): () => void
  /** Emit to a global stream. */
  emit(name: string, data: unknown): void
}

Phase helpers

/** Resolve once a phase-observable session reaches 'live' (standalone form of session.whenLive) */
function whenLive(session: PhaseObservable, options?: WhenLiveOptions): Promise<void>

/** Default, honest display copy for a phase ("Starting model…", a queue position, …) */
function describeSessionPhase(phase: SessionPhase): string

/** True for the pre-live waiting phases: queued | unavailable | provisioning */
function isWakingPhase(phase: SessionPhase | SessionPhaseName | null | undefined): boolean

On this page