import { z } from 'zod'; import * as react_jsx_runtime from 'react/jsx-runtime'; import React, { ReactNode } from 'react'; /** * Internal types for the Reactor SDK. * * All Zod schemas and derived TypeScript types live here. * Version constants are sourced from package.json via resolveJsonModule. */ declare const TrackCapabilitySchema: z.ZodObject<{ name: z.ZodString; kind: z.ZodEnum<{ video: "video"; audio: "audio"; }>; direction: z.ZodEnum<{ recvonly: "recvonly"; sendonly: "sendonly"; }>; }, z.core.$strip>; declare const CommandCapabilitySchema: z.ZodObject<{ name: z.ZodString; description: z.ZodString; schema: z.ZodOptional>; }, z.core.$strip>; declare const CapabilitiesSchema: z.ZodObject<{ protocol_version: z.ZodString; tracks: z.ZodArray; direction: z.ZodEnum<{ recvonly: "recvonly"; sendonly: "sendonly"; }>; }, z.core.$strip>>; commands: z.ZodOptional>; }, z.core.$strip>>>; emission_fps: z.ZodOptional>; }, z.core.$strip>; declare const SessionResponseSchema: z.ZodObject<{ session_id: z.ZodString; state: z.ZodString; cluster: z.ZodString; model: z.ZodObject<{ name: z.ZodString; version: z.ZodOptional; }, z.core.$strip>; server_info: z.ZodObject<{ server_version: z.ZodString; }, z.core.$strip>; selected_transport: z.ZodOptional>; capabilities: z.ZodOptional; direction: z.ZodEnum<{ recvonly: "recvonly"; sendonly: "sendonly"; }>; }, z.core.$strip>>; commands: z.ZodOptional>; }, z.core.$strip>>>; emission_fps: z.ZodOptional>; }, z.core.$strip>>; }, z.core.$strip>; type TrackCapability = z.infer; type CommandCapability = z.infer; type SessionResponse = z.infer; type Capabilities = z.infer; /** * Reference to an uploaded file, returned by {@link Reactor.uploadFile}. * * Pass a `FileRef` as a value in {@link Reactor.sendCommand} and it will * be serialized into the `uploads` section of the wire envelope, separate * from scalar arguments. The runtime resolves each reference to bytes * before dispatching the event to the model handler. */ declare class FileRef { readonly uploadId: string; readonly name: string; readonly mimeType: string; readonly size: number; constructor(uploadId: string, name: string, mimeType: string, size: number); } type ReactorStatus = "disconnected" | "connecting" | "waiting" | "ready"; /** * The message scope identifies the envelope layer a data channel message belongs to. * - "application": model-defined commands (client->runtime) and model-emitted payloads (runtime->client). * - "runtime": platform-level control messages (e.g., capabilities exchange). */ type MessageScope = "application" | "runtime"; interface ReactorError { code: string; message: string; timestamp: number; recoverable: boolean; component: "api" | "gpu"; retryAfter?: number; } declare class ConflictError extends Error { constructor(message: string); } declare class AbortError extends Error { constructor(message: string); } /** Matches both our custom AbortError and the native DOMException thrown by fetch(). */ declare function isAbortError(error: unknown): boolean; interface ReactorState$1 { status: ReactorStatus; lastError?: ReactorError; } /** * Options for configuring the connect polling behavior. */ interface ConnectOptions { /** Maximum number of SDP polling attempts before giving up. Default: 6. */ maxAttempts?: number; /** * When true (default), sends `resume_track` for every recvonly track * immediately after the connection is established, causing the backend to * begin streaming those tracks — this preserves the pre-multi-connection * behaviour where output tracks flow automatically on connect. Set to false * to keep recvonly tracks paused on connect and resume them individually via * `resumeTrack()` (e.g. multi-connection apps that only subscribe to a * subset of peers). */ autoResumeTracks?: boolean; /** * Attach to a session that already exists (e.g. one created by a backend) * instead of creating a new one. When set, `connect()` skips `POST /sessions` * and brings up the transport directly against this id. The JWT passed to * `connect()` must be valid for the account that owns the session. */ sessionId?: string; /** * Use a specific WebRTC connection id instead of minting one. When omitted * (default), the transport registers a fresh connection (`POST .../connections`) * and the server mints the id. When set, the transport skips registration and * sends its SDP offer directly to that connection — so the id must already have * been registered under this session (e.g. a backend called `POST .../connections` * and handed the id to this client) and must still be open. * * `connect()` rejects if the id is invalid (out of the server's accepted range) * or if no open connection with that id exists for the session. */ connectionId?: number; } /** * Transport-agnostic timing breakdown of the connect() handshake, recorded * once per connection and included in every subsequent {@link ConnectionStats} * update. All durations are in milliseconds (from `performance.now()`). * * For transport-specific timings (e.g. ICE negotiation, data channel open), * see the relevant transport stats type (e.g. {@link WebRTCTransportTimings}). */ interface ConnectionTimings { /** POST /sessions round-trip time */ sessionCreationMs: number; /** Total time spent in transport.connect() (signaling, negotiation, etc.) */ transportConnectingMs: number; /** End-to-end: connect() invocation → status "ready" */ totalMs: number; } interface ConnectionStats { /** ICE candidate-pair round-trip time in milliseconds */ rtt?: number; /** ICE candidate type: "host", "srflx", "prflx", or "relay" (TURN) */ candidateType?: string; /** Estimated available incoming bitrate in bits/second */ availableIncomingBitrate?: number; /** Estimated available outgoing bitrate in bits/second */ availableOutgoingBitrate?: number; /** Real-time Incoming bitrate in bits/second */ incomingBitrate?: number; /** Real-time Outgoing bitrate in bits/second */ outgoingBitrate?: number; /** Received video frames per second */ framesPerSecond?: number; /** Ratio of packets lost (0-1) */ packetLossRatio?: number; /** Network jitter in seconds (from inbound-rtp) */ jitter?: number; /** Timing breakdown of the initial connection handshake (set once, persisted until disconnect) */ connectionTimings?: ConnectionTimings; timestamp: number; } type ReactorEvent = "statusChanged" | "sessionIdChanged" | "message" | "runtimeMessage" | "trackReceived" | "error" | "sessionExpirationChanged" | "capabilitiesReceived" | "statsUpdate"; /** * Severity tier of a content moderation event delivered as the inner * payload of a `runtimeMessage` with `type === "moderation"`. * * - `"warn"`: the input scored above the warn threshold but below the * terminate threshold. The session continues; this is informational * only. * - `"terminate"`: the input crossed the terminate threshold. The * session will be ended shortly after this message is dispatched. */ type ModerationAction = "warn" | "terminate"; /** * Inner payload of a `runtimeMessage` with `type === "moderation"`. * * Surfaces a content-moderation outcome to the client app. Fires on * any moderatable input (free-text fields, file uploads) that the * configured moderation policy flags. Apps receive it by subscribing * to the existing `runtimeMessage` event and filtering on `type`: * * reactor.on("runtimeMessage", (m) => { * if (m?.type === "moderation") { * const payload = m.data as ModerationEvent; * // ...render banner, log, etc. * } * }); */ interface ModerationEvent { /** Severity tier — `"warn"` continues the session, `"terminate"` ends it. */ action: ModerationAction; /** * Modality of the flagged input. `"text"` for string fields, * `"image"` for `UploadedFile` payloads with an image MIME type. */ input_kind: "text" | "image"; /** * Name of the inbound command/event whose payload was flagged * (e.g. `"set_prompt"`, `"set_image"`, `"fileUploaded"`). */ command: string; /** Category labels that flagged (e.g. `["sexual"]`, `["violence/graphic"]`). */ categories: string[]; /** Short human-readable summary suitable for UI rendering. */ message: string; } /** * Lazy resolver for the Coordinator bearer token. The SDK calls this * immediately before each authenticated HTTP request, so short-lived * tokens (Clerk session JWTs, etc.) are refreshed transparently. * Returning `""` suppresses the `Authorization` header entirely. */ type JwtResolver = () => string | Promise; /** Static token, or a {@link JwtResolver} invoked per request. */ type JwtSource = string | JwtResolver; /** Wrap a {@link JwtSource} into a {@link JwtResolver}. */ declare function normalizeJwtSource(source: JwtSource): JwtResolver; /** * Stateless primitives for the Reactor recording feature. * * This module owns: * - Public-facing {@link Clip} type, {@link RecordingError} class, and * the `RuntimeRecordingMessageType` enum. * - Wire schemas (`ClipReadyPayloadSchema`, `ClipFailedPayloadSchema`) * plus `clipFromPayload` to convert snake_case wire payloads into * camelCase `Clip` objects. * - HTTP helpers `fetchPlaylist` (deadline-driven polling) and * `parsePlaylist` (HLS `.m3u8` parser). * - The `downloadClipAsFile` helper that streams the referenced * fMP4 chunks, remuxes them into a flat social-media-ready MP4 * via `mp4box` (PTS=0, faststart, `major_brand=isom`), and * returns a Blob for browser download. * * Stateful pieces (FIFO promise correlator, in-flight request * lifecycle, integration with Reactor's event bus) live in the * `RecordingClient` class in `core/RecordingClient.ts`. * * Wire contract is documented end-to-end in the *Video Recording * Feature* Notion page; the runtime side ships in * `reactor_runtime/recording/`. */ /** * Names of the runtime-scoped messages used by the recording feature. * Outbound names are sent via `Reactor.sendCommand(name, …, "runtime")`; * inbound names appear as `message.type` on `runtimeMessage` events. */ declare const RuntimeRecordingMessageType: { readonly REQUEST_CLIP: "requestClip"; readonly REQUEST_RECORDING: "requestRecording"; readonly CLIP_READY: "clipReady"; readonly CLIP_FAILED: "clipFailed"; }; /** Discriminator on a {@link Clip}. `"snap"` from `requestClip`, `"recording"` from `requestRecording`. */ type ClipKind = "snap" | "recording"; /** * Resolved outcome of a {@link Reactor.requestClip} / {@link Reactor.requestRecording} call. * * The runtime returns immediately on every request — it does *not* * block until the in-progress chunk has been finalised. The response * is a *promise*: `endMarker` reflects wall-clock at request time * and may point inside a chunk ffmpeg is still writing. The * `/clips` manifest endpoint returns `202 Retry-After` while that * chunk is in flight; the SDK polls until `200`. * * - `startMarker` / `endMarker` / `nowMarker` are session-relative * seconds since recorder start. * - `predictedReadyAtMs` is a **Unix epoch in milliseconds** — the * runtime's estimate of when the boundary chunk will be servable * by `/clips`. Compare against `Date.now()` to drive a "ready in * Ns" indicator. The SDK uses this as the polling deadline: past * `predictedReadyAtMs + slackMs` and still 202, the SDK fails with * `CLIP_NOT_READY` on the assumption the runtime crashed. * - `playlistUrl` is short-lived in production (the embedded chunk * URLs are presigned for a few minutes). Re-issuing the request * produces a fresh URL with fresh presigning. */ interface Clip { sessionId: string; kind: ClipKind; startMarker: number; endMarker: number; nowMarker: number; predictedReadyAtMs: number; playlistUrl: string; } /** * Error thrown when a recording request cannot be served. * * `code` is a stable, machine-readable identifier; `reason` is the * raw string the runtime / manifest endpoint returned (passed through * verbatim from `clipFailed.reason`, the `/clips` HTTP body, or the * SDK's pre-flight check). */ declare class RecordingError extends Error { readonly code: "RECORDER_DISABLED" | "INVALID_DURATION" | "REQUEST_TIMEOUT" | "DISCONNECTED" | "CLIP_GONE" | "CLIP_NOT_READY" | "PLAYLIST_FETCH_FAILED" | "CHUNK_FETCH_FAILED" | "INVALID_PLAYLIST" | "DOWNLOAD_UNSUPPORTED" | "INTERNAL_ERROR"; readonly reason: string; constructor(code: "RECORDER_DISABLED" | "INVALID_DURATION" | "REQUEST_TIMEOUT" | "DISCONNECTED" | "CLIP_GONE" | "CLIP_NOT_READY" | "PLAYLIST_FETCH_FAILED" | "CHUNK_FETCH_FAILED" | "INVALID_PLAYLIST" | "DOWNLOAD_UNSUPPORTED" | "INTERNAL_ERROR", reason: string); } /** `clipReady.data` payload as serialised by `ClipResult.to_dict()`. */ declare const ClipReadyPayloadSchema: z.ZodObject<{ session_id: z.ZodString; kind: z.ZodEnum<{ snap: "snap"; recording: "recording"; }>; start_marker: z.ZodNumber; end_marker: z.ZodNumber; now_marker: z.ZodNumber; predicted_ready_at_ms: z.ZodNumber; playlist_url: z.ZodString; }, z.core.$loose>; type ClipReadyPayload = z.infer; /** `clipFailed.data` payload — short reason string from the runtime. */ declare const ClipFailedPayloadSchema: z.ZodObject<{ reason: z.ZodDefault; }, z.core.$loose>; type ClipFailedPayload = z.infer; /** Options for {@link clipFromPayload}. */ interface ParseClipOptions { /** * Coordinator base URL. When set, resolves `payload.playlist_url` * against this base — used to convert the runtime's path-only * playlist URL (``/clips?session_id=…``) into an absolute URL the * browser/SDK can fetch. Also handles the legacy case where an * older runtime emits an absolute URL bound to its own * ``0.0.0.0`` listener: the host/port are rewritten onto the * coordinator. */ coordinatorBaseUrl?: string; } /** * Convert a validated {@link ClipReadyPayload} into the public {@link Clip}. * Pure: no IO, no side effects. */ declare function clipFromPayload(payload: ClipReadyPayload, options?: ParseClipOptions): Clip; /** * Resolve `target` against `base`, returning an absolute URL. * * Two cases: * * * **Path-only / relative target** (no scheme — e.g. the * runtime's current ``/clips?session_id=…`` shape): joined * against ``base`` via ``new URL(target, base)``, preserving * path, query, and fragment. * * **Absolute target** (``http://…`` / ``https://…``, e.g. a legacy * runtime emitting a URL bound to its own ``0.0.0.0`` listener): * scheme, hostname, and port are replaced with ``base``'s, * preserving path, query, and fragment. * * Returns the original ``target`` on any parse failure rather than * throwing — the SDK consumes the URL eagerly and shouldn't break * a session over a malformed clip envelope. */ declare function rewriteUrlHost(target: string, base: string): string; /** Internal — segments referenced by an HLS manifest, in playback order. */ interface ParsedPlaylist { initUrl: string; segmentUrls: string[]; } /** * Default grace period after `predictedReadyAtMs` before * {@link fetchPlaylist} gives up. Applied from * `max(predictedReadyAtMs, pollStart)` so late clicks still get the * full window. */ declare const DEFAULT_PLAYLIST_POLL_SLACK_MS = 15000; interface FetchPlaylistOptions { /** * Unix epoch (ms) when the runtime predicts the boundary chunk will * be servable. Pass `clip.predictedReadyAtMs` here. When set, polling * continues until `predictedReadyAtMs + slackMs`; once past, a * stuck `202` produces `CLIP_NOT_READY` (assume runtime crashed). */ predictedReadyAtMs?: number; /** Grace period after `predictedReadyAtMs`. Default {@link DEFAULT_PLAYLIST_POLL_SLACK_MS}. */ slackMs?: number; /** * Hard cap on the per-poll wait. The server's `Retry-After` header is * honored but clamped. Default 2000 ms keeps pending UI snappy. */ maxRetryDelayMs?: number; /** Floor on the per-poll wait so we don't hot-loop on cheap networks. Default 200 ms. */ minRetryDelayMs?: number; /** * Fallback retry count used when `predictedReadyAtMs` is omitted * (e.g. someone calls `fetchPlaylist` directly with a saved URL, * outside of a fresh `Clip`). Default 5. */ maxRetries?: number; /** Aborts in-flight fetches and the inter-poll sleep. */ signal?: AbortSignal; /** * Coordinator JWT. When set, attached as `Authorization: Bearer * ` on the manifest GET — the Coordinator hop. In local mode * (HttpRuntime), leave undefined. */ jwt?: string; } /** * Fetch the playlist URL, polling on `202 Accepted` (which the manifest * endpoint returns while the boundary chunk is still uploading). * * Polling deadline is driven by `predictedReadyAtMs + slackMs` when a * `Clip` was just minted by the runtime; without that field the * function falls back to `maxRetries` attempts. Either way: * - `200` → returns the manifest body. * - `410` / `404` → throws `CLIP_GONE`. * - `5xx`/other → throws `PLAYLIST_FETCH_FAILED` (no retry). * - Past the deadline / retries with stuck `202` → throws * `CLIP_NOT_READY` (the runtime probably crashed mid-chunk). */ declare function fetchPlaylist(playlistUrl: string, options?: FetchPlaylistOptions): Promise; /** * Parse an HLS `.m3u8` body into the init segment URL plus the ordered * list of media segment URLs. Resolves relative URLs against the * playlist URL itself. */ declare function parsePlaylist(manifestBody: string, playlistUrl: string): ParsedPlaylist; /** * Wrap an HLS manifest body in a `blob:` URL suitable for `