@tanstack/ai-client 0.25.2 → 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.
Files changed (51) hide show
  1. package/README.md +2 -1
  2. package/dist/esm/byok/client.d.ts +52 -0
  3. package/dist/esm/byok/client.js +300 -0
  4. package/dist/esm/byok/client.js.map +1 -0
  5. package/dist/esm/byok/passkey.d.ts +43 -0
  6. package/dist/esm/byok/passkey.js +251 -0
  7. package/dist/esm/byok/passkey.js.map +1 -0
  8. package/dist/esm/byok/resolve.d.ts +8 -0
  9. package/dist/esm/byok/resolve.js +20 -0
  10. package/dist/esm/byok/resolve.js.map +1 -0
  11. package/dist/esm/byok/storage.d.ts +15 -0
  12. package/dist/esm/byok/storage.js +16 -0
  13. package/dist/esm/byok/storage.js.map +1 -0
  14. package/dist/esm/byok.d.ts +8 -0
  15. package/dist/esm/byok.js +5 -0
  16. package/dist/esm/chat-client.d.ts +18 -5
  17. package/dist/esm/chat-client.js +96 -59
  18. package/dist/esm/chat-client.js.map +1 -1
  19. package/dist/esm/connection-adapters.d.ts +2 -0
  20. package/dist/esm/connection-adapters.js +70 -30
  21. package/dist/esm/connection-adapters.js.map +1 -1
  22. package/dist/esm/generation-client.d.ts +3 -1
  23. package/dist/esm/generation-client.js +27 -6
  24. package/dist/esm/generation-client.js.map +1 -1
  25. package/dist/esm/generation-types.d.ts +14 -0
  26. package/dist/esm/generation-types.js +4 -2
  27. package/dist/esm/generation-types.js.map +1 -1
  28. package/dist/esm/interrupt-manager.d.ts +1 -0
  29. package/dist/esm/interrupt-manager.js +15 -6
  30. package/dist/esm/interrupt-manager.js.map +1 -1
  31. package/dist/esm/sse-parser.js +8 -1
  32. package/dist/esm/sse-parser.js.map +1 -1
  33. package/dist/esm/types.d.ts +46 -3
  34. package/dist/esm/types.js.map +1 -1
  35. package/dist/esm/video-generation-client.d.ts +3 -1
  36. package/dist/esm/video-generation-client.js +29 -8
  37. package/dist/esm/video-generation-client.js.map +1 -1
  38. package/package.json +7 -3
  39. package/src/byok/client.ts +360 -0
  40. package/src/byok/passkey.ts +399 -0
  41. package/src/byok/resolve.ts +30 -0
  42. package/src/byok/storage.ts +29 -0
  43. package/src/byok.ts +20 -0
  44. package/src/chat-client.ts +138 -77
  45. package/src/connection-adapters.ts +108 -53
  46. package/src/generation-client.ts +53 -9
  47. package/src/generation-types.ts +24 -2
  48. package/src/interrupt-manager.ts +20 -6
  49. package/src/sse-parser.ts +10 -0
  50. package/src/types.ts +48 -3
  51. package/src/video-generation-client.ts +50 -9
@@ -46,6 +46,8 @@ export declare class GenerationClient<TInput extends Record<string, any>, TResul
46
46
  private readonly persistenceScope;
47
47
  private readonly serverDriven;
48
48
  private body;
49
+ private byok;
50
+ private byokProvider;
49
51
  private result;
50
52
  private input;
51
53
  private progress;
@@ -96,7 +98,7 @@ export declare class GenerationClient<TInput extends Record<string, any>, TResul
96
98
  /**
97
99
  * Update options without recreating the client.
98
100
  */
99
- updateOptions(options: Partial<Pick<GenerationClientOptions<TInput, TResult, TOutput>, 'body' | 'onResult' | 'onError' | 'onProgress' | 'onChunk'>>): void;
101
+ updateOptions(options: Partial<Pick<GenerationClientOptions<TInput, TResult, TOutput>, 'body' | 'byok' | 'byokProvider' | 'onResult' | 'onError' | 'onProgress' | 'onChunk'>>): void;
100
102
  dispose(): void;
101
103
  getResult(): TOutput | null;
102
104
  getIsLoading(): boolean;
@@ -1,6 +1,9 @@
1
+ import { prepareResolvedByokHeaders, resolveByokProviderId } from "./byok/resolve.js";
1
2
  import { createNoOpGenerationDevtoolsBridge } from "./devtools-noop.js";
2
3
  import { GENERATION_EVENTS, GENERATION_STREAM_TRUNCATED_MESSAGE, GENERATION_UNRESTORABLE_RESULT_MESSAGE, clientStateFromResumeStatus, createGenerationHydrationError, createGenerationResultSnapshot, parseGenerationResumeSnapshot, updateGenerationResumeSnapshot } from "./generation-types.js";
3
4
  import { parseSSEResponse } from "./sse-parser.js";
5
+ import { restoreInboundChunk } from "@tanstack/ai/client";
6
+ import { ByokBlockedError, ByokMissingError } from "@tanstack/ai/byok";
4
7
  //#region src/generation-client.ts
5
8
  /**
6
9
  * A lightweight, generic client for one-shot generation tasks
@@ -49,6 +52,8 @@ var GenerationClient = class {
49
52
  persistenceScope;
50
53
  serverDriven = false;
51
54
  body;
55
+ byok;
56
+ byokProvider;
52
57
  result = null;
53
58
  input = null;
54
59
  progress = null;
@@ -72,6 +77,8 @@ var GenerationClient = class {
72
77
  this.hydrateGenerationHandler = options.hydrateGeneration;
73
78
  this.joinRunHandler = options.joinRun;
74
79
  this.body = options.body ?? {};
80
+ this.byok = options.byok;
81
+ this.byokProvider = options.byokProvider;
75
82
  this.serverDriven = options.persistence === true;
76
83
  if (options.persistence && !this.persistenceScope) console.warn("[TanStack AI] `persistence` needs a stable `threadId` to key on. Without one nothing will be restored after a reload. Pass a `threadId` derived from your own domain (e.g. `product-123-hero`).");
77
84
  this.callbacksRef = {
@@ -142,8 +149,16 @@ var GenerationClient = class {
142
149
  this.abortController = abortController;
143
150
  const { signal } = abortController;
144
151
  try {
152
+ let headers;
153
+ if (this.byok) {
154
+ const provider = resolveByokProviderId(this.byokProvider, this.body.provider);
155
+ headers = await prepareResolvedByokHeaders(this.byok, provider);
156
+ }
145
157
  if (this.fetcher) {
146
- const result = await this.fetcher(input, { signal });
158
+ const result = await this.fetcher(input, headers === void 0 ? { signal } : {
159
+ signal,
160
+ headers
161
+ });
147
162
  if (signal.aborted) return;
148
163
  if (result instanceof Response) await this.processStream(parseSSEResponse(result, signal), runId, signal);
149
164
  else {
@@ -157,7 +172,7 @@ var GenerationClient = class {
157
172
  ...this.body,
158
173
  ...input
159
174
  };
160
- const stream = this.connection.connect([], mergedData, signal, this.createRunContext(runId));
175
+ const stream = this.connection.connect([], mergedData, signal, this.createRunContext(runId, headers));
161
176
  await this.processStream(stream, runId, signal);
162
177
  } else throw new Error("GenerationClient requires either a connection or fetcher option");
163
178
  if (!signal.aborted && this.status === "success") {
@@ -167,6 +182,8 @@ var GenerationClient = class {
167
182
  } catch (err) {
168
183
  if (signal.aborted) return;
169
184
  const error = err instanceof Error ? err : new Error(String(err));
185
+ if (error instanceof ByokMissingError) this.byok?.request(error.provider, "missing");
186
+ if (error instanceof ByokBlockedError && error.reason === "locked") this.byok?.request(error.provider, "locked");
170
187
  this.setError(error);
171
188
  this.setStatus("error");
172
189
  this.recordResumeSnapshotError(error);
@@ -194,8 +211,9 @@ var GenerationClient = class {
194
211
  async processStream(source, fallbackRunId, signal) {
195
212
  let streamRunId;
196
213
  let sawTerminalChunk = false;
197
- for await (const chunk of source) {
214
+ for await (const raw of source) {
198
215
  if (signal.aborted) break;
216
+ const chunk = restoreInboundChunk(raw);
199
217
  this.callbacksRef.onChunk?.(chunk);
200
218
  this.observeResumeSnapshot(chunk);
201
219
  const chunkRunId = "runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : void 0;
@@ -220,7 +238,7 @@ var GenerationClient = class {
220
238
  break;
221
239
  case "RUN_ERROR": {
222
240
  this.devtoolsBridge.ensureRunStarted(chunkRunId ?? streamRunId ?? fallbackRunId);
223
- const msg = chunk.message || chunk.error?.message || "An error occurred";
241
+ const msg = chunk.message || "An error occurred";
224
242
  throw new Error(msg);
225
243
  }
226
244
  }
@@ -272,6 +290,8 @@ var GenerationClient = class {
272
290
  */
