docs
TypeScript SDKReference

App

API reference for App, app.<function>(), and the Session handle

API reference for App, app.<function>(), and Session — calling deployed Python functions and streaming results.

Public API at a glance

NameDescription
App(appId, options)Reference a deployed app
app.<fnName>(args?)Invoke a deployed Python function, get a Session
session.stream(name)Get a named SessionStream
session.doc(key)Get a synced SessionDocument
session.request() / requestStream() / complete()Correlated request/response riding the session
session.onPhase(handler)Subscribe to phase changes
session.whenLive(options?)Resolve at live; reject with a typed failure
session.touch()Record client activity (idle-timeout primitive)
session.recordings / session.artifactsDVR recordings and runtime artifacts
session.end() / session.disconnect()Terminal release vs. detach-and-stay-reconnectable
Session.attach(id, options)Join a running session as a consume-only viewer

App()

function App(appId: string, options: AppOptions): App

Reference a deployed uRun app. This is a lookup, not a deployment — the app must already be deployed. No network call happens until you invoke a function.

ParameterTypeRequiredDescription
appIdstringyesThe deployed app name
options.baseUrlstringyesBase URL of your uRun backend (e.g. https://urun.sh)
options.orgIdstringyesYour organization ID
options.jwtstringAdvanced/manual user JWT (React WorkOS resolves this for you)
options.getAccessTokenfunctionProvider for fresh browser-safe JWTs
options.authProviderstringAuth provider id, e.g. "workos"
import { App } from '@urun-sh/core'

const app = App('my-app', { baseUrl: 'https://urun.sh', orgId: 'your-org-id' })

app.<function>()

app.<fnName>(args?: Record<string, unknown>): Session

Invoke a deployed Python function. Names defined with @app.function become methods on the app. Returns a Session synchronously; allocation and connection happen lazily.

ParameterTypeDefaultDescription
argsRecord<string, unknown>{}Invocation arguments, delivered verbatim to the Python function

args are invocation arguments only — they never override the function's deploy-time GPU/scheduling config.

const session = app.generate({ prompt: 'A cat walking' })

session.stream()

session.stream(name: string): SessionStream

Get (or create) a named media stream. The stream name matches ctx.stream(name) on the Python side.

const video = session.stream('video')

video.on('track', (track) => {
  if (track) videoEl.srcObject = new MediaStream([track])
})

// publish a local track upstream (attach = mic audio; attachVideo = camera):
await video.attachVideo(cameraTrack)
await mic.attach(micTrack)

// seek within the recorded (DVR) window, or return to live:
await video.seek(12.5)
await video.seek('live')

// data lane: consume (subscribes this peer) and produce
for await (const msg of video.messages()) render(msg)
await video.emit({ kind: 'note', at: Date.now() })

SessionStream has .track (latest inbound MediaStreamTrack | null), .attach/.detach (audio), .attachVideo/.detachVideo (video, source swap via replaceTrack), .seek / .onSeeked, .chunks() (lazy byte-stream read, same surface live and post-seek), .messages()/.emit() (the data lane), and .on('track', …).


session.doc()

session.doc(key: string): SessionDocument

Get (or create) a CRDT document for the session (vanilla Yjs underneath). Every doc syncs independently to the runtime — control (read in Python as ctx.doc("control")), the internal request/response docs, and any app-defined key.

session.doc('control').set({ prompt: { text: 'A dog running' } })

session.doc('control').on('change', (snapshot) => {
  console.log(snapshot)
})

.set(patch) deep-merges. .get(path?) reads a dotted path. .text(field) returns an append-only text value for efficient token streaming; .synced / .onSynced() report when the server's initial state has applied.


session.request()

session.request(payload, options?): Promise<unknown>
session.requestStream(payload, options?): AsyncIterable<string>
session.complete(payload, options?): Promise<unknown>

Correlated request/response riding a session doc — no new transport or endpoint, so an in-flight request survives a reconnect. request awaits a single result. requestStream consumes the runtime's response as a stream of incremental token deltas (the OpenAI stream: true shape) on a dedicated, addressed llm-resp:<id> stream; complete is the non-stream convenience over the same lane, resolving with the terminal body. Rejects are typed (RequestTimeoutError, RequestAbortError, RequestError).


session.onPhase()

session.onPhase(handler: (phase: SessionPhase) => void): () => void

Subscribe to high-level phase changes across the nine phases (idle, queued, unavailable, provisioning, connecting, live, error, ended, expired — see Types). Fires immediately with the current phase, then on each transition. Returns an unsubscribe function.

import { describeSessionPhase } from '@urun-sh/core'

session.onPhase((p) => {
  statusEl.textContent = describeSessionPhase(p)
  if (p.name === 'live') hideSpinner()
})

session.whenLive()

session.whenLive(options?: { timeout?: number; signal?: AbortSignal }): Promise<void>

Resolve once the session reaches live; reject on error/ended/expired, timeout (default 45s, 0 disables), or abort. The canonical "is media ready?" gate before publishing a local track. Rejects with a typed SessionFailedError carrying a SessionFailureKind and the full SessionStatus snapshot, so you can render why (backend busy vs. network vs. auth) instead of an opaque error. Also available standalone: import { whenLive } from '@urun-sh/core'.


Session properties & lifecycle

MemberDescription
session.phase / session.statusCurrent phase snapshot / full diagnosable status (transport state, raw last admission status, elapsed)
session.endsAtThe session's deadline (Date | null) when the app declared a maximum length; at that instant the phase becomes expired
session.mediaTransport'webtransport' | 'webrtc' | null — which negotiated transport carries media
session.presenceEphemeral who's-here presence over the session's Yjs awareness (clears on disconnect)
session.touch()Record client activity now — the idle-timeout "I'm still here" signal (throttled internally)
session.recordingsDVR recordings for this session: list, get, retain(id, "7d"), pin, unpin, permanent, delete
session.artifactsRuntime artifacts: list(), getDownloadUrl(artifactId) (short-lived object-store URL)

session.recordings requires functionsUrl on AppOptions; accessing it otherwise throws a clear error.


session.end() / session.disconnect()

session.end(): Promise<SessionEndResult>   // terminal: ends the server session (idempotent)
session.disconnect(): void                 // detach this handle only; session stays reconnectable

end() terminally releases the session on the control plane and resolves with the acknowledged outcome. disconnect() detaches this handle's media transport without ending the server session.


Session.attach()

Session.attach(sessionId: string, options: AttachOptions): Promise<Session>

Join an already-running session as a consume-only viewer. The viewer watches the live producer; it never allocates a GPU, never takes the publisher slot, and never tears down the session on disconnect. Cross-org or unknown ids are rejected.

import { Session } from '@urun-sh/core'

const viewer = await Session.attach(sessionId, {
  baseUrl: 'https://urun.sh',
  orgId: 'your-org-id',
  jwt: await getCurrentUserJwt(),
})
const video = viewer.stream('video')

See Transport for the phase model and viewer attach in context.

On this page