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 errorRuntimeAvailability
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 timeSessionPhase
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