273
291
  updateOptions(options) {
274
292
  if (options.body !== void 0) this.body = options.body ?? {};
293
+ if (options.byok !== void 0) this.byok = options.byok;
294
+ if (options.byokProvider !== void 0) this.byokProvider = options.byokProvider;
275
295
  if (options.onResult !== void 0) this.callbacksRef.onResult = options.onResult;
276
296
  if (options.onError !== void 0) this.callbacksRef.onError = options.onError;
277
297
  if (options.onProgress !== void 0) this.callbacksRef.onProgress = options.onProgress;
@@ -377,10 +397,11 @@ var GenerationClient = class {
377
397
  generateUniqueId(prefix) {
378
398
  return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
379
399
  }
380
- createRunContext(runId) {
400
+ createRunContext(runId, headers) {
381
401
  return {
382
402
  threadId: this.threadId,
383
- runId
403
+ runId,
404
+ ...headers ? { headers } : {}
384
405
  };
385
406
  }
386
407
  observeResumeSnapshot(chunk) {
@@ -1 +1 @@
1
- {"version":3,"file":"generation-client.js","names":[],"sources":["../../src/generation-client.ts"],"sourcesContent":["import {\n GENERATION_EVENTS,\n GENERATION_STREAM_TRUNCATED_MESSAGE,\n GENERATION_UNRESTORABLE_RESULT_MESSAGE,\n clientStateFromResumeStatus,\n createGenerationHydrationError,\n createGenerationResultSnapshot,\n parseGenerationResumeSnapshot,\n updateGenerationResumeSnapshot,\n} from './generation-types'\nimport { createNoOpGenerationDevtoolsBridge } from './devtools-noop'\nimport { parseSSEResponse } from './sse-parser'\nimport type { StreamChunk } from '@tanstack/ai/client'\nimport type {\n ConnectConnectionAdapter,\n GenerationHydrationResult,\n RunAgentInputContext,\n} from './connection-adapters'\nimport type {\n AIDevtoolsClientMetadata,\n AIDevtoolsGenerationProgress,\n GenerationDevtoolsBridge,\n GenerationDevtoolsBridgeOptions,\n} from './devtools'\nimport type {\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n GenerationRestoredResult,\n GenerationResumeSnapshot,\n GenerationResumeState,\n GenerationTransport,\n} from './generation-types'\n\n/**\n * Callbacks stored in a ref so hooks can update them without recreating the client.\n */\n// All optional fields explicitly allow `| undefined` so callers can spread\n// option bags (where each callback may be `undefined`) into the callbacks\n// ref under `exactOptionalPropertyTypes`.\ninterface GenerationCallbacks<TResult, TOutput> {\n onResult?: ((result: TResult) => TOutput | null | void) | undefined\n onError?: ((error: Error) => void) | undefined\n onProgress?: ((progress: number, message?: string) => void) | undefined\n onChunk?: ((chunk: StreamChunk) => void) | undefined\n onResultChange?: ((result: TOutput | null) => void) | undefined\n onLoadingChange?: ((isLoading: boolean) => void) | undefined\n onErrorChange?: ((error: Error | undefined) => void) | undefined\n onStatusChange?: ((status: GenerationClientState) => void) | undefined\n onResumeSnapshotChange?:\n | ((snapshot: GenerationResumeSnapshot | undefined) => void)\n | undefined\n onResumeStateChange?:\n | ((resumeState: GenerationResumeState | null) => void)\n | undefined\n reconstructResult?:\n | ((restored: GenerationRestoredResult) => TResult | null)\n | undefined\n}\n\n/**\n * A lightweight, generic client for one-shot generation tasks\n * (image, speech, transcription, summarize).\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom).\n * Server wraps results in StreamChunk events with CUSTOM event names.\n * - **Fetcher** — Direct async function call. No streaming protocol needed.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n *\n * @example\n * ```typescript\n * // With streaming connection adapter\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * connection: fetchServerSentEvents('/api/generate/image'),\n * onResultChange: setResult,\n * onLoadingChange: setIsLoading,\n * })\n *\n * // With fetcher (direct)\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * fetcher: async (input) => {\n * const res = await fetch('/api/generate/image', {\n * method: 'POST',\n * body: JSON.stringify(input),\n * })\n * return res.json()\n * },\n * })\n *\n * await client.generate({ prompt: 'A sunset over mountains' })\n * ```\n */\nexport class GenerationClient<\n TInput extends Record<string, any>,\n TResult,\n TOutput = TResult,\n> {\n private readonly connection: ConnectConnectionAdapter | undefined\n private readonly fetcher: GenerationFetcher<TInput, TResult> | undefined\n // Persistence handlers supplied as options (e.g. alongside a `fetcher`), used\n // when the connection doesn't carry its own — the connection's handlers take\n // precedence when both exist.\n private readonly hydrateGenerationHandler:\n | ConnectConnectionAdapter['hydrateGeneration']\n | undefined\n private readonly joinRunHandler:\n | ConnectConnectionAdapter['joinRun']\n | undefined\n private uniqueId: string\n private readonly devtoolsMetadata: AIDevtoolsClientMetadata\n private readonly devtoolsBridge: GenerationDevtoolsBridge<TOutput>\n private threadId: string\n private readonly persistenceScope: string | undefined\n // Server-driven mode (`persistence: true`): no local snapshot store; on mount\n // the client hydrates the last generation for `threadId` from the server.\n private readonly serverDriven: boolean = false\n private body: Record<string, any>\n private result: TOutput | null = null\n private input: TInput | null = null\n private progress: AIDevtoolsGenerationProgress | null = null\n private isLoading = false\n private error: Error | undefined = undefined\n private status: GenerationClientState = 'idle'\n private resumeSnapshot: GenerationResumeSnapshot | undefined\n private lastEmittedResumeState: string | undefined\n private abortController: AbortController | null = null\n private rejoinedRunId: string | undefined\n private readonly callbacksRef: GenerationCallbacks<TResult, TOutput>\n private devtoolsMounted = false\n private disposed = false\n private serverHydrationStarted = false\n\n constructor(\n options: Omit<\n GenerationClientOptions<TInput, TResult, TOutput>,\n 'persistence' | 'threadId'\n > &\n GenerationPersistenceOptions &\n GenerationTransport<TInput, TResult>,\n ) {\n // `threadId` is the only identity. Do not mint a random id during\n // construct: hooks build this client during render.\n this.threadId = options.threadId ?? ''\n this.uniqueId = this.threadId\n // The persistence scope is the explicit `threadId` and nothing else.\n // The types require it whenever `persistence` is set. This field keeps a\n // generated wire id from becoming a storage key for JS callers.\n this.persistenceScope = options.threadId\n this.connection = options.connection\n this.fetcher = options.fetcher\n this.hydrateGenerationHandler = options.hydrateGeneration\n this.joinRunHandler = options.joinRun\n this.body = options.body ?? {}\n // `persistence` is `false`/omitted (ephemeral) or `true` (server-driven:\n // hydrate the last generation for `threadId` from the server on mount).\n this.serverDriven = options.persistence === true\n // The types require `threadId` alongside `persistence`, so this only fires\n // for JS callers. Warn rather than fall back silently: keying on the\n // generated wire id would write a different slot every reload, restoring\n // nothing while accumulating orphaned records.\n if (options.persistence && !this.persistenceScope) {\n console.warn(\n '[TanStack AI] `persistence` needs a stable `threadId` to key on. Without one nothing will be restored after a reload. Pass a `threadId` derived from your own domain (e.g. `product-123-hero`).',\n )\n }\n this.callbacksRef = {\n onResult: options.onResult,\n onError: options.onError,\n onProgress: options.onProgress,\n onChunk: options.onChunk,\n onResultChange: options.onResultChange,\n onLoadingChange: options.onLoadingChange,\n onErrorChange: options.onErrorChange,\n onStatusChange: options.onStatusChange,\n onResumeSnapshotChange: options.onResumeSnapshotChange,\n onResumeStateChange: options.onResumeStateChange,\n reconstructResult: options.reconstructResult,\n }\n\n this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools)\n this.devtoolsBridge = (\n options.devtoolsBridgeFactory ?? createNoOpGenerationDevtoolsBridge\n )<TOutput>(this.buildDevtoolsBridgeOptions())\n\n // Mount hydration (`maybeHydrateFromServer`) is deliberately NOT run here. The framework\n // hooks build this client inside `useMemo`, so the constructor executes in\n // React's render phase; hydrating here would re-fire the hydrate GET on\n // every discarded/speculative render, flooding the connection pool when\n // several clients mount together. It is kicked off once from\n // `mountDevtools`, which the hooks call from a commit-phase mount effect.\n }\n\n private buildDevtoolsBridgeOptions(): GenerationDevtoolsBridgeOptions<TOutput> {\n const client = this\n return {\n get hookId() {\n return client.uniqueId\n },\n get clientId() {\n return client.uniqueId\n },\n get threadId() {\n return client.threadId\n },\n metadata: this.devtoolsMetadata,\n getCoreState: () => ({\n input: this.input,\n result: this.result,\n progress: this.progress,\n status: this.status,\n isLoading: this.isLoading,\n ...(this.error ? { error: this.error.message } : {}),\n }),\n }\n }\n\n mountDevtools(): void {\n this.ensureThreadId()\n // Mounting revives a disposed client. Framework hooks call this from\n // their mount effect, so a dispose → remount cycle (e.g. React\n // StrictMode's mount → cleanup → mount replay against the same memoized\n // client) leaves the client usable again.\n this.disposed = false\n this.maybeHydrateFromServer()\n // Re-attach to an in-flight run whose snapshot is already loaded — the\n // remount case. On the FIRST mount the snapshot loads asynchronously and\n // `repaintRestoredSnapshot` starts the rejoin; on a StrictMode remount the\n // snapshot is already present but the prior rejoin was aborted by\n // `dispose()`, so retrigger it here. Guarded by `rejoinInFlight`'s own\n // dedupe/in-flight checks, so this never double-joins.\n this.maybeResumeInFlight()\n if (this.devtoolsMounted) {\n return\n }\n\n this.devtoolsMounted = true\n this.devtoolsBridge.emitRegistered()\n this.devtoolsBridge.emitSnapshot()\n }\n\n /**\n * Trigger a generation request.\n * Only one generation can be in-flight at a time; calling generate()\n * while already generating will be a no-op.\n */\n async generate(input: TInput): Promise<void> {\n if (this.disposed) return\n if (this.isLoading) return\n this.mountDevtools()\n\n this.input = input\n this.progress = null\n const runId = this.devtoolsBridge.beginRun(input)\n this.setIsLoading(true)\n this.setStatus('generating')\n this.setError(undefined)\n\n const abortController = new AbortController()\n this.abortController = abortController\n const { signal } = abortController\n\n try {\n if (this.fetcher) {\n // Direct fetch path\n const result = await this.fetcher(input, { signal })\n if (signal.aborted) return\n if (result instanceof Response) {\n // Server function returned SSE Response — parse stream\n await this.processStream(\n parseSSEResponse(result, signal),\n runId,\n signal,\n )\n } else {\n this.devtoolsBridge.ensureRunStarted(runId)\n this.setResult(result)\n this.setStatus('success')\n this.completePlainFetcherResumeSnapshot(result)\n }\n } else if (this.connection) {\n // Streaming adapter path\n const mergedData = { ...this.body, ...input }\n const stream = this.connection.connect(\n [],\n mergedData,\n signal,\n this.createRunContext(runId),\n )\n await this.processStream(stream, runId, signal)\n } else {\n throw new Error(\n 'GenerationClient requires either a connection or fetcher option',\n )\n }\n if (!signal.aborted && this.status === 'success') {\n // Bump progress to 100 on successful completion so devtools\n // snapshots reflect the final state. The bridge mirrors this in\n // the run's recorded progress, but the snapshot reads `progress`\n // from the client's core state.\n this.progress = completeProgressValue(this.progress)\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:completed',\n 'completed',\n )\n }\n } catch (err: unknown) {\n if (signal.aborted) return\n const error = err instanceof Error ? err : new Error(String(err))\n this.setError(error)\n this.setStatus('error')\n this.recordResumeSnapshotError(error)\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:errored',\n 'errored',\n error.message,\n )\n this.callbacksRef.onError?.(error)\n } finally {\n if (this.abortController === abortController) {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n }\n\n /**\n * Process a stream of AG-UI events from the streaming connection adapter.\n *\n * Throws {@link GENERATION_STREAM_TRUNCATED_MESSAGE} when the iteration ends\n * without a terminal chunk. A `for await` over a stream that simply stops —\n * proxy idle timeout, server restart, a durable log missing its terminal\n * append — returns normally and would otherwise leave the caller's `status`\n * on `generating` forever, with the persisted snapshot still `running` so\n * every reload rejoins the same dead run. Throwing routes it through the\n * caller's error path instead, which settles the status and rewrites the\n * snapshot so nothing chases it again.\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n fallbackRunId: string,\n signal: AbortSignal,\n ): Promise<void> {\n let streamRunId: string | undefined\n let sawTerminalChunk = false\n\n for await (const chunk of source) {\n if (signal.aborted) break\n\n this.callbacksRef.onChunk?.(chunk)\n this.observeResumeSnapshot(chunk)\n const chunkRunId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType has ~22 variants; this consumer only handles the subset relevant to generation lifecycle.\n switch (chunk.type) {\n case 'RUN_STARTED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n break\n }\n case 'CUSTOM': {\n this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId)\n if (chunk.name === GENERATION_EVENTS.RESULT) {\n this.setResult(chunk.value as TResult)\n } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {\n const { progress, message } = chunk.value as {\n progress: number\n message?: string\n }\n this.setProgress(progress, message)\n }\n break\n }\n case 'RUN_FINISHED': {\n streamRunId = chunk.runId\n sawTerminalChunk = true\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n this.setStatus('success')\n break\n }\n case 'RUN_ERROR': {\n this.devtoolsBridge.ensureRunStarted(\n chunkRunId ?? streamRunId ?? fallbackRunId,\n )\n // Prefer spec `message`; fall back to deprecated `error.message`\n const msg =\n (chunk.message as string | undefined) ||\n chunk.error?.message ||\n 'An error occurred'\n throw new Error(msg)\n }\n default:\n break\n }\n }\n\n // An aborted read is a deliberate stop/dispose, not a truncation.\n if (!sawTerminalChunk && !signal.aborted) {\n throw new Error(GENERATION_STREAM_TRUNCATED_MESSAGE)\n }\n }\n\n /**\n * Abort any in-flight generation request.\n */\n stop(): void {\n const runId = this.devtoolsBridge.getActiveRunId()\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n if (this.status === 'generating') {\n this.setStatus('idle')\n if (runId) {\n this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled')\n }\n }\n // A stopped run is no longer resumable. Without this the in-memory\n // snapshot stays `running`, and a remount's `maybeResumeInFlight` would\n // rejoin a run the user just cancelled.\n if (this.resumeSnapshot && this.resumeSnapshot.status === 'running') {\n this.resumeSnapshot = {\n ...this.resumeSnapshot,\n resumeState: null,\n status: 'idle',\n }\n this.notifyResumeSnapshotChanged()\n }\n }\n\n /**\n * Clear the result, error, and return to idle state. Also drops the client's\n * in-memory resume snapshot, so a remount restores nothing. The server-side\n * record is untouched — this client no longer writes one — so a full page\n * reload under `persistence: true` re-hydrates the last generation again.\n */\n reset(): void {\n this.stop()\n this.setResult(null)\n this.input = null\n this.progress = null\n this.devtoolsBridge.resetRuns()\n this.setError(undefined)\n this.setStatus('idle')\n this.clearResumeSnapshot()\n this.devtoolsBridge.emitState()\n }\n\n /**\n * Update options without recreating the client.\n */\n updateOptions(\n options: Partial<\n Pick<\n GenerationClientOptions<TInput, TResult, TOutput>,\n 'body' | 'onResult' | 'onError' | 'onProgress' | 'onChunk'\n >\n >,\n ): void {\n if (options.body !== undefined) {\n this.body = options.body ?? {}\n }\n if (options.onResult !== undefined) {\n this.callbacksRef.onResult = options.onResult\n }\n if (options.onError !== undefined) {\n this.callbacksRef.onError = options.onError\n }\n if (options.onProgress !== undefined) {\n this.callbacksRef.onProgress = options.onProgress\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.onChunk = options.onChunk\n }\n }\n\n dispose(): void {\n this.disposed = true\n // Teardown, NOT a user cancel: abort in-flight DELIVERY (this reader) but\n // do NOT call `stop()` — `stop()` marks the run non-resumable and wipes the\n // `running` snapshot, which is correct for a Stop button but wrong for an\n // unmount / React StrictMode dispose. Clearing it here would destroy the\n // in-memory resume state, so a remount of this same client instance could\n // never rejoin. (A real page revisit re-hydrates from the server instead.)\n // The run itself survives server-side (durable delivery), so the snapshot\n // must stay `running` for the remount to resume it.\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n this.devtoolsBridge.dispose()\n this.devtoolsMounted = false\n // Re-arm mount hydration + rejoin so a remount resumes from the (preserved)\n // snapshot. `mountDevtools` re-runs the hydration entry point and\n // `maybeResumeInFlight`, both individually guarded.\n this.serverHydrationStarted = false\n this.rejoinedRunId = undefined\n }\n\n // ===========================\n // Getters\n // ===========================\n\n getResult(): TOutput | null {\n return this.result\n }\n\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n getError(): Error | undefined {\n return this.error\n }\n\n getStatus(): GenerationClientState {\n return this.status\n }\n\n getResumeSnapshot(): GenerationResumeSnapshot | undefined {\n return this.resumeSnapshot\n ? {\n ...this.resumeSnapshot,\n ...(this.resumeSnapshot.pendingArtifacts\n ? { pendingArtifacts: [...this.resumeSnapshot.pendingArtifacts] }\n : {}),\n ...(this.resumeSnapshot.result\n ? {\n result: {\n ...this.resumeSnapshot.result,\n ...(this.resumeSnapshot.result.artifacts\n ? { artifacts: [...this.resumeSnapshot.result.artifacts] }\n : {}),\n },\n }\n : {}),\n ...(this.resumeSnapshot.error\n ? { error: { ...this.resumeSnapshot.error } }\n : {}),\n ...(this.resumeSnapshot.lastEvent\n ? { lastEvent: { ...this.resumeSnapshot.lastEvent } }\n : {}),\n }\n : undefined\n }\n\n // ===========================\n // Private state setters\n // ===========================\n\n private setResult(rawResult: TResult | null): void {\n if (rawResult === null) {\n this.result = null\n this.callbacksRef.onResultChange?.(null)\n this.devtoolsBridge.recordResultChange()\n return\n }\n\n if (this.callbacksRef.onResult) {\n const transformed = this.callbacksRef.onResult(rawResult)\n if (transformed === null) {\n // null return → keep previous result unchanged, just re-emit\n this.devtoolsBridge.emitState()\n return\n }\n if (transformed !== undefined) {\n // Non-null, non-undefined → use transformed value\n this.result = transformed\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n return\n }\n }\n\n // No onResult callback, or callback returned void → use raw value as\n // TOutput. When the caller did not supply an onResult transform,\n // `TOutput` defaults to `TResult`, so the runtime cast is sound.\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- TOutput defaults to TResult when no onResult transform is supplied\n this.result = rawResult as unknown as TOutput\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.onLoadingChange?.(isLoading)\n this.devtoolsBridge.recordLoadingChange()\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.onErrorChange?.(error)\n this.devtoolsBridge.recordErrorChange(error)\n }\n\n private setStatus(status: GenerationClientState): void {\n this.status = status\n this.callbacksRef.onStatusChange?.(status)\n this.devtoolsBridge.recordStatusChange(status)\n }\n\n private setProgress(value: number, message?: string): void {\n this.progress = {\n value,\n ...(message ? { message } : {}),\n }\n if (message === undefined) {\n this.callbacksRef.onProgress?.(value)\n } else {\n this.callbacksRef.onProgress?.(value, message)\n }\n this.devtoolsBridge.recordProgressChange()\n }\n\n private createDevtoolsMetadata(\n metadata?: Partial<AIDevtoolsClientMetadata>,\n ): AIDevtoolsClientMetadata {\n return {\n hookName: metadata?.hookName ?? 'useGeneration',\n ...(metadata?.framework ? { framework: metadata.framework } : {}),\n ...(metadata?.outputKind ? { outputKind: metadata.outputKind } : {}),\n ...(metadata?.name ? { name: metadata.name } : {}),\n }\n }\n\n private ensureThreadId(): string {\n if (!this.threadId) {\n this.threadId = this.generateUniqueId('generation')\n }\n this.uniqueId = this.threadId\n return this.threadId\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private createRunContext(runId: string): RunAgentInputContext {\n return {\n threadId: this.threadId,\n runId,\n }\n }\n\n private observeResumeSnapshot(chunk: StreamChunk): void {\n this.resumeSnapshot = updateGenerationResumeSnapshot(\n this.resumeSnapshot,\n chunk,\n )\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Notify the (internal) snapshot listener AND emit the public resume state.\n * The snapshot stays internal (persistence + devtools); the hook consumes\n * `resumeState`, mirroring the chat client.\n */\n private notifyResumeSnapshotChanged(): void {\n this.callbacksRef.onResumeSnapshotChange?.(this.resumeSnapshot)\n this.emitResumeState()\n }\n\n /**\n * Derive the public `resumeState` from the internal snapshot: the in-flight\n * run identity, with any in-flight artifact refs folded under it. `null` once\n * no run is in flight.\n *\n * The snapshot is rebuilt for every chunk, so emitting unconditionally would\n * hand each framework hook a fresh object per chunk and re-render the\n * component on every stream event. `resumeState` only changes at run\n * boundaries and when artifacts land, so skip the notification unless it\n * materially changed — same gate the persistence writes use.\n */\n private emitResumeState(): void {\n const snapshot = this.resumeSnapshot\n const state = snapshot?.resumeState\n const resumeState: GenerationResumeState | null = state\n ? {\n ...state,\n ...(snapshot?.pendingArtifacts && snapshot.pendingArtifacts.length > 0\n ? { pendingArtifacts: [...snapshot.pendingArtifacts] }\n : {}),\n }\n : null\n const signature = JSON.stringify(resumeState)\n if (signature === this.lastEmittedResumeState) {\n return\n }\n this.lastEmittedResumeState = signature\n this.callbacksRef.onResumeStateChange?.(resumeState)\n }\n\n /**\n * Repaint the normal fields from a restored snapshot (client store or server\n * hydrate), so a reload presents the run in `result` / `status` / `error`\n * exactly as a just-finished run would, never a bolt-on snapshot object.\n * `isLoading` stays false: the client never auto-tails a restored run. The\n * snapshot is not re-persisted here (it came from storage / the server).\n *\n * When the activity's mapper DECLINES a `complete` snapshot the repaint\n * settles as an error instead: `success` with a `null` result is a state no\n * consumer can render, and it hides the real cause (an output artifact\n * persisted without a serve URL). A decline on any other status is expected —\n * a `running` snapshot has no result yet, the rejoin will deliver it.\n */\n private repaintFromSnapshot(snapshot: GenerationResumeSnapshot): void {\n this.resumeSnapshot = snapshot\n this.notifyResumeSnapshotChanged()\n this.setStatus(clientStateFromResumeStatus(snapshot.status))\n this.setError(\n snapshot.error\n ? Object.assign(\n new Error(snapshot.error.message),\n snapshot.error.code ? { code: snapshot.error.code } : {},\n )\n : undefined,\n )\n const restored = this.reconstructRestoredResult(snapshot)\n if (restored !== null) {\n this.setResult(restored)\n } else if (\n this.callbacksRef.reconstructResult &&\n snapshot.status === 'complete'\n ) {\n this.reportUnrestorableResult()\n }\n }\n\n /**\n * Report a `complete` snapshot the activity's mapper could not rebuild.\n * Runs after the status/error repaint above, so it wins over the snapshot's\n * own `complete` status.\n */\n private reportUnrestorableResult(): void {\n const error = new Error(GENERATION_UNRESTORABLE_RESULT_MESSAGE)\n this.setStatus('error')\n this.setError(error)\n this.callbacksRef.onError?.(error)\n }\n\n /**\n * Repaint a restored snapshot (client store or server hydrate) and, when it\n * reports a run still in flight, tail that run to completion via `joinRun`\n * (from the connection, or the `joinRun` option when the transport can't\n * carry one).\n *\n * A `running` snapshot that no `joinRun` handler can tail is repainted as an\n * interrupted error instead of a `generating` status that would never\n * settle: an interrupted generation cannot be resumed, only re-run.\n */\n private repaintRestoredSnapshot(\n snapshot: GenerationResumeSnapshot,\n activeRunId?: string,\n ): void {\n if (snapshot.status !== 'running') {\n this.repaintFromSnapshot(snapshot)\n return\n }\n const joinRun = this.connection?.joinRun ?? this.joinRunHandler\n const runId = activeRunId ?? snapshot.resumeState?.runId\n if (runId && joinRun) {\n this.repaintFromSnapshot(snapshot)\n this.rejoinInFlight(runId)\n return\n }\n this.repaintFromSnapshot({\n ...snapshot,\n resumeState: null,\n status: 'error',\n error: {\n message:\n 'The previous generation was interrupted before it finished and cannot be resumed — generate again to retry.',\n },\n })\n }\n\n /**\n * Build the restorable result shape from the snapshot and hand it to the\n * per-activity `reconstructResult` mapper (injected by the specialized\n * client/hook, which knows the concrete result type).\n *\n * Returns `null` both when no mapper is set (nothing to rebuild — `result`\n * simply stays null) and when the mapper declines. The caller distinguishes\n * the two: see {@link repaintFromSnapshot}.\n */\n private reconstructRestoredResult(\n snapshot: GenerationResumeSnapshot,\n ): TResult | null {\n const build = this.callbacksRef.reconstructResult\n if (!build) return null\n const result = snapshot.result\n const restored: GenerationRestoredResult = {\n ...(result?.id !== undefined ? { id: result.id } : {}),\n ...(result?.model !== undefined ? { model: result.model } : {}),\n ...(result?.status !== undefined ? { status: result.status } : {}),\n ...(result?.providerJobId !== undefined\n ? { providerJobId: result.providerJobId }\n : {}),\n ...(result?.expiresAt !== undefined\n ? { expiresAt: result.expiresAt }\n : {}),\n ...(result?.text !== undefined ? { text: result.text } : {}),\n ...(result?.usage !== undefined ? { usage: result.usage } : {}),\n ...(snapshot.activity !== undefined\n ? { activity: snapshot.activity }\n : {}),\n artifacts: result?.artifacts ?? [],\n }\n return build(restored)\n }\n\n /**\n * The plain (non-Response) fetcher path never observes stream chunks, so\n * the terminal snapshot is built here from the fetcher's own result. A\n * stale `error` from a previous run is intentionally dropped — this run\n * succeeded.\n */\n private completePlainFetcherResumeSnapshot(rawResult: unknown): void {\n const previous = this.resumeSnapshot\n const result = createGenerationResultSnapshot(rawResult)\n this.resumeSnapshot = {\n schemaVersion: 1,\n resumeState: null,\n status: 'complete',\n ...(previous?.activity ? { activity: previous.activity } : {}),\n ...(previous?.pendingArtifacts && previous.pendingArtifacts.length > 0\n ? { pendingArtifacts: [...previous.pendingArtifacts] }\n : {}),\n ...(result\n ? { result }\n : previous?.result\n ? { result: { ...previous.result } }\n : {}),\n }\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Records a transport-level failure (network drop, throwing callback) in\n * the snapshot. Without this, only a server-emitted RUN_ERROR chunk would\n * mark the snapshot `error`, leaving a persisted record that claims the\n * run is still in flight.\n */\n private recordResumeSnapshotError(error: Error): void {\n // Surface the failure on the OBSERVABLE fields FIRST: a rejoin (or live\n // stream) that emits RUN_ERROR has already flipped the snapshot to `error`\n // via `observeResumeSnapshot`, so the early-return below would otherwise\n // skip this and leave `status` stuck on `generating` — the run would look\n // like it is still going forever. The guard avoids a duplicate `error`\n // emission on the live `generate()` path, which sets the status itself.\n if (this.status !== 'error') this.setStatus('error')\n this.setError(error)\n if (this.resumeSnapshot?.status === 'error') return\n if (!this.resumeSnapshot && !this.serverDriven) return\n const previous = this.resumeSnapshot\n this.resumeSnapshot = {\n schemaVersion: 1,\n resumeState: null,\n status: 'error',\n ...(previous?.activity ? { activity: previous.activity } : {}),\n ...(previous?.pendingArtifacts && previous.pendingArtifacts.length > 0\n ? { pendingArtifacts: [...previous.pendingArtifacts] }\n : {}),\n ...(previous?.result ? { result: { ...previous.result } } : {}),\n error: { message: error.message },\n }\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Drop the client's in-memory snapshot and re-emit. Purely local — this\n * client writes no storage, so nothing persisted is removed.\n */\n private clearResumeSnapshot(): void {\n this.resumeSnapshot = undefined\n this.lastEmittedResumeState = undefined\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Server-driven mount hydration entry point (`persistence: true`). Runs at\n * most once, from the commit-phase mount path (`mountDevtools`) — never the\n * constructor / render phase — so remounts and speculative renders can't\n * re-fire the hydrate GET.\n */\n private maybeHydrateFromServer(): void {\n if (!this.serverDriven || this.serverHydrationStarted) return\n this.serverHydrationStarted = true\n if (this.connection?.hydrateGeneration ?? this.hydrateGenerationHandler) {\n this.hydrateFromServer()\n } else {\n // `persistence: true` without any hydrate source can never restore\n // anything — warn rather than silently no-op.\n console.warn(\n '[TanStack AI] `persistence: true` (server-driven) needs a `hydrateGeneration` handler — either a connection that implements one (e.g. `fetchServerSentEvents` / `fetchHttpStream`, or `stream()` / `rpcStream()` with persistence handlers) or the `hydrateGeneration` option. Without one, nothing is persisted or restored.',\n )\n }\n }\n\n /**\n * Server-driven mount hydration (`persistence: true`). The client holds no\n * local snapshot; on mount it asks the server — keyed by the stable threadId —\n * for the last generation's resume snapshot, validates it, and repaints it. It\n * never auto-starts a run, and never blocks: a `generate()` that starts first\n * owns the client and hydration backs off, mirroring the chat client.\n *\n * A genuine **miss** (the server reports no record for the thread) is silent —\n * a fresh thread is not an error. A genuine **failure** (transport error, a\n * 403 from the authorize gate, a malformed body, a record the client's own\n * validator rejects) is surfaced through `status` / `error` / `onError`, so a\n * broken server is distinguishable from an empty one and the app can retry.\n */\n private hydrateFromServer(): void {\n const hydrate =\n this.connection?.hydrateGeneration ?? this.hydrateGenerationHandler\n if (!hydrate) return\n // A send that already started owns the client; don't stomp it.\n if (this.resumeSnapshot || this.isLoading || this.status !== 'idle') return\n void (async () => {\n let res: GenerationHydrationResult\n try {\n res = await hydrate(this.threadId)\n } catch (cause) {\n this.failHydration(\n createGenerationHydrationError(\n 'the request to the server did not succeed',\n cause,\n ),\n )\n return\n }\n // No record for this thread — a fresh thread, not a failure.\n if (!res.resumeSnapshot) return\n const snapshot = parseGenerationResumeSnapshot(res.resumeSnapshot)\n if (!snapshot) {\n this.failHydration(\n createGenerationHydrationError(\n 'the server returned a record this client cannot read (unknown schema version, or a missing/invalid `status` or `resumeState`)',\n ),\n )\n return\n }\n // Re-check: a send may have started while the fetch was in flight.\n if (this.resumeSnapshot || this.isLoading || this.status !== 'idle')\n return\n // A run still generating on the server: re-attach and finish it in place.\n this.repaintRestoredSnapshot(snapshot, res.activeRun?.runId)\n })()\n }\n\n /**\n * Surface a hydration failure on the observable fields. Skipped when a\n * `generate()` took ownership while the hydrate GET was in flight — the live\n * run's state must win over a stale mount-time failure.\n */\n private failHydration(error: Error): void {\n if (this.resumeSnapshot || this.isLoading || this.status !== 'idle') return\n this.setStatus('error')\n this.setError(error)\n this.callbacksRef.onError?.(error)\n }\n\n /**\n * Re-attach to an already-loaded `running` snapshot (the remount case). Safe\n * to call repeatedly: `rejoinInFlight` dedupes on `rejoinedRunId` and bails\n * when a run is already in flight, so on the first mount (where the rejoin was\n * already started from `repaintRestoredSnapshot`) this is a no-op.\n */\n private maybeResumeInFlight(): void {\n if (this.resumeSnapshot?.status !== 'running') return\n const runId = this.resumeSnapshot.resumeState?.runId\n if (runId) this.rejoinInFlight(runId)\n }\n\n /**\n * Re-attach to a run that is still generating and stream it to completion,\n * mirroring the chat client's mount-time rejoin. Reuses `processStream`, so\n * `result` / `progress` / `status` repaint from the replayed chunks exactly as\n * a live run does. Best-effort: a live `generate()` owns the client and is\n * never stomped, and the same run is only rejoined once.\n */\n private rejoinInFlight(runId: string): void {\n const joinRun = this.connection?.joinRun ?? this.joinRunHandler\n if (!joinRun) return\n if (this.rejoinedRunId === runId) return\n // A fresh send (or an in-progress rejoin) owns the client.\n if (this.isLoading || this.abortController) return\n this.rejoinedRunId = runId\n const controller = new AbortController()\n this.abortController = controller\n this.setIsLoading(true)\n this.setStatus('generating')\n void (async () => {\n try {\n await this.processStream(\n joinRun(runId, controller.signal),\n runId,\n controller.signal,\n )\n } catch (error) {\n if (!controller.signal.aborted) {\n const failure =\n error instanceof Error ? error : new Error(String(error))\n // Settles `status`/`error` AND rewrites the snapshot to a terminal\n // `error` with a null `resumeState`, so the next mount does not\n // rejoin this run again.\n this.recordResumeSnapshotError(failure)\n this.callbacksRef.onError?.(failure)\n }\n } finally {\n // Only reset if this rejoin still owns the client: a `stop()` +\n // fresh `generate()` may have replaced the controller while the tail\n // was settling, and that live run owns `isLoading` now.\n if (this.abortController === controller) {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n })()\n }\n}\n\nfunction completeProgressValue(\n progress: AIDevtoolsGenerationProgress | null,\n): AIDevtoolsGenerationProgress | null {\n if (!progress) return null\n const message = progress.message\n return {\n value: 100,\n ...(message ? { message } : {}),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"generation-client.js","names":[],"sources":["../../src/generation-client.ts"],"sourcesContent":["import { ByokBlockedError, ByokMissingError } from '@tanstack/ai/byok'\nimport {\n prepareResolvedByokHeaders,\n resolveByokProviderId,\n} from './byok/resolve'\nimport {\n GENERATION_EVENTS,\n GENERATION_STREAM_TRUNCATED_MESSAGE,\n GENERATION_UNRESTORABLE_RESULT_MESSAGE,\n clientStateFromResumeStatus,\n createGenerationHydrationError,\n createGenerationResultSnapshot,\n parseGenerationResumeSnapshot,\n updateGenerationResumeSnapshot,\n} from './generation-types'\nimport { createNoOpGenerationDevtoolsBridge } from './devtools-noop'\nimport { parseSSEResponse } from './sse-parser'\nimport { restoreInboundChunk } from '@tanstack/ai/client'\nimport type { StreamChunk } from '@tanstack/ai/client'\nimport type { ByokClient } from './byok'\nimport type {\n ConnectConnectionAdapter,\n GenerationHydrationResult,\n RunAgentInputContext,\n} from './connection-adapters'\nimport type {\n AIDevtoolsClientMetadata,\n AIDevtoolsGenerationProgress,\n GenerationDevtoolsBridge,\n GenerationDevtoolsBridgeOptions,\n} from './devtools'\nimport type {\n GenerationClientOptions,\n GenerationClientState,\n GenerationFetcher,\n GenerationPersistenceOptions,\n GenerationRestoredResult,\n GenerationResumeSnapshot,\n GenerationResumeState,\n GenerationTransport,\n} from './generation-types'\n\n/**\n * Callbacks stored in a ref so hooks can update them without recreating the client.\n */\n// All optional fields explicitly allow `| undefined` so callers can spread\n// option bags (where each callback may be `undefined`) into the callbacks\n// ref under `exactOptionalPropertyTypes`.\ninterface GenerationCallbacks<TResult, TOutput> {\n onResult?: ((result: TResult) => TOutput | null | void) | undefined\n onError?: ((error: Error) => void) | undefined\n onProgress?: ((progress: number, message?: string) => void) | undefined\n onChunk?: ((chunk: StreamChunk) => void) | undefined\n onResultChange?: ((result: TOutput | null) => void) | undefined\n onLoadingChange?: ((isLoading: boolean) => void) | undefined\n onErrorChange?: ((error: Error | undefined) => void) | undefined\n onStatusChange?: ((status: GenerationClientState) => void) | undefined\n onResumeSnapshotChange?:\n | ((snapshot: GenerationResumeSnapshot | undefined) => void)\n | undefined\n onResumeStateChange?:\n | ((resumeState: GenerationResumeState | null) => void)\n | undefined\n reconstructResult?:\n | ((restored: GenerationRestoredResult) => TResult | null)\n | undefined\n}\n\n/**\n * A lightweight, generic client for one-shot generation tasks\n * (image, speech, transcription, summarize).\n *\n * Supports two transport modes:\n * - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom).\n * Server wraps results in StreamChunk events with CUSTOM event names.\n * - **Fetcher** — Direct async function call. No streaming protocol needed.\n *\n * @template TInput - The input type for the generation request\n * @template TResult - The result type returned by the generation\n *\n * @example\n * ```typescript\n * // With streaming connection adapter\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * connection: fetchServerSentEvents('/api/generate/image'),\n * onResultChange: setResult,\n * onLoadingChange: setIsLoading,\n * })\n *\n * // With fetcher (direct)\n * const client = new GenerationClient<ImageGenerateInput, ImageGenerationResult>({\n * fetcher: async (input) => {\n * const res = await fetch('/api/generate/image', {\n * method: 'POST',\n * body: JSON.stringify(input),\n * })\n * return res.json()\n * },\n * })\n *\n * await client.generate({ prompt: 'A sunset over mountains' })\n * ```\n */\nexport class GenerationClient<\n TInput extends Record<string, any>,\n TResult,\n TOutput = TResult,\n> {\n private readonly connection: ConnectConnectionAdapter | undefined\n private readonly fetcher: GenerationFetcher<TInput, TResult> | undefined\n // Persistence handlers supplied as options (e.g. alongside a `fetcher`), used\n // when the connection doesn't carry its own — the connection's handlers take\n // precedence when both exist.\n private readonly hydrateGenerationHandler:\n | ConnectConnectionAdapter['hydrateGeneration']\n | undefined\n private readonly joinRunHandler:\n | ConnectConnectionAdapter['joinRun']\n | undefined\n private uniqueId: string\n private readonly devtoolsMetadata: AIDevtoolsClientMetadata\n private readonly devtoolsBridge: GenerationDevtoolsBridge<TOutput>\n private threadId: string\n private readonly persistenceScope: string | undefined\n // Server-driven mode (`persistence: true`): no local snapshot store; on mount\n // the client hydrates the last generation for `threadId` from the server.\n private readonly serverDriven: boolean = false\n private body: Record<string, any>\n private byok: ByokClient | undefined\n private byokProvider: (() => string | undefined) | undefined\n private result: TOutput | null = null\n private input: TInput | null = null\n private progress: AIDevtoolsGenerationProgress | null = null\n private isLoading = false\n private error: Error | undefined = undefined\n private status: GenerationClientState = 'idle'\n private resumeSnapshot: GenerationResumeSnapshot | undefined\n private lastEmittedResumeState: string | undefined\n private abortController: AbortController | null = null\n private rejoinedRunId: string | undefined\n private readonly callbacksRef: GenerationCallbacks<TResult, TOutput>\n private devtoolsMounted = false\n private disposed = false\n private serverHydrationStarted = false\n\n constructor(\n options: Omit<\n GenerationClientOptions<TInput, TResult, TOutput>,\n 'persistence' | 'threadId'\n > &\n GenerationPersistenceOptions &\n GenerationTransport<TInput, TResult>,\n ) {\n // `threadId` is the only identity. Do not mint a random id during\n // construct: hooks build this client during render.\n this.threadId = options.threadId ?? ''\n this.uniqueId = this.threadId\n // The persistence scope is the explicit `threadId` and nothing else.\n // The types require it whenever `persistence` is set. This field keeps a\n // generated wire id from becoming a storage key for JS callers.\n this.persistenceScope = options.threadId\n this.connection = options.connection\n this.fetcher = options.fetcher\n this.hydrateGenerationHandler = options.hydrateGeneration\n this.joinRunHandler = options.joinRun\n this.body = options.body ?? {}\n this.byok = options.byok\n this.byokProvider = options.byokProvider\n // `persistence` is `false`/omitted (ephemeral) or `true` (server-driven:\n // hydrate the last generation for `threadId` from the server on mount).\n this.serverDriven = options.persistence === true\n // The types require `threadId` alongside `persistence`, so this only fires\n // for JS callers. Warn rather than fall back silently: keying on the\n // generated wire id would write a different slot every reload, restoring\n // nothing while accumulating orphaned records.\n if (options.persistence && !this.persistenceScope) {\n console.warn(\n '[TanStack AI] `persistence` needs a stable `threadId` to key on. Without one nothing will be restored after a reload. Pass a `threadId` derived from your own domain (e.g. `product-123-hero`).',\n )\n }\n this.callbacksRef = {\n onResult: options.onResult,\n onError: options.onError,\n onProgress: options.onProgress,\n onChunk: options.onChunk,\n onResultChange: options.onResultChange,\n onLoadingChange: options.onLoadingChange,\n onErrorChange: options.onErrorChange,\n onStatusChange: options.onStatusChange,\n onResumeSnapshotChange: options.onResumeSnapshotChange,\n onResumeStateChange: options.onResumeStateChange,\n reconstructResult: options.reconstructResult,\n }\n\n this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools)\n this.devtoolsBridge = (\n options.devtoolsBridgeFactory ?? createNoOpGenerationDevtoolsBridge\n )<TOutput>(this.buildDevtoolsBridgeOptions())\n\n // Mount hydration (`maybeHydrateFromServer`) is deliberately NOT run here. The framework\n // hooks build this client inside `useMemo`, so the constructor executes in\n // React's render phase; hydrating here would re-fire the hydrate GET on\n // every discarded/speculative render, flooding the connection pool when\n // several clients mount together. It is kicked off once from\n // `mountDevtools`, which the hooks call from a commit-phase mount effect.\n }\n\n private buildDevtoolsBridgeOptions(): GenerationDevtoolsBridgeOptions<TOutput> {\n const client = this\n return {\n get hookId() {\n return client.uniqueId\n },\n get clientId() {\n return client.uniqueId\n },\n get threadId() {\n return client.threadId\n },\n metadata: this.devtoolsMetadata,\n getCoreState: () => ({\n input: this.input,\n result: this.result,\n progress: this.progress,\n status: this.status,\n isLoading: this.isLoading,\n ...(this.error ? { error: this.error.message } : {}),\n }),\n }\n }\n\n mountDevtools(): void {\n this.ensureThreadId()\n // Mounting revives a disposed client. Framework hooks call this from\n // their mount effect, so a dispose → remount cycle (e.g. React\n // StrictMode's mount → cleanup → mount replay against the same memoized\n // client) leaves the client usable again.\n this.disposed = false\n this.maybeHydrateFromServer()\n // Re-attach to an in-flight run whose snapshot is already loaded — the\n // remount case. On the FIRST mount the snapshot loads asynchronously and\n // `repaintRestoredSnapshot` starts the rejoin; on a StrictMode remount the\n // snapshot is already present but the prior rejoin was aborted by\n // `dispose()`, so retrigger it here. Guarded by `rejoinInFlight`'s own\n // dedupe/in-flight checks, so this never double-joins.\n this.maybeResumeInFlight()\n if (this.devtoolsMounted) {\n return\n }\n\n this.devtoolsMounted = true\n this.devtoolsBridge.emitRegistered()\n this.devtoolsBridge.emitSnapshot()\n }\n\n /**\n * Trigger a generation request.\n * Only one generation can be in-flight at a time; calling generate()\n * while already generating will be a no-op.\n */\n async generate(input: TInput): Promise<void> {\n if (this.disposed) return\n if (this.isLoading) return\n this.mountDevtools()\n\n this.input = input\n this.progress = null\n const runId = this.devtoolsBridge.beginRun(input)\n this.setIsLoading(true)\n this.setStatus('generating')\n this.setError(undefined)\n\n const abortController = new AbortController()\n this.abortController = abortController\n const { signal } = abortController\n\n try {\n let headers: Record<string, string> | undefined\n if (this.byok) {\n const provider = resolveByokProviderId(\n this.byokProvider,\n this.body.provider,\n )\n headers = await prepareResolvedByokHeaders(this.byok, provider)\n }\n\n if (this.fetcher) {\n // Direct fetch path\n const result = await this.fetcher(\n input,\n headers === undefined ? { signal } : { signal, headers },\n )\n if (signal.aborted) return\n if (result instanceof Response) {\n // Server function returned SSE Response — parse stream\n await this.processStream(\n parseSSEResponse(result, signal),\n runId,\n signal,\n )\n } else {\n this.devtoolsBridge.ensureRunStarted(runId)\n this.setResult(result)\n this.setStatus('success')\n this.completePlainFetcherResumeSnapshot(result)\n }\n } else if (this.connection) {\n // Streaming adapter path\n const mergedData = { ...this.body, ...input }\n const stream = this.connection.connect(\n [],\n mergedData,\n signal,\n this.createRunContext(runId, headers),\n )\n await this.processStream(stream, runId, signal)\n } else {\n throw new Error(\n 'GenerationClient requires either a connection or fetcher option',\n )\n }\n if (!signal.aborted && this.status === 'success') {\n // Bump progress to 100 on successful completion so devtools\n // snapshots reflect the final state. The bridge mirrors this in\n // the run's recorded progress, but the snapshot reads `progress`\n // from the client's core state.\n this.progress = completeProgressValue(this.progress)\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:completed',\n 'completed',\n )\n }\n } catch (err: unknown) {\n if (signal.aborted) return\n const error = err instanceof Error ? err : new Error(String(err))\n if (error instanceof ByokMissingError) {\n this.byok?.request(error.provider, 'missing')\n }\n if (error instanceof ByokBlockedError && error.reason === 'locked') {\n this.byok?.request(error.provider, 'locked')\n }\n this.setError(error)\n this.setStatus('error')\n this.recordResumeSnapshotError(error)\n this.devtoolsBridge.finishRun(\n this.devtoolsBridge.getActiveRunId() ?? runId,\n 'run:errored',\n 'errored',\n error.message,\n )\n this.callbacksRef.onError?.(error)\n } finally {\n if (this.abortController === abortController) {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n }\n\n /**\n * Process a stream of AG-UI events from the streaming connection adapter.\n *\n * Throws {@link GENERATION_STREAM_TRUNCATED_MESSAGE} when the iteration ends\n * without a terminal chunk. A `for await` over a stream that simply stops —\n * proxy idle timeout, server restart, a durable log missing its terminal\n * append — returns normally and would otherwise leave the caller's `status`\n * on `generating` forever, with the persisted snapshot still `running` so\n * every reload rejoins the same dead run. Throwing routes it through the\n * caller's error path instead, which settles the status and rewrites the\n * snapshot so nothing chases it again.\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n fallbackRunId: string,\n signal: AbortSignal,\n ): Promise<void> {\n let streamRunId: string | undefined\n let sawTerminalChunk = false\n\n for await (const raw of source) {\n if (signal.aborted) break\n\n const chunk = restoreInboundChunk(raw)\n this.callbacksRef.onChunk?.(chunk)\n this.observeResumeSnapshot(chunk)\n const chunkRunId =\n 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : undefined\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType has ~22 variants; this consumer only handles the subset relevant to generation lifecycle.\n switch (chunk.type) {\n case 'RUN_STARTED': {\n streamRunId = chunk.runId\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n break\n }\n case 'CUSTOM': {\n this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId)\n if (chunk.name === GENERATION_EVENTS.RESULT) {\n this.setResult(chunk.value as TResult)\n } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {\n const { progress, message } = chunk.value as {\n progress: number\n message?: string\n }\n this.setProgress(progress, message)\n }\n break\n }\n case 'RUN_FINISHED': {\n streamRunId = chunk.runId\n sawTerminalChunk = true\n this.devtoolsBridge.ensureRunStarted(chunk.runId)\n this.setStatus('success')\n break\n }\n case 'RUN_ERROR': {\n this.devtoolsBridge.ensureRunStarted(\n chunkRunId ?? streamRunId ?? fallbackRunId,\n )\n // Spec RUN_ERROR message. Missing message uses this fallback.\n const msg =\n (chunk.message as string | undefined) || 'An error occurred'\n throw new Error(msg)\n }\n default:\n break\n }\n }\n\n // An aborted read is a deliberate stop/dispose, not a truncation.\n if (!sawTerminalChunk && !signal.aborted) {\n throw new Error(GENERATION_STREAM_TRUNCATED_MESSAGE)\n }\n }\n\n /**\n * Abort any in-flight generation request.\n */\n stop(): void {\n const runId = this.devtoolsBridge.getActiveRunId()\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n if (this.status === 'generating') {\n this.setStatus('idle')\n if (runId) {\n this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled')\n }\n }\n // A stopped run is no longer resumable. Without this the in-memory\n // snapshot stays `running`, and a remount's `maybeResumeInFlight` would\n // rejoin a run the user just cancelled.\n if (this.resumeSnapshot && this.resumeSnapshot.status === 'running') {\n this.resumeSnapshot = {\n ...this.resumeSnapshot,\n resumeState: null,\n status: 'idle',\n }\n this.notifyResumeSnapshotChanged()\n }\n }\n\n /**\n * Clear the result, error, and return to idle state. Also drops the client's\n * in-memory resume snapshot, so a remount restores nothing. The server-side\n * record is untouched — this client no longer writes one — so a full page\n * reload under `persistence: true` re-hydrates the last generation again.\n */\n reset(): void {\n this.stop()\n this.setResult(null)\n this.input = null\n this.progress = null\n this.devtoolsBridge.resetRuns()\n this.setError(undefined)\n this.setStatus('idle')\n this.clearResumeSnapshot()\n this.devtoolsBridge.emitState()\n }\n\n /**\n * Update options without recreating the client.\n */\n updateOptions(\n options: Partial<\n Pick<\n GenerationClientOptions<TInput, TResult, TOutput>,\n | 'body'\n | 'byok'\n | 'byokProvider'\n | 'onResult'\n | 'onError'\n | 'onProgress'\n | 'onChunk'\n >\n >,\n ): void {\n if (options.body !== undefined) {\n this.body = options.body ?? {}\n }\n if (options.byok !== undefined) {\n this.byok = options.byok\n }\n if (options.byokProvider !== undefined) {\n this.byokProvider = options.byokProvider\n }\n if (options.onResult !== undefined) {\n this.callbacksRef.onResult = options.onResult\n }\n if (options.onError !== undefined) {\n this.callbacksRef.onError = options.onError\n }\n if (options.onProgress !== undefined) {\n this.callbacksRef.onProgress = options.onProgress\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.onChunk = options.onChunk\n }\n }\n\n dispose(): void {\n this.disposed = true\n // Teardown, NOT a user cancel: abort in-flight DELIVERY (this reader) but\n // do NOT call `stop()` — `stop()` marks the run non-resumable and wipes the\n // `running` snapshot, which is correct for a Stop button but wrong for an\n // unmount / React StrictMode dispose. Clearing it here would destroy the\n // in-memory resume state, so a remount of this same client instance could\n // never rejoin. (A real page revisit re-hydrates from the server instead.)\n // The run itself survives server-side (durable delivery), so the snapshot\n // must stay `running` for the remount to resume it.\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n this.devtoolsBridge.dispose()\n this.devtoolsMounted = false\n // Re-arm mount hydration + rejoin so a remount resumes from the (preserved)\n // snapshot. `mountDevtools` re-runs the hydration entry point and\n // `maybeResumeInFlight`, both individually guarded.\n this.serverHydrationStarted = false\n this.rejoinedRunId = undefined\n }\n\n // ===========================\n // Getters\n // ===========================\n\n getResult(): TOutput | null {\n return this.result\n }\n\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n getError(): Error | undefined {\n return this.error\n }\n\n getStatus(): GenerationClientState {\n return this.status\n }\n\n getResumeSnapshot(): GenerationResumeSnapshot | undefined {\n return this.resumeSnapshot\n ? {\n ...this.resumeSnapshot,\n ...(this.resumeSnapshot.pendingArtifacts\n ? { pendingArtifacts: [...this.resumeSnapshot.pendingArtifacts] }\n : {}),\n ...(this.resumeSnapshot.result\n ? {\n result: {\n ...this.resumeSnapshot.result,\n ...(this.resumeSnapshot.result.artifacts\n ? { artifacts: [...this.resumeSnapshot.result.artifacts] }\n : {}),\n },\n }\n : {}),\n ...(this.resumeSnapshot.error\n ? { error: { ...this.resumeSnapshot.error } }\n : {}),\n ...(this.resumeSnapshot.lastEvent\n ? { lastEvent: { ...this.resumeSnapshot.lastEvent } }\n : {}),\n }\n : undefined\n }\n\n // ===========================\n // Private state setters\n // ===========================\n\n private setResult(rawResult: TResult | null): void {\n if (rawResult === null) {\n this.result = null\n this.callbacksRef.onResultChange?.(null)\n this.devtoolsBridge.recordResultChange()\n return\n }\n\n if (this.callbacksRef.onResult) {\n const transformed = this.callbacksRef.onResult(rawResult)\n if (transformed === null) {\n // null return → keep previous result unchanged, just re-emit\n this.devtoolsBridge.emitState()\n return\n }\n if (transformed !== undefined) {\n // Non-null, non-undefined → use transformed value\n this.result = transformed\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n return\n }\n }\n\n // No onResult callback, or callback returned void → use raw value as\n // TOutput. When the caller did not supply an onResult transform,\n // `TOutput` defaults to `TResult`, so the runtime cast is sound.\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- TOutput defaults to TResult when no onResult transform is supplied\n this.result = rawResult as unknown as TOutput\n this.callbacksRef.onResultChange?.(this.result)\n this.devtoolsBridge.recordResultChange()\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.onLoadingChange?.(isLoading)\n this.devtoolsBridge.recordLoadingChange()\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.onErrorChange?.(error)\n this.devtoolsBridge.recordErrorChange(error)\n }\n\n private setStatus(status: GenerationClientState): void {\n this.status = status\n this.callbacksRef.onStatusChange?.(status)\n this.devtoolsBridge.recordStatusChange(status)\n }\n\n private setProgress(value: number, message?: string): void {\n this.progress = {\n value,\n ...(message ? { message } : {}),\n }\n if (message === undefined) {\n this.callbacksRef.onProgress?.(value)\n } else {\n this.callbacksRef.onProgress?.(value, message)\n }\n this.devtoolsBridge.recordProgressChange()\n }\n\n private createDevtoolsMetadata(\n metadata?: Partial<AIDevtoolsClientMetadata>,\n ): AIDevtoolsClientMetadata {\n return {\n hookName: metadata?.hookName ?? 'useGeneration',\n ...(metadata?.framework ? { framework: metadata.framework } : {}),\n ...(metadata?.outputKind ? { outputKind: metadata.outputKind } : {}),\n ...(metadata?.name ? { name: metadata.name } : {}),\n }\n }\n\n private ensureThreadId(): string {\n if (!this.threadId) {\n this.threadId = this.generateUniqueId('generation')\n }\n this.uniqueId = this.threadId\n return this.threadId\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private createRunContext(\n runId: string,\n headers?: Record<string, string>,\n ): RunAgentInputContext {\n return {\n threadId: this.threadId,\n runId,\n ...(headers ? { headers } : {}),\n }\n }\n\n private observeResumeSnapshot(chunk: StreamChunk): void {\n this.resumeSnapshot = updateGenerationResumeSnapshot(\n this.resumeSnapshot,\n chunk,\n )\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Notify the (internal) snapshot listener AND emit the public resume state.\n * The snapshot stays internal (persistence + devtools); the hook consumes\n * `resumeState`, mirroring the chat client.\n */\n private notifyResumeSnapshotChanged(): void {\n this.callbacksRef.onResumeSnapshotChange?.(this.resumeSnapshot)\n this.emitResumeState()\n }\n\n /**\n * Derive the public `resumeState` from the internal snapshot: the in-flight\n * run identity, with any in-flight artifact refs folded under it. `null` once\n * no run is in flight.\n *\n * The snapshot is rebuilt for every chunk, so emitting unconditionally would\n * hand each framework hook a fresh object per chunk and re-render the\n * component on every stream event. `resumeState` only changes at run\n * boundaries and when artifacts land, so skip the notification unless it\n * materially changed — same gate the persistence writes use.\n */\n private emitResumeState(): void {\n const snapshot = this.resumeSnapshot\n const state = snapshot?.resumeState\n const resumeState: GenerationResumeState | null = state\n ? {\n ...state,\n ...(snapshot?.pendingArtifacts && snapshot.pendingArtifacts.length > 0\n ? { pendingArtifacts: [...snapshot.pendingArtifacts] }\n : {}),\n }\n : null\n const signature = JSON.stringify(resumeState)\n if (signature === this.lastEmittedResumeState) {\n return\n }\n this.lastEmittedResumeState = signature\n this.callbacksRef.onResumeStateChange?.(resumeState)\n }\n\n /**\n * Repaint the normal fields from a restored snapshot (client store or server\n * hydrate), so a reload presents the run in `result` / `status` / `error`\n * exactly as a just-finished run would, never a bolt-on snapshot object.\n * `isLoading` stays false: the client never auto-tails a restored run. The\n * snapshot is not re-persisted here (it came from storage / the server).\n *\n * When the activity's mapper DECLINES a `complete` snapshot the repaint\n * settles as an error instead: `success` with a `null` result is a state no\n * consumer can render, and it hides the real cause (an output artifact\n * persisted without a serve URL). A decline on any other status is expected —\n * a `running` snapshot has no result yet, the rejoin will deliver it.\n */\n private repaintFromSnapshot(snapshot: GenerationResumeSnapshot): void {\n this.resumeSnapshot = snapshot\n this.notifyResumeSnapshotChanged()\n this.setStatus(clientStateFromResumeStatus(snapshot.status))\n this.setError(\n snapshot.error\n ? Object.assign(\n new Error(snapshot.error.message),\n snapshot.error.code ? { code: snapshot.error.code } : {},\n )\n : undefined,\n )\n const restored = this.reconstructRestoredResult(snapshot)\n if (restored !== null) {\n this.setResult(restored)\n } else if (\n this.callbacksRef.reconstructResult &&\n snapshot.status === 'complete'\n ) {\n this.reportUnrestorableResult()\n }\n }\n\n /**\n * Report a `complete` snapshot the activity's mapper could not rebuild.\n * Runs after the status/error repaint above, so it wins over the snapshot's\n * own `complete` status.\n */\n private reportUnrestorableResult(): void {\n const error = new Error(GENERATION_UNRESTORABLE_RESULT_MESSAGE)\n this.setStatus('error')\n this.setError(error)\n this.callbacksRef.onError?.(error)\n }\n\n /**\n * Repaint a restored snapshot (client store or server hydrate) and, when it\n * reports a run still in flight, tail that run to completion via `joinRun`\n * (from the connection, or the `joinRun` option when the transport can't\n * carry one).\n *\n * A `running` snapshot that no `joinRun` handler can tail is repainted as an\n * interrupted error instead of a `generating` status that would never\n * settle: an interrupted generation cannot be resumed, only re-run.\n */\n private repaintRestoredSnapshot(\n snapshot: GenerationResumeSnapshot,\n activeRunId?: string,\n ): void {\n if (snapshot.status !== 'running') {\n this.repaintFromSnapshot(snapshot)\n return\n }\n const joinRun = this.connection?.joinRun ?? this.joinRunHandler\n const runId = activeRunId ?? snapshot.resumeState?.runId\n if (runId && joinRun) {\n this.repaintFromSnapshot(snapshot)\n this.rejoinInFlight(runId)\n return\n }\n this.repaintFromSnapshot({\n ...snapshot,\n resumeState: null,\n status: 'error',\n error: {\n message:\n 'The previous generation was interrupted before it finished and cannot be resumed — generate again to retry.',\n },\n })\n }\n\n /**\n * Build the restorable result shape from the snapshot and hand it to the\n * per-activity `reconstructResult` mapper (injected by the specialized\n * client/hook, which knows the concrete result type).\n *\n * Returns `null` both when no mapper is set (nothing to rebuild — `result`\n * simply stays null) and when the mapper declines. The caller distinguishes\n * the two: see {@link repaintFromSnapshot}.\n */\n private reconstructRestoredResult(\n snapshot: GenerationResumeSnapshot,\n ): TResult | null {\n const build = this.callbacksRef.reconstructResult\n if (!build) return null\n const result = snapshot.result\n const restored: GenerationRestoredResult = {\n ...(result?.id !== undefined ? { id: result.id } : {}),\n ...(result?.model !== undefined ? { model: result.model } : {}),\n ...(result?.status !== undefined ? { status: result.status } : {}),\n ...(result?.providerJobId !== undefined\n ? { providerJobId: result.providerJobId }\n : {}),\n ...(result?.expiresAt !== undefined\n ? { expiresAt: result.expiresAt }\n : {}),\n ...(result?.text !== undefined ? { text: result.text } : {}),\n ...(result?.usage !== undefined ? { usage: result.usage } : {}),\n ...(snapshot.activity !== undefined\n ? { activity: snapshot.activity }\n : {}),\n artifacts: result?.artifacts ?? [],\n }\n return build(restored)\n }\n\n /**\n * The plain (non-Response) fetcher path never observes stream chunks, so\n * the terminal snapshot is built here from the fetcher's own result. A\n * stale `error` from a previous run is intentionally dropped — this run\n * succeeded.\n */\n private completePlainFetcherResumeSnapshot(rawResult: unknown): void {\n const previous = this.resumeSnapshot\n const result = createGenerationResultSnapshot(rawResult)\n this.resumeSnapshot = {\n schemaVersion: 1,\n resumeState: null,\n status: 'complete',\n ...(previous?.activity ? { activity: previous.activity } : {}),\n ...(previous?.pendingArtifacts && previous.pendingArtifacts.length > 0\n ? { pendingArtifacts: [...previous.pendingArtifacts] }\n : {}),\n ...(result\n ? { result }\n : previous?.result\n ? { result: { ...previous.result } }\n : {}),\n }\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Records a transport-level failure (network drop, throwing callback) in\n * the snapshot. Without this, only a server-emitted RUN_ERROR chunk would\n * mark the snapshot `error`, leaving a persisted record that claims the\n * run is still in flight.\n */\n private recordResumeSnapshotError(error: Error): void {\n // Surface the failure on the OBSERVABLE fields FIRST: a rejoin (or live\n // stream) that emits RUN_ERROR has already flipped the snapshot to `error`\n // via `observeResumeSnapshot`, so the early-return below would otherwise\n // skip this and leave `status` stuck on `generating` — the run would look\n // like it is still going forever. The guard avoids a duplicate `error`\n // emission on the live `generate()` path, which sets the status itself.\n if (this.status !== 'error') this.setStatus('error')\n this.setError(error)\n if (this.resumeSnapshot?.status === 'error') return\n if (!this.resumeSnapshot && !this.serverDriven) return\n const previous = this.resumeSnapshot\n this.resumeSnapshot = {\n schemaVersion: 1,\n resumeState: null,\n status: 'error',\n ...(previous?.activity ? { activity: previous.activity } : {}),\n ...(previous?.pendingArtifacts && previous.pendingArtifacts.length > 0\n ? { pendingArtifacts: [...previous.pendingArtifacts] }\n : {}),\n ...(previous?.result ? { result: { ...previous.result } } : {}),\n error: { message: error.message },\n }\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Drop the client's in-memory snapshot and re-emit. Purely local — this\n * client writes no storage, so nothing persisted is removed.\n */\n private clearResumeSnapshot(): void {\n this.resumeSnapshot = undefined\n this.lastEmittedResumeState = undefined\n this.notifyResumeSnapshotChanged()\n }\n\n /**\n * Server-driven mount hydration entry point (`persistence: true`). Runs at\n * most once, from the commit-phase mount path (`mountDevtools`) — never the\n * constructor / render phase — so remounts and speculative renders can't\n * re-fire the hydrate GET.\n */\n private maybeHydrateFromServer(): void {\n if (!this.serverDriven || this.serverHydrationStarted) return\n this.serverHydrationStarted = true\n if (this.connection?.hydrateGeneration ?? this.hydrateGenerationHandler) {\n this.hydrateFromServer()\n } else {\n // `persistence: true` without any hydrate source can never restore\n // anything — warn rather than silently no-op.\n console.warn(\n '[TanStack AI] `persistence: true` (server-driven) needs a `hydrateGeneration` handler — either a connection that implements one (e.g. `fetchServerSentEvents` / `fetchHttpStream`, or `stream()` / `rpcStream()` with persistence handlers) or the `hydrateGeneration` option. Without one, nothing is persisted or restored.',\n )\n }\n }\n\n /**\n * Server-driven mount hydration (`persistence: true`). The client holds no\n * local snapshot; on mount it asks the server — keyed by the stable threadId —\n * for the last generation's resume snapshot, validates it, and repaints it. It\n * never auto-starts a run, and never blocks: a `generate()` that starts first\n * owns the client and hydration backs off, mirroring the chat client.\n *\n * A genuine **miss** (the server reports no record for the thread) is silent —\n * a fresh thread is not an error. A genuine **failure** (transport error, a\n * 403 from the authorize gate, a malformed body, a record the client's own\n * validator rejects) is surfaced through `status` / `error` / `onError`, so a\n * broken server is distinguishable from an empty one and the app can retry.\n */\n private hydrateFromServer(): void {\n const hydrate =\n this.connection?.hydrateGeneration ?? this.hydrateGenerationHandler\n if (!hydrate) return\n // A send that already started owns the client; don't stomp it.\n if (this.resumeSnapshot || this.isLoading || this.status !== 'idle') return\n void (async () => {\n let res: GenerationHydrationResult\n try {\n res = await hydrate(this.threadId)\n } catch (cause) {\n this.failHydration(\n createGenerationHydrationError(\n 'the request to the server did not succeed',\n cause,\n ),\n )\n return\n }\n // No record for this thread — a fresh thread, not a failure.\n if (!res.resumeSnapshot) return\n const snapshot = parseGenerationResumeSnapshot(res.resumeSnapshot)\n if (!snapshot) {\n this.failHydration(\n createGenerationHydrationError(\n 'the server returned a record this client cannot read (unknown schema version, or a missing/invalid `status` or `resumeState`)',\n ),\n )\n return\n }\n // Re-check: a send may have started while the fetch was in flight.\n if (this.resumeSnapshot || this.isLoading || this.status !== 'idle')\n return\n // A run still generating on the server: re-attach and finish it in place.\n this.repaintRestoredSnapshot(snapshot, res.activeRun?.runId)\n })()\n }\n\n /**\n * Surface a hydration failure on the observable fields. Skipped when a\n * `generate()` took ownership while the hydrate GET was in flight — the live\n * run's state must win over a stale mount-time failure.\n */\n private failHydration(error: Error): void {\n if (this.resumeSnapshot || this.isLoading || this.status !== 'idle') return\n this.setStatus('error')\n this.setError(error)\n this.callbacksRef.onError?.(error)\n }\n\n /**\n * Re-attach to an already-loaded `running` snapshot (the remount case). Safe\n * to call repeatedly: `rejoinInFlight` dedupes on `rejoinedRunId` and bails\n * when a run is already in flight, so on the first mount (where the rejoin was\n * already started from `repaintRestoredSnapshot`) this is a no-op.\n */\n private maybeResumeInFlight(): void {\n if (this.resumeSnapshot?.status !== 'running') return\n const runId = this.resumeSnapshot.resumeState?.runId\n if (runId) this.rejoinInFlight(runId)\n }\n\n /**\n * Re-attach to a run that is still generating and stream it to completion,\n * mirroring the chat client's mount-time rejoin. Reuses `processStream`, so\n * `result` / `progress` / `status` repaint from the replayed chunks exactly as\n * a live run does. Best-effort: a live `generate()` owns the client and is\n * never stomped, and the same run is only rejoined once.\n */\n private rejoinInFlight(runId: string): void {\n const joinRun = this.connection?.joinRun ?? this.joinRunHandler\n if (!joinRun) return\n if (this.rejoinedRunId === runId) return\n // A fresh send (or an in-progress rejoin) owns the client.\n if (this.isLoading || this.abortController) return\n this.rejoinedRunId = runId\n const controller = new AbortController()\n this.abortController = controller\n this.setIsLoading(true)\n this.setStatus('generating')\n void (async () => {\n try {\n await this.processStream(\n joinRun(runId, controller.signal),\n runId,\n controller.signal,\n )\n } catch (error) {\n if (!controller.signal.aborted) {\n const failure =\n error instanceof Error ? error : new Error(String(error))\n // Settles `status`/`error` AND rewrites the snapshot to a terminal\n // `error` with a null `resumeState`, so the next mount does not\n // rejoin this run again.\n this.recordResumeSnapshotError(failure)\n this.callbacksRef.onError?.(failure)\n }\n } finally {\n // Only reset if this rejoin still owns the client: a `stop()` +\n // fresh `generate()` may have replaced the controller while the tail\n // was settling, and that live run owns `isLoading` now.\n if (this.abortController === controller) {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n })()\n }\n}\n\nfunction completeProgressValue(\n progress: AIDevtoolsGenerationProgress | null,\n): AIDevtoolsGenerationProgress | null {\n if (!progress) return null\n const message = progress.message\n return {\n value: 100,\n ...(message ? { message } : {}),\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { MediaPrompt, PersistedArtifactRef, StreamChunk } from '@tanstack/ai/client';
2
2
  import { TokenUsage, TranscriptionResponseFormat } from '@tanstack/ai';
3
+ import { ByokClient } from './byok.js';
3
4
  import { ConnectConnectionAdapter } from './connection-adapters.js';
4
5
  import { AIDevtoolsClientMetadata } from './devtools.js';
5
6
  import { GenerationDevtoolsBridgeFactory, VideoDevtoolsBridgeFactory } from './devtools-noop.js';
@@ -196,6 +197,8 @@ export declare const GENERATION_EVENTS: {
196
197
  export interface GenerationFetcherOptions {
197
198
  /** AbortSignal that is triggered when the user calls `stop()` */
198
199
  signal: AbortSignal;
200
+ /** Extra request headers for this run (e.g. BYOK keys). */
201
+ headers?: Record<string, string>;
199
202
  }
200
203
  /**
201
204
  * A direct async function that performs a generation request.
@@ -254,6 +257,17 @@ export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
254
257
  threadId?: string;
255
258
  /** Additional body parameters to send with connect-based adapter requests */
256
259
  body?: Record<string, any>;
260
+ /**
261
+ * Optional BYOK keyring. On each generate the client prepares the resolved
262
+ * provider and stamps `x-byok-*` request headers. Keys never go in the body.
263
+ */
264
+ byok?: ByokClient;
265
+ /**
266
+ * Optional provider id for this generation. If it returns a provider slug,
267
+ * only that key is prepared and sent. Otherwise `body.provider` is used.
268
+ * If no slug resolves, generate throws instead of attaching every stored key.
269
+ */
270
+ byokProvider?: () => string | undefined;
257
271
  /** Metadata used to register this generation hook with TanStack AI Devtools */
258
272
  devtools?: Partial<AIDevtoolsClientMetadata>;
259
273
  /**
@@ -1,3 +1,4 @@
1
+ import { tanstackMetadata } from "@tanstack/ai/client";
1
2
  //#region src/generation-types.ts
2
3
  /**
3
4
  * Thrown when a generation stream ends without a terminal `RUN_FINISHED` /
@@ -68,8 +69,9 @@ var GENERATION_EVENTS = {
68
69
  * @internal
69
70
  */
70
71
  function updateGenerationResumeSnapshot(previous, chunk) {
71
- const threadId = stringField(chunk, "threadId");
72
- const runId = stringField(chunk, "runId");
72
+ const tanstack = tanstackMetadata(chunk);
73
+ const threadId = stringField(chunk, "threadId") ?? (typeof tanstack?.threadId === "string" ? tanstack.threadId : void 0);
74
+ const runId = stringField(chunk, "runId") ?? (typeof tanstack?.runId === "string" ? tanstack.runId : void 0);
73
75
  const carried = chunk.type === "RUN_STARTED" ? void 0 : previous;
74
76
  const previousArtifacts = carried?.pendingArtifacts ?? [];
75
77
  const next = {