docs
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, UrunJwtProvider

There 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 a Session. args map 1:1 to the Python function's args/kwargs and never override GPU/scheduling config.
  • session.stream(name) returns a SessionStream; read .track (a MediaStreamTrack | null) — it does not return a track directly.
  • Stream names match the Python ctx.stream(name); the steering doc is controlctx.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 the urun_… deploy key in the browser.
  • Render session.phase for honest progress — nine phases: idle/queued/unavailable/provisioning/connecting/live/error/ended/expired. describeSessionPhase(p) gives default copy; gate publishes on await session.whenLive().

Definition of done

  • @urun-sh/react (or @urun-sh/core) installed.
  • An auth bridge wraps UrunProvider; orgId + appId + authProvider set.
  • useApp() invokes the correct function name with the right args.
  • Media read via session.stream(name).track; state via session.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.

On this page