@runappsai/sdk 0.6.10 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -2
- package/dist/audio.d.ts +7 -1
- package/dist/audio.d.ts.map +1 -1
- package/dist/audio.js +6 -0
- package/dist/audio.js.map +1 -1
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +2 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/realtime.d.ts +137 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +359 -0
- package/dist/realtime.js.map +1 -0
- package/dist/sdk.umd.js +3 -3
- package/dist/sdk.umd.js.map +1 -1
- package/dist/transport.d.ts +2 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +4 -0
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
- package/src/audio.ts +12 -1
- package/src/client.ts +4 -0
- package/src/events.ts +2 -0
- package/src/index.ts +17 -0
- package/src/realtime.ts +446 -0
- package/src/transport.ts +5 -0
package/dist/sdk.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/transport.ts","../src/events.ts","../src/event-wrap.ts","../src/chat.ts","../src/models.ts","../src/image.ts","../src/media.ts","../src/audio.ts","../src/video.ts","../src/computer.ts","../src/files.ts","../src/embeddings.ts","../src/activity-tracker.ts","../src/sync/codec.ts","../src/browser-auth.ts","../src/sync/hook.ts","../src/sync/log.ts","../src/sync/engine.ts","../src/sync/remote.ts","../src/client.ts","../src/model_options.ts","../src/validate.ts"],"sourcesContent":["/**\n * RunApps SDK for JavaScript / TypeScript.\n *\n * Top-level entry point — exports the `RunApps` client class plus all\n * public types so callers can write strongly-typed wrappers without\n * reaching into per-service modules.\n */\n\n// `RunJobs` is a deprecated alias of `RunApps`, kept for code written\n// before the rename.\nexport { RunApps, RunApps as Client, RunApps as RunJobs, type ClientOptions, type AuthProvider } from \"./client.js\";\n\n// SDK telemetry — `client.events` fires these around every LLM-ish\n// service call so UI overlays (the badge being the canonical example)\n// can render real-time state. The ActivityTracker turns the raw\n// stream into the snapshot the badge reads each frame; exported\n// alongside the bus so power users can build their own status UI.\nexport {\n SDKEvents,\n newRequestId,\n type RequestStartEvent,\n type RequestStreamDeltaEvent,\n type RequestEndEvent,\n type RequestErrorEvent,\n type SDKEventMap,\n type SDKCapability,\n type Unsubscribe,\n} from \"./events.js\";\nexport {\n ActivityTracker,\n type ActiveCall,\n type CompletedCall,\n type SessionStats,\n type ActivitySnapshot,\n} from \"./activity-tracker.js\";\nexport { BrowserAuth, type BrowserAuthOptions, type BrowserUser } from \"./browser-auth.js\";\n// RunApps Sync — on by default in apps; exported for status UIs and\n// for tests that drive the engine with their own storage.\nexport {\n SyncEngine,\n FilesRemote,\n installIndexedDBHook,\n type SyncOptions,\n type SyncState,\n type DbChange,\n type SyncRemote,\n type SyncDeps,\n} from \"./sync/index.js\";\nexport type { SyncStateEvent, DbChangeEvent } from \"./events.js\";\nexport { APIError } from \"./errors.js\";\nexport type { Usage } from \"./types.js\";\n\n// Chat — service class is internal; types + builders are public.\nexport type {\n ChatMessage,\n ContentPart,\n ChatTool,\n StreamOptions,\n ChatCompletionParams,\n ChatToolCall,\n ChatChoiceMessage,\n ChatChoice,\n ChatCompletion,\n ChatChunkDelta,\n ChatChunkChoice,\n ChatCompletionChunk,\n PromptRef,\n PromptInput,\n} from \"./chat.js\";\nexport {\n userMessage,\n systemMessage,\n assistantMessage,\n toolResultMessage,\n userMessageParts,\n textPart,\n imagePart,\n videoPart,\n audioPart,\n} from \"./chat.js\";\n\n// Models\nexport type { Model, ModelListOptions, Tag } from \"./models.js\";\nexport {\n hasCapabilityTag,\n acceptsModality,\n supportsVoiceClone,\n supportsInstructText,\n defaultVoice,\n} from \"./models.js\";\n\n// Model options schema (input validation + capability introspection)\nexport type {\n Schema,\n FieldSchema,\n Constraint,\n ConstraintKind,\n Catalog,\n} from \"./model_options.js\";\nexport {\n getOptionsSchema,\n acceptsField,\n requiresField,\n allowedValuesFor,\n} from \"./model_options.js\";\nexport type { ValidationError } from \"./validate.js\";\nexport { validateRequest } from \"./validate.js\";\n\n// Image\nexport type {\n ImageGenerateParams,\n ImageResult,\n ImageUsage,\n ImageResponse,\n ImageEditParams,\n ImageFileInput,\n AsyncImageJob,\n} from \"./image.js\";\n\n// Audio\nexport type {\n SpeechParams,\n SpeechResponse,\n TranscribeParams,\n TranscribeResponse,\n AudioFileInput,\n} from \"./audio.js\";\n\n// Video\nexport type {\n VideoGenerateParams,\n VideoTask,\n VideoStatus,\n VideoUsageTokens,\n WaitOptions,\n} from \"./video.js\";\n\n// Media helpers (shared by image + video + chat-multimodal)\nexport { encodeImageUrl, decodeMediaUrl } from \"./media.js\";\n\n// Computer\nexport type {\n ComputerStepParams,\n ComputerContentBlock,\n ComputerResponse,\n} from \"./computer.js\";\n\n// Files (per-project file system)\nexport type {\n FileObject,\n FileListResult,\n PutOptions as FilePutOptions,\n ListOptions as FileListOptions,\n PutFromURLOptions as FilePutFromURLOptions,\n BatchOp as FileBatchOp,\n BatchResult as FileBatchResult,\n} from \"./files.js\";\n\n// Embeddings\nexport type {\n EmbeddingsParams,\n Embedding,\n EmbeddingsUsage,\n EmbeddingsResponse,\n} from \"./embeddings.js\";\n\n// Server-tool names for chat.create({ server_tools }), with autocomplete.\nexport { ServerTools, type ServerToolName } from \"./chat.js\";\n","/**\n * APIError represents an error returned by the RunApps gateway.\n *\n * Use `instanceof APIError` (or check `name === \"APIError\"`) to distinguish\n * gateway errors from network / runtime errors:\n *\n * ```ts\n * try {\n * await client.chat.create({ model: \"...\", messages: [...] });\n * } catch (e) {\n * if (e instanceof APIError) {\n * console.error(`API ${e.statusCode}: ${e.message}`);\n * } else {\n * throw e;\n * }\n * }\n * ```\n */\nexport class APIError extends Error {\n public readonly statusCode: number;\n public readonly type: string;\n\n constructor(statusCode: number, type: string, message: string) {\n super(`runapps: ${statusCode} ${type}: ${message}`);\n this.name = \"APIError\";\n this.statusCode = statusCode;\n this.type = type;\n }\n}\n","import { APIError } from \"./errors.js\";\n\n/**\n * Internal — shared HTTP transport used by every service. Not part of the\n * public API; do not import from outside `src/`.\n *\n * Uses the global `fetch` (Node 18+ / browsers / Deno / Bun all ship it).\n * Errors from the gateway (status >= 400) are parsed and surfaced as\n * `APIError`. Network errors propagate as the underlying fetch reject.\n */\nexport interface TransportOptions {\n baseURL: string;\n /** Static bearer token. Required unless `apiKeyResolver` is supplied. */\n apiKey?: string;\n /** Dynamic resolver — called immediately before each request. Use this\n * when the token is short-lived and refreshed externally (e.g. running\n * inside an app bundle that exposes\n * its own `getToken()`). The result is awaited per request, so\n * the caller can return a Promise that hits a token endpoint. Wins\n * over `apiKey` when both are supplied. */\n apiKeyResolver?: () => string | Promise<string>;\n /**\n * Hook invoked on 401 responses, *before* the request is automatically\n * retried once. Use this to invalidate any cached token so the next\n * `apiKeyResolver` call fetches a fresh one. Throwing here cancels\n * the retry — the original 401 propagates.\n *\n * Typical wiring (browser-auth):\n * onUnauthorized: () => auth.invalidate()\n * which clears the in-memory + persisted token without flipping the\n * sticky \"signed out\" flag. The retry's resolveToken() then triggers\n * the standard signIn() redirect-grant flow.\n */\n onUnauthorized?: () => void | Promise<void>;\n /** Optional fetch override (e.g. node-fetch with custom agent). */\n fetchImpl?: typeof fetch;\n}\n\nexport class Transport {\n readonly baseURL: string;\n private readonly apiKey: string | undefined;\n private readonly apiKeyResolver: (() => string | Promise<string>) | undefined;\n private readonly onUnauthorized: (() => void | Promise<void>) | undefined;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: TransportOptions) {\n if (!opts.apiKey && !opts.apiKeyResolver) {\n throw new Error(\n \"RunApps: pass either `apiKey` or `apiKeyResolver` when constructing the client\",\n );\n }\n this.baseURL = opts.baseURL.replace(/\\/+$/, \"\");\n this.apiKey = opts.apiKey;\n this.apiKeyResolver = opts.apiKeyResolver;\n this.onUnauthorized = opts.onUnauthorized;\n // Bind to globalThis when using the platform's native fetch.\n // Without binding, `this.fetchImpl(...)` invokes with `this`\n // pointing at the Transport instance, and the browser's\n // `Window.fetch` rejects that with \"Illegal invocation\".\n // Caller-supplied `fetchImpl` is used as-is — they're responsible\n // for any binding their wrapper needs.\n this.fetchImpl = opts.fetchImpl ?? fetch.bind(globalThis);\n }\n\n /** Resolve the bearer token for the next request. */\n private async resolveToken(): Promise<string> {\n if (this.apiKeyResolver) return await this.apiKeyResolver();\n return this.apiKey ?? \"\";\n }\n\n /**\n * Wrap a fetch call with single-attempt 401 retry. `build` produces a\n * fresh RequestInit each call (so headers can re-resolve a fresh token,\n * and bodies can be re-streamed for retry). On 401, invokes\n * `onUnauthorized()` (giving the caller a chance to invalidate cached\n * auth state) then retries once with a fresh init. No retry if\n * `onUnauthorized` is absent or already retried.\n */\n private async fetchWithAuthRetry(\n path: string,\n build: () => Promise<RequestInit>,\n ): Promise<Response> {\n const url = this.baseURL + path;\n let resp = await this.fetchImpl(url, await this.buildInit(build));\n if (resp.status !== 401 || !this.onUnauthorized) return resp;\n try {\n await this.onUnauthorized();\n } catch {\n // Hook bailed out — surface the original 401.\n return resp;\n }\n resp = await this.fetchImpl(url, await this.buildInit(build));\n return resp;\n }\n\n /**\n * Every request goes out uncacheable.\n *\n * Our identity lives in the Authorization header, never in the URL — so\n * two different apps calling e.g. `/v1/files/projects/index.json` hit the\n * *same* URL with different tokens. The browser's HTTP cache doesn't key\n * on request headers, and it is partitioned by the top-level registrable\n * domain, which every `*.runapps.dev` bundle shares. One cacheable\n * response was therefore enough for app A to read app B's file and then\n * save it back as its own. The server no longer marks these cacheable;\n * this is the client-side half of that fix, and it also protects bundles\n * pinned to an older backend.\n */\n private async buildInit(build: () => Promise<RequestInit>): Promise<RequestInit> {\n const init = await build();\n return init.cache ? init : { ...init, cache: \"no-store\" };\n }\n\n /** POST JSON body; parse JSON response. */\n async postJSON<T>(\n path: string,\n body: unknown,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"POST\",\n headers: await this.jsonHeaders(),\n body: JSON.stringify(body),\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /** GET path; parse JSON response. */\n async getJSON<T>(\n path: string,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"GET\",\n headers: await this.authHeaders(),\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /** GET a public path: no Authorization header, so it never fetches a\n * token — in browser sign-in mode that would send a signed-out user to\n * the sign-in page just to read the model catalogue. */\n async getPublicJSON<T>(\n path: string,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchImpl(this.baseURL + path, {\n method: \"GET\",\n cache: \"no-store\",\n ...(init?.signal ? { signal: init.signal } : {}),\n });\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /** GET path; return raw bytes + content-type (for video/audio downloads)\n * and the ETag of those bytes, when the server sends one. */\n async getRaw(\n path: string,\n init?: { signal?: AbortSignal; ifNoneMatch?: string },\n ): Promise<{ data: Uint8Array; contentType: string; etag?: string; notModified?: boolean; serverTime?: number }> {\n const resp = await this.fetchWithAuthRetry(path, async () => {\n const headers: Record<string, string> = await this.authHeaders();\n if (init?.ifNoneMatch) headers[\"If-None-Match\"] = `\"${cleanETag(init.ifNoneMatch)}\"`;\n return { method: \"GET\", headers, signal: init?.signal };\n });\n const serverTime = parseServerDate(resp.headers.get(\"date\"));\n if (resp.status === 304) {\n const etag = cleanETag(resp.headers.get(\"etag\")) || init?.ifNoneMatch;\n return { data: new Uint8Array(0), contentType: \"\", etag, notModified: true, serverTime };\n }\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n const buf = new Uint8Array(await resp.arrayBuffer());\n const contentType = resp.headers.get(\"content-type\") ?? \"\";\n const etag = cleanETag(resp.headers.get(\"etag\")) || undefined;\n return { data: buf, contentType, etag, serverTime };\n }\n\n /** POST a multipart/form-data body. Used by audio.transcribe and image.edit. */\n async postMultipart<T>(\n path: string,\n form: FormData,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"POST\",\n headers: await this.authHeaders(), // do NOT set Content-Type — fetch sets it with boundary\n body: form,\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /**\n * POST JSON body; return the raw streaming Response. Used by chat\n * streaming. Caller is responsible for consuming the SSE body.\n */\n async postJSONStream(\n path: string,\n body: unknown,\n init?: { signal?: AbortSignal },\n ): Promise<Response> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"POST\",\n headers: {\n ...(await this.jsonHeaders()),\n \"Accept-Encoding\": \"identity\", // SSE lines arrive immediately, no gzip\n },\n body: JSON.stringify(body),\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return resp;\n }\n\n /** PUT raw bytes with caller-supplied content type and headers. */\n async putBytes<T>(\n path: string,\n body: BodyInit,\n opts?: {\n contentType?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n parse?: \"json\" | \"none\";\n },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => {\n const headers: Record<string, string> = {\n ...(await this.authHeaders()),\n \"Content-Type\": opts?.contentType ?? \"application/octet-stream\",\n };\n if (opts?.headers) Object.assign(headers, opts.headers);\n return {\n method: \"PUT\",\n headers,\n body,\n signal: opts?.signal,\n };\n });\n if (!resp.ok) throw await this.parseError(resp);\n if (opts?.parse === \"none\") return undefined as unknown as T;\n return (await resp.json()) as T;\n }\n\n /** DELETE path; parse JSON response (or no body). */\n async deletePath<T>(\n path: string,\n init?: { signal?: AbortSignal; parse?: \"json\" | \"none\" },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"DELETE\",\n headers: await this.authHeaders(),\n signal: init?.signal,\n }));\n if (!resp.ok) throw await this.parseError(resp);\n if (init?.parse === \"none\") return undefined as unknown as T;\n return (await resp.json()) as T;\n }\n\n /** HEAD path; surface status + selected response headers. Used by\n * exists / stat where the body would just be an opaque blob. */\n async head(\n path: string,\n init?: { signal?: AbortSignal },\n ): Promise<{ status: number; headers: Headers }> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"HEAD\",\n headers: await this.authHeaders(),\n signal: init?.signal,\n }));\n return { status: resp.status, headers: resp.headers };\n }\n\n private async jsonHeaders(): Promise<Record<string, string>> {\n return {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${await this.resolveToken()}`,\n };\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n return { Authorization: `Bearer ${await this.resolveToken()}` };\n }\n\n private async parseError(resp: Response): Promise<APIError> {\n const text = await resp.text();\n let type = \"error\";\n let message = text || resp.statusText;\n try {\n const parsed = JSON.parse(text) as { type?: string; message?: string };\n if (parsed.type) type = parsed.type;\n if (parsed.message) message = parsed.message;\n } catch {\n // Body wasn't JSON — fall back to raw text.\n }\n return new APIError(resp.status, type, message);\n }\n}\n\n/** The server's clock from a Date header, as ms since epoch. */\n/**\n * The bare ETag value. A CDN that compresses the response marks the\n * ETag weak (`W/\"…\"`); the value is still the object's, and it is what\n * `If-Match` must carry on the next write.\n */\nexport function cleanETag(raw: string | null | undefined): string {\n return (raw ?? \"\").trim().replace(/^W\\//i, \"\").replace(/\"/g, \"\");\n}\n\nexport function parseServerDate(h: string | null): number | undefined {\n if (!h) return undefined;\n const t = Date.parse(h);\n return Number.isNaN(t) ? undefined : t;\n}\n","/**\n * Typed runtime event bus for SDK call telemetry.\n *\n * Every LLM-ish service method (chat, embeddings, image, audio, video,\n * computer) emits start / streamDelta / end / error events on the\n * shared `client.events` bus so UI overlays (e.g. the bottom-right\n * identity badge with its activity ring) can render real-time state\n * without business code having to thread anything through.\n *\n * Goals:\n * - **Typed**: handler signatures are inferred from the event name.\n * - **Zero-cost when nobody listens**: no listeners → emit is a few\n * property reads. The browser `EventTarget` does the heavy lifting.\n * - **Service-agnostic**: events carry just enough context (id /\n * model / capability / token counts) for a generic UI to render\n * them without knowing which service fired the call.\n *\n * Why a custom wrapper instead of `EventTarget` directly:\n * - The native API is untyped (`addEventListener(\"foo\", anyHandler)`).\n * - Browser CustomEvent's `detail` payload trick is awkward to read.\n * - Subscribers want an `off()` closure, not a separate\n * `removeEventListener` call with the same callback reference.\n */\n\n/** Capability bucket of the calling service, mirroring /v1/models. */\nexport type SDKCapability =\n | \"text\"\n | \"vision\"\n | \"embedding\"\n | \"image_generation\"\n | \"image_edit\"\n | \"text_to_speech\"\n | \"speech_to_text\"\n | \"video_generation\"\n | \"computer_use\"\n /** A file-space call (`client.files`). Shown on the badge while in\n * flight and on failure, but not listed or charged per call. */\n | \"files\";\n\n/** Fired the moment a request hits the wire. */\nexport interface RequestStartEvent {\n /** Stable id correlating start → end/error events for one call. */\n id: string;\n /** Model name the caller asked for (NOT the upstream id). */\n model: string;\n /** Capability bucket — drives badge icon / colour. */\n capability: SDKCapability;\n /** ms since epoch when the request left the SDK. */\n startedAt: number;\n /** `true` for stream:true requests so the UI can pre-pick the\n * pulsing-ring animation instead of swapping mid-call. */\n streaming: boolean;\n}\n\n/** Fired on every stream chunk that carried new content tokens. */\nexport interface RequestStreamDeltaEvent {\n /** Matches the corresponding RequestStartEvent.id. */\n id: string;\n /** Approximate completion tokens in THIS chunk (one delta event\n * per visible SSE chunk; usage roll-up arrives in end). */\n deltaTokens: number;\n /** Cumulative completion tokens since stream start. */\n totalTokens: number;\n}\n\n/** Fired once when the call settles successfully. */\nexport interface RequestEndEvent {\n id: string;\n model: string;\n capability: SDKCapability;\n /** Wall-clock from start to settle. */\n latencyMs: number;\n /** Final completion tokens (matches the gateway's usage.completion_tokens\n * when the response carries it; 0 otherwise). */\n totalTokens: number;\n /** USD cost as reported by the gateway's `usage.total_cost`. May be\n * undefined for endpoints that don't return cost (rare). */\n costUSD?: number;\n /** OpenAI-style stop / length / tool_calls / content_filter, or\n * undefined for non-chat endpoints. */\n finishReason?: string;\n}\n\n/** Fired once when the call fails — at start, mid-stream, or anywhere. */\nexport interface RequestErrorEvent {\n id: string;\n model: string;\n capability: SDKCapability;\n latencyMs: number;\n /** The thrown Error (typically `APIError` with statusCode / body). */\n error: Error;\n /** HTTP status when known (forwarded from APIError when present). */\n statusCode?: number;\n}\n\n/** Compile-time map of event name → payload shape. Drives handler\n * inference in `on()` / `emit()` so callers can't subscribe to a\n * typo'd name or read a non-existent field. */\n/** Fired whenever Sync's state, pending count or last\n * sync time changes. See `client.sync`. */\nexport interface SyncStateEvent {\n state: \"off\" | \"idle\" | \"syncing\" | \"offline\" | \"error\";\n pending: number;\n lastSyncedAt?: number;\n /** When a sync last pushed or applied data (not set by a quiet pull). */\n lastChangeAt?: number;\n error?: string;\n}\n\n/** Fired after the sync wrote remote changes into a local object store,\n * in this tab and (through BroadcastChannel) in the app's other tabs.\n * Re-read the store to show the new data. */\nexport interface DbChangeEvent {\n db: string;\n store: string;\n keys: IDBValidKey[];\n cleared: boolean;\n /** \"remote\": another device's change, just applied locally.\n * \"tab\": another tab of this browser wrote to the shared database. */\n source: \"remote\" | \"tab\";\n}\n\nexport interface SDKEventMap {\n \"request:start\": RequestStartEvent;\n \"request:streamDelta\": RequestStreamDeltaEvent;\n \"request:end\": RequestEndEvent;\n \"request:error\": RequestErrorEvent;\n \"sync:state\": SyncStateEvent;\n \"db:change\": DbChangeEvent;\n}\n\n/** Disposer returned by `on()` — call to unsubscribe. */\nexport type Unsubscribe = () => void;\n\n/**\n * Lightweight typed event bus. Backed by `EventTarget` so memory\n * leaks from un-disposed listeners surface in the standard DevTools\n * memory profile path. In Node we fall back to a plain Map<string,\n * Set<handler>> since `EventTarget` was only added in Node 15+ and we\n * still support 14 in places (the SDK targets 18+ but tests sometimes\n * stub globals).\n */\nexport class SDKEvents {\n private readonly handlers = new Map<keyof SDKEventMap, Set<(payload: unknown) => void>>();\n\n /**\n * Subscribe to an event. Returns a disposer; calling the disposer\n * removes the subscription. Multiple subscriptions to the same\n * event are supported.\n */\n on<K extends keyof SDKEventMap>(name: K, handler: (event: SDKEventMap[K]) => void): Unsubscribe {\n let set = this.handlers.get(name);\n if (!set) {\n set = new Set();\n this.handlers.set(name, set);\n }\n const wrapped = handler as (payload: unknown) => void;\n set.add(wrapped);\n return () => {\n this.handlers.get(name)?.delete(wrapped);\n };\n }\n\n /**\n * Fire an event. Handlers run synchronously in subscription order.\n * Throws inside one handler don't prevent the others from running —\n * the bus catches and logs (console.error) so a buggy listener can't\n * take down the rest of the UI.\n */\n emit<K extends keyof SDKEventMap>(name: K, payload: SDKEventMap[K]): void {\n const set = this.handlers.get(name);\n if (!set || set.size === 0) return;\n for (const h of set) {\n try {\n h(payload);\n } catch (err) {\n // Listener bug — don't propagate, but surface in the console\n // so developers notice during integration.\n // eslint-disable-next-line no-console\n console.error(\"[runapps sdk] event listener for\", name, \"threw:\", err);\n }\n }\n }\n\n /** Drop all subscriptions. Tests call this between cases. */\n clear(): void {\n this.handlers.clear();\n }\n}\n\n/** Generate a short opaque ID for correlating start/end events.\n * Not a UUID — sub-microsecond uniqueness within a session is\n * enough, and we want to keep the wire log compact. */\nexport function newRequestId(): string {\n return (\n \"req_\" +\n Date.now().toString(36) +\n \"_\" +\n Math.random().toString(36).slice(2, 8)\n );\n}\n","/**\n * Tiny wrapper used by every LLM-ish service to fire\n * `request:start` → `request:end` / `request:error` events around a\n * single network call. Centralised here so the service files don't\n * duplicate the try/catch + latency-stopwatch pattern N times.\n *\n * Streaming chat has its own logic (it must yield deltas mid-flight)\n * — see `wrapStream` below.\n */\n\nimport { newRequestId, type SDKCapability, type SDKEvents } from \"./events.js\";\n\ninterface CallMeta {\n model: string;\n capability: SDKCapability;\n}\n\ninterface EndInfo {\n totalTokens?: number;\n costUSD?: number;\n finishReason?: string;\n}\n\n/**\n * Run `fn` while emitting start/end/error events around it.\n * `extractEnd` plucks token / cost / finish_reason off the response\n * shape so each service can map its own response type without the\n * helper knowing about it.\n *\n * Re-throws on error so the caller still sees the rejection — the\n * helper only inserts telemetry, it doesn't swallow.\n */\nexport async function wrapEvents<T>(\n events: SDKEvents,\n meta: CallMeta,\n fn: () => Promise<T>,\n extractEnd?: (r: T) => EndInfo,\n): Promise<T> {\n const id = newRequestId();\n const startedAt = Date.now();\n events.emit(\"request:start\", {\n id,\n model: meta.model,\n capability: meta.capability,\n startedAt,\n streaming: false,\n });\n try {\n const result = await fn();\n const info = extractEnd ? extractEnd(result) : {};\n events.emit(\"request:end\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n totalTokens: info.totalTokens ?? 0,\n ...(info.costUSD !== undefined && { costUSD: info.costUSD }),\n ...(info.finishReason !== undefined && { finishReason: info.finishReason }),\n });\n return result;\n } catch (err) {\n const e = err as Error & { statusCode?: number };\n events.emit(\"request:error\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n error: e,\n ...(typeof e.statusCode === \"number\" && { statusCode: e.statusCode }),\n });\n throw err;\n }\n}\n\n/**\n * Token-count estimator for stream deltas. Stream chunks carry text,\n * not tokens — getting an exact count would mean shipping a\n * tokenizer in the SDK (huge), so we use the standard ~4-chars =\n * 1-token English heuristic. Off by 10-30% for code or non-English,\n * but the badge UI shows tokens/sec as a vibe indicator, not for\n * billing, so the imprecision is fine.\n */\nexport function estimateTokens(text: string): number {\n if (!text) return 0;\n return Math.max(1, Math.round(text.length / 4));\n}\n\n/**\n * Stream wrapper — same start/end/error envelope as wrapEvents but\n * also fires `request:streamDelta` for each yielded chunk so the\n * badge's tokens/sec sliding window has samples.\n *\n * Generic over the chunk type because each service's stream\n * (chat / video / etc.) carries its own shape. The caller passes a\n * `extractDelta` that pulls text + cumulative usage off the chunk.\n *\n * Usage:\n * yield* wrapStream(events, {model, capability}, () => transport.postJSONStream(...),\n * (chunk) => ({ deltaText: chunk.choices[0]?.delta?.content, usage: chunk.usage }));\n */\nexport async function* wrapStream<TChunk>(\n events: SDKEvents,\n meta: CallMeta,\n source: () => AsyncIterable<TChunk>,\n extractDelta: (chunk: TChunk) => {\n deltaText?: string;\n // When a chunk carries cumulative usage (final chunk in OpenAI\n // streams), surface it so the end event can use exact totals\n // instead of the heuristic running sum.\n completionTokens?: number;\n costUSD?: number;\n finishReason?: string;\n },\n): AsyncGenerator<TChunk, void, unknown> {\n const id = newRequestId();\n const startedAt = Date.now();\n events.emit(\"request:start\", {\n id,\n model: meta.model,\n capability: meta.capability,\n startedAt,\n streaming: true,\n });\n let totalTokens = 0;\n let lastUsageTokens: number | undefined;\n let costUSD: number | undefined;\n let finishReason: string | undefined;\n try {\n for await (const chunk of source()) {\n const info = extractDelta(chunk);\n if (info.deltaText) {\n const delta = estimateTokens(info.deltaText);\n totalTokens += delta;\n events.emit(\"request:streamDelta\", {\n id,\n deltaTokens: delta,\n totalTokens,\n });\n }\n if (info.completionTokens !== undefined) lastUsageTokens = info.completionTokens;\n if (info.costUSD !== undefined) costUSD = info.costUSD;\n if (info.finishReason !== undefined) finishReason = info.finishReason;\n yield chunk;\n }\n events.emit(\"request:end\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n // Prefer the exact upstream count over our heuristic when it\n // arrived in the final chunk.\n totalTokens: lastUsageTokens ?? totalTokens,\n ...(costUSD !== undefined && { costUSD }),\n ...(finishReason !== undefined && { finishReason }),\n });\n } catch (err) {\n const e = err as Error & { statusCode?: number };\n events.emit(\"request:error\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n error: e,\n ...(typeof e.statusCode === \"number\" && { statusCode: e.statusCode }),\n });\n throw err;\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents, wrapStream } from \"./event-wrap.js\";\nimport { APIError } from \"./errors.js\";\n\n/* ------------------------------------------------------------------ */\n/* Request types */\n/* ------------------------------------------------------------------ */\n\n/**\n * A single message in a chat conversation. Content is a string for plain\n * text, or an array of `ContentPart` for multi-modal messages.\n */\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | ContentPart[] | null;\n name?: string;\n tool_call_id?: string;\n tool_calls?: ChatToolCall[];\n}\n\n/** One block inside a multi-modal message content array.\n *\n * Multi-modal LLMs see content as a heterogeneous list — some mix\n * text + images (gpt-4o, claude), some accept video frames or raw\n * audio (Gemini 2.x). The shape mirrors OpenAI's chat completions\n * schema and is forwarded verbatim to the gateway; unsupported\n * variants are rejected server-side with a clear error rather than\n * silently ignored. */\nexport type ContentPart =\n | { type: \"text\"; text: string }\n | { type: \"image_url\"; image_url: { url: string; detail?: \"auto\" | \"low\" | \"high\" } }\n | { type: \"video_url\"; video_url: { url: string } }\n | { type: \"audio_url\"; audio_url: { url: string } };\n\nexport interface ChatTool {\n type: \"function\";\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n };\n}\n\nexport interface StreamOptions {\n include_usage: boolean;\n}\n\n/**\n * Names of platform-executed tools the SDK can ask the gateway to run on\n * its behalf during a chat completion. When you set `server_tools` on a\n * `ChatCompletionParams`, the gateway resolves each name to its tool\n * schema, sends both server tools and your own `tools` (if any) to the\n * model, then handles any server-tool calls itself — looping with the\n * model until it produces a final answer. Your code only sees the final\n * response; you don't have to implement web search yourself.\n *\n * The whitelist is intentionally narrow. For image, audio, file, and\n * vision generation the SDK already exposes dedicated endpoints\n * (`client.image.*`, `client.audio.*`, etc.) that callers should hit\n * directly instead of paying for an LLM-in-the-middle. Search-class\n * tools live here because they need the model in the loop — \"search →\n * read result → fetch → answer\" can't be expressed as a single call.\n */\nexport type ServerToolName = \"web_search\" | \"web_fetch\" | \"twitter_search\";\n\n/**\n * One of the app's private prompts, kept out of the browser. The\n * gateway reads `<id>.md` from the app's bundle (`bundle.json` or\n * `.private/prompts/`), fills `{{var}}` placeholders from `vars`, and\n * uses the result: as the system message for chat, or as the whole\n * text for image, video and speech calls, where it takes the place of\n * `prompt` / `input`. `template` sends the text inline instead — for a\n * creator's local dev server, before a bundle exists; the gateway\n * accepts it only from the app's creator.\n */\nexport interface PromptRef {\n id?: string;\n vars?: Record<string, unknown>;\n template?: string;\n}\n\n/**\n * What a prompt field takes: one reference, or a list of references and\n * plain texts joined in order by a blank line — a private style guide\n * followed by the user's own words, or a persona and a rules prompt kept\n * as two files. At most 16 parts.\n */\nexport type PromptInput = PromptRef | Array<PromptRef | string>;\n\n/** Typed constants for {@link ServerToolName} so you get autocomplete. */\nexport const ServerTools = {\n WebSearch: \"web_search\",\n WebFetch: \"web_fetch\",\n TwitterSearch: \"twitter_search\",\n} as const;\n\nexport interface ChatCompletionParams {\n model: string;\n messages: ChatMessage[];\n temperature?: number;\n top_p?: number;\n max_tokens?: number;\n stop?: string[];\n frequency_penalty?: number;\n presence_penalty?: number;\n n?: number;\n tools?: ChatTool[];\n tool_choice?: unknown;\n user?: string;\n metadata?: Record<string, unknown>;\n stream?: boolean;\n stream_options?: StreamOptions;\n /**\n * Names of platform-executed tools — the gateway will run these for\n * you and loop with the model until it produces a final answer. Mix\n * freely with your own `tools`: the model can call either; the\n * platform handles the server ones, your code handles the rest.\n */\n server_tools?: ServerToolName[];\n /**\n * Maximum number of LLM round-trips the server-tool loop will make\n * (default 5, hard cap 10). Only meaningful when `server_tools` is\n * set. Tune up for chains like \"search → fetch → answer\", down for\n * tighter cost guarantees.\n */\n max_server_iterations?: number;\n /**\n * One of the app's private prompts, kept out of the browser: the\n * gateway reads `bundle.json (or .private/prompts/)<id>.md` from the app's bundle, fills\n * `{{var}}` placeholders from `vars`, and prepends it as the system\n * message. `template` sends the text inline instead — for a creator's\n * local dev server, before a bundle exists; the gateway accepts it\n * only from the app's creator.\n */\n prompt?: PromptInput;\n /** Pass-through for provider-specific knobs. */\n [extra: string]: unknown;\n}\n\n/* ------------------------------------------------------------------ */\n/* Response types */\n/* ------------------------------------------------------------------ */\n\nexport interface ChatToolCall {\n index?: number;\n id?: string;\n type?: string;\n function: {\n name: string;\n arguments: string;\n };\n}\n\nexport interface ChatChoiceMessage {\n role: string;\n content: string | null;\n tool_calls?: ChatToolCall[];\n}\n\nexport interface ChatChoice {\n index: number;\n message: ChatChoiceMessage;\n finish_reason: string;\n}\n\nexport interface ChatCompletion {\n id: string;\n object: string;\n created: number;\n model: string;\n choices: ChatChoice[];\n usage: Usage;\n}\n\nexport interface ChatChunkDelta {\n role?: string;\n content?: string;\n tool_calls?: ChatToolCall[];\n}\n\nexport interface ChatChunkChoice {\n index: number;\n delta: ChatChunkDelta;\n finish_reason?: string | null;\n}\n\nexport interface ChatCompletionChunk {\n id: string;\n object: string;\n created: number;\n model: string;\n choices: ChatChunkChoice[];\n usage?: Usage;\n}\n\n/* ------------------------------------------------------------------ */\n/* Convenience builders */\n/* ------------------------------------------------------------------ */\n\nexport const userMessage = (content: string): ChatMessage =>\n ({ role: \"user\", content });\n\nexport const systemMessage = (content: string): ChatMessage =>\n ({ role: \"system\", content });\n\nexport const assistantMessage = (content: string): ChatMessage =>\n ({ role: \"assistant\", content });\n\nexport const toolResultMessage = (toolCallId: string, content: string): ChatMessage =>\n ({ role: \"tool\", tool_call_id: toolCallId, content });\n\nexport const userMessageParts = (...parts: ContentPart[]): ChatMessage =>\n ({ role: \"user\", content: parts });\n\nexport const textPart = (text: string): ContentPart => ({ type: \"text\", text });\n\nexport const imagePart = (\n url: string,\n detail?: \"auto\" | \"low\" | \"high\",\n): ContentPart => ({\n type: \"image_url\",\n image_url: detail ? { url, detail } : { url },\n});\n\n/**\n * Video content part. Only supported by chat models whose\n * input_modalities includes `\"video\"` (currently Gemini 3.x). Other\n * models 400 at the gateway. URL may be http(s) or a\n * `data:video/mp4;base64,...` inline upload.\n */\nexport const videoPart = (url: string): ContentPart => ({\n type: \"video_url\",\n video_url: { url },\n});\n\n/**\n * Audio content part. Only supported by chat models whose\n * input_modalities includes `\"audio\"` (currently Gemini 3.x). Other\n * models 400 at the gateway. URL may be http(s) or a\n * `data:audio/wav;base64,...` inline upload.\n */\nexport const audioPart = (url: string): ContentPart => ({\n type: \"audio_url\",\n audio_url: { url },\n});\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n\nexport class ChatService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Create a chat completion (non-streaming).\n *\n * Streaming is forced off — pass `stream()` instead for incremental output.\n */\n async create(\n params: ChatCompletionParams,\n init?: { signal?: AbortSignal },\n ): Promise<ChatCompletion> {\n const body = { ...params, stream: false };\n return wrapEvents(\n this.events,\n { model: params.model, capability: \"text\" },\n () =>\n this.transport.postJSON<ChatCompletion>(\n \"/v1/chat/completions\",\n body,\n init,\n ),\n (r) => ({\n totalTokens: r.usage?.completion_tokens,\n costUSD: r.usage?.total_cost,\n finishReason: r.choices?.[0]?.finish_reason,\n }),\n );\n }\n\n /**\n * Create a streaming chat completion. Returns an async iterator over\n * `ChatCompletionChunk`. The final chunk carries `usage` (token / cost\n * totals) — `stream_options.include_usage` is set automatically.\n *\n * ```ts\n * let cost = 0;\n * for await (const chunk of client.chat.stream({ ... })) {\n * for (const c of chunk.choices) process.stdout.write(c.delta.content ?? \"\");\n * if (chunk.usage) cost = chunk.usage.total_cost;\n * }\n * ```\n *\n * Also fires `request:streamDelta` on `client.events` for each chunk\n * that carried text — drives the badge's tokens/sec rate display.\n */\n async *stream(\n params: ChatCompletionParams,\n init?: { signal?: AbortSignal },\n ): AsyncGenerator<ChatCompletionChunk, void, unknown> {\n const body = {\n ...params,\n stream: true,\n stream_options: params.stream_options ?? { include_usage: true },\n };\n const sourceGen = async function* (this: ChatService) {\n const resp = await this.transport.postJSONStream(\n \"/v1/chat/completions\",\n body,\n init,\n );\n if (!resp.body) {\n throw new Error(\"runapps: streaming response had no body\");\n }\n yield* parseSSE<ChatCompletionChunk>(resp.body);\n }.bind(this);\n yield* wrapStream(\n this.events,\n { model: params.model, capability: \"text\" },\n sourceGen,\n (chunk) => ({\n deltaText: chunk.choices?.[0]?.delta?.content ?? \"\",\n completionTokens: chunk.usage?.completion_tokens,\n costUSD: chunk.usage?.total_cost,\n finishReason: chunk.choices?.[0]?.finish_reason ?? undefined,\n }),\n );\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* SSE parsing */\n/* ------------------------------------------------------------------ */\n\n/**\n * Decode an `text/event-stream` body into a stream of parsed JSON chunks.\n * Stops at the `[DONE]` sentinel. Lines that don't start with `data: ` are\n * ignored (keep-alive comments, etc.).\n */\nasync function* parseSSE<T>(\n body: ReadableStream<Uint8Array>,\n): AsyncGenerator<T, void, unknown> {\n const decoder = new TextDecoder();\n const reader = body.getReader();\n let buffer = \"\";\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, nl).replace(/\\r$/, \"\");\n buffer = buffer.slice(nl + 1);\n if (!line.startsWith(\"data: \")) continue;\n const data = line.slice(6).trim();\n if (data === \"[DONE]\") return;\n if (!data) continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n } catch (e) {\n throw new Error(\n `runapps: decode stream chunk: ${(e as Error).message}`,\n );\n }\n // The gateway reports mid-stream failures as an SSE error event\n // (`data: {\"error\":{...}}`) followed by [DONE]. Such an event has no\n // \"choices\", so without this it would surface as an empty chunk and\n // the stream would end looking merely empty.\n const err = (parsed as { error?: { code?: number; type?: string; message?: string } })\n .error;\n if (err) {\n throw new APIError(\n err.code ?? 0,\n err.type ?? \"upstream_error\",\n err.message ?? data,\n );\n }\n yield parsed as T;\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n","import type { Transport } from \"./transport.js\";\n\n/**\n * One model available on the gateway. Pricing is in **pips** (1 USD =\n * 1,000,000 pips), per million tokens — multiply by tokens / 10⁶ for the\n * billed pip cost, or by tokens / 10¹² for USD.\n */\nexport interface Model {\n id: string;\n object: string;\n capability: string;\n provider?: string;\n /**\n * Server-derived \"what this model can actually do\" tags\n * (e.g. {id:\"t2v\", label:\"Text-to-Video\"}, {id:\"i2v\", label:\"Image-to-Video\"}).\n * Use `hasCapabilityTag` for stable filter checks; iterate for display.\n * Labels are in English — frontends localise on their side.\n */\n capability_tags?: Tag[];\n /**\n * Accepted input modalities for chat models. Omitted on capabilities\n * where it doesn't apply (image generation, TTS, etc.). Mirrors\n * Anthropic / Gemini's own `inputModalities` field — operator-set on\n * the gateway, surfaced verbatim here.\n *\n * Canonical values: `\"text\"`, `\"image\"`, `\"video\"`, `\"audio\"`. The\n * list is open — future modalities ride through without an SDK\n * update. Use `acceptsModality` for the stable filter check.\n *\n * @example\n * const videoCapable = models.filter(m => acceptsModality(m, \"video\"));\n */\n input_modalities?: string[];\n options?: Record<string, unknown>;\n input_price_per_mtok: number;\n output_price_per_mtok: number;\n fixed_price: boolean;\n fixed_cost: number;\n max_tokens: number;\n max_input_tokens: number;\n icon_url?: string;\n available_voices?: string[];\n}\n\n/**\n * One capability tag. Mirrors options_schema.Tag on the gateway. IDs\n * are stable across releases; Labels are subject to translation.\n */\nexport interface Tag {\n id: string;\n label: string;\n}\n\n/** True iff `model.capability_tags` includes a tag with the given stable ID. */\nexport const hasCapabilityTag = (model: Model, id: string): boolean =>\n Array.isArray(model.capability_tags) &&\n model.capability_tags.some((t) => t.id === id);\n\n/**\n * True iff the chat model accepts the given input modality. Lets you\n * filter by `\"image\"` / `\"video\"` / `\"audio\"` (or `\"text\"`, always\n * true for chat models that set the field at all) without remembering\n * whether the gateway uses canonical or aliased names.\n *\n * Returns `false` when `input_modalities` is unset — text-only is the\n * conservative default.\n *\n * @example\n * const videoModels = (await client.models.list())\n * .filter(m => acceptsModality(m, \"video\"));\n */\nexport const acceptsModality = (model: Model, modality: string): boolean =>\n Array.isArray(model.input_modalities) &&\n model.input_modalities.includes(modality);\n\nexport interface ModelListOptions {\n /** Filter to one capability (e.g. \"text\", \"vision\", \"image\", \"tts\"). */\n capability?: string;\n}\n\n/** True iff the gateway flag in `options[key]` is set. Accepts bool or numeric 1. */\nfunction optBool(model: Model, key: string): boolean {\n const v = model.options?.[key];\n if (typeof v === \"boolean\") return v;\n if (typeof v === \"number\") return v !== 0;\n return false;\n}\n\n/** Convenience: TTS model accepts a reference audio clip as `voice`? */\nexport const supportsVoiceClone = (model: Model): boolean =>\n optBool(model, \"supports_voice_clone\");\n\n/** Convenience: TTS model accepts a free-form `instruct_text` directive? */\nexport const supportsInstructText = (model: Model): boolean =>\n optBool(model, \"supports_instruct_text\");\n\n/** Convenience: admin-set default voice for a TTS model, or null. */\nexport const defaultVoice = (model: Model): string | null => {\n const v = model.options?.[\"default_voice\"];\n return typeof v === \"string\" ? v : null;\n};\n\nexport class ModelsService {\n constructor(private readonly transport: Transport) {}\n\n /**\n * List models available on the gateway. Pass `{ capability: \"...\" }` to\n * filter server-side. Needs no sign-in or key.\n */\n async list(\n opts: ModelListOptions = {},\n init?: { signal?: AbortSignal },\n ): Promise<Model[]> {\n let path = \"/v1/models\";\n if (opts.capability) {\n path += `?capability=${encodeURIComponent(opts.capability)}`;\n }\n // The catalogue is public: no token, so listing models never sends a\n // signed-out user to sign in.\n const resp = await this.transport.getPublicJSON<{ data: Model[] }>(path, init);\n return resp.data;\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { PromptInput } from \"./chat.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/**\n * Image generation request.\n *\n * Sizing: prefer the 2-axis `resolution` + `aspect_ratio` controls —\n * the gateway translates them into a concrete WxH from the target\n * model's declared size enum. `size` is the legacy explicit override;\n * when set, it wins over `(resolution, aspect_ratio)`. When none of\n * the three is set, the gateway / upstream applies its own default.\n */\nexport interface ImageGenerateParams {\n /** The prompt text, a private prompt `{ id, vars }`, or a list of\n * those and texts joined in order (see `PromptInput`). */\n prompt: string | PromptInput;\n /** Legacy explicit \"WxH\" override. Prefer `resolution` +\n * `aspect_ratio` for new code. */\n size?: string;\n /** Short-side pixel tier — \"512P\" | \"1K\" | \"2K\" | \"4K\".\n * Model-specific subset; `/v1/models` advertises which tiers each\n * image model supports. */\n resolution?: string;\n /** Output shape — \"1:1\" | \"9:16\" | \"16:9\" | \"3:4\" | \"4:3\" | \"3:2\"\n * | \"2:3\" | \"5:4\" | \"4:5\" | \"21:9\" | \"adaptive\". Model-specific\n * subset; see `/v1/models`. */\n aspect_ratio?: string;\n n?: number;\n style?: string;\n reference_image_urls?: string[];\n user?: string;\n /**\n * Pass-through for provider-specific knobs.\n *\n * Note: `quality` (OpenAI's render-quality preset\n * — \"low\"/\"medium\"/\"high\"/\"auto\") was removed from the canonical\n * surface. Only one upstream used it and its own default (\"auto\") is\n * already optimal; the field caused naming confusion with the\n * \"画质\" resolution-tier control. The gateway silently drops it\n * from inbound requests, so legacy callers keep working.\n */\n [extra: string]: unknown;\n}\n\n/**\n * Single generated image. `url` carries the bytes via one of two\n * transport modes — `<img src={url}>` works for both:\n * - \"https://api.runapps.ai/v1/blobs/<id>\" — async / hosted blob\n * - \"data:<mime>;base64,<payload>\" — sync / inline\n *\n * `decodeMediaUrl(url)` resolves either shape into `Uint8Array` +\n * mime type when raw bytes are wanted (file save, post-process).\n *\n * `size` is the actual dimensions of this image — Seedream sequential\n * generation may produce results different from the requested size.\n */\nexport interface ImageResult {\n url: string;\n revised_prompt?: string;\n size?: string;\n /**\n * Human-readable credit line that the client SHOULD display\n * alongside the image. Populated by stock-library models (Pexels)\n * whose terms of service require crediting the photographer;\n * AI-generation models leave it empty. Render verbatim — don't\n * try to parse the format.\n *\n * Example: \"Photo by Jane Doe on Pexels (https://www.pexels.com/photo/12345/)\"\n */\n attribution?: string;\n}\n\nexport interface ImageUsage extends Usage {\n generated_images?: number;\n output_tokens?: number;\n total_tokens?: number;\n /** Tool name → invocation count (e.g. Seedream 5.0 lite web_search). */\n tool_usage?: Record<string, number>;\n}\n\nexport interface ImageResponse {\n created: number;\n data: ImageResult[];\n usage: ImageUsage;\n}\n\n/**\n * Status payload returned by the resumable async-image endpoints\n * (`/v1/async/images/generations` and friends). Returned verbatim by\n * `submitGenerate()` (job is still queued / running), and again by\n * `getAsyncStatus()` (status may now be terminal — `data[]` is\n * populated when `status === 'succeeded'`).\n *\n * The fully-managed `generateAsync()` hides this type behind an\n * `ImageResponse`; `submitGenerate` + `getAsyncStatus` expose it for\n * callers who need to PERSIST the job id (e.g. resume polling across\n * a page refresh, drive their own UI progress, etc.).\n */\nexport interface AsyncImageJob {\n id: string;\n status: \"queued\" | \"running\" | \"succeeded\" | \"failed\" | (string & {});\n data?: ImageResult[];\n usage?: ImageUsage;\n error?: string;\n /** Unix epoch seconds — same semantics as VideoStatus.created_at /\n * current_time. Pair to compute server-truth elapsed. */\n created_at?: number;\n current_time?: number;\n}\n\n/** Minimal interface for an image / mask file passed to `edit()`. */\nexport interface ImageFileInput {\n /** File contents — Blob, ArrayBuffer, Uint8Array, or string. */\n data: Blob | ArrayBuffer | Uint8Array | string;\n /** File name including extension (sent in the multipart field). */\n filename: string;\n /** Optional MIME type — inferred from the extension when omitted. */\n contentType?: string;\n}\n\nexport interface ImageEditParams {\n image: ImageFileInput;\n mask?: ImageFileInput;\n /** The prompt text, a private prompt `{ id, vars }`, or a list. */\n prompt: string | PromptInput;\n size?: string;\n n?: number;\n user?: string;\n}\n\n/* ------------------------------------------------------------------ */\n\nexport class ImageService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Generate images from a text prompt via the synchronous endpoint.\n * For long-running requests (large Seedream batches, slow upstreams)\n * use `generateAsync()` to avoid 100-second origin timeouts.\n */\n async generate(\n model: string,\n params: ImageGenerateParams,\n init?: { signal?: AbortSignal },\n ): Promise<ImageResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"image_generation\" },\n () =>\n this.transport.postJSON<ImageResponse>(\n \"/v1/images/generations\",\n { model, ...params },\n init,\n ),\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Async equivalent of `generate()`. Submits a job to\n * `/v1/async/images/generations`, polls `/v1/async/images/generations/:id`\n * until terminal, then resolves to a sync-shape `ImageResponse`. Use\n * this when a request may exceed the ~100s origin timeout (Cloudflare\n * otherwise replaces the real upstream error with its own 502 page).\n *\n * Implementation is `submitGenerate` + `getAsyncStatus` poll loop, so\n * the path layout matches the rest of the async API (`/v1/async/...`\n * everywhere). The caller's `signal` aborts both the submit and the\n * poll wait; `pollIntervalMs` defaults to 2s.\n */\n async generateAsync(\n model: string,\n params: ImageGenerateParams,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n ): Promise<ImageResponse> {\n const interval = init?.pollIntervalMs ?? 2000;\n const signal = init?.signal;\n return wrapEvents(\n this.events,\n { model, capability: \"image_generation\" },\n async () => {\n const job = await this.transport.postJSON<AsyncImageJob>(\n \"/v1/async/images/generations\",\n { model, ...params },\n { signal },\n );\n let current = job;\n while (current.status !== \"succeeded\" && current.status !== \"failed\") {\n await sleep(interval, signal);\n current = await this.getAsyncStatus(current.id, { signal });\n }\n if (current.status === \"failed\") {\n throw new Error(current.error ?? \"image generation failed\");\n }\n return {\n created: current.created_at ?? Math.floor(Date.now() / 1000),\n data: current.data ?? [],\n usage: current.usage ?? { total_cost: 0 },\n } satisfies ImageResponse;\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Submit-only variant of `generate()`. Returns the gateway's job\n * descriptor (`id`, `status`, `usage`) without polling. Use this\n * when you want to PERSIST the job id and resume polling later\n * (e.g. across page reloads, in a separate worker process). Pair\n * with `getAsyncStatus(id)` to drive the polling loop yourself.\n *\n * For a one-shot \"submit and wait\" call site, use `generateAsync()`\n * — that wraps the same underlying endpoint with a built-in poll.\n *\n * Telemetry note: only the SUBMIT step fires start/end on\n * `client.events` — the per-call latency reflects the gateway\n * accepting the job, NOT how long the upstream takes to render.\n * Track render progress via your own poll loop's status responses.\n */\n async submitGenerate(\n model: string,\n params: ImageGenerateParams,\n init?: { signal?: AbortSignal },\n ): Promise<AsyncImageJob> {\n return wrapEvents(\n this.events,\n { model, capability: \"image_generation\" },\n () =>\n this.transport.postJSON<AsyncImageJob>(\n \"/v1/async/images/generations\",\n { model, ...params },\n init,\n ),\n );\n }\n\n /**\n * Single poll of an in-flight async image job. Returns the same\n * shape `submitGenerate()` returned, with `data[]` populated once\n * `status` flips to `succeeded`. Caller decides cadence + bail-out;\n * the gateway's per-poll `current_time` / `created_at` together\n * give a server-truth elapsed reading regardless of client clock skew.\n */\n async getAsyncStatus(\n id: string,\n init?: { signal?: AbortSignal },\n ): Promise<AsyncImageJob> {\n return this.transport.getJSON<AsyncImageJob>(\n `/v1/async/images/generations/${encodeURIComponent(id)}`,\n init,\n );\n }\n\n /**\n * Edit an image (inpaint / outpaint / variations) via multipart upload.\n * Provider-dependent — only available on `gpt-image-1` and similar.\n */\n async edit(\n model: string,\n params: ImageEditParams,\n init?: { signal?: AbortSignal },\n ): Promise<ImageResponse> {\n const form = buildEditForm(model, params);\n return wrapEvents(\n this.events,\n { model, capability: \"image_edit\" },\n () =>\n this.transport.postMultipart<ImageResponse>(\n \"/v1/images/edits\",\n form,\n init,\n ),\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new Error(\"aborted\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error(\"aborted\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nfunction buildEditForm(model: string, params: ImageEditParams): FormData {\n const form = new FormData();\n form.set(\"model\", model);\n form.set(\"prompt\", typeof params.prompt === \"string\" ? params.prompt : JSON.stringify(params.prompt));\n if (params.size) form.set(\"size\", params.size);\n if (params.n !== undefined) form.set(\"n\", String(params.n));\n if (params.user) form.set(\"user\", params.user);\n form.set(\"image\", toBlob(params.image), params.image.filename);\n if (params.mask) form.set(\"mask\", toBlob(params.mask), params.mask.filename);\n return form;\n}\n\n/** Internal: coerce ImageFileInput to a Blob the multipart layer accepts. */\nfunction toBlob(input: ImageFileInput): Blob {\n const ct = input.contentType ?? guessMime(input.filename);\n if (input.data instanceof Blob) {\n return input.data.type === ct ? input.data : new Blob([input.data], { type: ct });\n }\n if (typeof input.data === \"string\") {\n return new Blob([input.data], { type: ct });\n }\n // ArrayBuffer | Uint8Array — wrap in Blob. Cast widens TS's strict\n // Uint8Array<ArrayBuffer> back to BlobPart (it's accepted at runtime).\n return new Blob([input.data as BlobPart], { type: ct });\n}\n\nfunction guessMime(filename: string): string {\n const ext = filename.toLowerCase().split(\".\").pop() ?? \"\";\n switch (ext) {\n case \"png\":\n return \"image/png\";\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \"webp\":\n return \"image/webp\";\n case \"gif\":\n return \"image/gif\";\n default:\n return \"application/octet-stream\";\n }\n}\n","/**\n * Media helpers shared by image / video / chat-multimodal endpoints.\n *\n * The gateway accepts media payloads as `data:<mime>;base64,<payload>`\n * URIs on every `*_url` field — `encodeImageUrl` is the bridge from\n * raw bytes to that wire format. Replaces the deprecated `*_b64` SDK\n * fields, which were removed in favour of routing every payload\n * through `*_url`.\n */\n\n/**\n * Wrap raw image bytes as a `data:<mime>;base64,<payload>` URI.\n *\n * MIME is sniffed from the magic bytes (PNG / JPEG / GIF / WebP). On\n * unrecognised input we default to `application/octet-stream` — most\n * upstreams accept and re-sniff server-side.\n *\n * @param bytes Image bytes — `Uint8Array`, `ArrayBuffer`, or `Blob`.\n * @returns A data: URI suitable for any `*_url` field on the gateway.\n *\n * @example\n * ```ts\n * import { encodeImageUrl } from \"@runappsai/sdk\";\n * const png = await fs.readFile(\"frame.png\");\n * await client.video.generate(\"Veo 3.1\", {\n * prompt: \"...\",\n * first_frame_url: encodeImageUrl(png),\n * });\n * ```\n */\nexport function encodeImageUrl(\n bytes: Uint8Array | ArrayBuffer | Blob,\n): string {\n // Normalize to Uint8Array so the rest of the function is uniform.\n if (bytes instanceof Blob) {\n throw new Error(\n \"encodeImageUrl: Blob input requires async; await blob.arrayBuffer() and pass the result instead\",\n );\n }\n const u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\n const mime = sniffImageMime(u8);\n\n // btoa(String.fromCharCode(...u8)) blows the stack on large arrays;\n // chunk to keep arg count bounded. 0x8000 is the conventional safe\n // limit on JS engines.\n const CHUNK = 0x8000;\n let binary = \"\";\n for (let i = 0; i < u8.length; i += CHUNK) {\n binary += String.fromCharCode.apply(\n null,\n Array.from(u8.subarray(i, i + CHUNK)),\n );\n }\n // btoa is browser-native and shimmed in Node ≥ 16 globalThis.\n const b64 = (globalThis as { btoa?: (s: string) => string }).btoa\n ? (globalThis as { btoa: (s: string) => string }).btoa(binary)\n : Buffer.from(binary, \"binary\").toString(\"base64\");\n\n return `data:${mime};base64,${b64}`;\n}\n\n/**\n * Resolve a media URL to raw bytes plus the declared mime label.\n *\n * Inverse of `encodeImageUrl`. Two transport modes handled:\n * - \"data:<mime>;base64,<payload>\" — decode the inline payload\n * - \"https://...\", \"http://...\" — `fetch()` + `.arrayBuffer()`,\n * mime from the response's\n * `Content-Type` header\n *\n * Returns `{bytes: Uint8Array, contentType: string}`. Pairs with\n * `encodeImageUrl` so callers can round-trip bytes through the gateway\n * without thinking about which transport mode the response chose.\n *\n * @example\n * ```ts\n * import { decodeMediaUrl } from \"@runappsai/sdk\";\n * const job = await client.image.generateAsync(\"Seedream 5.0\", {...});\n * const { bytes, contentType } = await decodeMediaUrl(job.data[0].url);\n * await fs.writeFile(\"out.png\", bytes);\n * ```\n */\nexport async function decodeMediaUrl(\n url: string,\n): Promise<{ bytes: Uint8Array; contentType: string }> {\n if (url.startsWith(\"data:\")) {\n const rest = url.slice(5);\n const semi = rest.indexOf(\";\");\n const comma = rest.indexOf(\",\");\n if (semi === -1 || comma === -1 || comma < semi) {\n throw new Error(\"decodeMediaUrl: malformed data URI\");\n }\n const mime = rest.slice(0, semi);\n const b64 = rest.slice(comma + 1);\n return { bytes: base64ToBytes(b64), contentType: mime };\n }\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {\n const resp = await fetch(url);\n if (!resp.ok) {\n throw new Error(`decodeMediaUrl: upstream ${resp.status}`);\n }\n const buf = await resp.arrayBuffer();\n return {\n bytes: new Uint8Array(buf),\n contentType: resp.headers.get(\"Content-Type\") ?? \"\",\n };\n }\n throw new Error(`decodeMediaUrl: unsupported url scheme: ${url.slice(0, 32)}`);\n}\n\n/** Decode standard base64 to Uint8Array. Browser `atob` + Node `Buffer`\n * fallback — same impl as the audio service uses internally. */\nfunction base64ToBytes(b64: string): Uint8Array {\n if (typeof Buffer !== \"undefined\") {\n return new Uint8Array(Buffer.from(b64, \"base64\"));\n }\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\n/**\n * Sniff a small set of image mimes from magic bytes. Mirrors the\n * gateway's own sniff so encode / decode round-trip lands on the same\n * label.\n */\nfunction sniffImageMime(b: Uint8Array): string {\n if (b.length >= 8 &&\n b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) {\n return \"image/png\";\n }\n if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) {\n return \"image/jpeg\";\n }\n if (b.length >= 6 &&\n b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38) {\n return \"image/gif\";\n }\n if (b.length >= 12 &&\n b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 &&\n b[8] === 0x57 && b[9] === 0x45 && b[10] === 0x42 && b[11] === 0x50) {\n return \"image/webp\";\n }\n return \"application/octet-stream\";\n}\n","import type { Transport } from \"./transport.js\";\nimport type { PromptInput } from \"./chat.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\nimport { decodeMediaUrl } from \"./media.js\";\n\n/* ------------------------------------------------------------------ */\n/* TTS */\n/* ------------------------------------------------------------------ */\n/* */\n/* Voice metadata (id / name / gender / preview_url / supported */\n/* emotions) is no longer fetched through a dedicated endpoint — */\n/* every text_to_speech row on /v1/models carries its full catalog */\n/* under `options.voices` and `options.supported_emotions`. Use */\n/* `client.models.get(name)` (or the entry from `client.models.list`) */\n/* and read those fields off `model.options`. */\n/* ------------------------------------------------------------------ */\n\nexport interface SpeechParams {\n /** The text to speak, a private prompt `{ id, vars }`, or a list of\n * those and texts joined in order (see `PromptInput`). */\n input: string | PromptInput;\n /** Named voice id (`alloy`, `nova`, …; provider-dependent). Omit\n * when supplying `reference_audio_url` for zero-shot voice\n * cloning — the cloned timbre overrides any named voice. */\n voice?: string;\n response_format?: string;\n speed?: number;\n /** Provider-specific. Inspect the chosen TTS model's\n * `options.supported_emotions` (via `client.models.get(name)`) for\n * the legal enum values. */\n emotion?: string;\n /** -12 … 12 semitones (provider-dependent). */\n pitch?: number;\n /** 0.1 … 10.0 — 1.0 = normal. */\n volume?: number;\n /** -12 … 12 — voice timbre shift (provider-dependent). */\n timber?: number;\n /**\n * Free-form natural-language directive for voiceclone (CosyVoice).\n * e.g. \"用四川话快速地说\"\n */\n instruct_text?: string;\n /**\n * Public URL of a reference clip whose timbre to match (zero-shot\n * voice cloning). When set, voiceclone-capable models ignore `voice`\n * and synthesize matching the referenced audio.\n */\n reference_audio_url?: string;\n /** Transcript of the reference clip — improves CosyVoice quality. */\n reference_text?: string;\n user?: string;\n /**\n * Vendor-specific top-level fields the canonical params don't model.\n * Used for music-generation models on the TTS bucket — ACE-Step needs\n * `tags` (genre, required), `duration`, `seed`, `scheduler`, etc.\n * The SDK spreads these at the request body's TOP LEVEL (not nested\n * under \"extra\") to match the gateway's extractSpeechExtra contract.\n * Canonical fields above always win over extra keys with the same name.\n */\n extra?: Record<string, unknown>;\n}\n\nexport interface SpeechResponse {\n /** Decoded audio bytes. */\n data: Uint8Array;\n /** MIME type reported by the gateway (e.g. \"audio/mpeg\"). */\n contentType: string;\n usage: Usage;\n}\n\n/* ------------------------------------------------------------------ */\n/* STT */\n/* ------------------------------------------------------------------ */\n\n/** Same shape as image.ImageFileInput — kept independent so callers can import either. */\nexport interface AudioFileInput {\n data: Blob | ArrayBuffer | Uint8Array | string;\n filename: string;\n contentType?: string;\n}\n\nexport interface TranscribeParams {\n file: AudioFileInput;\n language?: string;\n prompt?: string;\n response_format?: string;\n timestamp_granularities?: (\"segment\" | \"word\")[];\n user?: string;\n}\n\nexport interface TranscribeResponse {\n text: string;\n usage: Usage;\n /** Raw upstream payload — segment timings, etc., when present. */\n raw?: Record<string, unknown>;\n}\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n\nexport class AudioService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n // (Removed: listVoices — fetch voice metadata via client.models.get /\n // client.models.list and read model.options.voices /\n // model.options.supported_emotions.)\n\n /**\n * Generate speech from text. Output bytes are base64-decoded by the SDK\n * — `response.data` is a `Uint8Array` you can write directly to disk:\n *\n * ```ts\n * await fs.writeFile(\"out.mp3\", await client.audio.speech(\"OpenAI/TTS\", {\n * input: \"Hello!\",\n * voice: \"nova\",\n * }).then(r => r.data));\n * ```\n */\n async speech(\n model: string,\n params: SpeechParams,\n init?: { signal?: AbortSignal },\n ): Promise<SpeechResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"text_to_speech\" },\n async () => {\n // Wire shape: {audio_url: \"data:<mime>;base64,...\", usage: ...}.\n const raw = await this.transport.postJSON<{\n audio_url: string;\n usage: Usage;\n }>(\n \"/v1/audio/speech\",\n buildSpeechBody(model, params),\n init,\n );\n const { bytes, contentType } = await decodeMediaUrl(raw.audio_url);\n return { data: bytes, contentType, usage: raw.usage };\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Async equivalent of `speech()`. Submits the job to the gateway's\n * async TTS endpoint, polls every ~3s until terminal, then decodes\n * the result audio_url. Use this when a request may exceed the\n * ~100s origin timeout — ACE-Step music generation at high quality,\n * large CosyVoice batches, etc. The sync `speech()` method is the\n * right choice for short-clip TTS that fits inside Cloudflare's\n * timeout (skips the submit + poll round-trips).\n *\n * Returns the same `SpeechResponse` shape as `speech()` — `data` +\n * `usage` — so callers can swap the two methods with no other change.\n *\n * Caller's `signal` deadline bounds the poll wait. Without one,\n * an internal 10-minute cap applies.\n */\n async speechAsync(\n model: string,\n params: SpeechParams,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n ): Promise<SpeechResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"text_to_speech\" },\n async () => {\n const submit = await this.transport.postJSON<{ id: string; status: string }>(\n \"/v1/async/audio/speech\",\n buildSpeechBody(model, params),\n init,\n );\n if (!submit.id) {\n throw new Error(\"runapps: speech submit response missing job id\");\n }\n return waitSpeechJob(this.transport, submit.id, init);\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /** Transcribe audio to text via multipart upload. */\n async transcribe(\n model: string,\n params: TranscribeParams,\n init?: { signal?: AbortSignal },\n ): Promise<TranscribeResponse> {\n const form = buildTranscribeForm(model, params);\n return wrapEvents(\n this.events,\n { model, capability: \"speech_to_text\" },\n () =>\n this.transport.postMultipart<TranscribeResponse>(\n \"/v1/audio/transcriptions\",\n form,\n init,\n ),\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Async equivalent of `transcribe()`. Submits the upload, polls\n * every ~3s until terminal, returns the same `TranscribeResponse`.\n * Use this for long audio (lectures, podcasts, multi-hour\n * recordings) where Whisper can take minutes — well past\n * Cloudflare's ~100s sync ceiling.\n *\n * Caller's `signal` deadline bounds the poll wait. Without one,\n * an internal 10-minute cap applies.\n */\n async transcribeAsync(\n model: string,\n params: TranscribeParams,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n ): Promise<TranscribeResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"speech_to_text\" },\n async () => {\n const form = buildTranscribeForm(model, params);\n const submit = await this.transport.postMultipart<{ id: string; status: string }>(\n \"/v1/async/audio/transcriptions\",\n form,\n init,\n );\n if (!submit.id) {\n throw new Error(\"runapps: transcribe submit response missing job id\");\n }\n return waitTranscribeJob(this.transport, submit.id, init);\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\n/**\n * Polls the gateway's async speech-status endpoint until the job\n * reaches a terminal state, then decodes the result `audio_url`\n * data: URI into raw bytes + mime. Used by `AudioService.speechAsync`.\n *\n * Default poll cadence: first poll 2s after submit, then every 3s.\n * Caller can override via `init.pollIntervalMs`. `init.signal`\n * cancels the poll loop.\n */\nasync function waitSpeechJob(\n transport: Transport,\n jobId: string,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n): Promise<SpeechResponse> {\n const path = `/v1/async/audio/speech/${encodeURIComponent(jobId)}`;\n const interval = init?.pollIntervalMs ?? 3000;\n // First poll happens fast — typical TTS already done in 2-5s.\n await sleep(2000, init?.signal);\n // 10-minute hard cap if caller didn't supply a signal with a\n // narrower deadline. Long enough for the slowest ACE-Step jobs.\n const startedAt = Date.now();\n const internalDeadline = startedAt + 10 * 60 * 1000;\n\n for (;;) {\n if (init?.signal?.aborted) {\n throw new DOMException(\"speech poll aborted\", \"AbortError\");\n }\n if (!init?.signal && Date.now() > internalDeadline) {\n throw new Error(\"runapps: speech job timed out (10 min internal cap)\");\n }\n const status = await transport.getJSON<{\n id: string;\n status: string;\n audio_url?: string;\n usage?: Usage;\n error?: string;\n }>(path, init);\n if (status.status === \"succeeded\") {\n if (!status.audio_url) {\n throw new Error(\"runapps: speech job succeeded but no audio_url returned\");\n }\n const { bytes, contentType } = await decodeMediaUrl(status.audio_url);\n return {\n data: bytes,\n contentType,\n usage: status.usage ?? { total_cost: 0 },\n };\n }\n if (status.status === \"failed\") {\n throw new Error(status.error || \"runapps: speech job failed\");\n }\n // queued | running → wait + retry.\n await sleep(interval, init?.signal);\n }\n}\n\n/**\n * Polls the gateway's async transcribe-status endpoint until terminal,\n * then assembles a TranscribeResponse from the final payload. Used by\n * `AudioService.transcribeAsync`.\n */\nasync function waitTranscribeJob(\n transport: Transport,\n jobId: string,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n): Promise<TranscribeResponse> {\n const path = `/v1/async/audio/transcriptions/${encodeURIComponent(jobId)}`;\n const interval = init?.pollIntervalMs ?? 3000;\n await sleep(2000, init?.signal);\n const internalDeadline = Date.now() + 10 * 60 * 1000;\n for (;;) {\n if (init?.signal?.aborted) {\n throw new DOMException(\"transcribe poll aborted\", \"AbortError\");\n }\n if (!init?.signal && Date.now() > internalDeadline) {\n throw new Error(\"runapps: transcribe job timed out (10 min internal cap)\");\n }\n const status = await transport.getJSON<Record<string, unknown>>(path, init);\n switch (status.status) {\n case \"succeeded\":\n return assembleTranscribeResponse(status);\n case \"failed\":\n throw new Error((status.error as string) || \"runapps: transcribe job failed\");\n case \"queued\":\n case \"running\":\n await sleep(interval, init?.signal);\n break;\n default:\n throw new Error(`runapps: unknown transcribe job status ${JSON.stringify(status.status)}`);\n }\n }\n}\n\n/**\n * Pulls canonical text + usage out of a succeeded transcribe-status\n * payload, leaves the rest under .raw — matches the sync response\n * shape produced by `transcribe()`.\n */\nfunction assembleTranscribeResponse(payload: Record<string, unknown>): TranscribeResponse {\n const out: TranscribeResponse = {\n text: (payload.text as string) ?? \"\",\n usage: (payload.usage as Usage) ?? { total_cost: 0 },\n };\n const raw: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(payload)) {\n if (k === \"text\" || k === \"usage\" || k === \"id\" || k === \"status\") continue;\n raw[k] = v;\n }\n if (Object.keys(raw).length > 0) {\n out.raw = raw;\n }\n return out;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(new DOMException(\"aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * buildSpeechBody assembles the wire body for /v1/audio/speech (and\n * its async sibling): `{model, ...canonical, ...extra}` flattened at\n * the top level. The gateway's extractSpeechExtra peels non-canonical\n * keys back into req.Extra, so the round-trip is symmetric. Canonical\n * fields always win over `extra` keys of the same name — never let\n * Extra silently override a typed field.\n */\nfunction buildSpeechBody(model: string, params: SpeechParams): Record<string, unknown> {\n const { extra, ...canonical } = params;\n const body: Record<string, unknown> = { model, ...canonical };\n if (extra) {\n for (const [k, v] of Object.entries(extra)) {\n if (k in body) continue;\n body[k] = v;\n }\n }\n return body;\n}\n\nfunction buildTranscribeForm(model: string, params: TranscribeParams): FormData {\n const form = new FormData();\n form.set(\"model\", model);\n if (params.language) form.set(\"language\", params.language);\n if (params.prompt) form.set(\"prompt\", params.prompt);\n if (params.response_format) form.set(\"response_format\", params.response_format);\n if (params.timestamp_granularities) {\n for (const g of params.timestamp_granularities) {\n form.append(\"timestamp_granularities[]\", g);\n }\n }\n if (params.user) form.set(\"user\", params.user);\n\n const ct = params.file.contentType ?? guessAudioMime(params.file.filename);\n const blob =\n params.file.data instanceof Blob\n ? params.file.data\n : new Blob([params.file.data as BlobPart], { type: ct });\n form.set(\"file\", blob, params.file.filename);\n return form;\n}\n\nfunction guessAudioMime(filename: string): string {\n const ext = filename.toLowerCase().split(\".\").pop() ?? \"\";\n switch (ext) {\n case \"mp3\":\n return \"audio/mpeg\";\n case \"wav\":\n return \"audio/wav\";\n case \"m4a\":\n return \"audio/mp4\";\n case \"flac\":\n return \"audio/flac\";\n case \"ogg\":\n return \"audio/ogg\";\n case \"webm\":\n return \"audio/webm\";\n default:\n return \"application/octet-stream\";\n }\n}\n\n// (base64ToBytes was here — promoted to media.ts as part of\n// decodeMediaUrl. The audio path now goes through that single helper\n// so image and audio decode through one implementation.)\n","import type { Transport } from \"./transport.js\";\nimport type { PromptInput } from \"./chat.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/* ------------------------------------------------------------------ */\n/* Request */\n/* ------------------------------------------------------------------ */\n\n/**\n * Video generation params. Most fields map 1:1 to the gateway's\n * `/v1/videos/generations` body. Optional booleans (`generate_audio`,\n * `watermark`, `camera_fixed`, `return_last_frame`, `draft`) are\n * tri-state — omit to use the upstream default; explicit `false`\n * forces off.\n */\nexport interface VideoGenerateParams {\n /** The prompt text, a private prompt `{ id, vars }`, or a list of\n * those and texts joined in order (see `PromptInput`). */\n prompt: string | PromptInput;\n aspect_ratio?: string;\n duration?: number;\n resolution?: string;\n generate_audio?: boolean;\n\n /**\n * First / last frame keyframes (image-to-video). Each accepts:\n * - hosted https:// URL\n * - data:image/...;base64,<payload> URI (use `encodeImageUrl`)\n *\n * The previous `first_frame_b64` / `last_frame_b64` sibling fields\n * were removed — wrap raw bytes in a data: URI client-side via\n * `encodeImageUrl` and pass through `*_url` instead.\n */\n first_frame_url?: string;\n last_frame_url?: string;\n\n /**\n * Multimodal reference inputs (Seedance 2.0). Each entry can be a\n * hosted URL or a data: URI (use `encodeImageUrl` for raw bytes).\n * `reference_images_b64` was removed — fold any raw-base64 entries\n * into `reference_image_urls` as data: URIs.\n */\n reference_image_urls?: string[];\n reference_video_urls?: string[];\n reference_audio_urls?: string[];\n\n /** Single-input drivers (video-edit / motion-transfer / lip-sync). */\n source_video_url?: string;\n source_image_url?: string;\n source_audio_url?: string;\n\n watermark?: boolean;\n camera_fixed?: boolean;\n return_last_frame?: boolean;\n seed?: number;\n\n /** Alternative to `duration` for Seedance 1.0 pro / lite. */\n frames?: number;\n\n /** Seedance 1.5 pro Draft Mode. */\n draft?: boolean;\n draft_task_id?: string;\n\n /** \"flex\" = offline (~50% price); pair with execution_expires_after. */\n service_tier?: string;\n execution_expires_after?: number;\n\n /** Webhook for state-transition notifications. */\n callback_url?: string;\n user?: string;\n\n /** Pass-through for provider-specific knobs. */\n [extra: string]: unknown;\n}\n\n/* ------------------------------------------------------------------ */\n/* Response */\n/* ------------------------------------------------------------------ */\n\nexport interface VideoTask {\n id: string;\n status: string;\n usage: Usage;\n}\n\nexport interface VideoUsageTokens {\n completion_tokens?: number;\n total_tokens?: number;\n}\n\nexport interface VideoStatus {\n id: string;\n status: string;\n progress: number;\n video_url?: string;\n error?: string;\n /** Final frame as a separate image — populated when return_last_frame=true. */\n last_frame_url?: string;\n /** Seedance Draft Mode Step1 task id, exposed when the draft succeeds. */\n draft_task_id?: string;\n duration?: number;\n fps?: number;\n resolution?: string;\n ratio?: string;\n seed?: number;\n service_tier?: string;\n /** Upstream-reported tokens (Seedance bills per output token). */\n usage_tokens?: VideoUsageTokens;\n created_at?: number;\n updated_at?: number;\n /**\n * Human-readable credit line that the client SHOULD display alongside\n * the video. Populated by stock-library models (Pexels) whose terms\n * of service require crediting the contributor; AI generators leave\n * it empty. Render verbatim — don't try to parse the format. See\n * ImageResult.attribution for the same convention.\n */\n attribution?: string;\n}\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n\nexport interface WaitOptions {\n /** Defaults to 5000ms. */\n pollIntervalMs?: number;\n signal?: AbortSignal;\n}\n\nexport class VideoService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /** Submit a video generation task.\n *\n * Telemetry note: only the submit step fires start/end on\n * `client.events` — the per-call latency reflects the gateway\n * accepting the job, NOT how long upstream takes to render. The\n * caller's poll loop sees status flips but the SDK doesn't emit\n * end events for those (would otherwise leave the badge \"active\"\n * for as long as the upstream renders, sometimes minutes). */\n async generate(\n model: string,\n params: VideoGenerateParams,\n init?: { signal?: AbortSignal },\n ): Promise<VideoTask> {\n return wrapEvents(\n this.events,\n { model, capability: \"video_generation\" },\n () =>\n this.transport.postJSON<VideoTask>(\n \"/v1/videos/generations\",\n { model, ...params },\n init,\n ),\n );\n }\n\n /** Fetch the current status of a task. */\n async getStatus(\n taskID: string,\n init?: { signal?: AbortSignal },\n ): Promise<VideoStatus> {\n return this.transport.getJSON<VideoStatus>(\n `/v1/videos/generations/${encodeURIComponent(taskID)}`,\n init,\n );\n }\n\n /**\n * Poll `getStatus()` until the task reaches a terminal state\n * (`succeeded` / `failed`). Default poll interval is 5s.\n */\n async wait(taskID: string, opts: WaitOptions = {}): Promise<VideoStatus> {\n const interval = opts.pollIntervalMs ?? 5000;\n while (true) {\n const status = await this.getStatus(taskID, { signal: opts.signal });\n if (status.status === \"succeeded\" || status.status === \"failed\") {\n return status;\n }\n await sleep(interval, opts.signal);\n }\n }\n\n /** Download the raw video bytes for a completed task. */\n async getContent(\n taskID: string,\n init?: { signal?: AbortSignal },\n ): Promise<{ data: Uint8Array; contentType: string }> {\n return this.transport.getRaw(\n `/v1/videos/${encodeURIComponent(taskID)}/content`,\n init,\n );\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new Error(\"aborted\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error(\"aborted\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n","import type { Transport } from \"./transport.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/** Single-step computer-use request — a screenshot + history → next action(s). */\nexport interface ComputerStepParams {\n /**\n * Conversation history including screenshots and tool results. Each\n * entry has `role` + `content` (string or array of blocks). Format is\n * intentionally opaque to support both Anthropic and OpenAI computer-use\n * protocols — the gateway adapts.\n */\n messages: Record<string, unknown>[];\n /** Screen resolution. Defaults to 1920×1080 if omitted. */\n display_width?: number;\n display_height?: number;\n max_tokens?: number;\n enable_zoom?: boolean;\n /** OpenAI Responses-API state chaining. */\n previous_response_id?: string;\n /** Follow-up `computer_call_output` for OpenAI's Responses API. */\n openai_input?: unknown;\n user?: string;\n}\n\n/** One block of a computer-use response: text, tool_use (Anthropic), or computer_call (OpenAI). */\nexport interface ComputerContentBlock {\n type: string;\n text?: string;\n id?: string;\n name?: string;\n /** Anthropic tool input (raw JSON). */\n input?: unknown;\n call_id?: string;\n /** OpenAI computer-call action. */\n action?: Record<string, unknown>;\n}\n\nexport interface ComputerResponse {\n content: ComputerContentBlock[];\n stop_reason?: string;\n usage: Usage;\n response_id?: string;\n /** \"anthropic\" | \"openai\" — which protocol the upstream returned. */\n protocol?: string;\n}\n\nexport class ComputerService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Execute one step of a computer-use agent loop. Given a screenshot\n * and conversation history, returns the next action(s) the model\n * wants the caller to execute.\n */\n async step(\n model: string,\n params: ComputerStepParams,\n init?: { signal?: AbortSignal },\n ): Promise<ComputerResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"computer_use\" },\n () =>\n this.transport.postJSON<ComputerResponse>(\n \"/v1/computer/step\",\n { model, ...params },\n init,\n ),\n (r) => ({\n totalTokens: r.usage?.completion_tokens,\n costUSD: r.usage?.total_cost,\n }),\n );\n }\n}\n","// Per-project file system at /v1/files/*. Files are stored under\n// (user, project) on the gateway and addressed by POSIX-style paths.\n// Each FileObject's `url` is a stable public address — embed it\n// directly in `<img src>`, share it, persist it.\n//\n// The bundle's RunApps client uses `client.files` to access these\n// endpoints; the underlying token must be a project-bound resource\n// token (rrt_*) issued by the grant flow.\n\nimport { cleanETag, type Transport } from \"./transport.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\nexport interface FileObject {\n path: string;\n size: number;\n content_type?: string;\n etag?: string;\n last_modified?: string;\n url: string;\n}\n\nexport interface FileListResult {\n files: FileObject[];\n next_cursor?: string;\n}\n\nexport interface PutOptions {\n /** Override the MIME type guessed from the path extension. */\n contentType?: string;\n /** Refuse to overwrite an existing object (gateway returns 409). */\n ifNoneMatch?: boolean;\n /**\n * Write only if the stored object still has this ETag — the one\n * `read()` / `stat()` / a previous `put()` returned. Someone else\n * having written in between makes the gateway answer 412 instead\n * of silently overwriting their version; re-read, merge, put again.\n */\n ifMatch?: string;\n signal?: AbortSignal;\n}\n\n/** The bytes at a path together with the version they are. */\nexport interface FileRead {\n blob: Blob;\n contentType: string;\n /** Pass back as `ifMatch` to write only if nobody else has since. */\n etag?: string;\n}\n\nexport interface ListOptions {\n prefix?: string;\n cursor?: string;\n limit?: number;\n /**\n * Shell-style pattern, post-filtered after the underlying S3 list:\n * * any chars except `/`\n * ** any chars including `/`\n * ? single non-slash char\n * [abc] character class\n *\n * Examples:\n * client.files.list({ glob: \"*.png\" })\n * client.files.list({ glob: \"projects/*\\/assets/*.png\" })\n * client.files.list({ prefix: \"projects/\", glob: \"**\\/*.wav\" })\n *\n * S3 has no native glob — the gateway extracts the longest literal\n * head of the pattern as the underlying prefix scan, then filters\n * the rest in process. Patterns must match the WHOLE path; no\n * implicit anchoring needed.\n */\n glob?: string;\n signal?: AbortSignal;\n}\n\nexport interface PutFromURLOptions {\n contentType?: string;\n ifNoneMatch?: boolean;\n ifMatch?: string;\n signal?: AbortSignal;\n}\n\nexport type BatchOp =\n | { op: \"put_url\"; path: string; src_url: string; content_type?: string; if_none_match?: boolean; if_match?: string }\n | { op: \"del\"; path: string }\n | { op: \"move\"; from: string; to: string }\n | { op: \"copy\"; from: string; to: string }\n | { op: \"exists\"; path: string }\n | { op: \"stat\"; path: string };\n\nexport interface BatchResult {\n ok: boolean;\n error?: string;\n object?: FileObject;\n exists?: boolean;\n}\n\nfunction pathToURL(base: string, p: string): string {\n const trimmed = p.replace(/^\\/+/, \"\");\n return base + \"/\" + trimmed.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\nexport class FilesService {\n /**\n * URL prefix for every file call.\n *\n * When the client was constructed with `project`, requests go to the\n * project-scoped form — `/v1/p/<project>/files/...` — instead of the\n * bare `/v1/files/...`.\n *\n * This matters because the unscoped URL is byte-identical across apps:\n * two bundles built from the same template both read\n * `projects/index.json`, so anything keying on URL alone (starting\n * with the browser's HTTP cache, which ignores request headers and is\n * partitioned by registrable domain, not by subdomain) cannot tell\n * them apart. Putting the project in the path makes the URL unique\n * per app. The gateway still authorizes purely from the token and\n * rejects a scope that disagrees with it, so this is a correctness\n * measure, not a permission one.\n */\n private readonly base: string;\n\n constructor(\n private readonly transport: Transport,\n project?: string,\n /** When given, every call emits request:start / end / error so the\n * badge can show it in flight; the sync engine's own file traffic\n * uses a service without one. */\n private readonly events?: SDKEvents,\n ) {\n this.base = project\n ? \"/v1/p/\" + encodeURIComponent(project) + \"/files\"\n : \"/v1/files\";\n }\n\n /** Run one file call, with badge events when the service has a bus. */\n private run<T>(op: string, fn: () => Promise<T>): Promise<T> {\n return this.events ? wrapEvents(this.events, { model: op, capability: \"files\" }, fn) : fn();\n }\n\n /**\n * Upload `body` to `path`. Returns the resulting FileObject — its\n * `url` is the stable public address for that path.\n *\n * `body` accepts anything `fetch` accepts as a request body: Blob,\n * File, ArrayBuffer, ArrayBufferView, FormData (rare here),\n * ReadableStream, or a string.\n */\n async put(\n path: string,\n body: BodyInit,\n opts?: PutOptions,\n ): Promise<FileObject> {\n const headers: Record<string, string> = {};\n if (opts?.ifNoneMatch) headers[\"If-None-Match\"] = \"*\";\n if (opts?.ifMatch) headers[\"If-Match\"] = `\"${cleanETag(opts.ifMatch)}\"`;\n return this.run(\"files.put\", () => this.transport.putBytes<FileObject>(pathToURL(this.base, path), body, {\n contentType: opts?.contentType,\n headers: Object.keys(headers).length ? headers : undefined,\n signal: opts?.signal,\n }));\n }\n\n /** Convenience: upload string content with sensible content-type. */\n async putString(\n path: string,\n content: string,\n opts?: PutOptions,\n ): Promise<FileObject> {\n return this.put(path, content, {\n ...opts,\n contentType: opts?.contentType ?? \"text/plain; charset=utf-8\",\n });\n }\n\n /**\n * Download the bytes at `path` as a Blob. Use `URL.createObjectURL`\n * to get a temporary local URL, or persist the bytes elsewhere.\n * Bundles that just need to render an asset should use the FileObject's\n * `.url` directly instead — that's a stable public URL with no token.\n */\n async get(path: string, init?: { signal?: AbortSignal }): Promise<Blob> {\n return (await this.read(path, init)).blob;\n }\n\n /**\n * Like `get`, but also returns the ETag of the bytes, so a later\n * `put(path, body, { ifMatch: etag })` writes only if the file is\n * still the version that was read.\n */\n async read(path: string, init?: { signal?: AbortSignal; ifNoneMatch?: string }): Promise<FileRead> {\n return this.run(\"files.read\", () => this.readRaw(path, init));\n }\n\n private async readRaw(path: string, init?: { signal?: AbortSignal; ifNoneMatch?: string }): Promise<FileRead> {\n const { data, contentType, etag, notModified } = await this.transport.getRaw(pathToURL(this.base, path), init);\n return {\n blob: new Blob([new Uint8Array(data)], { type: contentType || \"application/octet-stream\" }),\n contentType,\n etag,\n ...(notModified ? { notModified: true } : {}),\n };\n }\n\n /**\n * Read a JSON file and its version in one call. A missing file is\n * `{ value: undefined, etag: undefined }`, so a first save can pass\n * `ifNoneMatch: true` and a later one `ifMatch: etag`.\n */\n async readJSON<T = unknown>(\n path: string,\n init?: { signal?: AbortSignal; ifNoneMatch?: string },\n ): Promise<{ value: T | undefined; etag?: string; notModified?: boolean }> {\n let r: FileRead & { notModified?: boolean };\n try {\n r = await this.read(path, init);\n } catch (e) {\n if ((e as { statusCode?: number; status?: number })?.statusCode === 404 ||\n (e as { statusCode?: number; status?: number })?.status === 404) {\n return { value: undefined, etag: undefined };\n }\n throw e;\n }\n if (r.notModified) return { value: undefined, etag: r.etag, notModified: true };\n return { value: JSON.parse(await r.blob.text()) as T, etag: r.etag };\n }\n\n /** Stat: HEAD-only object metadata. */\n async stat(path: string, init?: { signal?: AbortSignal }): Promise<FileObject> {\n const { status, headers } = await this.run(\"files.stat\", () => this.transport.head(pathToURL(this.base, path), init));\n if (status === 404) {\n throw Object.assign(new Error(\"not found\"), { status: 404 });\n }\n if (status >= 400) {\n throw Object.assign(new Error(\"stat failed\"), { status });\n }\n const size = parseInt(headers.get(\"content-length\") ?? \"0\", 10);\n return {\n path: headers.get(\"x-file-path\") ?? path,\n size,\n content_type: headers.get(\"content-type\") ?? undefined,\n etag: (headers.get(\"etag\") ?? \"\").replace(/\"/g, \"\") || undefined,\n url: headers.get(\"x-file-url\") ?? \"\",\n };\n }\n\n /** Returns true iff the path resolves to an existing object. */\n async exists(path: string, init?: { signal?: AbortSignal }): Promise<boolean> {\n const { status } = await this.run(\"files.exists\", () => this.transport.head(pathToURL(this.base, path), init));\n return status >= 200 && status < 400;\n }\n\n /** Delete the object at `path`. Idempotent. */\n async del(path: string, init?: { signal?: AbortSignal }): Promise<void> {\n await this.run(\"files.del\", () => this.transport.deletePath<{ ok: boolean }>(pathToURL(this.base, path), init));\n }\n\n /** List objects. Pass an empty / omitted prefix for the root. */\n async list(opts?: ListOptions): Promise<FileListResult> {\n const params = new URLSearchParams();\n if (opts?.prefix) params.set(\"prefix\", opts.prefix);\n if (opts?.cursor) params.set(\"cursor\", opts.cursor);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n if (opts?.glob) params.set(\"glob\", opts.glob);\n const qs = params.toString();\n return this.run(\"files.list\", () => this.transport.getJSON<FileListResult>(\n this.base + (qs ? \"?\" + qs : \"\"),\n opts?.signal ? { signal: opts.signal } : undefined,\n ));\n }\n\n /**\n * Bulk delete: removes every object whose path starts with `prefix`\n * AND (when set) matches `glob`. Returns the count of deleted\n * objects. Refuses an empty prefix + empty glob — the caller must\n * be explicit about wiping the project namespace.\n *\n * client.files.deleteMany({ prefix: \"tmp/\" }) // wipe a directory\n * client.files.deleteMany({ glob: \"**\\/*.tmp\" }) // wipe by pattern\n * client.files.deleteMany({ prefix: \"logs/\", glob: \"*.bak\" })\n */\n async deleteMany(\n opts: { prefix?: string; glob?: string },\n init?: { signal?: AbortSignal },\n ): Promise<{ deleted: number }> {\n return this.run(\"files.deleteMany\", () => this.transport.postJSON<{ deleted: number }>(\n this.base + \"/delete\",\n { prefix: opts.prefix ?? \"\", glob: opts.glob ?? \"\" },\n init,\n ));\n }\n\n /** Atomic rename (copy + delete server-side). */\n async move(from: string, to: string, init?: { signal?: AbortSignal }): Promise<FileObject> {\n return this.run(\"files.move\", () => this.transport.postJSON<FileObject>(this.base + \"/move\", { from, to }, init));\n }\n\n /** Server-side copy. */\n async copy(from: string, to: string, init?: { signal?: AbortSignal }): Promise<FileObject> {\n return this.run(\"files.copy\", () => this.transport.postJSON<FileObject>(this.base + \"/copy\", { from, to }, init));\n }\n\n /**\n * Ask the gateway to fetch `srcURL` and store the body at `path`.\n * Saves a round trip vs downloading then re-uploading. `srcURL`\n * must be a public http(s) URL (private/loopback hosts rejected).\n */\n async putFromURL(\n path: string,\n srcURL: string,\n opts?: PutFromURLOptions,\n ): Promise<FileObject> {\n return this.run(\"files.putFromURL\", () => this.transport.postJSON<FileObject>(\n this.base + \"/put-url\",\n {\n path,\n src_url: srcURL,\n content_type: opts?.contentType,\n if_none_match: opts?.ifNoneMatch,\n if_match: opts?.ifMatch,\n },\n opts?.signal ? { signal: opts.signal } : undefined,\n ));\n }\n\n /**\n * Pipeline several file ops in one round trip. Operations execute\n * sequentially server-side; one op failing does not abort the rest.\n * Inspect each result.ok individually.\n *\n * Auto-chunks: the gateway caps a single batch at 64 ops and returns\n * 400 \"too many ops\" above that. This method splits long input into\n * 30-op chunks, awaits each chunk in order, and concatenates the\n * results — so callers can hand it 1000 ops in a single call.\n * Chunks run serially (not in parallel) to preserve the\n * \"operations execute in submission order\" contract.\n */\n async batch(\n ops: BatchOp[],\n init?: { signal?: AbortSignal },\n ): Promise<BatchResult[]> {\n if (ops.length === 0) return [];\n return this.run(\"files.batch\", () => this.batchRaw(ops, init));\n }\n\n private async batchRaw(ops: BatchOp[], init?: { signal?: AbortSignal }): Promise<BatchResult[]> {\n const chunkSize = 30;\n if (ops.length <= chunkSize) {\n const resp = await this.transport.postJSON<{ results: BatchResult[] }>(\n this.base + \"/batch\",\n { ops },\n init,\n );\n return resp.results;\n }\n const all: BatchResult[] = [];\n for (let i = 0; i < ops.length; i += chunkSize) {\n const chunk = ops.slice(i, i + chunkSize);\n const resp = await this.transport.postJSON<{ results: BatchResult[] }>(\n this.base + \"/batch\",\n { ops: chunk },\n init,\n );\n all.push(...resp.results);\n }\n return all;\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/* ------------------------------------------------------------------ */\n/* Embeddings — POST /v1/embeddings */\n/* ------------------------------------------------------------------ */\n/* */\n/* OpenAI-compatible vector embedding endpoint. Wire shape mirrors */\n/* OpenAI's `/v1/embeddings` exactly so callers porting from the */\n/* official `openai` SDK can swap the import line and keep working. */\n/* ------------------------------------------------------------------ */\n\nexport interface EmbeddingsParams {\n /** Single string OR array of strings — gateway forwards either\n * shape verbatim. Passing a string returns `data: [{...}]` (1\n * element), passing an array returns one element per input. */\n input: string | string[];\n /** OpenAI per-end-user observability tag. */\n user?: string;\n /** \"float\" (default) or \"base64\". `base64` packs vectors as\n * little-endian float32 + base64-encoded — saves bandwidth for\n * high-D embeddings (3072-D × 1000 docs is ~3× smaller than the\n * JSON float-array form). */\n encoding_format?: \"float\" | \"base64\";\n /** Truncate the output vector. text-embedding-3-* only — other\n * models reject. */\n dimensions?: number;\n}\n\nexport interface Embedding {\n object: \"embedding\";\n /** Vector. When `encoding_format` was \"float\" (default), this is\n * `number[]`. When \"base64\", this is the encoded `string`. */\n embedding: number[] | string;\n index: number;\n}\n\n/** OpenAI's usage block plus the gateway's billing extension\n * (`total_cost` in USD).\n *\n * Shape parallels the `Usage` type used by chat / image / video so\n * callers can read `.total_cost` on every response uniformly. */\nexport interface EmbeddingsUsage extends Usage {\n prompt_tokens: number;\n total_tokens: number;\n}\n\nexport interface EmbeddingsResponse {\n object: \"list\";\n data: Embedding[];\n model: string;\n usage: EmbeddingsUsage;\n}\n\nexport class EmbeddingsService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Create one or more embeddings.\n *\n * ```ts\n * const r = await client.embeddings.create(\"text-embedding-3-small\", {\n * input: [\"alpha\", \"beta\"],\n * dimensions: 1536,\n * });\n * for (const e of r.data) {\n * console.log(e.index, (e.embedding as number[]).slice(0, 4));\n * }\n * console.log(`Cost: $${r.usage.total_cost.toFixed(6)}`);\n * ```\n */\n async create(\n model: string,\n params: EmbeddingsParams,\n init?: { signal?: AbortSignal },\n ): Promise<EmbeddingsResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"embedding\" },\n () =>\n this.transport.postJSON<EmbeddingsResponse>(\n \"/v1/embeddings\",\n { model, ...params },\n init,\n ),\n (r) => ({\n totalTokens: r.usage?.total_tokens,\n costUSD: r.usage?.total_cost,\n }),\n );\n }\n}\n","/**\n * In-memory session-scoped activity index — turns the raw event\n * stream from `SDKEvents` into the snapshot the badge UI reads each\n * frame: which calls are in flight, what just finished, what the\n * running session cost is.\n *\n * Memory bounded:\n * - `active` map shrinks on every end/error (calls don't accumulate\n * past their settle event).\n * - `recent` is a bounded ring of the last RECENT_MAX completions.\n * - tokens/sec windows hold only `RATE_WINDOW_MS` worth of deltas;\n * stale samples get dropped on every push.\n *\n * Snapshot pattern (vs. emitting another event per change): the\n * badge re-renders on `requestAnimationFrame` while it's open, so\n * a pull-snapshot API matches that cadence — pushing on every\n * delta would over-paint and cost battery on token-heavy streams.\n */\n\nimport type {\n RequestEndEvent,\n RequestErrorEvent,\n RequestStartEvent,\n RequestStreamDeltaEvent,\n SDKCapability,\n SDKEvents,\n} from \"./events.js\";\n\n/** Sliding window for tokens/sec — 1.5s gives a snappy display\n * without jittering on every chunk arrival (most providers emit\n * ~5-30 deltas/s). */\nconst RATE_WINDOW_MS = 1500;\n\n/** Cap on the recent-completion ring buffer. The popover lists the\n * last 5 by default but we keep a few extra so a future \"show more\"\n * UI doesn't need a refactor. */\nconst RECENT_MAX = 20;\n\n/** One LLM call currently in flight. */\nexport interface ActiveCall {\n id: string;\n model: string;\n capability: SDKCapability;\n startedAt: number;\n /** True for stream:true. Drives the pulsing-ring animation. */\n streaming: boolean;\n /** Cumulative completion tokens (0 until first delta). */\n tokensSoFar: number;\n /** Tokens per second over the last RATE_WINDOW_MS. 0 until the\n * window has 2+ samples. */\n tokensPerSec: number;\n /** Internal sample buffer — exposed for tests; UI ignores. */\n rateSamples: Array<{ t: number; tokens: number }>;\n}\n\n/** One LLM call that already finished (success OR failure). */\nexport interface CompletedCall {\n id: string;\n model: string;\n capability: SDKCapability;\n latencyMs: number;\n totalTokens: number;\n costUSD?: number;\n finishReason?: string;\n /** false for RequestErrorEvent settles. */\n ok: boolean;\n /** Error message when ok=false. */\n errorMessage?: string;\n /** Wall clock at settle — drives the \"30s ago\" relative display. */\n endedAt: number;\n}\n\n/** Running session totals — shown in the popover's footer. */\nexport interface SessionStats {\n totalCalls: number;\n totalCostUSD: number;\n errorCount: number;\n /** Wall clock when the tracker first saw an event. Drives the\n * \"session lasted N minutes\" string. */\n startedAt: number;\n}\n\n/** Pull-snapshot of the tracker — taken once per badge re-render. */\nexport interface ActivitySnapshot {\n active: ActiveCall[];\n recent: CompletedCall[];\n session: SessionStats;\n /** Sync state, when RunApps Sync is on (see `client.sync`). */\n sync?: { state: \"off\" | \"idle\" | \"syncing\" | \"offline\" | \"error\"; pending: number; lastSyncedAt?: number; lastChangeAt?: number; error?: string };\n /** Highest-priority status colour for the LED dot:\n * \"error\" → recent failure within ERROR_HIGHLIGHT_MS\n * \"active\" → at least one in-flight call\n * \"idle\" → nothing happening\n * The badge's CSS uses this as a class. */\n status: \"idle\" | \"active\" | \"error\";\n}\n\n/** Window during which a recent error keeps the LED red. 30s is\n * long enough to grab attention but short enough that an old\n * failure doesn't dominate when subsequent calls succeed. */\nconst ERROR_HIGHLIGHT_MS = 30_000;\n\nexport class ActivityTracker {\n private active = new Map<string, ActiveCall>();\n private recent: CompletedCall[] = [];\n private session: SessionStats = {\n totalCalls: 0,\n totalCostUSD: 0,\n errorCount: 0,\n startedAt: Date.now(),\n };\n /** Track most recent error wall-clock so the LED stays red for\n * ERROR_HIGHLIGHT_MS even after the error scrolls out of recent. */\n private lastErrorAt = 0;\n /** \"Snapshot changed\" listeners. Fired AFTER every event handler\n * runs (start / delta / end / error), letting UIs re-render in\n * push-mode instead of polling. Without this hook, the badge's\n * ring/LED stayed idle until the user opened the popover —\n * events landed in the tracker but nobody told the DOM. */\n private changeListeners = new Set<() => void>();\n private sync: ActivitySnapshot[\"sync\"] | undefined = undefined;\n\n /**\n * Subscribe the tracker to a bus. Returns a single disposer that\n * unsubscribes all four handlers — callers (BrowserAuth in our\n * case) attach this on construction and dispose on teardown.\n */\n attach(events: SDKEvents): () => void {\n const offs = [\n events.on(\"request:start\", (e) => { this.onStart(e); this.notify(); }),\n events.on(\"request:streamDelta\", (e) => { this.onDelta(e); this.notify(); }),\n events.on(\"request:end\", (e) => { this.onEnd(e); this.notify(); }),\n events.on(\"request:error\", (e) => { this.onError(e); this.notify(); }),\n events.on(\"sync:state\", (e) => { this.sync = { ...e }; this.notify(); }),\n ];\n return () => {\n for (const o of offs) o();\n };\n }\n\n /**\n * Subscribe to \"snapshot may have changed\" notifications. Fires\n * synchronously after each event handler runs — typical consumer\n * is the badge's `requestRedraw()` which coalesces multiple\n * notifications into one rAF paint.\n *\n * Returns a disposer; call to unsubscribe.\n */\n onChange(handler: () => void): () => void {\n this.changeListeners.add(handler);\n return () => this.changeListeners.delete(handler);\n }\n\n private notify(): void {\n for (const h of this.changeListeners) {\n try {\n h();\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(\"[runapps sdk] ActivityTracker onChange listener threw:\", err);\n }\n }\n }\n\n /** O(active+recent) — small in practice, fine on every frame. */\n snapshot(): ActivitySnapshot {\n const now = Date.now();\n return {\n active: Array.from(this.active.values()),\n recent: this.recent.slice(),\n session: { ...this.session },\n ...(this.sync ? { sync: { ...this.sync } } : {}),\n status:\n now - this.lastErrorAt < ERROR_HIGHLIGHT_MS\n ? \"error\"\n : this.active.size > 0\n ? \"active\"\n : \"idle\",\n };\n }\n\n // ─── handlers ─────────────────────────────────────────────────\n\n private onStart(e: RequestStartEvent): void {\n this.active.set(e.id, {\n id: e.id,\n model: e.model,\n capability: e.capability,\n startedAt: e.startedAt,\n streaming: e.streaming,\n tokensSoFar: 0,\n tokensPerSec: 0,\n rateSamples: [],\n });\n }\n\n private onDelta(e: RequestStreamDeltaEvent): void {\n const call = this.active.get(e.id);\n if (!call) return; // delta after end — drop\n call.tokensSoFar = e.totalTokens;\n const now = Date.now();\n call.rateSamples.push({ t: now, tokens: e.deltaTokens });\n // Drop samples older than the window.\n const cutoff = now - RATE_WINDOW_MS;\n while (call.rateSamples.length > 0 && call.rateSamples[0]!.t < cutoff) {\n call.rateSamples.shift();\n }\n if (call.rateSamples.length >= 2) {\n const sumTokens = call.rateSamples.reduce((s, x) => s + x.tokens, 0);\n const spanMs = now - call.rateSamples[0]!.t;\n call.tokensPerSec = spanMs > 0 ? (sumTokens * 1000) / spanMs : 0;\n }\n }\n\n private onEnd(e: RequestEndEvent): void {\n const call = this.active.get(e.id);\n this.active.delete(e.id);\n // File calls only show while in flight: nothing to list or add up.\n if (e.capability === \"files\") return;\n this.pushRecent({\n id: e.id,\n model: e.model,\n capability: e.capability,\n latencyMs: e.latencyMs,\n totalTokens: e.totalTokens || call?.tokensSoFar || 0,\n ...(e.costUSD !== undefined && { costUSD: e.costUSD }),\n ...(e.finishReason !== undefined && { finishReason: e.finishReason }),\n ok: true,\n endedAt: Date.now(),\n });\n this.session.totalCalls += 1;\n if (e.costUSD !== undefined) this.session.totalCostUSD += e.costUSD;\n }\n\n private onError(e: RequestErrorEvent): void {\n const call = this.active.get(e.id);\n this.active.delete(e.id);\n if (e.capability === \"files\") {\n // Not listed; the LED goes red for a moment unless it was only a\n // missing file, which apps probe for routinely.\n if (e.statusCode !== 404) this.lastErrorAt = Date.now();\n return;\n }\n this.pushRecent({\n id: e.id,\n model: e.model,\n capability: e.capability,\n latencyMs: e.latencyMs,\n totalTokens: call?.tokensSoFar || 0,\n ok: false,\n errorMessage: e.error.message,\n endedAt: Date.now(),\n });\n this.session.totalCalls += 1;\n this.session.errorCount += 1;\n this.lastErrorAt = Date.now();\n }\n\n private pushRecent(c: CompletedCall): void {\n // Newest first — popover renders top-to-bottom newest→oldest.\n this.recent.unshift(c);\n if (this.recent.length > RECENT_MAX) this.recent.length = RECENT_MAX;\n }\n}\n","// sync/codec.ts — JSON encoding for IndexedDB values and keys.\n//\n// IndexedDB stores anything the structured-clone algorithm accepts:\n// Dates, Maps, Sets, binary, Blobs, nested objects, undefined. The sync\n// snapshots on the server are JSON, so every such value is wrapped in a\n// small tagged object on the way out and rebuilt on the way in. Plain\n// JSON values pass through untouched, which keeps the common case (a\n// record of strings and numbers) byte-for-byte readable on the server.\n//\n// Blobs are not inlined: `encodeValue` hands them to a `BlobSink` which\n// stores the bytes elsewhere (the engine uploads them as files) and\n// returns a reference; `decodeValue` asks a `BlobSource` for the bytes.\n\nconst TAG = \"$rt\";\n\n/** How an encoded value refers to a Blob stored out of band. */\nexport interface BlobRef {\n ref: string;\n type: string;\n size: number;\n}\n\nexport interface BlobSink {\n /** Store the blob and return a stable reference for it. */\n (blob: Blob): Promise<BlobRef>;\n}\n\nexport interface BlobSource {\n (ref: BlobRef): Promise<Blob>;\n}\n\ntype Tagged =\n | { [TAG]: \"date\"; v: number | string }\n | { [TAG]: \"undef\" }\n | { [TAG]: \"num\"; v: \"NaN\" | \"Infinity\" | \"-Infinity\" | \"-0\" }\n | { [TAG]: \"bigint\"; v: string }\n | { [TAG]: \"map\"; v: unknown[] }\n | { [TAG]: \"set\"; v: unknown[] }\n | { [TAG]: \"bin\"; c: string; v: string }\n | { [TAG]: \"blob\"; r: string; t: string; s: number; n?: string; m?: number }\n | { [TAG]: \"re\"; s: string; f: string }\n | { [TAG]: \"obj\"; v: Record<string, unknown> };\n\nconst BIN_CTORS: Record<string, (buf: ArrayBuffer) => ArrayBufferView | ArrayBuffer> = {\n ArrayBuffer: (b) => b,\n Uint8Array: (b) => new Uint8Array(b),\n Int8Array: (b) => new Int8Array(b),\n Uint8ClampedArray: (b) => new Uint8ClampedArray(b),\n Int16Array: (b) => new Int16Array(b),\n Uint16Array: (b) => new Uint16Array(b),\n Int32Array: (b) => new Int32Array(b),\n Uint32Array: (b) => new Uint32Array(b),\n Float32Array: (b) => new Float32Array(b),\n Float64Array: (b) => new Float64Array(b),\n BigInt64Array: (b) => new BigInt64Array(b),\n BigUint64Array: (b) => new BigUint64Array(b),\n DataView: (b) => new DataView(b),\n};\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let s = \"\";\n for (let i = 0; i < bytes.length; i += 0x8000) {\n s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));\n }\n return btoa(s);\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const s = atob(b64);\n const out = new Uint8Array(s.length);\n for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);\n return out;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n if (v === null || typeof v !== \"object\") return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Encode a structured-clone value into a JSON-safe one. Async only\n * because Blobs have to be read.\n */\nexport async function encodeValue(value: unknown, sink?: BlobSink): Promise<unknown> {\n if (value === undefined) return { [TAG]: \"undef\" } satisfies Tagged;\n if (value === null) return null;\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return value;\n case \"number\":\n if (Number.isNaN(value)) return { [TAG]: \"num\", v: \"NaN\" };\n if (value === Infinity) return { [TAG]: \"num\", v: \"Infinity\" };\n if (value === -Infinity) return { [TAG]: \"num\", v: \"-Infinity\" };\n if (Object.is(value, -0)) return { [TAG]: \"num\", v: \"-0\" };\n return value;\n case \"bigint\":\n return { [TAG]: \"bigint\", v: value.toString() };\n case \"function\":\n case \"symbol\":\n throw new TypeError(`runapps sync: ${typeof value} values cannot be stored`);\n }\n if (value instanceof Date) {\n const t = value.getTime();\n return { [TAG]: \"date\", v: Number.isNaN(t) ? \"invalid\" : t };\n }\n if (value instanceof RegExp) return { [TAG]: \"re\", s: value.source, f: value.flags };\n if (typeof Blob !== \"undefined\" && value instanceof Blob) {\n if (!sink) throw new TypeError(\"runapps sync: Blob values need a blob sink\");\n const ref = await sink(value);\n const out: Tagged = { [TAG]: \"blob\", r: ref.ref, t: ref.type, s: ref.size };\n const f = value as Blob & { name?: string; lastModified?: number };\n if (typeof File !== \"undefined\" && value instanceof File) {\n out.n = f.name;\n out.m = f.lastModified;\n }\n return out;\n }\n if (value instanceof ArrayBuffer) {\n return { [TAG]: \"bin\", c: \"ArrayBuffer\", v: bytesToBase64(new Uint8Array(value)) };\n }\n if (ArrayBuffer.isView(value)) {\n const name = value.constructor.name;\n if (!(name in BIN_CTORS)) throw new TypeError(`runapps sync: unsupported binary type ${name}`);\n const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n return { [TAG]: \"bin\", c: name, v: bytesToBase64(bytes) };\n }\n if (value instanceof Map) {\n const v: unknown[] = [];\n for (const [k, x] of value) v.push([await encodeValue(k, sink), await encodeValue(x, sink)]);\n return { [TAG]: \"map\", v };\n }\n if (value instanceof Set) {\n const v: unknown[] = [];\n for (const x of value) v.push(await encodeValue(x, sink));\n return { [TAG]: \"set\", v };\n }\n if (Array.isArray(value)) {\n const out: unknown[] = new Array(value.length);\n for (let i = 0; i < value.length; i++) out[i] = await encodeValue(value[i], sink);\n return out;\n }\n if (isPlainObject(value)) {\n // A plain object that happens to carry our tag key is wrapped so it\n // cannot be mistaken for an encoding marker.\n const needsWrap = Object.prototype.hasOwnProperty.call(value, TAG);\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value)) out[k] = await encodeValue(value[k], sink);\n return needsWrap ? { [TAG]: \"obj\", v: out } : out;\n }\n // Class instances: structured clone keeps own enumerable properties\n // only, so do the same.\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as object)) out[k] = await encodeValue((value as Record<string, unknown>)[k], sink);\n return out;\n}\n\n/** Inverse of `encodeValue`. */\nexport async function decodeValue(value: unknown, source?: BlobSource): Promise<unknown> {\n if (value === null || typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n const out: unknown[] = new Array(value.length);\n for (let i = 0; i < value.length; i++) out[i] = await decodeValue(value[i], source);\n return out;\n }\n const obj = value as Record<string, unknown>;\n const tag = obj[TAG];\n if (typeof tag === \"string\") {\n const t = obj as unknown as Tagged;\n switch (t[TAG]) {\n case \"undef\":\n return undefined;\n case \"date\":\n return t.v === \"invalid\" ? new Date(NaN) : new Date(t.v);\n case \"num\":\n return t.v === \"NaN\" ? NaN : t.v === \"Infinity\" ? Infinity : t.v === \"-Infinity\" ? -Infinity : -0;\n case \"bigint\":\n return BigInt(t.v);\n case \"re\":\n return new RegExp(t.s, t.f);\n case \"bin\": {\n const bytes = base64ToBytes(t.v);\n const buf = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(buf).set(bytes);\n const make = BIN_CTORS[t.c];\n if (!make) throw new TypeError(`runapps sync: unsupported binary type ${t.c}`);\n return make(buf);\n }\n case \"map\": {\n const m = new Map<unknown, unknown>();\n for (const pair of t.v as [unknown, unknown][]) {\n m.set(await decodeValue(pair[0], source), await decodeValue(pair[1], source));\n }\n return m;\n }\n case \"set\": {\n const s = new Set<unknown>();\n for (const x of t.v) s.add(await decodeValue(x, source));\n return s;\n }\n case \"blob\": {\n if (!source) throw new TypeError(\"runapps sync: Blob values need a blob source\");\n const blob = await source({ ref: t.r, type: t.t, size: t.s });\n if (t.n !== undefined && typeof File !== \"undefined\") {\n return new File([blob], t.n, { type: t.t, lastModified: t.m ?? Date.now() });\n }\n return blob;\n }\n case \"obj\":\n return decodeValue(t.v, source);\n }\n }\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(obj)) out[k] = await decodeValue(obj[k], source);\n return out;\n}\n\n// ── Keys ─────────────────────────────────────────────────────────────\n//\n// A valid IndexedDB key is a number, a string, a Date, binary, or an\n// array of those. Keys are encoded synchronously (no Blobs possible)\n// and also rendered to a canonical string that serves as the record id\n// inside a shard and as the input to the shard hash.\n\nexport function encodeKey(key: IDBValidKey): unknown {\n if (typeof key === \"number\") {\n if (Number.isNaN(key)) throw new TypeError(\"runapps sync: NaN is not a valid key\");\n return key;\n }\n if (typeof key === \"string\") return key;\n if (key instanceof Date) return { [TAG]: \"date\", v: key.getTime() };\n if (key instanceof ArrayBuffer) return { [TAG]: \"bin\", c: \"ArrayBuffer\", v: bytesToBase64(new Uint8Array(key)) };\n if (ArrayBuffer.isView(key)) {\n return {\n [TAG]: \"bin\",\n c: \"ArrayBuffer\",\n v: bytesToBase64(new Uint8Array(key.buffer, key.byteOffset, key.byteLength)),\n };\n }\n if (Array.isArray(key)) return key.map((k) => encodeKey(k as IDBValidKey));\n throw new TypeError(\"runapps sync: invalid IndexedDB key\");\n}\n\nexport function decodeKey(encoded: unknown): IDBValidKey {\n if (typeof encoded === \"number\" || typeof encoded === \"string\") return encoded;\n if (Array.isArray(encoded)) return encoded.map(decodeKey);\n if (encoded && typeof encoded === \"object\") {\n const t = encoded as { [TAG]?: string; v?: unknown };\n if (t[TAG] === \"date\") return new Date(t.v as number);\n if (t[TAG] === \"bin\") {\n const bytes = base64ToBytes(t.v as string);\n const buf = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(buf).set(bytes);\n return buf;\n }\n }\n throw new TypeError(\"runapps sync: invalid encoded key\");\n}\n\n/** Canonical string id for a key: equal keys give equal ids. */\nexport function keyId(key: IDBValidKey): string {\n return JSON.stringify(encodeKey(key));\n}\n\n/** Deterministic 32-bit hash of a string (FNV-1a). */\nexport function hash32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/**\n * Structural equality for structured-clone values: Dates by time,\n * binary by bytes, Map/Set by entries, Blobs by identity (two different\n * Blob objects count as a change). Used to find which fields a write\n * touched.\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (a instanceof Date || b instanceof Date) {\n return a instanceof Date && b instanceof Date && Object.is(a.getTime(), b.getTime());\n }\n if (typeof Blob !== \"undefined\" && (a instanceof Blob || b instanceof Blob)) return false;\n if (ArrayBuffer.isView(a) || a instanceof ArrayBuffer || ArrayBuffer.isView(b) || b instanceof ArrayBuffer) {\n const toBytes = (x: unknown): Uint8Array | null =>\n x instanceof ArrayBuffer ? new Uint8Array(x) : ArrayBuffer.isView(x) ? new Uint8Array(x.buffer, x.byteOffset, x.byteLength) : null;\n const ba = toBytes(a);\n const bb = toBytes(b);\n if (!ba || !bb || ba.length !== bb.length || a.constructor !== b.constructor) return false;\n for (let i = 0; i < ba.length; i++) if (ba[i] !== bb[i]) return false;\n return true;\n }\n if (a instanceof Map || b instanceof Map) {\n if (!(a instanceof Map && b instanceof Map) || a.size !== b.size) return false;\n for (const [k, v] of a) if (!b.has(k) || !deepEqual(v, b.get(k))) return false;\n return true;\n }\n if (a instanceof Set || b instanceof Set) {\n if (!(a instanceof Set && b instanceof Set) || a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;\n return true;\n }\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;\n }\n return true;\n}\n\n/** The fields of `value` that differ from `prior` (added, changed or\n * removed). `undefined` when there is no prior: everything is new. */\nexport function changedFields(prior: unknown, value: unknown): string[] | undefined {\n if (!isPlainObject(prior) || !isPlainObject(value)) return undefined;\n const out: string[] = [];\n for (const k of new Set([...Object.keys(prior), ...Object.keys(value)])) {\n const inP = Object.prototype.hasOwnProperty.call(prior, k);\n const inV = Object.prototype.hasOwnProperty.call(value, k);\n if (inP !== inV || !deepEqual(prior[k], value[k])) out.push(k);\n }\n return out;\n}\n\n/** Read an inline key out of a value by IndexedDB keyPath rules. */\nexport function extractKey(value: unknown, keyPath: string | string[] | null): IDBValidKey | undefined {\n if (keyPath === null || keyPath === undefined) return undefined;\n const one = (path: string): unknown => {\n let cur: unknown = value;\n if (path === \"\") return cur;\n for (const part of path.split(\".\")) {\n if (cur === null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n };\n if (Array.isArray(keyPath)) {\n const parts = keyPath.map(one);\n return parts.some((p) => p === undefined) ? undefined : (parts as IDBValidKey[]);\n }\n const k = one(keyPath);\n return k === undefined ? undefined : (k as IDBValidKey);\n}\n","// browser-auth.ts — opt-in runapps.ai auth flow for browser bundles.\n//\n// Activated by `new RunApps({ authProvider: \"runapps\", baseURL: \"...\" })`.\n// In Node and other non-window environments this module's runtime code\n// short-circuits to no-ops; it ships with the IIFE bundle for resource\n// bundles loaded via <script> tag.\n//\n// Responsibilities:\n// - Detect & consume the `#runapps_token=…` URL fragment planted by the\n// grant page after a successful sign-in.\n// - Trigger a redirect-based grant flow when the bundle calls a\n// gateway endpoint without a fresh token.\n// - In iframe contexts (a RunApps page embedding the app), handshake\n// with the parent via postMessage to silently obtain a token.\n// - Render an unobtrusive identity badge in the bottom-right corner\n// so the end user can see WHICH RunApps account is paying for the\n// calls being made on their behalf.\n//\n// Why redirect (not popup): under COOP=same-origin (required so the\n// bundle can use OPFS / SharedArrayBuffer), the browser severs the\n// window.opener relationship for cross-origin popups, so popup-based\n// grant flows silently lose their postMessage replies.\n\nimport { ActivityTracker, type ActivitySnapshot, type SessionStats } from \"./activity-tracker.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { hash32 } from \"./sync/codec.js\";\n\nexport interface BrowserAuthOptions {\n /**\n * Origin where the RunApps gateway lives — also the page that issues\n * tokens at /api/sdk/grant. Default `https://www.runapps.ai`.\n */\n origin?: string;\n /**\n * Suppress the floating identity badge. Default `false` at this\n * layer. The wrapping `RunApps` client passes `hideBadge: false`\n * by default (badge SHOWN — it's now a live activity indicator,\n * see `activity-tracker.ts`); callers wanting their own status UI\n * opt out via `RunApps({ showIdentityBadge: false })`. Set this\n * directly only when constructing BrowserAuth without going\n * through `RunApps`.\n */\n hideBadge?: boolean;\n /**\n * Pin the grant flow to a named runapps.ai project. When set, the\n * SDK passes `project_id=<value>` to `/api/sdk/grant`, asking the\n * gateway to mint a project-bound `rrt_*` resource token for THAT\n * project regardless of which (origin, app) pair the bundle is\n * served from.\n *\n * Use cases:\n * - **Local dev** for a 1:1 bundle/project app: the localhost\n * origin isn't registered with any project, so without this\n * option the grant flow can't pick one and `client.files` 403s\n * on every call. Pin the project explicitly and `pnpm dev`\n * hits the same files namespace as production.\n * - **Multi-tenant bundles** that let the user pick a project at\n * runtime — pass the chosen project id, then call `signOut()` +\n * `signIn()` (or reconstruct the client) on switch.\n *\n * Omit to keep the default behaviour: the gateway derives the\n * project from the registered (origin, app) pair.\n */\n project?: string;\n /**\n * Optional event bus from the parent RunApps client. When wired,\n * the identity badge subscribes to call telemetry events and\n * renders a real-time activity ring + LED dot + click-through\n * popover with active / recent / session stats — turning the\n * static \"signed in\" badge into a desktop-ball-style live\n * indicator (similar to a tray-app gauge).\n *\n * Optional and back-compat: when omitted (or when `hideBadge` is\n * true), the badge falls back to its original static behaviour.\n */\n events?: SDKEvents;\n /**\n * Which corner the badge floats in. See `RunApps.badgePosition`\n * for the user-facing flavour. Defaults to `\"bottom-right\"`.\n */\n badgePosition?: BadgePosition;\n}\n\n/** Four-corner anchor for the floating activity badge. */\nexport type BadgePosition = \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\";\n\nexport interface BrowserUser {\n id?: string;\n name?: string;\n}\n\nconst TOKEN_REFRESH_MARGIN_S = 60;\n// Per-project localStorage namespacing. When two BrowserAuth bundles\n// live on the same origin (most commonly `localhost:5173` during\n// development of multiple SDK-using apps, or a deployed origin that\n// hosts more than one bundle), a single shared key would let them\n// clobber each other's tokens — bundle B's signIn() would overwrite\n// the token bundle A had cached, and the next time A booted it'd\n// either reuse B's token (wrong project scope, every gateway call\n// returns 404 on `files/...`) or skip the cache and redirect through\n// the grant flow on every reload. Append the constructor-supplied\n// `project` to the key so bundles scoped to different projects keep\n// independent slots in localStorage. Bundles without an explicit\n// project (iframe / parent-handshake mode) fall back to the legacy\n// unsuffixed key, which preserves back-compat for already-persisted\n// tokens after upgrade.\nconst STORAGE_KEY_BASE = \"__runapps_auth_v1__\";\n// Written by SDK 0.3 and earlier; read once and migrated, so an upgrade\n// keeps the user signed in.\nconst LEGACY_STORAGE_KEY_BASE = \"__runjobs_auth_v1__\";\n// Set when the user clicks \"Sign out\". While present, getToken() must\n// not silently redirect to the grant page even though the user still\n// has a runapps.ai cookie — otherwise the backend's auto-grant turns\n// every page reload into a re-authentication. Cleared on explicit\n// signIn() so the user can come back. Same per-project namespacing\n// rationale as STORAGE_KEY_BASE — signing out of bundle A shouldn't\n// also sign out bundle B that happens to share the origin.\nconst SIGNED_OUT_KEY_BASE = \"__runapps_signed_out_v1__\";\nconst LEGACY_SIGNED_OUT_KEY_BASE = \"__runjobs_signed_out_v1__\";\n\n/** Compose the localStorage key for an auth slot. `null` project\n * yields the base key (iframe-mode and pre-v1.x bundles), which\n * preserves back-compat for tokens persisted before this change. */\nfunction storageKey(project: string | null): string {\n return project ? `${STORAGE_KEY_BASE}:${project}` : STORAGE_KEY_BASE;\n}\n\n/** Same as storageKey, for the sticky sign-out flag. */\nfunction signedOutKey(project: string | null): string {\n return project ? `${SIGNED_OUT_KEY_BASE}:${project}` : SIGNED_OUT_KEY_BASE;\n}\n\nfunction legacyStorageKey(project: string | null): string {\n return project ? `${LEGACY_STORAGE_KEY_BASE}:${project}` : LEGACY_STORAGE_KEY_BASE;\n}\n\nfunction legacySignedOutKey(project: string | null): string {\n return project ? `${LEGACY_SIGNED_OUT_KEY_BASE}:${project}` : LEGACY_SIGNED_OUT_KEY_BASE;\n}\n\ninterface PersistedAuth {\n token: string;\n expiresAt: number;\n user?: BrowserUser;\n origin: string;\n}\n\n/**\n * BrowserAuth encapsulates the browser-side auth state for a RunApps\n * client. Exposes:\n *\n * - getToken() fresh bearer (auto-refreshes / signs in)\n * - signIn() force a redirect to the grant page\n * - user { id, name } — null until signed in\n * - onTokenChange(fn) subscribe to token (re)acquisition\n *\n * Construct once per page; the SDK passes its `getToken` as the\n * transport's `apiKeyResolver`.\n */\nexport class BrowserAuth {\n private readonly origin: string;\n private readonly hideBadge: boolean;\n private readonly project: string | null;\n private readonly events: SDKEvents | null;\n /** Lazily-constructed when events are present — drives the badge's\n * activity ring / LED / popover. nullable for tests / non-browser. */\n private readonly tracker: ActivityTracker | null;\n private readonly badgePosition: BadgePosition;\n private token: string | null = null;\n private expiresAt = 0;\n private userInfo: BrowserUser | null = null;\n private listeners: Array<(token: string) => void> = [];\n private parentHandshake: Promise<void> | null = null;\n private signingIn = false;\n\n constructor(opts: BrowserAuthOptions = {}) {\n this.origin = (opts.origin ?? \"https://www.runapps.ai\").replace(/\\/$/, \"\");\n this.hideBadge = !!opts.hideBadge;\n this.project = opts.project ?? null;\n this.events = opts.events ?? null;\n this.badgePosition = opts.badgePosition ?? \"bottom-right\";\n if (this.events && !this.hideBadge) {\n this.tracker = new ActivityTracker();\n this.tracker.attach(this.events);\n } else {\n this.tracker = null;\n }\n if (typeof window === \"undefined\") return;\n\n // Restore from localStorage (survives page reloads). We pin the\n // saved token to the gateway origin: a bundle that switches\n // between dev / prod gateways shouldn't accidentally reuse the\n // previous one's token.\n this.loadPersisted();\n\n // Eager: install token from the URL fragment if the grant page\n // just redirected us back here. This always WINS over a\n // localStorage-cached token because a fresh redirect implies the\n // user just (re)authenticated and the old cache may be stale.\n // A successful fragment install also clears the \"signed out\"\n // sticky flag — the user explicitly signed in again.\n if (this.consumeFragment()) {\n this.clearSignedOut();\n }\n\n if (this.isSignedOut()) {\n // Honour the user's explicit sign-out across reloads. Don't\n // attempt parent handshake or render the badge.\n } else if (this.inIframe() && !this.tokenIsFresh()) {\n // Best-effort parent handshake — silent if it succeeds, falls\n // through to the standard fragment / redirect path otherwise.\n this.parentHandshake = this.requestTokenFromParent().catch(() => {\n /* dashboard parent unavailable; will redirect on next call */\n });\n } else if (this.tokenIsFresh() && this.userInfo && !this.hideBadge) {\n // Restored from cache — render the badge so the user can see\n // they're already signed in (and find the sign-out affordance\n // their bundle author exposed).\n this.renderBadge();\n }\n }\n\n /** Public token-fetcher; pass to RunApps as `apiKeyResolver`. */\n getToken = async (): Promise<string> => {\n if (this.tokenIsFresh()) return this.token as string;\n\n // User explicitly signed out — don't silently re-auth. Throw so\n // the caller surfaces an unauthenticated state and can prompt the\n // user to click signIn. Without this guard, the backend's\n // auto-grant flow would re-issue a token on every page reload\n // (the cookie on the gateway domain is still valid).\n if (this.isSignedOut()) {\n throw new Error(\"RunApps: signed out — call client.signIn() to authenticate\");\n }\n\n // ── Embedded (cross-site iframe) path ──\n //\n // Inside a cross-site iframe (the dashboard's bundle embed),\n // signIn() → /api/sdk/grant is a DEAD END: navigating the iframe to\n // the gateway origin is a *sub-frame* navigation, so the session\n // cookie is NOT sent (SameSite) and grant bounces to a login form\n // the user can't complete in place. The dashboard parent — same\n // origin as the gateway, and it holds the cookie — is the ONLY\n // party that can mint a token here. So we RE-handshake with the\n // parent on EVERY stale token (not just once at construction).\n // This is what lets a bundle survive token expiry / revocation\n // without ever dropping to the login screen.\n if (this.inIframe()) {\n try {\n // Reuse the eager construction-time handshake on the first call\n // (avoids a duplicate post), then consume it so later staleness\n // (expiry / 401-invalidate) starts a fresh handshake instead.\n if (this.parentHandshake) {\n await this.parentHandshake;\n this.parentHandshake = null;\n }\n if (!this.tokenIsFresh()) {\n await this.requestTokenFromParent();\n }\n } catch {\n /* parent slow / unreachable — fall through to the throw */\n }\n if (this.tokenIsFresh()) return this.token as string;\n // Never signIn() inside an iframe (cookie-less grant dead end).\n // Surface as unauthenticated so the bundle can retry / show state.\n throw new Error(\"RunApps: could not obtain a token from the dashboard\");\n }\n\n // ── Top-level (own tab / window) path ──\n //\n // signIn() → grant works here because the cookie IS sent on a\n // top-level navigation.\n if (this.parentHandshake) {\n await this.parentHandshake;\n if (this.tokenIsFresh()) return this.token as string;\n }\n // No fresh token + can't get one silently → redirect to grant.\n // The page is about to unload; return a never-resolving Promise so\n // the caller doesn't proceed with a half-issued request.\n this.signIn();\n return new Promise(() => {});\n };\n\n /** Subscribe to (re)acquisition events. Returns an unsubscribe fn. */\n onTokenChange(handler: (token: string) => void): () => void {\n this.listeners.push(handler);\n return () => {\n const i = this.listeners.indexOf(handler);\n if (i >= 0) this.listeners.splice(i, 1);\n };\n }\n\n /** Currently-authenticated user, or null. */\n get user(): BrowserUser | null {\n return this.userInfo;\n }\n\n /**\n * True iff a cached bearer token is present AND not within the\n * 60-second pre-expiry refresh margin. Use this — not `user !==\n * null` — to decide whether to show \"please sign in\" UI: the user\n * metadata is only populated when the SDK was fed fresh user info\n * (post-redirect fragment carries it; iframe handshake carries it;\n * an older localStorage save format may have persisted only the\n * token). A token without metadata is still a fully-usable\n * session — every gateway call will succeed — and prompting the\n * user to sign in there causes a redirect that loses session\n * continuity for no reason.\n */\n hasFreshToken(): boolean {\n return this.tokenIsFresh();\n }\n\n /**\n * A stable id for whose data is on this browser, or null when nobody\n * is signed in. The user's id when the grant carried one; otherwise\n * derived from the token, which is bound to one user and one app and\n * never rotates. Used by Sync to tell an account switch.\n */\n ownerId(): string | null {\n if (!this.tokenIsFresh()) return null;\n if (this.userInfo?.id) return this.userInfo.id;\n return \"t\" + hash32(this.token as string).toString(36);\n }\n\n /**\n * Clear the cached token / user / badge and the persisted copy in\n * localStorage. Next gateway call will trigger a redirect to the\n * grant page — i.e. the standard sign-in flow. Wire this to a\n * \"Sign out\" button in the bundle's settings UI.\n *\n * Does NOT redirect on its own; the bundle decides whether to\n * also navigate the user (e.g. to a logged-out splash page) or\n * just refresh state and let the next API call re-prompt.\n */\n signOut(): void {\n this.token = null;\n this.expiresAt = 0;\n this.userInfo = null;\n this.parentHandshake = null;\n this.signingIn = false;\n this.clearPersisted();\n this.markSignedOut();\n this.removeBadge();\n }\n\n /**\n * Drop the in-memory + persisted token without setting the sticky\n * \"signed out\" flag. Used by the transport layer when the gateway\n * returns 401 (token revoked server-side, e.g. user unsubscribed in\n * another tab). The next `getToken()` will trigger the standard\n * `signIn()` redirect-grant flow and the user re-auths transparently.\n */\n invalidate(): void {\n this.token = null;\n this.expiresAt = 0;\n this.userInfo = null;\n this.parentHandshake = null;\n this.signingIn = false;\n this.clearPersisted();\n }\n\n /** Force a redirect to the grant page. */\n signIn(): void {\n if (typeof window === \"undefined\" || this.signingIn) return;\n // Explicit signIn clears the \"signed out\" flag so getToken() can\n // proceed with the redirect-grant flow.\n this.clearSignedOut();\n this.signingIn = true;\n const ret = location.href.split(\"#\")[0] ?? location.href;\n const scheme = window.matchMedia?.(\"(prefers-color-scheme: dark)\")\n .matches\n ? \"dark\"\n : \"light\";\n location.href = this.buildGrantUrl({\n pageOrigin: location.origin,\n app: location.host,\n redirectTo: ret,\n scheme,\n });\n }\n\n /**\n * Build the `/api/sdk/grant?…` URL the user is redirected to. Split\n * out so tests can assert URL formation without a `window` context;\n * `signIn` is the only production caller.\n *\n * Public surface, but namespaced under `_buildGrantUrlForTest` so it\n * doesn't show up in IntelliSense as a normal API.\n */\n _buildGrantUrlForTest(args: {\n pageOrigin: string;\n app: string;\n redirectTo: string;\n scheme: \"light\" | \"dark\";\n }): string {\n return this.buildGrantUrl(args);\n }\n\n /**\n * Return the localStorage keys this instance reads / writes for its\n * persisted token + sign-out flag. Exposed for tests so we can\n * assert per-project namespacing without a real DOM; not part of\n * the public API surface (the leading underscore + `ForTest` suffix\n * is the SDK's convention for this).\n */\n _storageKeysForTest(): { auth: string; signedOut: string } {\n return {\n auth: storageKey(this.project),\n signedOut: signedOutKey(this.project),\n };\n }\n\n private buildGrantUrl(args: {\n pageOrigin: string;\n app: string;\n redirectTo: string;\n scheme: \"light\" | \"dark\";\n }): string {\n const params = new URLSearchParams();\n params.set(\"origin\", args.pageOrigin);\n params.set(\"app\", args.app);\n params.set(\"redirect_to\", args.redirectTo);\n params.set(\"scheme\", args.scheme);\n if (this.project) params.set(\"project_id\", this.project);\n // Ask for #runapps_token=…; a platform that predates it answers with\n // #runjobs_token=…, which consumeFragment also accepts.\n params.set(\"token_name\", \"runapps_token\");\n return this.origin + \"/api/sdk/grant?\" + params.toString();\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private nowSec() {\n return Math.floor(Date.now() / 1000);\n }\n\n private tokenIsFresh(): boolean {\n return !!this.token && this.nowSec() < this.expiresAt - TOKEN_REFRESH_MARGIN_S;\n }\n\n private inIframe(): boolean {\n try {\n return window.self !== window.top;\n } catch {\n return true;\n }\n }\n\n private setToken(token: string, expiresAt: number, user?: BrowserUser) {\n this.token = token;\n this.expiresAt = expiresAt || this.nowSec() + 3600;\n if (user) {\n this.userInfo = user;\n if (!this.hideBadge) this.renderBadge();\n }\n this.savePersisted();\n for (const fn of this.listeners) {\n try {\n fn(token);\n } catch (err) {\n console.warn(\"[runapps] onTokenChange handler threw\", err);\n }\n }\n }\n\n private savePersisted() {\n if (typeof localStorage === \"undefined\" || !this.token) return;\n try {\n const data: PersistedAuth = {\n token: this.token,\n expiresAt: this.expiresAt,\n origin: this.origin,\n ...(this.userInfo ? { user: this.userInfo } : {}),\n };\n localStorage.setItem(storageKey(this.project), JSON.stringify(data));\n } catch {\n /* quota / private mode — degrade to in-memory only */\n }\n }\n\n private loadPersisted() {\n if (typeof localStorage === \"undefined\") return;\n try {\n let raw = localStorage.getItem(storageKey(this.project));\n if (!raw) {\n // Upgrade from SDK 0.3 or earlier: move the token to the new key.\n raw = localStorage.getItem(legacyStorageKey(this.project));\n if (!raw) return;\n localStorage.setItem(storageKey(this.project), raw);\n localStorage.removeItem(legacyStorageKey(this.project));\n }\n const data = JSON.parse(raw) as Partial<PersistedAuth>;\n if (!data.token || !data.origin) return;\n // Pin to the gateway origin we were constructed with — a bundle\n // talking to a different gateway shouldn't reuse this token.\n if (data.origin !== this.origin) return;\n // Already expired? Drop it; next call will redirect to grant.\n if (typeof data.expiresAt === \"number\" && data.expiresAt > 0 &&\n this.nowSec() >= data.expiresAt - TOKEN_REFRESH_MARGIN_S) {\n this.clearPersisted();\n return;\n }\n this.token = data.token;\n this.expiresAt = data.expiresAt ?? 0;\n if (data.user) this.userInfo = data.user;\n } catch {\n /* corrupted blob — drop it */\n this.clearPersisted();\n }\n }\n\n private clearPersisted() {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(storageKey(this.project));\n localStorage.removeItem(legacyStorageKey(this.project));\n } catch { /* ignore */ }\n }\n\n private isSignedOut(): boolean {\n if (typeof localStorage === \"undefined\") return false;\n try {\n return localStorage.getItem(signedOutKey(this.project)) === \"1\" ||\n localStorage.getItem(legacySignedOutKey(this.project)) === \"1\";\n } catch { return false; }\n }\n\n private markSignedOut() {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.setItem(signedOutKey(this.project), \"1\");\n } catch { /* ignore */ }\n }\n\n private clearSignedOut() {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(signedOutKey(this.project));\n localStorage.removeItem(legacySignedOutKey(this.project));\n } catch { /* ignore */ }\n }\n\n private removeBadge() {\n if (typeof document === \"undefined\") return;\n document.getElementById(\"__runapps_identity__\")?.remove();\n }\n\n /** Parse the #runapps_token=… fragment (or the older #runjobs_token=…),\n * install, then strip it. */\n private consumeFragment(): boolean {\n if (!location.hash || !/(^#|&)run(apps|jobs)_token=/.test(location.hash))\n return false;\n const params: Record<string, string> = {};\n location.hash\n .replace(/^#/, \"\")\n .split(\"&\")\n .forEach((kv) => {\n const i = kv.indexOf(\"=\");\n if (i >= 0) params[decodeURIComponent(kv.slice(0, i))] = decodeURIComponent(kv.slice(i + 1).replace(/\\+/g, \" \"));\n });\n const token = params.runapps_token || params.runjobs_token;\n if (!token) return false;\n let user: BrowserUser | undefined;\n if (params.user) {\n try {\n user = JSON.parse(params.user);\n } catch {\n /* malformed; ignore — token still works */\n }\n }\n this.setToken(\n token,\n parseInt(params.expires_at ?? \"\", 10) || 0,\n user,\n );\n // Clean fragment so a refresh doesn't re-consume.\n try {\n history.replaceState(null, \"\", location.pathname + location.search);\n } catch {\n /* ignore — non-fatal */\n }\n return true;\n }\n\n /** postMessage handshake with the dashboard parent (iframe path). */\n private requestTokenFromParent(): Promise<void> {\n return new Promise((resolve, reject) => {\n let done = false;\n const timers: ReturnType<typeof setTimeout>[] = [];\n const cleanup = () => {\n done = true;\n window.removeEventListener(\"message\", onMessage);\n for (const t of timers) clearTimeout(t);\n };\n const onMessage = (e: MessageEvent) => {\n if (e.origin !== this.origin) return;\n const data = e.data as\n | { type: string; token?: string; expires_at?: number; user?: BrowserUser }\n | null;\n if (data && (data.type === \"runapps:token\" || data.type === \"runjobs:token\") && data.token) {\n this.setToken(data.token, data.expires_at ?? 0, data.user);\n cleanup();\n resolve();\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n // postMessage is NOT queued: if the parent's React listener isn't\n // mounted at the instant of our first post, that single message is\n // lost forever and we'd time out → dead-end grant redirect. So\n // re-post a few times across the wait window; a late-mounting\n // parent still receives a request to answer. The parent also\n // pushes the token proactively on iframe load — belt-and-braces.\n const post = () => {\n if (done) return;\n try {\n window.parent.postMessage({ type: \"runapps:request-token\" }, this.origin);\n window.parent.postMessage({ type: \"runjobs:request-token\" }, this.origin); // older embedders\n } catch (err) {\n cleanup();\n reject(err);\n }\n };\n post();\n for (const d of [250, 750, 1500, 3000]) timers.push(setTimeout(post, d));\n\n // Longer overall window (5s vs the old 3s) gives a heavy bundle +\n // a cold parent room to complete the round trip before we give up.\n timers.push(\n setTimeout(() => {\n if (done) return;\n cleanup();\n reject(new Error(\"runapps: parent handshake timeout\"));\n }, 5000),\n );\n });\n }\n\n /** Floating identity badge — bottom-right pill showing the user.\n * Click opens the runapps.ai dashboard in a new tab so the user\n * can manage their account / billing without losing the bundle's\n * in-flight state. Uses `noopener,noreferrer` so the new tab\n * can't reach back into the bundle window via `window.opener`. */\n private renderBadge() {\n if (typeof document === \"undefined\" || !this.userInfo) return;\n const ID = \"__runapps_identity__\";\n const dashboardUrl = `${this.origin}/dashboard`;\n const ready = (cb: () => void) =>\n document.body ? cb() : document.addEventListener(\"DOMContentLoaded\", cb);\n ready(() => {\n // Idempotent: remove an older instance so successive renders\n // (e.g. after sign-in finishes) don't stack badges.\n document.getElementById(ID)?.remove();\n const root = mountActivityBadge({\n id: ID,\n user: this.userInfo as BrowserUser,\n dashboardUrl,\n tracker: this.tracker,\n position: this.badgePosition,\n // Bind through `this` so the popover's Sign-out button runs\n // BrowserAuth.signOut (clears token cache + flips signed-out\n // flag + dispatches the auth event); a plain method reference\n // would lose `this`.\n //\n // Then hard-reload the page. signOut() deliberately doesn't\n // navigate (its contract leaves that to the host bundle), but\n // the badge IS the SDK's own UI, and a user clicking its\n // \"Sign out\" expects to land back on the app's signed-out /\n // login view — not keep staring at a now-stale authed page.\n // The reload re-runs the bundle's bootstrap against a cleared\n // token + the sticky signed-out flag, so the app naturally\n // shows its login state. Guard window for non-browser hosts.\n onSignOut: () => {\n this.signOut();\n if (typeof window !== \"undefined\") window.location.reload();\n },\n });\n document.body.appendChild(root);\n });\n }\n}\n\n/* ============================================================ *\n * Activity Badge UI *\n * ============================================================ *\n *\n * Vanilla-DOM render of the bottom-right identity badge with a\n * desktop-ball-style real-time activity layer. Stays out of\n * BrowserAuth's main class body to keep auth state and DOM concerns\n * separate — auth owns \"who is signed in\", this owns \"what's\n * happening right now\". Three visual layers:\n *\n * 1. Avatar + name pill — the existing static badge.\n * 2. Progress ring (SVG) wrapping the avatar — animates speed\n * proportional to tokens/sec when a stream is active; fades\n * out at idle. Single in-flight call shows one rotating arc;\n * multi-call shows the avatar's outline glowing.\n * 3. LED dot at avatar's bottom-right — discrete colour signal:\n * grey (idle) / blue pulsing (active) / red (recent error) /\n * green fade (success-burst, 300ms after end).\n *\n * Clicking the badge toggles a popover showing the same data the\n * spec called for: Active / Recent / Session. Hover with 400ms\n * grace also opens it (matches macOS / Discord tray tooltips).\n *\n * Performance: the redraw loop pauses entirely when status==idle\n * AND the popover is closed (no rAF burn). When active, it\n * re-renders at ~6 Hz — fast enough for token rate to look live,\n * cheap enough to not warm a laptop fan.\n */\n\ninterface MountOpts {\n id: string;\n user: BrowserUser;\n dashboardUrl: string;\n tracker: ActivityTracker | null;\n position: BadgePosition;\n /** Fired when the user clicks the popover's \"Sign out\" link.\n * Wires up to BrowserAuth.signOut so the token cache is cleared\n * and the in-tab state flips to signed-out without a reload. */\n onSignOut: () => void;\n}\n\n/**\n * Per-corner CSS overrides for the floating badge + its popover.\n * `top` vs `bottom` flips the badge's vertical anchor AND the\n * popover's open direction (top-anchored badge → popover opens\n * downward); `right` vs `left` flips horizontal anchor on both.\n */\n/**\n * Inject the `@keyframes __runapps_led_pulse__` rule into the document\n * head ONCE (idempotent — re-mounts and multiple SDK instances on the\n * same page share the same rule). Must run at badge mount time so\n * the LED's `animation` CSS resolves the keyframe reference the\n * moment status flips from idle to active — previously this lived\n * inside createPopover() and only fired on first hover, leaving the\n * LED frozen until the user interacted.\n */\nfunction ensurePulseKeyframes(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(\"__runapps_kf__\")) return;\n const style = document.createElement(\"style\");\n style.id = \"__runapps_kf__\";\n style.textContent =\n \"@keyframes __runapps_led_pulse__ {\" +\n \" 0% { box-shadow: 0 0 0 0 rgba(59,130,246,0.7); }\" +\n \" 70% { box-shadow: 0 0 0 6px rgba(59,130,246,0); }\" +\n \" 100% { box-shadow: 0 0 0 0 rgba(59,130,246,0); }\" +\n \"}\" +\n // A sync that moved data: one green ring swells out of the avatar.\n \"@keyframes __runapps_sync_ping__ {\" +\n \" 0% { transform: scale(1); opacity: 0.9; }\" +\n \" 100% { transform: scale(2.1); opacity: 0; }\" +\n \"}\";\n (document.head ?? document.documentElement).appendChild(style);\n}\n\nfunction badgeCornerStyles(position: BadgePosition): {\n badge: string;\n popover: string;\n} {\n const v = position.startsWith(\"top\") ? \"top\" : \"bottom\";\n const h = position.endsWith(\"right\") ? \"right\" : \"left\";\n // Popover sits on the OPPOSITE vertical side of the badge so it\n // grows AWAY from the edge of the viewport, never offscreen.\n const popoverVerticalAnchor =\n v === \"bottom\" ? \"bottom:calc(100% + 8px);top:auto\" : \"top:calc(100% + 8px);bottom:auto\";\n const popoverHorizontalAnchor = h === \"right\" ? \"right:0;left:auto\" : \"left:0;right:auto\";\n return {\n badge: `${v}:16px;${h}:16px`,\n popover: `${popoverVerticalAnchor};${popoverHorizontalAnchor}`,\n };\n}\n\n// ─── Drag-to-move + position persistence ────────────────────────────\n//\n// Users sometimes want to move the floating badge — it overlaps a\n// site's own corner UI (chat widget, footer button, etc.). Drag to\n// reposition; the saved coords land in a cookie so reloads keep the\n// chosen spot. Cookie because the user asked for cookie; localStorage\n// would be the more typical choice for ~10 bytes of UI state, but\n// cookie is fine — small payload, same-site, no SDK dependency on a\n// storage permission.\n\n/** Cookie key the saved badge position lives under. URI-safe, prefixed\n * so other libraries / consumers don't accidentally collide. */\nconst BADGE_POS_COOKIE = \"__runapps_badge_pos__\";\nconst LEGACY_BADGE_POS_COOKIE = \"__runjobs_badge_pos__\";\n\n/** Pixel threshold a pointer must travel before we treat the gesture\n * as a drag instead of a click. Below this, it's a normal badge tap\n * that opens the popover. 5 px matches OS-level click slop tolerances. */\nconst BADGE_DRAG_THRESHOLD_PX = 5;\n\n/** Edge inset when clamping a dragged badge into the viewport — keeps\n * it visually inside the safe area on resize. Match the default\n * corner offset (`16px` in badgeCornerStyles). */\nconst BADGE_EDGE_INSET_PX = 8;\n\nfunction readBadgePosCookie(): { x: number; y: number } | null {\n if (typeof document === \"undefined\") return null;\n const cookies = document.cookie.split(\";\").map((s) => s.trim());\n let name = BADGE_POS_COOKIE;\n let raw = cookies.find((s) => s.startsWith(name + \"=\"));\n if (!raw) {\n name = LEGACY_BADGE_POS_COOKIE;\n raw = cookies.find((s) => s.startsWith(name + \"=\"));\n }\n if (!raw) return null;\n const value = decodeURIComponent(raw.slice(name.length + 1));\n const parts = value.split(\",\");\n const rawX = parts[0];\n const rawY = parts[1];\n if (parts.length !== 2 || rawX === undefined || rawY === undefined) return null;\n const x = Number.parseInt(rawX, 10);\n const y = Number.parseInt(rawY, 10);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return { x, y };\n}\n\nfunction writeBadgePosCookie(x: number, y: number): void {\n if (typeof document === \"undefined\") return;\n // 1-year expiry, root path, lax cross-site so the cookie survives\n // navigation between subdomains under the same site. Secure when\n // we're on HTTPS so iframes can read it back without warnings.\n const maxAge = 60 * 60 * 24 * 365;\n const secure = typeof window !== \"undefined\" && window.location?.protocol === \"https:\" ? \";Secure\" : \"\";\n document.cookie =\n `${BADGE_POS_COOKIE}=${encodeURIComponent(`${Math.round(x)},${Math.round(y)}`)};` +\n `path=/;max-age=${maxAge};SameSite=Lax${secure}`;\n}\n\n/** Snap (x, y) into the viewport so a saved position from a wider\n * screen doesn't render off-canvas after a window resize.\n * Coordinates are top-left of the badge element; w/h are its bounds. */\nfunction clampToViewport(\n x: number,\n y: number,\n w: number,\n h: number,\n): { x: number; y: number } {\n const vw = typeof window !== \"undefined\" ? window.innerWidth : 1024;\n const vh = typeof window !== \"undefined\" ? window.innerHeight : 768;\n const maxX = Math.max(BADGE_EDGE_INSET_PX, vw - w - BADGE_EDGE_INSET_PX);\n const maxY = Math.max(BADGE_EDGE_INSET_PX, vh - h - BADGE_EDGE_INSET_PX);\n return {\n x: Math.min(Math.max(BADGE_EDGE_INSET_PX, x), maxX),\n y: Math.min(Math.max(BADGE_EDGE_INSET_PX, y), maxY),\n };\n}\n\n/** Recompute the popover anchor CSS based on which quadrant of the\n * viewport the badge currently sits in. Mirrors badgeCornerStyles'\n * rule: popover grows AWAY from the nearer edge so it never escapes\n * the viewport. Used after a drag so the popover keeps opening\n * inward even when the badge is no longer in its starting corner. */\nfunction popoverCssForRect(rect: { left: number; top: number; width: number; height: number }): string {\n const vw = typeof window !== \"undefined\" ? window.innerWidth : 1024;\n const vh = typeof window !== \"undefined\" ? window.innerHeight : 768;\n const cx = rect.left + rect.width / 2;\n const cy = rect.top + rect.height / 2;\n const verticalAnchor =\n cy < vh / 2\n ? \"top:calc(100% + 8px);bottom:auto\"\n : \"bottom:calc(100% + 8px);top:auto\";\n const horizontalAnchor = cx < vw / 2 ? \"left:0;right:auto\" : \"right:0;left:auto\";\n return `${verticalAnchor};${horizontalAnchor}`;\n}\n\nfunction mountActivityBadge(opts: MountOpts): HTMLButtonElement {\n const { id, user, dashboardUrl, tracker, position, onSignOut } = opts;\n const corners = badgeCornerStyles(position);\n\n // `currentPopoverCss` starts at the corner-derived anchor and gets\n // overwritten when the user drags the badge to a new quadrant.\n // createPopover() reads this at popover instantiation time; if the\n // popover was already built before a drag, the drag handler also\n // patches popover.style directly so the next open uses the new anchor.\n let currentPopoverCss = corners.popover;\n // Inject the LED pulse keyframes at mount time. Previously this\n // lived inside createPopover() — which only runs on first hover —\n // so the LED's `animation` CSS referenced a keyframe rule that\n // didn't exist until the user hovered, and the browser silently\n // skipped the animation. Result: LED stayed solid until hover,\n // exactly matching the bug report.\n ensurePulseKeyframes();\n const el = document.createElement(\"button\");\n el.id = id;\n el.type = \"button\";\n el.setAttribute(\"aria-label\", \"RunApps activity status\");\n el.style.cssText = [\n \"position:fixed\",\n corners.badge,\n \"z-index:2147483647\",\n \"display:flex\",\n \"align-items:center\",\n \"justify-content:center\",\n \"padding:5px\",\n \"border-radius:50%\",\n \"font:500 12px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif\",\n \"color:#fff\",\n \"background:rgba(15,15,20,0.55)\",\n \"backdrop-filter:blur(10px) saturate(1.2)\",\n \"-webkit-backdrop-filter:blur(10px) saturate(1.2)\",\n \"border:1px solid rgba(255,255,255,0.12)\",\n \"box-shadow:0 4px 16px rgba(0,0,0,0.15)\",\n \"user-select:none\",\n \"-webkit-user-select:none\",\n // Touch: without this the browser claims the first finger move as a\n // page scroll and fires pointercancel, so the badge could not be\n // dragged on phones. No callout either: a long press is a drag.\n \"touch-action:none\",\n \"-webkit-touch-callout:none\",\n \"overflow:visible\", // popover spills out the top\n \"cursor:pointer\",\n \"appearance:none\",\n \"-webkit-appearance:none\",\n \"transition:background-color 120ms ease-out, box-shadow 120ms ease-out\",\n ].join(\";\");\n el.addEventListener(\"mouseenter\", () => {\n el.style.backgroundColor = \"rgba(30,30,40,0.7)\";\n el.style.boxShadow = \"0 6px 20px rgba(0,0,0,0.22)\";\n });\n el.addEventListener(\"mouseleave\", () => {\n el.style.backgroundColor = \"rgba(15,15,20,0.55)\";\n el.style.boxShadow = \"0 4px 16px rgba(0,0,0,0.15)\";\n });\n\n // ─── Avatar with ring + LED ────────────────────────────────\n const avatarWrap = document.createElement(\"span\");\n avatarWrap.style.cssText = [\n \"position:relative\",\n \"width:26px;height:26px\",\n \"flex-shrink:0\",\n \"display:inline-flex;align-items:center;justify-content:center\",\n ].join(\";\");\n\n // Progress ring (SVG). Hidden at idle so we don't paint when\n // nothing's happening. Stroke offset / dasharray animate via\n // direct style writes from the redraw loop.\n const ringNS = \"http://www.w3.org/2000/svg\";\n const ring = document.createElementNS(ringNS, \"svg\");\n ring.setAttribute(\"viewBox\", \"0 0 26 26\");\n ring.setAttribute(\"width\", \"26\");\n ring.setAttribute(\"height\", \"26\");\n ring.style.cssText = [\n \"position:absolute;inset:0\",\n \"pointer-events:none\",\n \"opacity:0\",\n \"transition:opacity 200ms ease-out\",\n ].join(\";\");\n const ringTrack = document.createElementNS(ringNS, \"circle\");\n ringTrack.setAttribute(\"cx\", \"13\");\n ringTrack.setAttribute(\"cy\", \"13\");\n ringTrack.setAttribute(\"r\", \"12\");\n ringTrack.setAttribute(\"fill\", \"none\");\n ringTrack.setAttribute(\"stroke\", \"rgba(255,255,255,0.08)\");\n ringTrack.setAttribute(\"stroke-width\", \"1.5\");\n const ringArc = document.createElementNS(ringNS, \"circle\");\n ringArc.setAttribute(\"cx\", \"13\");\n ringArc.setAttribute(\"cy\", \"13\");\n ringArc.setAttribute(\"r\", \"12\");\n ringArc.setAttribute(\"fill\", \"none\");\n ringArc.setAttribute(\"stroke\", \"#3b82f6\");\n ringArc.setAttribute(\"stroke-width\", \"2\");\n ringArc.setAttribute(\"stroke-linecap\", \"round\");\n // Rotate origin = center, start arc from 12 o'clock so progress\n // sweeps clockwise like every progress UI on the planet.\n ringArc.setAttribute(\"transform\", \"rotate(-90 13 13)\");\n // Circumference = 2πr ≈ 75.4. dasharray=circumference + dashoffset\n // drives the arc length.\n const RING_CIRC = 2 * Math.PI * 12;\n ringArc.setAttribute(\"stroke-dasharray\", String(RING_CIRC));\n ringArc.setAttribute(\"stroke-dashoffset\", String(RING_CIRC));\n ring.appendChild(ringTrack);\n ring.appendChild(ringArc);\n\n const avatar = document.createElement(\"div\");\n avatar.textContent = (user.name || \"?\").charAt(0).toUpperCase();\n avatar.style.cssText = [\n \"width:22px;height:22px;border-radius:50%;flex-shrink:0\",\n \"display:flex;align-items:center;justify-content:center\",\n \"background:linear-gradient(135deg,#3b82f6,#8b5cf6)\",\n \"font-size:11px;font-weight:600;color:#fff\",\n \"position:relative\",\n ].join(\";\");\n\n // LED status dot — absolute-positioned over the avatar's\n // bottom-right corner. Same trick Discord uses for online status.\n const led = document.createElement(\"span\");\n led.style.cssText = [\n \"position:absolute\",\n \"bottom:-1px;right:-1px\",\n \"width:7px;height:7px;border-radius:50%\",\n \"background:rgba(255,255,255,0.2)\",\n \"border:1.5px solid rgba(15,15,20,1)\",\n \"box-shadow:0 0 0 0 transparent\",\n // Hidden at rest: it only appears while something is happening\n // (syncing, offline, an error, the green flash after a sync).\n \"opacity:0\",\n \"transition:background-color 150ms ease-out, box-shadow 200ms ease-out, opacity 200ms ease-out\",\n \"pointer-events:none\",\n ].join(\";\");\n\n avatarWrap.appendChild(ring);\n avatarWrap.appendChild(avatar);\n avatarWrap.appendChild(led);\n\n el.appendChild(avatarWrap);\n\n // ─── Popover (lazy, append on first open) ──────────────────\n //\n // Open / close has two modes:\n // - hover: 400ms-grace open on badge enter; 300ms-grace close\n // when neither badge NOR popover is hovered. The grace lets\n // the user cross the 8px gap between badge and popover without\n // losing the popover. Standard tooltip pattern.\n // - click: sticky — stays open until the user clicks outside.\n // Useful for reading + copying values out of the panel.\n let popover: HTMLDivElement | null = null;\n let popoverOpen = false;\n let popoverOpenedBy: \"hover\" | \"click\" | null = null;\n let openTimer: number | null = null;\n let closeTimer: number | null = null;\n\n const cancelOpen = () => {\n if (openTimer !== null) {\n window.clearTimeout(openTimer);\n openTimer = null;\n }\n };\n const cancelClose = () => {\n if (closeTimer !== null) {\n window.clearTimeout(closeTimer);\n closeTimer = null;\n }\n };\n const scheduleClose = () => {\n cancelClose();\n closeTimer = window.setTimeout(() => {\n // Re-check the mode — a click between schedule and fire turns\n // a hover-pending close into a sticky open.\n if (popoverOpenedBy === \"hover\") closePopover();\n }, 300);\n };\n\n const openPopover = (by: \"hover\" | \"click\") => {\n cancelOpen();\n cancelClose();\n if (popoverOpen) {\n // Upgrade a hover-open to click-stickiness if the user\n // clicked while the popover was already hover-shown.\n if (by === \"click\") popoverOpenedBy = \"click\";\n return;\n }\n if (!popover) {\n popover = createPopover(user, dashboardUrl, currentPopoverCss, () => {\n // Tear down our DOM before the auth flip runs so the popover\n // doesn't briefly show \"signed out\" state to a user who's\n // about to navigate away. closePopover() also removes the\n // hover/click handlers' state.\n closePopover();\n onSignOut();\n });\n // Attach hover handlers ONCE — the popover element is reused\n // across open/close cycles, so .remove() / .appendChild()\n // doesn't shake the listeners off.\n popover.addEventListener(\"mouseenter\", cancelClose);\n popover.addEventListener(\"mouseleave\", () => {\n if (popoverOpenedBy === \"hover\") scheduleClose();\n });\n }\n el.appendChild(popover);\n popoverOpen = true;\n popoverOpenedBy = by;\n // Schedule one immediate redraw so the popover shows current\n // state, not a stale snapshot from the last close.\n requestRedraw();\n };\n\n const closePopover = () => {\n cancelClose();\n if (!popoverOpen) return;\n popover?.remove();\n popoverOpen = false;\n popoverOpenedBy = null;\n };\n\n el.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n popoverOpen ? closePopover() : openPopover(\"click\");\n });\n // Hover-open with 400ms delay — matches macOS tray tooltips.\n el.addEventListener(\"mouseenter\", () => {\n cancelClose(); // moving back onto badge cancels a pending close\n cancelOpen();\n openTimer = window.setTimeout(() => {\n if (!popoverOpen) openPopover(\"hover\");\n }, 400);\n });\n el.addEventListener(\"mouseleave\", () => {\n cancelOpen();\n // Only the hover branch auto-closes; click-opened stays sticky\n // and dismisses via the click-outside handler below.\n if (popoverOpenedBy === \"hover\") scheduleClose();\n });\n // Click anywhere outside the badge closes the popover.\n document.addEventListener(\"click\", (e) => {\n if (popoverOpen && !el.contains(e.target as Node)) closePopover();\n });\n\n // ─── Drag-to-move + cookie persistence ─────────────────────\n //\n // Two intents share the pointer-down on the badge:\n // 1. Quick tap → open popover (existing click handler).\n // 2. Hold-and-drag → reposition the badge.\n //\n // Disambiguate by movement distance: until the pointer travels\n // BADGE_DRAG_THRESHOLD_PX from its starting point we treat the\n // gesture as a pending click and let the existing click handler\n // run on pointerup. Past that threshold we flip to drag mode —\n // start moving the badge, claim the pointer via setPointerCapture\n // so we keep getting moves even when the pointer leaves the badge\n // bounds, and on pointerup persist the final coords + suppress\n // the click event the browser would otherwise synthesize.\n let dragStartClientX = 0;\n let dragStartClientY = 0;\n let dragStartBadgeLeft = 0;\n let dragStartBadgeTop = 0;\n let pointerDown = false;\n let isDragging = false;\n let suppressNextClick = false;\n\n /** Apply absolute pixel position to the badge, clearing the corner\n * anchors set at mount. Used both by the drag handler (live update)\n * and the cookie-restore path (mount-time hydrate). */\n const applyAbsolutePos = (x: number, y: number) => {\n el.style.left = `${x}px`;\n el.style.top = `${y}px`;\n el.style.right = \"auto\";\n el.style.bottom = \"auto\";\n };\n\n /** Recompute + apply the popover anchor based on the badge's current\n * position in the viewport. Called after a drag so the popover\n * always grows away from the nearest viewport edge. If the popover\n * was already instantiated, patches its inline style in place. */\n const recomputePopoverAnchor = () => {\n const r = el.getBoundingClientRect();\n currentPopoverCss = popoverCssForRect({\n left: r.left,\n top: r.top,\n width: r.width,\n height: r.height,\n });\n if (popover) {\n // Reset the four anchor properties before re-applying so we\n // don't leave stale `right` / `bottom` declarations stuck on\n // the element from the previous anchor.\n popover.style.top = \"\";\n popover.style.bottom = \"\";\n popover.style.left = \"\";\n popover.style.right = \"\";\n for (const decl of currentPopoverCss.split(\";\")) {\n const [prop, val] = decl.split(\":\");\n if (prop && val) popover.style.setProperty(prop.trim(), val.trim());\n }\n }\n };\n\n el.addEventListener(\"pointerdown\", (e) => {\n // Only the primary button drags — secondary clicks should fall\n // through (browsers may show a context menu, etc.).\n if (e.button !== 0) return;\n pointerDown = true;\n isDragging = false;\n dragStartClientX = e.clientX;\n dragStartClientY = e.clientY;\n const r = el.getBoundingClientRect();\n dragStartBadgeLeft = r.left;\n dragStartBadgeTop = r.top;\n });\n\n el.addEventListener(\"pointermove\", (e) => {\n if (!pointerDown) return;\n const dx = e.clientX - dragStartClientX;\n const dy = e.clientY - dragStartClientY;\n if (!isDragging) {\n if (Math.abs(dx) < BADGE_DRAG_THRESHOLD_PX && Math.abs(dy) < BADGE_DRAG_THRESHOLD_PX) {\n return; // still within click slop — keep treating as a tap\n }\n // Cross the threshold → enter drag mode. Capture the pointer\n // so further moves arrive even when the pointer escapes the\n // badge bounds (fast drags leave the small pill quickly).\n isDragging = true;\n try {\n el.setPointerCapture(e.pointerId);\n } catch {\n /* no active pointer of that id (synthetic events, odd WebViews): drag still works while the finger stays on the badge */\n }\n el.style.cursor = \"grabbing\";\n // Pre-emptively close any open popover — keeping it pinned to a\n // moving badge looks janky and a drag is clearly a different\n // intent than \"look at the popover\".\n if (popoverOpen) closePopover();\n }\n const rect = el.getBoundingClientRect();\n const clamped = clampToViewport(\n dragStartBadgeLeft + dx,\n dragStartBadgeTop + dy,\n rect.width,\n rect.height,\n );\n applyAbsolutePos(clamped.x, clamped.y);\n });\n\n const endDrag = (e: PointerEvent) => {\n if (!pointerDown) return;\n pointerDown = false;\n if (!isDragging) return;\n isDragging = false;\n el.style.cursor = \"pointer\";\n try {\n if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);\n } catch {\n /* same */\n }\n // Persist final position. Re-read the rect so what we store is\n // exactly what's on screen — clampToViewport may have shaved\n // the trailing few pixels.\n const r = el.getBoundingClientRect();\n writeBadgePosCookie(r.left, r.top);\n recomputePopoverAnchor();\n // The browser will synthesize a click on the badge from this\n // pointer sequence (pointer went down + up on the same element).\n // Swallow it so a drag doesn't immediately open the popover.\n suppressNextClick = true;\n };\n el.addEventListener(\"pointerup\", endDrag);\n el.addEventListener(\"pointercancel\", endDrag);\n\n // Capture-phase click swallower — runs BEFORE the existing click\n // handler that toggles the popover, so we can stop a drag-tail\n // click without modifying the open/close logic itself.\n el.addEventListener(\n \"click\",\n (e) => {\n if (suppressNextClick) {\n suppressNextClick = false;\n e.stopPropagation();\n e.preventDefault();\n }\n },\n true,\n );\n\n // Hydrate from cookie. After all the listeners and the corner-based\n // default style are in place, fast-forward to the user's last saved\n // position if any. Clamp to the current viewport so a saved spot\n // from a wider monitor doesn't put the badge off-canvas.\n const savedPos = readBadgePosCookie();\n if (savedPos) {\n // We need el's measured size to clamp; but el isn't in the DOM\n // yet, so we lean on a defer to next frame after first append.\n // Until then, apply the raw saved coords (the corner-default\n // styles still apply if these are absurd; clamp will fix it).\n applyAbsolutePos(savedPos.x, savedPos.y);\n // Queue a one-shot post-mount clamp + popover-anchor recompute.\n // requestAnimationFrame fires after the parent appendChild\n // brings the badge into layout, so getBoundingClientRect works.\n requestAnimationFrame(() => {\n const r = el.getBoundingClientRect();\n const clamped = clampToViewport(savedPos.x, savedPos.y, r.width, r.height);\n applyAbsolutePos(clamped.x, clamped.y);\n recomputePopoverAnchor();\n });\n }\n\n // ─── Redraw loop ───────────────────────────────────────────\n //\n // Idle: zero work. Active OR popover open: redraw at ~6 Hz so\n // the ring sweep and per-call elapsed counters stay live without\n // burning CPU. Token-arrival events also push a redraw out of\n // band so the ring jumps as soon as a chunk lands.\n let rafScheduled = false;\n let intervalHandle: number | null = null;\n let seenChangeAt: number | undefined = undefined;\n\n const reconcile = () => {\n rafScheduled = false;\n if (!tracker) {\n // No event bus → static badge. Hide ring + LED.\n ring.style.opacity = \"0\";\n led.style.background = \"rgba(255,255,255,0.2)\";\n led.style.opacity = \"0\";\n return;\n }\n const snap = tracker.snapshot();\n // lastChangeAt is only ever set by a sync in this page's lifetime,\n // so every new value is a change the user just saw happen.\n const changeAt = snap.sync?.lastChangeAt;\n if (changeAt && changeAt !== seenChangeAt) {\n seenChangeAt = changeAt;\n syncedPing(avatarWrap, led);\n }\n updateRing(ring, ringArc, RING_CIRC, snap);\n updateLED(led, snap);\n if (popoverOpen && popover) updatePopover(popover, snap);\n // Schedule the next paint only if there's reason. When the\n // popover is open we want the elapsed seconds to tick; when\n // active we want the ring to keep sweeping.\n const needsLoop = snap.status !== \"idle\" || popoverOpen;\n if (needsLoop && intervalHandle === null) {\n intervalHandle = window.setInterval(requestRedraw, 160);\n } else if (!needsLoop && intervalHandle !== null) {\n window.clearInterval(intervalHandle);\n intervalHandle = null;\n }\n };\n\n function requestRedraw() {\n if (rafScheduled) return;\n rafScheduled = true;\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(reconcile);\n } else {\n setTimeout(reconcile, 0);\n }\n }\n\n // Drive redraws off the tracker's onChange — fires synchronously\n // after every start / delta / end / error so the LED + ring update\n // within an animation frame of the event, not when the user\n // happens to hover the badge. requestRedraw() coalesces multiple\n // notifications into one rAF paint, so a chunky stream (30+ deltas\n // per second) still paints at most once per frame.\n if (tracker) {\n tracker.onChange(requestRedraw);\n }\n\n // Initial paint so the user sees their LED/ring state at mount\n // even before any event fires.\n requestRedraw();\n\n return el;\n}\n\nfunction updateRing(\n ring: SVGSVGElement,\n arc: SVGCircleElement,\n circ: number,\n snap: ActivitySnapshot,\n): void {\n if (snap.active.length === 0) {\n ring.style.opacity = \"0\";\n return;\n }\n ring.style.opacity = \"1\";\n // Pick the most informative active call for the ring (highest\n // tokens/sec, falling back to oldest). The popover lists every\n // call individually; this is just for the at-a-glance glow.\n const primary = snap.active.reduce((best, c) => (c.tokensPerSec > best.tokensPerSec ? c : best), snap.active[0]!);\n if (primary.streaming && primary.tokensPerSec > 0) {\n // Active streaming → arc length scales with tokens/sec, capped\n // at the full ring at ~60 tok/s (typical chat ceiling).\n const fraction = Math.min(primary.tokensPerSec / 60, 1);\n arc.setAttribute(\"stroke-dashoffset\", String(circ * (1 - fraction)));\n arc.setAttribute(\"stroke\", \"#3b82f6\");\n } else if (snap.status === \"error\") {\n arc.setAttribute(\"stroke-dashoffset\", \"0\");\n arc.setAttribute(\"stroke\", \"#ef4444\");\n } else {\n // Non-streaming or pre-first-token: indeterminate sweep — just\n // show a quarter-arc that rotates via CSS keyframes (added\n // below as a one-shot class).\n arc.setAttribute(\"stroke-dashoffset\", String(circ * 0.75));\n arc.setAttribute(\"stroke\", \"#a78bfa\");\n }\n}\n\n/** Until when the LED shows green after a sync that moved data. */\nlet syncFlashUntil = 0;\n\n/**\n * A sync just pushed or applied something: a green ring swells out of\n * the avatar once, and the LED holds green for a moment. Quiet syncs\n * (nothing to move) show nothing — the badge would otherwise blink\n * every minute.\n */\nfunction syncedPing(avatarWrap: HTMLElement, led: HTMLSpanElement): void {\n const ping = document.createElement(\"span\");\n ping.style.cssText = [\n \"position:absolute;inset:0\",\n \"border-radius:50%\",\n \"border:2px solid #10b981\",\n \"pointer-events:none\",\n \"animation:__runapps_sync_ping__ 700ms ease-out forwards\",\n ].join(\";\");\n ping.addEventListener(\"animationend\", () => ping.remove());\n avatarWrap.appendChild(ping);\n setTimeout(() => ping.remove(), 1000); // in case animationend never fires\n syncFlashUntil = Date.now() + 1200;\n led.style.opacity = \"1\";\n led.style.background = \"#10b981\";\n led.style.boxShadow = \"0 0 0 2px rgba(16,185,129,0.35)\";\n led.style.animation = \"\";\n setTimeout(() => {\n // Back to the resting colour once the flash is over.\n if (Date.now() >= syncFlashUntil) {\n led.style.background = \"rgba(255,255,255,0.2)\";\n led.style.opacity = \"0\";\n led.style.boxShadow = \"0 0 0 0 transparent\";\n }\n }, 1250);\n}\n\nfunction updateLED(led: HTMLSpanElement, snap: ActivitySnapshot): void {\n led.style.opacity = \"1\"; // the resting branches below hide it again\n switch (snap.status) {\n case \"idle\": {\n // Nothing in flight: the LED shows the sync instead — blue pulse\n // while syncing, amber while changes wait offline or after a\n // failed attempt, grey when everything is on the server.\n const sync = snap.sync;\n if (Date.now() < syncFlashUntil) {\n led.style.background = \"#10b981\";\n led.style.boxShadow = \"0 0 0 2px rgba(16,185,129,0.35)\";\n led.style.animation = \"\";\n } else if (sync && sync.state === \"syncing\") {\n led.style.background = \"#3b82f6\";\n led.style.boxShadow = \"0 0 0 0 rgba(59,130,246,0.7)\";\n led.style.animation = \"__runapps_led_pulse__ 1.2s ease-out infinite\";\n } else if (sync && (sync.state === \"offline\" || sync.state === \"error\")) {\n led.style.background = \"#f59e0b\";\n led.style.boxShadow = \"0 0 0 2px rgba(245,158,11,0.3)\";\n led.style.animation = \"\";\n } else {\n led.style.background = \"rgba(255,255,255,0.2)\";\n led.style.opacity = \"0\";\n led.style.boxShadow = \"0 0 0 0 transparent\";\n led.style.animation = \"\";\n }\n break;\n }\n case \"active\":\n led.style.background = \"#3b82f6\";\n // CSS-driven pulse via box-shadow ring expanding outward —\n // single keyframe loop, cheaper than animating opacity.\n led.style.boxShadow = \"0 0 0 0 rgba(59,130,246,0.7)\";\n led.style.animation = \"__runapps_led_pulse__ 1.2s ease-out infinite\";\n break;\n case \"error\":\n led.style.background = \"#ef4444\";\n led.style.boxShadow = \"0 0 0 2px rgba(239,68,68,0.3)\";\n led.style.animation = \"\";\n break;\n }\n}\n\nfunction createPopover(user: BrowserUser, dashboardUrl: string, anchorCss: string, onSignOut: () => void): HTMLDivElement {\n const pop = document.createElement(\"div\");\n pop.style.cssText = [\n \"position:absolute\",\n // Anchor varies by badge corner — see badgeCornerStyles. Splits\n // top/bottom + left/right so the popover always grows TOWARDS\n // the viewport interior.\n anchorCss,\n \"min-width:280px;max-width:340px\",\n \"padding:10px 12px\",\n \"border-radius:10px\",\n \"background:rgba(15,15,20,0.92)\",\n \"backdrop-filter:blur(20px) saturate(1.3)\",\n \"-webkit-backdrop-filter:blur(20px) saturate(1.3)\",\n \"border:1px solid rgba(255,255,255,0.12)\",\n \"box-shadow:0 10px 30px rgba(0,0,0,0.4)\",\n \"color:#fff\",\n \"font:400 11px/1.4 -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif\",\n \"cursor:default\",\n \"text-align:left\",\n ].join(\";\");\n pop.setAttribute(\"data-runapps-popover\", \"1\");\n pop.addEventListener(\"click\", (e) => e.stopPropagation());\n\n // Who is signed in. The badge itself is only the avatar circle, so\n // the full name lives here, above the activity sections.\n const header = document.createElement(\"div\");\n header.style.cssText = [\n \"display:flex;align-items:center;gap:8px\",\n \"margin-bottom:10px;padding-bottom:8px\",\n \"border-bottom:1px solid rgba(255,255,255,0.08)\",\n ].join(\";\");\n const headAvatar = document.createElement(\"div\");\n headAvatar.textContent = (user.name || \"?\").charAt(0).toUpperCase();\n headAvatar.style.cssText = [\n \"width:24px;height:24px;border-radius:50%;flex-shrink:0\",\n \"display:flex;align-items:center;justify-content:center\",\n \"background:linear-gradient(135deg,#3b82f6,#8b5cf6)\",\n \"font-size:11px;font-weight:600;color:#fff\",\n ].join(\";\");\n const headName = document.createElement(\"div\");\n headName.textContent = user.name || \"Signed in\";\n headName.title = user.name || \"\";\n headName.style.cssText = \"font-size:12px;font-weight:500;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\";\n const headSub = document.createElement(\"div\");\n headSub.textContent = \"Signed in with RunApps\";\n headSub.style.cssText = \"font-size:10px;color:rgba(255,255,255,0.5)\";\n const headText = document.createElement(\"div\");\n headText.style.cssText = \"min-width:0\";\n headText.appendChild(headName);\n headText.appendChild(headSub);\n header.appendChild(headAvatar);\n header.appendChild(headText);\n pop.appendChild(header);\n\n // The popover's content is rebuilt on every redraw to keep DOM\n // diffing simple. ~3 sections × ~6 children = ~20 nodes, no\n // measurable cost at the 6 Hz redraw rate.\n const inner = document.createElement(\"div\");\n inner.setAttribute(\"data-runapps-popover-content\", \"1\");\n pop.appendChild(inner);\n\n const footer = document.createElement(\"div\");\n footer.style.cssText = [\n \"margin-top:10px;padding-top:8px\",\n \"border-top:1px solid rgba(255,255,255,0.08)\",\n \"display:flex;justify-content:space-between;align-items:center\",\n \"font-size:10px;color:rgba(255,255,255,0.6)\",\n ].join(\";\");\n const dash = document.createElement(\"a\");\n dash.textContent = \"Open dashboard →\";\n dash.href = dashboardUrl;\n dash.target = \"_blank\";\n dash.rel = \"noopener noreferrer\";\n dash.style.cssText = \"color:#a78bfa;text-decoration:none\";\n footer.appendChild(dash);\n\n // Sign-out lives opposite the dashboard link. Styled subtle on\n // purpose — it's a destructive-ish action (drops the token cache,\n // forces the next API call into the signIn redirect) but not\n // dangerous, and we don't want it to compete visually with the\n // dashboard CTA. Hover highlights it red as a \"this is the action\n // that ends your session\" cue.\n const signOutBtn = document.createElement(\"button\");\n signOutBtn.type = \"button\";\n signOutBtn.textContent = \"Sign out\";\n signOutBtn.style.cssText = [\n \"background:none;border:0;padding:0;margin:0\",\n \"font:inherit;cursor:pointer\",\n \"color:rgba(255,255,255,0.55)\",\n \"transition:color 120ms ease\",\n ].join(\";\");\n signOutBtn.addEventListener(\"mouseenter\", () => {\n signOutBtn.style.color = \"#f87171\";\n });\n signOutBtn.addEventListener(\"mouseleave\", () => {\n signOutBtn.style.color = \"rgba(255,255,255,0.55)\";\n });\n signOutBtn.addEventListener(\"click\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n onSignOut();\n });\n footer.appendChild(signOutBtn);\n\n pop.appendChild(footer);\n return pop;\n}\n\nfunction updatePopover(pop: HTMLDivElement, snap: ActivitySnapshot): void {\n const inner = pop.querySelector('[data-runapps-popover-content]') as HTMLDivElement | null;\n if (!inner) return;\n inner.innerHTML = \"\";\n const now = Date.now();\n inner.appendChild(renderActiveSection(snap, now));\n inner.appendChild(renderRecentSection(snap, now));\n if (snap.sync) inner.appendChild(renderSyncSection(snap.sync, now));\n inner.appendChild(renderSessionSection(snap.session, now));\n}\n\nfunction renderSyncSection(sync: NonNullable<ActivitySnapshot[\"sync\"]>, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.style.marginBottom = \"10px\";\n const hint = sync.lastSyncedAt ? `synced ${relativeTime(now - sync.lastSyncedAt)}` : \"—\";\n sec.appendChild(sectionLabel(\"Sync\", hint));\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:8px;padding:3px 0;font-size:10px\";\n const left = document.createElement(\"span\");\n const dot = document.createElement(\"span\");\n dot.textContent = \"● \";\n const label: Record<string, [string, string]> = {\n off: [\"Not signed in\", \"rgba(255,255,255,0.4)\"],\n idle: [\"Up to date\", \"#10b981\"],\n syncing: [\"Syncing…\", \"#3b82f6\"],\n offline: [\"Offline\", \"#f59e0b\"],\n error: [\"Retrying\", \"#f59e0b\"],\n };\n const [text, color] = label[sync.state] ?? [\"\", \"#fff\"];\n dot.style.color = color;\n left.style.color = \"#fff\";\n left.appendChild(dot);\n left.appendChild(document.createTextNode(text));\n const right = document.createElement(\"span\");\n right.style.cssText = \"color:rgba(255,255,255,0.7);font-variant-numeric:tabular-nums\";\n right.textContent = sync.pending > 0 ? `${sync.pending} pending` : \"\";\n right.title = sync.error ?? \"\";\n row.appendChild(left);\n row.appendChild(right);\n sec.appendChild(row);\n return sec;\n}\n\nfunction renderActiveSection(snap: ActivitySnapshot, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.style.marginBottom = \"10px\";\n sec.appendChild(sectionLabel(\"Active\", snap.active.length > 0 ? `${snap.active.length} in flight` : \"—\"));\n if (snap.active.length === 0) {\n const empty = document.createElement(\"div\");\n empty.style.cssText = \"color:rgba(255,255,255,0.4);font-size:10px;padding:2px 0\";\n empty.textContent = \"No calls in flight.\";\n sec.appendChild(empty);\n return sec;\n }\n for (const c of snap.active) {\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:8px;padding:3px 0\";\n const left = document.createElement(\"span\");\n left.style.cssText = \"color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px\";\n left.textContent = c.model;\n const right = document.createElement(\"span\");\n right.style.cssText = \"color:rgba(255,255,255,0.7);font-variant-numeric:tabular-nums;font-size:10px\";\n const elapsed = ((now - c.startedAt) / 1000).toFixed(1);\n const rate = c.tokensPerSec > 0 ? ` · ${Math.round(c.tokensPerSec)} tok/s` : \"\";\n right.textContent = `${elapsed}s${rate}`;\n row.appendChild(left);\n row.appendChild(right);\n sec.appendChild(row);\n }\n return sec;\n}\n\nfunction renderRecentSection(snap: ActivitySnapshot, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.style.marginBottom = \"10px\";\n sec.appendChild(sectionLabel(\"Recent\", snap.recent.length > 0 ? `last ${Math.min(snap.recent.length, 5)}` : \"—\"));\n if (snap.recent.length === 0) {\n const empty = document.createElement(\"div\");\n empty.style.cssText = \"color:rgba(255,255,255,0.4);font-size:10px;padding:2px 0\";\n empty.textContent = \"Nothing yet this session.\";\n sec.appendChild(empty);\n return sec;\n }\n for (const c of snap.recent.slice(0, 5)) {\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:8px;padding:3px 0\";\n const left = document.createElement(\"span\");\n left.style.cssText = \"color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px\";\n // Prefix bullet by status — cheap visual scan.\n const bullet = document.createElement(\"span\");\n bullet.textContent = c.ok ? \"✓ \" : \"✗ \";\n bullet.style.color = c.ok ? \"#10b981\" : \"#ef4444\";\n left.appendChild(bullet);\n left.appendChild(document.createTextNode(c.model));\n const right = document.createElement(\"span\");\n right.style.cssText = \"color:rgba(255,255,255,0.7);font-variant-numeric:tabular-nums;font-size:10px\";\n const latency = `${(c.latencyMs / 1000).toFixed(1)}s`;\n const cost = c.costUSD !== undefined ? ` · $${c.costUSD.toFixed(4)}` : \"\";\n const ago = ` · ${relativeTime(now - c.endedAt)}`;\n right.textContent = `${latency}${cost}${ago}`;\n row.appendChild(left);\n row.appendChild(right);\n sec.appendChild(row);\n }\n return sec;\n}\n\nfunction renderSessionSection(s: SessionStats, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.appendChild(sectionLabel(\"Session\", relativeTime(now - s.startedAt)));\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:12px;color:rgba(255,255,255,0.85);font-size:10px\";\n const calls = document.createElement(\"span\");\n calls.textContent = `${s.totalCalls} call${s.totalCalls === 1 ? \"\" : \"s\"}`;\n const cost = document.createElement(\"span\");\n cost.textContent = `$${s.totalCostUSD.toFixed(4)}`;\n const errors = document.createElement(\"span\");\n errors.textContent = s.errorCount > 0 ? `${s.errorCount} error${s.errorCount === 1 ? \"\" : \"s\"}` : \"0 errors\";\n if (s.errorCount > 0) errors.style.color = \"#ef4444\";\n row.appendChild(calls);\n row.appendChild(cost);\n row.appendChild(errors);\n sec.appendChild(row);\n return sec;\n}\n\nfunction sectionLabel(title: string, hint: string): HTMLDivElement {\n const lbl = document.createElement(\"div\");\n lbl.style.cssText = [\n \"display:flex;justify-content:space-between;align-items:baseline\",\n \"margin-bottom:4px\",\n \"color:rgba(255,255,255,0.55)\",\n \"font-size:10px;text-transform:uppercase;letter-spacing:0.05em\",\n ].join(\";\");\n const t = document.createElement(\"span\");\n t.textContent = title;\n const h = document.createElement(\"span\");\n h.textContent = hint;\n h.style.cssText = \"font-size:9px;letter-spacing:0\";\n lbl.appendChild(t);\n lbl.appendChild(h);\n return lbl;\n}\n\nfunction relativeTime(ms: number): string {\n if (ms < 1500) return \"just now\";\n if (ms < 60_000) return `${Math.round(ms / 1000)}s ago`;\n if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m ago`;\n return `${Math.round(ms / 3_600_000)}h ago`;\n}\n","// sync/hook.ts — observe an app's IndexedDB writes without the app\n// knowing.\n//\n// There is no browser event for \"an object store changed\", so the SDK\n// wraps the few prototype methods every write goes through — on the\n// factory, the database, object stores and cursors. Dexie, idb,\n// idb-keyval, zustand's IndexedDB storage and hand-written code all end\n// up in these methods, so they are all observed.\n//\n// What the hook does:\n// open() returns a request that forwards the real one's\n// events, and delays `success` until `onOpen` (the\n// engine's first pull) has run, so a new device sees\n// its data on the first read.\n// transaction() attaches a change collector to readwrite\n// transactions on observed databases.\n// put/add/delete/clear, cursor.update/delete\n// record the committed change into the collector; the\n// batch is handed to `onCommit` only when the\n// transaction completes, so an aborted write is never\n// synced. A put also reads the record's previous value\n// first (same transaction), so the engine can tell which\n// fields the write changed and merge field by field.\n//\n// Writes the engine itself makes while applying remote changes go\n// through its own connection, which is never tagged, so they are not\n// recorded again.\n//\n// Several subscribers (engines) can be installed at once; each sees the\n// databases its `include` admits, on the factory it was bound to (tests\n// run one \"device\" per factory; a page has one factory).\n\nimport { extractKey } from \"./codec.js\";\n\nexport type CommittedOp =\n | { store: string; kind: \"put\"; key: IDBValidKey; value: unknown; prior?: unknown }\n | { store: string; kind: \"del\"; key: IDBValidKey }\n | { store: string; kind: \"clear\" };\n\nexport interface HookOptions {\n /** Which databases (by name) to observe. */\n include: (dbName: string) => boolean;\n /** Stores to leave out of sync. */\n excludeStore: (dbName: string, store: string) => boolean;\n /** A readwrite transaction on an observed database committed. */\n onCommit: (dbName: string, ops: CommittedOp[]) => void;\n /** The app opened an observed database. The open request's success is\n * held until this resolves (or `openTimeoutMs` passes). */\n onOpen: (db: IDBDatabase, dbName: string) => Promise<void> | void;\n /** The app deleted an observed database. */\n onDeleteDatabase: (dbName: string) => void;\n openTimeoutMs: number;\n /** Only observe this factory's databases. Default: the global one at\n * install time. */\n factory?: IDBFactory;\n}\n\nexport interface InstalledHook {\n /** The factory's untouched open — for the SDK's own databases. */\n originalOpen: (name: string, version?: number) => IDBOpenDBRequest;\n uninstall(): void;\n}\n\ninterface Subscriber {\n opts: HookOptions;\n factory: IDBFactory | null;\n}\n\ninterface TxState {\n db: string;\n subs: Subscriber[];\n ops: CommittedOp[];\n}\n\ninterface DbInfo {\n name: string;\n subs: Subscriber[];\n}\n\nconst txState = new WeakMap<IDBTransaction, TxState>();\n/** IDBDatabase handles the app obtained through the hooked open. */\nconst observedDbs = new WeakMap<IDBDatabase, DbInfo>();\nconst subscribers = new Set<Subscriber>();\nlet originals: {\n open: IDBFactory[\"open\"];\n deleteDatabase: IDBFactory[\"deleteDatabase\"];\n transaction: IDBDatabase[\"transaction\"];\n put: IDBObjectStore[\"put\"];\n add: IDBObjectStore[\"add\"];\n get: IDBObjectStore[\"get\"];\n delete: IDBObjectStore[\"delete\"];\n clear: IDBObjectStore[\"clear\"];\n getAllKeys: IDBObjectStore[\"getAllKeys\"];\n cursorUpdate: IDBCursor[\"update\"];\n cursorDelete: IDBCursor[\"delete\"];\n} | null = null;\n\nfunction clone<T>(v: T): T {\n try {\n return typeof structuredClone === \"function\" ? structuredClone(v) : v;\n } catch {\n return v;\n }\n}\n\nfunction isPlainObject(v: unknown): boolean {\n if (v === null || typeof v !== \"object\") return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\n/** Minimal event with `target` pointing at the hooked request. */\nfunction makeEvent(type: string, init?: { oldVersion?: number; newVersion?: number | null }, target?: object): Event {\n let ev: Event;\n if (init && typeof IDBVersionChangeEvent === \"function\") {\n ev = new IDBVersionChangeEvent(type, { oldVersion: init.oldVersion, newVersion: init.newVersion ?? null });\n } else {\n ev = new Event(type, { bubbles: false, cancelable: type === \"error\" });\n if (init) Object.assign(ev, init);\n }\n if (target) {\n for (const p of [\"target\", \"currentTarget\", \"srcElement\"]) {\n try {\n Object.defineProperty(ev, p, { value: target, configurable: true });\n } catch {\n /* locked-down event implementation; listeners read request.result anyway */\n }\n }\n }\n return ev;\n}\n\ntype Handler = ((ev: Event) => unknown) | null;\n\n/**\n * Stand-in for the IDBOpenDBRequest the app would have received. It has\n * the same surface (result, error, readyState, transaction, source,\n * the four on* handlers, add/removeEventListener) and an\n * IDBOpenDBRequest prototype chain so `instanceof` holds.\n */\nclass HookedOpenRequest {\n readyState: \"pending\" | \"done\" = \"pending\";\n result: IDBDatabase | undefined = undefined;\n error: DOMException | null = null;\n transaction: IDBTransaction | null = null;\n readonly source: null = null;\n private listeners = new Map<string, Set<(ev: Event) => unknown>>();\n private handlers: Record<string, Handler> = { success: null, error: null, blocked: null, upgradeneeded: null };\n\n constructor(real: IDBOpenDBRequest, name: string, subs: Subscriber[]) {\n real.addEventListener(\"upgradeneeded\", (ev) => {\n const e = ev as IDBVersionChangeEvent;\n this.result = real.result;\n this.transaction = real.transaction;\n observedDbs.set(real.result, { name, subs });\n this.dispatch(makeEvent(\"upgradeneeded\", { oldVersion: e.oldVersion, newVersion: e.newVersion }, this));\n });\n real.addEventListener(\"blocked\", (ev) => {\n const e = ev as IDBVersionChangeEvent;\n this.dispatch(makeEvent(\"blocked\", { oldVersion: e.oldVersion, newVersion: e.newVersion }, this));\n });\n real.addEventListener(\"error\", () => {\n this.error = real.error;\n this.readyState = \"done\";\n this.transaction = null;\n this.dispatch(makeEvent(\"error\", undefined, this));\n });\n real.addEventListener(\"success\", () => {\n const db = real.result;\n observedDbs.set(db, { name, subs });\n this.transaction = null;\n let settled = false;\n const deliver = () => {\n if (settled) return;\n settled = true;\n this.result = db;\n this.readyState = \"done\";\n this.dispatch(makeEvent(\"success\", undefined, this));\n };\n // Hold success for the engines' first pull, bounded by a timeout\n // so an offline device still opens.\n const waits: Promise<void>[] = [];\n let timeout = 0;\n for (const s of subs) {\n try {\n const p = s.opts.onOpen(db, name);\n if (p && typeof (p as Promise<void>).then === \"function\") {\n waits.push((p as Promise<void>).catch(() => {}));\n timeout = Math.max(timeout, s.opts.openTimeoutMs);\n }\n } catch {\n /* a failing onOpen must not keep the app from its database */\n }\n }\n if (waits.length === 0) {\n deliver();\n return;\n }\n const timer = setTimeout(deliver, timeout);\n Promise.all(waits).then(() => {\n clearTimeout(timer);\n deliver();\n });\n });\n }\n\n addEventListener(type: string, listener: ((ev: Event) => unknown) | { handleEvent(ev: Event): unknown } | null): void {\n if (!listener) return;\n const fn = typeof listener === \"function\" ? listener : (ev: Event) => listener.handleEvent(ev);\n let set = this.listeners.get(type);\n if (!set) this.listeners.set(type, (set = new Set()));\n set.add(fn);\n (listener as { __rtFn?: (ev: Event) => unknown }).__rtFn = fn;\n }\n\n removeEventListener(type: string, listener: ((ev: Event) => unknown) | { handleEvent(ev: Event): unknown } | null): void {\n if (!listener) return;\n const fn = typeof listener === \"function\" ? listener : (listener as { __rtFn?: (ev: Event) => unknown }).__rtFn;\n if (fn) this.listeners.get(type)?.delete(fn);\n }\n\n dispatchEvent(ev: Event): boolean {\n this.dispatch(ev);\n return !ev.defaultPrevented;\n }\n\n private dispatch(ev: Event): void {\n const h = this.handlers[ev.type];\n if (h) {\n try {\n h.call(this, ev);\n } catch (err) {\n reportListenerError(err);\n }\n }\n for (const fn of this.listeners.get(ev.type) ?? []) {\n try {\n fn.call(this, ev);\n } catch (err) {\n reportListenerError(err);\n }\n }\n }\n\n get onsuccess(): Handler { return this.handlers.success ?? null; }\n set onsuccess(h: Handler) { this.handlers.success = h; }\n get onerror(): Handler { return this.handlers.error ?? null; }\n set onerror(h: Handler) { this.handlers.error = h; }\n get onblocked(): Handler { return this.handlers.blocked ?? null; }\n set onblocked(h: Handler) { this.handlers.blocked = h; }\n get onupgradeneeded(): Handler { return this.handlers.upgradeneeded ?? null; }\n set onupgradeneeded(h: Handler) { this.handlers.upgradeneeded = h; }\n}\n\nfunction reportListenerError(err: unknown): void {\n // Same as the browser: a throwing listener is reported, not swallowed\n // silently, and does not stop the other listeners.\n setTimeout(() => {\n throw err;\n }, 0);\n}\n\nif (typeof IDBOpenDBRequest === \"function\") {\n Object.setPrototypeOf(HookedOpenRequest.prototype, IDBOpenDBRequest.prototype);\n}\n\n/** Object store of a cursor, whether it was opened on a store or an index. */\nfunction cursorStore(cursor: IDBCursor): IDBObjectStore {\n const src = cursor.source as IDBObjectStore | IDBIndex;\n return typeof IDBIndex === \"function\" && src instanceof IDBIndex ? src.objectStore : (src as IDBObjectStore);\n}\n\nfunction subscribersFor(factory: IDBFactory, name: string): Subscriber[] {\n if (name.startsWith(\"__runapps\")) return [];\n const out: Subscriber[] = [];\n for (const s of subscribers) {\n if ((s.factory === null || s.factory === factory) && s.opts.include(name)) out.push(s);\n }\n return out;\n}\n\nfunction patchPrototypes(): void {\n const F = IDBFactory.prototype;\n const D = IDBDatabase.prototype;\n const S = IDBObjectStore.prototype;\n const C = IDBCursor.prototype;\n const o = (originals = {\n open: F.open,\n deleteDatabase: F.deleteDatabase,\n transaction: D.transaction,\n put: S.put,\n add: S.add,\n get: S.get,\n delete: S.delete,\n clear: S.clear,\n getAllKeys: S.getAllKeys,\n cursorUpdate: C.update,\n cursorDelete: C.delete,\n });\n\n const tracked = (store: IDBObjectStore): TxState | null => txState.get(store.transaction) ?? null;\n\n F.open = function (this: IDBFactory, name: string, version?: number): IDBOpenDBRequest {\n const real = version === undefined ? o.open.call(this, name) : o.open.call(this, name, version);\n const subs = subscribersFor(this, name);\n if (subs.length === 0) return real;\n return new HookedOpenRequest(real, name, subs) as unknown as IDBOpenDBRequest;\n };\n\n F.deleteDatabase = function (this: IDBFactory, name: string): IDBOpenDBRequest {\n for (const s of subscribersFor(this, name)) {\n try {\n s.opts.onDeleteDatabase(name);\n } catch {\n /* never block the app's delete */\n }\n }\n return o.deleteDatabase.call(this, name);\n };\n\n D.transaction = function (\n this: IDBDatabase,\n storeNames: string | string[],\n mode?: IDBTransactionMode,\n options?: IDBTransactionOptions,\n ): IDBTransaction {\n const tx =\n options !== undefined\n ? o.transaction.call(this, storeNames, mode, options)\n : mode !== undefined\n ? o.transaction.call(this, storeNames, mode)\n : o.transaction.call(this, storeNames);\n const info = observedDbs.get(this);\n if (info && tx.mode === \"readwrite\") {\n const st: TxState = { db: info.name, subs: info.subs, ops: [] };\n txState.set(tx, st);\n tx.addEventListener(\"complete\", () => {\n if (st.ops.length === 0) return;\n const ops = st.ops;\n st.ops = [];\n for (const s of st.subs) {\n const mine = ops.filter((op) => !s.opts.excludeStore(st.db, op.store));\n if (mine.length === 0) continue;\n try {\n s.opts.onCommit(st.db, mine);\n } catch (err) {\n reportListenerError(err);\n }\n }\n });\n }\n return tx;\n };\n\n S.put = function (this: IDBObjectStore, value: unknown, key?: IDBValidKey): IDBRequest<IDBValidKey> {\n const st = tracked(this);\n let prior: { value: unknown } | null = null;\n if (st && isPlainObject(value)) {\n // The record's previous value, read before the write in the same\n // transaction, tells the engine which fields this write changed.\n const k = key !== undefined ? key : extractKey(value, this.keyPath as string | string[] | null);\n if (k !== undefined) {\n try {\n const g = o.get.call(this, k);\n const slot = (prior = { value: undefined });\n g.addEventListener(\"success\", () => {\n slot.value = g.result;\n });\n } catch {\n prior = null;\n }\n }\n }\n const r = key === undefined ? o.put.call(this, value) : o.put.call(this, value, key);\n if (st) {\n const store = this.name;\n const snapshot = clone(value);\n const priorSlot = prior;\n r.addEventListener(\"success\", () => {\n st.ops.push({ store, kind: \"put\", key: r.result, value: snapshot, prior: priorSlot?.value });\n });\n }\n return r;\n };\n\n S.add = function (this: IDBObjectStore, value: unknown, key?: IDBValidKey): IDBRequest<IDBValidKey> {\n const r = key === undefined ? o.add.call(this, value) : o.add.call(this, value, key);\n const st = tracked(this);\n if (st) {\n const store = this.name;\n const snapshot = clone(value);\n r.addEventListener(\"success\", () => {\n st.ops.push({ store, kind: \"put\", key: r.result, value: snapshot });\n });\n }\n return r;\n };\n\n S.delete = function (this: IDBObjectStore, query: IDBValidKey | IDBKeyRange): IDBRequest<undefined> {\n const st = tracked(this);\n if (st) {\n const store = this.name;\n if (typeof IDBKeyRange === \"function\" && query instanceof IDBKeyRange) {\n // Which keys a range covers is only knowable before the delete;\n // getAllKeys runs first in the same transaction.\n const kr = o.getAllKeys.call(this, query);\n kr.addEventListener(\"success\", () => {\n for (const k of kr.result) st.ops.push({ store, kind: \"del\", key: k });\n });\n } else {\n st.ops.push({ store, kind: \"del\", key: query as IDBValidKey });\n }\n }\n return o.delete.call(this, query);\n };\n\n S.clear = function (this: IDBObjectStore): IDBRequest<undefined> {\n const st = tracked(this);\n const r = o.clear.call(this);\n if (st) {\n const store = this.name;\n r.addEventListener(\"success\", () => {\n st.ops.push({ store, kind: \"clear\" });\n });\n }\n return r;\n };\n\n C.update = function (this: IDBCursor, value: unknown): IDBRequest<IDBValidKey> {\n const store = cursorStore(this);\n const st = tracked(store);\n const prior = st ? clone((this as IDBCursorWithValue).value) : undefined;\n const r = o.cursorUpdate.call(this, value);\n if (st) {\n const name = store.name;\n const snapshot = clone(value);\n const key = this.primaryKey;\n r.addEventListener(\"success\", () => {\n st.ops.push({ store: name, kind: \"put\", key, value: snapshot, prior });\n });\n }\n return r;\n };\n\n C.delete = function (this: IDBCursor): IDBRequest<undefined> {\n const store = cursorStore(this);\n const st = tracked(store);\n if (st) st.ops.push({ store: store.name, kind: \"del\", key: this.primaryKey });\n return o.cursorDelete.call(this);\n };\n}\n\nfunction restorePrototypes(): void {\n if (!originals) return;\n const o = originals;\n IDBFactory.prototype.open = o.open;\n IDBFactory.prototype.deleteDatabase = o.deleteDatabase;\n IDBDatabase.prototype.transaction = o.transaction;\n IDBObjectStore.prototype.put = o.put;\n IDBObjectStore.prototype.add = o.add;\n IDBObjectStore.prototype.delete = o.delete;\n IDBObjectStore.prototype.clear = o.clear;\n IDBCursor.prototype.update = o.cursorUpdate;\n IDBCursor.prototype.delete = o.cursorDelete;\n originals = null;\n}\n\n/**\n * Install the hook for one subscriber. The prototypes are patched on the\n * first install and restored when the last subscriber uninstalls.\n */\nexport function installIndexedDBHook(opts: HookOptions): InstalledHook | null {\n if (\n typeof indexedDB === \"undefined\" ||\n typeof IDBFactory !== \"function\" ||\n typeof IDBDatabase !== \"function\" ||\n typeof IDBObjectStore !== \"function\" ||\n typeof IDBCursor !== \"function\"\n ) {\n return null;\n }\n if (!originals) patchPrototypes();\n const o = originals!;\n const sub: Subscriber = { opts, factory: opts.factory ?? indexedDB };\n subscribers.add(sub);\n const factory = sub.factory as IDBFactory;\n return {\n originalOpen: (name, version) =>\n version === undefined ? o.open.call(factory, name) : o.open.call(factory, name, version),\n uninstall() {\n subscribers.delete(sub);\n if (subscribers.size === 0) restorePrototypes();\n },\n };\n}\n","// sync/log.ts — the SDK's own IndexedDB database.\n//\n// Holds what the sync engine needs to survive a reload:\n// ops — local changes not yet pushed, in commit order. Values are kept\n// as the app wrote them (structured clone, Blobs included);\n// they are encoded only when pushed, so logging works offline.\n// meta — small key/value state: the owner (user id the local data\n// belongs to), shard ETags seen last, store clear marks.\n//\n// Opened through the untouched `indexedDB.open`, never the hooked one,\n// and its name starts with `__runapps` so the hook leaves it alone.\n\nexport const SYNC_DB_NAME = \"__runapps_sync__\";\nconst SYNC_DB_VERSION = 1;\n\nexport type OpKind = \"put\" | \"del\" | \"clear\";\n\nexport interface LoggedOp {\n id?: number;\n db: string;\n store: string;\n kind: OpKind;\n /** Canonical key id (codec.keyId); \"\" for clear. */\n kid: string;\n /** Encoded key (codec.encodeKey); undefined for clear. */\n key?: unknown;\n /** The value as written, for put. */\n value?: unknown;\n /** For a put of a plain object whose previous value was seen: the\n * fields this write changed (added, modified or removed). Undefined\n * means \"treat every field as changed\". */\n changed?: string[];\n /** Hybrid logical clock timestamp (ms, monotonic per device). */\n ts: number;\n}\n\nfunction req<T>(r: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n r.onsuccess = () => resolve(r.result);\n r.onerror = () => reject(r.error ?? new Error(\"IndexedDB request failed\"));\n });\n}\n\nfunction done(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onabort = () => reject(tx.error ?? new Error(\"IndexedDB transaction aborted\"));\n tx.onerror = () => reject(tx.error ?? new Error(\"IndexedDB transaction failed\"));\n });\n}\n\nexport class SyncLog {\n private dbPromise: Promise<IDBDatabase> | null = null;\n\n constructor(private readonly open: (name: string, version?: number) => IDBOpenDBRequest) {}\n\n private db(): Promise<IDBDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const r = this.open(SYNC_DB_NAME, SYNC_DB_VERSION);\n r.onupgradeneeded = () => {\n const db = r.result;\n if (!db.objectStoreNames.contains(\"ops\")) {\n const ops = db.createObjectStore(\"ops\", { keyPath: \"id\", autoIncrement: true });\n ops.createIndex(\"store\", [\"db\", \"store\"], { unique: false });\n }\n if (!db.objectStoreNames.contains(\"meta\")) db.createObjectStore(\"meta\");\n };\n r.onsuccess = () => {\n const db = r.result;\n // Another tab upgrading (a future SDK) must not be blocked by us.\n db.onversionchange = () => {\n db.close();\n this.dbPromise = null;\n };\n resolve(db);\n };\n r.onerror = () => reject(r.error ?? new Error(\"cannot open sync log\"));\n r.onblocked = () => reject(new Error(\"sync log open blocked\"));\n });\n this.dbPromise.catch(() => {\n this.dbPromise = null;\n });\n }\n return this.dbPromise;\n }\n\n async append(ops: LoggedOp[]): Promise<void> {\n if (ops.length === 0) return;\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readwrite\");\n const st = tx.objectStore(\"ops\");\n for (const op of ops) st.add(op);\n await done(tx);\n }\n\n async pending(): Promise<LoggedOp[]> {\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readonly\");\n const all = (await req(tx.objectStore(\"ops\").getAll())) as LoggedOp[];\n return all;\n }\n\n async pendingCount(): Promise<number> {\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readonly\");\n return req(tx.objectStore(\"ops\").count());\n }\n\n async remove(ids: number[]): Promise<void> {\n if (ids.length === 0) return;\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readwrite\");\n const st = tx.objectStore(\"ops\");\n for (const id of ids) st.delete(id);\n await done(tx);\n }\n\n async clearOps(): Promise<void> {\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readwrite\");\n tx.objectStore(\"ops\").clear();\n await done(tx);\n }\n\n async getMeta<T = unknown>(key: string): Promise<T | undefined> {\n const db = await this.db();\n const tx = db.transaction(\"meta\", \"readonly\");\n return (await req(tx.objectStore(\"meta\").get(key))) as T | undefined;\n }\n\n async setMeta(entries: Record<string, unknown>): Promise<void> {\n const db = await this.db();\n const tx = db.transaction(\"meta\", \"readwrite\");\n const st = tx.objectStore(\"meta\");\n for (const k of Object.keys(entries)) {\n if (entries[k] === undefined) st.delete(k);\n else st.put(entries[k], k);\n }\n await done(tx);\n }\n\n /** Every meta entry whose key starts with `prefix`, as a map. */\n async metaWithPrefix(prefix: string): Promise<Map<string, unknown>> {\n const db = await this.db();\n const tx = db.transaction(\"meta\", \"readonly\");\n const st = tx.objectStore(\"meta\");\n const range = IDBKeyRange.bound(prefix, prefix + \"\");\n const keys = (await req(st.getAllKeys(range))) as string[];\n const vals = await req(st.getAll(range));\n const out = new Map<string, unknown>();\n keys.forEach((k, i) => out.set(k, vals[i]));\n return out;\n }\n\n async clearAll(): Promise<void> {\n const db = await this.db();\n const tx = db.transaction([\"ops\", \"meta\"], \"readwrite\");\n tx.objectStore(\"ops\").clear();\n tx.objectStore(\"meta\").clear();\n await done(tx);\n }\n\n close(): void {\n this.dbPromise?.then((db) => db.close()).catch(() => {});\n this.dbPromise = null;\n }\n}\n\nexport { req as idbRequest, done as idbDone };\n","// sync/engine.ts — keeps an app's IndexedDB and the user's cloud copy\n// the same.\n//\n// Storage model (one object per shard, under the user's file space):\n//\n// db/<database>/<store>/<shard>.json\n// { v: 1, cleared: <ts>, r: { <keyId>: { k, v, t, d? } } }\n//\n// A store's records are spread over a fixed number of shards by key\n// hash, so a change rewrites one small object, and a device that has\n// nothing new to see pays one conditional read per shard (304). Every\n// record carries the timestamp of its last write; the newer write wins,\n// a deletion is a record with `d: 1`, and a store `clear` raises the\n// shard's `cleared` mark, which drops everything older. Shards are\n// written with If-Match, so two devices never overwrite each other: the\n// loser re-reads, merges and writes again.\n//\n// Locally the engine owns a change log (log.ts) fed by the hook\n// (hook.ts); pushing turns pending ops into shard writes, pulling turns\n// changed shards into writes on the app's database through the engine's\n// own connection, which the hook does not observe.\n\nimport { changedFields, decodeKey, decodeValue, encodeKey, encodeValue, hash32, keyId, type BlobRef } from \"./codec.js\";\nimport { type CommittedOp, type InstalledHook, installIndexedDBHook } from \"./hook.js\";\nimport { idbDone, idbRequest, SyncLog, type LoggedOp } from \"./log.js\";\nimport type { SDKEvents } from \"../events.js\";\n\nexport interface SyncOptions {\n /** Observe only these databases (by name). Default: every database the\n * app opens, except the SDK's own. */\n databases?: string[];\n /** Leave out a whole database (`\"cache\"`) or one store (`\"app/videos\"`). */\n exclude?: string[];\n /** Shards per store. Default 8. Changing it later splits data: pick once. */\n shards?: number;\n /** How long to gather local writes before pushing. Default 2500 ms. */\n pushDelayMs?: number;\n /** Background pull interval while the page is visible. Default 60 s. */\n pullIntervalMs?: number;\n /** How long a database open waits for the first pull. Default 5 s. */\n openTimeoutMs?: number;\n /** Path prefix in the user's file space. Default \"db\". */\n prefix?: string;\n}\n\nexport interface SyncState {\n /** off: no signed-in user; idle: everything pushed; syncing: a push or\n * pull is running; offline: changes waiting for the network; error:\n * the last attempt failed, will retry. */\n state: \"off\" | \"idle\" | \"syncing\" | \"offline\" | \"error\";\n /** Local changes not yet on the server. */\n pending: number;\n lastSyncedAt?: number;\n /** When a sync last moved data — pushed local writes or applied\n * another device's. A quiet pull that found nothing leaves it. */\n lastChangeAt?: number;\n error?: string;\n}\n\nexport interface DbChange {\n db: string;\n store: string;\n /** Primary keys that changed; empty when the whole store was cleared. */\n keys: IDBValidKey[];\n cleared: boolean;\n /** remote: another device's change, just applied here. tab: another\n * tab of this browser wrote to the shared database (IndexedDB has\n * no event for that). */\n source: \"remote\" | \"tab\";\n}\n\n/** What the engine needs from the rest of the SDK. */\nexport interface SyncDeps {\n remote: SyncRemote;\n events: SDKEvents;\n /** Signed-in user id, or null. */\n userId: () => string | null;\n /** Subscribe to sign-in changes. */\n onAuthChange: (cb: () => void) => () => void;\n /** The IndexedDB factory to observe. Default: the global one. */\n factory?: IDBFactory;\n}\n\n/** The storage side. Implemented over FilesService; tests use memory. */\nexport interface SyncRemote {\n list(prefix: string): Promise<{ path: string; etag: string }[]>;\n getJSON(path: string, ifNoneMatch?: string): Promise<{ status: \"ok\"; value: unknown; etag: string; serverTime?: number } | { status: \"missing\" } | { status: \"unchanged\" }>;\n putJSON(path: string, value: unknown, cond: { ifMatch?: string; ifNoneMatch?: boolean }): Promise<{ status: \"ok\"; etag: string; serverTime?: number } | { status: \"conflict\" }>;\n putBlob(path: string, blob: Blob): Promise<void>;\n getBlob(path: string): Promise<Blob>;\n}\n\ninterface ShardRecord {\n k: unknown;\n v: unknown;\n t: number;\n d?: 1;\n /** Per-field write times, kept for plain-object values so two devices\n * editing different fields of one record both keep their edits. */\n f?: Record<string, number>;\n}\n\nfunction isPlainRecord(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v) && !(\"$rt\" in (v as object));\n}\n\nfunction sameJSON(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * Fold an incoming write into the record already in the shard.\n *\n * Both plain objects: field by field. `changed` names the fields the\n * incoming write touched (the hook compared the record with its previous\n * value in the same transaction); every other field keeps the stored\n * value, which may have been written by another device meanwhile. A\n * changed field takes the incoming value unless the stored field was\n * written later by someone else; a field the incoming write dropped is\n * removed on the same condition. Without `changed` (an `add`, or a prior\n * value the hook could not read), a field is taken as changed when its\n * value differs. Anything else: the later write wins whole.\n */\nfunction mergeRecord(cur: ShardRecord | undefined, inc: ShardRecord, changed?: string[]): ShardRecord {\n if (!cur) return inc;\n if (inc.d || cur.d || !isPlainRecord(cur.v) || !isPlainRecord(inc.v)) {\n return inc.t >= cur.t ? inc : cur;\n }\n const curF = cur.f ?? {};\n const touched = changed ? new Set(changed) : null;\n const out: Record<string, unknown> = {};\n const f: Record<string, number> = {};\n const fields = new Set([...Object.keys(cur.v), ...Object.keys(inc.v)]);\n for (const name of fields) {\n const inCur = name in cur.v;\n const inInc = name in inc.v;\n const curT = curF[name] ?? cur.t;\n const untouched = touched ? !touched.has(name) : inCur && inInc && sameJSON(cur.v[name], inc.v[name]);\n if (untouched) {\n if (inCur) {\n out[name] = cur.v[name];\n if (curF[name] !== undefined) f[name] = curF[name];\n }\n continue;\n }\n const incomingWins = inc.t >= curT;\n if (incomingWins) {\n if (inInc) {\n out[name] = inc.v[name];\n f[name] = inc.t;\n }\n // dropped by the incoming write: leave it out\n } else if (inCur) {\n out[name] = cur.v[name];\n f[name] = curT;\n }\n }\n return { k: inc.k, v: out, t: Math.max(cur.t, inc.t), f };\n}\n\ninterface ShardOp {\n kid: string;\n rec: ShardRecord;\n changed?: string[];\n}\n\ninterface Shard {\n v: 1;\n cleared: number;\n r: Record<string, ShardRecord>;\n}\n\ninterface StoreSchema {\n keyPath: string | string[] | null;\n autoIncrement: boolean;\n}\n\nconst TOMBSTONE_TTL_MS = 30 * 24 * 3600 * 1000;\nconst MAX_PUSH_DELAY_MS = 10_000;\nconst BACKOFF_MIN_MS = 5_000;\nconst BACKOFF_MAX_MS = 5 * 60_000;\n\n/** Hybrid logical clock: wall time, never going backwards, with the\n * server's clock offset applied once learned. */\nclass Clock {\n private last = 0;\n private offset = 0;\n now(): number {\n const t = Date.now() + this.offset;\n this.last = t > this.last ? t : this.last + 1;\n return this.last;\n }\n observe(serverTime: number, localBefore: number, localAfter: number): void {\n const mid = (localBefore + localAfter) / 2;\n const off = serverTime - mid;\n if (Math.abs(off - this.offset) > 2000) this.offset = off;\n }\n}\n\nfunction pathSeg(s: string): string {\n return encodeURIComponent(s).replace(/%2F/gi, \"%2F\");\n}\n\nexport class SyncEngine {\n readonly options: Required<SyncOptions>;\n private readonly log: SyncLog;\n private readonly hook: InstalledHook | null;\n private readonly clock = new Clock();\n private readonly schemas = new Map<string, Map<string, StoreSchema>>();\n private readonly etags = new Map<string, string>();\n private readonly conns = new Map<string, Promise<IDBDatabase>>();\n private state: SyncState = { state: \"off\", pending: 0 };\n private paused = false;\n private disposed = false;\n private leader = true;\n private pushTimer: ReturnType<typeof setTimeout> | null = null;\n private pushDeadline = 0;\n private pullTimer: ReturnType<typeof setInterval> | null = null;\n private backoffMs = 0;\n private backoffUntil = 0;\n private chain: Promise<void> = Promise.resolve();\n private readonly cleanup: (() => void)[] = [];\n private channel: BroadcastChannel | null = null;\n private metaLoaded: Promise<void> | null = null;\n\n constructor(\n private readonly deps: SyncDeps,\n options: SyncOptions = {},\n ) {\n this.options = {\n databases: options.databases ?? [],\n exclude: options.exclude ?? [],\n shards: Math.max(1, Math.min(64, options.shards ?? 8)),\n pushDelayMs: options.pushDelayMs ?? 2500,\n pullIntervalMs: options.pullIntervalMs ?? 60_000,\n openTimeoutMs: options.openTimeoutMs ?? 5000,\n prefix: (options.prefix ?? \"db\").replace(/^\\/+|\\/+$/g, \"\"),\n };\n const excludedDbs = new Set(this.options.exclude.filter((e) => !e.includes(\"/\")));\n const excludedStores = new Set(this.options.exclude.filter((e) => e.includes(\"/\")));\n const only = new Set(this.options.databases);\n\n this.hook = installIndexedDBHook({\n include: (name) => (only.size === 0 || only.has(name)) && !excludedDbs.has(name),\n excludeStore: (db, store) => excludedStores.has(db + \"/\" + store),\n onCommit: (db, ops) => this.onCommit(db, ops),\n onOpen: (db, name) => this.onOpen(db, name),\n onDeleteDatabase: (name) => this.onDeleteDatabase(name),\n openTimeoutMs: this.options.openTimeoutMs,\n factory: deps.factory,\n });\n this.log = new SyncLog((name, version) =>\n this.hook ? this.hook.originalOpen(name, version) : indexedDB.open(name, version),\n );\n\n if (typeof window !== \"undefined\") this.attachBrowserTriggers();\n this.cleanup.push(deps.onAuthChange(() => this.schedule(\"auth\")));\n void this.refreshPending();\n void this.schedule(\"start\");\n }\n\n // ── Public surface ───────────────────────────────────────────────\n\n status(): SyncState {\n return { ...this.state };\n }\n\n /** Push pending changes and pull remote ones now. */\n now(): Promise<void> {\n if (!this.leader) {\n // Another tab syncs for this origin: ask it to go now. Its state\n // comes back over the channel.\n this.post({ type: \"sync:request\" });\n return Promise.resolve();\n }\n return this.enqueue(() => this.sync());\n }\n\n /** Send to the other tabs of this origin. */\n private post(msg: unknown): void {\n try {\n this.channel?.postMessage(msg);\n } catch {\n /* channel closed */\n }\n }\n\n pause(): void {\n this.paused = true;\n }\n\n resume(): void {\n this.paused = false;\n void this.schedule(\"resume\");\n }\n\n dispose(): void {\n this.disposed = true;\n for (const c of this.cleanup) c();\n if (this.pushTimer) clearTimeout(this.pushTimer);\n if (this.pullTimer) clearInterval(this.pullTimer);\n this.channel?.close();\n for (const p of this.conns.values()) p.then((db) => db.close()).catch(() => {});\n this.conns.clear();\n this.log.close();\n this.hook?.uninstall();\n }\n\n // ── Hook callbacks ───────────────────────────────────────────────\n\n private onCommit(db: string, ops: CommittedOp[]): void {\n const logged: LoggedOp[] = [];\n for (const op of ops) {\n const ts = this.clock.now();\n if (op.kind === \"clear\") logged.push({ db, store: op.store, kind: \"clear\", kid: \"\", ts });\n else if (op.kind === \"del\") logged.push({ db, store: op.store, kind: \"del\", kid: keyId(op.key), key: encodeKey(op.key), ts });\n else {\n logged.push({\n db,\n store: op.store,\n kind: \"put\",\n kid: keyId(op.key),\n key: encodeKey(op.key),\n value: op.value,\n changed: op.prior === undefined ? undefined : changedFields(op.prior, op.value),\n ts,\n });\n }\n }\n this.enqueueLog(logged);\n // The other tabs share this database but get no event from it:\n // tell them what changed so their views can re-read.\n const byStore = new Map<string, { keys: IDBValidKey[]; cleared: boolean }>();\n for (const op of ops) {\n let e = byStore.get(op.store);\n if (!e) byStore.set(op.store, (e = { keys: [], cleared: false }));\n if (op.kind === \"clear\") {\n e.cleared = true;\n e.keys = [];\n } else if (!e.cleared) {\n e.keys.push(op.key);\n }\n }\n for (const [store, e] of byStore) {\n const change: DbChange = { db, store, keys: e.keys, cleared: e.cleared, source: \"tab\" };\n this.post({ type: \"db:change\", change });\n }\n }\n\n private logChain: Promise<void> = Promise.resolve();\n private enqueueLog(ops: LoggedOp[]): void {\n this.logChain = this.logChain\n .then(() => this.log.append(ops))\n .then(() => this.refreshPending())\n .then(() => {\n if (!this.leader) this.post({ type: \"sync:request\" });\n })\n .catch((err) => this.fail(err));\n this.schedulePush();\n }\n\n private async onOpen(db: IDBDatabase, name: string): Promise<void> {\n const schema = new Map<string, StoreSchema>();\n const names = Array.from(db.objectStoreNames);\n if (names.length > 0) {\n const tx = db.transaction(names, \"readonly\");\n for (const n of names) {\n const st = tx.objectStore(n);\n schema.set(n, { keyPath: st.keyPath as string | string[] | null, autoIncrement: st.autoIncrement });\n if (st.autoIncrement && typeof console !== \"undefined\") {\n console.warn(\n `[runapps sync] \"${name}/${n}\" uses autoIncrement keys: records created on two devices while offline can get the same key, and only one survives. Prefer client-generated ids (e.g. crypto.randomUUID()).`,\n );\n }\n }\n }\n this.schemas.set(name, schema);\n void this.log.setMeta({ [\"schema:\" + name]: Object.fromEntries(schema) }).catch(() => {});\n if (!this.deps.userId() || this.paused || !this.leader) return;\n await this.enqueue(() => this.pullDatabase(name)).catch(() => {});\n }\n\n private onDeleteDatabase(name: string): void {\n const schema = this.schemas.get(name);\n const stores = schema ? Array.from(schema.keys()) : [];\n this.schemas.delete(name);\n this.conns.get(name)?.then((db) => db.close()).catch(() => {});\n this.conns.delete(name);\n const ts = this.clock.now();\n this.enqueueLog(stores.map((store) => ({ db: name, store, kind: \"clear\" as const, kid: \"\", ts })));\n }\n\n // ── Scheduling ───────────────────────────────────────────────────\n\n private attachBrowserTriggers(): void {\n const on = (target: EventTarget, type: string, fn: () => void) => {\n target.addEventListener(type, fn);\n this.cleanup.push(() => target.removeEventListener(type, fn));\n };\n on(window, \"online\", () => {\n this.backoffUntil = 0;\n void this.schedule(\"online\");\n });\n on(window, \"focus\", () => void this.schedule(\"focus\"));\n if (typeof document !== \"undefined\") {\n on(document, \"visibilitychange\", () => {\n if (document.visibilityState === \"visible\") void this.schedule(\"visible\");\n this.updatePullTimer();\n });\n }\n this.updatePullTimer();\n if (typeof BroadcastChannel === \"function\") {\n this.channel = new BroadcastChannel(\"__runapps_db_change__\");\n this.channel.onmessage = (ev) => {\n const d = ev.data as\n | (DbChange & { type?: undefined })\n | { type: \"sync:state\"; state: SyncState }\n | { type: \"sync:request\" }\n | { type: \"db:change\"; change: DbChange }\n | undefined;\n if (!d) return;\n if (d.type === \"db:change\") {\n this.deps.events.emit(\"db:change\", d.change);\n } else if (d.type === \"sync:state\") {\n // The leader's state is the origin's state; a follower shows it.\n if (!this.leader) {\n this.state = { ...d.state };\n this.deps.events.emit(\"sync:state\", { ...d.state });\n }\n } else if (d.type === \"sync:request\") {\n // A follower wrote to the log, or wants a sync now: go, and\n // tell it where things stand either way.\n if (this.leader) {\n this.post({ type: \"sync:state\", state: { ...this.state } });\n void this.schedule(\"peer\");\n }\n } else if (d.source === \"remote\") {\n this.deps.events.emit(\"db:change\", d);\n }\n };\n }\n // One tab syncs at a time; the others feed the shared log and wait\n // for the lock, taking over when the leader closes.\n const locks = (navigator as Navigator & { locks?: LockManager }).locks;\n if (locks && typeof locks.request === \"function\") {\n this.leader = false;\n // Until the lock is ours, another tab is syncing: ask it where\n // things stand so this tab does not sit on the initial \"off\".\n this.post({ type: \"sync:request\" });\n void locks\n .request(\"__runapps_sync__\", () => {\n this.leader = true;\n void this.schedule(\"leader\");\n return new Promise<void>(() => {}); // hold until the tab dies\n })\n .catch(() => {\n this.leader = true;\n });\n }\n }\n\n private updatePullTimer(): void {\n const visible = typeof document === \"undefined\" || document.visibilityState !== \"hidden\";\n if (visible && !this.pullTimer && this.options.pullIntervalMs > 0) {\n this.pullTimer = setInterval(() => void this.schedule(\"interval\"), this.options.pullIntervalMs);\n (this.pullTimer as { unref?: () => void }).unref?.();\n } else if (!visible && this.pullTimer) {\n clearInterval(this.pullTimer);\n this.pullTimer = null;\n }\n }\n\n private schedulePush(): void {\n if (this.disposed) return;\n const now = Date.now();\n if (!this.pushDeadline) this.pushDeadline = now + MAX_PUSH_DELAY_MS;\n const delay = Math.max(0, Math.min(this.options.pushDelayMs, this.pushDeadline - now));\n if (this.pushTimer) clearTimeout(this.pushTimer);\n this.pushTimer = setTimeout(() => {\n this.pushTimer = null;\n this.pushDeadline = 0;\n void this.schedule(\"push\");\n }, delay);\n (this.pushTimer as { unref?: () => void }).unref?.();\n }\n\n private schedule(_reason: string): Promise<void> {\n if (this.disposed || this.paused || !this.leader) return Promise.resolve();\n if (!this.deps.userId()) {\n this.setState({ state: \"off\" });\n return Promise.resolve();\n }\n if (Date.now() < this.backoffUntil) return Promise.resolve();\n return this.enqueue(() => this.sync());\n }\n\n private enqueue(fn: () => Promise<void>): Promise<void> {\n const run = this.chain.then(fn, fn);\n this.chain = run.catch(() => {});\n return run;\n }\n\n // ── State ────────────────────────────────────────────────────────\n\n /** Set while a sync cycle pushes or applies something. */\n private moved = false;\n\n private setState(patch: Partial<SyncState>): void {\n const next = { ...this.state, ...patch };\n if (patch.state && patch.state !== \"error\") delete next.error;\n const changed =\n next.state !== this.state.state ||\n next.pending !== this.state.pending ||\n next.lastSyncedAt !== this.state.lastSyncedAt ||\n next.lastChangeAt !== this.state.lastChangeAt ||\n next.error !== this.state.error;\n this.state = next;\n if (changed) {\n this.deps.events.emit(\"sync:state\", { ...next });\n if (this.leader) this.post({ type: \"sync:state\", state: { ...next } });\n }\n }\n\n private async refreshPending(): Promise<void> {\n try {\n const n = await this.log.pendingCount();\n this.setState({ pending: n });\n } catch {\n /* log unavailable (private mode): stay with the last count */\n }\n }\n\n private fail(err: unknown): void {\n const offline = typeof navigator !== \"undefined\" && navigator.onLine === false;\n const msg = err instanceof Error ? err.message : String(err);\n this.backoffMs = this.backoffMs ? Math.min(this.backoffMs * 2, BACKOFF_MAX_MS) : BACKOFF_MIN_MS;\n this.backoffUntil = Date.now() + this.backoffMs;\n this.setState({ state: offline ? \"offline\" : \"error\", error: msg });\n if (!offline && typeof console !== \"undefined\") console.warn(\"[runapps sync]\", msg);\n }\n\n // ── Sync ─────────────────────────────────────────────────────────\n\n private async loadMeta(): Promise<void> {\n if (!this.metaLoaded) {\n this.metaLoaded = (async () => {\n for (const [k, v] of await this.log.metaWithPrefix(\"etag:\")) this.etags.set(k.slice(5), v as string);\n for (const [k, v] of await this.log.metaWithPrefix(\"schema:\")) {\n const name = k.slice(7);\n if (!this.schemas.has(name)) {\n this.schemas.set(name, new Map(Object.entries(v as Record<string, StoreSchema>)));\n }\n }\n })();\n this.metaLoaded.catch(() => {\n this.metaLoaded = null;\n });\n }\n return this.metaLoaded;\n }\n\n /** The local copy belongs to one user. Another user signing in on the\n * same browser starts from the server's copy, never from theirs. */\n private async ensureOwner(userId: string): Promise<void> {\n const owner = await this.log.getMeta<string>(\"owner\");\n if (owner === userId) return;\n if (owner !== undefined) {\n for (const [db, schema] of this.schemas) {\n for (const store of schema.keys()) {\n if (this.storeExcluded(db, store)) continue;\n await this.withStore(db, store, \"readwrite\", (st) => {\n st.clear();\n }).catch(() => {});\n this.emitChange({ db, store, keys: [], cleared: true, source: \"remote\" });\n }\n }\n await this.log.clearAll();\n this.etags.clear();\n this.metaLoaded = null;\n await this.loadMeta();\n }\n await this.log.setMeta({ owner: userId });\n }\n\n private async sync(): Promise<void> {\n if (this.disposed || this.paused || !this.leader) return;\n const userId = this.deps.userId();\n if (!userId) {\n this.setState({ state: \"off\" });\n return;\n }\n this.setState({ state: \"syncing\" });\n this.moved = false;\n try {\n await this.loadMeta();\n await this.ensureOwner(userId);\n await this.logChain;\n await this.push();\n await this.pull();\n this.backoffMs = 0;\n this.backoffUntil = 0;\n await this.refreshPending();\n const now = Date.now();\n this.setState({ state: \"idle\", lastSyncedAt: now, ...(this.moved ? { lastChangeAt: now } : {}) });\n } catch (err) {\n await this.refreshPending();\n this.fail(err);\n }\n }\n\n private storeExcluded(db: string, store: string): boolean {\n return this.options.exclude.includes(db + \"/\" + store) || this.options.exclude.includes(db);\n }\n\n private shardPath(db: string, store: string, shard: number): string {\n return `${this.options.prefix}/${pathSeg(db)}/${pathSeg(store)}/${shard}.json`;\n }\n\n private shardOf(kid: string): number {\n return hash32(kid) % this.options.shards;\n }\n\n private async readShard(path: string): Promise<{ shard: Shard; etag?: string }> {\n const before = Date.now();\n const r = await this.deps.remote.getJSON(path);\n if (r.status === \"ok\") {\n if (r.serverTime) this.clock.observe(r.serverTime, before, Date.now());\n const s = r.value as Partial<Shard> | null;\n const shard: Shard = { v: 1, cleared: s?.cleared ?? 0, r: s?.r ?? {} };\n this.etags.set(path, r.etag);\n return { shard, etag: r.etag };\n }\n this.etags.delete(path);\n return { shard: { v: 1, cleared: 0, r: {} } };\n }\n\n // ── Push ─────────────────────────────────────────────────────────\n\n private async push(): Promise<void> {\n const ops = await this.log.pending();\n if (ops.length === 0) return;\n // Group by store, keeping commit order inside a store.\n const groups = new Map<string, LoggedOp[]>();\n for (const op of ops) {\n const k = op.db + \"\\u0000\" + op.store;\n let g = groups.get(k);\n if (!g) groups.set(k, (g = []));\n g.push(op);\n }\n const done: number[] = [];\n const etagUpdates: Record<string, unknown> = {};\n for (const [, g] of groups) {\n const db = g[0]!.db;\n const store = g[0]!.store;\n if (this.storeExcluded(db, store)) {\n for (const op of g) if (op.id !== undefined) done.push(op.id);\n continue;\n }\n // A clear drops everything written before it; ops after the last\n // clear are the ones that still matter.\n let clearedAt = 0;\n let lastClear = -1;\n g.forEach((op, i) => {\n if (op.kind === \"clear\") {\n clearedAt = Math.max(clearedAt, op.ts);\n lastClear = i;\n }\n });\n const live = g.slice(lastClear + 1);\n const byShard = new Map<number, LoggedOp[]>();\n for (const op of live) {\n const s = this.shardOf(op.kid);\n let list = byShard.get(s);\n if (!list) byShard.set(s, (list = []));\n list.push(op);\n }\n const shards = clearedAt > 0 ? Array.from({ length: this.options.shards }, (_, i) => i) : Array.from(byShard.keys());\n for (const n of shards) {\n const path = this.shardPath(db, store, n);\n const shardOps = byShard.get(n) ?? [];\n const encoded = await Promise.all(shardOps.map((op) => this.encodeOp(op)));\n const etag = await this.writeShard(path, clearedAt, encoded);\n etagUpdates[\"etag:\" + path] = etag;\n }\n for (const op of g) if (op.id !== undefined) done.push(op.id);\n }\n await this.log.setMeta(etagUpdates);\n await this.log.remove(done);\n if (done.length > 0) this.moved = true;\n await this.refreshPending();\n }\n\n private async encodeOp(op: LoggedOp): Promise<ShardOp> {\n if (op.kind === \"del\") return { kid: op.kid, rec: { k: op.key, v: null, t: op.ts, d: 1 } };\n const v = await encodeValue(op.value, (blob) => this.storeBlob(blob));\n return { kid: op.kid, rec: { k: op.key, v, t: op.ts }, changed: op.changed };\n }\n\n /** Merge ops into one shard and write it; retried on a 412. */\n private async writeShard(path: string, clearedAt: number, ops: ShardOp[]): Promise<string> {\n for (let attempt = 0; attempt < 5; attempt++) {\n const { shard, etag } = await this.readShard(path);\n const now = this.clock.now();\n if (clearedAt > shard.cleared) {\n shard.cleared = clearedAt;\n for (const kid of Object.keys(shard.r)) {\n if (shard.r[kid]!.t <= clearedAt) delete shard.r[kid];\n }\n }\n for (const { kid, rec, changed } of ops) {\n if (rec.t <= shard.cleared) continue;\n shard.r[kid] = mergeRecord(shard.r[kid], rec, changed);\n }\n // Tombstones only matter until every device has seen them.\n for (const kid of Object.keys(shard.r)) {\n const rec = shard.r[kid]!;\n if (rec.d && now - rec.t > TOMBSTONE_TTL_MS) delete shard.r[kid];\n }\n const before = Date.now();\n const r = await this.deps.remote.putJSON(path, shard, etag ? { ifMatch: etag } : { ifNoneMatch: true });\n if (r.status === \"ok\") {\n if (r.serverTime) this.clock.observe(r.serverTime, before, Date.now());\n this.etags.set(path, r.etag);\n return r.etag;\n }\n this.etags.delete(path);\n }\n throw new Error(`could not write ${path}: too many concurrent writers`);\n }\n\n private async storeBlob(blob: Blob): Promise<BlobRef> {\n const bytes = new Uint8Array(await blob.arrayBuffer());\n const digest = await crypto.subtle.digest(\"SHA-256\", bytes);\n const hex = Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n const ref = `${this.options.prefix}/_blobs/${hex}`;\n await this.deps.remote.putBlob(ref, blob);\n return { ref, type: blob.type, size: blob.size };\n }\n\n // ── Pull ─────────────────────────────────────────────────────────\n\n private async pull(): Promise<void> {\n const listing = await this.deps.remote.list(this.options.prefix + \"/\");\n await this.applyListing(listing);\n }\n\n private async pullDatabase(name: string): Promise<void> {\n if (!this.deps.userId()) return;\n await this.loadMeta();\n const listing = await this.deps.remote.list(`${this.options.prefix}/${pathSeg(name)}/`);\n await this.applyListing(listing);\n }\n\n private async applyListing(listing: { path: string; etag: string }[]): Promise<void> {\n const re = new RegExp(\"^\" + this.options.prefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\") + \"/([^/]+)/([^/]+)/(\\\\d+)\\\\.json$\");\n const pending = await this.log.pending();\n const etagUpdates: Record<string, unknown> = {};\n for (const entry of listing) {\n const m = re.exec(entry.path);\n if (!m) continue;\n const db = decodeURIComponent(m[1]!);\n const store = decodeURIComponent(m[2]!);\n if (this.etags.get(entry.path) === entry.etag) continue;\n if (this.storeExcluded(db, store)) continue;\n const schema = this.schemas.get(db);\n if (!schema || !schema.has(store)) continue; // the app has not created it (yet)\n const r = await this.deps.remote.getJSON(entry.path, this.etags.get(entry.path));\n if (r.status === \"unchanged\") {\n continue;\n }\n if (r.status === \"missing\") {\n this.etags.delete(entry.path);\n etagUpdates[\"etag:\" + entry.path] = undefined;\n continue;\n }\n const s = r.value as Partial<Shard> | null;\n const shard: Shard = { v: 1, cleared: s?.cleared ?? 0, r: s?.r ?? {} };\n await this.applyShard(db, store, shard, pending);\n this.etags.set(entry.path, r.etag);\n etagUpdates[\"etag:\" + entry.path] = r.etag;\n }\n if (Object.keys(etagUpdates).length) await this.log.setMeta(etagUpdates);\n }\n\n private async applyShard(db: string, store: string, shard: Shard, pending: LoggedOp[]): Promise<void> {\n // A local write newer than the remote record wins until it is pushed.\n const localNewer = new Map<string, number>();\n let localClear = 0;\n for (const op of pending) {\n if (op.db !== db || op.store !== store) continue;\n if (op.kind === \"clear\") localClear = Math.max(localClear, op.ts);\n else localNewer.set(op.kid, Math.max(localNewer.get(op.kid) ?? 0, op.ts));\n }\n const clearKey = `cleared:${db}/${store}`;\n const appliedClear = (await this.log.getMeta<number>(clearKey)) ?? 0;\n const schema = this.schemas.get(db)?.get(store);\n const keys: IDBValidKey[] = [];\n let clearedLocally = false;\n await this.withStore(db, store, \"readwrite\", (st) => {\n if (shard.cleared > appliedClear && shard.cleared > localClear) {\n // Another device cleared this store after our last pull: drop the\n // local rows, keep what is still unpushed (replayed below).\n st.clear();\n clearedLocally = true;\n }\n for (const kid of Object.keys(shard.r)) {\n const rec = shard.r[kid]!;\n if ((localNewer.get(kid) ?? 0) >= rec.t) continue;\n if (rec.t <= localClear) continue;\n const key = decodeKey(rec.k);\n keys.push(key);\n if (rec.d) {\n st.delete(key);\n } else {\n // Values are decoded before the transaction (Blobs need the\n // network); see the pre-pass below.\n const value = (rec as ShardRecord & { _decoded?: unknown })._decoded;\n if (schema && schema.keyPath !== null) st.put(value);\n else st.put(value, key);\n }\n }\n if (clearedLocally) {\n for (const op of pending) {\n if (op.db !== db || op.store !== store || op.kind !== \"put\" || op.ts <= shard.cleared) continue;\n if (schema && schema.keyPath !== null) st.put(op.value);\n else st.put(op.value, decodeKey(op.key));\n }\n }\n }, async () => {\n for (const kid of Object.keys(shard.r)) {\n const rec = shard.r[kid]!;\n if (rec.d || (localNewer.get(kid) ?? 0) >= rec.t || rec.t <= localClear) continue;\n (rec as ShardRecord & { _decoded?: unknown })._decoded = await decodeValue(rec.v, (ref) =>\n this.deps.remote.getBlob(ref.ref),\n );\n }\n });\n if (shard.cleared > appliedClear) await this.log.setMeta({ [clearKey]: shard.cleared });\n if (keys.length > 0 || clearedLocally) {\n this.emitChange({ db, store, keys, cleared: clearedLocally, source: \"remote\" });\n }\n }\n\n private emitChange(change: DbChange): void {\n this.moved = true;\n this.deps.events.emit(\"db:change\", change);\n try {\n this.post(change);\n } catch {\n /* keys that cannot be cloned across tabs: the local event already fired */\n }\n }\n\n // ── The engine's own connections ─────────────────────────────────\n\n private conn(db: string): Promise<IDBDatabase> {\n let p = this.conns.get(db);\n if (!p) {\n p = new Promise<IDBDatabase>((resolve, reject) => {\n const open = this.hook ? this.hook.originalOpen : (n: string) => indexedDB.open(n);\n const r = open(db);\n r.onsuccess = () => {\n const h = r.result;\n // Step aside when the app upgrades its schema.\n h.onversionchange = () => {\n h.close();\n this.conns.delete(db);\n };\n resolve(h);\n };\n r.onerror = () => reject(r.error ?? new Error(`cannot open ${db}`));\n r.onblocked = () => reject(new Error(`open of ${db} blocked`));\n });\n this.conns.set(db, p);\n p.catch(() => this.conns.delete(db));\n }\n return p;\n }\n\n private async withStore(\n db: string,\n store: string,\n mode: IDBTransactionMode,\n fn: (st: IDBObjectStore) => void,\n prepare?: () => Promise<void>,\n ): Promise<void> {\n if (prepare) await prepare();\n const h = await this.conn(db);\n if (!h.objectStoreNames.contains(store)) return;\n const tx = h.transaction(store, mode);\n fn(tx.objectStore(store));\n await idbDone(tx);\n }\n}\n\nexport { idbRequest };\n","// sync/remote.ts — the sync engine's storage, over the files API.\n//\n// Shards are plain JSON files in the user's file space for the app, so\n// they share its quota, billing and deletion with everything else the\n// app stores for that user. Conditional reads (304) and writes (412 /\n// 409) come from the files API, which checks them against the store.\n\nimport type { FilesService } from \"../files.js\";\nimport type { APIError } from \"../errors.js\";\nimport type { SyncRemote } from \"./engine.js\";\n\nexport class FilesRemote implements SyncRemote {\n constructor(private readonly files: FilesService) {}\n\n async list(prefix: string): Promise<{ path: string; etag: string }[]> {\n const out: { path: string; etag: string }[] = [];\n let cursor: string | undefined;\n do {\n const page = await this.files.list({ prefix, limit: 1000, ...(cursor ? { cursor } : {}) });\n for (const f of page.files) if (f.etag) out.push({ path: f.path, etag: f.etag });\n cursor = page.next_cursor;\n } while (cursor);\n return out;\n }\n\n async getJSON(path: string, ifNoneMatch?: string) {\n const r = await this.files.readJSON(path, ifNoneMatch ? { ifNoneMatch } : undefined);\n if (r.notModified) return { status: \"unchanged\" as const };\n if (r.value === undefined) return { status: \"missing\" as const };\n return { status: \"ok\" as const, value: r.value, etag: r.etag ?? \"\" };\n }\n\n async putJSON(path: string, value: unknown, cond: { ifMatch?: string; ifNoneMatch?: boolean }) {\n try {\n const obj = await this.files.put(path, JSON.stringify(value), {\n contentType: \"application/json\",\n ...(cond.ifMatch ? { ifMatch: cond.ifMatch } : {}),\n ...(cond.ifNoneMatch ? { ifNoneMatch: true } : {}),\n });\n return { status: \"ok\" as const, etag: obj.etag ?? \"\", serverTime: obj.last_modified ? Date.parse(obj.last_modified) : undefined };\n } catch (e) {\n const code = (e as APIError).statusCode;\n if (code === 412 || code === 409) return { status: \"conflict\" as const };\n throw e;\n }\n }\n\n async putBlob(path: string, blob: Blob): Promise<void> {\n try {\n await this.files.put(path, blob, { contentType: blob.type || \"application/octet-stream\", ifNoneMatch: true });\n } catch (e) {\n if ((e as APIError).statusCode !== 409) throw e; // already stored: same bytes, same name\n }\n }\n\n getBlob(path: string): Promise<Blob> {\n return this.files.get(path);\n }\n}\n","import { Transport } from \"./transport.js\";\nimport { ChatService } from \"./chat.js\";\nimport { ModelsService } from \"./models.js\";\nimport { ImageService } from \"./image.js\";\nimport { AudioService } from \"./audio.js\";\nimport { VideoService } from \"./video.js\";\nimport { ComputerService } from \"./computer.js\";\nimport { FilesService } from \"./files.js\";\nimport { EmbeddingsService } from \"./embeddings.js\";\nimport { BrowserAuth, type BrowserUser } from \"./browser-auth.js\";\nimport { FilesRemote, SyncEngine, type SyncOptions, type SyncState } from \"./sync/index.js\";\nimport { SDKEvents } from \"./events.js\";\n\n// The platform, not the bare gateway: personal rk_ keys are checked and\n// billed by www.runapps.ai, which forwards to the gateway. api.runapps.ai\n// only knows the gateway's own keys.\nconst DEFAULT_BASE_URL = \"https://www.runapps.ai\";\n\n/**\n * Auth strategy.\n *\n * - `\"static\"` (default): use `apiKey` or `apiKeyResolver` directly.\n * - `\"runapps\"`: opt into the built-in browser auth flow that\n * handshakes with `https://www.runapps.ai/api/sdk/grant`. No\n * external auth.js script needed; the SDK consumes the URL\n * fragment after the redirect-back, exposes `client.user` and\n * `client.signIn()`, and renders an identity badge. Only useful\n * in browsers — in Node this falls back to throwing the same\n * \"missing apiKey\" error you'd get with no auth config.\n */\n// \"runapps\" turns on the platform sign-in. \"runjobs\" is a deprecated\n// alias for it, kept so code written before the rename keeps working.\nexport type AuthProvider = \"static\" | \"runapps\" | \"runjobs\";\n\nexport interface ClientOptions {\n /** A personal API key (`rk_…`) or another static key. Leave it out in an\n * app published on RunApps: the browser client then signs the user in. */\n apiKey?: string;\n /**\n * Dynamic API key resolver. See `AuthProvider` for the typical\n * runapps.ai browser-bundle wiring.\n *\n * Wins over `apiKey` when both are supplied.\n */\n apiKeyResolver?: () => string | Promise<string>;\n /**\n * Auth strategy. Rarely needed: in a browser with no `apiKey` /\n * `apiKeyResolver` the client uses `\"runapps\"` (the platform sign-in),\n * otherwise `\"static\"` (the key you pass).\n */\n authProvider?: AuthProvider;\n /**\n * Show the floating identity badge in `runapps` auth mode.\n * **Default `true`** — the badge is now a desktop-ball-style\n * real-time activity indicator (LED dot + progress ring around\n * the avatar + click-through popover with active calls / recent\n * completions / session totals). Set `false` only when the\n * bundle ships its own status UI and you don't want the platform\n * badge in the corner.\n */\n showIdentityBadge?: boolean;\n /**\n * Which corner the identity badge floats in. Defaults to\n * `\"bottom-right\"` — the canonical tray-app position. Switch to\n * `\"bottom-left\"` when the bundle's own UI occupies the right\n * gutter (e.g. a chat dock); the top corners are useful for\n * fullscreen / portrait kiosk layouts.\n *\n * The popover anchors itself to the same corner — bottom-left\n * badge → popover opens up + right of the badge; top-* badges →\n * popover opens down. Each corner only nudges the CSS; the badge\n * + popover content is identical.\n */\n badgePosition?: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\";\n /**\n * @deprecated Use `showIdentityBadge` instead. Kept for back-compat;\n * acts as `showIdentityBadge: !hideIdentityBadge` when set.\n */\n hideIdentityBadge?: boolean;\n /**\n * Pin the runapps.ai grant flow to a specific project (only used\n * when `authProvider: \"runapps\"`). When set, `/api/sdk/grant`\n * receives `project_id=<value>` and mints an `rrt_*` resource\n * token bound to THAT project — required for `client.files.*`\n * calls from any origin that isn't a registered (origin, app)\n * pair (e.g. a bundle running on `localhost:5173` during dev).\n *\n * Production bundles served from a registered origin can omit\n * this; the gateway derives the project from the (origin, app)\n * pair.\n */\n project?: string;\n /** Override the default base URL, `https://www.runapps.ai` (the\n * platform: personal keys and app tokens are both accepted there). */\n baseURL?: string;\n /** Optional fetch override (e.g. node-fetch with custom agent). */\n fetch?: typeof fetch;\n /**\n * RunApps Sync: keep the data the app stores in IndexedDB on every\n * device the user signs in on. **On by default** in an app on RunApps\n * (browser sign-in mode): the SDK observes every database the app\n * opens, pushes committed writes a few seconds later, pulls other\n * devices' changes when the page is opened, focused or comes back\n * online, and shows the state on the badge. Pass `false` to turn it\n * off, or options to limit it to some databases / leave stores out.\n * See `client.sync`.\n */\n sync?: boolean | SyncOptions;\n}\n\n/**\n * Top-level RunApps SDK client. Construct once, share across services.\n *\n * Browser bundle (resource projects on runapps.ai):\n *\n * ```html\n * <script src=\"https://cdn.jsdelivr.net/npm/@runappsai/sdk/dist/sdk.umd.js\"></script>\n * <script>\n * const client = new RunApps();\n * const res = await client.chat.create({\n * model: \"gpt-4o-mini\",\n * messages: [{ role: \"user\", content: \"hello\" }],\n * });\n * </script>\n * ```\n *\n * Node / server-side:\n *\n * ```ts\n * import { RunApps } from \"@runappsai/sdk\";\n * const client = new RunApps({ apiKey: process.env.RUNAPPS_API_KEY! });\n * ```\n */\nexport class RunApps {\n readonly chat: ChatService;\n readonly models: ModelsService;\n readonly image: ImageService;\n readonly audio: AudioService;\n readonly video: VideoService;\n readonly computer: ComputerService;\n readonly files: FilesService;\n readonly embeddings: EmbeddingsService;\n\n /**\n * Browser auth helper, populated only when `authProvider: \"runapps\"`.\n * Use `client.signIn()` and `client.user` for the common cases; the\n * full instance is exposed for advanced flows (manual `onTokenChange`\n * subscription, etc.).\n */\n readonly auth: BrowserAuth | null = null;\n /**\n * RunApps Sync (see `ClientOptions.sync`), or null when it\n * is off. `status()` gives the state the badge shows; `now()` pushes\n * and pulls immediately; `client.events.on(\"db:change\", …)` tells the\n * app which store received remote changes so it can re-read.\n */\n readonly sync: { status(): SyncState; now(): Promise<void>; pause(): void; resume(): void; dispose(): void } | null = null;\n\n /**\n * Runtime event bus for SDK-call telemetry. Every LLM-ish service\n * (chat / embeddings / image / audio / video / computer) fires\n * `request:start`, `request:streamDelta`, `request:end`, and\n * `request:error` events here so UI overlays — like the bottom-right\n * identity badge's activity ring — can render real-time state\n * without business code threading anything through.\n *\n * Zero-cost when nobody subscribes (emit is a few property reads).\n * Subscribe via `client.events.on(\"request:start\", handler)` — the\n * returned closure is the unsubscribe.\n */\n readonly events: SDKEvents = new SDKEvents();\n\n constructor(options: ClientOptions = {}) {\n // With no key and no explicit authProvider, a browser client uses the\n // platform sign-in: `new RunApps()` is all an app needs. Elsewhere\n // (Node, workers without a window) there is no one to sign in.\n const provider =\n options.authProvider ??\n (!options.apiKey && !options.apiKeyResolver && typeof window !== \"undefined\"\n ? \"runapps\"\n : \"static\");\n let apiKeyResolver = options.apiKeyResolver;\n let baseURL = options.baseURL;\n let onUnauthorized: (() => void) | undefined;\n\n if (provider === \"runapps\" || provider === \"runjobs\") {\n // The grant page (/api/sdk/grant) and the gateway both live on\n // www.runapps.ai. An explicit baseURL keeps control.\n baseURL = baseURL ?? \"https://www.runapps.ai\";\n // Badge default: SHOWN. The badge is now a real-time activity\n // indicator (LED + ring + popover) — useful enough that we'd\n // rather have the rare \"I already have my own UI\" bundle opt\n // OUT with `showIdentityBadge: false` than have every bundle\n // miss the live feedback by default. The legacy\n // `hideIdentityBadge` flag still wins when set, for callers\n // that explicitly suppressed the badge under the old default.\n const showBadge =\n options.showIdentityBadge !== undefined\n ? options.showIdentityBadge\n : options.hideIdentityBadge !== undefined\n ? !options.hideIdentityBadge\n : true;\n const auth = new BrowserAuth({\n origin: baseURL,\n hideBadge: !showBadge,\n // Pipe the shared event bus into BrowserAuth so the badge can\n // render its real-time activity ring / LED / popover. Safe to\n // pass even when the caller never subscribes — emit is a few\n // property reads when nobody's listening.\n events: this.events,\n ...(options.badgePosition !== undefined && { badgePosition: options.badgePosition }),\n ...(options.project !== undefined && { project: options.project }),\n });\n (this as { auth: BrowserAuth | null }).auth = auth;\n // The browser auth resolver wins over any caller-supplied one\n // because the auth flow owns token issuance + refresh from now on.\n apiKeyResolver = auth.getToken;\n // 401 from the gateway → token was revoked server-side (typically\n // because the user unsubscribed in another tab). Drop the cached\n // token so the transport's auto-retry hits a fresh signIn() flow.\n onUnauthorized = () => auth.invalidate();\n }\n\n if (!options.apiKey && !apiKeyResolver) {\n throw new Error(\n \"runapps: pass `apiKey` (or `apiKeyResolver`); outside a browser there is no sign-in to fall back to\",\n );\n }\n const transport = new Transport({\n baseURL: baseURL ?? DEFAULT_BASE_URL,\n ...(options.apiKey !== undefined && { apiKey: options.apiKey }),\n ...(apiKeyResolver !== undefined && { apiKeyResolver }),\n ...(onUnauthorized !== undefined && { onUnauthorized }),\n ...(options.fetch ? { fetchImpl: options.fetch } : {}),\n });\n // Service constructors receive the shared event bus so each call\n // path (chat/embeddings/image/audio/video/computer) can emit\n // start/end/error around its network operations. Files and Models\n // intentionally don't take it — they're infrastructure, not\n // user-facing LLM calls, and surfacing them would clutter the\n // badge with /v1/models polls.\n this.chat = new ChatService(transport, this.events);\n this.models = new ModelsService(transport);\n this.image = new ImageService(transport, this.events);\n this.audio = new AudioService(transport, this.events);\n this.video = new VideoService(transport, this.events);\n this.computer = new ComputerService(transport, this.events);\n // `project` also scopes the file URLs (/v1/p/<project>/files/...),\n // so two apps never issue byte-identical file requests. Omitted →\n // legacy unscoped /v1/files/... paths.\n this.files = new FilesService(transport, options.project, this.events);\n this.embeddings = new EmbeddingsService(transport, this.events);\n\n // Sync: an app on RunApps gets it unless it says no. It\n // stores under the signed-in user, so only browser sign-in mode has\n // someone to sync for.\n const wantSync = options.sync ?? this.auth !== null;\n if (wantSync && typeof indexedDB !== \"undefined\" && this.auth) {\n const auth = this.auth;\n const engine = new SyncEngine(\n {\n // Its own file service, without the event bus: sync traffic\n // must not spin the badge every minute.\n remote: new FilesRemote(new FilesService(transport, options.project)),\n events: this.events,\n userId: () => auth.ownerId(),\n onAuthChange: (cb) => auth.onTokenChange(() => cb()),\n },\n typeof wantSync === \"object\" ? wantSync : {},\n );\n (this as { sync: RunApps[\"sync\"] }).sync = engine;\n }\n }\n\n /** Force a redirect to the runapps.ai grant page. No-op in static\n * auth mode or in Node. */\n signIn(): void {\n this.auth?.signIn();\n }\n\n /**\n * Clear the cached token + identity from memory and localStorage.\n * The next API call will trigger sign-in again (redirect to grant\n * page). Wire this to a \"Sign out\" button in your settings UI.\n *\n * No-op in static auth mode or in Node.\n */\n signOut(): void {\n this.auth?.signOut();\n }\n\n /** Currently-authenticated user (runapps auth mode), or null. */\n get user(): BrowserUser | null {\n return this.auth?.user ?? null;\n }\n}\n","// Mirror of internal/api/options_schema/types.go on the JS side.\n// See API.md \"Options schema\" for the full contract; the gateway\n// materialises this from the legacy options blob at /v1/models\n// response time.\n\nimport type { Model } from \"./models.js\";\n\n/** Wire-format options shape returned by /v1/models. */\nexport interface Schema {\n /**\n * Per-field constraints, keyed by canonical request field name\n * (matching the JSON keys on Image/Video/Speech/Embed/Transcribe\n * params). A field absent from this map is NOT accepted by the\n * model.\n */\n inputs?: Record<string, FieldSchema>;\n /** Cross-field rules. */\n constraints?: Constraint[];\n /** Rich content (TTS voices, emotions). */\n catalog?: Catalog;\n}\n\nexport interface FieldSchema {\n type:\n | \"string\"\n | \"int\"\n | \"float\"\n | \"bool\"\n | \"url\"\n | \"url[]\"\n | \"string[]\"\n | \"file\";\n /** Default when omitted is \"optional\". */\n presence?: \"required\" | \"optional\" | \"forbidden\";\n min?: number;\n max?: number;\n default?: unknown;\n enum?: unknown[];\n max_items?: number;\n /** Lower-bound counterpart of max_items. Applies to array types\n * AND `repeatable` object groups. Nil = no minimum. */\n min_items?: number;\n /** Semantic hint for media-input fields. */\n role?: \"source\" | \"reference\" | \"motion\";\n /** When present, this field is a structured sub-object. The wire\n * value is `{<inner-name>: <value>, ...}` matching this inner\n * schema; renderers recurse into it. With `repeatable: true` the\n * wire value becomes an array of such objects (e.g. multi-utterance\n * TTS \"+ add another group\"). */\n fields?: Record<string, FieldSchema>;\n /** Pairs with `fields`: array-of-objects rather than a single\n * nested object. Ignored when `fields` is absent. */\n repeatable?: boolean;\n\n // ─── Display metadata — drives auto-rendered playground / form UIs.\n // All optional; renderers fall back to type/role-based rendering\n // when absent. See API.md \"Options schema\" for the full vocabulary.\n\n /** Human-readable display label. Falls back to the field name. */\n label?: string;\n /** One-sentence tooltip / sub-text explainer. */\n help?: string;\n /**\n * Widget override hint:\n * \"textarea\" — large multi-line string\n * \"slider\" — numeric with min/max\n * \"radio\" — small enum\n * \"voice_picker\" — string with values from catalog.voices\n * \"code\" — long string with syntax highlighting\n * \"segmented\" — pill-shaped row of enum values (compact,\n * horizontal). Best for short ordered enums\n * like the resolution tier (512P / 1K / 2K /\n * 4K) in image generation. Renders as a single\n * line; selected option has solid background.\n * \"tile\" — grid of square cards, each carrying a small\n * visual proxy for the value (aspect-ratio\n * shape icon, or fit-brackets for the\n * \"adaptive\" sentinel). Used for aspect_ratio\n * in image generation. When the enum includes\n * \"adaptive\" or \"auto\", that entry is\n * promoted to a row-spanning anchor block.\n * \"tabs\" — horizontal tab strip. Same data contract as\n * \"radio\" but a different visual. Drives\n * `ui.show_when` on sibling fields.\n * \"card_group\" — bordered card containing the field's nested\n * `fields`. With `repeatable: true` the card\n * becomes a \"+ add another\"-able stack.\n * Unknown values fall back to the type default (forward-compat).\n */\n widget?: string;\n /**\n * Nested presentational hints:\n * \"group\" (string) — sectional grouping (\"main\"/\"advanced\"/\"output\").\n * \"order\" (int) — display order within the group; lower first.\n * \"card_label\" (string) — title above a card_group; \"{{i}}\" is\n * substituted with the card index for\n * repeatable groups.\n * \"show_when\" ({field, equals})\n * — purely-visual conditional: hide this\n * field unless a sibling's value matches.\n * Validator ignores it; server still\n * accepts conditionally-hidden fields.\n */\n ui?: {\n group?: string;\n order?: number;\n card_label?: string;\n show_when?: { field: string; equals: unknown };\n [k: string]: unknown;\n };\n}\n\nexport type ConstraintKind =\n | \"any_of_required\"\n | \"mutually_exclusive\"\n | \"group_mutex\"\n | \"requires_all\"\n | \"pixel_bounds\";\n\nexport interface Group {\n /** Stable id; also the value the discriminator field\n * (`Constraint.name`) takes when this group is active. */\n name?: string;\n /** Tab strip display label. Falls back to `name`. */\n label?: string;\n fields: string[];\n}\n\nexport interface Constraint {\n kind: ConstraintKind;\n fields?: string[];\n /**\n * On a `group_mutex` constraint, names the discriminator field\n * selecting which group is active. The renderer auto-injects a\n * `tabs` input with this name (enum = group names) and gives\n * every group member an implicit `show_when` pointing at it.\n * When omitted, the constraint behaves as legacy anonymous-group\n * mutex — all members render flat, mutex enforced at submit.\n */\n name?: string;\n /**\n * Human-readable title for the synthesized tab widget (e.g.\n * \"Animation mode\"). Falls back to `name` when omitted.\n */\n label?: string;\n /**\n * Used by `group_mutex`. Wire shape is either the legacy\n * `string[][]` (each `[a,b,...]` reads as an anonymous\n * `{fields:[a,b,...]}` Group) or the new `Group[]`. Consumers\n * should normalize via `normalizeGroups()` before reading.\n */\n groups?: Group[] | string[][];\n when?: string;\n then?: string[];\n min?: number;\n max?: number;\n}\n\n/**\n * Normalize either legacy `string[][]` or new `Group[]` into a\n * uniform `Group[]`. Mirror of sdk-go's `Group.UnmarshalJSON`.\n */\nexport function normalizeGroups(g: Constraint[\"groups\"]): Group[] {\n if (!g) return [];\n return g.map(item =>\n Array.isArray(item) ? { fields: item as string[] } : (item as Group),\n );\n}\n\nexport interface Catalog {\n voices?: Array<Record<string, unknown>>;\n emotions?: string[];\n}\n\n/**\n * Parse the model's wire-format `options` blob into the typed Schema.\n * Returns null when the model has no options or the blob can't be\n * structurally decoded — callers fall back to whatever default\n * behaviour they had pre-schema.\n */\nexport function getOptionsSchema(model: Model): Schema | null {\n const opts = model.options;\n if (!opts || typeof opts !== \"object\") return null;\n // The Model interface types options as Record<string, unknown>;\n // Schema has the same JSON layout, so a structural cast is safe\n // as long as the gateway emits the wire shape we expect. SDK\n // consumers should call validateRequest() to surface errors —\n // this getter is a thin typed view over the same data.\n return opts as unknown as Schema;\n}\n\n/**\n * True iff the model declares an Inputs entry for `name` with\n * presence != \"forbidden\". Use for \"should I show this UI chip\"\n * decisions.\n */\nexport function acceptsField(model: Model, name: string): boolean {\n const schema = getOptionsSchema(model);\n const f = schema?.inputs?.[name];\n if (!f) return false;\n return f.presence !== \"forbidden\";\n}\n\n/**\n * True iff the schema marks `name` as presence: \"required\". Use for\n * red-star UI marks and submit-button gating.\n */\nexport function requiresField(model: Model, name: string): boolean {\n const schema = getOptionsSchema(model);\n return schema?.inputs?.[name]?.presence === \"required\";\n}\n\n/**\n * Discrete enum for the named field, or null if no enum constraint.\n * Use to populate dropdown options on the client.\n */\nexport function allowedValuesFor(model: Model, name: string): unknown[] | null {\n const schema = getOptionsSchema(model);\n return schema?.inputs?.[name]?.enum ?? null;\n}\n","// Generic validator for the gateway's options schema. Mirrors\n// /Users/zero/repos/sdk-go/validate.go — three-pass algorithm,\n// same field/constraint vocabulary, same parse logic.\n\nimport { normalizeGroups } from \"./model_options.js\";\nimport type { Schema, FieldSchema, Constraint, Group } from \"./model_options.js\";\n\nexport interface ValidationError {\n field: string;\n reason: string;\n}\n\n/**\n * Three-pass validator: per-field type/enum/bounds → per-field\n * presence → cross-field constraints. Unknown fields in `req`\n * (vendor passthroughs) are silently ignored — the gateway still\n * validates them server-side if it cares.\n *\n * Returns an empty array on success, or a flat list of all\n * validation failures so the UI can surface them at once.\n */\nexport function validateRequest(\n schema: Schema | null | undefined,\n req: Record<string, unknown> = {},\n): ValidationError[] {\n if (!schema) return [];\n const errors: ValidationError[] = [];\n\n // Pass 1+2: per-field.\n if (schema.inputs) {\n for (const [name, field] of Object.entries(schema.inputs)) {\n const set = name in req;\n const val = req[name];\n if (set && !isEmpty(val)) {\n errors.push(...validateFieldValue(name, field, val));\n } else if (set && field.min_items !== undefined) {\n // Empty array trips min_items even when isEmpty() short-\n // circuits validateFieldValue. Mirrors the sdk-go fix: bound\n // was declared explicitly, dropping it silently would let a\n // client POST `groups: []` to a schema that requires >=1.\n if (Array.isArray(val) && val.length < field.min_items) {\n errors.push({ field: name, reason: `at least ${field.min_items} items` });\n }\n }\n errors.push(...validatePresence(name, field, val, set));\n }\n }\n\n // Pass 3: cross-field.\n for (const c of schema.constraints ?? []) {\n errors.push(...validateConstraint(c, req));\n }\n\n return errors;\n}\n\nfunction validateFieldValue(\n name: string,\n field: FieldSchema,\n val: unknown,\n): ValidationError[] {\n const errors: ValidationError[] = [];\n\n if (!typeMatches(field.type, val)) {\n errors.push({\n field: name,\n reason: `expected type ${field.type}, got ${describeType(val)}`,\n });\n return errors; // bail early — bound/enum checks need correct type\n }\n\n if (field.enum && field.enum.length > 0 && !inEnum(val, field.enum)) {\n errors.push({\n field: name,\n reason: `must be one of ${JSON.stringify(field.enum)}`,\n });\n }\n\n if (field.type === \"int\" || field.type === \"float\") {\n const n = numberValue(val);\n if (n !== null) {\n if (field.min !== undefined && n < field.min) {\n errors.push({ field: name, reason: `must be >= ${field.min}` });\n }\n if (field.max !== undefined && n > field.max) {\n errors.push({ field: name, reason: `must be <= ${field.max}` });\n }\n }\n }\n\n // Array bounds — apply to scalar array Types (\"url[]\", \"string[]\")\n // AND to `repeatable` object groups whose value is a JSON array.\n if (Array.isArray(val)) {\n if (field.max_items !== undefined && val.length > field.max_items) {\n errors.push({\n field: name,\n reason: `at most ${field.max_items} items`,\n });\n }\n if (field.min_items !== undefined && val.length < field.min_items) {\n errors.push({\n field: name,\n reason: `at least ${field.min_items} items`,\n });\n }\n }\n\n // Nested object recursion. `field.fields` declares a structured\n // sub-object; with `repeatable: true` the wire value is an array of\n // such objects. Errors carry a dotted path like\n // \"groups[1].prompt: required\" so callers see the offending row.\n // `ui.show_when` is deliberately NOT honoured at validation time —\n // it's a UI hint only; conditionally-hidden fields are still\n // subject to the same checks if a client sends them.\n if (field.fields) {\n if (field.repeatable) {\n if (!Array.isArray(val)) {\n errors.push({\n field: name,\n reason: `expected array of objects, got ${describeType(val)}`,\n });\n } else {\n val.forEach((item, i) => {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) {\n errors.push({\n field: `${name}[${i}]`,\n reason: `expected object, got ${describeType(item)}`,\n });\n return;\n }\n errors.push(...validateObject(`${name}[${i}]`, field.fields!, item as Record<string, unknown>));\n });\n }\n } else {\n if (typeof val !== \"object\" || val === null || Array.isArray(val)) {\n errors.push({\n field: name,\n reason: `expected object, got ${describeType(val)}`,\n });\n } else {\n errors.push(...validateObject(name, field.fields, val as Record<string, unknown>));\n }\n }\n }\n\n return errors;\n}\n\n/** validateObject runs per-field validation against a sub-schema's\n * `fields` map for one object value. Errors return with\n * `<pathPrefix>.<innerName>` so the caller sees the full nested path. */\nfunction validateObject(\n pathPrefix: string,\n fields: Record<string, FieldSchema>,\n obj: Record<string, unknown>,\n): ValidationError[] {\n const errors: ValidationError[] = [];\n for (const [innerName, innerField] of Object.entries(fields)) {\n const childPath = `${pathPrefix}.${innerName}`;\n const val = obj[innerName];\n const set = innerName in obj;\n if (set && !isEmpty(val)) {\n errors.push(...validateFieldValue(childPath, innerField, val));\n }\n errors.push(...validatePresence(childPath, innerField, val, set));\n }\n return errors;\n}\n\nfunction validatePresence(\n name: string,\n field: FieldSchema,\n val: unknown,\n set: boolean,\n): ValidationError[] {\n const presence = field.presence ?? \"optional\";\n if (presence === \"required\" && (!set || isEmpty(val))) {\n return [{ field: name, reason: \"required\" }];\n }\n if (presence === \"forbidden\" && set && !isEmpty(val)) {\n return [{ field: name, reason: \"must not be set for this model\" }];\n }\n return [];\n}\n\nfunction validateConstraint(\n c: Constraint,\n req: Record<string, unknown>,\n): ValidationError[] {\n switch (c.kind) {\n case \"any_of_required\": {\n const fields = c.fields ?? [];\n const anySet = fields.some((f) => f in req && !isEmpty(req[f]));\n if (anySet) return [];\n return [\n {\n field: fields.join(\"/\"),\n reason: `at least one of ${fields.join(\", \")} is required`,\n },\n ];\n }\n\n case \"mutually_exclusive\": {\n const fields = c.fields ?? [];\n const setFields = fields.filter((f) => f in req && !isEmpty(req[f]));\n if (setFields.length > 1) {\n return [\n {\n field: setFields.join(\"/\"),\n reason: `at most one of ${fields.join(\", \")} may be set (got: ${setFields.join(\", \")})`,\n },\n ];\n }\n return [];\n }\n\n case \"group_mutex\": {\n // Best-effort: enforce only the anonymous \"at most one group\n // active\" rule for both shapes. The strict discriminator-based\n // check (validating `c.name`'s value + rejecting non-active\n // group fields) lives in the gateway only — SDK validation\n // stays a fast pre-flight hint, not a contract enforcer, so we\n // avoid doubling the maintenance surface across SDKs.\n const groups: Group[] = normalizeGroups(c.groups);\n const activeGroups: Group[] = [];\n const activeFields: string[] = [];\n for (const g of groups) {\n const setHere = g.fields.filter((f) => f in req && !isEmpty(req[f]));\n if (setHere.length > 0) {\n activeGroups.push(g);\n activeFields.push(...setHere);\n }\n }\n if (activeGroups.length > 1) {\n const labels = groups.map((g) => `[${g.fields.join(\"+\")}]`).join(\" or \");\n return [\n {\n field: activeFields.join(\"/\"),\n reason: `at most one of ${labels} may be used (got: ${activeFields.join(\", \")})`,\n },\n ];\n }\n return [];\n }\n\n case \"requires_all\": {\n const whenName = c.when;\n if (!whenName) return [];\n const whenSet = whenName in req && !isEmpty(req[whenName]);\n if (!whenSet) return [];\n const missing = (c.then ?? []).filter(\n (f) => !(f in req) || isEmpty(req[f]),\n );\n if (missing.length === 0) return [];\n return [\n {\n field: missing.join(\"/\"),\n reason: `required when ${whenName} is set`,\n },\n ];\n }\n\n case \"pixel_bounds\": {\n const fieldName = c.fields?.[0];\n if (!fieldName) return [];\n const raw = req[fieldName];\n if (typeof raw !== \"string\" || raw === \"\") return [];\n const dims = parsePixelDims(raw);\n if (!dims) {\n return [{ field: fieldName, reason: `expected WxH dimensions, got \"${raw}\"` }];\n }\n const [w, h] = dims;\n const px = w * h;\n const errs: ValidationError[] = [];\n if (c.min !== undefined && px < c.min) {\n errs.push({\n field: fieldName,\n reason: `image too small: ${w}x${h} = ${px} pixels, minimum ${c.min}`,\n });\n }\n if (c.max !== undefined && px > c.max) {\n errs.push({\n field: fieldName,\n reason: `image too large: ${w}x${h} = ${px} pixels, maximum ${c.max}`,\n });\n }\n return errs;\n }\n }\n // Unknown constraint kind — skip (forward-compat).\n return [];\n}\n\n// ─── Type helpers ────────────────────────────────────────────────────\n\nfunction typeMatches(typ: string, val: unknown): boolean {\n switch (typ) {\n case \"string\":\n return typeof val === \"string\";\n case \"int\":\n case \"float\":\n return numberValue(val) !== null;\n case \"bool\":\n return typeof val === \"boolean\";\n case \"url\":\n return typeof val === \"string\" && isURLLike(val);\n case \"url[]\":\n return Array.isArray(val) && val.every((v) => typeof v === \"string\" && isURLLike(v));\n case \"string[]\":\n return Array.isArray(val) && val.every((v) => typeof v === \"string\");\n case \"file\":\n return true; // multipart — can't validate\n default:\n return true; // unknown type — forward-compat\n }\n}\n\nfunction describeType(val: unknown): string {\n if (val === null) return \"null\";\n if (Array.isArray(val)) return \"array\";\n return typeof val;\n}\n\nfunction numberValue(val: unknown): number | null {\n if (typeof val === \"number\" && Number.isFinite(val)) return val;\n return null;\n}\n\nfunction isURLLike(s: string): boolean {\n return s.startsWith(\"http://\") || s.startsWith(\"https://\") || s.startsWith(\"data:\");\n}\n\nfunction isEmpty(val: unknown): boolean {\n if (val === undefined || val === null) return true;\n if (typeof val === \"string\") return val === \"\";\n if (Array.isArray(val)) return val.length === 0;\n return false;\n}\n\nfunction inEnum(val: unknown, enumVals: unknown[]): boolean {\n for (const e of enumVals) {\n if (equalLoose(val, e)) return true;\n }\n return false;\n}\n\nfunction equalLoose(a: unknown, b: unknown): boolean {\n // Numbers compared as JS numbers (already float64-equivalent);\n // strings/bools by ===.\n return a === b;\n}\n\nfunction parsePixelDims(s: string): [number, number] | null {\n for (const sep of [\"x\", \"X\", \"*\", \"×\"]) {\n const idx = s.indexOf(sep);\n if (idx <= 0) continue;\n const w = parseInt(s.slice(0, idx).trim(), 10);\n const h = parseInt(s.slice(idx + sep.length).trim(), 10);\n if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {\n return [w, h];\n }\n }\n return null;\n}\n"],"mappings":"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","names":["src_exports","__export","APIError","ActivityTracker","BrowserAuth","RunApps","FilesRemote","SDKEvents","ServerTools","SyncEngine","acceptsField","acceptsModality","allowedValuesFor","assistantMessage","audioPart","decodeMediaUrl","defaultVoice","encodeImageUrl","getOptionsSchema","hasCapabilityTag","imagePart","installIndexedDBHook","newRequestId","requiresField","supportsInstructText","supportsVoiceClone","systemMessage","textPart","toolResultMessage","userMessage","userMessageParts","validateRequest","videoPart","APIError","statusCode","type","message","__publicField","Transport","opts","__publicField","path","build","url","resp","init","body","headers","cleanETag","serverTime","parseServerDate","etag","buf","contentType","form","text","type","message","parsed","APIError","raw","h","t","SDKEvents","__publicField","name","handler","set","wrapped","payload","h","err","newRequestId","wrapEvents","events","meta","fn","extractEnd","id","newRequestId","startedAt","result","info","err","e","estimateTokens","text","wrapStream","source","extractDelta","totalTokens","lastUsageTokens","costUSD","finishReason","chunk","delta","ServerTools","userMessage","content","systemMessage","assistantMessage","toolResultMessage","toolCallId","userMessageParts","parts","textPart","text","imagePart","url","detail","videoPart","audioPart","ChatService","transport","events","__publicField","params","init","body","wrapEvents","r","sourceGen","resp","parseSSE","wrapStream","chunk","decoder","reader","buffer","value","done","nl","line","data","parsed","e","err","APIError","hasCapabilityTag","model","id","acceptsModality","modality","optBool","key","v","supportsVoiceClone","supportsInstructText","defaultVoice","ModelsService","transport","__publicField","opts","init","path","ImageService","transport","events","__publicField","model","params","init","wrapEvents","r","interval","signal","current","sleep","id","form","buildEditForm","ms","resolve","reject","timer","onAbort","toBlob","input","ct","guessMime","filename","encodeImageUrl","bytes","u8","mime","sniffImageMime","CHUNK","binary","b64","decodeMediaUrl","url","rest","semi","comma","base64ToBytes","resp","buf","bin","out","i","b","AudioService","transport","events","__publicField","model","params","init","wrapEvents","raw","buildSpeechBody","bytes","contentType","decodeMediaUrl","r","submit","waitSpeechJob","form","buildTranscribeForm","waitTranscribeJob","jobId","path","interval","sleep","internalDeadline","status","assembleTranscribeResponse","payload","out","k","v","ms","signal","resolve","reject","timer","onAbort","extra","canonical","body","g","ct","guessAudioMime","blob","filename","VideoService","transport","events","__publicField","model","params","init","wrapEvents","taskID","opts","interval","status","sleep","ms","signal","resolve","reject","timer","onAbort","ComputerService","transport","events","__publicField","model","params","init","wrapEvents","r","pathToURL","base","p","trimmed","FilesService","transport","project","events","__publicField","op","fn","wrapEvents","path","body","opts","headers","cleanETag","content","init","data","contentType","etag","notModified","e","status","size","params","qs","from","to","srcURL","ops","all","i","chunk","resp","EmbeddingsService","transport","events","__publicField","model","params","init","wrapEvents","r","ActivityTracker","__publicField","events","offs","e","o","handler","h","err","now","call","cutoff","sumTokens","s","x","spanMs","c","BIN_CTORS","b","bytesToBase64","bytes","s","i","base64ToBytes","b64","out","isPlainObject","v","proto","encodeValue","value","sink","t","ref","f","name","k","x","needsWrap","decodeValue","source","obj","buf","make","m","pair","blob","encodeKey","key","decodeKey","encoded","keyId","hash32","h","deepEqual","a","toBytes","ba","bb","ka","kb","changedFields","prior","inP","inV","extractKey","keyPath","one","path","cur","part","parts","p","TOKEN_REFRESH_MARGIN_S","STORAGE_KEY_BASE","LEGACY_STORAGE_KEY_BASE","SIGNED_OUT_KEY_BASE","LEGACY_SIGNED_OUT_KEY_BASE","storageKey","project","signedOutKey","legacyStorageKey","legacySignedOutKey","BrowserAuth","opts","__publicField","ActivityTracker","handler","i","hash32","ret","scheme","args","params","token","expiresAt","user","fn","err","data","raw","kv","resolve","reject","done","timers","cleanup","onMessage","t","e","post","d","ID","dashboardUrl","cb","root","mountActivityBadge","ensurePulseKeyframes","style","badgeCornerStyles","position","v","h","popoverVerticalAnchor","popoverHorizontalAnchor","BADGE_POS_COOKIE","LEGACY_BADGE_POS_COOKIE","BADGE_DRAG_THRESHOLD_PX","BADGE_EDGE_INSET_PX","readBadgePosCookie","cookies","s","name","parts","rawX","rawY","x","y","writeBadgePosCookie","maxAge","secure","clampToViewport","w","vw","vh","maxX","maxY","popoverCssForRect","rect","cx","verticalAnchor","horizontalAnchor","id","tracker","onSignOut","corners","currentPopoverCss","el","avatarWrap","ringNS","ring","ringTrack","ringArc","RING_CIRC","avatar","led","popover","popoverOpen","popoverOpenedBy","openTimer","closeTimer","cancelOpen","cancelClose","scheduleClose","closePopover","openPopover","by","createPopover","requestRedraw","dragStartClientX","dragStartClientY","dragStartBadgeLeft","dragStartBadgeTop","pointerDown","isDragging","suppressNextClick","applyAbsolutePos","recomputePopoverAnchor","r","decl","prop","val","dx","dy","clamped","endDrag","savedPos","rafScheduled","intervalHandle","seenChangeAt","reconcile","snap","changeAt","syncedPing","updateRing","updateLED","updatePopover","needsLoop","arc","circ","primary","best","c","fraction","syncFlashUntil","ping","sync","anchorCss","pop","header","headAvatar","headName","headSub","headText","inner","footer","dash","signOutBtn","now","renderActiveSection","renderRecentSection","renderSyncSection","renderSessionSection","sec","hint","relativeTime","sectionLabel","row","left","dot","label","text","color","right","empty","elapsed","rate","bullet","latency","cost","ago","calls","errors","title","lbl","ms","txState","observedDbs","subscribers","originals","clone","v","isPlainObject","proto","makeEvent","type","init","target","ev","p","HookedOpenRequest","real","name","subs","__publicField","e","db","settled","deliver","waits","timeout","s","timer","listener","fn","set","h","err","reportListenerError","cursorStore","cursor","src","subscribersFor","factory","out","patchPrototypes","F","D","S","C","o","tracked","store","version","storeNames","mode","options","tx","info","st","ops","mine","op","value","key","prior","k","extractKey","g","slot","r","snapshot","priorSlot","query","kr","restorePrototypes","installIndexedDBHook","opts","sub","SYNC_DB_NAME","req","r","resolve","reject","done","tx","SyncLog","open","__publicField","SYNC_DB_NAME","db","ops","st","op","ids","id","key","entries","k","prefix","range","keys","vals","out","i","isPlainRecord","v","sameJSON","a","b","mergeRecord","cur","inc","changed","curF","touched","out","f","fields","name","inCur","inInc","curT","TOMBSTONE_TTL_MS","MAX_PUSH_DELAY_MS","BACKOFF_MIN_MS","BACKOFF_MAX_MS","Clock","__publicField","t","serverTime","localBefore","localAfter","mid","off","pathSeg","s","SyncEngine","deps","options","excludedDbs","e","excludedStores","only","installIndexedDBHook","db","store","ops","SyncLog","version","msg","c","p","logged","op","ts","keyId","encodeKey","changedFields","byStore","change","err","schema","names","tx","n","st","stores","on","target","type","fn","ev","d","locks","visible","now","delay","_reason","run","patch","next","offline","k","userId","owner","shard","kid","hash32","path","before","groups","g","done","etagUpdates","clearedAt","lastClear","i","live","byShard","list","shards","_","shardOps","encoded","etag","encodeValue","blob","attempt","rec","r","bytes","digest","hex","ref","listing","re","pending","entry","m","localNewer","localClear","clearKey","appliedClear","keys","clearedLocally","key","decodeKey","value","decodeValue","resolve","reject","h","mode","prepare","FilesRemote","files","__publicField","prefix","out","cursor","page","f","path","ifNoneMatch","value","cond","obj","e","code","blob","DEFAULT_BASE_URL","RunApps","options","__publicField","SDKEvents","provider","apiKeyResolver","baseURL","onUnauthorized","showBadge","auth","BrowserAuth","transport","Transport","ChatService","ModelsService","ImageService","AudioService","VideoService","ComputerService","FilesService","EmbeddingsService","wantSync","engine","SyncEngine","FilesRemote","cb","normalizeGroups","g","item","getOptionsSchema","model","opts","acceptsField","name","f","requiresField","allowedValuesFor","validateRequest","schema","req","errors","name","field","set","val","isEmpty","validateFieldValue","validatePresence","c","validateConstraint","typeMatches","describeType","inEnum","n","numberValue","item","i","validateObject","pathPrefix","fields","obj","innerName","innerField","childPath","presence","f","setFields","groups","normalizeGroups","activeGroups","activeFields","g","setHere","labels","whenName","missing","fieldName","raw","dims","parsePixelDims","w","h","px","errs","typ","isURLLike","v","s","enumVals","e","equalLoose","a","b","sep","idx"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/transport.ts","../src/events.ts","../src/event-wrap.ts","../src/chat.ts","../src/models.ts","../src/image.ts","../src/media.ts","../src/audio.ts","../src/realtime.ts","../src/video.ts","../src/computer.ts","../src/files.ts","../src/embeddings.ts","../src/activity-tracker.ts","../src/sync/codec.ts","../src/browser-auth.ts","../src/sync/hook.ts","../src/sync/log.ts","../src/sync/engine.ts","../src/sync/remote.ts","../src/client.ts","../src/model_options.ts","../src/validate.ts"],"sourcesContent":["/**\n * RunApps SDK for JavaScript / TypeScript.\n *\n * Top-level entry point — exports the `RunApps` client class plus all\n * public types so callers can write strongly-typed wrappers without\n * reaching into per-service modules.\n */\n\n// `RunJobs` is a deprecated alias of `RunApps`, kept for code written\n// before the rename.\nexport { RunApps, RunApps as Client, RunApps as RunJobs, type ClientOptions, type AuthProvider } from \"./client.js\";\n\n// SDK telemetry — `client.events` fires these around every LLM-ish\n// service call so UI overlays (the badge being the canonical example)\n// can render real-time state. The ActivityTracker turns the raw\n// stream into the snapshot the badge reads each frame; exported\n// alongside the bus so power users can build their own status UI.\nexport {\n SDKEvents,\n newRequestId,\n type RequestStartEvent,\n type RequestStreamDeltaEvent,\n type RequestEndEvent,\n type RequestErrorEvent,\n type SDKEventMap,\n type SDKCapability,\n type Unsubscribe,\n} from \"./events.js\";\nexport {\n ActivityTracker,\n type ActiveCall,\n type CompletedCall,\n type SessionStats,\n type ActivitySnapshot,\n} from \"./activity-tracker.js\";\nexport { BrowserAuth, type BrowserAuthOptions, type BrowserUser } from \"./browser-auth.js\";\n// RunApps Sync — on by default in apps; exported for status UIs and\n// for tests that drive the engine with their own storage.\nexport {\n SyncEngine,\n FilesRemote,\n installIndexedDBHook,\n type SyncOptions,\n type SyncState,\n type DbChange,\n type SyncRemote,\n type SyncDeps,\n} from \"./sync/index.js\";\nexport type { SyncStateEvent, DbChangeEvent } from \"./events.js\";\nexport { APIError } from \"./errors.js\";\nexport type { Usage } from \"./types.js\";\n\n// Chat — service class is internal; types + builders are public.\nexport type {\n ChatMessage,\n ContentPart,\n ChatTool,\n StreamOptions,\n ChatCompletionParams,\n ChatToolCall,\n ChatChoiceMessage,\n ChatChoice,\n ChatCompletion,\n ChatChunkDelta,\n ChatChunkChoice,\n ChatCompletionChunk,\n PromptRef,\n PromptInput,\n} from \"./chat.js\";\nexport {\n userMessage,\n systemMessage,\n assistantMessage,\n toolResultMessage,\n userMessageParts,\n textPart,\n imagePart,\n videoPart,\n audioPart,\n} from \"./chat.js\";\n\n// Models\nexport type { Model, ModelListOptions, Tag } from \"./models.js\";\nexport {\n hasCapabilityTag,\n acceptsModality,\n supportsVoiceClone,\n supportsInstructText,\n defaultVoice,\n} from \"./models.js\";\n\n// Model options schema (input validation + capability introspection)\nexport type {\n Schema,\n FieldSchema,\n Constraint,\n ConstraintKind,\n Catalog,\n} from \"./model_options.js\";\nexport {\n getOptionsSchema,\n acceptsField,\n requiresField,\n allowedValuesFor,\n} from \"./model_options.js\";\nexport type { ValidationError } from \"./validate.js\";\nexport { validateRequest } from \"./validate.js\";\n\n// Image\nexport type {\n ImageGenerateParams,\n ImageResult,\n ImageUsage,\n ImageResponse,\n ImageEditParams,\n ImageFileInput,\n AsyncImageJob,\n} from \"./image.js\";\n\n// Audio\nexport type {\n SpeechParams,\n SpeechResponse,\n TranscribeParams,\n TranscribeResponse,\n AudioFileInput,\n} from \"./audio.js\";\n\n// Realtime voice\nexport type {\n RealtimeEvent,\n RealtimeUsage,\n RealtimeCloseInfo,\n RealtimeConnectOptions,\n WebSocketLike,\n WebSocketConstructor,\n} from \"./realtime.js\";\nexport {\n RealtimeSession,\n pcm16FromFloat32,\n float32FromPCM16,\n bytesToBase64,\n base64ToBytes,\n} from \"./realtime.js\";\n\n// Video\nexport type {\n VideoGenerateParams,\n VideoTask,\n VideoStatus,\n VideoUsageTokens,\n WaitOptions,\n} from \"./video.js\";\n\n// Media helpers (shared by image + video + chat-multimodal)\nexport { encodeImageUrl, decodeMediaUrl } from \"./media.js\";\n\n// Computer\nexport type {\n ComputerStepParams,\n ComputerContentBlock,\n ComputerResponse,\n} from \"./computer.js\";\n\n// Files (per-project file system)\nexport type {\n FileObject,\n FileListResult,\n PutOptions as FilePutOptions,\n ListOptions as FileListOptions,\n PutFromURLOptions as FilePutFromURLOptions,\n BatchOp as FileBatchOp,\n BatchResult as FileBatchResult,\n} from \"./files.js\";\n\n// Embeddings\nexport type {\n EmbeddingsParams,\n Embedding,\n EmbeddingsUsage,\n EmbeddingsResponse,\n} from \"./embeddings.js\";\n\n// Server-tool names for chat.create({ server_tools }), with autocomplete.\nexport { ServerTools, type ServerToolName } from \"./chat.js\";\n","/**\n * APIError represents an error returned by the RunApps gateway.\n *\n * Use `instanceof APIError` (or check `name === \"APIError\"`) to distinguish\n * gateway errors from network / runtime errors:\n *\n * ```ts\n * try {\n * await client.chat.create({ model: \"...\", messages: [...] });\n * } catch (e) {\n * if (e instanceof APIError) {\n * console.error(`API ${e.statusCode}: ${e.message}`);\n * } else {\n * throw e;\n * }\n * }\n * ```\n */\nexport class APIError extends Error {\n public readonly statusCode: number;\n public readonly type: string;\n\n constructor(statusCode: number, type: string, message: string) {\n super(`runapps: ${statusCode} ${type}: ${message}`);\n this.name = \"APIError\";\n this.statusCode = statusCode;\n this.type = type;\n }\n}\n","import { APIError } from \"./errors.js\";\n\n/**\n * Internal — shared HTTP transport used by every service. Not part of the\n * public API; do not import from outside `src/`.\n *\n * Uses the global `fetch` (Node 18+ / browsers / Deno / Bun all ship it).\n * Errors from the gateway (status >= 400) are parsed and surfaced as\n * `APIError`. Network errors propagate as the underlying fetch reject.\n */\nexport interface TransportOptions {\n baseURL: string;\n /** Static bearer token. Required unless `apiKeyResolver` is supplied. */\n apiKey?: string;\n /** Dynamic resolver — called immediately before each request. Use this\n * when the token is short-lived and refreshed externally (e.g. running\n * inside an app bundle that exposes\n * its own `getToken()`). The result is awaited per request, so\n * the caller can return a Promise that hits a token endpoint. Wins\n * over `apiKey` when both are supplied. */\n apiKeyResolver?: () => string | Promise<string>;\n /**\n * Hook invoked on 401 responses, *before* the request is automatically\n * retried once. Use this to invalidate any cached token so the next\n * `apiKeyResolver` call fetches a fresh one. Throwing here cancels\n * the retry — the original 401 propagates.\n *\n * Typical wiring (browser-auth):\n * onUnauthorized: () => auth.invalidate()\n * which clears the in-memory + persisted token without flipping the\n * sticky \"signed out\" flag. The retry's resolveToken() then triggers\n * the standard signIn() redirect-grant flow.\n */\n onUnauthorized?: () => void | Promise<void>;\n /** Optional fetch override (e.g. node-fetch with custom agent). */\n fetchImpl?: typeof fetch;\n}\n\nexport class Transport {\n readonly baseURL: string;\n private readonly apiKey: string | undefined;\n private readonly apiKeyResolver: (() => string | Promise<string>) | undefined;\n private readonly onUnauthorized: (() => void | Promise<void>) | undefined;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: TransportOptions) {\n if (!opts.apiKey && !opts.apiKeyResolver) {\n throw new Error(\n \"RunApps: pass either `apiKey` or `apiKeyResolver` when constructing the client\",\n );\n }\n this.baseURL = opts.baseURL.replace(/\\/+$/, \"\");\n this.apiKey = opts.apiKey;\n this.apiKeyResolver = opts.apiKeyResolver;\n this.onUnauthorized = opts.onUnauthorized;\n // Bind to globalThis when using the platform's native fetch.\n // Without binding, `this.fetchImpl(...)` invokes with `this`\n // pointing at the Transport instance, and the browser's\n // `Window.fetch` rejects that with \"Illegal invocation\".\n // Caller-supplied `fetchImpl` is used as-is — they're responsible\n // for any binding their wrapper needs.\n this.fetchImpl = opts.fetchImpl ?? fetch.bind(globalThis);\n }\n\n /** Resolve the bearer token for the next request. */\n private async resolveToken(): Promise<string> {\n if (this.apiKeyResolver) return await this.apiKeyResolver();\n return this.apiKey ?? \"\";\n }\n\n /** The current bearer token, for connections fetch can't make (WebSocket). */\n bearerToken(): Promise<string> {\n return this.resolveToken();\n }\n\n /**\n * Wrap a fetch call with single-attempt 401 retry. `build` produces a\n * fresh RequestInit each call (so headers can re-resolve a fresh token,\n * and bodies can be re-streamed for retry). On 401, invokes\n * `onUnauthorized()` (giving the caller a chance to invalidate cached\n * auth state) then retries once with a fresh init. No retry if\n * `onUnauthorized` is absent or already retried.\n */\n private async fetchWithAuthRetry(\n path: string,\n build: () => Promise<RequestInit>,\n ): Promise<Response> {\n const url = this.baseURL + path;\n let resp = await this.fetchImpl(url, await this.buildInit(build));\n if (resp.status !== 401 || !this.onUnauthorized) return resp;\n try {\n await this.onUnauthorized();\n } catch {\n // Hook bailed out — surface the original 401.\n return resp;\n }\n resp = await this.fetchImpl(url, await this.buildInit(build));\n return resp;\n }\n\n /**\n * Every request goes out uncacheable.\n *\n * Our identity lives in the Authorization header, never in the URL — so\n * two different apps calling e.g. `/v1/files/projects/index.json` hit the\n * *same* URL with different tokens. The browser's HTTP cache doesn't key\n * on request headers, and it is partitioned by the top-level registrable\n * domain, which every `*.runapps.dev` bundle shares. One cacheable\n * response was therefore enough for app A to read app B's file and then\n * save it back as its own. The server no longer marks these cacheable;\n * this is the client-side half of that fix, and it also protects bundles\n * pinned to an older backend.\n */\n private async buildInit(build: () => Promise<RequestInit>): Promise<RequestInit> {\n const init = await build();\n return init.cache ? init : { ...init, cache: \"no-store\" };\n }\n\n /** POST JSON body; parse JSON response. */\n async postJSON<T>(\n path: string,\n body: unknown,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"POST\",\n headers: await this.jsonHeaders(),\n body: JSON.stringify(body),\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /** GET path; parse JSON response. */\n async getJSON<T>(\n path: string,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"GET\",\n headers: await this.authHeaders(),\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /** GET a public path: no Authorization header, so it never fetches a\n * token — in browser sign-in mode that would send a signed-out user to\n * the sign-in page just to read the model catalogue. */\n async getPublicJSON<T>(\n path: string,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchImpl(this.baseURL + path, {\n method: \"GET\",\n cache: \"no-store\",\n ...(init?.signal ? { signal: init.signal } : {}),\n });\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /** GET path; return raw bytes + content-type (for video/audio downloads)\n * and the ETag of those bytes, when the server sends one. */\n async getRaw(\n path: string,\n init?: { signal?: AbortSignal; ifNoneMatch?: string },\n ): Promise<{ data: Uint8Array; contentType: string; etag?: string; notModified?: boolean; serverTime?: number }> {\n const resp = await this.fetchWithAuthRetry(path, async () => {\n const headers: Record<string, string> = await this.authHeaders();\n if (init?.ifNoneMatch) headers[\"If-None-Match\"] = `\"${cleanETag(init.ifNoneMatch)}\"`;\n return { method: \"GET\", headers, signal: init?.signal };\n });\n const serverTime = parseServerDate(resp.headers.get(\"date\"));\n if (resp.status === 304) {\n const etag = cleanETag(resp.headers.get(\"etag\")) || init?.ifNoneMatch;\n return { data: new Uint8Array(0), contentType: \"\", etag, notModified: true, serverTime };\n }\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n const buf = new Uint8Array(await resp.arrayBuffer());\n const contentType = resp.headers.get(\"content-type\") ?? \"\";\n const etag = cleanETag(resp.headers.get(\"etag\")) || undefined;\n return { data: buf, contentType, etag, serverTime };\n }\n\n /** POST a multipart/form-data body. Used by audio.transcribe and image.edit. */\n async postMultipart<T>(\n path: string,\n form: FormData,\n init?: { signal?: AbortSignal },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"POST\",\n headers: await this.authHeaders(), // do NOT set Content-Type — fetch sets it with boundary\n body: form,\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return (await resp.json()) as T;\n }\n\n /**\n * POST JSON body; return the raw streaming Response. Used by chat\n * streaming. Caller is responsible for consuming the SSE body.\n */\n async postJSONStream(\n path: string,\n body: unknown,\n init?: { signal?: AbortSignal },\n ): Promise<Response> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"POST\",\n headers: {\n ...(await this.jsonHeaders()),\n \"Accept-Encoding\": \"identity\", // SSE lines arrive immediately, no gzip\n },\n body: JSON.stringify(body),\n signal: init?.signal,\n }));\n if (!resp.ok) {\n throw await this.parseError(resp);\n }\n return resp;\n }\n\n /** PUT raw bytes with caller-supplied content type and headers. */\n async putBytes<T>(\n path: string,\n body: BodyInit,\n opts?: {\n contentType?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n parse?: \"json\" | \"none\";\n },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => {\n const headers: Record<string, string> = {\n ...(await this.authHeaders()),\n \"Content-Type\": opts?.contentType ?? \"application/octet-stream\",\n };\n if (opts?.headers) Object.assign(headers, opts.headers);\n return {\n method: \"PUT\",\n headers,\n body,\n signal: opts?.signal,\n };\n });\n if (!resp.ok) throw await this.parseError(resp);\n if (opts?.parse === \"none\") return undefined as unknown as T;\n return (await resp.json()) as T;\n }\n\n /** DELETE path; parse JSON response (or no body). */\n async deletePath<T>(\n path: string,\n init?: { signal?: AbortSignal; parse?: \"json\" | \"none\" },\n ): Promise<T> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"DELETE\",\n headers: await this.authHeaders(),\n signal: init?.signal,\n }));\n if (!resp.ok) throw await this.parseError(resp);\n if (init?.parse === \"none\") return undefined as unknown as T;\n return (await resp.json()) as T;\n }\n\n /** HEAD path; surface status + selected response headers. Used by\n * exists / stat where the body would just be an opaque blob. */\n async head(\n path: string,\n init?: { signal?: AbortSignal },\n ): Promise<{ status: number; headers: Headers }> {\n const resp = await this.fetchWithAuthRetry(path, async () => ({\n method: \"HEAD\",\n headers: await this.authHeaders(),\n signal: init?.signal,\n }));\n return { status: resp.status, headers: resp.headers };\n }\n\n private async jsonHeaders(): Promise<Record<string, string>> {\n return {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${await this.resolveToken()}`,\n };\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n return { Authorization: `Bearer ${await this.resolveToken()}` };\n }\n\n private async parseError(resp: Response): Promise<APIError> {\n const text = await resp.text();\n let type = \"error\";\n let message = text || resp.statusText;\n try {\n const parsed = JSON.parse(text) as { type?: string; message?: string };\n if (parsed.type) type = parsed.type;\n if (parsed.message) message = parsed.message;\n } catch {\n // Body wasn't JSON — fall back to raw text.\n }\n return new APIError(resp.status, type, message);\n }\n}\n\n/** The server's clock from a Date header, as ms since epoch. */\n/**\n * The bare ETag value. A CDN that compresses the response marks the\n * ETag weak (`W/\"…\"`); the value is still the object's, and it is what\n * `If-Match` must carry on the next write.\n */\nexport function cleanETag(raw: string | null | undefined): string {\n return (raw ?? \"\").trim().replace(/^W\\//i, \"\").replace(/\"/g, \"\");\n}\n\nexport function parseServerDate(h: string | null): number | undefined {\n if (!h) return undefined;\n const t = Date.parse(h);\n return Number.isNaN(t) ? undefined : t;\n}\n","/**\n * Typed runtime event bus for SDK call telemetry.\n *\n * Every LLM-ish service method (chat, embeddings, image, audio, video,\n * computer) emits start / streamDelta / end / error events on the\n * shared `client.events` bus so UI overlays (e.g. the bottom-right\n * identity badge with its activity ring) can render real-time state\n * without business code having to thread anything through.\n *\n * Goals:\n * - **Typed**: handler signatures are inferred from the event name.\n * - **Zero-cost when nobody listens**: no listeners → emit is a few\n * property reads. The browser `EventTarget` does the heavy lifting.\n * - **Service-agnostic**: events carry just enough context (id /\n * model / capability / token counts) for a generic UI to render\n * them without knowing which service fired the call.\n *\n * Why a custom wrapper instead of `EventTarget` directly:\n * - The native API is untyped (`addEventListener(\"foo\", anyHandler)`).\n * - Browser CustomEvent's `detail` payload trick is awkward to read.\n * - Subscribers want an `off()` closure, not a separate\n * `removeEventListener` call with the same callback reference.\n */\n\n/** Capability bucket of the calling service, mirroring /v1/models. */\nexport type SDKCapability =\n | \"text\"\n | \"vision\"\n | \"embedding\"\n | \"image_generation\"\n | \"image_edit\"\n | \"text_to_speech\"\n | \"speech_to_text\"\n | \"video_generation\"\n | \"computer_use\"\n /** A realtime voice session (`client.realtime`). */\n | \"realtime\"\n /** A file-space call (`client.files`). Shown on the badge while in\n * flight and on failure, but not listed or charged per call. */\n | \"files\";\n\n/** Fired the moment a request hits the wire. */\nexport interface RequestStartEvent {\n /** Stable id correlating start → end/error events for one call. */\n id: string;\n /** Model name the caller asked for (NOT the upstream id). */\n model: string;\n /** Capability bucket — drives badge icon / colour. */\n capability: SDKCapability;\n /** ms since epoch when the request left the SDK. */\n startedAt: number;\n /** `true` for stream:true requests so the UI can pre-pick the\n * pulsing-ring animation instead of swapping mid-call. */\n streaming: boolean;\n}\n\n/** Fired on every stream chunk that carried new content tokens. */\nexport interface RequestStreamDeltaEvent {\n /** Matches the corresponding RequestStartEvent.id. */\n id: string;\n /** Approximate completion tokens in THIS chunk (one delta event\n * per visible SSE chunk; usage roll-up arrives in end). */\n deltaTokens: number;\n /** Cumulative completion tokens since stream start. */\n totalTokens: number;\n}\n\n/** Fired once when the call settles successfully. */\nexport interface RequestEndEvent {\n id: string;\n model: string;\n capability: SDKCapability;\n /** Wall-clock from start to settle. */\n latencyMs: number;\n /** Final completion tokens (matches the gateway's usage.completion_tokens\n * when the response carries it; 0 otherwise). */\n totalTokens: number;\n /** USD cost as reported by the gateway's `usage.total_cost`. May be\n * undefined for endpoints that don't return cost (rare). */\n costUSD?: number;\n /** OpenAI-style stop / length / tool_calls / content_filter, or\n * undefined for non-chat endpoints. */\n finishReason?: string;\n}\n\n/** Fired once when the call fails — at start, mid-stream, or anywhere. */\nexport interface RequestErrorEvent {\n id: string;\n model: string;\n capability: SDKCapability;\n latencyMs: number;\n /** The thrown Error (typically `APIError` with statusCode / body). */\n error: Error;\n /** HTTP status when known (forwarded from APIError when present). */\n statusCode?: number;\n}\n\n/** Compile-time map of event name → payload shape. Drives handler\n * inference in `on()` / `emit()` so callers can't subscribe to a\n * typo'd name or read a non-existent field. */\n/** Fired whenever Sync's state, pending count or last\n * sync time changes. See `client.sync`. */\nexport interface SyncStateEvent {\n state: \"off\" | \"idle\" | \"syncing\" | \"offline\" | \"error\";\n pending: number;\n lastSyncedAt?: number;\n /** When a sync last pushed or applied data (not set by a quiet pull). */\n lastChangeAt?: number;\n error?: string;\n}\n\n/** Fired after the sync wrote remote changes into a local object store,\n * in this tab and (through BroadcastChannel) in the app's other tabs.\n * Re-read the store to show the new data. */\nexport interface DbChangeEvent {\n db: string;\n store: string;\n keys: IDBValidKey[];\n cleared: boolean;\n /** \"remote\": another device's change, just applied locally.\n * \"tab\": another tab of this browser wrote to the shared database. */\n source: \"remote\" | \"tab\";\n}\n\nexport interface SDKEventMap {\n \"request:start\": RequestStartEvent;\n \"request:streamDelta\": RequestStreamDeltaEvent;\n \"request:end\": RequestEndEvent;\n \"request:error\": RequestErrorEvent;\n \"sync:state\": SyncStateEvent;\n \"db:change\": DbChangeEvent;\n}\n\n/** Disposer returned by `on()` — call to unsubscribe. */\nexport type Unsubscribe = () => void;\n\n/**\n * Lightweight typed event bus. Backed by `EventTarget` so memory\n * leaks from un-disposed listeners surface in the standard DevTools\n * memory profile path. In Node we fall back to a plain Map<string,\n * Set<handler>> since `EventTarget` was only added in Node 15+ and we\n * still support 14 in places (the SDK targets 18+ but tests sometimes\n * stub globals).\n */\nexport class SDKEvents {\n private readonly handlers = new Map<keyof SDKEventMap, Set<(payload: unknown) => void>>();\n\n /**\n * Subscribe to an event. Returns a disposer; calling the disposer\n * removes the subscription. Multiple subscriptions to the same\n * event are supported.\n */\n on<K extends keyof SDKEventMap>(name: K, handler: (event: SDKEventMap[K]) => void): Unsubscribe {\n let set = this.handlers.get(name);\n if (!set) {\n set = new Set();\n this.handlers.set(name, set);\n }\n const wrapped = handler as (payload: unknown) => void;\n set.add(wrapped);\n return () => {\n this.handlers.get(name)?.delete(wrapped);\n };\n }\n\n /**\n * Fire an event. Handlers run synchronously in subscription order.\n * Throws inside one handler don't prevent the others from running —\n * the bus catches and logs (console.error) so a buggy listener can't\n * take down the rest of the UI.\n */\n emit<K extends keyof SDKEventMap>(name: K, payload: SDKEventMap[K]): void {\n const set = this.handlers.get(name);\n if (!set || set.size === 0) return;\n for (const h of set) {\n try {\n h(payload);\n } catch (err) {\n // Listener bug — don't propagate, but surface in the console\n // so developers notice during integration.\n // eslint-disable-next-line no-console\n console.error(\"[runapps sdk] event listener for\", name, \"threw:\", err);\n }\n }\n }\n\n /** Drop all subscriptions. Tests call this between cases. */\n clear(): void {\n this.handlers.clear();\n }\n}\n\n/** Generate a short opaque ID for correlating start/end events.\n * Not a UUID — sub-microsecond uniqueness within a session is\n * enough, and we want to keep the wire log compact. */\nexport function newRequestId(): string {\n return (\n \"req_\" +\n Date.now().toString(36) +\n \"_\" +\n Math.random().toString(36).slice(2, 8)\n );\n}\n","/**\n * Tiny wrapper used by every LLM-ish service to fire\n * `request:start` → `request:end` / `request:error` events around a\n * single network call. Centralised here so the service files don't\n * duplicate the try/catch + latency-stopwatch pattern N times.\n *\n * Streaming chat has its own logic (it must yield deltas mid-flight)\n * — see `wrapStream` below.\n */\n\nimport { newRequestId, type SDKCapability, type SDKEvents } from \"./events.js\";\n\ninterface CallMeta {\n model: string;\n capability: SDKCapability;\n}\n\ninterface EndInfo {\n totalTokens?: number;\n costUSD?: number;\n finishReason?: string;\n}\n\n/**\n * Run `fn` while emitting start/end/error events around it.\n * `extractEnd` plucks token / cost / finish_reason off the response\n * shape so each service can map its own response type without the\n * helper knowing about it.\n *\n * Re-throws on error so the caller still sees the rejection — the\n * helper only inserts telemetry, it doesn't swallow.\n */\nexport async function wrapEvents<T>(\n events: SDKEvents,\n meta: CallMeta,\n fn: () => Promise<T>,\n extractEnd?: (r: T) => EndInfo,\n): Promise<T> {\n const id = newRequestId();\n const startedAt = Date.now();\n events.emit(\"request:start\", {\n id,\n model: meta.model,\n capability: meta.capability,\n startedAt,\n streaming: false,\n });\n try {\n const result = await fn();\n const info = extractEnd ? extractEnd(result) : {};\n events.emit(\"request:end\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n totalTokens: info.totalTokens ?? 0,\n ...(info.costUSD !== undefined && { costUSD: info.costUSD }),\n ...(info.finishReason !== undefined && { finishReason: info.finishReason }),\n });\n return result;\n } catch (err) {\n const e = err as Error & { statusCode?: number };\n events.emit(\"request:error\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n error: e,\n ...(typeof e.statusCode === \"number\" && { statusCode: e.statusCode }),\n });\n throw err;\n }\n}\n\n/**\n * Token-count estimator for stream deltas. Stream chunks carry text,\n * not tokens — getting an exact count would mean shipping a\n * tokenizer in the SDK (huge), so we use the standard ~4-chars =\n * 1-token English heuristic. Off by 10-30% for code or non-English,\n * but the badge UI shows tokens/sec as a vibe indicator, not for\n * billing, so the imprecision is fine.\n */\nexport function estimateTokens(text: string): number {\n if (!text) return 0;\n return Math.max(1, Math.round(text.length / 4));\n}\n\n/**\n * Stream wrapper — same start/end/error envelope as wrapEvents but\n * also fires `request:streamDelta` for each yielded chunk so the\n * badge's tokens/sec sliding window has samples.\n *\n * Generic over the chunk type because each service's stream\n * (chat / video / etc.) carries its own shape. The caller passes a\n * `extractDelta` that pulls text + cumulative usage off the chunk.\n *\n * Usage:\n * yield* wrapStream(events, {model, capability}, () => transport.postJSONStream(...),\n * (chunk) => ({ deltaText: chunk.choices[0]?.delta?.content, usage: chunk.usage }));\n */\nexport async function* wrapStream<TChunk>(\n events: SDKEvents,\n meta: CallMeta,\n source: () => AsyncIterable<TChunk>,\n extractDelta: (chunk: TChunk) => {\n deltaText?: string;\n // When a chunk carries cumulative usage (final chunk in OpenAI\n // streams), surface it so the end event can use exact totals\n // instead of the heuristic running sum.\n completionTokens?: number;\n costUSD?: number;\n finishReason?: string;\n },\n): AsyncGenerator<TChunk, void, unknown> {\n const id = newRequestId();\n const startedAt = Date.now();\n events.emit(\"request:start\", {\n id,\n model: meta.model,\n capability: meta.capability,\n startedAt,\n streaming: true,\n });\n let totalTokens = 0;\n let lastUsageTokens: number | undefined;\n let costUSD: number | undefined;\n let finishReason: string | undefined;\n try {\n for await (const chunk of source()) {\n const info = extractDelta(chunk);\n if (info.deltaText) {\n const delta = estimateTokens(info.deltaText);\n totalTokens += delta;\n events.emit(\"request:streamDelta\", {\n id,\n deltaTokens: delta,\n totalTokens,\n });\n }\n if (info.completionTokens !== undefined) lastUsageTokens = info.completionTokens;\n if (info.costUSD !== undefined) costUSD = info.costUSD;\n if (info.finishReason !== undefined) finishReason = info.finishReason;\n yield chunk;\n }\n events.emit(\"request:end\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n // Prefer the exact upstream count over our heuristic when it\n // arrived in the final chunk.\n totalTokens: lastUsageTokens ?? totalTokens,\n ...(costUSD !== undefined && { costUSD }),\n ...(finishReason !== undefined && { finishReason }),\n });\n } catch (err) {\n const e = err as Error & { statusCode?: number };\n events.emit(\"request:error\", {\n id,\n model: meta.model,\n capability: meta.capability,\n latencyMs: Date.now() - startedAt,\n error: e,\n ...(typeof e.statusCode === \"number\" && { statusCode: e.statusCode }),\n });\n throw err;\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents, wrapStream } from \"./event-wrap.js\";\nimport { APIError } from \"./errors.js\";\n\n/* ------------------------------------------------------------------ */\n/* Request types */\n/* ------------------------------------------------------------------ */\n\n/**\n * A single message in a chat conversation. Content is a string for plain\n * text, or an array of `ContentPart` for multi-modal messages.\n */\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | ContentPart[] | null;\n name?: string;\n tool_call_id?: string;\n tool_calls?: ChatToolCall[];\n}\n\n/** One block inside a multi-modal message content array.\n *\n * Multi-modal LLMs see content as a heterogeneous list — some mix\n * text + images (gpt-4o, claude), some accept video frames or raw\n * audio (Gemini 2.x). The shape mirrors OpenAI's chat completions\n * schema and is forwarded verbatim to the gateway; unsupported\n * variants are rejected server-side with a clear error rather than\n * silently ignored. */\nexport type ContentPart =\n | { type: \"text\"; text: string }\n | { type: \"image_url\"; image_url: { url: string; detail?: \"auto\" | \"low\" | \"high\" } }\n | { type: \"video_url\"; video_url: { url: string } }\n | { type: \"audio_url\"; audio_url: { url: string } };\n\nexport interface ChatTool {\n type: \"function\";\n function: {\n name: string;\n description?: string;\n parameters?: Record<string, unknown>;\n };\n}\n\nexport interface StreamOptions {\n include_usage: boolean;\n}\n\n/**\n * Names of platform-executed tools the SDK can ask the gateway to run on\n * its behalf during a chat completion. When you set `server_tools` on a\n * `ChatCompletionParams`, the gateway resolves each name to its tool\n * schema, sends both server tools and your own `tools` (if any) to the\n * model, then handles any server-tool calls itself — looping with the\n * model until it produces a final answer. Your code only sees the final\n * response; you don't have to implement web search yourself.\n *\n * The whitelist is intentionally narrow. For image, audio, file, and\n * vision generation the SDK already exposes dedicated endpoints\n * (`client.image.*`, `client.audio.*`, etc.) that callers should hit\n * directly instead of paying for an LLM-in-the-middle. Search-class\n * tools live here because they need the model in the loop — \"search →\n * read result → fetch → answer\" can't be expressed as a single call.\n */\nexport type ServerToolName = \"web_search\" | \"web_fetch\" | \"twitter_search\";\n\n/**\n * One of the app's private prompts, kept out of the browser. The\n * gateway reads `<id>.md` from the app's bundle (`bundle.json` or\n * `.private/prompts/`), fills `{{var}}` placeholders from `vars`, and\n * uses the result: as the system message for chat, or as the whole\n * text for image, video and speech calls, where it takes the place of\n * `prompt` / `input`. `template` sends the text inline instead — for a\n * creator's local dev server, before a bundle exists; the gateway\n * accepts it only from the app's creator.\n */\nexport interface PromptRef {\n id?: string;\n vars?: Record<string, unknown>;\n template?: string;\n}\n\n/**\n * What a prompt field takes: one reference, or a list of references and\n * plain texts joined in order by a blank line — a private style guide\n * followed by the user's own words, or a persona and a rules prompt kept\n * as two files. At most 16 parts.\n */\nexport type PromptInput = PromptRef | Array<PromptRef | string>;\n\n/** Typed constants for {@link ServerToolName} so you get autocomplete. */\nexport const ServerTools = {\n WebSearch: \"web_search\",\n WebFetch: \"web_fetch\",\n TwitterSearch: \"twitter_search\",\n} as const;\n\nexport interface ChatCompletionParams {\n model: string;\n messages: ChatMessage[];\n temperature?: number;\n top_p?: number;\n max_tokens?: number;\n stop?: string[];\n frequency_penalty?: number;\n presence_penalty?: number;\n n?: number;\n tools?: ChatTool[];\n tool_choice?: unknown;\n user?: string;\n metadata?: Record<string, unknown>;\n stream?: boolean;\n stream_options?: StreamOptions;\n /**\n * Names of platform-executed tools — the gateway will run these for\n * you and loop with the model until it produces a final answer. Mix\n * freely with your own `tools`: the model can call either; the\n * platform handles the server ones, your code handles the rest.\n */\n server_tools?: ServerToolName[];\n /**\n * Maximum number of LLM round-trips the server-tool loop will make\n * (default 5, hard cap 10). Only meaningful when `server_tools` is\n * set. Tune up for chains like \"search → fetch → answer\", down for\n * tighter cost guarantees.\n */\n max_server_iterations?: number;\n /**\n * One of the app's private prompts, kept out of the browser: the\n * gateway reads `bundle.json (or .private/prompts/)<id>.md` from the app's bundle, fills\n * `{{var}}` placeholders from `vars`, and prepends it as the system\n * message. `template` sends the text inline instead — for a creator's\n * local dev server, before a bundle exists; the gateway accepts it\n * only from the app's creator.\n */\n prompt?: PromptInput;\n /** Pass-through for provider-specific knobs. */\n [extra: string]: unknown;\n}\n\n/* ------------------------------------------------------------------ */\n/* Response types */\n/* ------------------------------------------------------------------ */\n\nexport interface ChatToolCall {\n index?: number;\n id?: string;\n type?: string;\n function: {\n name: string;\n arguments: string;\n };\n}\n\nexport interface ChatChoiceMessage {\n role: string;\n content: string | null;\n tool_calls?: ChatToolCall[];\n}\n\nexport interface ChatChoice {\n index: number;\n message: ChatChoiceMessage;\n finish_reason: string;\n}\n\nexport interface ChatCompletion {\n id: string;\n object: string;\n created: number;\n model: string;\n choices: ChatChoice[];\n usage: Usage;\n}\n\nexport interface ChatChunkDelta {\n role?: string;\n content?: string;\n tool_calls?: ChatToolCall[];\n}\n\nexport interface ChatChunkChoice {\n index: number;\n delta: ChatChunkDelta;\n finish_reason?: string | null;\n}\n\nexport interface ChatCompletionChunk {\n id: string;\n object: string;\n created: number;\n model: string;\n choices: ChatChunkChoice[];\n usage?: Usage;\n}\n\n/* ------------------------------------------------------------------ */\n/* Convenience builders */\n/* ------------------------------------------------------------------ */\n\nexport const userMessage = (content: string): ChatMessage =>\n ({ role: \"user\", content });\n\nexport const systemMessage = (content: string): ChatMessage =>\n ({ role: \"system\", content });\n\nexport const assistantMessage = (content: string): ChatMessage =>\n ({ role: \"assistant\", content });\n\nexport const toolResultMessage = (toolCallId: string, content: string): ChatMessage =>\n ({ role: \"tool\", tool_call_id: toolCallId, content });\n\nexport const userMessageParts = (...parts: ContentPart[]): ChatMessage =>\n ({ role: \"user\", content: parts });\n\nexport const textPart = (text: string): ContentPart => ({ type: \"text\", text });\n\nexport const imagePart = (\n url: string,\n detail?: \"auto\" | \"low\" | \"high\",\n): ContentPart => ({\n type: \"image_url\",\n image_url: detail ? { url, detail } : { url },\n});\n\n/**\n * Video content part. Only supported by chat models whose\n * input_modalities includes `\"video\"` (currently Gemini 3.x). Other\n * models 400 at the gateway. URL may be http(s) or a\n * `data:video/mp4;base64,...` inline upload.\n */\nexport const videoPart = (url: string): ContentPart => ({\n type: \"video_url\",\n video_url: { url },\n});\n\n/**\n * Audio content part. Only supported by chat models whose\n * input_modalities includes `\"audio\"` (currently Gemini 3.x). Other\n * models 400 at the gateway. URL may be http(s) or a\n * `data:audio/wav;base64,...` inline upload.\n */\nexport const audioPart = (url: string): ContentPart => ({\n type: \"audio_url\",\n audio_url: { url },\n});\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n\nexport class ChatService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Create a chat completion (non-streaming).\n *\n * Streaming is forced off — pass `stream()` instead for incremental output.\n */\n async create(\n params: ChatCompletionParams,\n init?: { signal?: AbortSignal },\n ): Promise<ChatCompletion> {\n const body = { ...params, stream: false };\n return wrapEvents(\n this.events,\n { model: params.model, capability: \"text\" },\n () =>\n this.transport.postJSON<ChatCompletion>(\n \"/v1/chat/completions\",\n body,\n init,\n ),\n (r) => ({\n totalTokens: r.usage?.completion_tokens,\n costUSD: r.usage?.total_cost,\n finishReason: r.choices?.[0]?.finish_reason,\n }),\n );\n }\n\n /**\n * Create a streaming chat completion. Returns an async iterator over\n * `ChatCompletionChunk`. The final chunk carries `usage` (token / cost\n * totals) — `stream_options.include_usage` is set automatically.\n *\n * ```ts\n * let cost = 0;\n * for await (const chunk of client.chat.stream({ ... })) {\n * for (const c of chunk.choices) process.stdout.write(c.delta.content ?? \"\");\n * if (chunk.usage) cost = chunk.usage.total_cost;\n * }\n * ```\n *\n * Also fires `request:streamDelta` on `client.events` for each chunk\n * that carried text — drives the badge's tokens/sec rate display.\n */\n async *stream(\n params: ChatCompletionParams,\n init?: { signal?: AbortSignal },\n ): AsyncGenerator<ChatCompletionChunk, void, unknown> {\n const body = {\n ...params,\n stream: true,\n stream_options: params.stream_options ?? { include_usage: true },\n };\n const sourceGen = async function* (this: ChatService) {\n const resp = await this.transport.postJSONStream(\n \"/v1/chat/completions\",\n body,\n init,\n );\n if (!resp.body) {\n throw new Error(\"runapps: streaming response had no body\");\n }\n yield* parseSSE<ChatCompletionChunk>(resp.body);\n }.bind(this);\n yield* wrapStream(\n this.events,\n { model: params.model, capability: \"text\" },\n sourceGen,\n (chunk) => ({\n deltaText: chunk.choices?.[0]?.delta?.content ?? \"\",\n completionTokens: chunk.usage?.completion_tokens,\n costUSD: chunk.usage?.total_cost,\n finishReason: chunk.choices?.[0]?.finish_reason ?? undefined,\n }),\n );\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* SSE parsing */\n/* ------------------------------------------------------------------ */\n\n/**\n * Decode an `text/event-stream` body into a stream of parsed JSON chunks.\n * Stops at the `[DONE]` sentinel. Lines that don't start with `data: ` are\n * ignored (keep-alive comments, etc.).\n */\nasync function* parseSSE<T>(\n body: ReadableStream<Uint8Array>,\n): AsyncGenerator<T, void, unknown> {\n const decoder = new TextDecoder();\n const reader = body.getReader();\n let buffer = \"\";\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, nl).replace(/\\r$/, \"\");\n buffer = buffer.slice(nl + 1);\n if (!line.startsWith(\"data: \")) continue;\n const data = line.slice(6).trim();\n if (data === \"[DONE]\") return;\n if (!data) continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n } catch (e) {\n throw new Error(\n `runapps: decode stream chunk: ${(e as Error).message}`,\n );\n }\n // The gateway reports mid-stream failures as an SSE error event\n // (`data: {\"error\":{...}}`) followed by [DONE]. Such an event has no\n // \"choices\", so without this it would surface as an empty chunk and\n // the stream would end looking merely empty.\n const err = (parsed as { error?: { code?: number; type?: string; message?: string } })\n .error;\n if (err) {\n throw new APIError(\n err.code ?? 0,\n err.type ?? \"upstream_error\",\n err.message ?? data,\n );\n }\n yield parsed as T;\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n","import type { Transport } from \"./transport.js\";\n\n/**\n * One model available on the gateway. Pricing is in **pips** (1 USD =\n * 1,000,000 pips), per million tokens — multiply by tokens / 10⁶ for the\n * billed pip cost, or by tokens / 10¹² for USD.\n */\nexport interface Model {\n id: string;\n object: string;\n capability: string;\n provider?: string;\n /**\n * Server-derived \"what this model can actually do\" tags\n * (e.g. {id:\"t2v\", label:\"Text-to-Video\"}, {id:\"i2v\", label:\"Image-to-Video\"}).\n * Use `hasCapabilityTag` for stable filter checks; iterate for display.\n * Labels are in English — frontends localise on their side.\n */\n capability_tags?: Tag[];\n /**\n * Accepted input modalities for chat models. Omitted on capabilities\n * where it doesn't apply (image generation, TTS, etc.). Mirrors\n * Anthropic / Gemini's own `inputModalities` field — operator-set on\n * the gateway, surfaced verbatim here.\n *\n * Canonical values: `\"text\"`, `\"image\"`, `\"video\"`, `\"audio\"`. The\n * list is open — future modalities ride through without an SDK\n * update. Use `acceptsModality` for the stable filter check.\n *\n * @example\n * const videoCapable = models.filter(m => acceptsModality(m, \"video\"));\n */\n input_modalities?: string[];\n options?: Record<string, unknown>;\n input_price_per_mtok: number;\n output_price_per_mtok: number;\n fixed_price: boolean;\n fixed_cost: number;\n max_tokens: number;\n max_input_tokens: number;\n icon_url?: string;\n available_voices?: string[];\n}\n\n/**\n * One capability tag. Mirrors options_schema.Tag on the gateway. IDs\n * are stable across releases; Labels are subject to translation.\n */\nexport interface Tag {\n id: string;\n label: string;\n}\n\n/** True iff `model.capability_tags` includes a tag with the given stable ID. */\nexport const hasCapabilityTag = (model: Model, id: string): boolean =>\n Array.isArray(model.capability_tags) &&\n model.capability_tags.some((t) => t.id === id);\n\n/**\n * True iff the chat model accepts the given input modality. Lets you\n * filter by `\"image\"` / `\"video\"` / `\"audio\"` (or `\"text\"`, always\n * true for chat models that set the field at all) without remembering\n * whether the gateway uses canonical or aliased names.\n *\n * Returns `false` when `input_modalities` is unset — text-only is the\n * conservative default.\n *\n * @example\n * const videoModels = (await client.models.list())\n * .filter(m => acceptsModality(m, \"video\"));\n */\nexport const acceptsModality = (model: Model, modality: string): boolean =>\n Array.isArray(model.input_modalities) &&\n model.input_modalities.includes(modality);\n\nexport interface ModelListOptions {\n /** Filter to one capability (e.g. \"text\", \"vision\", \"image\", \"tts\"). */\n capability?: string;\n}\n\n/** True iff the gateway flag in `options[key]` is set. Accepts bool or numeric 1. */\nfunction optBool(model: Model, key: string): boolean {\n const v = model.options?.[key];\n if (typeof v === \"boolean\") return v;\n if (typeof v === \"number\") return v !== 0;\n return false;\n}\n\n/** Convenience: TTS model accepts a reference audio clip as `voice`? */\nexport const supportsVoiceClone = (model: Model): boolean =>\n optBool(model, \"supports_voice_clone\");\n\n/** Convenience: TTS model accepts a free-form `instruct_text` directive? */\nexport const supportsInstructText = (model: Model): boolean =>\n optBool(model, \"supports_instruct_text\");\n\n/** Convenience: admin-set default voice for a TTS model, or null. */\nexport const defaultVoice = (model: Model): string | null => {\n const v = model.options?.[\"default_voice\"];\n return typeof v === \"string\" ? v : null;\n};\n\nexport class ModelsService {\n constructor(private readonly transport: Transport) {}\n\n /**\n * List models available on the gateway. Pass `{ capability: \"...\" }` to\n * filter server-side. Needs no sign-in or key.\n */\n async list(\n opts: ModelListOptions = {},\n init?: { signal?: AbortSignal },\n ): Promise<Model[]> {\n let path = \"/v1/models\";\n if (opts.capability) {\n path += `?capability=${encodeURIComponent(opts.capability)}`;\n }\n // The catalogue is public: no token, so listing models never sends a\n // signed-out user to sign in.\n const resp = await this.transport.getPublicJSON<{ data: Model[] }>(path, init);\n return resp.data;\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { PromptInput } from \"./chat.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/**\n * Image generation request.\n *\n * Sizing: prefer the 2-axis `resolution` + `aspect_ratio` controls —\n * the gateway translates them into a concrete WxH from the target\n * model's declared size enum. `size` is the legacy explicit override;\n * when set, it wins over `(resolution, aspect_ratio)`. When none of\n * the three is set, the gateway / upstream applies its own default.\n */\nexport interface ImageGenerateParams {\n /** The prompt text, a private prompt `{ id, vars }`, or a list of\n * those and texts joined in order (see `PromptInput`). */\n prompt: string | PromptInput;\n /** Legacy explicit \"WxH\" override. Prefer `resolution` +\n * `aspect_ratio` for new code. */\n size?: string;\n /** Short-side pixel tier — \"512P\" | \"1K\" | \"2K\" | \"4K\".\n * Model-specific subset; `/v1/models` advertises which tiers each\n * image model supports. */\n resolution?: string;\n /** Output shape — \"1:1\" | \"9:16\" | \"16:9\" | \"3:4\" | \"4:3\" | \"3:2\"\n * | \"2:3\" | \"5:4\" | \"4:5\" | \"21:9\" | \"adaptive\". Model-specific\n * subset; see `/v1/models`. */\n aspect_ratio?: string;\n n?: number;\n style?: string;\n reference_image_urls?: string[];\n user?: string;\n /**\n * Pass-through for provider-specific knobs.\n *\n * Note: `quality` (OpenAI's render-quality preset\n * — \"low\"/\"medium\"/\"high\"/\"auto\") was removed from the canonical\n * surface. Only one upstream used it and its own default (\"auto\") is\n * already optimal; the field caused naming confusion with the\n * \"画质\" resolution-tier control. The gateway silently drops it\n * from inbound requests, so legacy callers keep working.\n */\n [extra: string]: unknown;\n}\n\n/**\n * Single generated image. `url` carries the bytes via one of two\n * transport modes — `<img src={url}>` works for both:\n * - \"https://api.runapps.ai/v1/blobs/<id>\" — async / hosted blob\n * - \"data:<mime>;base64,<payload>\" — sync / inline\n *\n * `decodeMediaUrl(url)` resolves either shape into `Uint8Array` +\n * mime type when raw bytes are wanted (file save, post-process).\n *\n * `size` is the actual dimensions of this image — Seedream sequential\n * generation may produce results different from the requested size.\n */\nexport interface ImageResult {\n url: string;\n revised_prompt?: string;\n size?: string;\n /**\n * Human-readable credit line that the client SHOULD display\n * alongside the image. Populated by stock-library models (Pexels)\n * whose terms of service require crediting the photographer;\n * AI-generation models leave it empty. Render verbatim — don't\n * try to parse the format.\n *\n * Example: \"Photo by Jane Doe on Pexels (https://www.pexels.com/photo/12345/)\"\n */\n attribution?: string;\n}\n\nexport interface ImageUsage extends Usage {\n generated_images?: number;\n output_tokens?: number;\n total_tokens?: number;\n /** Tool name → invocation count (e.g. Seedream 5.0 lite web_search). */\n tool_usage?: Record<string, number>;\n}\n\nexport interface ImageResponse {\n created: number;\n data: ImageResult[];\n usage: ImageUsage;\n}\n\n/**\n * Status payload returned by the resumable async-image endpoints\n * (`/v1/async/images/generations` and friends). Returned verbatim by\n * `submitGenerate()` (job is still queued / running), and again by\n * `getAsyncStatus()` (status may now be terminal — `data[]` is\n * populated when `status === 'succeeded'`).\n *\n * The fully-managed `generateAsync()` hides this type behind an\n * `ImageResponse`; `submitGenerate` + `getAsyncStatus` expose it for\n * callers who need to PERSIST the job id (e.g. resume polling across\n * a page refresh, drive their own UI progress, etc.).\n */\nexport interface AsyncImageJob {\n id: string;\n status: \"queued\" | \"running\" | \"succeeded\" | \"failed\" | (string & {});\n data?: ImageResult[];\n usage?: ImageUsage;\n error?: string;\n /** Unix epoch seconds — same semantics as VideoStatus.created_at /\n * current_time. Pair to compute server-truth elapsed. */\n created_at?: number;\n current_time?: number;\n}\n\n/** Minimal interface for an image / mask file passed to `edit()`. */\nexport interface ImageFileInput {\n /** File contents — Blob, ArrayBuffer, Uint8Array, or string. */\n data: Blob | ArrayBuffer | Uint8Array | string;\n /** File name including extension (sent in the multipart field). */\n filename: string;\n /** Optional MIME type — inferred from the extension when omitted. */\n contentType?: string;\n}\n\nexport interface ImageEditParams {\n image: ImageFileInput;\n mask?: ImageFileInput;\n /** The prompt text, a private prompt `{ id, vars }`, or a list. */\n prompt: string | PromptInput;\n size?: string;\n n?: number;\n user?: string;\n}\n\n/* ------------------------------------------------------------------ */\n\nexport class ImageService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Generate images from a text prompt via the synchronous endpoint.\n * For long-running requests (large Seedream batches, slow upstreams)\n * use `generateAsync()` to avoid 100-second origin timeouts.\n */\n async generate(\n model: string,\n params: ImageGenerateParams,\n init?: { signal?: AbortSignal },\n ): Promise<ImageResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"image_generation\" },\n () =>\n this.transport.postJSON<ImageResponse>(\n \"/v1/images/generations\",\n { model, ...params },\n init,\n ),\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Async equivalent of `generate()`. Submits a job to\n * `/v1/async/images/generations`, polls `/v1/async/images/generations/:id`\n * until terminal, then resolves to a sync-shape `ImageResponse`. Use\n * this when a request may exceed the ~100s origin timeout (Cloudflare\n * otherwise replaces the real upstream error with its own 502 page).\n *\n * Implementation is `submitGenerate` + `getAsyncStatus` poll loop, so\n * the path layout matches the rest of the async API (`/v1/async/...`\n * everywhere). The caller's `signal` aborts both the submit and the\n * poll wait; `pollIntervalMs` defaults to 2s.\n */\n async generateAsync(\n model: string,\n params: ImageGenerateParams,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n ): Promise<ImageResponse> {\n const interval = init?.pollIntervalMs ?? 2000;\n const signal = init?.signal;\n return wrapEvents(\n this.events,\n { model, capability: \"image_generation\" },\n async () => {\n const job = await this.transport.postJSON<AsyncImageJob>(\n \"/v1/async/images/generations\",\n { model, ...params },\n { signal },\n );\n let current = job;\n while (current.status !== \"succeeded\" && current.status !== \"failed\") {\n await sleep(interval, signal);\n current = await this.getAsyncStatus(current.id, { signal });\n }\n if (current.status === \"failed\") {\n throw new Error(current.error ?? \"image generation failed\");\n }\n return {\n created: current.created_at ?? Math.floor(Date.now() / 1000),\n data: current.data ?? [],\n usage: current.usage ?? { total_cost: 0 },\n } satisfies ImageResponse;\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Submit-only variant of `generate()`. Returns the gateway's job\n * descriptor (`id`, `status`, `usage`) without polling. Use this\n * when you want to PERSIST the job id and resume polling later\n * (e.g. across page reloads, in a separate worker process). Pair\n * with `getAsyncStatus(id)` to drive the polling loop yourself.\n *\n * For a one-shot \"submit and wait\" call site, use `generateAsync()`\n * — that wraps the same underlying endpoint with a built-in poll.\n *\n * Telemetry note: only the SUBMIT step fires start/end on\n * `client.events` — the per-call latency reflects the gateway\n * accepting the job, NOT how long the upstream takes to render.\n * Track render progress via your own poll loop's status responses.\n */\n async submitGenerate(\n model: string,\n params: ImageGenerateParams,\n init?: { signal?: AbortSignal },\n ): Promise<AsyncImageJob> {\n return wrapEvents(\n this.events,\n { model, capability: \"image_generation\" },\n () =>\n this.transport.postJSON<AsyncImageJob>(\n \"/v1/async/images/generations\",\n { model, ...params },\n init,\n ),\n );\n }\n\n /**\n * Single poll of an in-flight async image job. Returns the same\n * shape `submitGenerate()` returned, with `data[]` populated once\n * `status` flips to `succeeded`. Caller decides cadence + bail-out;\n * the gateway's per-poll `current_time` / `created_at` together\n * give a server-truth elapsed reading regardless of client clock skew.\n */\n async getAsyncStatus(\n id: string,\n init?: { signal?: AbortSignal },\n ): Promise<AsyncImageJob> {\n return this.transport.getJSON<AsyncImageJob>(\n `/v1/async/images/generations/${encodeURIComponent(id)}`,\n init,\n );\n }\n\n /**\n * Edit an image (inpaint / outpaint / variations) via multipart upload.\n * Provider-dependent — only available on `gpt-image-1` and similar.\n */\n async edit(\n model: string,\n params: ImageEditParams,\n init?: { signal?: AbortSignal },\n ): Promise<ImageResponse> {\n const form = buildEditForm(model, params);\n return wrapEvents(\n this.events,\n { model, capability: \"image_edit\" },\n () =>\n this.transport.postMultipart<ImageResponse>(\n \"/v1/images/edits\",\n form,\n init,\n ),\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new Error(\"aborted\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error(\"aborted\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nfunction buildEditForm(model: string, params: ImageEditParams): FormData {\n const form = new FormData();\n form.set(\"model\", model);\n form.set(\"prompt\", typeof params.prompt === \"string\" ? params.prompt : JSON.stringify(params.prompt));\n if (params.size) form.set(\"size\", params.size);\n if (params.n !== undefined) form.set(\"n\", String(params.n));\n if (params.user) form.set(\"user\", params.user);\n form.set(\"image\", toBlob(params.image), params.image.filename);\n if (params.mask) form.set(\"mask\", toBlob(params.mask), params.mask.filename);\n return form;\n}\n\n/** Internal: coerce ImageFileInput to a Blob the multipart layer accepts. */\nfunction toBlob(input: ImageFileInput): Blob {\n const ct = input.contentType ?? guessMime(input.filename);\n if (input.data instanceof Blob) {\n return input.data.type === ct ? input.data : new Blob([input.data], { type: ct });\n }\n if (typeof input.data === \"string\") {\n return new Blob([input.data], { type: ct });\n }\n // ArrayBuffer | Uint8Array — wrap in Blob. Cast widens TS's strict\n // Uint8Array<ArrayBuffer> back to BlobPart (it's accepted at runtime).\n return new Blob([input.data as BlobPart], { type: ct });\n}\n\nfunction guessMime(filename: string): string {\n const ext = filename.toLowerCase().split(\".\").pop() ?? \"\";\n switch (ext) {\n case \"png\":\n return \"image/png\";\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \"webp\":\n return \"image/webp\";\n case \"gif\":\n return \"image/gif\";\n default:\n return \"application/octet-stream\";\n }\n}\n","/**\n * Media helpers shared by image / video / chat-multimodal endpoints.\n *\n * The gateway accepts media payloads as `data:<mime>;base64,<payload>`\n * URIs on every `*_url` field — `encodeImageUrl` is the bridge from\n * raw bytes to that wire format. Replaces the deprecated `*_b64` SDK\n * fields, which were removed in favour of routing every payload\n * through `*_url`.\n */\n\n/**\n * Wrap raw image bytes as a `data:<mime>;base64,<payload>` URI.\n *\n * MIME is sniffed from the magic bytes (PNG / JPEG / GIF / WebP). On\n * unrecognised input we default to `application/octet-stream` — most\n * upstreams accept and re-sniff server-side.\n *\n * @param bytes Image bytes — `Uint8Array`, `ArrayBuffer`, or `Blob`.\n * @returns A data: URI suitable for any `*_url` field on the gateway.\n *\n * @example\n * ```ts\n * import { encodeImageUrl } from \"@runappsai/sdk\";\n * const png = await fs.readFile(\"frame.png\");\n * await client.video.generate(\"Veo 3.1\", {\n * prompt: \"...\",\n * first_frame_url: encodeImageUrl(png),\n * });\n * ```\n */\nexport function encodeImageUrl(\n bytes: Uint8Array | ArrayBuffer | Blob,\n): string {\n // Normalize to Uint8Array so the rest of the function is uniform.\n if (bytes instanceof Blob) {\n throw new Error(\n \"encodeImageUrl: Blob input requires async; await blob.arrayBuffer() and pass the result instead\",\n );\n }\n const u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\n const mime = sniffImageMime(u8);\n\n // btoa(String.fromCharCode(...u8)) blows the stack on large arrays;\n // chunk to keep arg count bounded. 0x8000 is the conventional safe\n // limit on JS engines.\n const CHUNK = 0x8000;\n let binary = \"\";\n for (let i = 0; i < u8.length; i += CHUNK) {\n binary += String.fromCharCode.apply(\n null,\n Array.from(u8.subarray(i, i + CHUNK)),\n );\n }\n // btoa is browser-native and shimmed in Node ≥ 16 globalThis.\n const b64 = (globalThis as { btoa?: (s: string) => string }).btoa\n ? (globalThis as { btoa: (s: string) => string }).btoa(binary)\n : Buffer.from(binary, \"binary\").toString(\"base64\");\n\n return `data:${mime};base64,${b64}`;\n}\n\n/**\n * Resolve a media URL to raw bytes plus the declared mime label.\n *\n * Inverse of `encodeImageUrl`. Two transport modes handled:\n * - \"data:<mime>;base64,<payload>\" — decode the inline payload\n * - \"https://...\", \"http://...\" — `fetch()` + `.arrayBuffer()`,\n * mime from the response's\n * `Content-Type` header\n *\n * Returns `{bytes: Uint8Array, contentType: string}`. Pairs with\n * `encodeImageUrl` so callers can round-trip bytes through the gateway\n * without thinking about which transport mode the response chose.\n *\n * @example\n * ```ts\n * import { decodeMediaUrl } from \"@runappsai/sdk\";\n * const job = await client.image.generateAsync(\"Seedream 5.0\", {...});\n * const { bytes, contentType } = await decodeMediaUrl(job.data[0].url);\n * await fs.writeFile(\"out.png\", bytes);\n * ```\n */\nexport async function decodeMediaUrl(\n url: string,\n): Promise<{ bytes: Uint8Array; contentType: string }> {\n if (url.startsWith(\"data:\")) {\n const rest = url.slice(5);\n const semi = rest.indexOf(\";\");\n const comma = rest.indexOf(\",\");\n if (semi === -1 || comma === -1 || comma < semi) {\n throw new Error(\"decodeMediaUrl: malformed data URI\");\n }\n const mime = rest.slice(0, semi);\n const b64 = rest.slice(comma + 1);\n return { bytes: base64ToBytes(b64), contentType: mime };\n }\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {\n const resp = await fetch(url);\n if (!resp.ok) {\n throw new Error(`decodeMediaUrl: upstream ${resp.status}`);\n }\n const buf = await resp.arrayBuffer();\n return {\n bytes: new Uint8Array(buf),\n contentType: resp.headers.get(\"Content-Type\") ?? \"\",\n };\n }\n throw new Error(`decodeMediaUrl: unsupported url scheme: ${url.slice(0, 32)}`);\n}\n\n/** Decode standard base64 to Uint8Array. Browser `atob` + Node `Buffer`\n * fallback — same impl as the audio service uses internally. */\nfunction base64ToBytes(b64: string): Uint8Array {\n if (typeof Buffer !== \"undefined\") {\n return new Uint8Array(Buffer.from(b64, \"base64\"));\n }\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\n/**\n * Sniff a small set of image mimes from magic bytes. Mirrors the\n * gateway's own sniff so encode / decode round-trip lands on the same\n * label.\n */\nfunction sniffImageMime(b: Uint8Array): string {\n if (b.length >= 8 &&\n b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) {\n return \"image/png\";\n }\n if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) {\n return \"image/jpeg\";\n }\n if (b.length >= 6 &&\n b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38) {\n return \"image/gif\";\n }\n if (b.length >= 12 &&\n b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 &&\n b[8] === 0x57 && b[9] === 0x45 && b[10] === 0x42 && b[11] === 0x50) {\n return \"image/webp\";\n }\n return \"application/octet-stream\";\n}\n","import type { Transport } from \"./transport.js\";\nimport type { PromptInput } from \"./chat.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\nimport { decodeMediaUrl } from \"./media.js\";\n\n/* ------------------------------------------------------------------ */\n/* TTS */\n/* ------------------------------------------------------------------ */\n/* */\n/* Voice metadata (id / name / gender / preview_url / supported */\n/* emotions) is no longer fetched through a dedicated endpoint — */\n/* every text_to_speech row on /v1/models carries its full catalog */\n/* under `options.voices` and `options.supported_emotions`. Use */\n/* `client.models.get(name)` (or the entry from `client.models.list`) */\n/* and read those fields off `model.options`. */\n/* ------------------------------------------------------------------ */\n\nexport interface SpeechParams {\n /** The text to speak, a private prompt `{ id, vars }`, or a list of\n * those and texts joined in order (see `PromptInput`). */\n input: string | PromptInput;\n /** Named voice id (`alloy`, `nova`, …; provider-dependent). Omit\n * when supplying `reference_audio_url` for zero-shot voice\n * cloning — the cloned timbre overrides any named voice. */\n voice?: string;\n response_format?: string;\n speed?: number;\n /** Provider-specific. Inspect the chosen TTS model's\n * `options.supported_emotions` (via `client.models.get(name)`) for\n * the legal enum values. */\n emotion?: string;\n /** -12 … 12 semitones (provider-dependent). */\n pitch?: number;\n /** 0.1 … 10.0 — 1.0 = normal. */\n volume?: number;\n /** -12 … 12 — voice timbre shift (provider-dependent). */\n timber?: number;\n /**\n * Free-form natural-language directive for voiceclone (CosyVoice).\n * e.g. \"用四川话快速地说\"\n */\n instruct_text?: string;\n /**\n * Public URL of a reference clip whose timbre to match (zero-shot\n * voice cloning). When set, voiceclone-capable models ignore `voice`\n * and synthesize matching the referenced audio.\n */\n reference_audio_url?: string;\n /** Transcript of the reference clip — improves CosyVoice quality. */\n reference_text?: string;\n user?: string;\n /**\n * Vendor-specific top-level fields the canonical params don't model.\n * Used for music-generation models on the TTS bucket — ACE-Step needs\n * `tags` (genre, required), `duration`, `seed`, `scheduler`, etc.\n * The SDK spreads these at the request body's TOP LEVEL (not nested\n * under \"extra\") to match the gateway's extractSpeechExtra contract.\n * Canonical fields above always win over extra keys with the same name.\n */\n extra?: Record<string, unknown>;\n}\n\nexport interface SpeechResponse {\n /** Decoded audio bytes. */\n data: Uint8Array;\n /** MIME type reported by the gateway (e.g. \"audio/mpeg\"). */\n contentType: string;\n usage: Usage;\n}\n\n/* ------------------------------------------------------------------ */\n/* STT */\n/* ------------------------------------------------------------------ */\n\n/** Same shape as image.ImageFileInput — kept independent so callers can import either. */\nexport interface AudioFileInput {\n data: Blob | ArrayBuffer | Uint8Array | string;\n filename: string;\n contentType?: string;\n}\n\nexport interface TranscribeParams {\n /** The audio to transcribe. Either this or `url`. */\n file?: AudioFileInput;\n /**\n * A public URL of the audio, instead of uploading it — for models that\n * fetch audio themselves (e.g. \"Doubao ASR 2.0\"); no upload size limit.\n */\n url?: string;\n language?: string;\n prompt?: string;\n response_format?: string;\n timestamp_granularities?: (\"segment\" | \"word\")[];\n user?: string;\n}\n\nexport interface TranscribeResponse {\n text: string;\n usage: Usage;\n /** Raw upstream payload — segment timings, etc., when present. */\n raw?: Record<string, unknown>;\n}\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n\nexport class AudioService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n // (Removed: listVoices — fetch voice metadata via client.models.get /\n // client.models.list and read model.options.voices /\n // model.options.supported_emotions.)\n\n /**\n * Generate speech from text. Output bytes are base64-decoded by the SDK\n * — `response.data` is a `Uint8Array` you can write directly to disk:\n *\n * ```ts\n * await fs.writeFile(\"out.mp3\", await client.audio.speech(\"OpenAI/TTS\", {\n * input: \"Hello!\",\n * voice: \"nova\",\n * }).then(r => r.data));\n * ```\n */\n async speech(\n model: string,\n params: SpeechParams,\n init?: { signal?: AbortSignal },\n ): Promise<SpeechResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"text_to_speech\" },\n async () => {\n // Wire shape: {audio_url: \"data:<mime>;base64,...\", usage: ...}.\n const raw = await this.transport.postJSON<{\n audio_url: string;\n usage: Usage;\n }>(\n \"/v1/audio/speech\",\n buildSpeechBody(model, params),\n init,\n );\n const { bytes, contentType } = await decodeMediaUrl(raw.audio_url);\n return { data: bytes, contentType, usage: raw.usage };\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Async equivalent of `speech()`. Submits the job to the gateway's\n * async TTS endpoint, polls every ~3s until terminal, then decodes\n * the result audio_url. Use this when a request may exceed the\n * ~100s origin timeout — ACE-Step music generation at high quality,\n * large CosyVoice batches, etc. The sync `speech()` method is the\n * right choice for short-clip TTS that fits inside Cloudflare's\n * timeout (skips the submit + poll round-trips).\n *\n * Returns the same `SpeechResponse` shape as `speech()` — `data` +\n * `usage` — so callers can swap the two methods with no other change.\n *\n * Caller's `signal` deadline bounds the poll wait. Without one,\n * an internal 10-minute cap applies.\n */\n async speechAsync(\n model: string,\n params: SpeechParams,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n ): Promise<SpeechResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"text_to_speech\" },\n async () => {\n const submit = await this.transport.postJSON<{ id: string; status: string }>(\n \"/v1/async/audio/speech\",\n buildSpeechBody(model, params),\n init,\n );\n if (!submit.id) {\n throw new Error(\"runapps: speech submit response missing job id\");\n }\n return waitSpeechJob(this.transport, submit.id, init);\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /** Transcribe audio to text via multipart upload. */\n async transcribe(\n model: string,\n params: TranscribeParams,\n init?: { signal?: AbortSignal },\n ): Promise<TranscribeResponse> {\n const form = buildTranscribeForm(model, params);\n return wrapEvents(\n this.events,\n { model, capability: \"speech_to_text\" },\n () =>\n this.transport.postMultipart<TranscribeResponse>(\n \"/v1/audio/transcriptions\",\n form,\n init,\n ),\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n\n /**\n * Async equivalent of `transcribe()`. Submits the upload, polls\n * every ~3s until terminal, returns the same `TranscribeResponse`.\n * Use this for long audio (lectures, podcasts, multi-hour\n * recordings) where Whisper can take minutes — well past\n * Cloudflare's ~100s sync ceiling.\n *\n * Caller's `signal` deadline bounds the poll wait. Without one,\n * an internal 10-minute cap applies.\n */\n async transcribeAsync(\n model: string,\n params: TranscribeParams,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n ): Promise<TranscribeResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"speech_to_text\" },\n async () => {\n const form = buildTranscribeForm(model, params);\n const submit = await this.transport.postMultipart<{ id: string; status: string }>(\n \"/v1/async/audio/transcriptions\",\n form,\n init,\n );\n if (!submit.id) {\n throw new Error(\"runapps: transcribe submit response missing job id\");\n }\n return waitTranscribeJob(this.transport, submit.id, init);\n },\n (r) => ({ costUSD: r.usage?.total_cost }),\n );\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\n/**\n * Polls the gateway's async speech-status endpoint until the job\n * reaches a terminal state, then decodes the result `audio_url`\n * data: URI into raw bytes + mime. Used by `AudioService.speechAsync`.\n *\n * Default poll cadence: first poll 2s after submit, then every 3s.\n * Caller can override via `init.pollIntervalMs`. `init.signal`\n * cancels the poll loop.\n */\nasync function waitSpeechJob(\n transport: Transport,\n jobId: string,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n): Promise<SpeechResponse> {\n const path = `/v1/async/audio/speech/${encodeURIComponent(jobId)}`;\n const interval = init?.pollIntervalMs ?? 3000;\n // First poll happens fast — typical TTS already done in 2-5s.\n await sleep(2000, init?.signal);\n // 10-minute hard cap if caller didn't supply a signal with a\n // narrower deadline. Long enough for the slowest ACE-Step jobs.\n const startedAt = Date.now();\n const internalDeadline = startedAt + 10 * 60 * 1000;\n\n for (;;) {\n if (init?.signal?.aborted) {\n throw new DOMException(\"speech poll aborted\", \"AbortError\");\n }\n if (!init?.signal && Date.now() > internalDeadline) {\n throw new Error(\"runapps: speech job timed out (10 min internal cap)\");\n }\n const status = await transport.getJSON<{\n id: string;\n status: string;\n audio_url?: string;\n usage?: Usage;\n error?: string;\n }>(path, init);\n if (status.status === \"succeeded\") {\n if (!status.audio_url) {\n throw new Error(\"runapps: speech job succeeded but no audio_url returned\");\n }\n const { bytes, contentType } = await decodeMediaUrl(status.audio_url);\n return {\n data: bytes,\n contentType,\n usage: status.usage ?? { total_cost: 0 },\n };\n }\n if (status.status === \"failed\") {\n throw new Error(status.error || \"runapps: speech job failed\");\n }\n // queued | running → wait + retry.\n await sleep(interval, init?.signal);\n }\n}\n\n/**\n * Polls the gateway's async transcribe-status endpoint until terminal,\n * then assembles a TranscribeResponse from the final payload. Used by\n * `AudioService.transcribeAsync`.\n */\nasync function waitTranscribeJob(\n transport: Transport,\n jobId: string,\n init?: { signal?: AbortSignal; pollIntervalMs?: number },\n): Promise<TranscribeResponse> {\n const path = `/v1/async/audio/transcriptions/${encodeURIComponent(jobId)}`;\n const interval = init?.pollIntervalMs ?? 3000;\n await sleep(2000, init?.signal);\n const internalDeadline = Date.now() + 10 * 60 * 1000;\n for (;;) {\n if (init?.signal?.aborted) {\n throw new DOMException(\"transcribe poll aborted\", \"AbortError\");\n }\n if (!init?.signal && Date.now() > internalDeadline) {\n throw new Error(\"runapps: transcribe job timed out (10 min internal cap)\");\n }\n const status = await transport.getJSON<Record<string, unknown>>(path, init);\n switch (status.status) {\n case \"succeeded\":\n return assembleTranscribeResponse(status);\n case \"failed\":\n throw new Error((status.error as string) || \"runapps: transcribe job failed\");\n case \"queued\":\n case \"running\":\n await sleep(interval, init?.signal);\n break;\n default:\n throw new Error(`runapps: unknown transcribe job status ${JSON.stringify(status.status)}`);\n }\n }\n}\n\n/**\n * Pulls canonical text + usage out of a succeeded transcribe-status\n * payload, leaves the rest under .raw — matches the sync response\n * shape produced by `transcribe()`.\n */\nfunction assembleTranscribeResponse(payload: Record<string, unknown>): TranscribeResponse {\n const out: TranscribeResponse = {\n text: (payload.text as string) ?? \"\",\n usage: (payload.usage as Usage) ?? { total_cost: 0 },\n };\n const raw: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(payload)) {\n if (k === \"text\" || k === \"usage\" || k === \"id\" || k === \"status\") continue;\n raw[k] = v;\n }\n if (Object.keys(raw).length > 0) {\n out.raw = raw;\n }\n return out;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(new DOMException(\"aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * buildSpeechBody assembles the wire body for /v1/audio/speech (and\n * its async sibling): `{model, ...canonical, ...extra}` flattened at\n * the top level. The gateway's extractSpeechExtra peels non-canonical\n * keys back into req.Extra, so the round-trip is symmetric. Canonical\n * fields always win over `extra` keys of the same name — never let\n * Extra silently override a typed field.\n */\nfunction buildSpeechBody(model: string, params: SpeechParams): Record<string, unknown> {\n const { extra, ...canonical } = params;\n const body: Record<string, unknown> = { model, ...canonical };\n if (extra) {\n for (const [k, v] of Object.entries(extra)) {\n if (k in body) continue;\n body[k] = v;\n }\n }\n return body;\n}\n\nfunction buildTranscribeForm(model: string, params: TranscribeParams): FormData {\n const form = new FormData();\n form.set(\"model\", model);\n if (params.language) form.set(\"language\", params.language);\n if (params.prompt) form.set(\"prompt\", params.prompt);\n if (params.response_format) form.set(\"response_format\", params.response_format);\n if (params.timestamp_granularities) {\n for (const g of params.timestamp_granularities) {\n form.append(\"timestamp_granularities[]\", g);\n }\n }\n if (params.user) form.set(\"user\", params.user);\n\n if (!params.file) {\n if (!params.url) throw new Error(\"runapps: transcribe needs `file` or `url`\");\n form.set(\"url\", params.url);\n return form;\n }\n const ct = params.file.contentType ?? guessAudioMime(params.file.filename);\n const blob =\n params.file.data instanceof Blob\n ? params.file.data\n : new Blob([params.file.data as BlobPart], { type: ct });\n form.set(\"file\", blob, params.file.filename);\n return form;\n}\n\nfunction guessAudioMime(filename: string): string {\n const ext = filename.toLowerCase().split(\".\").pop() ?? \"\";\n switch (ext) {\n case \"mp3\":\n return \"audio/mpeg\";\n case \"wav\":\n return \"audio/wav\";\n case \"m4a\":\n return \"audio/mp4\";\n case \"flac\":\n return \"audio/flac\";\n case \"ogg\":\n return \"audio/ogg\";\n case \"webm\":\n return \"audio/webm\";\n default:\n return \"application/octet-stream\";\n }\n}\n\n// (base64ToBytes was here — promoted to media.ts as part of\n// decodeMediaUrl. The audio path now goes through that single helper\n// so image and audio decode through one implementation.)\n","import { newRequestId, type SDKEvents } from \"./events.js\";\nimport type { Transport } from \"./transport.js\";\n\n/* ------------------------------------------------------------------ */\n/* Realtime voice */\n/* ------------------------------------------------------------------ */\n/*\n * `client.realtime.connect()` opens a full-duplex voice session over a\n * WebSocket to `/v1/realtime`: stream microphone audio up, get the\n * model's transcripts, text and spoken reply back as they happen, and\n * interrupt it mid-sentence.\n *\n * The wire protocol is the realtime model's own. For the Doubao realtime\n * model it is Realtime-style JSON events, one per frame:\n *\n * → session.create / session.update (connect() sends the first one)\n * → input_audio_buffer.append {audio: base64 PCM16, 16 kHz mono}\n * → speech_text_buffer.commit {text} — have the model say this\n * → response.cancel interrupt the reply\n * → session.close (close() sends it)\n * ← session.created\n * ← conversation.item.input_audio_transcription.delta / .completed (what the user said)\n * ← response.output_text.delta / .done (what the model says)\n * ← response.output_audio.delta {delta: base64 PCM16, 24 kHz mono by default}\n * ← response.done one turn finished, with token usage\n * ← error\n * ← session.closed\n * ← gateway.usage the session's cost, just before the socket closes\n *\n * Audio input must arrive at real-time pace (send ~20 ms chunks as they\n * are captured); the model also expects a steady stream, so keep sending\n * silence — or `mute()` — while the microphone is off.\n */\n\n/** One protocol event. `type` names it; the rest depends on the event. */\nexport interface RealtimeEvent {\n type: string;\n [key: string]: unknown;\n}\n\n/** What a realtime session cost, from the closing `gateway.usage` event. */\nexport interface RealtimeUsage {\n input_tokens?: number;\n output_tokens?: number;\n /** Session length in seconds. */\n seconds?: number;\n /** USD. */\n total_cost?: number;\n}\n\n/** How a session ended. */\nexport interface RealtimeCloseInfo {\n /** WebSocket close code: 1000 when the session completed; 4000+N for an\n * upstream HTTP error N (e.g. 4429); 4408 idle or over-long session. */\n code: number;\n reason: string;\n /** Null when the session ended before the cost could be reported. */\n usage: RealtimeUsage | null;\n}\n\n/** The subset of the WHATWG WebSocket the SDK uses (also what `ws` provides). */\nexport interface WebSocketLike {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: string, listener: (ev: any) => void): void;\n}\n\nexport type WebSocketConstructor = new (url: string, protocols?: string | string[]) => WebSocketLike;\n\nexport interface RealtimeConnectOptions {\n /** Realtime model name (e.g. \"Doubao Realtime Voice\"); omit for the platform default. */\n model?: string;\n /**\n * Session settings sent in the opening `session.create`, e.g.\n * `{ instructions: \"...\", audio: { output: { voice: \"zh_female_vv_jupiter_bigtts\" } } }`.\n */\n session?: Record<string, unknown>;\n /** Model-specific extension block (Doubao: `{ asr, tts, dialog }`). */\n extension?: Record<string, unknown>;\n /** Extra query parameters for the handshake. */\n query?: Record<string, string>;\n /**\n * WebSocket implementation. Defaults to the global `WebSocket` (browsers,\n * Node 22+, Deno, Bun); on older Node pass the `ws` package's export.\n */\n WebSocket?: WebSocketConstructor;\n /** Give up if the session isn't open within this long (default 15 s). */\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\ntype Handler = (event: RealtimeEvent) => void;\n\nconst OPEN = 1;\nconst CLOSE_TIMEOUT_MS = 10_000;\n\n/**\n * A live realtime session. Listen with `on(type, handler)` (or iterate\n * it with `for await`), talk with `appendAudio()` / `say()`, and end it\n * with `close()`.\n */\nexport class RealtimeSession {\n /** The upstream session id (from `session.created`); pass it as\n * `session.id` to a later session to continue the conversation. */\n sessionId = \"\";\n /** Settles when the socket closes, however that happens. */\n readonly closed: Promise<RealtimeCloseInfo>;\n\n private readonly handlers = new Map<string, Set<Handler>>();\n private usage: RealtimeUsage | null = null;\n private resolveClosed!: (info: RealtimeCloseInfo) => void;\n private isClosed = false;\n private queue: RealtimeEvent[] = [];\n private waiters: ((r: IteratorResult<RealtimeEvent>) => void)[] = [];\n private iterating = false;\n\n /** @internal */\n constructor(private readonly ws: WebSocketLike) {\n this.closed = new Promise((resolve) => {\n this.resolveClosed = resolve;\n });\n ws.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (typeof ev.data !== \"string\") return;\n let event: RealtimeEvent;\n try {\n event = JSON.parse(ev.data) as RealtimeEvent;\n } catch {\n return;\n }\n if (!event || typeof event.type !== \"string\") return;\n if (event.type === \"session.created\") {\n const id = (event.session as { id?: unknown } | undefined)?.id;\n if (typeof id === \"string\") this.sessionId = id;\n } else if (event.type === \"gateway.usage\") {\n this.usage = (event.usage as RealtimeUsage) ?? null;\n }\n this.dispatch(event);\n });\n ws.addEventListener(\"close\", (ev: { code?: number; reason?: string }) => {\n if (this.isClosed) return;\n this.isClosed = true;\n for (const w of this.waiters.splice(0)) w({ value: undefined, done: true });\n this.resolveClosed({ code: ev.code ?? 1006, reason: ev.reason ?? \"\", usage: this.usage });\n });\n }\n\n /**\n * Subscribe to events of one type, or `\"*\"` for all of them. Returns\n * the unsubscribe function.\n */\n on(type: string, handler: Handler): () => void {\n let set = this.handlers.get(type);\n if (!set) {\n set = new Set();\n this.handlers.set(type, set);\n }\n set.add(handler);\n return () => set!.delete(handler);\n }\n\n /** Send a raw protocol event. */\n send(event: RealtimeEvent): void {\n if (this.ws.readyState !== OPEN) throw new Error(\"runapps: realtime session is closed\");\n this.ws.send(JSON.stringify(event));\n }\n\n /**\n * Stream a chunk of microphone audio: 16-bit little-endian PCM, 16 kHz\n * mono (`pcm16FromFloat32` converts Web Audio samples). Send ~20 ms\n * (640 bytes) at a time, as it is captured.\n */\n appendAudio(pcm16: Int16Array | Uint8Array | ArrayBuffer): void {\n this.send({ type: \"input_audio_buffer.append\", audio: bytesToBase64(asBytes(pcm16)) });\n }\n\n /** Tell the model the user has finished speaking (skips end-of-speech detection). */\n commitAudio(): void {\n this.send({ type: \"input_audio_buffer.commit\" });\n }\n\n /** The microphone is off: stops the model waiting on audio. */\n mute(): void {\n this.send({ type: \"input_audio_mute.commit\" });\n }\n\n /** The microphone is back on. */\n unmute(): void {\n this.send({ type: \"input_audio_unmute.commit\" });\n }\n\n /** Have the model speak `text` (a greeting, or a scripted line). */\n say(text: string): void {\n this.send({ type: \"speech_text_buffer.commit\", text });\n }\n\n /** Interrupt the model's current reply. */\n cancelResponse(): void {\n this.send({ type: \"response.cancel\" });\n }\n\n /** Change session settings mid-session (instructions, voice, tools, …). */\n update(session: Record<string, unknown>): void {\n this.send({ type: \"session.update\", session });\n }\n\n /** Answer a `response.function_call_arguments.done` tool call. */\n sendFunctionResult(callId: string, output: string): void {\n this.send({\n type: \"conversation.item.create\",\n items: [{ call_id: callId, role: \"tool\", content: [{ type: \"input_text\", text: output }] }],\n });\n }\n\n /**\n * End the session: asks the model to close, waits for the server to\n * hang up (so the closing `gateway.usage` arrives), and resolves with\n * how it ended, including the cost.\n */\n async close(): Promise<RealtimeCloseInfo> {\n if (!this.isClosed && this.ws.readyState === OPEN) {\n try {\n this.send({ type: \"session.close\" });\n } catch {\n // already closing\n }\n const timer = setTimeout(() => this.ws.close(1000, \"client timeout\"), CLOSE_TIMEOUT_MS);\n try {\n return await this.closed;\n } finally {\n clearTimeout(timer);\n }\n }\n if (!this.isClosed) this.ws.close(1000);\n return this.closed;\n }\n\n /** Iterate events until the session closes. */\n [Symbol.asyncIterator](): AsyncIterator<RealtimeEvent> {\n this.iterating = true;\n return {\n next: () => {\n const event = this.queue.shift();\n if (event) return Promise.resolve({ value: event, done: false });\n if (this.isClosed) return Promise.resolve({ value: undefined, done: true });\n return new Promise((resolve) => this.waiters.push(resolve));\n },\n return: () => {\n this.iterating = false;\n this.queue = [];\n return Promise.resolve({ value: undefined, done: true });\n },\n };\n }\n\n private dispatch(event: RealtimeEvent): void {\n for (const type of [event.type, \"*\"]) {\n const set = this.handlers.get(type);\n if (!set) continue;\n for (const h of [...set]) {\n try {\n h(event);\n } catch (err) {\n // A broken handler must not stop the others or the session.\n setTimeout(() => {\n throw err;\n });\n }\n }\n }\n if (this.iterating) {\n const w = this.waiters.shift();\n if (w) w({ value: event, done: false });\n else this.queue.push(event);\n }\n }\n}\n\nexport class RealtimeService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Open a realtime voice session. Resolves once the model has accepted\n * it (`session.created`); rejects if the connection fails first.\n */\n async connect(options: RealtimeConnectOptions = {}): Promise<RealtimeSession> {\n const WS = options.WebSocket ?? (globalThis as { WebSocket?: WebSocketConstructor }).WebSocket;\n if (!WS) {\n throw new Error(\"runapps: no WebSocket available — pass `WebSocket` (e.g. from the `ws` package) on Node < 22\");\n }\n const token = await this.transport.bearerToken();\n const ws = new WS(realtimeURL(this.transport.baseURL, options), [\"realtime\", \"runapps-key.\" + token]);\n const session = new RealtimeSession(ws);\n const model = options.model ?? \"\";\n const id = newRequestId();\n const startedAt = Date.now();\n this.events.emit(\"request:start\", { id, model, capability: \"realtime\", startedAt, streaming: true });\n\n try {\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n fail(new Error(\"runapps: realtime session did not open in time\"));\n ws.close(1000, \"open timeout\");\n }, options.timeoutMs ?? 15_000);\n const onAbort = () => {\n fail(new Error(\"runapps: realtime connect aborted\"));\n ws.close(1000, \"aborted\");\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n let settled = false;\n function fail(err: Error) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n reject(err);\n }\n const offCreated = session.on(\"session.created\", () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n offCreated();\n offError();\n resolve();\n });\n // An error before the session opens is fatal to connect().\n const offError = session.on(\"error\", (ev) => {\n const e = (ev.error ?? {}) as { message?: string; code?: unknown };\n fail(new Error(`runapps: realtime error: ${e.message ?? JSON.stringify(ev)}`));\n });\n ws.addEventListener(\"open\", () => {\n ws.send(JSON.stringify(openingEvent(options)));\n });\n void session.closed.then((info) => {\n fail(new Error(`runapps: realtime connection closed (${info.code}${info.reason ? \" \" + info.reason : \"\"})`));\n });\n });\n } catch (err) {\n try {\n ws.close(1000, \"connect failed\");\n } catch {\n // already closed\n }\n this.events.emit(\"request:error\", {\n id,\n model,\n capability: \"realtime\",\n latencyMs: Date.now() - startedAt,\n error: err as Error,\n });\n throw err;\n }\n\n void session.closed.then((info) => {\n this.events.emit(\"request:end\", {\n id,\n model,\n capability: \"realtime\",\n latencyMs: Date.now() - startedAt,\n totalTokens: info.usage?.output_tokens ?? 0,\n ...(info.usage?.total_cost !== undefined && { costUSD: info.usage.total_cost }),\n });\n });\n return session;\n }\n}\n\nfunction openingEvent(options: RealtimeConnectOptions): RealtimeEvent {\n const event: RealtimeEvent = { type: \"session.create\", session: options.session ?? {} };\n if (options.extension) event.extension = options.extension;\n return event;\n}\n\nfunction realtimeURL(baseURL: string, options: RealtimeConnectOptions): string {\n const u = new URL(baseURL.replace(/\\/+$/, \"\") + \"/v1/realtime\");\n u.protocol = u.protocol === \"http:\" ? \"ws:\" : \"wss:\";\n for (const [k, v] of Object.entries(options.query ?? {})) u.searchParams.set(k, v);\n if (options.model) u.searchParams.set(\"model\", options.model);\n return u.toString();\n}\n\n/* ------------------------------------------------------------------ */\n/* PCM helpers */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert Web Audio samples (Float32, -1…1) at `sampleRate` to 16-bit PCM\n * at `targetRate` (16 kHz, what realtime input expects), resampling\n * linearly when the rates differ.\n */\nexport function pcm16FromFloat32(samples: Float32Array, sampleRate: number, targetRate = 16000): Int16Array {\n const ratio = sampleRate / targetRate;\n const n = ratio === 1 ? samples.length : Math.floor(samples.length / ratio);\n const out = new Int16Array(n);\n for (let i = 0; i < n; i++) {\n let s: number;\n if (ratio === 1) {\n s = samples[i]!;\n } else {\n const pos = i * ratio;\n const j = Math.floor(pos);\n const frac = pos - j;\n const a = samples[j]!;\n const b = samples[Math.min(j + 1, samples.length - 1)]!;\n s = a + (b - a) * frac;\n }\n s = Math.max(-1, Math.min(1, s));\n out[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\n }\n return out;\n}\n\n/** Convert 16-bit little-endian PCM bytes (e.g. a decoded audio delta) to Float32 samples for Web Audio. */\nexport function float32FromPCM16(bytes: Uint8Array): Float32Array {\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const out = new Float32Array(bytes.byteLength >> 1);\n for (let i = 0; i < out.length; i++) out[i] = view.getInt16(i * 2, true) / 0x8000;\n return out;\n}\n\n/** Base64-encode bytes (audio for `input_audio_buffer.append`). */\nexport function bytesToBase64(bytes: Uint8Array): string {\n let bin = \"\";\n for (let i = 0; i < bytes.length; i += 0x8000) {\n bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));\n }\n return btoa(bin);\n}\n\n/** Decode base64 (e.g. `response.output_audio.delta`'s `delta`) to bytes. */\nexport function base64ToBytes(b64: string): Uint8Array {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\nfunction asBytes(v: Int16Array | Uint8Array | ArrayBuffer): Uint8Array {\n if (v instanceof Uint8Array) return v;\n if (v instanceof ArrayBuffer) return new Uint8Array(v);\n return new Uint8Array(v.buffer, v.byteOffset, v.byteLength);\n}\n","import type { Transport } from \"./transport.js\";\nimport type { PromptInput } from \"./chat.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/* ------------------------------------------------------------------ */\n/* Request */\n/* ------------------------------------------------------------------ */\n\n/**\n * Video generation params. Most fields map 1:1 to the gateway's\n * `/v1/videos/generations` body. Optional booleans (`generate_audio`,\n * `watermark`, `camera_fixed`, `return_last_frame`, `draft`) are\n * tri-state — omit to use the upstream default; explicit `false`\n * forces off.\n */\nexport interface VideoGenerateParams {\n /** The prompt text, a private prompt `{ id, vars }`, or a list of\n * those and texts joined in order (see `PromptInput`). */\n prompt: string | PromptInput;\n aspect_ratio?: string;\n duration?: number;\n resolution?: string;\n generate_audio?: boolean;\n\n /**\n * First / last frame keyframes (image-to-video). Each accepts:\n * - hosted https:// URL\n * - data:image/...;base64,<payload> URI (use `encodeImageUrl`)\n *\n * The previous `first_frame_b64` / `last_frame_b64` sibling fields\n * were removed — wrap raw bytes in a data: URI client-side via\n * `encodeImageUrl` and pass through `*_url` instead.\n */\n first_frame_url?: string;\n last_frame_url?: string;\n\n /**\n * Multimodal reference inputs (Seedance 2.0). Each entry can be a\n * hosted URL or a data: URI (use `encodeImageUrl` for raw bytes).\n * `reference_images_b64` was removed — fold any raw-base64 entries\n * into `reference_image_urls` as data: URIs.\n */\n reference_image_urls?: string[];\n reference_video_urls?: string[];\n reference_audio_urls?: string[];\n\n /** Single-input drivers (video-edit / motion-transfer / lip-sync). */\n source_video_url?: string;\n source_image_url?: string;\n source_audio_url?: string;\n\n watermark?: boolean;\n camera_fixed?: boolean;\n return_last_frame?: boolean;\n seed?: number;\n\n /** Alternative to `duration` for Seedance 1.0 pro / lite. */\n frames?: number;\n\n /** Seedance 1.5 pro Draft Mode. */\n draft?: boolean;\n draft_task_id?: string;\n\n /** \"flex\" = offline (~50% price); pair with execution_expires_after. */\n service_tier?: string;\n execution_expires_after?: number;\n\n /** Webhook for state-transition notifications. */\n callback_url?: string;\n user?: string;\n\n /** Pass-through for provider-specific knobs. */\n [extra: string]: unknown;\n}\n\n/* ------------------------------------------------------------------ */\n/* Response */\n/* ------------------------------------------------------------------ */\n\nexport interface VideoTask {\n id: string;\n status: string;\n usage: Usage;\n}\n\nexport interface VideoUsageTokens {\n completion_tokens?: number;\n total_tokens?: number;\n}\n\nexport interface VideoStatus {\n id: string;\n status: string;\n progress: number;\n video_url?: string;\n error?: string;\n /** Final frame as a separate image — populated when return_last_frame=true. */\n last_frame_url?: string;\n /** Seedance Draft Mode Step1 task id, exposed when the draft succeeds. */\n draft_task_id?: string;\n duration?: number;\n fps?: number;\n resolution?: string;\n ratio?: string;\n seed?: number;\n service_tier?: string;\n /** Upstream-reported tokens (Seedance bills per output token). */\n usage_tokens?: VideoUsageTokens;\n created_at?: number;\n updated_at?: number;\n /**\n * Human-readable credit line that the client SHOULD display alongside\n * the video. Populated by stock-library models (Pexels) whose terms\n * of service require crediting the contributor; AI generators leave\n * it empty. Render verbatim — don't try to parse the format. See\n * ImageResult.attribution for the same convention.\n */\n attribution?: string;\n}\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n\nexport interface WaitOptions {\n /** Defaults to 5000ms. */\n pollIntervalMs?: number;\n signal?: AbortSignal;\n}\n\nexport class VideoService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /** Submit a video generation task.\n *\n * Telemetry note: only the submit step fires start/end on\n * `client.events` — the per-call latency reflects the gateway\n * accepting the job, NOT how long upstream takes to render. The\n * caller's poll loop sees status flips but the SDK doesn't emit\n * end events for those (would otherwise leave the badge \"active\"\n * for as long as the upstream renders, sometimes minutes). */\n async generate(\n model: string,\n params: VideoGenerateParams,\n init?: { signal?: AbortSignal },\n ): Promise<VideoTask> {\n return wrapEvents(\n this.events,\n { model, capability: \"video_generation\" },\n () =>\n this.transport.postJSON<VideoTask>(\n \"/v1/videos/generations\",\n { model, ...params },\n init,\n ),\n );\n }\n\n /** Fetch the current status of a task. */\n async getStatus(\n taskID: string,\n init?: { signal?: AbortSignal },\n ): Promise<VideoStatus> {\n return this.transport.getJSON<VideoStatus>(\n `/v1/videos/generations/${encodeURIComponent(taskID)}`,\n init,\n );\n }\n\n /**\n * Poll `getStatus()` until the task reaches a terminal state\n * (`succeeded` / `failed`). Default poll interval is 5s.\n */\n async wait(taskID: string, opts: WaitOptions = {}): Promise<VideoStatus> {\n const interval = opts.pollIntervalMs ?? 5000;\n while (true) {\n const status = await this.getStatus(taskID, { signal: opts.signal });\n if (status.status === \"succeeded\" || status.status === \"failed\") {\n return status;\n }\n await sleep(interval, opts.signal);\n }\n }\n\n /** Download the raw video bytes for a completed task. */\n async getContent(\n taskID: string,\n init?: { signal?: AbortSignal },\n ): Promise<{ data: Uint8Array; contentType: string }> {\n return this.transport.getRaw(\n `/v1/videos/${encodeURIComponent(taskID)}/content`,\n init,\n );\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason ?? new Error(\"aborted\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error(\"aborted\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n","import type { Transport } from \"./transport.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/** Single-step computer-use request — a screenshot + history → next action(s). */\nexport interface ComputerStepParams {\n /**\n * Conversation history including screenshots and tool results. Each\n * entry has `role` + `content` (string or array of blocks). Format is\n * intentionally opaque to support both Anthropic and OpenAI computer-use\n * protocols — the gateway adapts.\n */\n messages: Record<string, unknown>[];\n /** Screen resolution. Defaults to 1920×1080 if omitted. */\n display_width?: number;\n display_height?: number;\n max_tokens?: number;\n enable_zoom?: boolean;\n /** OpenAI Responses-API state chaining. */\n previous_response_id?: string;\n /** Follow-up `computer_call_output` for OpenAI's Responses API. */\n openai_input?: unknown;\n user?: string;\n}\n\n/** One block of a computer-use response: text, tool_use (Anthropic), or computer_call (OpenAI). */\nexport interface ComputerContentBlock {\n type: string;\n text?: string;\n id?: string;\n name?: string;\n /** Anthropic tool input (raw JSON). */\n input?: unknown;\n call_id?: string;\n /** OpenAI computer-call action. */\n action?: Record<string, unknown>;\n}\n\nexport interface ComputerResponse {\n content: ComputerContentBlock[];\n stop_reason?: string;\n usage: Usage;\n response_id?: string;\n /** \"anthropic\" | \"openai\" — which protocol the upstream returned. */\n protocol?: string;\n}\n\nexport class ComputerService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Execute one step of a computer-use agent loop. Given a screenshot\n * and conversation history, returns the next action(s) the model\n * wants the caller to execute.\n */\n async step(\n model: string,\n params: ComputerStepParams,\n init?: { signal?: AbortSignal },\n ): Promise<ComputerResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"computer_use\" },\n () =>\n this.transport.postJSON<ComputerResponse>(\n \"/v1/computer/step\",\n { model, ...params },\n init,\n ),\n (r) => ({\n totalTokens: r.usage?.completion_tokens,\n costUSD: r.usage?.total_cost,\n }),\n );\n }\n}\n","// Per-project file system at /v1/files/*. Files are stored under\n// (user, project) on the gateway and addressed by POSIX-style paths.\n// Each FileObject's `url` is a stable public address — embed it\n// directly in `<img src>`, share it, persist it.\n//\n// The bundle's RunApps client uses `client.files` to access these\n// endpoints; the underlying token must be a project-bound resource\n// token (rrt_*) issued by the grant flow.\n\nimport { cleanETag, type Transport } from \"./transport.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\nexport interface FileObject {\n path: string;\n size: number;\n content_type?: string;\n etag?: string;\n last_modified?: string;\n url: string;\n}\n\nexport interface FileListResult {\n files: FileObject[];\n next_cursor?: string;\n}\n\nexport interface PutOptions {\n /** Override the MIME type guessed from the path extension. */\n contentType?: string;\n /** Refuse to overwrite an existing object (gateway returns 409). */\n ifNoneMatch?: boolean;\n /**\n * Write only if the stored object still has this ETag — the one\n * `read()` / `stat()` / a previous `put()` returned. Someone else\n * having written in between makes the gateway answer 412 instead\n * of silently overwriting their version; re-read, merge, put again.\n */\n ifMatch?: string;\n signal?: AbortSignal;\n}\n\n/** The bytes at a path together with the version they are. */\nexport interface FileRead {\n blob: Blob;\n contentType: string;\n /** Pass back as `ifMatch` to write only if nobody else has since. */\n etag?: string;\n}\n\nexport interface ListOptions {\n prefix?: string;\n cursor?: string;\n limit?: number;\n /**\n * Shell-style pattern, post-filtered after the underlying S3 list:\n * * any chars except `/`\n * ** any chars including `/`\n * ? single non-slash char\n * [abc] character class\n *\n * Examples:\n * client.files.list({ glob: \"*.png\" })\n * client.files.list({ glob: \"projects/*\\/assets/*.png\" })\n * client.files.list({ prefix: \"projects/\", glob: \"**\\/*.wav\" })\n *\n * S3 has no native glob — the gateway extracts the longest literal\n * head of the pattern as the underlying prefix scan, then filters\n * the rest in process. Patterns must match the WHOLE path; no\n * implicit anchoring needed.\n */\n glob?: string;\n signal?: AbortSignal;\n}\n\nexport interface PutFromURLOptions {\n contentType?: string;\n ifNoneMatch?: boolean;\n ifMatch?: string;\n signal?: AbortSignal;\n}\n\nexport type BatchOp =\n | { op: \"put_url\"; path: string; src_url: string; content_type?: string; if_none_match?: boolean; if_match?: string }\n | { op: \"del\"; path: string }\n | { op: \"move\"; from: string; to: string }\n | { op: \"copy\"; from: string; to: string }\n | { op: \"exists\"; path: string }\n | { op: \"stat\"; path: string };\n\nexport interface BatchResult {\n ok: boolean;\n error?: string;\n object?: FileObject;\n exists?: boolean;\n}\n\nfunction pathToURL(base: string, p: string): string {\n const trimmed = p.replace(/^\\/+/, \"\");\n return base + \"/\" + trimmed.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\nexport class FilesService {\n /**\n * URL prefix for every file call.\n *\n * When the client was constructed with `project`, requests go to the\n * project-scoped form — `/v1/p/<project>/files/...` — instead of the\n * bare `/v1/files/...`.\n *\n * This matters because the unscoped URL is byte-identical across apps:\n * two bundles built from the same template both read\n * `projects/index.json`, so anything keying on URL alone (starting\n * with the browser's HTTP cache, which ignores request headers and is\n * partitioned by registrable domain, not by subdomain) cannot tell\n * them apart. Putting the project in the path makes the URL unique\n * per app. The gateway still authorizes purely from the token and\n * rejects a scope that disagrees with it, so this is a correctness\n * measure, not a permission one.\n */\n private readonly base: string;\n\n constructor(\n private readonly transport: Transport,\n project?: string,\n /** When given, every call emits request:start / end / error so the\n * badge can show it in flight; the sync engine's own file traffic\n * uses a service without one. */\n private readonly events?: SDKEvents,\n ) {\n this.base = project\n ? \"/v1/p/\" + encodeURIComponent(project) + \"/files\"\n : \"/v1/files\";\n }\n\n /** Run one file call, with badge events when the service has a bus. */\n private run<T>(op: string, fn: () => Promise<T>): Promise<T> {\n return this.events ? wrapEvents(this.events, { model: op, capability: \"files\" }, fn) : fn();\n }\n\n /**\n * Upload `body` to `path`. Returns the resulting FileObject — its\n * `url` is the stable public address for that path.\n *\n * `body` accepts anything `fetch` accepts as a request body: Blob,\n * File, ArrayBuffer, ArrayBufferView, FormData (rare here),\n * ReadableStream, or a string.\n */\n async put(\n path: string,\n body: BodyInit,\n opts?: PutOptions,\n ): Promise<FileObject> {\n const headers: Record<string, string> = {};\n if (opts?.ifNoneMatch) headers[\"If-None-Match\"] = \"*\";\n if (opts?.ifMatch) headers[\"If-Match\"] = `\"${cleanETag(opts.ifMatch)}\"`;\n return this.run(\"files.put\", () => this.transport.putBytes<FileObject>(pathToURL(this.base, path), body, {\n contentType: opts?.contentType,\n headers: Object.keys(headers).length ? headers : undefined,\n signal: opts?.signal,\n }));\n }\n\n /** Convenience: upload string content with sensible content-type. */\n async putString(\n path: string,\n content: string,\n opts?: PutOptions,\n ): Promise<FileObject> {\n return this.put(path, content, {\n ...opts,\n contentType: opts?.contentType ?? \"text/plain; charset=utf-8\",\n });\n }\n\n /**\n * Download the bytes at `path` as a Blob. Use `URL.createObjectURL`\n * to get a temporary local URL, or persist the bytes elsewhere.\n * Bundles that just need to render an asset should use the FileObject's\n * `.url` directly instead — that's a stable public URL with no token.\n */\n async get(path: string, init?: { signal?: AbortSignal }): Promise<Blob> {\n return (await this.read(path, init)).blob;\n }\n\n /**\n * Like `get`, but also returns the ETag of the bytes, so a later\n * `put(path, body, { ifMatch: etag })` writes only if the file is\n * still the version that was read.\n */\n async read(path: string, init?: { signal?: AbortSignal; ifNoneMatch?: string }): Promise<FileRead> {\n return this.run(\"files.read\", () => this.readRaw(path, init));\n }\n\n private async readRaw(path: string, init?: { signal?: AbortSignal; ifNoneMatch?: string }): Promise<FileRead> {\n const { data, contentType, etag, notModified } = await this.transport.getRaw(pathToURL(this.base, path), init);\n return {\n blob: new Blob([new Uint8Array(data)], { type: contentType || \"application/octet-stream\" }),\n contentType,\n etag,\n ...(notModified ? { notModified: true } : {}),\n };\n }\n\n /**\n * Read a JSON file and its version in one call. A missing file is\n * `{ value: undefined, etag: undefined }`, so a first save can pass\n * `ifNoneMatch: true` and a later one `ifMatch: etag`.\n */\n async readJSON<T = unknown>(\n path: string,\n init?: { signal?: AbortSignal; ifNoneMatch?: string },\n ): Promise<{ value: T | undefined; etag?: string; notModified?: boolean }> {\n let r: FileRead & { notModified?: boolean };\n try {\n r = await this.read(path, init);\n } catch (e) {\n if ((e as { statusCode?: number; status?: number })?.statusCode === 404 ||\n (e as { statusCode?: number; status?: number })?.status === 404) {\n return { value: undefined, etag: undefined };\n }\n throw e;\n }\n if (r.notModified) return { value: undefined, etag: r.etag, notModified: true };\n return { value: JSON.parse(await r.blob.text()) as T, etag: r.etag };\n }\n\n /** Stat: HEAD-only object metadata. */\n async stat(path: string, init?: { signal?: AbortSignal }): Promise<FileObject> {\n const { status, headers } = await this.run(\"files.stat\", () => this.transport.head(pathToURL(this.base, path), init));\n if (status === 404) {\n throw Object.assign(new Error(\"not found\"), { status: 404 });\n }\n if (status >= 400) {\n throw Object.assign(new Error(\"stat failed\"), { status });\n }\n const size = parseInt(headers.get(\"content-length\") ?? \"0\", 10);\n return {\n path: headers.get(\"x-file-path\") ?? path,\n size,\n content_type: headers.get(\"content-type\") ?? undefined,\n etag: (headers.get(\"etag\") ?? \"\").replace(/\"/g, \"\") || undefined,\n url: headers.get(\"x-file-url\") ?? \"\",\n };\n }\n\n /** Returns true iff the path resolves to an existing object. */\n async exists(path: string, init?: { signal?: AbortSignal }): Promise<boolean> {\n const { status } = await this.run(\"files.exists\", () => this.transport.head(pathToURL(this.base, path), init));\n return status >= 200 && status < 400;\n }\n\n /** Delete the object at `path`. Idempotent. */\n async del(path: string, init?: { signal?: AbortSignal }): Promise<void> {\n await this.run(\"files.del\", () => this.transport.deletePath<{ ok: boolean }>(pathToURL(this.base, path), init));\n }\n\n /** List objects. Pass an empty / omitted prefix for the root. */\n async list(opts?: ListOptions): Promise<FileListResult> {\n const params = new URLSearchParams();\n if (opts?.prefix) params.set(\"prefix\", opts.prefix);\n if (opts?.cursor) params.set(\"cursor\", opts.cursor);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n if (opts?.glob) params.set(\"glob\", opts.glob);\n const qs = params.toString();\n return this.run(\"files.list\", () => this.transport.getJSON<FileListResult>(\n this.base + (qs ? \"?\" + qs : \"\"),\n opts?.signal ? { signal: opts.signal } : undefined,\n ));\n }\n\n /**\n * Bulk delete: removes every object whose path starts with `prefix`\n * AND (when set) matches `glob`. Returns the count of deleted\n * objects. Refuses an empty prefix + empty glob — the caller must\n * be explicit about wiping the project namespace.\n *\n * client.files.deleteMany({ prefix: \"tmp/\" }) // wipe a directory\n * client.files.deleteMany({ glob: \"**\\/*.tmp\" }) // wipe by pattern\n * client.files.deleteMany({ prefix: \"logs/\", glob: \"*.bak\" })\n */\n async deleteMany(\n opts: { prefix?: string; glob?: string },\n init?: { signal?: AbortSignal },\n ): Promise<{ deleted: number }> {\n return this.run(\"files.deleteMany\", () => this.transport.postJSON<{ deleted: number }>(\n this.base + \"/delete\",\n { prefix: opts.prefix ?? \"\", glob: opts.glob ?? \"\" },\n init,\n ));\n }\n\n /** Atomic rename (copy + delete server-side). */\n async move(from: string, to: string, init?: { signal?: AbortSignal }): Promise<FileObject> {\n return this.run(\"files.move\", () => this.transport.postJSON<FileObject>(this.base + \"/move\", { from, to }, init));\n }\n\n /** Server-side copy. */\n async copy(from: string, to: string, init?: { signal?: AbortSignal }): Promise<FileObject> {\n return this.run(\"files.copy\", () => this.transport.postJSON<FileObject>(this.base + \"/copy\", { from, to }, init));\n }\n\n /**\n * Ask the gateway to fetch `srcURL` and store the body at `path`.\n * Saves a round trip vs downloading then re-uploading. `srcURL`\n * must be a public http(s) URL (private/loopback hosts rejected).\n */\n async putFromURL(\n path: string,\n srcURL: string,\n opts?: PutFromURLOptions,\n ): Promise<FileObject> {\n return this.run(\"files.putFromURL\", () => this.transport.postJSON<FileObject>(\n this.base + \"/put-url\",\n {\n path,\n src_url: srcURL,\n content_type: opts?.contentType,\n if_none_match: opts?.ifNoneMatch,\n if_match: opts?.ifMatch,\n },\n opts?.signal ? { signal: opts.signal } : undefined,\n ));\n }\n\n /**\n * Pipeline several file ops in one round trip. Operations execute\n * sequentially server-side; one op failing does not abort the rest.\n * Inspect each result.ok individually.\n *\n * Auto-chunks: the gateway caps a single batch at 64 ops and returns\n * 400 \"too many ops\" above that. This method splits long input into\n * 30-op chunks, awaits each chunk in order, and concatenates the\n * results — so callers can hand it 1000 ops in a single call.\n * Chunks run serially (not in parallel) to preserve the\n * \"operations execute in submission order\" contract.\n */\n async batch(\n ops: BatchOp[],\n init?: { signal?: AbortSignal },\n ): Promise<BatchResult[]> {\n if (ops.length === 0) return [];\n return this.run(\"files.batch\", () => this.batchRaw(ops, init));\n }\n\n private async batchRaw(ops: BatchOp[], init?: { signal?: AbortSignal }): Promise<BatchResult[]> {\n const chunkSize = 30;\n if (ops.length <= chunkSize) {\n const resp = await this.transport.postJSON<{ results: BatchResult[] }>(\n this.base + \"/batch\",\n { ops },\n init,\n );\n return resp.results;\n }\n const all: BatchResult[] = [];\n for (let i = 0; i < ops.length; i += chunkSize) {\n const chunk = ops.slice(i, i + chunkSize);\n const resp = await this.transport.postJSON<{ results: BatchResult[] }>(\n this.base + \"/batch\",\n { ops: chunk },\n init,\n );\n all.push(...resp.results);\n }\n return all;\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { Usage } from \"./types.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { wrapEvents } from \"./event-wrap.js\";\n\n/* ------------------------------------------------------------------ */\n/* Embeddings — POST /v1/embeddings */\n/* ------------------------------------------------------------------ */\n/* */\n/* OpenAI-compatible vector embedding endpoint. Wire shape mirrors */\n/* OpenAI's `/v1/embeddings` exactly so callers porting from the */\n/* official `openai` SDK can swap the import line and keep working. */\n/* ------------------------------------------------------------------ */\n\nexport interface EmbeddingsParams {\n /** Single string OR array of strings — gateway forwards either\n * shape verbatim. Passing a string returns `data: [{...}]` (1\n * element), passing an array returns one element per input. */\n input: string | string[];\n /** OpenAI per-end-user observability tag. */\n user?: string;\n /** \"float\" (default) or \"base64\". `base64` packs vectors as\n * little-endian float32 + base64-encoded — saves bandwidth for\n * high-D embeddings (3072-D × 1000 docs is ~3× smaller than the\n * JSON float-array form). */\n encoding_format?: \"float\" | \"base64\";\n /** Truncate the output vector. text-embedding-3-* only — other\n * models reject. */\n dimensions?: number;\n}\n\nexport interface Embedding {\n object: \"embedding\";\n /** Vector. When `encoding_format` was \"float\" (default), this is\n * `number[]`. When \"base64\", this is the encoded `string`. */\n embedding: number[] | string;\n index: number;\n}\n\n/** OpenAI's usage block plus the gateway's billing extension\n * (`total_cost` in USD).\n *\n * Shape parallels the `Usage` type used by chat / image / video so\n * callers can read `.total_cost` on every response uniformly. */\nexport interface EmbeddingsUsage extends Usage {\n prompt_tokens: number;\n total_tokens: number;\n}\n\nexport interface EmbeddingsResponse {\n object: \"list\";\n data: Embedding[];\n model: string;\n usage: EmbeddingsUsage;\n}\n\nexport class EmbeddingsService {\n constructor(\n private readonly transport: Transport,\n private readonly events: SDKEvents,\n ) {}\n\n /**\n * Create one or more embeddings.\n *\n * ```ts\n * const r = await client.embeddings.create(\"text-embedding-3-small\", {\n * input: [\"alpha\", \"beta\"],\n * dimensions: 1536,\n * });\n * for (const e of r.data) {\n * console.log(e.index, (e.embedding as number[]).slice(0, 4));\n * }\n * console.log(`Cost: $${r.usage.total_cost.toFixed(6)}`);\n * ```\n */\n async create(\n model: string,\n params: EmbeddingsParams,\n init?: { signal?: AbortSignal },\n ): Promise<EmbeddingsResponse> {\n return wrapEvents(\n this.events,\n { model, capability: \"embedding\" },\n () =>\n this.transport.postJSON<EmbeddingsResponse>(\n \"/v1/embeddings\",\n { model, ...params },\n init,\n ),\n (r) => ({\n totalTokens: r.usage?.total_tokens,\n costUSD: r.usage?.total_cost,\n }),\n );\n }\n}\n","/**\n * In-memory session-scoped activity index — turns the raw event\n * stream from `SDKEvents` into the snapshot the badge UI reads each\n * frame: which calls are in flight, what just finished, what the\n * running session cost is.\n *\n * Memory bounded:\n * - `active` map shrinks on every end/error (calls don't accumulate\n * past their settle event).\n * - `recent` is a bounded ring of the last RECENT_MAX completions.\n * - tokens/sec windows hold only `RATE_WINDOW_MS` worth of deltas;\n * stale samples get dropped on every push.\n *\n * Snapshot pattern (vs. emitting another event per change): the\n * badge re-renders on `requestAnimationFrame` while it's open, so\n * a pull-snapshot API matches that cadence — pushing on every\n * delta would over-paint and cost battery on token-heavy streams.\n */\n\nimport type {\n RequestEndEvent,\n RequestErrorEvent,\n RequestStartEvent,\n RequestStreamDeltaEvent,\n SDKCapability,\n SDKEvents,\n} from \"./events.js\";\n\n/** Sliding window for tokens/sec — 1.5s gives a snappy display\n * without jittering on every chunk arrival (most providers emit\n * ~5-30 deltas/s). */\nconst RATE_WINDOW_MS = 1500;\n\n/** Cap on the recent-completion ring buffer. The popover lists the\n * last 5 by default but we keep a few extra so a future \"show more\"\n * UI doesn't need a refactor. */\nconst RECENT_MAX = 20;\n\n/** One LLM call currently in flight. */\nexport interface ActiveCall {\n id: string;\n model: string;\n capability: SDKCapability;\n startedAt: number;\n /** True for stream:true. Drives the pulsing-ring animation. */\n streaming: boolean;\n /** Cumulative completion tokens (0 until first delta). */\n tokensSoFar: number;\n /** Tokens per second over the last RATE_WINDOW_MS. 0 until the\n * window has 2+ samples. */\n tokensPerSec: number;\n /** Internal sample buffer — exposed for tests; UI ignores. */\n rateSamples: Array<{ t: number; tokens: number }>;\n}\n\n/** One LLM call that already finished (success OR failure). */\nexport interface CompletedCall {\n id: string;\n model: string;\n capability: SDKCapability;\n latencyMs: number;\n totalTokens: number;\n costUSD?: number;\n finishReason?: string;\n /** false for RequestErrorEvent settles. */\n ok: boolean;\n /** Error message when ok=false. */\n errorMessage?: string;\n /** Wall clock at settle — drives the \"30s ago\" relative display. */\n endedAt: number;\n}\n\n/** Running session totals — shown in the popover's footer. */\nexport interface SessionStats {\n totalCalls: number;\n totalCostUSD: number;\n errorCount: number;\n /** Wall clock when the tracker first saw an event. Drives the\n * \"session lasted N minutes\" string. */\n startedAt: number;\n}\n\n/** Pull-snapshot of the tracker — taken once per badge re-render. */\nexport interface ActivitySnapshot {\n active: ActiveCall[];\n recent: CompletedCall[];\n session: SessionStats;\n /** Sync state, when RunApps Sync is on (see `client.sync`). */\n sync?: { state: \"off\" | \"idle\" | \"syncing\" | \"offline\" | \"error\"; pending: number; lastSyncedAt?: number; lastChangeAt?: number; error?: string };\n /** Highest-priority status colour for the LED dot:\n * \"error\" → recent failure within ERROR_HIGHLIGHT_MS\n * \"active\" → at least one in-flight call\n * \"idle\" → nothing happening\n * The badge's CSS uses this as a class. */\n status: \"idle\" | \"active\" | \"error\";\n}\n\n/** Window during which a recent error keeps the LED red. 30s is\n * long enough to grab attention but short enough that an old\n * failure doesn't dominate when subsequent calls succeed. */\nconst ERROR_HIGHLIGHT_MS = 30_000;\n\nexport class ActivityTracker {\n private active = new Map<string, ActiveCall>();\n private recent: CompletedCall[] = [];\n private session: SessionStats = {\n totalCalls: 0,\n totalCostUSD: 0,\n errorCount: 0,\n startedAt: Date.now(),\n };\n /** Track most recent error wall-clock so the LED stays red for\n * ERROR_HIGHLIGHT_MS even after the error scrolls out of recent. */\n private lastErrorAt = 0;\n /** \"Snapshot changed\" listeners. Fired AFTER every event handler\n * runs (start / delta / end / error), letting UIs re-render in\n * push-mode instead of polling. Without this hook, the badge's\n * ring/LED stayed idle until the user opened the popover —\n * events landed in the tracker but nobody told the DOM. */\n private changeListeners = new Set<() => void>();\n private sync: ActivitySnapshot[\"sync\"] | undefined = undefined;\n\n /**\n * Subscribe the tracker to a bus. Returns a single disposer that\n * unsubscribes all four handlers — callers (BrowserAuth in our\n * case) attach this on construction and dispose on teardown.\n */\n attach(events: SDKEvents): () => void {\n const offs = [\n events.on(\"request:start\", (e) => { this.onStart(e); this.notify(); }),\n events.on(\"request:streamDelta\", (e) => { this.onDelta(e); this.notify(); }),\n events.on(\"request:end\", (e) => { this.onEnd(e); this.notify(); }),\n events.on(\"request:error\", (e) => { this.onError(e); this.notify(); }),\n events.on(\"sync:state\", (e) => { this.sync = { ...e }; this.notify(); }),\n ];\n return () => {\n for (const o of offs) o();\n };\n }\n\n /**\n * Subscribe to \"snapshot may have changed\" notifications. Fires\n * synchronously after each event handler runs — typical consumer\n * is the badge's `requestRedraw()` which coalesces multiple\n * notifications into one rAF paint.\n *\n * Returns a disposer; call to unsubscribe.\n */\n onChange(handler: () => void): () => void {\n this.changeListeners.add(handler);\n return () => this.changeListeners.delete(handler);\n }\n\n private notify(): void {\n for (const h of this.changeListeners) {\n try {\n h();\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(\"[runapps sdk] ActivityTracker onChange listener threw:\", err);\n }\n }\n }\n\n /** O(active+recent) — small in practice, fine on every frame. */\n snapshot(): ActivitySnapshot {\n const now = Date.now();\n return {\n active: Array.from(this.active.values()),\n recent: this.recent.slice(),\n session: { ...this.session },\n ...(this.sync ? { sync: { ...this.sync } } : {}),\n status:\n now - this.lastErrorAt < ERROR_HIGHLIGHT_MS\n ? \"error\"\n : this.active.size > 0\n ? \"active\"\n : \"idle\",\n };\n }\n\n // ─── handlers ─────────────────────────────────────────────────\n\n private onStart(e: RequestStartEvent): void {\n this.active.set(e.id, {\n id: e.id,\n model: e.model,\n capability: e.capability,\n startedAt: e.startedAt,\n streaming: e.streaming,\n tokensSoFar: 0,\n tokensPerSec: 0,\n rateSamples: [],\n });\n }\n\n private onDelta(e: RequestStreamDeltaEvent): void {\n const call = this.active.get(e.id);\n if (!call) return; // delta after end — drop\n call.tokensSoFar = e.totalTokens;\n const now = Date.now();\n call.rateSamples.push({ t: now, tokens: e.deltaTokens });\n // Drop samples older than the window.\n const cutoff = now - RATE_WINDOW_MS;\n while (call.rateSamples.length > 0 && call.rateSamples[0]!.t < cutoff) {\n call.rateSamples.shift();\n }\n if (call.rateSamples.length >= 2) {\n const sumTokens = call.rateSamples.reduce((s, x) => s + x.tokens, 0);\n const spanMs = now - call.rateSamples[0]!.t;\n call.tokensPerSec = spanMs > 0 ? (sumTokens * 1000) / spanMs : 0;\n }\n }\n\n private onEnd(e: RequestEndEvent): void {\n const call = this.active.get(e.id);\n this.active.delete(e.id);\n // File calls only show while in flight: nothing to list or add up.\n if (e.capability === \"files\") return;\n this.pushRecent({\n id: e.id,\n model: e.model,\n capability: e.capability,\n latencyMs: e.latencyMs,\n totalTokens: e.totalTokens || call?.tokensSoFar || 0,\n ...(e.costUSD !== undefined && { costUSD: e.costUSD }),\n ...(e.finishReason !== undefined && { finishReason: e.finishReason }),\n ok: true,\n endedAt: Date.now(),\n });\n this.session.totalCalls += 1;\n if (e.costUSD !== undefined) this.session.totalCostUSD += e.costUSD;\n }\n\n private onError(e: RequestErrorEvent): void {\n const call = this.active.get(e.id);\n this.active.delete(e.id);\n if (e.capability === \"files\") {\n // Not listed; the LED goes red for a moment unless it was only a\n // missing file, which apps probe for routinely.\n if (e.statusCode !== 404) this.lastErrorAt = Date.now();\n return;\n }\n this.pushRecent({\n id: e.id,\n model: e.model,\n capability: e.capability,\n latencyMs: e.latencyMs,\n totalTokens: call?.tokensSoFar || 0,\n ok: false,\n errorMessage: e.error.message,\n endedAt: Date.now(),\n });\n this.session.totalCalls += 1;\n this.session.errorCount += 1;\n this.lastErrorAt = Date.now();\n }\n\n private pushRecent(c: CompletedCall): void {\n // Newest first — popover renders top-to-bottom newest→oldest.\n this.recent.unshift(c);\n if (this.recent.length > RECENT_MAX) this.recent.length = RECENT_MAX;\n }\n}\n","// sync/codec.ts — JSON encoding for IndexedDB values and keys.\n//\n// IndexedDB stores anything the structured-clone algorithm accepts:\n// Dates, Maps, Sets, binary, Blobs, nested objects, undefined. The sync\n// snapshots on the server are JSON, so every such value is wrapped in a\n// small tagged object on the way out and rebuilt on the way in. Plain\n// JSON values pass through untouched, which keeps the common case (a\n// record of strings and numbers) byte-for-byte readable on the server.\n//\n// Blobs are not inlined: `encodeValue` hands them to a `BlobSink` which\n// stores the bytes elsewhere (the engine uploads them as files) and\n// returns a reference; `decodeValue` asks a `BlobSource` for the bytes.\n\nconst TAG = \"$rt\";\n\n/** How an encoded value refers to a Blob stored out of band. */\nexport interface BlobRef {\n ref: string;\n type: string;\n size: number;\n}\n\nexport interface BlobSink {\n /** Store the blob and return a stable reference for it. */\n (blob: Blob): Promise<BlobRef>;\n}\n\nexport interface BlobSource {\n (ref: BlobRef): Promise<Blob>;\n}\n\ntype Tagged =\n | { [TAG]: \"date\"; v: number | string }\n | { [TAG]: \"undef\" }\n | { [TAG]: \"num\"; v: \"NaN\" | \"Infinity\" | \"-Infinity\" | \"-0\" }\n | { [TAG]: \"bigint\"; v: string }\n | { [TAG]: \"map\"; v: unknown[] }\n | { [TAG]: \"set\"; v: unknown[] }\n | { [TAG]: \"bin\"; c: string; v: string }\n | { [TAG]: \"blob\"; r: string; t: string; s: number; n?: string; m?: number }\n | { [TAG]: \"re\"; s: string; f: string }\n | { [TAG]: \"obj\"; v: Record<string, unknown> };\n\nconst BIN_CTORS: Record<string, (buf: ArrayBuffer) => ArrayBufferView | ArrayBuffer> = {\n ArrayBuffer: (b) => b,\n Uint8Array: (b) => new Uint8Array(b),\n Int8Array: (b) => new Int8Array(b),\n Uint8ClampedArray: (b) => new Uint8ClampedArray(b),\n Int16Array: (b) => new Int16Array(b),\n Uint16Array: (b) => new Uint16Array(b),\n Int32Array: (b) => new Int32Array(b),\n Uint32Array: (b) => new Uint32Array(b),\n Float32Array: (b) => new Float32Array(b),\n Float64Array: (b) => new Float64Array(b),\n BigInt64Array: (b) => new BigInt64Array(b),\n BigUint64Array: (b) => new BigUint64Array(b),\n DataView: (b) => new DataView(b),\n};\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let s = \"\";\n for (let i = 0; i < bytes.length; i += 0x8000) {\n s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));\n }\n return btoa(s);\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const s = atob(b64);\n const out = new Uint8Array(s.length);\n for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);\n return out;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n if (v === null || typeof v !== \"object\") return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Encode a structured-clone value into a JSON-safe one. Async only\n * because Blobs have to be read.\n */\nexport async function encodeValue(value: unknown, sink?: BlobSink): Promise<unknown> {\n if (value === undefined) return { [TAG]: \"undef\" } satisfies Tagged;\n if (value === null) return null;\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return value;\n case \"number\":\n if (Number.isNaN(value)) return { [TAG]: \"num\", v: \"NaN\" };\n if (value === Infinity) return { [TAG]: \"num\", v: \"Infinity\" };\n if (value === -Infinity) return { [TAG]: \"num\", v: \"-Infinity\" };\n if (Object.is(value, -0)) return { [TAG]: \"num\", v: \"-0\" };\n return value;\n case \"bigint\":\n return { [TAG]: \"bigint\", v: value.toString() };\n case \"function\":\n case \"symbol\":\n throw new TypeError(`runapps sync: ${typeof value} values cannot be stored`);\n }\n if (value instanceof Date) {\n const t = value.getTime();\n return { [TAG]: \"date\", v: Number.isNaN(t) ? \"invalid\" : t };\n }\n if (value instanceof RegExp) return { [TAG]: \"re\", s: value.source, f: value.flags };\n if (typeof Blob !== \"undefined\" && value instanceof Blob) {\n if (!sink) throw new TypeError(\"runapps sync: Blob values need a blob sink\");\n const ref = await sink(value);\n const out: Tagged = { [TAG]: \"blob\", r: ref.ref, t: ref.type, s: ref.size };\n const f = value as Blob & { name?: string; lastModified?: number };\n if (typeof File !== \"undefined\" && value instanceof File) {\n out.n = f.name;\n out.m = f.lastModified;\n }\n return out;\n }\n if (value instanceof ArrayBuffer) {\n return { [TAG]: \"bin\", c: \"ArrayBuffer\", v: bytesToBase64(new Uint8Array(value)) };\n }\n if (ArrayBuffer.isView(value)) {\n const name = value.constructor.name;\n if (!(name in BIN_CTORS)) throw new TypeError(`runapps sync: unsupported binary type ${name}`);\n const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n return { [TAG]: \"bin\", c: name, v: bytesToBase64(bytes) };\n }\n if (value instanceof Map) {\n const v: unknown[] = [];\n for (const [k, x] of value) v.push([await encodeValue(k, sink), await encodeValue(x, sink)]);\n return { [TAG]: \"map\", v };\n }\n if (value instanceof Set) {\n const v: unknown[] = [];\n for (const x of value) v.push(await encodeValue(x, sink));\n return { [TAG]: \"set\", v };\n }\n if (Array.isArray(value)) {\n const out: unknown[] = new Array(value.length);\n for (let i = 0; i < value.length; i++) out[i] = await encodeValue(value[i], sink);\n return out;\n }\n if (isPlainObject(value)) {\n // A plain object that happens to carry our tag key is wrapped so it\n // cannot be mistaken for an encoding marker.\n const needsWrap = Object.prototype.hasOwnProperty.call(value, TAG);\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value)) out[k] = await encodeValue(value[k], sink);\n return needsWrap ? { [TAG]: \"obj\", v: out } : out;\n }\n // Class instances: structured clone keeps own enumerable properties\n // only, so do the same.\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as object)) out[k] = await encodeValue((value as Record<string, unknown>)[k], sink);\n return out;\n}\n\n/** Inverse of `encodeValue`. */\nexport async function decodeValue(value: unknown, source?: BlobSource): Promise<unknown> {\n if (value === null || typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n const out: unknown[] = new Array(value.length);\n for (let i = 0; i < value.length; i++) out[i] = await decodeValue(value[i], source);\n return out;\n }\n const obj = value as Record<string, unknown>;\n const tag = obj[TAG];\n if (typeof tag === \"string\") {\n const t = obj as unknown as Tagged;\n switch (t[TAG]) {\n case \"undef\":\n return undefined;\n case \"date\":\n return t.v === \"invalid\" ? new Date(NaN) : new Date(t.v);\n case \"num\":\n return t.v === \"NaN\" ? NaN : t.v === \"Infinity\" ? Infinity : t.v === \"-Infinity\" ? -Infinity : -0;\n case \"bigint\":\n return BigInt(t.v);\n case \"re\":\n return new RegExp(t.s, t.f);\n case \"bin\": {\n const bytes = base64ToBytes(t.v);\n const buf = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(buf).set(bytes);\n const make = BIN_CTORS[t.c];\n if (!make) throw new TypeError(`runapps sync: unsupported binary type ${t.c}`);\n return make(buf);\n }\n case \"map\": {\n const m = new Map<unknown, unknown>();\n for (const pair of t.v as [unknown, unknown][]) {\n m.set(await decodeValue(pair[0], source), await decodeValue(pair[1], source));\n }\n return m;\n }\n case \"set\": {\n const s = new Set<unknown>();\n for (const x of t.v) s.add(await decodeValue(x, source));\n return s;\n }\n case \"blob\": {\n if (!source) throw new TypeError(\"runapps sync: Blob values need a blob source\");\n const blob = await source({ ref: t.r, type: t.t, size: t.s });\n if (t.n !== undefined && typeof File !== \"undefined\") {\n return new File([blob], t.n, { type: t.t, lastModified: t.m ?? Date.now() });\n }\n return blob;\n }\n case \"obj\":\n return decodeValue(t.v, source);\n }\n }\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(obj)) out[k] = await decodeValue(obj[k], source);\n return out;\n}\n\n// ── Keys ─────────────────────────────────────────────────────────────\n//\n// A valid IndexedDB key is a number, a string, a Date, binary, or an\n// array of those. Keys are encoded synchronously (no Blobs possible)\n// and also rendered to a canonical string that serves as the record id\n// inside a shard and as the input to the shard hash.\n\nexport function encodeKey(key: IDBValidKey): unknown {\n if (typeof key === \"number\") {\n if (Number.isNaN(key)) throw new TypeError(\"runapps sync: NaN is not a valid key\");\n return key;\n }\n if (typeof key === \"string\") return key;\n if (key instanceof Date) return { [TAG]: \"date\", v: key.getTime() };\n if (key instanceof ArrayBuffer) return { [TAG]: \"bin\", c: \"ArrayBuffer\", v: bytesToBase64(new Uint8Array(key)) };\n if (ArrayBuffer.isView(key)) {\n return {\n [TAG]: \"bin\",\n c: \"ArrayBuffer\",\n v: bytesToBase64(new Uint8Array(key.buffer, key.byteOffset, key.byteLength)),\n };\n }\n if (Array.isArray(key)) return key.map((k) => encodeKey(k as IDBValidKey));\n throw new TypeError(\"runapps sync: invalid IndexedDB key\");\n}\n\nexport function decodeKey(encoded: unknown): IDBValidKey {\n if (typeof encoded === \"number\" || typeof encoded === \"string\") return encoded;\n if (Array.isArray(encoded)) return encoded.map(decodeKey);\n if (encoded && typeof encoded === \"object\") {\n const t = encoded as { [TAG]?: string; v?: unknown };\n if (t[TAG] === \"date\") return new Date(t.v as number);\n if (t[TAG] === \"bin\") {\n const bytes = base64ToBytes(t.v as string);\n const buf = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(buf).set(bytes);\n return buf;\n }\n }\n throw new TypeError(\"runapps sync: invalid encoded key\");\n}\n\n/** Canonical string id for a key: equal keys give equal ids. */\nexport function keyId(key: IDBValidKey): string {\n return JSON.stringify(encodeKey(key));\n}\n\n/** Deterministic 32-bit hash of a string (FNV-1a). */\nexport function hash32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/**\n * Structural equality for structured-clone values: Dates by time,\n * binary by bytes, Map/Set by entries, Blobs by identity (two different\n * Blob objects count as a change). Used to find which fields a write\n * touched.\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (a instanceof Date || b instanceof Date) {\n return a instanceof Date && b instanceof Date && Object.is(a.getTime(), b.getTime());\n }\n if (typeof Blob !== \"undefined\" && (a instanceof Blob || b instanceof Blob)) return false;\n if (ArrayBuffer.isView(a) || a instanceof ArrayBuffer || ArrayBuffer.isView(b) || b instanceof ArrayBuffer) {\n const toBytes = (x: unknown): Uint8Array | null =>\n x instanceof ArrayBuffer ? new Uint8Array(x) : ArrayBuffer.isView(x) ? new Uint8Array(x.buffer, x.byteOffset, x.byteLength) : null;\n const ba = toBytes(a);\n const bb = toBytes(b);\n if (!ba || !bb || ba.length !== bb.length || a.constructor !== b.constructor) return false;\n for (let i = 0; i < ba.length; i++) if (ba[i] !== bb[i]) return false;\n return true;\n }\n if (a instanceof Map || b instanceof Map) {\n if (!(a instanceof Map && b instanceof Map) || a.size !== b.size) return false;\n for (const [k, v] of a) if (!b.has(k) || !deepEqual(v, b.get(k))) return false;\n return true;\n }\n if (a instanceof Set || b instanceof Set) {\n if (!(a instanceof Set && b instanceof Set) || a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;\n return true;\n }\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;\n }\n return true;\n}\n\n/** The fields of `value` that differ from `prior` (added, changed or\n * removed). `undefined` when there is no prior: everything is new. */\nexport function changedFields(prior: unknown, value: unknown): string[] | undefined {\n if (!isPlainObject(prior) || !isPlainObject(value)) return undefined;\n const out: string[] = [];\n for (const k of new Set([...Object.keys(prior), ...Object.keys(value)])) {\n const inP = Object.prototype.hasOwnProperty.call(prior, k);\n const inV = Object.prototype.hasOwnProperty.call(value, k);\n if (inP !== inV || !deepEqual(prior[k], value[k])) out.push(k);\n }\n return out;\n}\n\n/** Read an inline key out of a value by IndexedDB keyPath rules. */\nexport function extractKey(value: unknown, keyPath: string | string[] | null): IDBValidKey | undefined {\n if (keyPath === null || keyPath === undefined) return undefined;\n const one = (path: string): unknown => {\n let cur: unknown = value;\n if (path === \"\") return cur;\n for (const part of path.split(\".\")) {\n if (cur === null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n };\n if (Array.isArray(keyPath)) {\n const parts = keyPath.map(one);\n return parts.some((p) => p === undefined) ? undefined : (parts as IDBValidKey[]);\n }\n const k = one(keyPath);\n return k === undefined ? undefined : (k as IDBValidKey);\n}\n","// browser-auth.ts — opt-in runapps.ai auth flow for browser bundles.\n//\n// Activated by `new RunApps({ authProvider: \"runapps\", baseURL: \"...\" })`.\n// In Node and other non-window environments this module's runtime code\n// short-circuits to no-ops; it ships with the IIFE bundle for resource\n// bundles loaded via <script> tag.\n//\n// Responsibilities:\n// - Detect & consume the `#runapps_token=…` URL fragment planted by the\n// grant page after a successful sign-in.\n// - Trigger a redirect-based grant flow when the bundle calls a\n// gateway endpoint without a fresh token.\n// - In iframe contexts (a RunApps page embedding the app), handshake\n// with the parent via postMessage to silently obtain a token.\n// - Render an unobtrusive identity badge in the bottom-right corner\n// so the end user can see WHICH RunApps account is paying for the\n// calls being made on their behalf.\n//\n// Why redirect (not popup): under COOP=same-origin (required so the\n// bundle can use OPFS / SharedArrayBuffer), the browser severs the\n// window.opener relationship for cross-origin popups, so popup-based\n// grant flows silently lose their postMessage replies.\n\nimport { ActivityTracker, type ActivitySnapshot, type SessionStats } from \"./activity-tracker.js\";\nimport type { SDKEvents } from \"./events.js\";\nimport { hash32 } from \"./sync/codec.js\";\n\nexport interface BrowserAuthOptions {\n /**\n * Origin where the RunApps gateway lives — also the page that issues\n * tokens at /api/sdk/grant. Default `https://www.runapps.ai`.\n */\n origin?: string;\n /**\n * Suppress the floating identity badge. Default `false` at this\n * layer. The wrapping `RunApps` client passes `hideBadge: false`\n * by default (badge SHOWN — it's now a live activity indicator,\n * see `activity-tracker.ts`); callers wanting their own status UI\n * opt out via `RunApps({ showIdentityBadge: false })`. Set this\n * directly only when constructing BrowserAuth without going\n * through `RunApps`.\n */\n hideBadge?: boolean;\n /**\n * Pin the grant flow to a named runapps.ai project. When set, the\n * SDK passes `project_id=<value>` to `/api/sdk/grant`, asking the\n * gateway to mint a project-bound `rrt_*` resource token for THAT\n * project regardless of which (origin, app) pair the bundle is\n * served from.\n *\n * Use cases:\n * - **Local dev** for a 1:1 bundle/project app: the localhost\n * origin isn't registered with any project, so without this\n * option the grant flow can't pick one and `client.files` 403s\n * on every call. Pin the project explicitly and `pnpm dev`\n * hits the same files namespace as production.\n * - **Multi-tenant bundles** that let the user pick a project at\n * runtime — pass the chosen project id, then call `signOut()` +\n * `signIn()` (or reconstruct the client) on switch.\n *\n * Omit to keep the default behaviour: the gateway derives the\n * project from the registered (origin, app) pair.\n */\n project?: string;\n /**\n * Optional event bus from the parent RunApps client. When wired,\n * the identity badge subscribes to call telemetry events and\n * renders a real-time activity ring + LED dot + click-through\n * popover with active / recent / session stats — turning the\n * static \"signed in\" badge into a desktop-ball-style live\n * indicator (similar to a tray-app gauge).\n *\n * Optional and back-compat: when omitted (or when `hideBadge` is\n * true), the badge falls back to its original static behaviour.\n */\n events?: SDKEvents;\n /**\n * Which corner the badge floats in. See `RunApps.badgePosition`\n * for the user-facing flavour. Defaults to `\"bottom-right\"`.\n */\n badgePosition?: BadgePosition;\n}\n\n/** Four-corner anchor for the floating activity badge. */\nexport type BadgePosition = \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\";\n\nexport interface BrowserUser {\n id?: string;\n name?: string;\n}\n\nconst TOKEN_REFRESH_MARGIN_S = 60;\n// Per-project localStorage namespacing. When two BrowserAuth bundles\n// live on the same origin (most commonly `localhost:5173` during\n// development of multiple SDK-using apps, or a deployed origin that\n// hosts more than one bundle), a single shared key would let them\n// clobber each other's tokens — bundle B's signIn() would overwrite\n// the token bundle A had cached, and the next time A booted it'd\n// either reuse B's token (wrong project scope, every gateway call\n// returns 404 on `files/...`) or skip the cache and redirect through\n// the grant flow on every reload. Append the constructor-supplied\n// `project` to the key so bundles scoped to different projects keep\n// independent slots in localStorage. Bundles without an explicit\n// project (iframe / parent-handshake mode) fall back to the legacy\n// unsuffixed key, which preserves back-compat for already-persisted\n// tokens after upgrade.\nconst STORAGE_KEY_BASE = \"__runapps_auth_v1__\";\n// Written by SDK 0.3 and earlier; read once and migrated, so an upgrade\n// keeps the user signed in.\nconst LEGACY_STORAGE_KEY_BASE = \"__runjobs_auth_v1__\";\n// Set when the user clicks \"Sign out\". While present, getToken() must\n// not silently redirect to the grant page even though the user still\n// has a runapps.ai cookie — otherwise the backend's auto-grant turns\n// every page reload into a re-authentication. Cleared on explicit\n// signIn() so the user can come back. Same per-project namespacing\n// rationale as STORAGE_KEY_BASE — signing out of bundle A shouldn't\n// also sign out bundle B that happens to share the origin.\nconst SIGNED_OUT_KEY_BASE = \"__runapps_signed_out_v1__\";\nconst LEGACY_SIGNED_OUT_KEY_BASE = \"__runjobs_signed_out_v1__\";\n\n/** Compose the localStorage key for an auth slot. `null` project\n * yields the base key (iframe-mode and pre-v1.x bundles), which\n * preserves back-compat for tokens persisted before this change. */\nfunction storageKey(project: string | null): string {\n return project ? `${STORAGE_KEY_BASE}:${project}` : STORAGE_KEY_BASE;\n}\n\n/** Same as storageKey, for the sticky sign-out flag. */\nfunction signedOutKey(project: string | null): string {\n return project ? `${SIGNED_OUT_KEY_BASE}:${project}` : SIGNED_OUT_KEY_BASE;\n}\n\nfunction legacyStorageKey(project: string | null): string {\n return project ? `${LEGACY_STORAGE_KEY_BASE}:${project}` : LEGACY_STORAGE_KEY_BASE;\n}\n\nfunction legacySignedOutKey(project: string | null): string {\n return project ? `${LEGACY_SIGNED_OUT_KEY_BASE}:${project}` : LEGACY_SIGNED_OUT_KEY_BASE;\n}\n\ninterface PersistedAuth {\n token: string;\n expiresAt: number;\n user?: BrowserUser;\n origin: string;\n}\n\n/**\n * BrowserAuth encapsulates the browser-side auth state for a RunApps\n * client. Exposes:\n *\n * - getToken() fresh bearer (auto-refreshes / signs in)\n * - signIn() force a redirect to the grant page\n * - user { id, name } — null until signed in\n * - onTokenChange(fn) subscribe to token (re)acquisition\n *\n * Construct once per page; the SDK passes its `getToken` as the\n * transport's `apiKeyResolver`.\n */\nexport class BrowserAuth {\n private readonly origin: string;\n private readonly hideBadge: boolean;\n private readonly project: string | null;\n private readonly events: SDKEvents | null;\n /** Lazily-constructed when events are present — drives the badge's\n * activity ring / LED / popover. nullable for tests / non-browser. */\n private readonly tracker: ActivityTracker | null;\n private readonly badgePosition: BadgePosition;\n private token: string | null = null;\n private expiresAt = 0;\n private userInfo: BrowserUser | null = null;\n private listeners: Array<(token: string) => void> = [];\n private parentHandshake: Promise<void> | null = null;\n private signingIn = false;\n\n constructor(opts: BrowserAuthOptions = {}) {\n this.origin = (opts.origin ?? \"https://www.runapps.ai\").replace(/\\/$/, \"\");\n this.hideBadge = !!opts.hideBadge;\n this.project = opts.project ?? null;\n this.events = opts.events ?? null;\n this.badgePosition = opts.badgePosition ?? \"bottom-right\";\n if (this.events && !this.hideBadge) {\n this.tracker = new ActivityTracker();\n this.tracker.attach(this.events);\n } else {\n this.tracker = null;\n }\n if (typeof window === \"undefined\") return;\n\n // Restore from localStorage (survives page reloads). We pin the\n // saved token to the gateway origin: a bundle that switches\n // between dev / prod gateways shouldn't accidentally reuse the\n // previous one's token.\n this.loadPersisted();\n\n // Eager: install token from the URL fragment if the grant page\n // just redirected us back here. This always WINS over a\n // localStorage-cached token because a fresh redirect implies the\n // user just (re)authenticated and the old cache may be stale.\n // A successful fragment install also clears the \"signed out\"\n // sticky flag — the user explicitly signed in again.\n if (this.consumeFragment()) {\n this.clearSignedOut();\n }\n\n if (this.isSignedOut()) {\n // Honour the user's explicit sign-out across reloads. Don't\n // attempt parent handshake or render the badge.\n } else if (this.inIframe() && !this.tokenIsFresh()) {\n // Best-effort parent handshake — silent if it succeeds, falls\n // through to the standard fragment / redirect path otherwise.\n this.parentHandshake = this.requestTokenFromParent().catch(() => {\n /* dashboard parent unavailable; will redirect on next call */\n });\n } else if (this.tokenIsFresh() && this.userInfo && !this.hideBadge) {\n // Restored from cache — render the badge so the user can see\n // they're already signed in (and find the sign-out affordance\n // their bundle author exposed).\n this.renderBadge();\n }\n }\n\n /** Public token-fetcher; pass to RunApps as `apiKeyResolver`. */\n getToken = async (): Promise<string> => {\n if (this.tokenIsFresh()) return this.token as string;\n\n // User explicitly signed out — don't silently re-auth. Throw so\n // the caller surfaces an unauthenticated state and can prompt the\n // user to click signIn. Without this guard, the backend's\n // auto-grant flow would re-issue a token on every page reload\n // (the cookie on the gateway domain is still valid).\n if (this.isSignedOut()) {\n throw new Error(\"RunApps: signed out — call client.signIn() to authenticate\");\n }\n\n // ── Embedded (cross-site iframe) path ──\n //\n // Inside a cross-site iframe (the dashboard's bundle embed),\n // signIn() → /api/sdk/grant is a DEAD END: navigating the iframe to\n // the gateway origin is a *sub-frame* navigation, so the session\n // cookie is NOT sent (SameSite) and grant bounces to a login form\n // the user can't complete in place. The dashboard parent — same\n // origin as the gateway, and it holds the cookie — is the ONLY\n // party that can mint a token here. So we RE-handshake with the\n // parent on EVERY stale token (not just once at construction).\n // This is what lets a bundle survive token expiry / revocation\n // without ever dropping to the login screen.\n if (this.inIframe()) {\n try {\n // Reuse the eager construction-time handshake on the first call\n // (avoids a duplicate post), then consume it so later staleness\n // (expiry / 401-invalidate) starts a fresh handshake instead.\n if (this.parentHandshake) {\n await this.parentHandshake;\n this.parentHandshake = null;\n }\n if (!this.tokenIsFresh()) {\n await this.requestTokenFromParent();\n }\n } catch {\n /* parent slow / unreachable — fall through to the throw */\n }\n if (this.tokenIsFresh()) return this.token as string;\n // Never signIn() inside an iframe (cookie-less grant dead end).\n // Surface as unauthenticated so the bundle can retry / show state.\n throw new Error(\"RunApps: could not obtain a token from the dashboard\");\n }\n\n // ── Top-level (own tab / window) path ──\n //\n // signIn() → grant works here because the cookie IS sent on a\n // top-level navigation.\n if (this.parentHandshake) {\n await this.parentHandshake;\n if (this.tokenIsFresh()) return this.token as string;\n }\n // No fresh token + can't get one silently → redirect to grant.\n // The page is about to unload; return a never-resolving Promise so\n // the caller doesn't proceed with a half-issued request.\n this.signIn();\n return new Promise(() => {});\n };\n\n /** Subscribe to (re)acquisition events. Returns an unsubscribe fn. */\n onTokenChange(handler: (token: string) => void): () => void {\n this.listeners.push(handler);\n return () => {\n const i = this.listeners.indexOf(handler);\n if (i >= 0) this.listeners.splice(i, 1);\n };\n }\n\n /** Currently-authenticated user, or null. */\n get user(): BrowserUser | null {\n return this.userInfo;\n }\n\n /**\n * True iff a cached bearer token is present AND not within the\n * 60-second pre-expiry refresh margin. Use this — not `user !==\n * null` — to decide whether to show \"please sign in\" UI: the user\n * metadata is only populated when the SDK was fed fresh user info\n * (post-redirect fragment carries it; iframe handshake carries it;\n * an older localStorage save format may have persisted only the\n * token). A token without metadata is still a fully-usable\n * session — every gateway call will succeed — and prompting the\n * user to sign in there causes a redirect that loses session\n * continuity for no reason.\n */\n hasFreshToken(): boolean {\n return this.tokenIsFresh();\n }\n\n /**\n * A stable id for whose data is on this browser, or null when nobody\n * is signed in. The user's id when the grant carried one; otherwise\n * derived from the token, which is bound to one user and one app and\n * never rotates. Used by Sync to tell an account switch.\n */\n ownerId(): string | null {\n if (!this.tokenIsFresh()) return null;\n if (this.userInfo?.id) return this.userInfo.id;\n return \"t\" + hash32(this.token as string).toString(36);\n }\n\n /**\n * Clear the cached token / user / badge and the persisted copy in\n * localStorage. Next gateway call will trigger a redirect to the\n * grant page — i.e. the standard sign-in flow. Wire this to a\n * \"Sign out\" button in the bundle's settings UI.\n *\n * Does NOT redirect on its own; the bundle decides whether to\n * also navigate the user (e.g. to a logged-out splash page) or\n * just refresh state and let the next API call re-prompt.\n */\n signOut(): void {\n this.token = null;\n this.expiresAt = 0;\n this.userInfo = null;\n this.parentHandshake = null;\n this.signingIn = false;\n this.clearPersisted();\n this.markSignedOut();\n this.removeBadge();\n }\n\n /**\n * Drop the in-memory + persisted token without setting the sticky\n * \"signed out\" flag. Used by the transport layer when the gateway\n * returns 401 (token revoked server-side, e.g. user unsubscribed in\n * another tab). The next `getToken()` will trigger the standard\n * `signIn()` redirect-grant flow and the user re-auths transparently.\n */\n invalidate(): void {\n this.token = null;\n this.expiresAt = 0;\n this.userInfo = null;\n this.parentHandshake = null;\n this.signingIn = false;\n this.clearPersisted();\n }\n\n /** Force a redirect to the grant page. */\n signIn(): void {\n if (typeof window === \"undefined\" || this.signingIn) return;\n // Explicit signIn clears the \"signed out\" flag so getToken() can\n // proceed with the redirect-grant flow.\n this.clearSignedOut();\n this.signingIn = true;\n const ret = location.href.split(\"#\")[0] ?? location.href;\n const scheme = window.matchMedia?.(\"(prefers-color-scheme: dark)\")\n .matches\n ? \"dark\"\n : \"light\";\n location.href = this.buildGrantUrl({\n pageOrigin: location.origin,\n app: location.host,\n redirectTo: ret,\n scheme,\n });\n }\n\n /**\n * Build the `/api/sdk/grant?…` URL the user is redirected to. Split\n * out so tests can assert URL formation without a `window` context;\n * `signIn` is the only production caller.\n *\n * Public surface, but namespaced under `_buildGrantUrlForTest` so it\n * doesn't show up in IntelliSense as a normal API.\n */\n _buildGrantUrlForTest(args: {\n pageOrigin: string;\n app: string;\n redirectTo: string;\n scheme: \"light\" | \"dark\";\n }): string {\n return this.buildGrantUrl(args);\n }\n\n /**\n * Return the localStorage keys this instance reads / writes for its\n * persisted token + sign-out flag. Exposed for tests so we can\n * assert per-project namespacing without a real DOM; not part of\n * the public API surface (the leading underscore + `ForTest` suffix\n * is the SDK's convention for this).\n */\n _storageKeysForTest(): { auth: string; signedOut: string } {\n return {\n auth: storageKey(this.project),\n signedOut: signedOutKey(this.project),\n };\n }\n\n private buildGrantUrl(args: {\n pageOrigin: string;\n app: string;\n redirectTo: string;\n scheme: \"light\" | \"dark\";\n }): string {\n const params = new URLSearchParams();\n params.set(\"origin\", args.pageOrigin);\n params.set(\"app\", args.app);\n params.set(\"redirect_to\", args.redirectTo);\n params.set(\"scheme\", args.scheme);\n if (this.project) params.set(\"project_id\", this.project);\n // Ask for #runapps_token=…; a platform that predates it answers with\n // #runjobs_token=…, which consumeFragment also accepts.\n params.set(\"token_name\", \"runapps_token\");\n return this.origin + \"/api/sdk/grant?\" + params.toString();\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private nowSec() {\n return Math.floor(Date.now() / 1000);\n }\n\n private tokenIsFresh(): boolean {\n return !!this.token && this.nowSec() < this.expiresAt - TOKEN_REFRESH_MARGIN_S;\n }\n\n private inIframe(): boolean {\n try {\n return window.self !== window.top;\n } catch {\n return true;\n }\n }\n\n private setToken(token: string, expiresAt: number, user?: BrowserUser) {\n this.token = token;\n this.expiresAt = expiresAt || this.nowSec() + 3600;\n if (user) {\n this.userInfo = user;\n if (!this.hideBadge) this.renderBadge();\n }\n this.savePersisted();\n for (const fn of this.listeners) {\n try {\n fn(token);\n } catch (err) {\n console.warn(\"[runapps] onTokenChange handler threw\", err);\n }\n }\n }\n\n private savePersisted() {\n if (typeof localStorage === \"undefined\" || !this.token) return;\n try {\n const data: PersistedAuth = {\n token: this.token,\n expiresAt: this.expiresAt,\n origin: this.origin,\n ...(this.userInfo ? { user: this.userInfo } : {}),\n };\n localStorage.setItem(storageKey(this.project), JSON.stringify(data));\n } catch {\n /* quota / private mode — degrade to in-memory only */\n }\n }\n\n private loadPersisted() {\n if (typeof localStorage === \"undefined\") return;\n try {\n let raw = localStorage.getItem(storageKey(this.project));\n if (!raw) {\n // Upgrade from SDK 0.3 or earlier: move the token to the new key.\n raw = localStorage.getItem(legacyStorageKey(this.project));\n if (!raw) return;\n localStorage.setItem(storageKey(this.project), raw);\n localStorage.removeItem(legacyStorageKey(this.project));\n }\n const data = JSON.parse(raw) as Partial<PersistedAuth>;\n if (!data.token || !data.origin) return;\n // Pin to the gateway origin we were constructed with — a bundle\n // talking to a different gateway shouldn't reuse this token.\n if (data.origin !== this.origin) return;\n // Already expired? Drop it; next call will redirect to grant.\n if (typeof data.expiresAt === \"number\" && data.expiresAt > 0 &&\n this.nowSec() >= data.expiresAt - TOKEN_REFRESH_MARGIN_S) {\n this.clearPersisted();\n return;\n }\n this.token = data.token;\n this.expiresAt = data.expiresAt ?? 0;\n if (data.user) this.userInfo = data.user;\n } catch {\n /* corrupted blob — drop it */\n this.clearPersisted();\n }\n }\n\n private clearPersisted() {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(storageKey(this.project));\n localStorage.removeItem(legacyStorageKey(this.project));\n } catch { /* ignore */ }\n }\n\n private isSignedOut(): boolean {\n if (typeof localStorage === \"undefined\") return false;\n try {\n return localStorage.getItem(signedOutKey(this.project)) === \"1\" ||\n localStorage.getItem(legacySignedOutKey(this.project)) === \"1\";\n } catch { return false; }\n }\n\n private markSignedOut() {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.setItem(signedOutKey(this.project), \"1\");\n } catch { /* ignore */ }\n }\n\n private clearSignedOut() {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.removeItem(signedOutKey(this.project));\n localStorage.removeItem(legacySignedOutKey(this.project));\n } catch { /* ignore */ }\n }\n\n private removeBadge() {\n if (typeof document === \"undefined\") return;\n document.getElementById(\"__runapps_identity__\")?.remove();\n }\n\n /** Parse the #runapps_token=… fragment (or the older #runjobs_token=…),\n * install, then strip it. */\n private consumeFragment(): boolean {\n if (!location.hash || !/(^#|&)run(apps|jobs)_token=/.test(location.hash))\n return false;\n const params: Record<string, string> = {};\n location.hash\n .replace(/^#/, \"\")\n .split(\"&\")\n .forEach((kv) => {\n const i = kv.indexOf(\"=\");\n if (i >= 0) params[decodeURIComponent(kv.slice(0, i))] = decodeURIComponent(kv.slice(i + 1).replace(/\\+/g, \" \"));\n });\n const token = params.runapps_token || params.runjobs_token;\n if (!token) return false;\n let user: BrowserUser | undefined;\n if (params.user) {\n try {\n user = JSON.parse(params.user);\n } catch {\n /* malformed; ignore — token still works */\n }\n }\n this.setToken(\n token,\n parseInt(params.expires_at ?? \"\", 10) || 0,\n user,\n );\n // Clean fragment so a refresh doesn't re-consume.\n try {\n history.replaceState(null, \"\", location.pathname + location.search);\n } catch {\n /* ignore — non-fatal */\n }\n return true;\n }\n\n /** postMessage handshake with the dashboard parent (iframe path). */\n private requestTokenFromParent(): Promise<void> {\n return new Promise((resolve, reject) => {\n let done = false;\n const timers: ReturnType<typeof setTimeout>[] = [];\n const cleanup = () => {\n done = true;\n window.removeEventListener(\"message\", onMessage);\n for (const t of timers) clearTimeout(t);\n };\n const onMessage = (e: MessageEvent) => {\n if (e.origin !== this.origin) return;\n const data = e.data as\n | { type: string; token?: string; expires_at?: number; user?: BrowserUser }\n | null;\n if (data && (data.type === \"runapps:token\" || data.type === \"runjobs:token\") && data.token) {\n this.setToken(data.token, data.expires_at ?? 0, data.user);\n cleanup();\n resolve();\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n // postMessage is NOT queued: if the parent's React listener isn't\n // mounted at the instant of our first post, that single message is\n // lost forever and we'd time out → dead-end grant redirect. So\n // re-post a few times across the wait window; a late-mounting\n // parent still receives a request to answer. The parent also\n // pushes the token proactively on iframe load — belt-and-braces.\n const post = () => {\n if (done) return;\n try {\n window.parent.postMessage({ type: \"runapps:request-token\" }, this.origin);\n window.parent.postMessage({ type: \"runjobs:request-token\" }, this.origin); // older embedders\n } catch (err) {\n cleanup();\n reject(err);\n }\n };\n post();\n for (const d of [250, 750, 1500, 3000]) timers.push(setTimeout(post, d));\n\n // Longer overall window (5s vs the old 3s) gives a heavy bundle +\n // a cold parent room to complete the round trip before we give up.\n timers.push(\n setTimeout(() => {\n if (done) return;\n cleanup();\n reject(new Error(\"runapps: parent handshake timeout\"));\n }, 5000),\n );\n });\n }\n\n /** Floating identity badge — bottom-right pill showing the user.\n * Click opens the runapps.ai dashboard in a new tab so the user\n * can manage their account / billing without losing the bundle's\n * in-flight state. Uses `noopener,noreferrer` so the new tab\n * can't reach back into the bundle window via `window.opener`. */\n private renderBadge() {\n if (typeof document === \"undefined\" || !this.userInfo) return;\n const ID = \"__runapps_identity__\";\n const dashboardUrl = `${this.origin}/dashboard`;\n const ready = (cb: () => void) =>\n document.body ? cb() : document.addEventListener(\"DOMContentLoaded\", cb);\n ready(() => {\n // Idempotent: remove an older instance so successive renders\n // (e.g. after sign-in finishes) don't stack badges.\n document.getElementById(ID)?.remove();\n const root = mountActivityBadge({\n id: ID,\n user: this.userInfo as BrowserUser,\n dashboardUrl,\n tracker: this.tracker,\n position: this.badgePosition,\n // Bind through `this` so the popover's Sign-out button runs\n // BrowserAuth.signOut (clears token cache + flips signed-out\n // flag + dispatches the auth event); a plain method reference\n // would lose `this`.\n //\n // Then hard-reload the page. signOut() deliberately doesn't\n // navigate (its contract leaves that to the host bundle), but\n // the badge IS the SDK's own UI, and a user clicking its\n // \"Sign out\" expects to land back on the app's signed-out /\n // login view — not keep staring at a now-stale authed page.\n // The reload re-runs the bundle's bootstrap against a cleared\n // token + the sticky signed-out flag, so the app naturally\n // shows its login state. Guard window for non-browser hosts.\n onSignOut: () => {\n this.signOut();\n if (typeof window !== \"undefined\") window.location.reload();\n },\n });\n document.body.appendChild(root);\n });\n }\n}\n\n/* ============================================================ *\n * Activity Badge UI *\n * ============================================================ *\n *\n * Vanilla-DOM render of the bottom-right identity badge with a\n * desktop-ball-style real-time activity layer. Stays out of\n * BrowserAuth's main class body to keep auth state and DOM concerns\n * separate — auth owns \"who is signed in\", this owns \"what's\n * happening right now\". Three visual layers:\n *\n * 1. Avatar + name pill — the existing static badge.\n * 2. Progress ring (SVG) wrapping the avatar — animates speed\n * proportional to tokens/sec when a stream is active; fades\n * out at idle. Single in-flight call shows one rotating arc;\n * multi-call shows the avatar's outline glowing.\n * 3. LED dot at avatar's bottom-right — discrete colour signal:\n * grey (idle) / blue pulsing (active) / red (recent error) /\n * green fade (success-burst, 300ms after end).\n *\n * Clicking the badge toggles a popover showing the same data the\n * spec called for: Active / Recent / Session. Hover with 400ms\n * grace also opens it (matches macOS / Discord tray tooltips).\n *\n * Performance: the redraw loop pauses entirely when status==idle\n * AND the popover is closed (no rAF burn). When active, it\n * re-renders at ~6 Hz — fast enough for token rate to look live,\n * cheap enough to not warm a laptop fan.\n */\n\ninterface MountOpts {\n id: string;\n user: BrowserUser;\n dashboardUrl: string;\n tracker: ActivityTracker | null;\n position: BadgePosition;\n /** Fired when the user clicks the popover's \"Sign out\" link.\n * Wires up to BrowserAuth.signOut so the token cache is cleared\n * and the in-tab state flips to signed-out without a reload. */\n onSignOut: () => void;\n}\n\n/**\n * Per-corner CSS overrides for the floating badge + its popover.\n * `top` vs `bottom` flips the badge's vertical anchor AND the\n * popover's open direction (top-anchored badge → popover opens\n * downward); `right` vs `left` flips horizontal anchor on both.\n */\n/**\n * Inject the `@keyframes __runapps_led_pulse__` rule into the document\n * head ONCE (idempotent — re-mounts and multiple SDK instances on the\n * same page share the same rule). Must run at badge mount time so\n * the LED's `animation` CSS resolves the keyframe reference the\n * moment status flips from idle to active — previously this lived\n * inside createPopover() and only fired on first hover, leaving the\n * LED frozen until the user interacted.\n */\nfunction ensurePulseKeyframes(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(\"__runapps_kf__\")) return;\n const style = document.createElement(\"style\");\n style.id = \"__runapps_kf__\";\n style.textContent =\n \"@keyframes __runapps_led_pulse__ {\" +\n \" 0% { box-shadow: 0 0 0 0 rgba(59,130,246,0.7); }\" +\n \" 70% { box-shadow: 0 0 0 6px rgba(59,130,246,0); }\" +\n \" 100% { box-shadow: 0 0 0 0 rgba(59,130,246,0); }\" +\n \"}\" +\n // A sync that moved data: one green ring swells out of the avatar.\n \"@keyframes __runapps_sync_ping__ {\" +\n \" 0% { transform: scale(1); opacity: 0.9; }\" +\n \" 100% { transform: scale(2.1); opacity: 0; }\" +\n \"}\";\n (document.head ?? document.documentElement).appendChild(style);\n}\n\nfunction badgeCornerStyles(position: BadgePosition): {\n badge: string;\n popover: string;\n} {\n const v = position.startsWith(\"top\") ? \"top\" : \"bottom\";\n const h = position.endsWith(\"right\") ? \"right\" : \"left\";\n // Popover sits on the OPPOSITE vertical side of the badge so it\n // grows AWAY from the edge of the viewport, never offscreen.\n const popoverVerticalAnchor =\n v === \"bottom\" ? \"bottom:calc(100% + 8px);top:auto\" : \"top:calc(100% + 8px);bottom:auto\";\n const popoverHorizontalAnchor = h === \"right\" ? \"right:0;left:auto\" : \"left:0;right:auto\";\n return {\n badge: `${v}:16px;${h}:16px`,\n popover: `${popoverVerticalAnchor};${popoverHorizontalAnchor}`,\n };\n}\n\n// ─── Drag-to-move + position persistence ────────────────────────────\n//\n// Users sometimes want to move the floating badge — it overlaps a\n// site's own corner UI (chat widget, footer button, etc.). Drag to\n// reposition; the saved coords land in a cookie so reloads keep the\n// chosen spot. Cookie because the user asked for cookie; localStorage\n// would be the more typical choice for ~10 bytes of UI state, but\n// cookie is fine — small payload, same-site, no SDK dependency on a\n// storage permission.\n\n/** Cookie key the saved badge position lives under. URI-safe, prefixed\n * so other libraries / consumers don't accidentally collide. */\nconst BADGE_POS_COOKIE = \"__runapps_badge_pos__\";\nconst LEGACY_BADGE_POS_COOKIE = \"__runjobs_badge_pos__\";\n\n/** Pixel threshold a pointer must travel before we treat the gesture\n * as a drag instead of a click. Below this, it's a normal badge tap\n * that opens the popover. 5 px matches OS-level click slop tolerances. */\nconst BADGE_DRAG_THRESHOLD_PX = 5;\n\n/** Edge inset when clamping a dragged badge into the viewport — keeps\n * it visually inside the safe area on resize. Match the default\n * corner offset (`16px` in badgeCornerStyles). */\nconst BADGE_EDGE_INSET_PX = 8;\n\nfunction readBadgePosCookie(): { x: number; y: number } | null {\n if (typeof document === \"undefined\") return null;\n const cookies = document.cookie.split(\";\").map((s) => s.trim());\n let name = BADGE_POS_COOKIE;\n let raw = cookies.find((s) => s.startsWith(name + \"=\"));\n if (!raw) {\n name = LEGACY_BADGE_POS_COOKIE;\n raw = cookies.find((s) => s.startsWith(name + \"=\"));\n }\n if (!raw) return null;\n const value = decodeURIComponent(raw.slice(name.length + 1));\n const parts = value.split(\",\");\n const rawX = parts[0];\n const rawY = parts[1];\n if (parts.length !== 2 || rawX === undefined || rawY === undefined) return null;\n const x = Number.parseInt(rawX, 10);\n const y = Number.parseInt(rawY, 10);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return { x, y };\n}\n\nfunction writeBadgePosCookie(x: number, y: number): void {\n if (typeof document === \"undefined\") return;\n // 1-year expiry, root path, lax cross-site so the cookie survives\n // navigation between subdomains under the same site. Secure when\n // we're on HTTPS so iframes can read it back without warnings.\n const maxAge = 60 * 60 * 24 * 365;\n const secure = typeof window !== \"undefined\" && window.location?.protocol === \"https:\" ? \";Secure\" : \"\";\n document.cookie =\n `${BADGE_POS_COOKIE}=${encodeURIComponent(`${Math.round(x)},${Math.round(y)}`)};` +\n `path=/;max-age=${maxAge};SameSite=Lax${secure}`;\n}\n\n/** Snap (x, y) into the viewport so a saved position from a wider\n * screen doesn't render off-canvas after a window resize.\n * Coordinates are top-left of the badge element; w/h are its bounds. */\nfunction clampToViewport(\n x: number,\n y: number,\n w: number,\n h: number,\n): { x: number; y: number } {\n const vw = typeof window !== \"undefined\" ? window.innerWidth : 1024;\n const vh = typeof window !== \"undefined\" ? window.innerHeight : 768;\n const maxX = Math.max(BADGE_EDGE_INSET_PX, vw - w - BADGE_EDGE_INSET_PX);\n const maxY = Math.max(BADGE_EDGE_INSET_PX, vh - h - BADGE_EDGE_INSET_PX);\n return {\n x: Math.min(Math.max(BADGE_EDGE_INSET_PX, x), maxX),\n y: Math.min(Math.max(BADGE_EDGE_INSET_PX, y), maxY),\n };\n}\n\n/** Recompute the popover anchor CSS based on which quadrant of the\n * viewport the badge currently sits in. Mirrors badgeCornerStyles'\n * rule: popover grows AWAY from the nearer edge so it never escapes\n * the viewport. Used after a drag so the popover keeps opening\n * inward even when the badge is no longer in its starting corner. */\nfunction popoverCssForRect(rect: { left: number; top: number; width: number; height: number }): string {\n const vw = typeof window !== \"undefined\" ? window.innerWidth : 1024;\n const vh = typeof window !== \"undefined\" ? window.innerHeight : 768;\n const cx = rect.left + rect.width / 2;\n const cy = rect.top + rect.height / 2;\n const verticalAnchor =\n cy < vh / 2\n ? \"top:calc(100% + 8px);bottom:auto\"\n : \"bottom:calc(100% + 8px);top:auto\";\n const horizontalAnchor = cx < vw / 2 ? \"left:0;right:auto\" : \"right:0;left:auto\";\n return `${verticalAnchor};${horizontalAnchor}`;\n}\n\nfunction mountActivityBadge(opts: MountOpts): HTMLButtonElement {\n const { id, user, dashboardUrl, tracker, position, onSignOut } = opts;\n const corners = badgeCornerStyles(position);\n\n // `currentPopoverCss` starts at the corner-derived anchor and gets\n // overwritten when the user drags the badge to a new quadrant.\n // createPopover() reads this at popover instantiation time; if the\n // popover was already built before a drag, the drag handler also\n // patches popover.style directly so the next open uses the new anchor.\n let currentPopoverCss = corners.popover;\n // Inject the LED pulse keyframes at mount time. Previously this\n // lived inside createPopover() — which only runs on first hover —\n // so the LED's `animation` CSS referenced a keyframe rule that\n // didn't exist until the user hovered, and the browser silently\n // skipped the animation. Result: LED stayed solid until hover,\n // exactly matching the bug report.\n ensurePulseKeyframes();\n const el = document.createElement(\"button\");\n el.id = id;\n el.type = \"button\";\n el.setAttribute(\"aria-label\", \"RunApps activity status\");\n el.style.cssText = [\n \"position:fixed\",\n corners.badge,\n \"z-index:2147483647\",\n \"display:flex\",\n \"align-items:center\",\n \"justify-content:center\",\n \"padding:5px\",\n \"border-radius:50%\",\n \"font:500 12px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif\",\n \"color:#fff\",\n \"background:rgba(15,15,20,0.55)\",\n \"backdrop-filter:blur(10px) saturate(1.2)\",\n \"-webkit-backdrop-filter:blur(10px) saturate(1.2)\",\n \"border:1px solid rgba(255,255,255,0.12)\",\n \"box-shadow:0 4px 16px rgba(0,0,0,0.15)\",\n \"user-select:none\",\n \"-webkit-user-select:none\",\n // Touch: without this the browser claims the first finger move as a\n // page scroll and fires pointercancel, so the badge could not be\n // dragged on phones. No callout either: a long press is a drag.\n \"touch-action:none\",\n \"-webkit-touch-callout:none\",\n \"overflow:visible\", // popover spills out the top\n \"cursor:pointer\",\n \"appearance:none\",\n \"-webkit-appearance:none\",\n \"transition:background-color 120ms ease-out, box-shadow 120ms ease-out\",\n ].join(\";\");\n el.addEventListener(\"mouseenter\", () => {\n el.style.backgroundColor = \"rgba(30,30,40,0.7)\";\n el.style.boxShadow = \"0 6px 20px rgba(0,0,0,0.22)\";\n });\n el.addEventListener(\"mouseleave\", () => {\n el.style.backgroundColor = \"rgba(15,15,20,0.55)\";\n el.style.boxShadow = \"0 4px 16px rgba(0,0,0,0.15)\";\n });\n\n // ─── Avatar with ring + LED ────────────────────────────────\n const avatarWrap = document.createElement(\"span\");\n avatarWrap.style.cssText = [\n \"position:relative\",\n \"width:26px;height:26px\",\n \"flex-shrink:0\",\n \"display:inline-flex;align-items:center;justify-content:center\",\n ].join(\";\");\n\n // Progress ring (SVG). Hidden at idle so we don't paint when\n // nothing's happening. Stroke offset / dasharray animate via\n // direct style writes from the redraw loop.\n const ringNS = \"http://www.w3.org/2000/svg\";\n const ring = document.createElementNS(ringNS, \"svg\");\n ring.setAttribute(\"viewBox\", \"0 0 26 26\");\n ring.setAttribute(\"width\", \"26\");\n ring.setAttribute(\"height\", \"26\");\n ring.style.cssText = [\n \"position:absolute;inset:0\",\n \"pointer-events:none\",\n \"opacity:0\",\n \"transition:opacity 200ms ease-out\",\n ].join(\";\");\n const ringTrack = document.createElementNS(ringNS, \"circle\");\n ringTrack.setAttribute(\"cx\", \"13\");\n ringTrack.setAttribute(\"cy\", \"13\");\n ringTrack.setAttribute(\"r\", \"12\");\n ringTrack.setAttribute(\"fill\", \"none\");\n ringTrack.setAttribute(\"stroke\", \"rgba(255,255,255,0.08)\");\n ringTrack.setAttribute(\"stroke-width\", \"1.5\");\n const ringArc = document.createElementNS(ringNS, \"circle\");\n ringArc.setAttribute(\"cx\", \"13\");\n ringArc.setAttribute(\"cy\", \"13\");\n ringArc.setAttribute(\"r\", \"12\");\n ringArc.setAttribute(\"fill\", \"none\");\n ringArc.setAttribute(\"stroke\", \"#3b82f6\");\n ringArc.setAttribute(\"stroke-width\", \"2\");\n ringArc.setAttribute(\"stroke-linecap\", \"round\");\n // Rotate origin = center, start arc from 12 o'clock so progress\n // sweeps clockwise like every progress UI on the planet.\n ringArc.setAttribute(\"transform\", \"rotate(-90 13 13)\");\n // Circumference = 2πr ≈ 75.4. dasharray=circumference + dashoffset\n // drives the arc length.\n const RING_CIRC = 2 * Math.PI * 12;\n ringArc.setAttribute(\"stroke-dasharray\", String(RING_CIRC));\n ringArc.setAttribute(\"stroke-dashoffset\", String(RING_CIRC));\n ring.appendChild(ringTrack);\n ring.appendChild(ringArc);\n\n const avatar = document.createElement(\"div\");\n avatar.textContent = (user.name || \"?\").charAt(0).toUpperCase();\n avatar.style.cssText = [\n \"width:22px;height:22px;border-radius:50%;flex-shrink:0\",\n \"display:flex;align-items:center;justify-content:center\",\n \"background:linear-gradient(135deg,#3b82f6,#8b5cf6)\",\n \"font-size:11px;font-weight:600;color:#fff\",\n \"position:relative\",\n ].join(\";\");\n\n // LED status dot — absolute-positioned over the avatar's\n // bottom-right corner. Same trick Discord uses for online status.\n const led = document.createElement(\"span\");\n led.style.cssText = [\n \"position:absolute\",\n \"bottom:-1px;right:-1px\",\n \"width:7px;height:7px;border-radius:50%\",\n \"background:rgba(255,255,255,0.2)\",\n \"border:1.5px solid rgba(15,15,20,1)\",\n \"box-shadow:0 0 0 0 transparent\",\n // Hidden at rest: it only appears while something is happening\n // (syncing, offline, an error, the green flash after a sync).\n \"opacity:0\",\n \"transition:background-color 150ms ease-out, box-shadow 200ms ease-out, opacity 200ms ease-out\",\n \"pointer-events:none\",\n ].join(\";\");\n\n avatarWrap.appendChild(ring);\n avatarWrap.appendChild(avatar);\n avatarWrap.appendChild(led);\n\n el.appendChild(avatarWrap);\n\n // ─── Popover (lazy, append on first open) ──────────────────\n //\n // Open / close has two modes:\n // - hover: 400ms-grace open on badge enter; 300ms-grace close\n // when neither badge NOR popover is hovered. The grace lets\n // the user cross the 8px gap between badge and popover without\n // losing the popover. Standard tooltip pattern.\n // - click: sticky — stays open until the user clicks outside.\n // Useful for reading + copying values out of the panel.\n let popover: HTMLDivElement | null = null;\n let popoverOpen = false;\n let popoverOpenedBy: \"hover\" | \"click\" | null = null;\n let openTimer: number | null = null;\n let closeTimer: number | null = null;\n\n const cancelOpen = () => {\n if (openTimer !== null) {\n window.clearTimeout(openTimer);\n openTimer = null;\n }\n };\n const cancelClose = () => {\n if (closeTimer !== null) {\n window.clearTimeout(closeTimer);\n closeTimer = null;\n }\n };\n const scheduleClose = () => {\n cancelClose();\n closeTimer = window.setTimeout(() => {\n // Re-check the mode — a click between schedule and fire turns\n // a hover-pending close into a sticky open.\n if (popoverOpenedBy === \"hover\") closePopover();\n }, 300);\n };\n\n const openPopover = (by: \"hover\" | \"click\") => {\n cancelOpen();\n cancelClose();\n if (popoverOpen) {\n // Upgrade a hover-open to click-stickiness if the user\n // clicked while the popover was already hover-shown.\n if (by === \"click\") popoverOpenedBy = \"click\";\n return;\n }\n if (!popover) {\n popover = createPopover(user, dashboardUrl, currentPopoverCss, () => {\n // Tear down our DOM before the auth flip runs so the popover\n // doesn't briefly show \"signed out\" state to a user who's\n // about to navigate away. closePopover() also removes the\n // hover/click handlers' state.\n closePopover();\n onSignOut();\n });\n // Attach hover handlers ONCE — the popover element is reused\n // across open/close cycles, so .remove() / .appendChild()\n // doesn't shake the listeners off.\n popover.addEventListener(\"mouseenter\", cancelClose);\n popover.addEventListener(\"mouseleave\", () => {\n if (popoverOpenedBy === \"hover\") scheduleClose();\n });\n }\n el.appendChild(popover);\n popoverOpen = true;\n popoverOpenedBy = by;\n // Schedule one immediate redraw so the popover shows current\n // state, not a stale snapshot from the last close.\n requestRedraw();\n };\n\n const closePopover = () => {\n cancelClose();\n if (!popoverOpen) return;\n popover?.remove();\n popoverOpen = false;\n popoverOpenedBy = null;\n };\n\n el.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n popoverOpen ? closePopover() : openPopover(\"click\");\n });\n // Hover-open with 400ms delay — matches macOS tray tooltips.\n el.addEventListener(\"mouseenter\", () => {\n cancelClose(); // moving back onto badge cancels a pending close\n cancelOpen();\n openTimer = window.setTimeout(() => {\n if (!popoverOpen) openPopover(\"hover\");\n }, 400);\n });\n el.addEventListener(\"mouseleave\", () => {\n cancelOpen();\n // Only the hover branch auto-closes; click-opened stays sticky\n // and dismisses via the click-outside handler below.\n if (popoverOpenedBy === \"hover\") scheduleClose();\n });\n // Click anywhere outside the badge closes the popover.\n document.addEventListener(\"click\", (e) => {\n if (popoverOpen && !el.contains(e.target as Node)) closePopover();\n });\n\n // ─── Drag-to-move + cookie persistence ─────────────────────\n //\n // Two intents share the pointer-down on the badge:\n // 1. Quick tap → open popover (existing click handler).\n // 2. Hold-and-drag → reposition the badge.\n //\n // Disambiguate by movement distance: until the pointer travels\n // BADGE_DRAG_THRESHOLD_PX from its starting point we treat the\n // gesture as a pending click and let the existing click handler\n // run on pointerup. Past that threshold we flip to drag mode —\n // start moving the badge, claim the pointer via setPointerCapture\n // so we keep getting moves even when the pointer leaves the badge\n // bounds, and on pointerup persist the final coords + suppress\n // the click event the browser would otherwise synthesize.\n let dragStartClientX = 0;\n let dragStartClientY = 0;\n let dragStartBadgeLeft = 0;\n let dragStartBadgeTop = 0;\n let pointerDown = false;\n let isDragging = false;\n let suppressNextClick = false;\n\n /** Apply absolute pixel position to the badge, clearing the corner\n * anchors set at mount. Used both by the drag handler (live update)\n * and the cookie-restore path (mount-time hydrate). */\n const applyAbsolutePos = (x: number, y: number) => {\n el.style.left = `${x}px`;\n el.style.top = `${y}px`;\n el.style.right = \"auto\";\n el.style.bottom = \"auto\";\n };\n\n /** Recompute + apply the popover anchor based on the badge's current\n * position in the viewport. Called after a drag so the popover\n * always grows away from the nearest viewport edge. If the popover\n * was already instantiated, patches its inline style in place. */\n const recomputePopoverAnchor = () => {\n const r = el.getBoundingClientRect();\n currentPopoverCss = popoverCssForRect({\n left: r.left,\n top: r.top,\n width: r.width,\n height: r.height,\n });\n if (popover) {\n // Reset the four anchor properties before re-applying so we\n // don't leave stale `right` / `bottom` declarations stuck on\n // the element from the previous anchor.\n popover.style.top = \"\";\n popover.style.bottom = \"\";\n popover.style.left = \"\";\n popover.style.right = \"\";\n for (const decl of currentPopoverCss.split(\";\")) {\n const [prop, val] = decl.split(\":\");\n if (prop && val) popover.style.setProperty(prop.trim(), val.trim());\n }\n }\n };\n\n el.addEventListener(\"pointerdown\", (e) => {\n // Only the primary button drags — secondary clicks should fall\n // through (browsers may show a context menu, etc.).\n if (e.button !== 0) return;\n pointerDown = true;\n isDragging = false;\n dragStartClientX = e.clientX;\n dragStartClientY = e.clientY;\n const r = el.getBoundingClientRect();\n dragStartBadgeLeft = r.left;\n dragStartBadgeTop = r.top;\n });\n\n el.addEventListener(\"pointermove\", (e) => {\n if (!pointerDown) return;\n const dx = e.clientX - dragStartClientX;\n const dy = e.clientY - dragStartClientY;\n if (!isDragging) {\n if (Math.abs(dx) < BADGE_DRAG_THRESHOLD_PX && Math.abs(dy) < BADGE_DRAG_THRESHOLD_PX) {\n return; // still within click slop — keep treating as a tap\n }\n // Cross the threshold → enter drag mode. Capture the pointer\n // so further moves arrive even when the pointer escapes the\n // badge bounds (fast drags leave the small pill quickly).\n isDragging = true;\n try {\n el.setPointerCapture(e.pointerId);\n } catch {\n /* no active pointer of that id (synthetic events, odd WebViews): drag still works while the finger stays on the badge */\n }\n el.style.cursor = \"grabbing\";\n // Pre-emptively close any open popover — keeping it pinned to a\n // moving badge looks janky and a drag is clearly a different\n // intent than \"look at the popover\".\n if (popoverOpen) closePopover();\n }\n const rect = el.getBoundingClientRect();\n const clamped = clampToViewport(\n dragStartBadgeLeft + dx,\n dragStartBadgeTop + dy,\n rect.width,\n rect.height,\n );\n applyAbsolutePos(clamped.x, clamped.y);\n });\n\n const endDrag = (e: PointerEvent) => {\n if (!pointerDown) return;\n pointerDown = false;\n if (!isDragging) return;\n isDragging = false;\n el.style.cursor = \"pointer\";\n try {\n if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);\n } catch {\n /* same */\n }\n // Persist final position. Re-read the rect so what we store is\n // exactly what's on screen — clampToViewport may have shaved\n // the trailing few pixels.\n const r = el.getBoundingClientRect();\n writeBadgePosCookie(r.left, r.top);\n recomputePopoverAnchor();\n // The browser will synthesize a click on the badge from this\n // pointer sequence (pointer went down + up on the same element).\n // Swallow it so a drag doesn't immediately open the popover.\n suppressNextClick = true;\n };\n el.addEventListener(\"pointerup\", endDrag);\n el.addEventListener(\"pointercancel\", endDrag);\n\n // Capture-phase click swallower — runs BEFORE the existing click\n // handler that toggles the popover, so we can stop a drag-tail\n // click without modifying the open/close logic itself.\n el.addEventListener(\n \"click\",\n (e) => {\n if (suppressNextClick) {\n suppressNextClick = false;\n e.stopPropagation();\n e.preventDefault();\n }\n },\n true,\n );\n\n // Hydrate from cookie. After all the listeners and the corner-based\n // default style are in place, fast-forward to the user's last saved\n // position if any. Clamp to the current viewport so a saved spot\n // from a wider monitor doesn't put the badge off-canvas.\n const savedPos = readBadgePosCookie();\n if (savedPos) {\n // We need el's measured size to clamp; but el isn't in the DOM\n // yet, so we lean on a defer to next frame after first append.\n // Until then, apply the raw saved coords (the corner-default\n // styles still apply if these are absurd; clamp will fix it).\n applyAbsolutePos(savedPos.x, savedPos.y);\n // Queue a one-shot post-mount clamp + popover-anchor recompute.\n // requestAnimationFrame fires after the parent appendChild\n // brings the badge into layout, so getBoundingClientRect works.\n requestAnimationFrame(() => {\n const r = el.getBoundingClientRect();\n const clamped = clampToViewport(savedPos.x, savedPos.y, r.width, r.height);\n applyAbsolutePos(clamped.x, clamped.y);\n recomputePopoverAnchor();\n });\n }\n\n // ─── Redraw loop ───────────────────────────────────────────\n //\n // Idle: zero work. Active OR popover open: redraw at ~6 Hz so\n // the ring sweep and per-call elapsed counters stay live without\n // burning CPU. Token-arrival events also push a redraw out of\n // band so the ring jumps as soon as a chunk lands.\n let rafScheduled = false;\n let intervalHandle: number | null = null;\n let seenChangeAt: number | undefined = undefined;\n\n const reconcile = () => {\n rafScheduled = false;\n if (!tracker) {\n // No event bus → static badge. Hide ring + LED.\n ring.style.opacity = \"0\";\n led.style.background = \"rgba(255,255,255,0.2)\";\n led.style.opacity = \"0\";\n return;\n }\n const snap = tracker.snapshot();\n // lastChangeAt is only ever set by a sync in this page's lifetime,\n // so every new value is a change the user just saw happen.\n const changeAt = snap.sync?.lastChangeAt;\n if (changeAt && changeAt !== seenChangeAt) {\n seenChangeAt = changeAt;\n syncedPing(avatarWrap, led);\n }\n updateRing(ring, ringArc, RING_CIRC, snap);\n updateLED(led, snap);\n if (popoverOpen && popover) updatePopover(popover, snap);\n // Schedule the next paint only if there's reason. When the\n // popover is open we want the elapsed seconds to tick; when\n // active we want the ring to keep sweeping.\n const needsLoop = snap.status !== \"idle\" || popoverOpen;\n if (needsLoop && intervalHandle === null) {\n intervalHandle = window.setInterval(requestRedraw, 160);\n } else if (!needsLoop && intervalHandle !== null) {\n window.clearInterval(intervalHandle);\n intervalHandle = null;\n }\n };\n\n function requestRedraw() {\n if (rafScheduled) return;\n rafScheduled = true;\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(reconcile);\n } else {\n setTimeout(reconcile, 0);\n }\n }\n\n // Drive redraws off the tracker's onChange — fires synchronously\n // after every start / delta / end / error so the LED + ring update\n // within an animation frame of the event, not when the user\n // happens to hover the badge. requestRedraw() coalesces multiple\n // notifications into one rAF paint, so a chunky stream (30+ deltas\n // per second) still paints at most once per frame.\n if (tracker) {\n tracker.onChange(requestRedraw);\n }\n\n // Initial paint so the user sees their LED/ring state at mount\n // even before any event fires.\n requestRedraw();\n\n return el;\n}\n\nfunction updateRing(\n ring: SVGSVGElement,\n arc: SVGCircleElement,\n circ: number,\n snap: ActivitySnapshot,\n): void {\n if (snap.active.length === 0) {\n ring.style.opacity = \"0\";\n return;\n }\n ring.style.opacity = \"1\";\n // Pick the most informative active call for the ring (highest\n // tokens/sec, falling back to oldest). The popover lists every\n // call individually; this is just for the at-a-glance glow.\n const primary = snap.active.reduce((best, c) => (c.tokensPerSec > best.tokensPerSec ? c : best), snap.active[0]!);\n if (primary.streaming && primary.tokensPerSec > 0) {\n // Active streaming → arc length scales with tokens/sec, capped\n // at the full ring at ~60 tok/s (typical chat ceiling).\n const fraction = Math.min(primary.tokensPerSec / 60, 1);\n arc.setAttribute(\"stroke-dashoffset\", String(circ * (1 - fraction)));\n arc.setAttribute(\"stroke\", \"#3b82f6\");\n } else if (snap.status === \"error\") {\n arc.setAttribute(\"stroke-dashoffset\", \"0\");\n arc.setAttribute(\"stroke\", \"#ef4444\");\n } else {\n // Non-streaming or pre-first-token: indeterminate sweep — just\n // show a quarter-arc that rotates via CSS keyframes (added\n // below as a one-shot class).\n arc.setAttribute(\"stroke-dashoffset\", String(circ * 0.75));\n arc.setAttribute(\"stroke\", \"#a78bfa\");\n }\n}\n\n/** Until when the LED shows green after a sync that moved data. */\nlet syncFlashUntil = 0;\n\n/**\n * A sync just pushed or applied something: a green ring swells out of\n * the avatar once, and the LED holds green for a moment. Quiet syncs\n * (nothing to move) show nothing — the badge would otherwise blink\n * every minute.\n */\nfunction syncedPing(avatarWrap: HTMLElement, led: HTMLSpanElement): void {\n const ping = document.createElement(\"span\");\n ping.style.cssText = [\n \"position:absolute;inset:0\",\n \"border-radius:50%\",\n \"border:2px solid #10b981\",\n \"pointer-events:none\",\n \"animation:__runapps_sync_ping__ 700ms ease-out forwards\",\n ].join(\";\");\n ping.addEventListener(\"animationend\", () => ping.remove());\n avatarWrap.appendChild(ping);\n setTimeout(() => ping.remove(), 1000); // in case animationend never fires\n syncFlashUntil = Date.now() + 1200;\n led.style.opacity = \"1\";\n led.style.background = \"#10b981\";\n led.style.boxShadow = \"0 0 0 2px rgba(16,185,129,0.35)\";\n led.style.animation = \"\";\n setTimeout(() => {\n // Back to the resting colour once the flash is over.\n if (Date.now() >= syncFlashUntil) {\n led.style.background = \"rgba(255,255,255,0.2)\";\n led.style.opacity = \"0\";\n led.style.boxShadow = \"0 0 0 0 transparent\";\n }\n }, 1250);\n}\n\nfunction updateLED(led: HTMLSpanElement, snap: ActivitySnapshot): void {\n led.style.opacity = \"1\"; // the resting branches below hide it again\n switch (snap.status) {\n case \"idle\": {\n // Nothing in flight: the LED shows the sync instead — blue pulse\n // while syncing, amber while changes wait offline or after a\n // failed attempt, grey when everything is on the server.\n const sync = snap.sync;\n if (Date.now() < syncFlashUntil) {\n led.style.background = \"#10b981\";\n led.style.boxShadow = \"0 0 0 2px rgba(16,185,129,0.35)\";\n led.style.animation = \"\";\n } else if (sync && sync.state === \"syncing\") {\n led.style.background = \"#3b82f6\";\n led.style.boxShadow = \"0 0 0 0 rgba(59,130,246,0.7)\";\n led.style.animation = \"__runapps_led_pulse__ 1.2s ease-out infinite\";\n } else if (sync && (sync.state === \"offline\" || sync.state === \"error\")) {\n led.style.background = \"#f59e0b\";\n led.style.boxShadow = \"0 0 0 2px rgba(245,158,11,0.3)\";\n led.style.animation = \"\";\n } else {\n led.style.background = \"rgba(255,255,255,0.2)\";\n led.style.opacity = \"0\";\n led.style.boxShadow = \"0 0 0 0 transparent\";\n led.style.animation = \"\";\n }\n break;\n }\n case \"active\":\n led.style.background = \"#3b82f6\";\n // CSS-driven pulse via box-shadow ring expanding outward —\n // single keyframe loop, cheaper than animating opacity.\n led.style.boxShadow = \"0 0 0 0 rgba(59,130,246,0.7)\";\n led.style.animation = \"__runapps_led_pulse__ 1.2s ease-out infinite\";\n break;\n case \"error\":\n led.style.background = \"#ef4444\";\n led.style.boxShadow = \"0 0 0 2px rgba(239,68,68,0.3)\";\n led.style.animation = \"\";\n break;\n }\n}\n\nfunction createPopover(user: BrowserUser, dashboardUrl: string, anchorCss: string, onSignOut: () => void): HTMLDivElement {\n const pop = document.createElement(\"div\");\n pop.style.cssText = [\n \"position:absolute\",\n // Anchor varies by badge corner — see badgeCornerStyles. Splits\n // top/bottom + left/right so the popover always grows TOWARDS\n // the viewport interior.\n anchorCss,\n \"min-width:280px;max-width:340px\",\n \"padding:10px 12px\",\n \"border-radius:10px\",\n \"background:rgba(15,15,20,0.92)\",\n \"backdrop-filter:blur(20px) saturate(1.3)\",\n \"-webkit-backdrop-filter:blur(20px) saturate(1.3)\",\n \"border:1px solid rgba(255,255,255,0.12)\",\n \"box-shadow:0 10px 30px rgba(0,0,0,0.4)\",\n \"color:#fff\",\n \"font:400 11px/1.4 -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif\",\n \"cursor:default\",\n \"text-align:left\",\n ].join(\";\");\n pop.setAttribute(\"data-runapps-popover\", \"1\");\n pop.addEventListener(\"click\", (e) => e.stopPropagation());\n\n // Who is signed in. The badge itself is only the avatar circle, so\n // the full name lives here, above the activity sections.\n const header = document.createElement(\"div\");\n header.style.cssText = [\n \"display:flex;align-items:center;gap:8px\",\n \"margin-bottom:10px;padding-bottom:8px\",\n \"border-bottom:1px solid rgba(255,255,255,0.08)\",\n ].join(\";\");\n const headAvatar = document.createElement(\"div\");\n headAvatar.textContent = (user.name || \"?\").charAt(0).toUpperCase();\n headAvatar.style.cssText = [\n \"width:24px;height:24px;border-radius:50%;flex-shrink:0\",\n \"display:flex;align-items:center;justify-content:center\",\n \"background:linear-gradient(135deg,#3b82f6,#8b5cf6)\",\n \"font-size:11px;font-weight:600;color:#fff\",\n ].join(\";\");\n const headName = document.createElement(\"div\");\n headName.textContent = user.name || \"Signed in\";\n headName.title = user.name || \"\";\n headName.style.cssText = \"font-size:12px;font-weight:500;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\";\n const headSub = document.createElement(\"div\");\n headSub.textContent = \"Signed in with RunApps\";\n headSub.style.cssText = \"font-size:10px;color:rgba(255,255,255,0.5)\";\n const headText = document.createElement(\"div\");\n headText.style.cssText = \"min-width:0\";\n headText.appendChild(headName);\n headText.appendChild(headSub);\n header.appendChild(headAvatar);\n header.appendChild(headText);\n pop.appendChild(header);\n\n // The popover's content is rebuilt on every redraw to keep DOM\n // diffing simple. ~3 sections × ~6 children = ~20 nodes, no\n // measurable cost at the 6 Hz redraw rate.\n const inner = document.createElement(\"div\");\n inner.setAttribute(\"data-runapps-popover-content\", \"1\");\n pop.appendChild(inner);\n\n const footer = document.createElement(\"div\");\n footer.style.cssText = [\n \"margin-top:10px;padding-top:8px\",\n \"border-top:1px solid rgba(255,255,255,0.08)\",\n \"display:flex;justify-content:space-between;align-items:center\",\n \"font-size:10px;color:rgba(255,255,255,0.6)\",\n ].join(\";\");\n const dash = document.createElement(\"a\");\n dash.textContent = \"Open dashboard →\";\n dash.href = dashboardUrl;\n dash.target = \"_blank\";\n dash.rel = \"noopener noreferrer\";\n dash.style.cssText = \"color:#a78bfa;text-decoration:none\";\n footer.appendChild(dash);\n\n // Sign-out lives opposite the dashboard link. Styled subtle on\n // purpose — it's a destructive-ish action (drops the token cache,\n // forces the next API call into the signIn redirect) but not\n // dangerous, and we don't want it to compete visually with the\n // dashboard CTA. Hover highlights it red as a \"this is the action\n // that ends your session\" cue.\n const signOutBtn = document.createElement(\"button\");\n signOutBtn.type = \"button\";\n signOutBtn.textContent = \"Sign out\";\n signOutBtn.style.cssText = [\n \"background:none;border:0;padding:0;margin:0\",\n \"font:inherit;cursor:pointer\",\n \"color:rgba(255,255,255,0.55)\",\n \"transition:color 120ms ease\",\n ].join(\";\");\n signOutBtn.addEventListener(\"mouseenter\", () => {\n signOutBtn.style.color = \"#f87171\";\n });\n signOutBtn.addEventListener(\"mouseleave\", () => {\n signOutBtn.style.color = \"rgba(255,255,255,0.55)\";\n });\n signOutBtn.addEventListener(\"click\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n onSignOut();\n });\n footer.appendChild(signOutBtn);\n\n pop.appendChild(footer);\n return pop;\n}\n\nfunction updatePopover(pop: HTMLDivElement, snap: ActivitySnapshot): void {\n const inner = pop.querySelector('[data-runapps-popover-content]') as HTMLDivElement | null;\n if (!inner) return;\n inner.innerHTML = \"\";\n const now = Date.now();\n inner.appendChild(renderActiveSection(snap, now));\n inner.appendChild(renderRecentSection(snap, now));\n if (snap.sync) inner.appendChild(renderSyncSection(snap.sync, now));\n inner.appendChild(renderSessionSection(snap.session, now));\n}\n\nfunction renderSyncSection(sync: NonNullable<ActivitySnapshot[\"sync\"]>, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.style.marginBottom = \"10px\";\n const hint = sync.lastSyncedAt ? `synced ${relativeTime(now - sync.lastSyncedAt)}` : \"—\";\n sec.appendChild(sectionLabel(\"Sync\", hint));\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:8px;padding:3px 0;font-size:10px\";\n const left = document.createElement(\"span\");\n const dot = document.createElement(\"span\");\n dot.textContent = \"● \";\n const label: Record<string, [string, string]> = {\n off: [\"Not signed in\", \"rgba(255,255,255,0.4)\"],\n idle: [\"Up to date\", \"#10b981\"],\n syncing: [\"Syncing…\", \"#3b82f6\"],\n offline: [\"Offline\", \"#f59e0b\"],\n error: [\"Retrying\", \"#f59e0b\"],\n };\n const [text, color] = label[sync.state] ?? [\"\", \"#fff\"];\n dot.style.color = color;\n left.style.color = \"#fff\";\n left.appendChild(dot);\n left.appendChild(document.createTextNode(text));\n const right = document.createElement(\"span\");\n right.style.cssText = \"color:rgba(255,255,255,0.7);font-variant-numeric:tabular-nums\";\n right.textContent = sync.pending > 0 ? `${sync.pending} pending` : \"\";\n right.title = sync.error ?? \"\";\n row.appendChild(left);\n row.appendChild(right);\n sec.appendChild(row);\n return sec;\n}\n\nfunction renderActiveSection(snap: ActivitySnapshot, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.style.marginBottom = \"10px\";\n sec.appendChild(sectionLabel(\"Active\", snap.active.length > 0 ? `${snap.active.length} in flight` : \"—\"));\n if (snap.active.length === 0) {\n const empty = document.createElement(\"div\");\n empty.style.cssText = \"color:rgba(255,255,255,0.4);font-size:10px;padding:2px 0\";\n empty.textContent = \"No calls in flight.\";\n sec.appendChild(empty);\n return sec;\n }\n for (const c of snap.active) {\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:8px;padding:3px 0\";\n const left = document.createElement(\"span\");\n left.style.cssText = \"color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px\";\n left.textContent = c.model;\n const right = document.createElement(\"span\");\n right.style.cssText = \"color:rgba(255,255,255,0.7);font-variant-numeric:tabular-nums;font-size:10px\";\n const elapsed = ((now - c.startedAt) / 1000).toFixed(1);\n const rate = c.tokensPerSec > 0 ? ` · ${Math.round(c.tokensPerSec)} tok/s` : \"\";\n right.textContent = `${elapsed}s${rate}`;\n row.appendChild(left);\n row.appendChild(right);\n sec.appendChild(row);\n }\n return sec;\n}\n\nfunction renderRecentSection(snap: ActivitySnapshot, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.style.marginBottom = \"10px\";\n sec.appendChild(sectionLabel(\"Recent\", snap.recent.length > 0 ? `last ${Math.min(snap.recent.length, 5)}` : \"—\"));\n if (snap.recent.length === 0) {\n const empty = document.createElement(\"div\");\n empty.style.cssText = \"color:rgba(255,255,255,0.4);font-size:10px;padding:2px 0\";\n empty.textContent = \"Nothing yet this session.\";\n sec.appendChild(empty);\n return sec;\n }\n for (const c of snap.recent.slice(0, 5)) {\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:8px;padding:3px 0\";\n const left = document.createElement(\"span\");\n left.style.cssText = \"color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px\";\n // Prefix bullet by status — cheap visual scan.\n const bullet = document.createElement(\"span\");\n bullet.textContent = c.ok ? \"✓ \" : \"✗ \";\n bullet.style.color = c.ok ? \"#10b981\" : \"#ef4444\";\n left.appendChild(bullet);\n left.appendChild(document.createTextNode(c.model));\n const right = document.createElement(\"span\");\n right.style.cssText = \"color:rgba(255,255,255,0.7);font-variant-numeric:tabular-nums;font-size:10px\";\n const latency = `${(c.latencyMs / 1000).toFixed(1)}s`;\n const cost = c.costUSD !== undefined ? ` · $${c.costUSD.toFixed(4)}` : \"\";\n const ago = ` · ${relativeTime(now - c.endedAt)}`;\n right.textContent = `${latency}${cost}${ago}`;\n row.appendChild(left);\n row.appendChild(right);\n sec.appendChild(row);\n }\n return sec;\n}\n\nfunction renderSessionSection(s: SessionStats, now: number): HTMLDivElement {\n const sec = document.createElement(\"div\");\n sec.appendChild(sectionLabel(\"Session\", relativeTime(now - s.startedAt)));\n const row = document.createElement(\"div\");\n row.style.cssText = \"display:flex;justify-content:space-between;gap:12px;color:rgba(255,255,255,0.85);font-size:10px\";\n const calls = document.createElement(\"span\");\n calls.textContent = `${s.totalCalls} call${s.totalCalls === 1 ? \"\" : \"s\"}`;\n const cost = document.createElement(\"span\");\n cost.textContent = `$${s.totalCostUSD.toFixed(4)}`;\n const errors = document.createElement(\"span\");\n errors.textContent = s.errorCount > 0 ? `${s.errorCount} error${s.errorCount === 1 ? \"\" : \"s\"}` : \"0 errors\";\n if (s.errorCount > 0) errors.style.color = \"#ef4444\";\n row.appendChild(calls);\n row.appendChild(cost);\n row.appendChild(errors);\n sec.appendChild(row);\n return sec;\n}\n\nfunction sectionLabel(title: string, hint: string): HTMLDivElement {\n const lbl = document.createElement(\"div\");\n lbl.style.cssText = [\n \"display:flex;justify-content:space-between;align-items:baseline\",\n \"margin-bottom:4px\",\n \"color:rgba(255,255,255,0.55)\",\n \"font-size:10px;text-transform:uppercase;letter-spacing:0.05em\",\n ].join(\";\");\n const t = document.createElement(\"span\");\n t.textContent = title;\n const h = document.createElement(\"span\");\n h.textContent = hint;\n h.style.cssText = \"font-size:9px;letter-spacing:0\";\n lbl.appendChild(t);\n lbl.appendChild(h);\n return lbl;\n}\n\nfunction relativeTime(ms: number): string {\n if (ms < 1500) return \"just now\";\n if (ms < 60_000) return `${Math.round(ms / 1000)}s ago`;\n if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m ago`;\n return `${Math.round(ms / 3_600_000)}h ago`;\n}\n","// sync/hook.ts — observe an app's IndexedDB writes without the app\n// knowing.\n//\n// There is no browser event for \"an object store changed\", so the SDK\n// wraps the few prototype methods every write goes through — on the\n// factory, the database, object stores and cursors. Dexie, idb,\n// idb-keyval, zustand's IndexedDB storage and hand-written code all end\n// up in these methods, so they are all observed.\n//\n// What the hook does:\n// open() returns a request that forwards the real one's\n// events, and delays `success` until `onOpen` (the\n// engine's first pull) has run, so a new device sees\n// its data on the first read.\n// transaction() attaches a change collector to readwrite\n// transactions on observed databases.\n// put/add/delete/clear, cursor.update/delete\n// record the committed change into the collector; the\n// batch is handed to `onCommit` only when the\n// transaction completes, so an aborted write is never\n// synced. A put also reads the record's previous value\n// first (same transaction), so the engine can tell which\n// fields the write changed and merge field by field.\n//\n// Writes the engine itself makes while applying remote changes go\n// through its own connection, which is never tagged, so they are not\n// recorded again.\n//\n// Several subscribers (engines) can be installed at once; each sees the\n// databases its `include` admits, on the factory it was bound to (tests\n// run one \"device\" per factory; a page has one factory).\n\nimport { extractKey } from \"./codec.js\";\n\nexport type CommittedOp =\n | { store: string; kind: \"put\"; key: IDBValidKey; value: unknown; prior?: unknown }\n | { store: string; kind: \"del\"; key: IDBValidKey }\n | { store: string; kind: \"clear\" };\n\nexport interface HookOptions {\n /** Which databases (by name) to observe. */\n include: (dbName: string) => boolean;\n /** Stores to leave out of sync. */\n excludeStore: (dbName: string, store: string) => boolean;\n /** A readwrite transaction on an observed database committed. */\n onCommit: (dbName: string, ops: CommittedOp[]) => void;\n /** The app opened an observed database. The open request's success is\n * held until this resolves (or `openTimeoutMs` passes). */\n onOpen: (db: IDBDatabase, dbName: string) => Promise<void> | void;\n /** The app deleted an observed database. */\n onDeleteDatabase: (dbName: string) => void;\n openTimeoutMs: number;\n /** Only observe this factory's databases. Default: the global one at\n * install time. */\n factory?: IDBFactory;\n}\n\nexport interface InstalledHook {\n /** The factory's untouched open — for the SDK's own databases. */\n originalOpen: (name: string, version?: number) => IDBOpenDBRequest;\n uninstall(): void;\n}\n\ninterface Subscriber {\n opts: HookOptions;\n factory: IDBFactory | null;\n}\n\ninterface TxState {\n db: string;\n subs: Subscriber[];\n ops: CommittedOp[];\n}\n\ninterface DbInfo {\n name: string;\n subs: Subscriber[];\n}\n\nconst txState = new WeakMap<IDBTransaction, TxState>();\n/** IDBDatabase handles the app obtained through the hooked open. */\nconst observedDbs = new WeakMap<IDBDatabase, DbInfo>();\nconst subscribers = new Set<Subscriber>();\nlet originals: {\n open: IDBFactory[\"open\"];\n deleteDatabase: IDBFactory[\"deleteDatabase\"];\n transaction: IDBDatabase[\"transaction\"];\n put: IDBObjectStore[\"put\"];\n add: IDBObjectStore[\"add\"];\n get: IDBObjectStore[\"get\"];\n delete: IDBObjectStore[\"delete\"];\n clear: IDBObjectStore[\"clear\"];\n getAllKeys: IDBObjectStore[\"getAllKeys\"];\n cursorUpdate: IDBCursor[\"update\"];\n cursorDelete: IDBCursor[\"delete\"];\n} | null = null;\n\nfunction clone<T>(v: T): T {\n try {\n return typeof structuredClone === \"function\" ? structuredClone(v) : v;\n } catch {\n return v;\n }\n}\n\nfunction isPlainObject(v: unknown): boolean {\n if (v === null || typeof v !== \"object\") return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\n/** Minimal event with `target` pointing at the hooked request. */\nfunction makeEvent(type: string, init?: { oldVersion?: number; newVersion?: number | null }, target?: object): Event {\n let ev: Event;\n if (init && typeof IDBVersionChangeEvent === \"function\") {\n ev = new IDBVersionChangeEvent(type, { oldVersion: init.oldVersion, newVersion: init.newVersion ?? null });\n } else {\n ev = new Event(type, { bubbles: false, cancelable: type === \"error\" });\n if (init) Object.assign(ev, init);\n }\n if (target) {\n for (const p of [\"target\", \"currentTarget\", \"srcElement\"]) {\n try {\n Object.defineProperty(ev, p, { value: target, configurable: true });\n } catch {\n /* locked-down event implementation; listeners read request.result anyway */\n }\n }\n }\n return ev;\n}\n\ntype Handler = ((ev: Event) => unknown) | null;\n\n/**\n * Stand-in for the IDBOpenDBRequest the app would have received. It has\n * the same surface (result, error, readyState, transaction, source,\n * the four on* handlers, add/removeEventListener) and an\n * IDBOpenDBRequest prototype chain so `instanceof` holds.\n */\nclass HookedOpenRequest {\n readyState: \"pending\" | \"done\" = \"pending\";\n result: IDBDatabase | undefined = undefined;\n error: DOMException | null = null;\n transaction: IDBTransaction | null = null;\n readonly source: null = null;\n private listeners = new Map<string, Set<(ev: Event) => unknown>>();\n private handlers: Record<string, Handler> = { success: null, error: null, blocked: null, upgradeneeded: null };\n\n constructor(real: IDBOpenDBRequest, name: string, subs: Subscriber[]) {\n real.addEventListener(\"upgradeneeded\", (ev) => {\n const e = ev as IDBVersionChangeEvent;\n this.result = real.result;\n this.transaction = real.transaction;\n observedDbs.set(real.result, { name, subs });\n this.dispatch(makeEvent(\"upgradeneeded\", { oldVersion: e.oldVersion, newVersion: e.newVersion }, this));\n });\n real.addEventListener(\"blocked\", (ev) => {\n const e = ev as IDBVersionChangeEvent;\n this.dispatch(makeEvent(\"blocked\", { oldVersion: e.oldVersion, newVersion: e.newVersion }, this));\n });\n real.addEventListener(\"error\", () => {\n this.error = real.error;\n this.readyState = \"done\";\n this.transaction = null;\n this.dispatch(makeEvent(\"error\", undefined, this));\n });\n real.addEventListener(\"success\", () => {\n const db = real.result;\n observedDbs.set(db, { name, subs });\n this.transaction = null;\n let settled = false;\n const deliver = () => {\n if (settled) return;\n settled = true;\n this.result = db;\n this.readyState = \"done\";\n this.dispatch(makeEvent(\"success\", undefined, this));\n };\n // Hold success for the engines' first pull, bounded by a timeout\n // so an offline device still opens.\n const waits: Promise<void>[] = [];\n let timeout = 0;\n for (const s of subs) {\n try {\n const p = s.opts.onOpen(db, name);\n if (p && typeof (p as Promise<void>).then === \"function\") {\n waits.push((p as Promise<void>).catch(() => {}));\n timeout = Math.max(timeout, s.opts.openTimeoutMs);\n }\n } catch {\n /* a failing onOpen must not keep the app from its database */\n }\n }\n if (waits.length === 0) {\n deliver();\n return;\n }\n const timer = setTimeout(deliver, timeout);\n Promise.all(waits).then(() => {\n clearTimeout(timer);\n deliver();\n });\n });\n }\n\n addEventListener(type: string, listener: ((ev: Event) => unknown) | { handleEvent(ev: Event): unknown } | null): void {\n if (!listener) return;\n const fn = typeof listener === \"function\" ? listener : (ev: Event) => listener.handleEvent(ev);\n let set = this.listeners.get(type);\n if (!set) this.listeners.set(type, (set = new Set()));\n set.add(fn);\n (listener as { __rtFn?: (ev: Event) => unknown }).__rtFn = fn;\n }\n\n removeEventListener(type: string, listener: ((ev: Event) => unknown) | { handleEvent(ev: Event): unknown } | null): void {\n if (!listener) return;\n const fn = typeof listener === \"function\" ? listener : (listener as { __rtFn?: (ev: Event) => unknown }).__rtFn;\n if (fn) this.listeners.get(type)?.delete(fn);\n }\n\n dispatchEvent(ev: Event): boolean {\n this.dispatch(ev);\n return !ev.defaultPrevented;\n }\n\n private dispatch(ev: Event): void {\n const h = this.handlers[ev.type];\n if (h) {\n try {\n h.call(this, ev);\n } catch (err) {\n reportListenerError(err);\n }\n }\n for (const fn of this.listeners.get(ev.type) ?? []) {\n try {\n fn.call(this, ev);\n } catch (err) {\n reportListenerError(err);\n }\n }\n }\n\n get onsuccess(): Handler { return this.handlers.success ?? null; }\n set onsuccess(h: Handler) { this.handlers.success = h; }\n get onerror(): Handler { return this.handlers.error ?? null; }\n set onerror(h: Handler) { this.handlers.error = h; }\n get onblocked(): Handler { return this.handlers.blocked ?? null; }\n set onblocked(h: Handler) { this.handlers.blocked = h; }\n get onupgradeneeded(): Handler { return this.handlers.upgradeneeded ?? null; }\n set onupgradeneeded(h: Handler) { this.handlers.upgradeneeded = h; }\n}\n\nfunction reportListenerError(err: unknown): void {\n // Same as the browser: a throwing listener is reported, not swallowed\n // silently, and does not stop the other listeners.\n setTimeout(() => {\n throw err;\n }, 0);\n}\n\nif (typeof IDBOpenDBRequest === \"function\") {\n Object.setPrototypeOf(HookedOpenRequest.prototype, IDBOpenDBRequest.prototype);\n}\n\n/** Object store of a cursor, whether it was opened on a store or an index. */\nfunction cursorStore(cursor: IDBCursor): IDBObjectStore {\n const src = cursor.source as IDBObjectStore | IDBIndex;\n return typeof IDBIndex === \"function\" && src instanceof IDBIndex ? src.objectStore : (src as IDBObjectStore);\n}\n\nfunction subscribersFor(factory: IDBFactory, name: string): Subscriber[] {\n if (name.startsWith(\"__runapps\")) return [];\n const out: Subscriber[] = [];\n for (const s of subscribers) {\n if ((s.factory === null || s.factory === factory) && s.opts.include(name)) out.push(s);\n }\n return out;\n}\n\nfunction patchPrototypes(): void {\n const F = IDBFactory.prototype;\n const D = IDBDatabase.prototype;\n const S = IDBObjectStore.prototype;\n const C = IDBCursor.prototype;\n const o = (originals = {\n open: F.open,\n deleteDatabase: F.deleteDatabase,\n transaction: D.transaction,\n put: S.put,\n add: S.add,\n get: S.get,\n delete: S.delete,\n clear: S.clear,\n getAllKeys: S.getAllKeys,\n cursorUpdate: C.update,\n cursorDelete: C.delete,\n });\n\n const tracked = (store: IDBObjectStore): TxState | null => txState.get(store.transaction) ?? null;\n\n F.open = function (this: IDBFactory, name: string, version?: number): IDBOpenDBRequest {\n const real = version === undefined ? o.open.call(this, name) : o.open.call(this, name, version);\n const subs = subscribersFor(this, name);\n if (subs.length === 0) return real;\n return new HookedOpenRequest(real, name, subs) as unknown as IDBOpenDBRequest;\n };\n\n F.deleteDatabase = function (this: IDBFactory, name: string): IDBOpenDBRequest {\n for (const s of subscribersFor(this, name)) {\n try {\n s.opts.onDeleteDatabase(name);\n } catch {\n /* never block the app's delete */\n }\n }\n return o.deleteDatabase.call(this, name);\n };\n\n D.transaction = function (\n this: IDBDatabase,\n storeNames: string | string[],\n mode?: IDBTransactionMode,\n options?: IDBTransactionOptions,\n ): IDBTransaction {\n const tx =\n options !== undefined\n ? o.transaction.call(this, storeNames, mode, options)\n : mode !== undefined\n ? o.transaction.call(this, storeNames, mode)\n : o.transaction.call(this, storeNames);\n const info = observedDbs.get(this);\n if (info && tx.mode === \"readwrite\") {\n const st: TxState = { db: info.name, subs: info.subs, ops: [] };\n txState.set(tx, st);\n tx.addEventListener(\"complete\", () => {\n if (st.ops.length === 0) return;\n const ops = st.ops;\n st.ops = [];\n for (const s of st.subs) {\n const mine = ops.filter((op) => !s.opts.excludeStore(st.db, op.store));\n if (mine.length === 0) continue;\n try {\n s.opts.onCommit(st.db, mine);\n } catch (err) {\n reportListenerError(err);\n }\n }\n });\n }\n return tx;\n };\n\n S.put = function (this: IDBObjectStore, value: unknown, key?: IDBValidKey): IDBRequest<IDBValidKey> {\n const st = tracked(this);\n let prior: { value: unknown } | null = null;\n if (st && isPlainObject(value)) {\n // The record's previous value, read before the write in the same\n // transaction, tells the engine which fields this write changed.\n const k = key !== undefined ? key : extractKey(value, this.keyPath as string | string[] | null);\n if (k !== undefined) {\n try {\n const g = o.get.call(this, k);\n const slot = (prior = { value: undefined });\n g.addEventListener(\"success\", () => {\n slot.value = g.result;\n });\n } catch {\n prior = null;\n }\n }\n }\n const r = key === undefined ? o.put.call(this, value) : o.put.call(this, value, key);\n if (st) {\n const store = this.name;\n const snapshot = clone(value);\n const priorSlot = prior;\n r.addEventListener(\"success\", () => {\n st.ops.push({ store, kind: \"put\", key: r.result, value: snapshot, prior: priorSlot?.value });\n });\n }\n return r;\n };\n\n S.add = function (this: IDBObjectStore, value: unknown, key?: IDBValidKey): IDBRequest<IDBValidKey> {\n const r = key === undefined ? o.add.call(this, value) : o.add.call(this, value, key);\n const st = tracked(this);\n if (st) {\n const store = this.name;\n const snapshot = clone(value);\n r.addEventListener(\"success\", () => {\n st.ops.push({ store, kind: \"put\", key: r.result, value: snapshot });\n });\n }\n return r;\n };\n\n S.delete = function (this: IDBObjectStore, query: IDBValidKey | IDBKeyRange): IDBRequest<undefined> {\n const st = tracked(this);\n if (st) {\n const store = this.name;\n if (typeof IDBKeyRange === \"function\" && query instanceof IDBKeyRange) {\n // Which keys a range covers is only knowable before the delete;\n // getAllKeys runs first in the same transaction.\n const kr = o.getAllKeys.call(this, query);\n kr.addEventListener(\"success\", () => {\n for (const k of kr.result) st.ops.push({ store, kind: \"del\", key: k });\n });\n } else {\n st.ops.push({ store, kind: \"del\", key: query as IDBValidKey });\n }\n }\n return o.delete.call(this, query);\n };\n\n S.clear = function (this: IDBObjectStore): IDBRequest<undefined> {\n const st = tracked(this);\n const r = o.clear.call(this);\n if (st) {\n const store = this.name;\n r.addEventListener(\"success\", () => {\n st.ops.push({ store, kind: \"clear\" });\n });\n }\n return r;\n };\n\n C.update = function (this: IDBCursor, value: unknown): IDBRequest<IDBValidKey> {\n const store = cursorStore(this);\n const st = tracked(store);\n const prior = st ? clone((this as IDBCursorWithValue).value) : undefined;\n const r = o.cursorUpdate.call(this, value);\n if (st) {\n const name = store.name;\n const snapshot = clone(value);\n const key = this.primaryKey;\n r.addEventListener(\"success\", () => {\n st.ops.push({ store: name, kind: \"put\", key, value: snapshot, prior });\n });\n }\n return r;\n };\n\n C.delete = function (this: IDBCursor): IDBRequest<undefined> {\n const store = cursorStore(this);\n const st = tracked(store);\n if (st) st.ops.push({ store: store.name, kind: \"del\", key: this.primaryKey });\n return o.cursorDelete.call(this);\n };\n}\n\nfunction restorePrototypes(): void {\n if (!originals) return;\n const o = originals;\n IDBFactory.prototype.open = o.open;\n IDBFactory.prototype.deleteDatabase = o.deleteDatabase;\n IDBDatabase.prototype.transaction = o.transaction;\n IDBObjectStore.prototype.put = o.put;\n IDBObjectStore.prototype.add = o.add;\n IDBObjectStore.prototype.delete = o.delete;\n IDBObjectStore.prototype.clear = o.clear;\n IDBCursor.prototype.update = o.cursorUpdate;\n IDBCursor.prototype.delete = o.cursorDelete;\n originals = null;\n}\n\n/**\n * Install the hook for one subscriber. The prototypes are patched on the\n * first install and restored when the last subscriber uninstalls.\n */\nexport function installIndexedDBHook(opts: HookOptions): InstalledHook | null {\n if (\n typeof indexedDB === \"undefined\" ||\n typeof IDBFactory !== \"function\" ||\n typeof IDBDatabase !== \"function\" ||\n typeof IDBObjectStore !== \"function\" ||\n typeof IDBCursor !== \"function\"\n ) {\n return null;\n }\n if (!originals) patchPrototypes();\n const o = originals!;\n const sub: Subscriber = { opts, factory: opts.factory ?? indexedDB };\n subscribers.add(sub);\n const factory = sub.factory as IDBFactory;\n return {\n originalOpen: (name, version) =>\n version === undefined ? o.open.call(factory, name) : o.open.call(factory, name, version),\n uninstall() {\n subscribers.delete(sub);\n if (subscribers.size === 0) restorePrototypes();\n },\n };\n}\n","// sync/log.ts — the SDK's own IndexedDB database.\n//\n// Holds what the sync engine needs to survive a reload:\n// ops — local changes not yet pushed, in commit order. Values are kept\n// as the app wrote them (structured clone, Blobs included);\n// they are encoded only when pushed, so logging works offline.\n// meta — small key/value state: the owner (user id the local data\n// belongs to), shard ETags seen last, store clear marks.\n//\n// Opened through the untouched `indexedDB.open`, never the hooked one,\n// and its name starts with `__runapps` so the hook leaves it alone.\n\nexport const SYNC_DB_NAME = \"__runapps_sync__\";\nconst SYNC_DB_VERSION = 1;\n\nexport type OpKind = \"put\" | \"del\" | \"clear\";\n\nexport interface LoggedOp {\n id?: number;\n db: string;\n store: string;\n kind: OpKind;\n /** Canonical key id (codec.keyId); \"\" for clear. */\n kid: string;\n /** Encoded key (codec.encodeKey); undefined for clear. */\n key?: unknown;\n /** The value as written, for put. */\n value?: unknown;\n /** For a put of a plain object whose previous value was seen: the\n * fields this write changed (added, modified or removed). Undefined\n * means \"treat every field as changed\". */\n changed?: string[];\n /** Hybrid logical clock timestamp (ms, monotonic per device). */\n ts: number;\n}\n\nfunction req<T>(r: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n r.onsuccess = () => resolve(r.result);\n r.onerror = () => reject(r.error ?? new Error(\"IndexedDB request failed\"));\n });\n}\n\nfunction done(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onabort = () => reject(tx.error ?? new Error(\"IndexedDB transaction aborted\"));\n tx.onerror = () => reject(tx.error ?? new Error(\"IndexedDB transaction failed\"));\n });\n}\n\nexport class SyncLog {\n private dbPromise: Promise<IDBDatabase> | null = null;\n\n constructor(private readonly open: (name: string, version?: number) => IDBOpenDBRequest) {}\n\n private db(): Promise<IDBDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const r = this.open(SYNC_DB_NAME, SYNC_DB_VERSION);\n r.onupgradeneeded = () => {\n const db = r.result;\n if (!db.objectStoreNames.contains(\"ops\")) {\n const ops = db.createObjectStore(\"ops\", { keyPath: \"id\", autoIncrement: true });\n ops.createIndex(\"store\", [\"db\", \"store\"], { unique: false });\n }\n if (!db.objectStoreNames.contains(\"meta\")) db.createObjectStore(\"meta\");\n };\n r.onsuccess = () => {\n const db = r.result;\n // Another tab upgrading (a future SDK) must not be blocked by us.\n db.onversionchange = () => {\n db.close();\n this.dbPromise = null;\n };\n resolve(db);\n };\n r.onerror = () => reject(r.error ?? new Error(\"cannot open sync log\"));\n r.onblocked = () => reject(new Error(\"sync log open blocked\"));\n });\n this.dbPromise.catch(() => {\n this.dbPromise = null;\n });\n }\n return this.dbPromise;\n }\n\n async append(ops: LoggedOp[]): Promise<void> {\n if (ops.length === 0) return;\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readwrite\");\n const st = tx.objectStore(\"ops\");\n for (const op of ops) st.add(op);\n await done(tx);\n }\n\n async pending(): Promise<LoggedOp[]> {\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readonly\");\n const all = (await req(tx.objectStore(\"ops\").getAll())) as LoggedOp[];\n return all;\n }\n\n async pendingCount(): Promise<number> {\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readonly\");\n return req(tx.objectStore(\"ops\").count());\n }\n\n async remove(ids: number[]): Promise<void> {\n if (ids.length === 0) return;\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readwrite\");\n const st = tx.objectStore(\"ops\");\n for (const id of ids) st.delete(id);\n await done(tx);\n }\n\n async clearOps(): Promise<void> {\n const db = await this.db();\n const tx = db.transaction(\"ops\", \"readwrite\");\n tx.objectStore(\"ops\").clear();\n await done(tx);\n }\n\n async getMeta<T = unknown>(key: string): Promise<T | undefined> {\n const db = await this.db();\n const tx = db.transaction(\"meta\", \"readonly\");\n return (await req(tx.objectStore(\"meta\").get(key))) as T | undefined;\n }\n\n async setMeta(entries: Record<string, unknown>): Promise<void> {\n const db = await this.db();\n const tx = db.transaction(\"meta\", \"readwrite\");\n const st = tx.objectStore(\"meta\");\n for (const k of Object.keys(entries)) {\n if (entries[k] === undefined) st.delete(k);\n else st.put(entries[k], k);\n }\n await done(tx);\n }\n\n /** Every meta entry whose key starts with `prefix`, as a map. */\n async metaWithPrefix(prefix: string): Promise<Map<string, unknown>> {\n const db = await this.db();\n const tx = db.transaction(\"meta\", \"readonly\");\n const st = tx.objectStore(\"meta\");\n const range = IDBKeyRange.bound(prefix, prefix + \"\");\n const keys = (await req(st.getAllKeys(range))) as string[];\n const vals = await req(st.getAll(range));\n const out = new Map<string, unknown>();\n keys.forEach((k, i) => out.set(k, vals[i]));\n return out;\n }\n\n async clearAll(): Promise<void> {\n const db = await this.db();\n const tx = db.transaction([\"ops\", \"meta\"], \"readwrite\");\n tx.objectStore(\"ops\").clear();\n tx.objectStore(\"meta\").clear();\n await done(tx);\n }\n\n close(): void {\n this.dbPromise?.then((db) => db.close()).catch(() => {});\n this.dbPromise = null;\n }\n}\n\nexport { req as idbRequest, done as idbDone };\n","// sync/engine.ts — keeps an app's IndexedDB and the user's cloud copy\n// the same.\n//\n// Storage model (one object per shard, under the user's file space):\n//\n// db/<database>/<store>/<shard>.json\n// { v: 1, cleared: <ts>, r: { <keyId>: { k, v, t, d? } } }\n//\n// A store's records are spread over a fixed number of shards by key\n// hash, so a change rewrites one small object, and a device that has\n// nothing new to see pays one conditional read per shard (304). Every\n// record carries the timestamp of its last write; the newer write wins,\n// a deletion is a record with `d: 1`, and a store `clear` raises the\n// shard's `cleared` mark, which drops everything older. Shards are\n// written with If-Match, so two devices never overwrite each other: the\n// loser re-reads, merges and writes again.\n//\n// Locally the engine owns a change log (log.ts) fed by the hook\n// (hook.ts); pushing turns pending ops into shard writes, pulling turns\n// changed shards into writes on the app's database through the engine's\n// own connection, which the hook does not observe.\n\nimport { changedFields, decodeKey, decodeValue, encodeKey, encodeValue, hash32, keyId, type BlobRef } from \"./codec.js\";\nimport { type CommittedOp, type InstalledHook, installIndexedDBHook } from \"./hook.js\";\nimport { idbDone, idbRequest, SyncLog, type LoggedOp } from \"./log.js\";\nimport type { SDKEvents } from \"../events.js\";\n\nexport interface SyncOptions {\n /** Observe only these databases (by name). Default: every database the\n * app opens, except the SDK's own. */\n databases?: string[];\n /** Leave out a whole database (`\"cache\"`) or one store (`\"app/videos\"`). */\n exclude?: string[];\n /** Shards per store. Default 8. Changing it later splits data: pick once. */\n shards?: number;\n /** How long to gather local writes before pushing. Default 2500 ms. */\n pushDelayMs?: number;\n /** Background pull interval while the page is visible. Default 60 s. */\n pullIntervalMs?: number;\n /** How long a database open waits for the first pull. Default 5 s. */\n openTimeoutMs?: number;\n /** Path prefix in the user's file space. Default \"db\". */\n prefix?: string;\n}\n\nexport interface SyncState {\n /** off: no signed-in user; idle: everything pushed; syncing: a push or\n * pull is running; offline: changes waiting for the network; error:\n * the last attempt failed, will retry. */\n state: \"off\" | \"idle\" | \"syncing\" | \"offline\" | \"error\";\n /** Local changes not yet on the server. */\n pending: number;\n lastSyncedAt?: number;\n /** When a sync last moved data — pushed local writes or applied\n * another device's. A quiet pull that found nothing leaves it. */\n lastChangeAt?: number;\n error?: string;\n}\n\nexport interface DbChange {\n db: string;\n store: string;\n /** Primary keys that changed; empty when the whole store was cleared. */\n keys: IDBValidKey[];\n cleared: boolean;\n /** remote: another device's change, just applied here. tab: another\n * tab of this browser wrote to the shared database (IndexedDB has\n * no event for that). */\n source: \"remote\" | \"tab\";\n}\n\n/** What the engine needs from the rest of the SDK. */\nexport interface SyncDeps {\n remote: SyncRemote;\n events: SDKEvents;\n /** Signed-in user id, or null. */\n userId: () => string | null;\n /** Subscribe to sign-in changes. */\n onAuthChange: (cb: () => void) => () => void;\n /** The IndexedDB factory to observe. Default: the global one. */\n factory?: IDBFactory;\n}\n\n/** The storage side. Implemented over FilesService; tests use memory. */\nexport interface SyncRemote {\n list(prefix: string): Promise<{ path: string; etag: string }[]>;\n getJSON(path: string, ifNoneMatch?: string): Promise<{ status: \"ok\"; value: unknown; etag: string; serverTime?: number } | { status: \"missing\" } | { status: \"unchanged\" }>;\n putJSON(path: string, value: unknown, cond: { ifMatch?: string; ifNoneMatch?: boolean }): Promise<{ status: \"ok\"; etag: string; serverTime?: number } | { status: \"conflict\" }>;\n putBlob(path: string, blob: Blob): Promise<void>;\n getBlob(path: string): Promise<Blob>;\n}\n\ninterface ShardRecord {\n k: unknown;\n v: unknown;\n t: number;\n d?: 1;\n /** Per-field write times, kept for plain-object values so two devices\n * editing different fields of one record both keep their edits. */\n f?: Record<string, number>;\n}\n\nfunction isPlainRecord(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v) && !(\"$rt\" in (v as object));\n}\n\nfunction sameJSON(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * Fold an incoming write into the record already in the shard.\n *\n * Both plain objects: field by field. `changed` names the fields the\n * incoming write touched (the hook compared the record with its previous\n * value in the same transaction); every other field keeps the stored\n * value, which may have been written by another device meanwhile. A\n * changed field takes the incoming value unless the stored field was\n * written later by someone else; a field the incoming write dropped is\n * removed on the same condition. Without `changed` (an `add`, or a prior\n * value the hook could not read), a field is taken as changed when its\n * value differs. Anything else: the later write wins whole.\n */\nfunction mergeRecord(cur: ShardRecord | undefined, inc: ShardRecord, changed?: string[]): ShardRecord {\n if (!cur) return inc;\n if (inc.d || cur.d || !isPlainRecord(cur.v) || !isPlainRecord(inc.v)) {\n return inc.t >= cur.t ? inc : cur;\n }\n const curF = cur.f ?? {};\n const touched = changed ? new Set(changed) : null;\n const out: Record<string, unknown> = {};\n const f: Record<string, number> = {};\n const fields = new Set([...Object.keys(cur.v), ...Object.keys(inc.v)]);\n for (const name of fields) {\n const inCur = name in cur.v;\n const inInc = name in inc.v;\n const curT = curF[name] ?? cur.t;\n const untouched = touched ? !touched.has(name) : inCur && inInc && sameJSON(cur.v[name], inc.v[name]);\n if (untouched) {\n if (inCur) {\n out[name] = cur.v[name];\n if (curF[name] !== undefined) f[name] = curF[name];\n }\n continue;\n }\n const incomingWins = inc.t >= curT;\n if (incomingWins) {\n if (inInc) {\n out[name] = inc.v[name];\n f[name] = inc.t;\n }\n // dropped by the incoming write: leave it out\n } else if (inCur) {\n out[name] = cur.v[name];\n f[name] = curT;\n }\n }\n return { k: inc.k, v: out, t: Math.max(cur.t, inc.t), f };\n}\n\ninterface ShardOp {\n kid: string;\n rec: ShardRecord;\n changed?: string[];\n}\n\ninterface Shard {\n v: 1;\n cleared: number;\n r: Record<string, ShardRecord>;\n}\n\ninterface StoreSchema {\n keyPath: string | string[] | null;\n autoIncrement: boolean;\n}\n\nconst TOMBSTONE_TTL_MS = 30 * 24 * 3600 * 1000;\nconst MAX_PUSH_DELAY_MS = 10_000;\nconst BACKOFF_MIN_MS = 5_000;\nconst BACKOFF_MAX_MS = 5 * 60_000;\n\n/** Hybrid logical clock: wall time, never going backwards, with the\n * server's clock offset applied once learned. */\nclass Clock {\n private last = 0;\n private offset = 0;\n now(): number {\n const t = Date.now() + this.offset;\n this.last = t > this.last ? t : this.last + 1;\n return this.last;\n }\n observe(serverTime: number, localBefore: number, localAfter: number): void {\n const mid = (localBefore + localAfter) / 2;\n const off = serverTime - mid;\n if (Math.abs(off - this.offset) > 2000) this.offset = off;\n }\n}\n\nfunction pathSeg(s: string): string {\n return encodeURIComponent(s).replace(/%2F/gi, \"%2F\");\n}\n\nexport class SyncEngine {\n readonly options: Required<SyncOptions>;\n private readonly log: SyncLog;\n private readonly hook: InstalledHook | null;\n private readonly clock = new Clock();\n private readonly schemas = new Map<string, Map<string, StoreSchema>>();\n private readonly etags = new Map<string, string>();\n private readonly conns = new Map<string, Promise<IDBDatabase>>();\n private state: SyncState = { state: \"off\", pending: 0 };\n private paused = false;\n private disposed = false;\n private leader = true;\n private pushTimer: ReturnType<typeof setTimeout> | null = null;\n private pushDeadline = 0;\n private pullTimer: ReturnType<typeof setInterval> | null = null;\n private backoffMs = 0;\n private backoffUntil = 0;\n private chain: Promise<void> = Promise.resolve();\n private readonly cleanup: (() => void)[] = [];\n private channel: BroadcastChannel | null = null;\n private metaLoaded: Promise<void> | null = null;\n\n constructor(\n private readonly deps: SyncDeps,\n options: SyncOptions = {},\n ) {\n this.options = {\n databases: options.databases ?? [],\n exclude: options.exclude ?? [],\n shards: Math.max(1, Math.min(64, options.shards ?? 8)),\n pushDelayMs: options.pushDelayMs ?? 2500,\n pullIntervalMs: options.pullIntervalMs ?? 60_000,\n openTimeoutMs: options.openTimeoutMs ?? 5000,\n prefix: (options.prefix ?? \"db\").replace(/^\\/+|\\/+$/g, \"\"),\n };\n const excludedDbs = new Set(this.options.exclude.filter((e) => !e.includes(\"/\")));\n const excludedStores = new Set(this.options.exclude.filter((e) => e.includes(\"/\")));\n const only = new Set(this.options.databases);\n\n this.hook = installIndexedDBHook({\n include: (name) => (only.size === 0 || only.has(name)) && !excludedDbs.has(name),\n excludeStore: (db, store) => excludedStores.has(db + \"/\" + store),\n onCommit: (db, ops) => this.onCommit(db, ops),\n onOpen: (db, name) => this.onOpen(db, name),\n onDeleteDatabase: (name) => this.onDeleteDatabase(name),\n openTimeoutMs: this.options.openTimeoutMs,\n factory: deps.factory,\n });\n this.log = new SyncLog((name, version) =>\n this.hook ? this.hook.originalOpen(name, version) : indexedDB.open(name, version),\n );\n\n if (typeof window !== \"undefined\") this.attachBrowserTriggers();\n this.cleanup.push(deps.onAuthChange(() => this.schedule(\"auth\")));\n void this.refreshPending();\n void this.schedule(\"start\");\n }\n\n // ── Public surface ───────────────────────────────────────────────\n\n status(): SyncState {\n return { ...this.state };\n }\n\n /** Push pending changes and pull remote ones now. */\n now(): Promise<void> {\n if (!this.leader) {\n // Another tab syncs for this origin: ask it to go now. Its state\n // comes back over the channel.\n this.post({ type: \"sync:request\" });\n return Promise.resolve();\n }\n return this.enqueue(() => this.sync());\n }\n\n /** Send to the other tabs of this origin. */\n private post(msg: unknown): void {\n try {\n this.channel?.postMessage(msg);\n } catch {\n /* channel closed */\n }\n }\n\n pause(): void {\n this.paused = true;\n }\n\n resume(): void {\n this.paused = false;\n void this.schedule(\"resume\");\n }\n\n dispose(): void {\n this.disposed = true;\n for (const c of this.cleanup) c();\n if (this.pushTimer) clearTimeout(this.pushTimer);\n if (this.pullTimer) clearInterval(this.pullTimer);\n this.channel?.close();\n for (const p of this.conns.values()) p.then((db) => db.close()).catch(() => {});\n this.conns.clear();\n this.log.close();\n this.hook?.uninstall();\n }\n\n // ── Hook callbacks ───────────────────────────────────────────────\n\n private onCommit(db: string, ops: CommittedOp[]): void {\n const logged: LoggedOp[] = [];\n for (const op of ops) {\n const ts = this.clock.now();\n if (op.kind === \"clear\") logged.push({ db, store: op.store, kind: \"clear\", kid: \"\", ts });\n else if (op.kind === \"del\") logged.push({ db, store: op.store, kind: \"del\", kid: keyId(op.key), key: encodeKey(op.key), ts });\n else {\n logged.push({\n db,\n store: op.store,\n kind: \"put\",\n kid: keyId(op.key),\n key: encodeKey(op.key),\n value: op.value,\n changed: op.prior === undefined ? undefined : changedFields(op.prior, op.value),\n ts,\n });\n }\n }\n this.enqueueLog(logged);\n // The other tabs share this database but get no event from it:\n // tell them what changed so their views can re-read.\n const byStore = new Map<string, { keys: IDBValidKey[]; cleared: boolean }>();\n for (const op of ops) {\n let e = byStore.get(op.store);\n if (!e) byStore.set(op.store, (e = { keys: [], cleared: false }));\n if (op.kind === \"clear\") {\n e.cleared = true;\n e.keys = [];\n } else if (!e.cleared) {\n e.keys.push(op.key);\n }\n }\n for (const [store, e] of byStore) {\n const change: DbChange = { db, store, keys: e.keys, cleared: e.cleared, source: \"tab\" };\n this.post({ type: \"db:change\", change });\n }\n }\n\n private logChain: Promise<void> = Promise.resolve();\n private enqueueLog(ops: LoggedOp[]): void {\n this.logChain = this.logChain\n .then(() => this.log.append(ops))\n .then(() => this.refreshPending())\n .then(() => {\n if (!this.leader) this.post({ type: \"sync:request\" });\n })\n .catch((err) => this.fail(err));\n this.schedulePush();\n }\n\n private async onOpen(db: IDBDatabase, name: string): Promise<void> {\n const schema = new Map<string, StoreSchema>();\n const names = Array.from(db.objectStoreNames);\n if (names.length > 0) {\n const tx = db.transaction(names, \"readonly\");\n for (const n of names) {\n const st = tx.objectStore(n);\n schema.set(n, { keyPath: st.keyPath as string | string[] | null, autoIncrement: st.autoIncrement });\n if (st.autoIncrement && typeof console !== \"undefined\") {\n console.warn(\n `[runapps sync] \"${name}/${n}\" uses autoIncrement keys: records created on two devices while offline can get the same key, and only one survives. Prefer client-generated ids (e.g. crypto.randomUUID()).`,\n );\n }\n }\n }\n this.schemas.set(name, schema);\n void this.log.setMeta({ [\"schema:\" + name]: Object.fromEntries(schema) }).catch(() => {});\n if (!this.deps.userId() || this.paused || !this.leader) return;\n await this.enqueue(() => this.pullDatabase(name)).catch(() => {});\n }\n\n private onDeleteDatabase(name: string): void {\n const schema = this.schemas.get(name);\n const stores = schema ? Array.from(schema.keys()) : [];\n this.schemas.delete(name);\n this.conns.get(name)?.then((db) => db.close()).catch(() => {});\n this.conns.delete(name);\n const ts = this.clock.now();\n this.enqueueLog(stores.map((store) => ({ db: name, store, kind: \"clear\" as const, kid: \"\", ts })));\n }\n\n // ── Scheduling ───────────────────────────────────────────────────\n\n private attachBrowserTriggers(): void {\n const on = (target: EventTarget, type: string, fn: () => void) => {\n target.addEventListener(type, fn);\n this.cleanup.push(() => target.removeEventListener(type, fn));\n };\n on(window, \"online\", () => {\n this.backoffUntil = 0;\n void this.schedule(\"online\");\n });\n on(window, \"focus\", () => void this.schedule(\"focus\"));\n if (typeof document !== \"undefined\") {\n on(document, \"visibilitychange\", () => {\n if (document.visibilityState === \"visible\") void this.schedule(\"visible\");\n this.updatePullTimer();\n });\n }\n this.updatePullTimer();\n if (typeof BroadcastChannel === \"function\") {\n this.channel = new BroadcastChannel(\"__runapps_db_change__\");\n this.channel.onmessage = (ev) => {\n const d = ev.data as\n | (DbChange & { type?: undefined })\n | { type: \"sync:state\"; state: SyncState }\n | { type: \"sync:request\" }\n | { type: \"db:change\"; change: DbChange }\n | undefined;\n if (!d) return;\n if (d.type === \"db:change\") {\n this.deps.events.emit(\"db:change\", d.change);\n } else if (d.type === \"sync:state\") {\n // The leader's state is the origin's state; a follower shows it.\n if (!this.leader) {\n this.state = { ...d.state };\n this.deps.events.emit(\"sync:state\", { ...d.state });\n }\n } else if (d.type === \"sync:request\") {\n // A follower wrote to the log, or wants a sync now: go, and\n // tell it where things stand either way.\n if (this.leader) {\n this.post({ type: \"sync:state\", state: { ...this.state } });\n void this.schedule(\"peer\");\n }\n } else if (d.source === \"remote\") {\n this.deps.events.emit(\"db:change\", d);\n }\n };\n }\n // One tab syncs at a time; the others feed the shared log and wait\n // for the lock, taking over when the leader closes.\n const locks = (navigator as Navigator & { locks?: LockManager }).locks;\n if (locks && typeof locks.request === \"function\") {\n this.leader = false;\n // Until the lock is ours, another tab is syncing: ask it where\n // things stand so this tab does not sit on the initial \"off\".\n this.post({ type: \"sync:request\" });\n void locks\n .request(\"__runapps_sync__\", () => {\n this.leader = true;\n void this.schedule(\"leader\");\n return new Promise<void>(() => {}); // hold until the tab dies\n })\n .catch(() => {\n this.leader = true;\n });\n }\n }\n\n private updatePullTimer(): void {\n const visible = typeof document === \"undefined\" || document.visibilityState !== \"hidden\";\n if (visible && !this.pullTimer && this.options.pullIntervalMs > 0) {\n this.pullTimer = setInterval(() => void this.schedule(\"interval\"), this.options.pullIntervalMs);\n (this.pullTimer as { unref?: () => void }).unref?.();\n } else if (!visible && this.pullTimer) {\n clearInterval(this.pullTimer);\n this.pullTimer = null;\n }\n }\n\n private schedulePush(): void {\n if (this.disposed) return;\n const now = Date.now();\n if (!this.pushDeadline) this.pushDeadline = now + MAX_PUSH_DELAY_MS;\n const delay = Math.max(0, Math.min(this.options.pushDelayMs, this.pushDeadline - now));\n if (this.pushTimer) clearTimeout(this.pushTimer);\n this.pushTimer = setTimeout(() => {\n this.pushTimer = null;\n this.pushDeadline = 0;\n void this.schedule(\"push\");\n }, delay);\n (this.pushTimer as { unref?: () => void }).unref?.();\n }\n\n private schedule(_reason: string): Promise<void> {\n if (this.disposed || this.paused || !this.leader) return Promise.resolve();\n if (!this.deps.userId()) {\n this.setState({ state: \"off\" });\n return Promise.resolve();\n }\n if (Date.now() < this.backoffUntil) return Promise.resolve();\n return this.enqueue(() => this.sync());\n }\n\n private enqueue(fn: () => Promise<void>): Promise<void> {\n const run = this.chain.then(fn, fn);\n this.chain = run.catch(() => {});\n return run;\n }\n\n // ── State ────────────────────────────────────────────────────────\n\n /** Set while a sync cycle pushes or applies something. */\n private moved = false;\n\n private setState(patch: Partial<SyncState>): void {\n const next = { ...this.state, ...patch };\n if (patch.state && patch.state !== \"error\") delete next.error;\n const changed =\n next.state !== this.state.state ||\n next.pending !== this.state.pending ||\n next.lastSyncedAt !== this.state.lastSyncedAt ||\n next.lastChangeAt !== this.state.lastChangeAt ||\n next.error !== this.state.error;\n this.state = next;\n if (changed) {\n this.deps.events.emit(\"sync:state\", { ...next });\n if (this.leader) this.post({ type: \"sync:state\", state: { ...next } });\n }\n }\n\n private async refreshPending(): Promise<void> {\n try {\n const n = await this.log.pendingCount();\n this.setState({ pending: n });\n } catch {\n /* log unavailable (private mode): stay with the last count */\n }\n }\n\n private fail(err: unknown): void {\n const offline = typeof navigator !== \"undefined\" && navigator.onLine === false;\n const msg = err instanceof Error ? err.message : String(err);\n this.backoffMs = this.backoffMs ? Math.min(this.backoffMs * 2, BACKOFF_MAX_MS) : BACKOFF_MIN_MS;\n this.backoffUntil = Date.now() + this.backoffMs;\n this.setState({ state: offline ? \"offline\" : \"error\", error: msg });\n if (!offline && typeof console !== \"undefined\") console.warn(\"[runapps sync]\", msg);\n }\n\n // ── Sync ─────────────────────────────────────────────────────────\n\n private async loadMeta(): Promise<void> {\n if (!this.metaLoaded) {\n this.metaLoaded = (async () => {\n for (const [k, v] of await this.log.metaWithPrefix(\"etag:\")) this.etags.set(k.slice(5), v as string);\n for (const [k, v] of await this.log.metaWithPrefix(\"schema:\")) {\n const name = k.slice(7);\n if (!this.schemas.has(name)) {\n this.schemas.set(name, new Map(Object.entries(v as Record<string, StoreSchema>)));\n }\n }\n })();\n this.metaLoaded.catch(() => {\n this.metaLoaded = null;\n });\n }\n return this.metaLoaded;\n }\n\n /** The local copy belongs to one user. Another user signing in on the\n * same browser starts from the server's copy, never from theirs. */\n private async ensureOwner(userId: string): Promise<void> {\n const owner = await this.log.getMeta<string>(\"owner\");\n if (owner === userId) return;\n if (owner !== undefined) {\n for (const [db, schema] of this.schemas) {\n for (const store of schema.keys()) {\n if (this.storeExcluded(db, store)) continue;\n await this.withStore(db, store, \"readwrite\", (st) => {\n st.clear();\n }).catch(() => {});\n this.emitChange({ db, store, keys: [], cleared: true, source: \"remote\" });\n }\n }\n await this.log.clearAll();\n this.etags.clear();\n this.metaLoaded = null;\n await this.loadMeta();\n }\n await this.log.setMeta({ owner: userId });\n }\n\n private async sync(): Promise<void> {\n if (this.disposed || this.paused || !this.leader) return;\n const userId = this.deps.userId();\n if (!userId) {\n this.setState({ state: \"off\" });\n return;\n }\n this.setState({ state: \"syncing\" });\n this.moved = false;\n try {\n await this.loadMeta();\n await this.ensureOwner(userId);\n await this.logChain;\n await this.push();\n await this.pull();\n this.backoffMs = 0;\n this.backoffUntil = 0;\n await this.refreshPending();\n const now = Date.now();\n this.setState({ state: \"idle\", lastSyncedAt: now, ...(this.moved ? { lastChangeAt: now } : {}) });\n } catch (err) {\n await this.refreshPending();\n this.fail(err);\n }\n }\n\n private storeExcluded(db: string, store: string): boolean {\n return this.options.exclude.includes(db + \"/\" + store) || this.options.exclude.includes(db);\n }\n\n private shardPath(db: string, store: string, shard: number): string {\n return `${this.options.prefix}/${pathSeg(db)}/${pathSeg(store)}/${shard}.json`;\n }\n\n private shardOf(kid: string): number {\n return hash32(kid) % this.options.shards;\n }\n\n private async readShard(path: string): Promise<{ shard: Shard; etag?: string }> {\n const before = Date.now();\n const r = await this.deps.remote.getJSON(path);\n if (r.status === \"ok\") {\n if (r.serverTime) this.clock.observe(r.serverTime, before, Date.now());\n const s = r.value as Partial<Shard> | null;\n const shard: Shard = { v: 1, cleared: s?.cleared ?? 0, r: s?.r ?? {} };\n this.etags.set(path, r.etag);\n return { shard, etag: r.etag };\n }\n this.etags.delete(path);\n return { shard: { v: 1, cleared: 0, r: {} } };\n }\n\n // ── Push ─────────────────────────────────────────────────────────\n\n private async push(): Promise<void> {\n const ops = await this.log.pending();\n if (ops.length === 0) return;\n // Group by store, keeping commit order inside a store.\n const groups = new Map<string, LoggedOp[]>();\n for (const op of ops) {\n const k = op.db + \"\\u0000\" + op.store;\n let g = groups.get(k);\n if (!g) groups.set(k, (g = []));\n g.push(op);\n }\n const done: number[] = [];\n const etagUpdates: Record<string, unknown> = {};\n for (const [, g] of groups) {\n const db = g[0]!.db;\n const store = g[0]!.store;\n if (this.storeExcluded(db, store)) {\n for (const op of g) if (op.id !== undefined) done.push(op.id);\n continue;\n }\n // A clear drops everything written before it; ops after the last\n // clear are the ones that still matter.\n let clearedAt = 0;\n let lastClear = -1;\n g.forEach((op, i) => {\n if (op.kind === \"clear\") {\n clearedAt = Math.max(clearedAt, op.ts);\n lastClear = i;\n }\n });\n const live = g.slice(lastClear + 1);\n const byShard = new Map<number, LoggedOp[]>();\n for (const op of live) {\n const s = this.shardOf(op.kid);\n let list = byShard.get(s);\n if (!list) byShard.set(s, (list = []));\n list.push(op);\n }\n const shards = clearedAt > 0 ? Array.from({ length: this.options.shards }, (_, i) => i) : Array.from(byShard.keys());\n for (const n of shards) {\n const path = this.shardPath(db, store, n);\n const shardOps = byShard.get(n) ?? [];\n const encoded = await Promise.all(shardOps.map((op) => this.encodeOp(op)));\n const etag = await this.writeShard(path, clearedAt, encoded);\n etagUpdates[\"etag:\" + path] = etag;\n }\n for (const op of g) if (op.id !== undefined) done.push(op.id);\n }\n await this.log.setMeta(etagUpdates);\n await this.log.remove(done);\n if (done.length > 0) this.moved = true;\n await this.refreshPending();\n }\n\n private async encodeOp(op: LoggedOp): Promise<ShardOp> {\n if (op.kind === \"del\") return { kid: op.kid, rec: { k: op.key, v: null, t: op.ts, d: 1 } };\n const v = await encodeValue(op.value, (blob) => this.storeBlob(blob));\n return { kid: op.kid, rec: { k: op.key, v, t: op.ts }, changed: op.changed };\n }\n\n /** Merge ops into one shard and write it; retried on a 412. */\n private async writeShard(path: string, clearedAt: number, ops: ShardOp[]): Promise<string> {\n for (let attempt = 0; attempt < 5; attempt++) {\n const { shard, etag } = await this.readShard(path);\n const now = this.clock.now();\n if (clearedAt > shard.cleared) {\n shard.cleared = clearedAt;\n for (const kid of Object.keys(shard.r)) {\n if (shard.r[kid]!.t <= clearedAt) delete shard.r[kid];\n }\n }\n for (const { kid, rec, changed } of ops) {\n if (rec.t <= shard.cleared) continue;\n shard.r[kid] = mergeRecord(shard.r[kid], rec, changed);\n }\n // Tombstones only matter until every device has seen them.\n for (const kid of Object.keys(shard.r)) {\n const rec = shard.r[kid]!;\n if (rec.d && now - rec.t > TOMBSTONE_TTL_MS) delete shard.r[kid];\n }\n const before = Date.now();\n const r = await this.deps.remote.putJSON(path, shard, etag ? { ifMatch: etag } : { ifNoneMatch: true });\n if (r.status === \"ok\") {\n if (r.serverTime) this.clock.observe(r.serverTime, before, Date.now());\n this.etags.set(path, r.etag);\n return r.etag;\n }\n this.etags.delete(path);\n }\n throw new Error(`could not write ${path}: too many concurrent writers`);\n }\n\n private async storeBlob(blob: Blob): Promise<BlobRef> {\n const bytes = new Uint8Array(await blob.arrayBuffer());\n const digest = await crypto.subtle.digest(\"SHA-256\", bytes);\n const hex = Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n const ref = `${this.options.prefix}/_blobs/${hex}`;\n await this.deps.remote.putBlob(ref, blob);\n return { ref, type: blob.type, size: blob.size };\n }\n\n // ── Pull ─────────────────────────────────────────────────────────\n\n private async pull(): Promise<void> {\n const listing = await this.deps.remote.list(this.options.prefix + \"/\");\n await this.applyListing(listing);\n }\n\n private async pullDatabase(name: string): Promise<void> {\n if (!this.deps.userId()) return;\n await this.loadMeta();\n const listing = await this.deps.remote.list(`${this.options.prefix}/${pathSeg(name)}/`);\n await this.applyListing(listing);\n }\n\n private async applyListing(listing: { path: string; etag: string }[]): Promise<void> {\n const re = new RegExp(\"^\" + this.options.prefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\") + \"/([^/]+)/([^/]+)/(\\\\d+)\\\\.json$\");\n const pending = await this.log.pending();\n const etagUpdates: Record<string, unknown> = {};\n for (const entry of listing) {\n const m = re.exec(entry.path);\n if (!m) continue;\n const db = decodeURIComponent(m[1]!);\n const store = decodeURIComponent(m[2]!);\n if (this.etags.get(entry.path) === entry.etag) continue;\n if (this.storeExcluded(db, store)) continue;\n const schema = this.schemas.get(db);\n if (!schema || !schema.has(store)) continue; // the app has not created it (yet)\n const r = await this.deps.remote.getJSON(entry.path, this.etags.get(entry.path));\n if (r.status === \"unchanged\") {\n continue;\n }\n if (r.status === \"missing\") {\n this.etags.delete(entry.path);\n etagUpdates[\"etag:\" + entry.path] = undefined;\n continue;\n }\n const s = r.value as Partial<Shard> | null;\n const shard: Shard = { v: 1, cleared: s?.cleared ?? 0, r: s?.r ?? {} };\n await this.applyShard(db, store, shard, pending);\n this.etags.set(entry.path, r.etag);\n etagUpdates[\"etag:\" + entry.path] = r.etag;\n }\n if (Object.keys(etagUpdates).length) await this.log.setMeta(etagUpdates);\n }\n\n private async applyShard(db: string, store: string, shard: Shard, pending: LoggedOp[]): Promise<void> {\n // A local write newer than the remote record wins until it is pushed.\n const localNewer = new Map<string, number>();\n let localClear = 0;\n for (const op of pending) {\n if (op.db !== db || op.store !== store) continue;\n if (op.kind === \"clear\") localClear = Math.max(localClear, op.ts);\n else localNewer.set(op.kid, Math.max(localNewer.get(op.kid) ?? 0, op.ts));\n }\n const clearKey = `cleared:${db}/${store}`;\n const appliedClear = (await this.log.getMeta<number>(clearKey)) ?? 0;\n const schema = this.schemas.get(db)?.get(store);\n const keys: IDBValidKey[] = [];\n let clearedLocally = false;\n await this.withStore(db, store, \"readwrite\", (st) => {\n if (shard.cleared > appliedClear && shard.cleared > localClear) {\n // Another device cleared this store after our last pull: drop the\n // local rows, keep what is still unpushed (replayed below).\n st.clear();\n clearedLocally = true;\n }\n for (const kid of Object.keys(shard.r)) {\n const rec = shard.r[kid]!;\n if ((localNewer.get(kid) ?? 0) >= rec.t) continue;\n if (rec.t <= localClear) continue;\n const key = decodeKey(rec.k);\n keys.push(key);\n if (rec.d) {\n st.delete(key);\n } else {\n // Values are decoded before the transaction (Blobs need the\n // network); see the pre-pass below.\n const value = (rec as ShardRecord & { _decoded?: unknown })._decoded;\n if (schema && schema.keyPath !== null) st.put(value);\n else st.put(value, key);\n }\n }\n if (clearedLocally) {\n for (const op of pending) {\n if (op.db !== db || op.store !== store || op.kind !== \"put\" || op.ts <= shard.cleared) continue;\n if (schema && schema.keyPath !== null) st.put(op.value);\n else st.put(op.value, decodeKey(op.key));\n }\n }\n }, async () => {\n for (const kid of Object.keys(shard.r)) {\n const rec = shard.r[kid]!;\n if (rec.d || (localNewer.get(kid) ?? 0) >= rec.t || rec.t <= localClear) continue;\n (rec as ShardRecord & { _decoded?: unknown })._decoded = await decodeValue(rec.v, (ref) =>\n this.deps.remote.getBlob(ref.ref),\n );\n }\n });\n if (shard.cleared > appliedClear) await this.log.setMeta({ [clearKey]: shard.cleared });\n if (keys.length > 0 || clearedLocally) {\n this.emitChange({ db, store, keys, cleared: clearedLocally, source: \"remote\" });\n }\n }\n\n private emitChange(change: DbChange): void {\n this.moved = true;\n this.deps.events.emit(\"db:change\", change);\n try {\n this.post(change);\n } catch {\n /* keys that cannot be cloned across tabs: the local event already fired */\n }\n }\n\n // ── The engine's own connections ─────────────────────────────────\n\n private conn(db: string): Promise<IDBDatabase> {\n let p = this.conns.get(db);\n if (!p) {\n p = new Promise<IDBDatabase>((resolve, reject) => {\n const open = this.hook ? this.hook.originalOpen : (n: string) => indexedDB.open(n);\n const r = open(db);\n r.onsuccess = () => {\n const h = r.result;\n // Step aside when the app upgrades its schema.\n h.onversionchange = () => {\n h.close();\n this.conns.delete(db);\n };\n resolve(h);\n };\n r.onerror = () => reject(r.error ?? new Error(`cannot open ${db}`));\n r.onblocked = () => reject(new Error(`open of ${db} blocked`));\n });\n this.conns.set(db, p);\n p.catch(() => this.conns.delete(db));\n }\n return p;\n }\n\n private async withStore(\n db: string,\n store: string,\n mode: IDBTransactionMode,\n fn: (st: IDBObjectStore) => void,\n prepare?: () => Promise<void>,\n ): Promise<void> {\n if (prepare) await prepare();\n const h = await this.conn(db);\n if (!h.objectStoreNames.contains(store)) return;\n const tx = h.transaction(store, mode);\n fn(tx.objectStore(store));\n await idbDone(tx);\n }\n}\n\nexport { idbRequest };\n","// sync/remote.ts — the sync engine's storage, over the files API.\n//\n// Shards are plain JSON files in the user's file space for the app, so\n// they share its quota, billing and deletion with everything else the\n// app stores for that user. Conditional reads (304) and writes (412 /\n// 409) come from the files API, which checks them against the store.\n\nimport type { FilesService } from \"../files.js\";\nimport type { APIError } from \"../errors.js\";\nimport type { SyncRemote } from \"./engine.js\";\n\nexport class FilesRemote implements SyncRemote {\n constructor(private readonly files: FilesService) {}\n\n async list(prefix: string): Promise<{ path: string; etag: string }[]> {\n const out: { path: string; etag: string }[] = [];\n let cursor: string | undefined;\n do {\n const page = await this.files.list({ prefix, limit: 1000, ...(cursor ? { cursor } : {}) });\n for (const f of page.files) if (f.etag) out.push({ path: f.path, etag: f.etag });\n cursor = page.next_cursor;\n } while (cursor);\n return out;\n }\n\n async getJSON(path: string, ifNoneMatch?: string) {\n const r = await this.files.readJSON(path, ifNoneMatch ? { ifNoneMatch } : undefined);\n if (r.notModified) return { status: \"unchanged\" as const };\n if (r.value === undefined) return { status: \"missing\" as const };\n return { status: \"ok\" as const, value: r.value, etag: r.etag ?? \"\" };\n }\n\n async putJSON(path: string, value: unknown, cond: { ifMatch?: string; ifNoneMatch?: boolean }) {\n try {\n const obj = await this.files.put(path, JSON.stringify(value), {\n contentType: \"application/json\",\n ...(cond.ifMatch ? { ifMatch: cond.ifMatch } : {}),\n ...(cond.ifNoneMatch ? { ifNoneMatch: true } : {}),\n });\n return { status: \"ok\" as const, etag: obj.etag ?? \"\", serverTime: obj.last_modified ? Date.parse(obj.last_modified) : undefined };\n } catch (e) {\n const code = (e as APIError).statusCode;\n if (code === 412 || code === 409) return { status: \"conflict\" as const };\n throw e;\n }\n }\n\n async putBlob(path: string, blob: Blob): Promise<void> {\n try {\n await this.files.put(path, blob, { contentType: blob.type || \"application/octet-stream\", ifNoneMatch: true });\n } catch (e) {\n if ((e as APIError).statusCode !== 409) throw e; // already stored: same bytes, same name\n }\n }\n\n getBlob(path: string): Promise<Blob> {\n return this.files.get(path);\n }\n}\n","import { Transport } from \"./transport.js\";\nimport { ChatService } from \"./chat.js\";\nimport { ModelsService } from \"./models.js\";\nimport { ImageService } from \"./image.js\";\nimport { AudioService } from \"./audio.js\";\nimport { RealtimeService } from \"./realtime.js\";\nimport { VideoService } from \"./video.js\";\nimport { ComputerService } from \"./computer.js\";\nimport { FilesService } from \"./files.js\";\nimport { EmbeddingsService } from \"./embeddings.js\";\nimport { BrowserAuth, type BrowserUser } from \"./browser-auth.js\";\nimport { FilesRemote, SyncEngine, type SyncOptions, type SyncState } from \"./sync/index.js\";\nimport { SDKEvents } from \"./events.js\";\n\n// The platform, not the bare gateway: personal rk_ keys are checked and\n// billed by www.runapps.ai, which forwards to the gateway. api.runapps.ai\n// only knows the gateway's own keys.\nconst DEFAULT_BASE_URL = \"https://www.runapps.ai\";\n\n/**\n * Auth strategy.\n *\n * - `\"static\"` (default): use `apiKey` or `apiKeyResolver` directly.\n * - `\"runapps\"`: opt into the built-in browser auth flow that\n * handshakes with `https://www.runapps.ai/api/sdk/grant`. No\n * external auth.js script needed; the SDK consumes the URL\n * fragment after the redirect-back, exposes `client.user` and\n * `client.signIn()`, and renders an identity badge. Only useful\n * in browsers — in Node this falls back to throwing the same\n * \"missing apiKey\" error you'd get with no auth config.\n */\n// \"runapps\" turns on the platform sign-in. \"runjobs\" is a deprecated\n// alias for it, kept so code written before the rename keeps working.\nexport type AuthProvider = \"static\" | \"runapps\" | \"runjobs\";\n\nexport interface ClientOptions {\n /** A personal API key (`rk_…`) or another static key. Leave it out in an\n * app published on RunApps: the browser client then signs the user in. */\n apiKey?: string;\n /**\n * Dynamic API key resolver. See `AuthProvider` for the typical\n * runapps.ai browser-bundle wiring.\n *\n * Wins over `apiKey` when both are supplied.\n */\n apiKeyResolver?: () => string | Promise<string>;\n /**\n * Auth strategy. Rarely needed: in a browser with no `apiKey` /\n * `apiKeyResolver` the client uses `\"runapps\"` (the platform sign-in),\n * otherwise `\"static\"` (the key you pass).\n */\n authProvider?: AuthProvider;\n /**\n * Show the floating identity badge in `runapps` auth mode.\n * **Default `true`** — the badge is now a desktop-ball-style\n * real-time activity indicator (LED dot + progress ring around\n * the avatar + click-through popover with active calls / recent\n * completions / session totals). Set `false` only when the\n * bundle ships its own status UI and you don't want the platform\n * badge in the corner.\n */\n showIdentityBadge?: boolean;\n /**\n * Which corner the identity badge floats in. Defaults to\n * `\"bottom-right\"` — the canonical tray-app position. Switch to\n * `\"bottom-left\"` when the bundle's own UI occupies the right\n * gutter (e.g. a chat dock); the top corners are useful for\n * fullscreen / portrait kiosk layouts.\n *\n * The popover anchors itself to the same corner — bottom-left\n * badge → popover opens up + right of the badge; top-* badges →\n * popover opens down. Each corner only nudges the CSS; the badge\n * + popover content is identical.\n */\n badgePosition?: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\";\n /**\n * @deprecated Use `showIdentityBadge` instead. Kept for back-compat;\n * acts as `showIdentityBadge: !hideIdentityBadge` when set.\n */\n hideIdentityBadge?: boolean;\n /**\n * Pin the runapps.ai grant flow to a specific project (only used\n * when `authProvider: \"runapps\"`). When set, `/api/sdk/grant`\n * receives `project_id=<value>` and mints an `rrt_*` resource\n * token bound to THAT project — required for `client.files.*`\n * calls from any origin that isn't a registered (origin, app)\n * pair (e.g. a bundle running on `localhost:5173` during dev).\n *\n * Production bundles served from a registered origin can omit\n * this; the gateway derives the project from the (origin, app)\n * pair.\n */\n project?: string;\n /** Override the default base URL, `https://www.runapps.ai` (the\n * platform: personal keys and app tokens are both accepted there). */\n baseURL?: string;\n /** Optional fetch override (e.g. node-fetch with custom agent). */\n fetch?: typeof fetch;\n /**\n * RunApps Sync: keep the data the app stores in IndexedDB on every\n * device the user signs in on. **On by default** in an app on RunApps\n * (browser sign-in mode): the SDK observes every database the app\n * opens, pushes committed writes a few seconds later, pulls other\n * devices' changes when the page is opened, focused or comes back\n * online, and shows the state on the badge. Pass `false` to turn it\n * off, or options to limit it to some databases / leave stores out.\n * See `client.sync`.\n */\n sync?: boolean | SyncOptions;\n}\n\n/**\n * Top-level RunApps SDK client. Construct once, share across services.\n *\n * Browser bundle (resource projects on runapps.ai):\n *\n * ```html\n * <script src=\"https://cdn.jsdelivr.net/npm/@runappsai/sdk/dist/sdk.umd.js\"></script>\n * <script>\n * const client = new RunApps();\n * const res = await client.chat.create({\n * model: \"gpt-4o-mini\",\n * messages: [{ role: \"user\", content: \"hello\" }],\n * });\n * </script>\n * ```\n *\n * Node / server-side:\n *\n * ```ts\n * import { RunApps } from \"@runappsai/sdk\";\n * const client = new RunApps({ apiKey: process.env.RUNAPPS_API_KEY! });\n * ```\n */\nexport class RunApps {\n readonly chat: ChatService;\n readonly models: ModelsService;\n readonly image: ImageService;\n readonly audio: AudioService;\n readonly video: VideoService;\n readonly computer: ComputerService;\n readonly files: FilesService;\n readonly embeddings: EmbeddingsService;\n /** Realtime voice sessions over WebSocket — see `RealtimeService.connect`. */\n readonly realtime: RealtimeService;\n\n /**\n * Browser auth helper, populated only when `authProvider: \"runapps\"`.\n * Use `client.signIn()` and `client.user` for the common cases; the\n * full instance is exposed for advanced flows (manual `onTokenChange`\n * subscription, etc.).\n */\n readonly auth: BrowserAuth | null = null;\n /**\n * RunApps Sync (see `ClientOptions.sync`), or null when it\n * is off. `status()` gives the state the badge shows; `now()` pushes\n * and pulls immediately; `client.events.on(\"db:change\", …)` tells the\n * app which store received remote changes so it can re-read.\n */\n readonly sync: { status(): SyncState; now(): Promise<void>; pause(): void; resume(): void; dispose(): void } | null = null;\n\n /**\n * Runtime event bus for SDK-call telemetry. Every LLM-ish service\n * (chat / embeddings / image / audio / video / computer) fires\n * `request:start`, `request:streamDelta`, `request:end`, and\n * `request:error` events here so UI overlays — like the bottom-right\n * identity badge's activity ring — can render real-time state\n * without business code threading anything through.\n *\n * Zero-cost when nobody subscribes (emit is a few property reads).\n * Subscribe via `client.events.on(\"request:start\", handler)` — the\n * returned closure is the unsubscribe.\n */\n readonly events: SDKEvents = new SDKEvents();\n\n constructor(options: ClientOptions = {}) {\n // With no key and no explicit authProvider, a browser client uses the\n // platform sign-in: `new RunApps()` is all an app needs. Elsewhere\n // (Node, workers without a window) there is no one to sign in.\n const provider =\n options.authProvider ??\n (!options.apiKey && !options.apiKeyResolver && typeof window !== \"undefined\"\n ? \"runapps\"\n : \"static\");\n let apiKeyResolver = options.apiKeyResolver;\n let baseURL = options.baseURL;\n let onUnauthorized: (() => void) | undefined;\n\n if (provider === \"runapps\" || provider === \"runjobs\") {\n // The grant page (/api/sdk/grant) and the gateway both live on\n // www.runapps.ai. An explicit baseURL keeps control.\n baseURL = baseURL ?? \"https://www.runapps.ai\";\n // Badge default: SHOWN. The badge is now a real-time activity\n // indicator (LED + ring + popover) — useful enough that we'd\n // rather have the rare \"I already have my own UI\" bundle opt\n // OUT with `showIdentityBadge: false` than have every bundle\n // miss the live feedback by default. The legacy\n // `hideIdentityBadge` flag still wins when set, for callers\n // that explicitly suppressed the badge under the old default.\n const showBadge =\n options.showIdentityBadge !== undefined\n ? options.showIdentityBadge\n : options.hideIdentityBadge !== undefined\n ? !options.hideIdentityBadge\n : true;\n const auth = new BrowserAuth({\n origin: baseURL,\n hideBadge: !showBadge,\n // Pipe the shared event bus into BrowserAuth so the badge can\n // render its real-time activity ring / LED / popover. Safe to\n // pass even when the caller never subscribes — emit is a few\n // property reads when nobody's listening.\n events: this.events,\n ...(options.badgePosition !== undefined && { badgePosition: options.badgePosition }),\n ...(options.project !== undefined && { project: options.project }),\n });\n (this as { auth: BrowserAuth | null }).auth = auth;\n // The browser auth resolver wins over any caller-supplied one\n // because the auth flow owns token issuance + refresh from now on.\n apiKeyResolver = auth.getToken;\n // 401 from the gateway → token was revoked server-side (typically\n // because the user unsubscribed in another tab). Drop the cached\n // token so the transport's auto-retry hits a fresh signIn() flow.\n onUnauthorized = () => auth.invalidate();\n }\n\n if (!options.apiKey && !apiKeyResolver) {\n throw new Error(\n \"runapps: pass `apiKey` (or `apiKeyResolver`); outside a browser there is no sign-in to fall back to\",\n );\n }\n const transport = new Transport({\n baseURL: baseURL ?? DEFAULT_BASE_URL,\n ...(options.apiKey !== undefined && { apiKey: options.apiKey }),\n ...(apiKeyResolver !== undefined && { apiKeyResolver }),\n ...(onUnauthorized !== undefined && { onUnauthorized }),\n ...(options.fetch ? { fetchImpl: options.fetch } : {}),\n });\n // Service constructors receive the shared event bus so each call\n // path (chat/embeddings/image/audio/video/computer) can emit\n // start/end/error around its network operations. Files and Models\n // intentionally don't take it — they're infrastructure, not\n // user-facing LLM calls, and surfacing them would clutter the\n // badge with /v1/models polls.\n this.chat = new ChatService(transport, this.events);\n this.models = new ModelsService(transport);\n this.image = new ImageService(transport, this.events);\n this.audio = new AudioService(transport, this.events);\n this.video = new VideoService(transport, this.events);\n this.computer = new ComputerService(transport, this.events);\n // `project` also scopes the file URLs (/v1/p/<project>/files/...),\n // so two apps never issue byte-identical file requests. Omitted →\n // legacy unscoped /v1/files/... paths.\n this.files = new FilesService(transport, options.project, this.events);\n this.embeddings = new EmbeddingsService(transport, this.events);\n this.realtime = new RealtimeService(transport, this.events);\n\n // Sync: an app on RunApps gets it unless it says no. It\n // stores under the signed-in user, so only browser sign-in mode has\n // someone to sync for.\n const wantSync = options.sync ?? this.auth !== null;\n if (wantSync && typeof indexedDB !== \"undefined\" && this.auth) {\n const auth = this.auth;\n const engine = new SyncEngine(\n {\n // Its own file service, without the event bus: sync traffic\n // must not spin the badge every minute.\n remote: new FilesRemote(new FilesService(transport, options.project)),\n events: this.events,\n userId: () => auth.ownerId(),\n onAuthChange: (cb) => auth.onTokenChange(() => cb()),\n },\n typeof wantSync === \"object\" ? wantSync : {},\n );\n (this as { sync: RunApps[\"sync\"] }).sync = engine;\n }\n }\n\n /** Force a redirect to the runapps.ai grant page. No-op in static\n * auth mode or in Node. */\n signIn(): void {\n this.auth?.signIn();\n }\n\n /**\n * Clear the cached token + identity from memory and localStorage.\n * The next API call will trigger sign-in again (redirect to grant\n * page). Wire this to a \"Sign out\" button in your settings UI.\n *\n * No-op in static auth mode or in Node.\n */\n signOut(): void {\n this.auth?.signOut();\n }\n\n /** Currently-authenticated user (runapps auth mode), or null. */\n get user(): BrowserUser | null {\n return this.auth?.user ?? null;\n }\n}\n","// Mirror of internal/api/options_schema/types.go on the JS side.\n// See API.md \"Options schema\" for the full contract; the gateway\n// materialises this from the legacy options blob at /v1/models\n// response time.\n\nimport type { Model } from \"./models.js\";\n\n/** Wire-format options shape returned by /v1/models. */\nexport interface Schema {\n /**\n * Per-field constraints, keyed by canonical request field name\n * (matching the JSON keys on Image/Video/Speech/Embed/Transcribe\n * params). A field absent from this map is NOT accepted by the\n * model.\n */\n inputs?: Record<string, FieldSchema>;\n /** Cross-field rules. */\n constraints?: Constraint[];\n /** Rich content (TTS voices, emotions). */\n catalog?: Catalog;\n}\n\nexport interface FieldSchema {\n type:\n | \"string\"\n | \"int\"\n | \"float\"\n | \"bool\"\n | \"url\"\n | \"url[]\"\n | \"string[]\"\n | \"file\";\n /** Default when omitted is \"optional\". */\n presence?: \"required\" | \"optional\" | \"forbidden\";\n min?: number;\n max?: number;\n default?: unknown;\n enum?: unknown[];\n max_items?: number;\n /** Lower-bound counterpart of max_items. Applies to array types\n * AND `repeatable` object groups. Nil = no minimum. */\n min_items?: number;\n /** Semantic hint for media-input fields. */\n role?: \"source\" | \"reference\" | \"motion\";\n /** When present, this field is a structured sub-object. The wire\n * value is `{<inner-name>: <value>, ...}` matching this inner\n * schema; renderers recurse into it. With `repeatable: true` the\n * wire value becomes an array of such objects (e.g. multi-utterance\n * TTS \"+ add another group\"). */\n fields?: Record<string, FieldSchema>;\n /** Pairs with `fields`: array-of-objects rather than a single\n * nested object. Ignored when `fields` is absent. */\n repeatable?: boolean;\n\n // ─── Display metadata — drives auto-rendered playground / form UIs.\n // All optional; renderers fall back to type/role-based rendering\n // when absent. See API.md \"Options schema\" for the full vocabulary.\n\n /** Human-readable display label. Falls back to the field name. */\n label?: string;\n /** One-sentence tooltip / sub-text explainer. */\n help?: string;\n /**\n * Widget override hint:\n * \"textarea\" — large multi-line string\n * \"slider\" — numeric with min/max\n * \"radio\" — small enum\n * \"voice_picker\" — string with values from catalog.voices\n * \"code\" — long string with syntax highlighting\n * \"segmented\" — pill-shaped row of enum values (compact,\n * horizontal). Best for short ordered enums\n * like the resolution tier (512P / 1K / 2K /\n * 4K) in image generation. Renders as a single\n * line; selected option has solid background.\n * \"tile\" — grid of square cards, each carrying a small\n * visual proxy for the value (aspect-ratio\n * shape icon, or fit-brackets for the\n * \"adaptive\" sentinel). Used for aspect_ratio\n * in image generation. When the enum includes\n * \"adaptive\" or \"auto\", that entry is\n * promoted to a row-spanning anchor block.\n * \"tabs\" — horizontal tab strip. Same data contract as\n * \"radio\" but a different visual. Drives\n * `ui.show_when` on sibling fields.\n * \"card_group\" — bordered card containing the field's nested\n * `fields`. With `repeatable: true` the card\n * becomes a \"+ add another\"-able stack.\n * Unknown values fall back to the type default (forward-compat).\n */\n widget?: string;\n /**\n * Nested presentational hints:\n * \"group\" (string) — sectional grouping (\"main\"/\"advanced\"/\"output\").\n * \"order\" (int) — display order within the group; lower first.\n * \"card_label\" (string) — title above a card_group; \"{{i}}\" is\n * substituted with the card index for\n * repeatable groups.\n * \"show_when\" ({field, equals})\n * — purely-visual conditional: hide this\n * field unless a sibling's value matches.\n * Validator ignores it; server still\n * accepts conditionally-hidden fields.\n */\n ui?: {\n group?: string;\n order?: number;\n card_label?: string;\n show_when?: { field: string; equals: unknown };\n [k: string]: unknown;\n };\n}\n\nexport type ConstraintKind =\n | \"any_of_required\"\n | \"mutually_exclusive\"\n | \"group_mutex\"\n | \"requires_all\"\n | \"pixel_bounds\";\n\nexport interface Group {\n /** Stable id; also the value the discriminator field\n * (`Constraint.name`) takes when this group is active. */\n name?: string;\n /** Tab strip display label. Falls back to `name`. */\n label?: string;\n fields: string[];\n}\n\nexport interface Constraint {\n kind: ConstraintKind;\n fields?: string[];\n /**\n * On a `group_mutex` constraint, names the discriminator field\n * selecting which group is active. The renderer auto-injects a\n * `tabs` input with this name (enum = group names) and gives\n * every group member an implicit `show_when` pointing at it.\n * When omitted, the constraint behaves as legacy anonymous-group\n * mutex — all members render flat, mutex enforced at submit.\n */\n name?: string;\n /**\n * Human-readable title for the synthesized tab widget (e.g.\n * \"Animation mode\"). Falls back to `name` when omitted.\n */\n label?: string;\n /**\n * Used by `group_mutex`. Wire shape is either the legacy\n * `string[][]` (each `[a,b,...]` reads as an anonymous\n * `{fields:[a,b,...]}` Group) or the new `Group[]`. Consumers\n * should normalize via `normalizeGroups()` before reading.\n */\n groups?: Group[] | string[][];\n when?: string;\n then?: string[];\n min?: number;\n max?: number;\n}\n\n/**\n * Normalize either legacy `string[][]` or new `Group[]` into a\n * uniform `Group[]`. Mirror of sdk-go's `Group.UnmarshalJSON`.\n */\nexport function normalizeGroups(g: Constraint[\"groups\"]): Group[] {\n if (!g) return [];\n return g.map(item =>\n Array.isArray(item) ? { fields: item as string[] } : (item as Group),\n );\n}\n\nexport interface Catalog {\n voices?: Array<Record<string, unknown>>;\n emotions?: string[];\n}\n\n/**\n * Parse the model's wire-format `options` blob into the typed Schema.\n * Returns null when the model has no options or the blob can't be\n * structurally decoded — callers fall back to whatever default\n * behaviour they had pre-schema.\n */\nexport function getOptionsSchema(model: Model): Schema | null {\n const opts = model.options;\n if (!opts || typeof opts !== \"object\") return null;\n // The Model interface types options as Record<string, unknown>;\n // Schema has the same JSON layout, so a structural cast is safe\n // as long as the gateway emits the wire shape we expect. SDK\n // consumers should call validateRequest() to surface errors —\n // this getter is a thin typed view over the same data.\n return opts as unknown as Schema;\n}\n\n/**\n * True iff the model declares an Inputs entry for `name` with\n * presence != \"forbidden\". Use for \"should I show this UI chip\"\n * decisions.\n */\nexport function acceptsField(model: Model, name: string): boolean {\n const schema = getOptionsSchema(model);\n const f = schema?.inputs?.[name];\n if (!f) return false;\n return f.presence !== \"forbidden\";\n}\n\n/**\n * True iff the schema marks `name` as presence: \"required\". Use for\n * red-star UI marks and submit-button gating.\n */\nexport function requiresField(model: Model, name: string): boolean {\n const schema = getOptionsSchema(model);\n return schema?.inputs?.[name]?.presence === \"required\";\n}\n\n/**\n * Discrete enum for the named field, or null if no enum constraint.\n * Use to populate dropdown options on the client.\n */\nexport function allowedValuesFor(model: Model, name: string): unknown[] | null {\n const schema = getOptionsSchema(model);\n return schema?.inputs?.[name]?.enum ?? null;\n}\n","// Generic validator for the gateway's options schema. Mirrors\n// /Users/zero/repos/sdk-go/validate.go — three-pass algorithm,\n// same field/constraint vocabulary, same parse logic.\n\nimport { normalizeGroups } from \"./model_options.js\";\nimport type { Schema, FieldSchema, Constraint, Group } from \"./model_options.js\";\n\nexport interface ValidationError {\n field: string;\n reason: string;\n}\n\n/**\n * Three-pass validator: per-field type/enum/bounds → per-field\n * presence → cross-field constraints. Unknown fields in `req`\n * (vendor passthroughs) are silently ignored — the gateway still\n * validates them server-side if it cares.\n *\n * Returns an empty array on success, or a flat list of all\n * validation failures so the UI can surface them at once.\n */\nexport function validateRequest(\n schema: Schema | null | undefined,\n req: Record<string, unknown> = {},\n): ValidationError[] {\n if (!schema) return [];\n const errors: ValidationError[] = [];\n\n // Pass 1+2: per-field.\n if (schema.inputs) {\n for (const [name, field] of Object.entries(schema.inputs)) {\n const set = name in req;\n const val = req[name];\n if (set && !isEmpty(val)) {\n errors.push(...validateFieldValue(name, field, val));\n } else if (set && field.min_items !== undefined) {\n // Empty array trips min_items even when isEmpty() short-\n // circuits validateFieldValue. Mirrors the sdk-go fix: bound\n // was declared explicitly, dropping it silently would let a\n // client POST `groups: []` to a schema that requires >=1.\n if (Array.isArray(val) && val.length < field.min_items) {\n errors.push({ field: name, reason: `at least ${field.min_items} items` });\n }\n }\n errors.push(...validatePresence(name, field, val, set));\n }\n }\n\n // Pass 3: cross-field.\n for (const c of schema.constraints ?? []) {\n errors.push(...validateConstraint(c, req));\n }\n\n return errors;\n}\n\nfunction validateFieldValue(\n name: string,\n field: FieldSchema,\n val: unknown,\n): ValidationError[] {\n const errors: ValidationError[] = [];\n\n if (!typeMatches(field.type, val)) {\n errors.push({\n field: name,\n reason: `expected type ${field.type}, got ${describeType(val)}`,\n });\n return errors; // bail early — bound/enum checks need correct type\n }\n\n if (field.enum && field.enum.length > 0 && !inEnum(val, field.enum)) {\n errors.push({\n field: name,\n reason: `must be one of ${JSON.stringify(field.enum)}`,\n });\n }\n\n if (field.type === \"int\" || field.type === \"float\") {\n const n = numberValue(val);\n if (n !== null) {\n if (field.min !== undefined && n < field.min) {\n errors.push({ field: name, reason: `must be >= ${field.min}` });\n }\n if (field.max !== undefined && n > field.max) {\n errors.push({ field: name, reason: `must be <= ${field.max}` });\n }\n }\n }\n\n // Array bounds — apply to scalar array Types (\"url[]\", \"string[]\")\n // AND to `repeatable` object groups whose value is a JSON array.\n if (Array.isArray(val)) {\n if (field.max_items !== undefined && val.length > field.max_items) {\n errors.push({\n field: name,\n reason: `at most ${field.max_items} items`,\n });\n }\n if (field.min_items !== undefined && val.length < field.min_items) {\n errors.push({\n field: name,\n reason: `at least ${field.min_items} items`,\n });\n }\n }\n\n // Nested object recursion. `field.fields` declares a structured\n // sub-object; with `repeatable: true` the wire value is an array of\n // such objects. Errors carry a dotted path like\n // \"groups[1].prompt: required\" so callers see the offending row.\n // `ui.show_when` is deliberately NOT honoured at validation time —\n // it's a UI hint only; conditionally-hidden fields are still\n // subject to the same checks if a client sends them.\n if (field.fields) {\n if (field.repeatable) {\n if (!Array.isArray(val)) {\n errors.push({\n field: name,\n reason: `expected array of objects, got ${describeType(val)}`,\n });\n } else {\n val.forEach((item, i) => {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) {\n errors.push({\n field: `${name}[${i}]`,\n reason: `expected object, got ${describeType(item)}`,\n });\n return;\n }\n errors.push(...validateObject(`${name}[${i}]`, field.fields!, item as Record<string, unknown>));\n });\n }\n } else {\n if (typeof val !== \"object\" || val === null || Array.isArray(val)) {\n errors.push({\n field: name,\n reason: `expected object, got ${describeType(val)}`,\n });\n } else {\n errors.push(...validateObject(name, field.fields, val as Record<string, unknown>));\n }\n }\n }\n\n return errors;\n}\n\n/** validateObject runs per-field validation against a sub-schema's\n * `fields` map for one object value. Errors return with\n * `<pathPrefix>.<innerName>` so the caller sees the full nested path. */\nfunction validateObject(\n pathPrefix: string,\n fields: Record<string, FieldSchema>,\n obj: Record<string, unknown>,\n): ValidationError[] {\n const errors: ValidationError[] = [];\n for (const [innerName, innerField] of Object.entries(fields)) {\n const childPath = `${pathPrefix}.${innerName}`;\n const val = obj[innerName];\n const set = innerName in obj;\n if (set && !isEmpty(val)) {\n errors.push(...validateFieldValue(childPath, innerField, val));\n }\n errors.push(...validatePresence(childPath, innerField, val, set));\n }\n return errors;\n}\n\nfunction validatePresence(\n name: string,\n field: FieldSchema,\n val: unknown,\n set: boolean,\n): ValidationError[] {\n const presence = field.presence ?? \"optional\";\n if (presence === \"required\" && (!set || isEmpty(val))) {\n return [{ field: name, reason: \"required\" }];\n }\n if (presence === \"forbidden\" && set && !isEmpty(val)) {\n return [{ field: name, reason: \"must not be set for this model\" }];\n }\n return [];\n}\n\nfunction validateConstraint(\n c: Constraint,\n req: Record<string, unknown>,\n): ValidationError[] {\n switch (c.kind) {\n case \"any_of_required\": {\n const fields = c.fields ?? [];\n const anySet = fields.some((f) => f in req && !isEmpty(req[f]));\n if (anySet) return [];\n return [\n {\n field: fields.join(\"/\"),\n reason: `at least one of ${fields.join(\", \")} is required`,\n },\n ];\n }\n\n case \"mutually_exclusive\": {\n const fields = c.fields ?? [];\n const setFields = fields.filter((f) => f in req && !isEmpty(req[f]));\n if (setFields.length > 1) {\n return [\n {\n field: setFields.join(\"/\"),\n reason: `at most one of ${fields.join(\", \")} may be set (got: ${setFields.join(\", \")})`,\n },\n ];\n }\n return [];\n }\n\n case \"group_mutex\": {\n // Best-effort: enforce only the anonymous \"at most one group\n // active\" rule for both shapes. The strict discriminator-based\n // check (validating `c.name`'s value + rejecting non-active\n // group fields) lives in the gateway only — SDK validation\n // stays a fast pre-flight hint, not a contract enforcer, so we\n // avoid doubling the maintenance surface across SDKs.\n const groups: Group[] = normalizeGroups(c.groups);\n const activeGroups: Group[] = [];\n const activeFields: string[] = [];\n for (const g of groups) {\n const setHere = g.fields.filter((f) => f in req && !isEmpty(req[f]));\n if (setHere.length > 0) {\n activeGroups.push(g);\n activeFields.push(...setHere);\n }\n }\n if (activeGroups.length > 1) {\n const labels = groups.map((g) => `[${g.fields.join(\"+\")}]`).join(\" or \");\n return [\n {\n field: activeFields.join(\"/\"),\n reason: `at most one of ${labels} may be used (got: ${activeFields.join(\", \")})`,\n },\n ];\n }\n return [];\n }\n\n case \"requires_all\": {\n const whenName = c.when;\n if (!whenName) return [];\n const whenSet = whenName in req && !isEmpty(req[whenName]);\n if (!whenSet) return [];\n const missing = (c.then ?? []).filter(\n (f) => !(f in req) || isEmpty(req[f]),\n );\n if (missing.length === 0) return [];\n return [\n {\n field: missing.join(\"/\"),\n reason: `required when ${whenName} is set`,\n },\n ];\n }\n\n case \"pixel_bounds\": {\n const fieldName = c.fields?.[0];\n if (!fieldName) return [];\n const raw = req[fieldName];\n if (typeof raw !== \"string\" || raw === \"\") return [];\n const dims = parsePixelDims(raw);\n if (!dims) {\n return [{ field: fieldName, reason: `expected WxH dimensions, got \"${raw}\"` }];\n }\n const [w, h] = dims;\n const px = w * h;\n const errs: ValidationError[] = [];\n if (c.min !== undefined && px < c.min) {\n errs.push({\n field: fieldName,\n reason: `image too small: ${w}x${h} = ${px} pixels, minimum ${c.min}`,\n });\n }\n if (c.max !== undefined && px > c.max) {\n errs.push({\n field: fieldName,\n reason: `image too large: ${w}x${h} = ${px} pixels, maximum ${c.max}`,\n });\n }\n return errs;\n }\n }\n // Unknown constraint kind — skip (forward-compat).\n return [];\n}\n\n// ─── Type helpers ────────────────────────────────────────────────────\n\nfunction typeMatches(typ: string, val: unknown): boolean {\n switch (typ) {\n case \"string\":\n return typeof val === \"string\";\n case \"int\":\n case \"float\":\n return numberValue(val) !== null;\n case \"bool\":\n return typeof val === \"boolean\";\n case \"url\":\n return typeof val === \"string\" && isURLLike(val);\n case \"url[]\":\n return Array.isArray(val) && val.every((v) => typeof v === \"string\" && isURLLike(v));\n case \"string[]\":\n return Array.isArray(val) && val.every((v) => typeof v === \"string\");\n case \"file\":\n return true; // multipart — can't validate\n default:\n return true; // unknown type — forward-compat\n }\n}\n\nfunction describeType(val: unknown): string {\n if (val === null) return \"null\";\n if (Array.isArray(val)) return \"array\";\n return typeof val;\n}\n\nfunction numberValue(val: unknown): number | null {\n if (typeof val === \"number\" && Number.isFinite(val)) return val;\n return null;\n}\n\nfunction isURLLike(s: string): boolean {\n return s.startsWith(\"http://\") || s.startsWith(\"https://\") || s.startsWith(\"data:\");\n}\n\nfunction isEmpty(val: unknown): boolean {\n if (val === undefined || val === null) return true;\n if (typeof val === \"string\") return val === \"\";\n if (Array.isArray(val)) return val.length === 0;\n return false;\n}\n\nfunction inEnum(val: unknown, enumVals: unknown[]): boolean {\n for (const e of enumVals) {\n if (equalLoose(val, e)) return true;\n }\n return false;\n}\n\nfunction equalLoose(a: unknown, b: unknown): boolean {\n // Numbers compared as JS numbers (already float64-equivalent);\n // strings/bools by ===.\n return a === b;\n}\n\nfunction parsePixelDims(s: string): [number, number] | null {\n for (const sep of [\"x\", \"X\", \"*\", \"×\"]) {\n const idx = s.indexOf(sep);\n if (idx <= 0) continue;\n const w = parseInt(s.slice(0, idx).trim(), 10);\n const h = parseInt(s.slice(idx + sep.length).trim(), 10);\n if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {\n return [w, h];\n }\n }\n return null;\n}\n"],"mappings":"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","names":["src_exports","__export","APIError","ActivityTracker","BrowserAuth","RunApps","FilesRemote","RealtimeSession","SDKEvents","ServerTools","SyncEngine","acceptsField","acceptsModality","allowedValuesFor","assistantMessage","audioPart","base64ToBytes","bytesToBase64","decodeMediaUrl","defaultVoice","encodeImageUrl","float32FromPCM16","getOptionsSchema","hasCapabilityTag","imagePart","installIndexedDBHook","newRequestId","pcm16FromFloat32","requiresField","supportsInstructText","supportsVoiceClone","systemMessage","textPart","toolResultMessage","userMessage","userMessageParts","validateRequest","videoPart","APIError","statusCode","type","message","__publicField","Transport","opts","__publicField","path","build","url","resp","init","body","headers","cleanETag","serverTime","parseServerDate","etag","buf","contentType","form","text","type","message","parsed","APIError","raw","h","t","SDKEvents","__publicField","name","handler","set","wrapped","payload","h","err","newRequestId","wrapEvents","events","meta","fn","extractEnd","id","newRequestId","startedAt","result","info","err","e","estimateTokens","text","wrapStream","source","extractDelta","totalTokens","lastUsageTokens","costUSD","finishReason","chunk","delta","ServerTools","userMessage","content","systemMessage","assistantMessage","toolResultMessage","toolCallId","userMessageParts","parts","textPart","text","imagePart","url","detail","videoPart","audioPart","ChatService","transport","events","__publicField","params","init","body","wrapEvents","r","sourceGen","resp","parseSSE","wrapStream","chunk","decoder","reader","buffer","value","done","nl","line","data","parsed","e","err","APIError","hasCapabilityTag","model","id","acceptsModality","modality","optBool","key","v","supportsVoiceClone","supportsInstructText","defaultVoice","ModelsService","transport","__publicField","opts","init","path","ImageService","transport","events","__publicField","model","params","init","wrapEvents","r","interval","signal","current","sleep","id","form","buildEditForm","ms","resolve","reject","timer","onAbort","toBlob","input","ct","guessMime","filename","encodeImageUrl","bytes","u8","mime","sniffImageMime","CHUNK","binary","b64","decodeMediaUrl","url","rest","semi","comma","base64ToBytes","resp","buf","bin","out","i","b","AudioService","transport","events","__publicField","model","params","init","wrapEvents","raw","buildSpeechBody","bytes","contentType","decodeMediaUrl","r","submit","waitSpeechJob","form","buildTranscribeForm","waitTranscribeJob","jobId","path","interval","sleep","internalDeadline","status","assembleTranscribeResponse","payload","out","k","v","ms","signal","resolve","reject","timer","onAbort","extra","canonical","body","g","ct","guessAudioMime","blob","filename","OPEN","CLOSE_TIMEOUT_MS","RealtimeSession","ws","__publicField","resolve","ev","event","id","w","type","handler","set","pcm16","bytesToBase64","asBytes","text","session","callId","output","timer","h","err","RealtimeService","transport","events","options","WS","token","realtimeURL","model","newRequestId","startedAt","reject","fail","onAbort","settled","offCreated","offError","e","openingEvent","info","baseURL","u","k","v","pcm16FromFloat32","samples","sampleRate","targetRate","ratio","n","out","s","pos","j","frac","a","b","float32FromPCM16","bytes","view","i","bin","base64ToBytes","b64","VideoService","transport","events","__publicField","model","params","init","wrapEvents","taskID","opts","interval","status","sleep","ms","signal","resolve","reject","timer","onAbort","ComputerService","transport","events","__publicField","model","params","init","wrapEvents","r","pathToURL","base","p","trimmed","FilesService","transport","project","events","__publicField","op","fn","wrapEvents","path","body","opts","headers","cleanETag","content","init","data","contentType","etag","notModified","e","status","size","params","qs","from","to","srcURL","ops","all","i","chunk","resp","EmbeddingsService","transport","events","__publicField","model","params","init","wrapEvents","r","ActivityTracker","__publicField","events","offs","e","o","handler","h","err","now","call","cutoff","sumTokens","s","x","spanMs","c","BIN_CTORS","b","bytesToBase64","bytes","s","i","base64ToBytes","b64","out","isPlainObject","v","proto","encodeValue","value","sink","t","ref","f","name","k","x","needsWrap","decodeValue","source","obj","buf","make","m","pair","blob","encodeKey","key","decodeKey","encoded","keyId","hash32","h","deepEqual","a","toBytes","ba","bb","ka","kb","changedFields","prior","inP","inV","extractKey","keyPath","one","path","cur","part","parts","p","TOKEN_REFRESH_MARGIN_S","STORAGE_KEY_BASE","LEGACY_STORAGE_KEY_BASE","SIGNED_OUT_KEY_BASE","LEGACY_SIGNED_OUT_KEY_BASE","storageKey","project","signedOutKey","legacyStorageKey","legacySignedOutKey","BrowserAuth","opts","__publicField","ActivityTracker","handler","i","hash32","ret","scheme","args","params","token","expiresAt","user","fn","err","data","raw","kv","resolve","reject","done","timers","cleanup","onMessage","t","e","post","d","ID","dashboardUrl","cb","root","mountActivityBadge","ensurePulseKeyframes","style","badgeCornerStyles","position","v","h","popoverVerticalAnchor","popoverHorizontalAnchor","BADGE_POS_COOKIE","LEGACY_BADGE_POS_COOKIE","BADGE_DRAG_THRESHOLD_PX","BADGE_EDGE_INSET_PX","readBadgePosCookie","cookies","s","name","parts","rawX","rawY","x","y","writeBadgePosCookie","maxAge","secure","clampToViewport","w","vw","vh","maxX","maxY","popoverCssForRect","rect","cx","verticalAnchor","horizontalAnchor","id","tracker","onSignOut","corners","currentPopoverCss","el","avatarWrap","ringNS","ring","ringTrack","ringArc","RING_CIRC","avatar","led","popover","popoverOpen","popoverOpenedBy","openTimer","closeTimer","cancelOpen","cancelClose","scheduleClose","closePopover","openPopover","by","createPopover","requestRedraw","dragStartClientX","dragStartClientY","dragStartBadgeLeft","dragStartBadgeTop","pointerDown","isDragging","suppressNextClick","applyAbsolutePos","recomputePopoverAnchor","r","decl","prop","val","dx","dy","clamped","endDrag","savedPos","rafScheduled","intervalHandle","seenChangeAt","reconcile","snap","changeAt","syncedPing","updateRing","updateLED","updatePopover","needsLoop","arc","circ","primary","best","c","fraction","syncFlashUntil","ping","sync","anchorCss","pop","header","headAvatar","headName","headSub","headText","inner","footer","dash","signOutBtn","now","renderActiveSection","renderRecentSection","renderSyncSection","renderSessionSection","sec","hint","relativeTime","sectionLabel","row","left","dot","label","text","color","right","empty","elapsed","rate","bullet","latency","cost","ago","calls","errors","title","lbl","ms","txState","observedDbs","subscribers","originals","clone","v","isPlainObject","proto","makeEvent","type","init","target","ev","p","HookedOpenRequest","real","name","subs","__publicField","e","db","settled","deliver","waits","timeout","s","timer","listener","fn","set","h","err","reportListenerError","cursorStore","cursor","src","subscribersFor","factory","out","patchPrototypes","F","D","S","C","o","tracked","store","version","storeNames","mode","options","tx","info","st","ops","mine","op","value","key","prior","k","extractKey","g","slot","r","snapshot","priorSlot","query","kr","restorePrototypes","installIndexedDBHook","opts","sub","SYNC_DB_NAME","req","r","resolve","reject","done","tx","SyncLog","open","__publicField","SYNC_DB_NAME","db","ops","st","op","ids","id","key","entries","k","prefix","range","keys","vals","out","i","isPlainRecord","v","sameJSON","a","b","mergeRecord","cur","inc","changed","curF","touched","out","f","fields","name","inCur","inInc","curT","TOMBSTONE_TTL_MS","MAX_PUSH_DELAY_MS","BACKOFF_MIN_MS","BACKOFF_MAX_MS","Clock","__publicField","t","serverTime","localBefore","localAfter","mid","off","pathSeg","s","SyncEngine","deps","options","excludedDbs","e","excludedStores","only","installIndexedDBHook","db","store","ops","SyncLog","version","msg","c","p","logged","op","ts","keyId","encodeKey","changedFields","byStore","change","err","schema","names","tx","n","st","stores","on","target","type","fn","ev","d","locks","visible","now","delay","_reason","run","patch","next","offline","k","userId","owner","shard","kid","hash32","path","before","groups","g","done","etagUpdates","clearedAt","lastClear","i","live","byShard","list","shards","_","shardOps","encoded","etag","encodeValue","blob","attempt","rec","r","bytes","digest","hex","ref","listing","re","pending","entry","m","localNewer","localClear","clearKey","appliedClear","keys","clearedLocally","key","decodeKey","value","decodeValue","resolve","reject","h","mode","prepare","FilesRemote","files","__publicField","prefix","out","cursor","page","f","path","ifNoneMatch","value","cond","obj","e","code","blob","DEFAULT_BASE_URL","RunApps","options","__publicField","SDKEvents","provider","apiKeyResolver","baseURL","onUnauthorized","showBadge","auth","BrowserAuth","transport","Transport","ChatService","ModelsService","ImageService","AudioService","VideoService","ComputerService","FilesService","EmbeddingsService","RealtimeService","wantSync","engine","SyncEngine","FilesRemote","cb","normalizeGroups","g","item","getOptionsSchema","model","opts","acceptsField","name","f","requiresField","allowedValuesFor","validateRequest","schema","req","errors","name","field","set","val","isEmpty","validateFieldValue","validatePresence","c","validateConstraint","typeMatches","describeType","inEnum","n","numberValue","item","i","validateObject","pathPrefix","fields","obj","innerName","innerField","childPath","presence","f","setFields","groups","normalizeGroups","activeGroups","activeFields","g","setHere","labels","whenName","missing","fieldName","raw","dims","parsePixelDims","w","h","px","errs","typ","isURLLike","v","s","enumVals","e","equalLoose","a","b","sep","idx"]}
|