@tanstack/ai-client 0.26.0 → 0.28.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/dist/esm/byok/client.d.ts +52 -0
- package/dist/esm/byok/client.js +300 -0
- package/dist/esm/byok/client.js.map +1 -0
- package/dist/esm/byok/passkey.d.ts +43 -0
- package/dist/esm/byok/passkey.js +251 -0
- package/dist/esm/byok/passkey.js.map +1 -0
- package/dist/esm/byok/resolve.d.ts +8 -0
- package/dist/esm/byok/resolve.js +20 -0
- package/dist/esm/byok/resolve.js.map +1 -0
- package/dist/esm/byok/storage.d.ts +15 -0
- package/dist/esm/byok/storage.js +16 -0
- package/dist/esm/byok/storage.js.map +1 -0
- package/dist/esm/byok.d.ts +8 -0
- package/dist/esm/byok.js +5 -0
- package/dist/esm/chat-client.d.ts +16 -3
- package/dist/esm/chat-client.js +49 -31
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +2 -0
- package/dist/esm/connection-adapters.js +33 -9
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/generation-client.d.ts +3 -1
- package/dist/esm/generation-client.js +23 -4
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +14 -0
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/interrupt-manager.d.ts +1 -0
- package/dist/esm/interrupt-manager.js +15 -6
- package/dist/esm/interrupt-manager.js.map +1 -1
- package/dist/esm/sse-parser.js +8 -1
- package/dist/esm/sse-parser.js.map +1 -1
- package/dist/esm/types.d.ts +29 -3
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +3 -1
- package/dist/esm/video-generation-client.js +25 -6
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +6 -2
- package/src/byok/client.ts +360 -0
- package/src/byok/passkey.ts +399 -0
- package/src/byok/resolve.ts +30 -0
- package/src/byok/storage.ts +29 -0
- package/src/byok.ts +20 -0
- package/src/chat-client.ts +95 -65
- package/src/connection-adapters.ts +41 -12
- package/src/generation-client.ts +48 -4
- package/src/generation-types.ts +16 -0
- package/src/interrupt-manager.ts +20 -6
- package/src/sse-parser.ts +10 -0
- package/src/types.ts +31 -3
- package/src/video-generation-client.ts +45 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-types.js","names":[],"sources":["../../src/generation-types.ts"],"sourcesContent":["import { tanstackMetadata } from '@tanstack/ai/client'\nimport type {\n MediaPrompt,\n PersistedArtifactRef,\n StreamChunk,\n} from '@tanstack/ai/client'\nimport type { TokenUsage, TranscriptionResponseFormat } from '@tanstack/ai'\nimport type { ConnectConnectionAdapter } from './connection-adapters'\nimport type { AIDevtoolsClientMetadata } from './devtools'\nimport type {\n GenerationDevtoolsBridgeFactory,\n VideoDevtoolsBridgeFactory,\n} from './devtools-noop'\n\n// ===========================\n// Inference Utilities\n// ===========================\n\n/**\n * Maps an `onResult` transform's raw return type to the stored output type.\n *\n * - A concrete return (excluding null/void/undefined) becomes the output type.\n * - A return of only null/void/undefined falls back to TResult (the transform\n * reacted to the result or chose to keep it, rather than replacing it).\n *\n * Hooks infer `TReturn` directly from the `onResult` return position — a\n * covariant inference site that works even for an optional nested property —\n * which both contextually types the callback parameter as `TResult` and\n * narrows `result`. See issue #848.\n *\n * @template TResult - The raw result type from the generation\n * @template TReturn - The transform's return type (defaults to `void` when no\n * transform is provided)\n */\nexport type InferGenerationOutputFromReturn<TResult, TReturn> = [\n Exclude<TReturn, null | void | undefined>,\n] extends [never]\n ? TResult\n : Exclude<TReturn, null | void | undefined>\n\n/**\n * Infers the output type from an `onResult` callback's type.\n *\n * - If the callback returns a concrete type (excluding null/void/undefined), uses that type.\n * - If the callback only returns null/void/undefined, or is not provided, falls back to TResult.\n *\n * @template TResult - The raw result type from the generation\n * @template TFn - The onResult callback type (or undefined if not provided)\n */\nexport type InferGenerationOutput<TResult, TFn> = TFn extends (\n result: any,\n) => infer R\n ? InferGenerationOutputFromReturn<TResult, R>\n : TResult\n\n// ===========================\n// State\n// ===========================\n\n/**\n * State machine for generation clients.\n * Simpler than ChatClientState since generation is a single request/response cycle.\n */\nexport type GenerationClientState = 'idle' | 'generating' | 'success' | 'error'\n\n/**\n * Status of a persisted/restored generation run.\n *\n * `running` / `complete` / `error` are the three the server-side mapper emits\n * over the wire. `idle` is client-local only: `stop()` rewrites a `running`\n * snapshot to it so a cancelled run is no longer resumable.\n *\n * @internal\n */\nexport type GenerationResumeStatus = 'idle' | 'running' | 'complete' | 'error'\n\n/**\n * Thrown when a generation stream ends without a terminal `RUN_FINISHED` /\n * `RUN_ERROR` chunk — a proxy/load-balancer idle timeout, a server restart\n * mid-run, or a durable log whose terminal append never landed. The run's\n * outcome is unknowable from the client, so it settles as an error rather than\n * leaving the client stuck on `generating` forever.\n */\nexport const GENERATION_STREAM_TRUNCATED_MESSAGE =\n 'The generation stream ended before the run finished (no RUN_FINISHED or RUN_ERROR was received) — the connection was interrupted. Generate again to retry.'\n\n/**\n * Reported when a restored snapshot says the run completed but the activity's\n * `reconstructResult` mapper cannot rebuild a result from it — typically an\n * output artifact persisted without a serve `url`. Surfacing it beats a\n * `success` status with a `null` result, which no consumer can render.\n */\nexport const GENERATION_UNRESTORABLE_RESULT_MESSAGE =\n 'The stored generation completed but its result could not be rebuilt from the persisted record (its output artifact carries no serve URL, or the fields this activity needs were not persisted). Generate again to produce a fresh result.'\n\n/**\n * Wrap a mount-hydration failure with context. Genuine failures (transport\n * error, a 403 from the authorize gate, an unparseable body, a record the\n * client's validator rejects) must reach the app; only a genuine miss — the\n * server reporting no record for the thread — stays silent.\n */\nexport function createGenerationHydrationError(\n detail: string,\n cause?: unknown,\n): Error {\n const suffix = cause instanceof Error ? `: ${cause.message}` : ''\n const error = new Error(\n `[TanStack AI] Restoring the last generation for this thread failed — ${detail}${suffix}`,\n )\n if (cause !== undefined) {\n error.cause = cause\n }\n return error\n}\n\n/**\n * Map a persisted resume status to the client's live state machine on restore:\n * complete → success, error → error, running → generating, idle → idle. A\n * restored `running` only reaches this mapping when a `joinRun` handler can\n * tail the run to completion; without one the client rewrites the snapshot to\n * `error` (interrupted) before repainting, so it never sticks on `generating`.\n */\nexport function clientStateFromResumeStatus(\n status: GenerationResumeStatus,\n): GenerationClientState {\n switch (status) {\n case 'complete':\n return 'success'\n case 'error':\n return 'error'\n case 'running':\n return 'generating'\n case 'idle':\n return 'idle'\n }\n}\n\n/** @internal */\nexport interface GenerationResumeState {\n threadId: string\n runId: string\n /**\n * Artifact refs observed while the run is still in flight. Non-null only while\n * a run is streaming (`resumeState` itself is null once it ends); the final\n * refs move onto `result.artifacts` when the run completes.\n */\n pendingArtifacts?: Array<PersistedArtifactRef>\n}\n\n/** @internal */\nexport interface GenerationResultSnapshot {\n id?: string\n model?: string\n status?: string\n /**\n * The provider's async job handle (e.g. a Veo/fal video job id used for\n * status polling) — NOT the generation's own `runId`, which lives on\n * {@link GenerationResumeState.runId}.\n */\n providerJobId?: string\n expiresAt?: string\n /**\n * The text output of a text activity (a transcription's `text` or a summary's\n * `summary`). Persisted so a text generation restores its result on reload\n * (text is small and not bytes). Absent for media activities, whose output\n * restores from `artifacts`.\n */\n text?: string\n /** Token usage, persisted so a text result that requires it can be rebuilt. */\n usage?: TokenUsage\n artifacts?: Array<PersistedArtifactRef>\n}\n\n/** @internal */\nexport interface GenerationErrorSnapshot {\n message: string\n code?: string\n}\n\n/** @internal */\nexport interface GenerationEventSnapshot {\n type: StreamChunk['type']\n name?: string\n timestamp?: number\n}\n\n/** @internal */\nexport interface GenerationResumeSnapshot {\n /**\n * Version of the snapshot shape. Written on every snapshot the client builds\n * so future shape changes can migrate (or reject) an older record hydrated\n * from the server. Absent means `1`.\n */\n schemaVersion?: 1\n resumeState: GenerationResumeState | null\n status: GenerationResumeStatus\n activity?: PersistedArtifactRef['source']['activity']\n pendingArtifacts?: Array<PersistedArtifactRef>\n result?: GenerationResultSnapshot\n error?: GenerationErrorSnapshot\n lastEvent?: GenerationEventSnapshot\n}\n\n/**\n * The `persistence` / `threadId` identity shared by every generation hook.\n *\n * Turning persistence on **requires** a `threadId`, the stable scope runs are\n * filed under. Without one the client would hydrate by a generated id that\n * changes every reload, so nothing would ever restore; making it a type error\n * means the compiler asks for the scope instead of the runtime inventing one.\n *\n * `threadId` is the only identity for the hook, the AG-UI wire thread, and\n * persistence. There is no separate instance `id`.\n *\n * USAGE: intersect this onto a hook's parameter and subtract `persistence`\n * and `threadId` from the options interface (plus any other keys the hook\n * redeclares, such as `onResult`), leaving that interface a plain (non-union)\n * object so `Pick` / `Omit` composition elsewhere keeps working:\n *\n * ```ts\n * options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId'> & {\n * onResult?: (result: ImageGenerationResult) => TTransformed\n * } & GenerationPersistenceOptions\n * ```\n *\n * Do NOT bake the union into the options interface itself: a later plain `Omit`\n * over a union collapses it to a single object type and the requirement\n * silently disappears. `use-generation-persistence-types.test.ts` pins this.\n */\nexport type GenerationPersistenceOptions =\n | {\n persistence: true\n /** Required by `persistence`. The stable scope runs are filed under. */\n threadId: string\n }\n | {\n persistence?: false | undefined\n /** Stable scope for the generation slot (also the wire / DevTools identity). */\n threadId?: string\n }\n\n// ===========================\n// Event Constants\n// ===========================\n\n/**\n * Well-known CUSTOM event names used by generation clients.\n * These events are emitted by the server-side streaming helpers\n * and consumed by the client-side GenerationClient.\n */\nexport const GENERATION_EVENTS = {\n /** The generation result payload */\n RESULT: 'generation:result',\n /** Persisted artifact refs for generated media */\n ARTIFACTS: 'generation:artifacts',\n /** Progress update (0-100) with optional message */\n PROGRESS: 'generation:progress',\n /** Video job created with jobId */\n VIDEO_JOB_CREATED: 'video:job:created',\n /** Video job status update */\n VIDEO_STATUS: 'video:status',\n} as const\n\n// ===========================\n// Transport Types\n// ===========================\n\n/**\n * Options passed to a fetcher function by the generation client.\n */\nexport interface GenerationFetcherOptions {\n /** AbortSignal that is triggered when the user calls `stop()` */\n signal: AbortSignal\n}\n\n/**\n * A direct async function that performs a generation request.\n *\n * Can return the result directly, or return a `Response` with an SSE body\n * (e.g., from a TanStack Start server function using `toServerSentEventsResponse()`).\n * When a `Response` is returned, the client will parse it as an SSE stream.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n */\nexport type GenerationFetcher<TInput, TResult> = (\n input: TInput,\n options?: GenerationFetcherOptions,\n) => Promise<TResult | Response>\n\n/**\n * Transport configuration for generation clients.\n * Supports either a connect-based streaming adapter or a direct fetcher function.\n */\nexport type GenerationTransport<TInput, TResult> =\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | { fetcher: GenerationFetcher<TInput, TResult>; connection?: never }\n\n// ===========================\n// Client Options\n// ===========================\n\n/**\n * Options for the GenerationClient.\n *\n * @template TInput - The input type for the generation request (used by consuming code)\n * @template TResult - The result type returned by the generation\n * @template TOutput - The output type after optional transform (defaults to TResult)\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention -- _TInput is unused in the interface body but part of the public positional generic API (callers supply it for inference)\nexport interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill, not a link to a chat conversation. This is the\n * only identity for the client: wire thread id, DevTools hook id, and\n * persistence key.\n *\n * A generation hook starts empty and produces many runs over its life. Each\n * run gets its own `runId`, but they all belong to one scope. Persistence\n * keys on this: server-driven hydrates the last run for it on mount. It is\n * also sent as the AG-UI thread id on the wire, since the protocol requires\n * one.\n *\n * Derive it from your own domain. It must be meaningful before any media\n * exists and identical after a reload:\n *\n * ```ts\n * threadId: `video-${videoId}-start-frame`\n * ```\n *\n * **Required whenever `persistence` is set.** An app that cannot name the\n * scope has nothing to restore *to*, and a generated fallback would key each\n * reload differently, silently restoring nothing. Optional only for\n * ephemeral runs. If omitted, the client mints a wire id after mount.\n */\n threadId?: string\n\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n\n /** Metadata used to register this generation hook with TanStack AI Devtools */\n devtools?: Partial<AIDevtoolsClientMetadata>\n\n /**\n * How this generation persists across reloads.\n *\n * - Omit or `false`: ephemeral, in-memory only.\n * - `true`: server-driven. On mount the client hydrates the last generation\n * for its `threadId` from the server (needs a `hydrateGeneration` handler,\n * from the connection or the option below) and repaints that snapshot. It\n * never auto-starts a run.\n *\n * The record lives on the server, written by `withGenerationPersistence`. The\n * browser caches nothing, so a generation's history is never duplicated into\n * client storage.\n */\n persistence?: boolean\n\n /**\n * Server-driven hydration handler, for transports that don't carry one on\n * the connection: supply it alongside `fetcher` (or a `stream()` /\n * `rpcStream()` connection built without handlers) so `persistence: true`\n * can restore the last generation for `threadId` on mount. Typically a\n * one-line TanStack Start server-function call backed by\n * `getGenerationHydration` from `@tanstack/ai-persistence`.\n *\n * A connection's own `hydrateGeneration` takes precedence when both exist.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n\n /**\n * Re-attach handler for a run that is still generating, for transports that\n * don't carry one on the connection. The client tails this on mount when a\n * restored/hydrated snapshot reports a run in flight, replaying it to\n * completion in place. Without it, a restored `running` snapshot surfaces\n * as an (interrupted) error — an interrupted generation cannot be resumed,\n * only re-run.\n *\n * A connection's own `joinRun` takes precedence when both exist.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n\n /**\n * Factory that constructs the devtools bridge. Default is a no-op\n * factory; the real implementation lives in `@tanstack/ai-client/devtools`.\n */\n devtoolsBridgeFactory?: GenerationDevtoolsBridgeFactory\n\n /**\n * Callback when a result is received. Can optionally return a transformed value\n * that replaces the stored result.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n\n // Framework state callbacks (set by hooks, not users)\n /** @internal Called when result changes */\n onResultChange?: (result: TOutput | null) => void\n /** @internal Called when loading state changes */\n onLoadingChange?: (isLoading: boolean) => void\n /** @internal Called when error state changes */\n onErrorChange?: (error: Error | undefined) => void\n /** @internal Called when generation status changes */\n onStatusChange?: (status: GenerationClientState) => void\n /** @internal Called when lightweight resume snapshot changes. Receives `undefined` when the snapshot is cleared by `reset()`. */\n onResumeSnapshotChange?: (\n snapshot: GenerationResumeSnapshot | undefined,\n ) => void\n /** @internal Called when the in-flight run identity changes. `null` once no run is in flight. Mirrors the chat client's resume-state callback. */\n onResumeStateChange?: (resumeState: GenerationResumeState | null) => void\n\n /**\n * @internal Rebuild a typed result from a restored snapshot, injected by each\n * specialized client/hook (which knows the concrete result shape). Called on\n * mount restore (client store or server hydrate) so `result` repaints as if the\n * run had just finished, with media resolved to the durable serve URL. Returns\n * `null` when the snapshot cannot rebuild a result (then `result` stays null;\n * `status` / `error` / `resumeState` still repaint).\n */\n reconstructResult?: (restored: GenerationRestoredResult) => TResult | null\n}\n\n/**\n * The restorable shape handed to a client's `reconstructResult` mapper: the\n * result metadata that survived persistence plus the durable artifact refs (each\n * carrying its serve {@link PersistedArtifactRef.url}). The specialized client\n * turns this into its own typed result (image `images`, video `url`, text\n * `text`, ...).\n */\nexport interface GenerationRestoredResult {\n id?: string\n model?: string\n status?: string\n /** The provider's async job handle — see {@link GenerationResultSnapshot.providerJobId}. */\n providerJobId?: string\n expiresAt?: string\n text?: string\n usage?: TokenUsage\n activity?: PersistedArtifactRef['source']['activity']\n artifacts: Array<PersistedArtifactRef>\n}\n\n/**\n * Reduces one observed stream chunk into the lightweight resume snapshot.\n *\n * A `RUN_STARTED` chunk begins a fresh run, so stale `result` / `error` /\n * `pendingArtifacts` from a previous run are dropped rather than carried into\n * the new run's snapshot.\n *\n * @internal\n */\nexport function updateGenerationResumeSnapshot(\n previous: GenerationResumeSnapshot | null | undefined,\n chunk: StreamChunk,\n): GenerationResumeSnapshot {\n const tanstack = tanstackMetadata(chunk)\n const threadId =\n stringField(chunk, 'threadId') ??\n (typeof tanstack?.threadId === 'string' ? tanstack.threadId : undefined)\n const runId =\n stringField(chunk, 'runId') ??\n (typeof tanstack?.runId === 'string' ? tanstack.runId : undefined)\n const carried = chunk.type === 'RUN_STARTED' ? undefined : previous\n const previousArtifacts = carried?.pendingArtifacts ?? []\n const next: GenerationResumeSnapshot = {\n schemaVersion: 1,\n resumeState: carried?.resumeState ?? null,\n status: carried?.status ?? 'idle',\n ...(carried?.activity ? { activity: carried.activity } : {}),\n ...(previousArtifacts.length > 0\n ? { pendingArtifacts: [...previousArtifacts] }\n : {}),\n ...(carried?.result ? { result: { ...carried.result } } : {}),\n ...(carried?.error ? { error: { ...carried.error } } : {}),\n lastEvent: createGenerationEventSnapshot(chunk),\n }\n\n if (threadId && runId) {\n next.resumeState = { threadId, runId }\n next.status = 'running'\n } else if (chunk.type === 'RUN_STARTED') {\n next.status = 'running'\n }\n\n if (chunk.type === 'CUSTOM') {\n if (chunk.name === GENERATION_EVENTS.ARTIFACTS) {\n const artifacts = collectArtifactRefs(chunk.value)\n if (artifacts.length > 0) {\n next.pendingArtifacts = artifacts\n next.activity = artifacts[0]?.source.activity\n }\n } else if (chunk.name === GENERATION_EVENTS.RESULT) {\n const result = createGenerationResultSnapshot(chunk.value)\n if (result) {\n next.result = result\n if (result.artifacts && result.artifacts.length > 0) {\n next.pendingArtifacts = result.artifacts\n next.activity = result.artifacts[0]?.source.activity\n }\n }\n } else if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) {\n // Capture the provider job id as soon as the job exists — for a long\n // video run this is the one piece of identity worth having after a\n // reload, and the terminal `generation:result` may never arrive.\n const providerJobId = isObject(chunk.value)\n ? stringField(chunk.value, 'jobId')\n : undefined\n if (providerJobId) {\n next.result = { ...next.result, providerJobId }\n }\n }\n } else if (chunk.type === 'RUN_FINISHED') {\n next.resumeState = null\n next.status = 'complete'\n } else if (chunk.type === 'RUN_ERROR') {\n next.resumeState = null\n next.status = 'error'\n next.error = createGenerationErrorSnapshot(chunk)\n }\n\n return next\n}\n\n/**\n * Validates an untrusted value (a hydration body resolved by the server) into a\n * {@link GenerationResumeSnapshot}, or returns `undefined` when the value is\n * not a usable snapshot.\n *\n * A hydrated record is outside the type system: it may be stale, truncated, or\n * written by a different version. Every field is re-validated with the same\n * narrowing the live chunk reducer uses. `lastEvent` is not restored, since it\n * describes a transient stream position with no meaning after a reload.\n *\n * @internal\n */\nexport function parseGenerationResumeSnapshot(\n value: unknown,\n): GenerationResumeSnapshot | undefined {\n if (!isObject(value)) return undefined\n\n const schemaVersion = Reflect.get(value, 'schemaVersion')\n if (schemaVersion !== undefined && schemaVersion !== 1) return undefined\n\n const status = generationResumeStatusField(value, 'status')\n if (!status) return undefined\n\n const rawResumeState = Reflect.get(value, 'resumeState')\n let resumeState: GenerationResumeState | null = null\n if (rawResumeState !== null && rawResumeState !== undefined) {\n if (!isObject(rawResumeState)) return undefined\n const threadId = stringField(rawResumeState, 'threadId')\n const runId = stringField(rawResumeState, 'runId')\n if (!threadId || !runId) return undefined\n resumeState = { threadId, runId }\n }\n\n const snapshot: GenerationResumeSnapshot = {\n schemaVersion: 1,\n resumeState,\n status,\n }\n\n const activity = persistedArtifactActivityField(value, 'activity')\n if (activity) snapshot.activity = activity\n\n const pendingArtifacts = collectArtifactRefs(\n Reflect.get(value, 'pendingArtifacts'),\n )\n if (pendingArtifacts.length > 0) snapshot.pendingArtifacts = pendingArtifacts\n\n const result = createGenerationResultSnapshot(Reflect.get(value, 'result'))\n if (result) snapshot.result = result\n\n const rawError = Reflect.get(value, 'error')\n if (isObject(rawError)) {\n const message = stringField(rawError, 'message')\n if (message) {\n const code = stringField(rawError, 'code')\n snapshot.error = { message, ...(code ? { code } : {}) }\n }\n }\n\n return snapshot\n}\n\nfunction generationResumeStatusField(\n value: object,\n key: string,\n): GenerationResumeStatus | undefined {\n const field = stringField(value, key)\n if (field === undefined) return undefined\n\n switch (field) {\n case 'idle':\n case 'running':\n case 'complete':\n case 'error':\n return field\n default:\n return undefined\n }\n}\n\n// ===========================\n// Video-Specific Options\n// ===========================\n\n/**\n * Video status information returned during job polling.\n */\nexport interface VideoStatusInfo {\n /** Job identifier */\n jobId: string\n /** Current status of the video generation job */\n status: 'pending' | 'processing' | 'completed' | 'failed'\n /** Progress percentage (0-100), if available */\n progress?: number\n /** URL to the generated video (when completed) */\n url?: string\n /** Error message if status is 'failed' */\n error?: string\n}\n\n/**\n * Composite result for video generation (job completion).\n */\nexport interface VideoGenerateResult {\n /** Job identifier */\n jobId: string\n /** Final status */\n status: 'completed'\n /** URL to the generated video */\n url: string\n /** When the URL expires, if applicable */\n expiresAt?: Date\n /** Persisted artifact references for generated assets, when available */\n artifacts?: Array<PersistedArtifactRef>\n}\n\n/**\n * Options for the VideoGenerationClient.\n */\nexport interface VideoGenerationClientOptions<\n TOutput = VideoGenerateResult,\n> extends Omit<\n GenerationClientOptions<VideoGenerateInput, VideoGenerateResult, TOutput>,\n 'devtoolsBridgeFactory'\n> {\n /**\n * Factory that constructs the video devtools bridge. Default is a no-op\n * factory; the real implementation lives in `@tanstack/ai-client/devtools`.\n */\n devtoolsBridgeFactory?: VideoDevtoolsBridgeFactory\n\n /** Callback when a video job is created */\n onJobCreated?: (jobId: string) => void\n /** Callback on each status update */\n onStatusUpdate?: (status: VideoStatusInfo) => void\n\n // Framework state callbacks\n /** @internal Called when jobId changes */\n onJobIdChange?: (jobId: string | null) => void\n /** @internal Called when video status changes */\n onVideoStatusChange?: (status: VideoStatusInfo | null) => void\n}\n\n// ===========================\n// Input Types\n// ===========================\n\n/**\n * Input for image generation.\n */\nexport interface ImageGenerateInput {\n /**\n * Description of the desired image(s): plain text, or an ordered array of\n * content parts (text + image) for image-conditioned generation\n * (image-to-image, multi-reference, edit / inpaint).\n */\n prompt: MediaPrompt\n /** Number of images to generate (default: 1) */\n numberOfImages?: number\n /** Image size in WIDTHxHEIGHT format (e.g., \"1024x1024\") */\n size?: string\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio generation (music, sound effects).\n */\nexport interface AudioGenerateInput {\n /** Text description of the desired audio */\n prompt: string\n /** Desired duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text-to-speech generation.\n */\nexport interface SpeechGenerateInput {\n /** The text to convert to speech */\n text: string\n /** The voice to use for generation */\n voice?: string\n /** The output audio format */\n format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'\n /** The speed of the generated audio (0.25 to 4.0) */\n speed?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio transcription.\n */\nexport interface TranscriptionGenerateInput {\n /** The audio data to transcribe - can be base64 string, File, Blob, or ArrayBuffer */\n audio: string | File | Blob | ArrayBuffer\n /** The language of the audio in ISO-639-1 format (e.g., 'en') */\n language?: string\n /** An optional prompt to guide the transcription */\n prompt?: string\n /** The format of the transcription output */\n responseFormat?: TranscriptionResponseFormat\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text summarization.\n */\nexport interface SummarizeGenerateInput {\n /** The text to summarize */\n text: string\n /** Maximum length of the summary */\n maxLength?: number\n /** Style of the summary */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics to focus on */\n focus?: Array<string>\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for video generation.\n */\nexport interface VideoGenerateInput {\n /**\n * Description of the desired video: plain text, or an ordered array of\n * content parts (text + image) for image-conditioned generation\n * (image-to-video, start/end frames).\n */\n prompt: MediaPrompt\n /** Video size — format depends on provider (e.g., \"16:9\", \"1280x720\") */\n size?: string\n /** Video duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\nfunction createGenerationEventSnapshot(\n chunk: StreamChunk,\n): GenerationEventSnapshot {\n const name = stringField(chunk, 'name')\n const timestamp = numberField(chunk, 'timestamp')\n return {\n type: chunk.type,\n ...(name ? { name } : {}),\n ...(timestamp !== undefined ? { timestamp } : {}),\n }\n}\n\n/** @internal Narrows an untrusted result payload into the persisted result snapshot shape. */\nexport function createGenerationResultSnapshot(\n value: unknown,\n): GenerationResultSnapshot | undefined {\n if (!isObject(value)) return undefined\n\n const artifacts = collectArtifactRefs(Reflect.get(value, 'artifacts'))\n const snapshot: GenerationResultSnapshot = {}\n const id = stringField(value, 'id')\n const model = stringField(value, 'model')\n const status = stringField(value, 'status')\n // A live provider result carries its job handle as `jobId` (e.g.\n // `VideoGenerateResult.jobId`); a persisted snapshot carries it as\n // `providerJobId`. Accept both — this narrows raw results AND stored\n // snapshots.\n const providerJobId =\n stringField(value, 'providerJobId') ?? stringField(value, 'jobId')\n // A transcription's output is `text`; a summary's is `summary`. Capture either\n // under `text` so a text result restores on reload.\n const text = stringField(value, 'text') ?? stringField(value, 'summary')\n const usage = Reflect.get(value, 'usage')\n if (id) snapshot.id = id\n if (model) snapshot.model = model\n if (status) snapshot.status = status\n if (providerJobId) snapshot.providerJobId = providerJobId\n if (text) snapshot.text = text\n // Passthrough opaque token-usage metadata (untrusted; not deeply validated).\n if (isObject(usage)) snapshot.usage = usage as TokenUsage\n const expiresAt = Reflect.get(value, 'expiresAt')\n if (typeof expiresAt === 'string') {\n snapshot.expiresAt = expiresAt\n } else if (expiresAt instanceof Date && !Number.isNaN(expiresAt.getTime())) {\n // `toISOString()` throws on an invalid Date. This runs per chunk on live\n // provider values, so drop an unusable date like every other bad field\n // here rather than throwing out of the stream loop.\n snapshot.expiresAt = expiresAt.toISOString()\n }\n if (artifacts.length > 0) {\n snapshot.artifacts = artifacts\n }\n\n return Object.keys(snapshot).length > 0 ? snapshot : undefined\n}\n\nfunction createGenerationErrorSnapshot(\n chunk: StreamChunk,\n): GenerationErrorSnapshot {\n const message =\n stringField(chunk, 'message') ??\n nestedStringField(chunk, 'error', 'message') ??\n 'An error occurred'\n const code = stringField(chunk, 'code')\n return {\n message,\n ...(code ? { code } : {}),\n }\n}\n\nfunction collectArtifactRefs(value: unknown): Array<PersistedArtifactRef> {\n if (!Array.isArray(value)) return []\n const refs: Array<PersistedArtifactRef> = []\n for (const item of value) {\n const ref = createPersistedArtifactRefSnapshot(item)\n if (ref) {\n refs.push(ref)\n }\n }\n return refs\n}\n\nfunction createPersistedArtifactRefSnapshot(\n value: unknown,\n): PersistedArtifactRef | undefined {\n if (!isObject(value)) return undefined\n const source = Reflect.get(value, 'source')\n if (!isObject(source)) return undefined\n\n const role = persistedArtifactRoleField(value, 'role')\n const artifactId = stringField(value, 'artifactId')\n const threadId = stringField(value, 'threadId')\n const runId = stringField(value, 'runId')\n const name = stringField(value, 'name')\n const mimeType = stringField(value, 'mimeType')\n const size = numberField(value, 'size')\n const createdAt = stringField(value, 'createdAt')\n const activity = persistedArtifactActivityField(source, 'activity')\n const path = stringField(source, 'path')\n const provider = stringField(source, 'provider')\n const model = stringField(source, 'model')\n if (\n !role ||\n !artifactId ||\n !threadId ||\n !runId ||\n !name ||\n !mimeType ||\n size === undefined ||\n !createdAt ||\n !activity ||\n !path ||\n !provider ||\n !model\n ) {\n return undefined\n }\n\n const sourceUrl = durableUrlField(value, 'sourceUrl')\n const url = serveUrlField(value, 'url')\n const mediaType = persistedArtifactMediaTypeField(source, 'mediaType')\n const jobId = stringField(source, 'jobId')\n const expiresAt = stringField(source, 'expiresAt')\n\n return {\n role,\n artifactId,\n threadId,\n runId,\n name,\n mimeType,\n size,\n createdAt,\n ...(sourceUrl ? { sourceUrl } : {}),\n ...(url ? { url } : {}),\n source: {\n activity,\n path,\n provider,\n model,\n ...(mediaType ? { mediaType } : {}),\n ...(jobId ? { jobId } : {}),\n ...(expiresAt ? { expiresAt } : {}),\n },\n }\n}\n\nfunction durableUrlField(value: object, key: string): string | undefined {\n const field = stringField(value, key)\n if (!field || field.length > 2048) return undefined\n try {\n const url = new URL(field)\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? field\n : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Validates an app-origin serve URL, which unlike a provider URL is usually a\n * same-origin path (`/api/.../artifact?id=...`). Accepts an absolute http(s) URL\n * or a path-absolute same-origin URL (single leading `/`); rejects\n * protocol-relative (`//host`), `javascript:` / `data:`, and anything else, since\n * this value is rendered as media `src`.\n */\nfunction serveUrlField(value: object, key: string): string | undefined {\n const field = stringField(value, key)\n if (!field || field.length > 2048) return undefined\n // A single leading `/` is a safe same-origin path. Reject protocol-relative\n // `//host` AND a backslash bypass (`/\\host` — the URL parser treats `\\` as `/`\n // for http(s), so it would resolve to a foreign origin as an `<img src>`).\n if (field.startsWith('/') && !field.startsWith('//') && !field.includes('\\\\'))\n return field\n try {\n const url = new URL(field)\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? field\n : undefined\n } catch {\n return undefined\n }\n}\n\nfunction persistedArtifactRoleField(\n value: object,\n key: string,\n): PersistedArtifactRef['role'] | undefined {\n const field = stringField(value, key)\n return field === 'input' || field === 'output' ? field : undefined\n}\n\nfunction persistedArtifactActivityField(\n value: object,\n key: string,\n): PersistedArtifactRef['source']['activity'] | undefined {\n const field = stringField(value, key)\n if (field === undefined) return undefined\n\n switch (field) {\n case 'image':\n case 'audio':\n case 'tts':\n case 'video':\n case 'transcription':\n return field\n default:\n return undefined\n }\n}\n\nfunction persistedArtifactMediaTypeField(\n value: object,\n key: string,\n): PersistedArtifactRef['source']['mediaType'] | undefined {\n const field = stringField(value, key)\n if (field === undefined) return undefined\n\n switch (field) {\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n case 'json':\n return field\n default:\n return undefined\n }\n}\n\nfunction nestedStringField(\n value: object,\n key: string,\n nestedKey: string,\n): string | undefined {\n const nested = Reflect.get(value, key)\n return isObject(nested) ? stringField(nested, nestedKey) : undefined\n}\n\nfunction stringField(value: object, key: string): string | undefined {\n const field = Reflect.get(value, key)\n return typeof field === 'string' ? field : undefined\n}\n\nfunction numberField(value: object, key: string): number | undefined {\n const field = Reflect.get(value, key)\n return typeof field === 'number' ? field : undefined\n}\n\nfunction isObject(value: unknown): value is object {\n return typeof value === 'object' && value !== null\n}\n"],"mappings":";;;;;;;;;AAmFA,IAAa,sCACX;;;;;;;AAQF,IAAa,yCACX;;;;;;;AAQF,SAAgB,+BACd,QACA,OACO;CACP,MAAM,SAAS,iBAAiB,QAAQ,KAAK,MAAM,YAAY;CAC/D,MAAM,wBAAQ,IAAI,MAChB,wEAAwE,SAAS,QACnF;CACA,IAAI,UAAU,KAAA,GACZ,MAAM,QAAQ;CAEhB,OAAO;AACT;;;;;;;;AASA,SAAgB,4BACd,QACuB;CACvB,QAAQ,QAAR;EACE,KAAK,YACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,QACH,OAAO;CACX;AACF;;;;;;AAmHA,IAAa,oBAAoB;;CAE/B,QAAQ;;CAER,WAAW;;CAEX,UAAU;;CAEV,mBAAmB;;CAEnB,cAAc;AAChB;;;;;;;;;;AAuMA,SAAgB,+BACd,UACA,OAC0B;CAC1B,MAAM,WAAW,iBAAiB,KAAK;CACvC,MAAM,WACJ,YAAY,OAAO,UAAU,MAC5B,OAAO,UAAU,aAAa,WAAW,SAAS,WAAW,KAAA;CAChE,MAAM,QACJ,YAAY,OAAO,OAAO,MACzB,OAAO,UAAU,UAAU,WAAW,SAAS,QAAQ,KAAA;CAC1D,MAAM,UAAU,MAAM,SAAS,gBAAgB,KAAA,IAAY;CAC3D,MAAM,oBAAoB,SAAS,oBAAoB,CAAC;CACxD,MAAM,OAAiC;EACrC,eAAe;EACf,aAAa,SAAS,eAAe;EACrC,QAAQ,SAAS,UAAU;EAC3B,GAAI,SAAS,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EAC1D,GAAI,kBAAkB,SAAS,IAC3B,EAAE,kBAAkB,CAAC,GAAG,iBAAiB,EAAE,IAC3C,CAAC;EACL,GAAI,SAAS,SAAS,EAAE,QAAQ,EAAE,GAAG,QAAQ,OAAO,EAAE,IAAI,CAAC;EAC3D,GAAI,SAAS,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,MAAM,EAAE,IAAI,CAAC;EACxD,WAAW,8BAA8B,KAAK;CAChD;CAEA,IAAI,YAAY,OAAO;EACrB,KAAK,cAAc;GAAE;GAAU;EAAM;EACrC,KAAK,SAAS;CAChB,OAAO,IAAI,MAAM,SAAS,eACxB,KAAK,SAAS;CAGhB,IAAI,MAAM,SAAS,UAAU;EAC3B,IAAI,MAAM,SAAS,kBAAkB,WAAW;GAC9C,MAAM,YAAY,oBAAoB,MAAM,KAAK;GACjD,IAAI,UAAU,SAAS,GAAG;IACxB,KAAK,mBAAmB;IACxB,KAAK,WAAW,UAAU,EAAE,EAAE,OAAO;GACvC;EACF,OAAO,IAAI,MAAM,SAAS,kBAAkB,QAAQ;GAClD,MAAM,SAAS,+BAA+B,MAAM,KAAK;GACzD,IAAI,QAAQ;IACV,KAAK,SAAS;IACd,IAAI,OAAO,aAAa,OAAO,UAAU,SAAS,GAAG;KACnD,KAAK,mBAAmB,OAAO;KAC/B,KAAK,WAAW,OAAO,UAAU,EAAE,EAAE,OAAO;IAC9C;GACF;EACF,OAAO,IAAI,MAAM,SAAS,kBAAkB,mBAAmB;GAI7D,MAAM,gBAAgB,SAAS,MAAM,KAAK,IACtC,YAAY,MAAM,OAAO,OAAO,IAChC,KAAA;GACJ,IAAI,eACF,KAAK,SAAS;IAAE,GAAG,KAAK;IAAQ;GAAc;EAElD;CACF,OAAO,IAAI,MAAM,SAAS,gBAAgB;EACxC,KAAK,cAAc;EACnB,KAAK,SAAS;CAChB,OAAO,IAAI,MAAM,SAAS,aAAa;EACrC,KAAK,cAAc;EACnB,KAAK,SAAS;EACd,KAAK,QAAQ,8BAA8B,KAAK;CAClD;CAEA,OAAO;AACT;;;;;;;;;;;;;AAcA,SAAgB,8BACd,OACsC;CACtC,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAE7B,MAAM,gBAAgB,QAAQ,IAAI,OAAO,eAAe;CACxD,IAAI,kBAAkB,KAAA,KAAa,kBAAkB,GAAG,OAAO,KAAA;CAE/D,MAAM,SAAS,4BAA4B,OAAO,QAAQ;CAC1D,IAAI,CAAC,QAAQ,OAAO,KAAA;CAEpB,MAAM,iBAAiB,QAAQ,IAAI,OAAO,aAAa;CACvD,IAAI,cAA4C;CAChD,IAAI,mBAAmB,QAAQ,mBAAmB,KAAA,GAAW;EAC3D,IAAI,CAAC,SAAS,cAAc,GAAG,OAAO,KAAA;EACtC,MAAM,WAAW,YAAY,gBAAgB,UAAU;EACvD,MAAM,QAAQ,YAAY,gBAAgB,OAAO;EACjD,IAAI,CAAC,YAAY,CAAC,OAAO,OAAO,KAAA;EAChC,cAAc;GAAE;GAAU;EAAM;CAClC;CAEA,MAAM,WAAqC;EACzC,eAAe;EACf;EACA;CACF;CAEA,MAAM,WAAW,+BAA+B,OAAO,UAAU;CACjE,IAAI,UAAU,SAAS,WAAW;CAElC,MAAM,mBAAmB,oBACvB,QAAQ,IAAI,OAAO,kBAAkB,CACvC;CACA,IAAI,iBAAiB,SAAS,GAAG,SAAS,mBAAmB;CAE7D,MAAM,SAAS,+BAA+B,QAAQ,IAAI,OAAO,QAAQ,CAAC;CAC1E,IAAI,QAAQ,SAAS,SAAS;CAE9B,MAAM,WAAW,QAAQ,IAAI,OAAO,OAAO;CAC3C,IAAI,SAAS,QAAQ,GAAG;EACtB,MAAM,UAAU,YAAY,UAAU,SAAS;EAC/C,IAAI,SAAS;GACX,MAAM,OAAO,YAAY,UAAU,MAAM;GACzC,SAAS,QAAQ;IAAE;IAAS,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GAAG;EACxD;CACF;CAEA,OAAO;AACT;AAEA,SAAS,4BACP,OACA,KACoC;CACpC,MAAM,QAAQ,YAAY,OAAO,GAAG;CACpC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAEhC,QAAQ,OAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,SACH,OAAO;EACT,SACE;CACJ;AACF;AAqKA,SAAS,8BACP,OACyB;CACzB,MAAM,OAAO,YAAY,OAAO,MAAM;CACtC,MAAM,YAAY,YAAY,OAAO,WAAW;CAChD,OAAO;EACL,MAAM,MAAM;EACZ,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACvB,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;CACjD;AACF;;AAGA,SAAgB,+BACd,OACsC;CACtC,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAE7B,MAAM,YAAY,oBAAoB,QAAQ,IAAI,OAAO,WAAW,CAAC;CACrE,MAAM,WAAqC,CAAC;CAC5C,MAAM,KAAK,YAAY,OAAO,IAAI;CAClC,MAAM,QAAQ,YAAY,OAAO,OAAO;CACxC,MAAM,SAAS,YAAY,OAAO,QAAQ;CAK1C,MAAM,gBACJ,YAAY,OAAO,eAAe,KAAK,YAAY,OAAO,OAAO;CAGnE,MAAM,OAAO,YAAY,OAAO,MAAM,KAAK,YAAY,OAAO,SAAS;CACvE,MAAM,QAAQ,QAAQ,IAAI,OAAO,OAAO;CACxC,IAAI,IAAI,SAAS,KAAK;CACtB,IAAI,OAAO,SAAS,QAAQ;CAC5B,IAAI,QAAQ,SAAS,SAAS;CAC9B,IAAI,eAAe,SAAS,gBAAgB;CAC5C,IAAI,MAAM,SAAS,OAAO;CAE1B,IAAI,SAAS,KAAK,GAAG,SAAS,QAAQ;CACtC,MAAM,YAAY,QAAQ,IAAI,OAAO,WAAW;CAChD,IAAI,OAAO,cAAc,UACvB,SAAS,YAAY;MAChB,IAAI,qBAAqB,QAAQ,CAAC,OAAO,MAAM,UAAU,QAAQ,CAAC,GAIvE,SAAS,YAAY,UAAU,YAAY;CAE7C,IAAI,UAAU,SAAS,GACrB,SAAS,YAAY;CAGvB,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,IAAI,WAAW,KAAA;AACvD;AAEA,SAAS,8BACP,OACyB;CACzB,MAAM,UACJ,YAAY,OAAO,SAAS,KAC5B,kBAAkB,OAAO,SAAS,SAAS,KAC3C;CACF,MAAM,OAAO,YAAY,OAAO,MAAM;CACtC,OAAO;EACL;EACA,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;CACzB;AACF;AAEA,SAAS,oBAAoB,OAA6C;CACxE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,OAAoC,CAAC;CAC3C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,mCAAmC,IAAI;EACnD,IAAI,KACF,KAAK,KAAK,GAAG;CAEjB;CACA,OAAO;AACT;AAEA,SAAS,mCACP,OACkC;CAClC,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAC7B,MAAM,SAAS,QAAQ,IAAI,OAAO,QAAQ;CAC1C,IAAI,CAAC,SAAS,MAAM,GAAG,OAAO,KAAA;CAE9B,MAAM,OAAO,2BAA2B,OAAO,MAAM;CACrD,MAAM,aAAa,YAAY,OAAO,YAAY;CAClD,MAAM,WAAW,YAAY,OAAO,UAAU;CAC9C,MAAM,QAAQ,YAAY,OAAO,OAAO;CACxC,MAAM,OAAO,YAAY,OAAO,MAAM;CACtC,MAAM,WAAW,YAAY,OAAO,UAAU;CAC9C,MAAM,OAAO,YAAY,OAAO,MAAM;CACtC,MAAM,YAAY,YAAY,OAAO,WAAW;CAChD,MAAM,WAAW,+BAA+B,QAAQ,UAAU;CAClE,MAAM,OAAO,YAAY,QAAQ,MAAM;CACvC,MAAM,WAAW,YAAY,QAAQ,UAAU;CAC/C,MAAM,QAAQ,YAAY,QAAQ,OAAO;CACzC,IACE,CAAC,QACD,CAAC,cACD,CAAC,YACD,CAAC,SACD,CAAC,QACD,CAAC,YACD,SAAS,KAAA,KACT,CAAC,aACD,CAAC,YACD,CAAC,QACD,CAAC,YACD,CAAC,OAED;CAGF,MAAM,YAAY,gBAAgB,OAAO,WAAW;CACpD,MAAM,MAAM,cAAc,OAAO,KAAK;CACtC,MAAM,YAAY,gCAAgC,QAAQ,WAAW;CACrE,MAAM,QAAQ,YAAY,QAAQ,OAAO;CACzC,MAAM,YAAY,YAAY,QAAQ,WAAW;CAEjD,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EACjC,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;EACrB,QAAQ;GACN;GACA;GACA;GACA;GACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;GACjC,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EACnC;CACF;AACF;AAEA,SAAS,gBAAgB,OAAe,KAAiC;CACvE,MAAM,QAAQ,YAAY,OAAO,GAAG;CACpC,IAAI,CAAC,SAAS,MAAM,SAAS,MAAM,OAAO,KAAA;CAC1C,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,KAAK;EACzB,OAAO,IAAI,aAAa,WAAW,IAAI,aAAa,WAChD,QACA,KAAA;CACN,QAAQ;EACN;CACF;AACF;;;;;;;;AASA,SAAS,cAAc,OAAe,KAAiC;CACrE,MAAM,QAAQ,YAAY,OAAO,GAAG;CACpC,IAAI,CAAC,SAAS,MAAM,SAAS,MAAM,OAAO,KAAA;CAI1C,IAAI,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,WAAW,IAAI,KAAK,CAAC,MAAM,SAAS,IAAI,GAC1E,OAAO;CACT,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,KAAK;EACzB,OAAO,IAAI,aAAa,WAAW,IAAI,aAAa,WAChD,QACA,KAAA;CACN,QAAQ;EACN;CACF;AACF;AAEA,SAAS,2BACP,OACA,KAC0C;CAC1C,MAAM,QAAQ,YAAY,OAAO,GAAG;CACpC,OAAO,UAAU,WAAW,UAAU,WAAW,QAAQ,KAAA;AAC3D;AAEA,SAAS,+BACP,OACA,KACwD;CACxD,MAAM,QAAQ,YAAY,OAAO,GAAG;CACpC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAEhC,QAAQ,OAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,iBACH,OAAO;EACT,SACE;CACJ;AACF;AAEA,SAAS,gCACP,OACA,KACyD;CACzD,MAAM,QAAQ,YAAY,OAAO,GAAG;CACpC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAEhC,QAAQ,OAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,QACH,OAAO;EACT,SACE;CACJ;AACF;AAEA,SAAS,kBACP,OACA,KACA,WACoB;CACpB,MAAM,SAAS,QAAQ,IAAI,OAAO,GAAG;CACrC,OAAO,SAAS,MAAM,IAAI,YAAY,QAAQ,SAAS,IAAI,KAAA;AAC7D;AAEA,SAAS,YAAY,OAAe,KAAiC;CACnE,MAAM,QAAQ,QAAQ,IAAI,OAAO,GAAG;CACpC,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;AAEA,SAAS,YAAY,OAAe,KAAiC;CACnE,MAAM,QAAQ,QAAQ,IAAI,OAAO,GAAG;CACpC,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;AAEA,SAAS,SAAS,OAAiC;CACjD,OAAO,OAAO,UAAU,YAAY,UAAU;AAChD"}
|
|
1
|
+
{"version":3,"file":"generation-types.js","names":[],"sources":["../../src/generation-types.ts"],"sourcesContent":["import { tanstackMetadata } from '@tanstack/ai/client'\nimport type {\n MediaPrompt,\n PersistedArtifactRef,\n StreamChunk,\n} from '@tanstack/ai/client'\nimport type { TokenUsage, TranscriptionResponseFormat } from '@tanstack/ai'\nimport type { ByokClient } from './byok'\nimport type { ConnectConnectionAdapter } from './connection-adapters'\nimport type { AIDevtoolsClientMetadata } from './devtools'\nimport type {\n GenerationDevtoolsBridgeFactory,\n VideoDevtoolsBridgeFactory,\n} from './devtools-noop'\n\n// ===========================\n// Inference Utilities\n// ===========================\n\n/**\n * Maps an `onResult` transform's raw return type to the stored output type.\n *\n * - A concrete return (excluding null/void/undefined) becomes the output type.\n * - A return of only null/void/undefined falls back to TResult (the transform\n * reacted to the result or chose to keep it, rather than replacing it).\n *\n * Hooks infer `TReturn` directly from the `onResult` return position — a\n * covariant inference site that works even for an optional nested property —\n * which both contextually types the callback parameter as `TResult` and\n * narrows `result`. See issue #848.\n *\n * @template TResult - The raw result type from the generation\n * @template TReturn - The transform's return type (defaults to `void` when no\n * transform is provided)\n */\nexport type InferGenerationOutputFromReturn<TResult, TReturn> = [\n Exclude<TReturn, null | void | undefined>,\n] extends [never]\n ? TResult\n : Exclude<TReturn, null | void | undefined>\n\n/**\n * Infers the output type from an `onResult` callback's type.\n *\n * - If the callback returns a concrete type (excluding null/void/undefined), uses that type.\n * - If the callback only returns null/void/undefined, or is not provided, falls back to TResult.\n *\n * @template TResult - The raw result type from the generation\n * @template TFn - The onResult callback type (or undefined if not provided)\n */\nexport type InferGenerationOutput<TResult, TFn> = TFn extends (\n result: any,\n) => infer R\n ? InferGenerationOutputFromReturn<TResult, R>\n : TResult\n\n// ===========================\n// State\n// ===========================\n\n/**\n * State machine for generation clients.\n * Simpler than ChatClientState since generation is a single request/response cycle.\n */\nexport type GenerationClientState = 'idle' | 'generating' | 'success' | 'error'\n\n/**\n * Status of a persisted/restored generation run.\n *\n * `running` / `complete` / `error` are the three the server-side mapper emits\n * over the wire. `idle` is client-local only: `stop()` rewrites a `running`\n * snapshot to it so a cancelled run is no longer resumable.\n *\n * @internal\n */\nexport type GenerationResumeStatus = 'idle' | 'running' | 'complete' | 'error'\n\n/**\n * Thrown when a generation stream ends without a terminal `RUN_FINISHED` /\n * `RUN_ERROR` chunk — a proxy/load-balancer idle timeout, a server restart\n * mid-run, or a durable log whose terminal append never landed. The run's\n * outcome is unknowable from the client, so it settles as an error rather than\n * leaving the client stuck on `generating` forever.\n */\nexport const GENERATION_STREAM_TRUNCATED_MESSAGE =\n 'The generation stream ended before the run finished (no RUN_FINISHED or RUN_ERROR was received) — the connection was interrupted. Generate again to retry.'\n\n/**\n * Reported when a restored snapshot says the run completed but the activity's\n * `reconstructResult` mapper cannot rebuild a result from it — typically an\n * output artifact persisted without a serve `url`. Surfacing it beats a\n * `success` status with a `null` result, which no consumer can render.\n */\nexport const GENERATION_UNRESTORABLE_RESULT_MESSAGE =\n 'The stored generation completed but its result could not be rebuilt from the persisted record (its output artifact carries no serve URL, or the fields this activity needs were not persisted). Generate again to produce a fresh result.'\n\n/**\n * Wrap a mount-hydration failure with context. Genuine failures (transport\n * error, a 403 from the authorize gate, an unparseable body, a record the\n * client's validator rejects) must reach the app; only a genuine miss — the\n * server reporting no record for the thread — stays silent.\n */\nexport function createGenerationHydrationError(\n detail: string,\n cause?: unknown,\n): Error {\n const suffix = cause instanceof Error ? `: ${cause.message}` : ''\n const error = new Error(\n `[TanStack AI] Restoring the last generation for this thread failed — ${detail}${suffix}`,\n )\n if (cause !== undefined) {\n error.cause = cause\n }\n return error\n}\n\n/**\n * Map a persisted resume status to the client's live state machine on restore:\n * complete → success, error → error, running → generating, idle → idle. A\n * restored `running` only reaches this mapping when a `joinRun` handler can\n * tail the run to completion; without one the client rewrites the snapshot to\n * `error` (interrupted) before repainting, so it never sticks on `generating`.\n */\nexport function clientStateFromResumeStatus(\n status: GenerationResumeStatus,\n): GenerationClientState {\n switch (status) {\n case 'complete':\n return 'success'\n case 'error':\n return 'error'\n case 'running':\n return 'generating'\n case 'idle':\n return 'idle'\n }\n}\n\n/** @internal */\nexport interface GenerationResumeState {\n threadId: string\n runId: string\n /**\n * Artifact refs observed while the run is still in flight. Non-null only while\n * a run is streaming (`resumeState` itself is null once it ends); the final\n * refs move onto `result.artifacts` when the run completes.\n */\n pendingArtifacts?: Array<PersistedArtifactRef>\n}\n\n/** @internal */\nexport interface GenerationResultSnapshot {\n id?: string\n model?: string\n status?: string\n /**\n * The provider's async job handle (e.g. a Veo/fal video job id used for\n * status polling) — NOT the generation's own `runId`, which lives on\n * {@link GenerationResumeState.runId}.\n */\n providerJobId?: string\n expiresAt?: string\n /**\n * The text output of a text activity (a transcription's `text` or a summary's\n * `summary`). Persisted so a text generation restores its result on reload\n * (text is small and not bytes). Absent for media activities, whose output\n * restores from `artifacts`.\n */\n text?: string\n /** Token usage, persisted so a text result that requires it can be rebuilt. */\n usage?: TokenUsage\n artifacts?: Array<PersistedArtifactRef>\n}\n\n/** @internal */\nexport interface GenerationErrorSnapshot {\n message: string\n code?: string\n}\n\n/** @internal */\nexport interface GenerationEventSnapshot {\n type: StreamChunk['type']\n name?: string\n timestamp?: number\n}\n\n/** @internal */\nexport interface GenerationResumeSnapshot {\n /**\n * Version of the snapshot shape. Written on every snapshot the client builds\n * so future shape changes can migrate (or reject) an older record hydrated\n * from the server. Absent means `1`.\n */\n schemaVersion?: 1\n resumeState: GenerationResumeState | null\n status: GenerationResumeStatus\n activity?: PersistedArtifactRef['source']['activity']\n pendingArtifacts?: Array<PersistedArtifactRef>\n result?: GenerationResultSnapshot\n error?: GenerationErrorSnapshot\n lastEvent?: GenerationEventSnapshot\n}\n\n/**\n * The `persistence` / `threadId` identity shared by every generation hook.\n *\n * Turning persistence on **requires** a `threadId`, the stable scope runs are\n * filed under. Without one the client would hydrate by a generated id that\n * changes every reload, so nothing would ever restore; making it a type error\n * means the compiler asks for the scope instead of the runtime inventing one.\n *\n * `threadId` is the only identity for the hook, the AG-UI wire thread, and\n * persistence. There is no separate instance `id`.\n *\n * USAGE: intersect this onto a hook's parameter and subtract `persistence`\n * and `threadId` from the options interface (plus any other keys the hook\n * redeclares, such as `onResult`), leaving that interface a plain (non-union)\n * object so `Pick` / `Omit` composition elsewhere keeps working:\n *\n * ```ts\n * options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId'> & {\n * onResult?: (result: ImageGenerationResult) => TTransformed\n * } & GenerationPersistenceOptions\n * ```\n *\n * Do NOT bake the union into the options interface itself: a later plain `Omit`\n * over a union collapses it to a single object type and the requirement\n * silently disappears. `use-generation-persistence-types.test.ts` pins this.\n */\nexport type GenerationPersistenceOptions =\n | {\n persistence: true\n /** Required by `persistence`. The stable scope runs are filed under. */\n threadId: string\n }\n | {\n persistence?: false | undefined\n /** Stable scope for the generation slot (also the wire / DevTools identity). */\n threadId?: string\n }\n\n// ===========================\n// Event Constants\n// ===========================\n\n/**\n * Well-known CUSTOM event names used by generation clients.\n * These events are emitted by the server-side streaming helpers\n * and consumed by the client-side GenerationClient.\n */\nexport const GENERATION_EVENTS = {\n /** The generation result payload */\n RESULT: 'generation:result',\n /** Persisted artifact refs for generated media */\n ARTIFACTS: 'generation:artifacts',\n /** Progress update (0-100) with optional message */\n PROGRESS: 'generation:progress',\n /** Video job created with jobId */\n VIDEO_JOB_CREATED: 'video:job:created',\n /** Video job status update */\n VIDEO_STATUS: 'video:status',\n} as const\n\n// ===========================\n// Transport Types\n// ===========================\n\n/**\n * Options passed to a fetcher function by the generation client.\n */\nexport interface GenerationFetcherOptions {\n /** AbortSignal that is triggered when the user calls `stop()` */\n signal: AbortSignal\n /** Extra request headers for this run (e.g. BYOK keys). */\n headers?: Record<string, string>\n}\n\n/**\n * A direct async function that performs a generation request.\n *\n * Can return the result directly, or return a `Response` with an SSE body\n * (e.g., from a TanStack Start server function using `toServerSentEventsResponse()`).\n * When a `Response` is returned, the client will parse it as an SSE stream.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n */\nexport type GenerationFetcher<TInput, TResult> = (\n input: TInput,\n options?: GenerationFetcherOptions,\n) => Promise<TResult | Response>\n\n/**\n * Transport configuration for generation clients.\n * Supports either a connect-based streaming adapter or a direct fetcher function.\n */\nexport type GenerationTransport<TInput, TResult> =\n | { connection: ConnectConnectionAdapter; fetcher?: never }\n | { fetcher: GenerationFetcher<TInput, TResult>; connection?: never }\n\n// ===========================\n// Client Options\n// ===========================\n\n/**\n * Options for the GenerationClient.\n *\n * @template TInput - The input type for the generation request (used by consuming code)\n * @template TResult - The result type returned by the generation\n * @template TOutput - The output type after optional transform (defaults to TResult)\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention -- _TInput is unused in the interface body but part of the public positional generic API (callers supply it for inference)\nexport interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {\n /**\n * The **scope** this generation belongs to: a stable, app-chosen name for the\n * slot successive runs fill, not a link to a chat conversation. This is the\n * only identity for the client: wire thread id, DevTools hook id, and\n * persistence key.\n *\n * A generation hook starts empty and produces many runs over its life. Each\n * run gets its own `runId`, but they all belong to one scope. Persistence\n * keys on this: server-driven hydrates the last run for it on mount. It is\n * also sent as the AG-UI thread id on the wire, since the protocol requires\n * one.\n *\n * Derive it from your own domain. It must be meaningful before any media\n * exists and identical after a reload:\n *\n * ```ts\n * threadId: `video-${videoId}-start-frame`\n * ```\n *\n * **Required whenever `persistence` is set.** An app that cannot name the\n * scope has nothing to restore *to*, and a generated fallback would key each\n * reload differently, silently restoring nothing. Optional only for\n * ephemeral runs. If omitted, the client mints a wire id after mount.\n */\n threadId?: string\n\n /** Additional body parameters to send with connect-based adapter requests */\n body?: Record<string, any>\n\n /**\n * Optional BYOK keyring. On each generate the client prepares the resolved\n * provider and stamps `x-byok-*` request headers. Keys never go in the body.\n */\n byok?: ByokClient\n\n /**\n * Optional provider id for this generation. If it returns a provider slug,\n * only that key is prepared and sent. Otherwise `body.provider` is used.\n * If no slug resolves, generate throws instead of attaching every stored key.\n */\n byokProvider?: () => string | undefined\n\n /** Metadata used to register this generation hook with TanStack AI Devtools */\n devtools?: Partial<AIDevtoolsClientMetadata>\n\n /**\n * How this generation persists across reloads.\n *\n * - Omit or `false`: ephemeral, in-memory only.\n * - `true`: server-driven. On mount the client hydrates the last generation\n * for its `threadId` from the server (needs a `hydrateGeneration` handler,\n * from the connection or the option below) and repaints that snapshot. It\n * never auto-starts a run.\n *\n * The record lives on the server, written by `withGenerationPersistence`. The\n * browser caches nothing, so a generation's history is never duplicated into\n * client storage.\n */\n persistence?: boolean\n\n /**\n * Server-driven hydration handler, for transports that don't carry one on\n * the connection: supply it alongside `fetcher` (or a `stream()` /\n * `rpcStream()` connection built without handlers) so `persistence: true`\n * can restore the last generation for `threadId` on mount. Typically a\n * one-line TanStack Start server-function call backed by\n * `getGenerationHydration` from `@tanstack/ai-persistence`.\n *\n * A connection's own `hydrateGeneration` takes precedence when both exist.\n */\n hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']\n\n /**\n * Re-attach handler for a run that is still generating, for transports that\n * don't carry one on the connection. The client tails this on mount when a\n * restored/hydrated snapshot reports a run in flight, replaying it to\n * completion in place. Without it, a restored `running` snapshot surfaces\n * as an (interrupted) error — an interrupted generation cannot be resumed,\n * only re-run.\n *\n * A connection's own `joinRun` takes precedence when both exist.\n */\n joinRun?: ConnectConnectionAdapter['joinRun']\n\n /**\n * Factory that constructs the devtools bridge. Default is a no-op\n * factory; the real implementation lives in `@tanstack/ai-client/devtools`.\n */\n devtoolsBridgeFactory?: GenerationDevtoolsBridgeFactory\n\n /**\n * Callback when a result is received. Can optionally return a transformed value\n * that replaces the stored result.\n *\n * - Return a non-null value to transform and store it as the result\n * - Return `null` to keep the previous result unchanged\n * - Return nothing (`void`) to store the raw result as-is\n */\n onResult?: (result: TResult) => TOutput | null | void\n /** Callback when an error occurs */\n onError?: (error: Error) => void\n /** Callback when progress is reported (0-100) */\n onProgress?: (progress: number, message?: string) => void\n /** Callback for each stream chunk (connect-based adapter mode only) */\n onChunk?: (chunk: StreamChunk) => void\n\n // Framework state callbacks (set by hooks, not users)\n /** @internal Called when result changes */\n onResultChange?: (result: TOutput | null) => void\n /** @internal Called when loading state changes */\n onLoadingChange?: (isLoading: boolean) => void\n /** @internal Called when error state changes */\n onErrorChange?: (error: Error | undefined) => void\n /** @internal Called when generation status changes */\n onStatusChange?: (status: GenerationClientState) => void\n /** @internal Called when lightweight resume snapshot changes. Receives `undefined` when the snapshot is cleared by `reset()`. */\n onResumeSnapshotChange?: (\n snapshot: GenerationResumeSnapshot | undefined,\n ) => void\n /** @internal Called when the in-flight run identity changes. `null` once no run is in flight. Mirrors the chat client's resume-state callback. */\n onResumeStateChange?: (resumeState: GenerationResumeState | null) => void\n\n /**\n * @internal Rebuild a typed result from a restored snapshot, injected by each\n * specialized client/hook (which knows the concrete result shape). Called on\n * mount restore (client store or server hydrate) so `result` repaints as if the\n * run had just finished, with media resolved to the durable serve URL. Returns\n * `null` when the snapshot cannot rebuild a result (then `result` stays null;\n * `status` / `error` / `resumeState` still repaint).\n */\n reconstructResult?: (restored: GenerationRestoredResult) => TResult | null\n}\n\n/**\n * The restorable shape handed to a client's `reconstructResult` mapper: the\n * result metadata that survived persistence plus the durable artifact refs (each\n * carrying its serve {@link PersistedArtifactRef.url}). The specialized client\n * turns this into its own typed result (image `images`, video `url`, text\n * `text`, ...).\n */\nexport interface GenerationRestoredResult {\n id?: string\n model?: string\n status?: string\n /** The provider's async job handle — see {@link GenerationResultSnapshot.providerJobId}. */\n providerJobId?: string\n expiresAt?: string\n text?: string\n usage?: TokenUsage\n activity?: PersistedArtifactRef['source']['activity']\n artifacts: Array<PersistedArtifactRef>\n}\n\n/**\n * Reduces one observed stream chunk into the lightweight resume snapshot.\n *\n * A `RUN_STARTED` chunk begins a fresh run, so stale `result` / `error` /\n * `pendingArtifacts` from a previous run are dropped rather than carried into\n * the new run's snapshot.\n *\n * @internal\n */\nexport function updateGenerationResumeSnapshot(\n previous: GenerationResumeSnapshot | null | undefined,\n chunk: StreamChunk,\n): GenerationResumeSnapshot {\n const tanstack = tanstackMetadata(chunk)\n const threadId =\n stringField(chunk, 'threadId') ??\n (typeof tanstack?.threadId === 'string' ? tanstack.threadId : undefined)\n const runId =\n stringField(chunk, 'runId') ??\n (typeof tanstack?.runId === 'string' ? tanstack.runId : undefined)\n const carried = chunk.type === 'RUN_STARTED' ? undefined : previous\n const previousArtifacts = carried?.pendingArtifacts ?? []\n const next: GenerationResumeSnapshot = {\n schemaVersion: 1,\n resumeState: carried?.resumeState ?? null,\n status: carried?.status ?? 'idle',\n ...(carried?.activity ? { activity: carried.activity } : {}),\n ...(previousArtifacts.length > 0\n ? { pendingArtifacts: [...previousArtifacts] }\n : {}),\n ...(carried?.result ? { result: { ...carried.result } } : {}),\n ...(carried?.error ? { error: { ...carried.error } } : {}),\n lastEvent: createGenerationEventSnapshot(chunk),\n }\n\n if (threadId && runId) {\n next.resumeState = { threadId, runId }\n next.status = 'running'\n } else if (chunk.type === 'RUN_STARTED') {\n next.status = 'running'\n }\n\n if (chunk.type === 'CUSTOM') {\n if (chunk.name === GENERATION_EVENTS.ARTIFACTS) {\n const artifacts = collectArtifactRefs(chunk.value)\n if (artifacts.length > 0) {\n next.pendingArtifacts = artifacts\n next.activity = artifacts[0]?.source.activity\n }\n } else if (chunk.name === GENERATION_EVENTS.RESULT) {\n const result = createGenerationResultSnapshot(chunk.value)\n if (result) {\n next.result = result\n if (result.artifacts && result.artifacts.length > 0) {\n next.pendingArtifacts = result.artifacts\n next.activity = result.artifacts[0]?.source.activity\n }\n }\n } else if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) {\n // Capture the provider job id as soon as the job exists — for a long\n // video run this is the one piece of identity worth having after a\n // reload, and the terminal `generation:result` may never arrive.\n const providerJobId = isObject(chunk.value)\n ? stringField(chunk.value, 'jobId')\n : undefined\n if (providerJobId) {\n next.result = { ...next.result, providerJobId }\n }\n }\n } else if (chunk.type === 'RUN_FINISHED') {\n next.resumeState = null\n next.status = 'complete'\n } else if (chunk.type === 'RUN_ERROR') {\n next.resumeState = null\n next.status = 'error'\n next.error = createGenerationErrorSnapshot(chunk)\n }\n\n return next\n}\n\n/**\n * Validates an untrusted value (a hydration body resolved by the server) into a\n * {@link GenerationResumeSnapshot}, or returns `undefined` when the value is\n * not a usable snapshot.\n *\n * A hydrated record is outside the type system: it may be stale, truncated, or\n * written by a different version. Every field is re-validated with the same\n * narrowing the live chunk reducer uses. `lastEvent` is not restored, since it\n * describes a transient stream position with no meaning after a reload.\n *\n * @internal\n */\nexport function parseGenerationResumeSnapshot(\n value: unknown,\n): GenerationResumeSnapshot | undefined {\n if (!isObject(value)) return undefined\n\n const schemaVersion = Reflect.get(value, 'schemaVersion')\n if (schemaVersion !== undefined && schemaVersion !== 1) return undefined\n\n const status = generationResumeStatusField(value, 'status')\n if (!status) return undefined\n\n const rawResumeState = Reflect.get(value, 'resumeState')\n let resumeState: GenerationResumeState | null = null\n if (rawResumeState !== null && rawResumeState !== undefined) {\n if (!isObject(rawResumeState)) return undefined\n const threadId = stringField(rawResumeState, 'threadId')\n const runId = stringField(rawResumeState, 'runId')\n if (!threadId || !runId) return undefined\n resumeState = { threadId, runId }\n }\n\n const snapshot: GenerationResumeSnapshot = {\n schemaVersion: 1,\n resumeState,\n status,\n }\n\n const activity = persistedArtifactActivityField(value, 'activity')\n if (activity) snapshot.activity = activity\n\n const pendingArtifacts = collectArtifactRefs(\n Reflect.get(value, 'pendingArtifacts'),\n )\n if (pendingArtifacts.length > 0) snapshot.pendingArtifacts = pendingArtifacts\n\n const result = createGenerationResultSnapshot(Reflect.get(value, 'result'))\n if (result) snapshot.result = result\n\n const rawError = Reflect.get(value, 'error')\n if (isObject(rawError)) {\n const message = stringField(rawError, 'message')\n if (message) {\n const code = stringField(rawError, 'code')\n snapshot.error = { message, ...(code ? { code } : {}) }\n }\n }\n\n return snapshot\n}\n\nfunction generationResumeStatusField(\n value: object,\n key: string,\n): GenerationResumeStatus | undefined {\n const field = stringField(value, key)\n if (field === undefined) return undefined\n\n switch (field) {\n case 'idle':\n case 'running':\n case 'complete':\n case 'error':\n return field\n default:\n return undefined\n }\n}\n\n// ===========================\n// Video-Specific Options\n// ===========================\n\n/**\n * Video status information returned during job polling.\n */\nexport interface VideoStatusInfo {\n /** Job identifier */\n jobId: string\n /** Current status of the video generation job */\n status: 'pending' | 'processing' | 'completed' | 'failed'\n /** Progress percentage (0-100), if available */\n progress?: number\n /** URL to the generated video (when completed) */\n url?: string\n /** Error message if status is 'failed' */\n error?: string\n}\n\n/**\n * Composite result for video generation (job completion).\n */\nexport interface VideoGenerateResult {\n /** Job identifier */\n jobId: string\n /** Final status */\n status: 'completed'\n /** URL to the generated video */\n url: string\n /** When the URL expires, if applicable */\n expiresAt?: Date\n /** Persisted artifact references for generated assets, when available */\n artifacts?: Array<PersistedArtifactRef>\n}\n\n/**\n * Options for the VideoGenerationClient.\n */\nexport interface VideoGenerationClientOptions<\n TOutput = VideoGenerateResult,\n> extends Omit<\n GenerationClientOptions<VideoGenerateInput, VideoGenerateResult, TOutput>,\n 'devtoolsBridgeFactory'\n> {\n /**\n * Factory that constructs the video devtools bridge. Default is a no-op\n * factory; the real implementation lives in `@tanstack/ai-client/devtools`.\n */\n devtoolsBridgeFactory?: VideoDevtoolsBridgeFactory\n\n /** Callback when a video job is created */\n onJobCreated?: (jobId: string) => void\n /** Callback on each status update */\n onStatusUpdate?: (status: VideoStatusInfo) => void\n\n // Framework state callbacks\n /** @internal Called when jobId changes */\n onJobIdChange?: (jobId: string | null) => void\n /** @internal Called when video status changes */\n onVideoStatusChange?: (status: VideoStatusInfo | null) => void\n}\n\n// ===========================\n// Input Types\n// ===========================\n\n/**\n * Input for image generation.\n */\nexport interface ImageGenerateInput {\n /**\n * Description of the desired image(s): plain text, or an ordered array of\n * content parts (text + image) for image-conditioned generation\n * (image-to-image, multi-reference, edit / inpaint).\n */\n prompt: MediaPrompt\n /** Number of images to generate (default: 1) */\n numberOfImages?: number\n /** Image size in WIDTHxHEIGHT format (e.g., \"1024x1024\") */\n size?: string\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio generation (music, sound effects).\n */\nexport interface AudioGenerateInput {\n /** Text description of the desired audio */\n prompt: string\n /** Desired duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text-to-speech generation.\n */\nexport interface SpeechGenerateInput {\n /** The text to convert to speech */\n text: string\n /** The voice to use for generation */\n voice?: string\n /** The output audio format */\n format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'\n /** The speed of the generated audio (0.25 to 4.0) */\n speed?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for audio transcription.\n */\nexport interface TranscriptionGenerateInput {\n /** The audio data to transcribe - can be base64 string, File, Blob, or ArrayBuffer */\n audio: string | File | Blob | ArrayBuffer\n /** The language of the audio in ISO-639-1 format (e.g., 'en') */\n language?: string\n /** An optional prompt to guide the transcription */\n prompt?: string\n /** The format of the transcription output */\n responseFormat?: TranscriptionResponseFormat\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for text summarization.\n */\nexport interface SummarizeGenerateInput {\n /** The text to summarize */\n text: string\n /** Maximum length of the summary */\n maxLength?: number\n /** Style of the summary */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics to focus on */\n focus?: Array<string>\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\n/**\n * Input for video generation.\n */\nexport interface VideoGenerateInput {\n /**\n * Description of the desired video: plain text, or an ordered array of\n * content parts (text + image) for image-conditioned generation\n * (image-to-video, start/end frames).\n */\n prompt: MediaPrompt\n /** Video size — format depends on provider (e.g., \"16:9\", \"1280x720\") */\n size?: string\n /** Video duration in seconds */\n duration?: number\n /** Model-specific options */\n modelOptions?: Record<string, any>\n}\n\nfunction createGenerationEventSnapshot(\n chunk: StreamChunk,\n): GenerationEventSnapshot {\n const name = stringField(chunk, 'name')\n const timestamp = numberField(chunk, 'timestamp')\n return {\n type: chunk.type,\n ...(name ? { name } : {}),\n ...(timestamp !== undefined ? { timestamp } : {}),\n }\n}\n\n/** @internal Narrows an untrusted result payload into the persisted result snapshot shape. */\nexport function createGenerationResultSnapshot(\n value: unknown,\n): GenerationResultSnapshot | undefined {\n if (!isObject(value)) return undefined\n\n const artifacts = collectArtifactRefs(Reflect.get(value, 'artifacts'))\n const snapshot: GenerationResultSnapshot = {}\n const id = stringField(value, 'id')\n const model = stringField(value, 'model')\n const status = stringField(value, 'status')\n // A live provider result carries its job handle as `jobId` (e.g.\n // `VideoGenerateResult.jobId`); a persisted snapshot carries it as\n // `providerJobId`. Accept both — this narrows raw results AND stored\n // snapshots.\n const providerJobId =\n stringField(value, 'providerJobId') ?? stringField(value, 'jobId')\n // A transcription's output is `text`; a summary's is `summary`. Capture either\n // under `text` so a text result restores on reload.\n const text = stringField(value, 'text') ?? stringField(value, 'summary')\n const usage = Reflect.get(value, 'usage')\n if (id) snapshot.id = id\n if (model) snapshot.model = model\n if (status) snapshot.status = status\n if (providerJobId) snapshot.providerJobId = providerJobId\n if (text) snapshot.text = text\n // Passthrough opaque token-usage metadata (untrusted; not deeply validated).\n if (isObject(usage)) snapshot.usage = usage as TokenUsage\n const expiresAt = Reflect.get(value, 'expiresAt')\n if (typeof expiresAt === 'string') {\n snapshot.expiresAt = expiresAt\n } else if (expiresAt instanceof Date && !Number.isNaN(expiresAt.getTime())) {\n // `toISOString()` throws on an invalid Date. This runs per chunk on live\n // provider values, so drop an unusable date like every other bad field\n // here rather than throwing out of the stream loop.\n snapshot.expiresAt = expiresAt.toISOString()\n }\n if (artifacts.length > 0) {\n snapshot.artifacts = artifacts\n }\n\n return Object.keys(snapshot).length > 0 ? snapshot : undefined\n}\n\nfunction createGenerationErrorSnapshot(\n chunk: StreamChunk,\n): GenerationErrorSnapshot {\n const message =\n stringField(chunk, 'message') ??\n nestedStringField(chunk, 'error', 'message') ??\n 'An error occurred'\n const code = stringField(chunk, 'code')\n return {\n message,\n ...(code ? { code } : {}),\n }\n}\n\nfunction collectArtifactRefs(value: unknown): Array<PersistedArtifactRef> {\n if (!Array.isArray(value)) return []\n const refs: Array<PersistedArtifactRef> = []\n for (const item of value) {\n const ref = createPersistedArtifactRefSnapshot(item)\n if (ref) {\n refs.push(ref)\n }\n }\n return refs\n}\n\nfunction createPersistedArtifactRefSnapshot(\n value: unknown,\n): PersistedArtifactRef | undefined {\n if (!isObject(value)) return undefined\n const source = Reflect.get(value, 'source')\n if (!isObject(source)) return undefined\n\n const role = persistedArtifactRoleField(value, 'role')\n const artifactId = stringField(value, 'artifactId')\n const threadId = stringField(value, 'threadId')\n const runId = stringField(value, 'runId')\n const name = stringField(value, 'name')\n const mimeType = stringField(value, 'mimeType')\n const size = numberField(value, 'size')\n const createdAt = stringField(value, 'createdAt')\n const activity = persistedArtifactActivityField(source, 'activity')\n const path = stringField(source, 'path')\n const provider = stringField(source, 'provider')\n const model = stringField(source, 'model')\n if (\n !role ||\n !artifactId ||\n !threadId ||\n !runId ||\n !name ||\n !mimeType ||\n size === undefined ||\n !createdAt ||\n !activity ||\n !path ||\n !provider ||\n !model\n ) {\n return undefined\n }\n\n const sourceUrl = durableUrlField(value, 'sourceUrl')\n const url = serveUrlField(value, 'url')\n const mediaType = persistedArtifactMediaTypeField(source, 'mediaType')\n const jobId = stringField(source, 'jobId')\n const expiresAt = stringField(source, 'expiresAt')\n\n return {\n role,\n artifactId,\n threadId,\n runId,\n name,\n mimeType,\n size,\n createdAt,\n ...(sourceUrl ? { sourceUrl } : {}),\n ...(url ? { url } : {}),\n source: {\n activity,\n path,\n provider,\n model,\n ...(mediaType ? { mediaType } : {}),\n ...(jobId ? { jobId } : {}),\n ...(expiresAt ? { expiresAt } : {}),\n },\n }\n}\n\nfunction durableUrlField(value: object, key: string): string | undefined {\n const field = stringField(value, key)\n if (!field || field.length > 2048) return undefined\n try {\n const url = new URL(field)\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? field\n : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Validates an app-origin serve URL, which unlike a provider URL is usually a\n * same-origin path (`/api/.../artifact?id=...`). Accepts an absolute http(s) URL\n * or a path-absolute same-origin URL (single leading `/`); rejects\n * protocol-relative (`//host`), `javascript:` / `data:`, and anything else, since\n * this value is rendered as media `src`.\n */\nfunction serveUrlField(value: object, key: string): string | undefined {\n const field = stringField(value, key)\n if (!field || field.length > 2048) return undefined\n // A single leading `/` is a safe same-origin path. Reject protocol-relative\n // `//host` AND a backslash bypass (`/\\host` — the URL parser treats `\\` as `/`\n // for http(s), so it would resolve to a foreign origin as an `<img src>`).\n if (field.startsWith('/') && !field.startsWith('//') && !field.includes('\\\\'))\n return field\n try {\n const url = new URL(field)\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? field\n : undefined\n } catch {\n return undefined\n }\n}\n\nfunction persistedArtifactRoleField(\n value: object,\n key: string,\n): PersistedArtifactRef['role'] | undefined {\n const field = stringField(value, key)\n return field === 'input' || field === 'output' ? field : undefined\n}\n\nfunction persistedArtifactActivityField(\n value: object,\n key: string,\n): PersistedArtifactRef['source']['activity'] | undefined {\n const field = stringField(value, key)\n if (field === undefined) return undefined\n\n switch (field) {\n case 'image':\n case 'audio':\n case 'tts':\n case 'video':\n case 'transcription':\n return field\n default:\n return undefined\n }\n}\n\nfunction persistedArtifactMediaTypeField(\n value: object,\n key: string,\n): PersistedArtifactRef['source']['mediaType'] | undefined {\n const field = stringField(value, key)\n if (field === undefined) return undefined\n\n switch (field) {\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n case 'json':\n return field\n default:\n return undefined\n }\n}\n\nfunction nestedStringField(\n value: object,\n key: string,\n nestedKey: string,\n): string | undefined {\n const nested = Reflect.get(value, key)\n return isObject(nested) ? stringField(nested, nestedKey) : undefined\n}\n\nfunction stringField(value: object, key: string): string | undefined {\n const field = Reflect.get(value, key)\n return typeof field === 'string' ? field : undefined\n}\n\nfunction numberField(value: object, key: string): number | undefined {\n const field = Reflect.get(value, key)\n return typeof field === 'number' ? field : undefined\n}\n\nfunction isObject(value: unknown): value is object {\n return typeof value === 'object' && value !== null\n}\n"],"mappings":"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"}
|
|
@@ -30,6 +30,7 @@ export declare class InterruptManager<TTools extends ReadonlyArray<AnyClientTool
|
|
|
30
30
|
private submissionRootErrors;
|
|
31
31
|
private state;
|
|
32
32
|
private activeTransaction;
|
|
33
|
+
private activeSubmissionOperation;
|
|
33
34
|
private retrySubmission;
|
|
34
35
|
private resuming;
|
|
35
36
|
private tools;
|
|
@@ -220,6 +220,7 @@ var InterruptManager = class {
|
|
|
220
220
|
resuming: false
|
|
221
221
|
});
|
|
222
222
|
activeTransaction;
|
|
223
|
+
activeSubmissionOperation;
|
|
223
224
|
retrySubmission;
|
|
224
225
|
resuming = false;
|
|
225
226
|
tools;
|
|
@@ -238,6 +239,7 @@ var InterruptManager = class {
|
|
|
238
239
|
this.tools = tools;
|
|
239
240
|
}
|
|
240
241
|
hydrate(hydration, source = "live") {
|
|
242
|
+
this.activeSubmissionOperation = void 0;
|
|
241
243
|
this.hydration = {
|
|
242
244
|
threadId: hydration.threadId,
|
|
243
245
|
interruptedRunId: hydration.interruptedRunId,
|
|
@@ -269,6 +271,7 @@ var InterruptManager = class {
|
|
|
269
271
|
return this.items.some((item) => item.kind === "generic" && item.definition !== void 0);
|
|
270
272
|
}
|
|
271
273
|
reset(options) {
|
|
274
|
+
this.activeSubmissionOperation = void 0;
|
|
272
275
|
this.hydration = void 0;
|
|
273
276
|
this.items = [];
|
|
274
277
|
this.snapshot = Object.freeze([]);
|
|
@@ -787,20 +790,26 @@ var InterruptManager = class {
|
|
|
787
790
|
this.retrySubmission = void 0;
|
|
788
791
|
}
|
|
789
792
|
submitBatch(submission) {
|
|
793
|
+
const operation = { submission };
|
|
794
|
+
this.activeSubmissionOperation = operation;
|
|
790
795
|
this.resuming = true;
|
|
791
796
|
this.retrySubmission = void 0;
|
|
792
797
|
for (const item of this.items) if (isClientOwnedInterrupt(item)) item.status = "submitting";
|
|
793
798
|
this.publish();
|
|
794
|
-
this.performSubmission(
|
|
799
|
+
this.performSubmission(operation);
|
|
795
800
|
}
|
|
796
|
-
async performSubmission(
|
|
801
|
+
async performSubmission(operation) {
|
|
797
802
|
try {
|
|
798
|
-
await this.options.submit(submission);
|
|
803
|
+
await this.options.submit(operation.submission);
|
|
799
804
|
} catch (error) {
|
|
800
|
-
this.
|
|
805
|
+
if (this.activeSubmissionOperation !== operation) return;
|
|
806
|
+
this.handleSubmissionFailure(error, operation.submission);
|
|
801
807
|
} finally {
|
|
802
|
-
this.
|
|
803
|
-
|
|
808
|
+
if (this.activeSubmissionOperation === operation) {
|
|
809
|
+
this.activeSubmissionOperation = void 0;
|
|
810
|
+
this.resuming = false;
|
|
811
|
+
this.publish();
|
|
812
|
+
}
|
|
804
813
|
}
|
|
805
814
|
}
|
|
806
815
|
handleSubmissionFailure(error, submission) {
|