TypeScript SDK
AI-Assisted Setup
A compact spec for AI agents scaffolding uRun TypeScript clients
A dense, copy-ready reference for AI coding agents producing uRun browser clients. Everything here reflects the shipped @urun-sh/core and @urun-sh/react. For a human walkthrough, see Getting Started.
Mental model
The TypeScript SDK is the browser client. You reference a deployed app, invoke a function by name, and get a real-time session back: named media streams plus a synced control doc. Two packages:
@urun-sh/react— React bindings (UrunProvider,useApp, auth bridges, media components). Recommended.@urun-sh/core— framework-agnostic primitives (App,Session,createStore).
Public API surface
@urun-sh/core: App(appId, options) → app.<fn>(args) → Session
Session: .stream(name) → SessionStream { track, attach, attachVideo, detach,
detachVideo, seek, chunks, messages, emit, on }
.doc(key) → SessionDocument { set, get, text, synced, onSynced, on }
.request / .requestStream / .complete // correlated req/resp on the doc
.phase, .status, .endsAt, .mediaTransport, .presence
.onPhase, .whenLive, .touch, .recordings, .artifacts
.end() (terminal) / .disconnect() (detach, reconnectable)
Session.attach(id, options) // consume-only viewer
createStore(options) → Store { get, has, on, emit }
createClientToken(apiKey, opts) // SERVER-side scoped token mint
prewake(options) // advisory scale-to-zero warm ping
whenLive, describeSessionPhase, isWakingPhase
RecordingsClient, RuntimeArtifactsClient, createActivityTracker
@urun-sh/react: UrunProvider, useApp(), useUrunAuth(), UrunErrorBoundary
req/resp hooks: useRequest, useCompletion, useChat
docs/streams: useSessionDoc, useDocStore, useStreamMessages,
UrunStreamTail, UrunDocPanel, UrunControlSender, UrunEventSpine
media: Session + bare-name components —
send: Camera, Mic (viewless unless `visible`)
show: Video, Image, Audio (sound-only unless `controls`)
loop: Voice (full-duplex over one named stream)
useSessionTrack, useUrunAudioLevel
VideoPlayer — SUBPATH ONLY: '@urun-sh/react/video'
(VOD/recordings; optional video.js peer; not in the root export)
input: useInputPresence // continuous kb/mouse over awareness
honest session UX: useSessionPhase, UrunSessionStatus, UrunSessionGate,
useSessionWake, UrunSessionWaking, useUrunPrewake,
useSessionEndsAt, UrunSessionClock, UrunSessionEnded,
useSessionIdle, UrunIdleWarning
component registry: registerComponent, ComponentRenderer + built-ins
auth bridges: UrunWorkOSProvider (/workos, /next-workos),
UrunAuthProvider, UrunJwtProviderThere is no useSession, useStream, useScene, useLayout, SessionProvider, CompositorProvider, or public TransportSession. Consume media through the Session returned by useApp().
React pattern
'use client'
import '@urun-sh/react/styles.css'
import { UrunProvider, useApp } from '@urun-sh/react'
import { UrunWorkOSProvider } from '@urun-sh/react/workos' // Next.js: '@urun-sh/react/next-workos'
import { useEffect, useRef } from 'react'
function Player() {
const app = useApp()
const ref = useRef<HTMLVideoElement>(null)
const session = app.generate({ prompt: 'a sunset' }) // render-safe
const video = session.stream('video')
useEffect(() => {
if (video.track && ref.current) ref.current.srcObject = new MediaStream([video.track])
}, [video.track])
return (
<>
<video ref={ref} autoPlay muted />
<button onClick={() => session.doc('control').set({ prompt: { text: 'a forest' } })}>Steer</button>
</>
)
}
export default function App() {
return (
<UrunWorkOSProvider clientId="client_...">
<UrunProvider baseUrl="https://urun.sh" orgId="your-org-id" authProvider="workos" appId="my-app">
<Player />
</UrunProvider>
</UrunWorkOSProvider>
)
}Vanilla pattern
import { App } from '@urun-sh/core'
const app = App('my-app', { baseUrl: 'https://urun.sh', orgId: 'your-org-id', authProvider: 'workos' })
const session = app.generate({ prompt: 'a cat walking' })
const video = session.stream('video')
video.on('track', (track) => { if (track) videoEl.srcObject = new MediaStream([track]) })
session.doc('control').set({ prompt: { text: 'a dog running' } })
session.onPhase((p) => console.log(p.name, p.queue))Best practices
- Reference apps with
App(appId, options)— the appId is the first arg, not part of options. app.<fn>(args)returns aSession.argsmap 1:1 to the Python function's args/kwargs and never override GPU/scheduling config.session.stream(name)returns aSessionStream; read.track(aMediaStreamTrack | null) — it does not return a track directly.- Stream names match the Python
ctx.stream(name); the steering doc iscontrol↔ctx.doc("control"). - Auth: WorkOS (default) or customer-JWT. Pass
orgId+ a short-lived user JWT via an auth bridge. Never put a server API key or theurun_…deploy key in the browser. - Render
session.phasefor honest progress — nine phases:idle/queued/unavailable/provisioning/connecting/live/error/ended/expired.describeSessionPhase(p)gives default copy; gate publishes onawait session.whenLive().
Definition of done
-
@urun-sh/react(or@urun-sh/core) installed. - An auth bridge wraps
UrunProvider;orgId+appId+authProviderset. -
useApp()invokes the correct function name with the right args. - Media read via
session.stream(name).track; state viasession.doc(key). - No
useSession/useStream/SessionProvider/CompositorProvider/TransportSession. - No
@urun/*package names (use@urun-sh/core/@urun-sh/react).
For the backend spec, see Python AI-Assisted Setup.