@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
@@ -94,6 +94,8 @@ export interface RunAgentInputContext {
94
94
  }>;
95
95
  /** Arbitrary user-controlled passthrough data. */
96
96
  forwardedProps?: Record<string, unknown>;
97
+ /** Extra request headers for this run (e.g. BYOK keys). POST only. */
98
+ headers?: Record<string, string>;
97
99
  }
98
100
  export interface ConnectConnectionAdapter {
99
101
  /**
@@ -1,6 +1,7 @@
1
1
  import { createResponseStreamTextDecoder, getResponseStreamReader } from "./response-stream.js";
2
2
  import { parseSseDataLine } from "./sse-utils.js";
3
- import { EventType, uiMessagesToWire } from "@tanstack/ai/client";
3
+ import { EventType, getChunkRunId, restoreInboundChunk, tanstackMetadata, uiMessagesToWire, withTanstackMetadata } from "@tanstack/ai/client";
4
+ import { ByokMissingError, isByokMissingBody } from "@tanstack/ai/byok";
4
5
  //#region src/connection-adapters.ts
5
6
  /**
6
7
  * Associates connect-wrapped chunks with the run they were produced under.
@@ -15,8 +16,8 @@ var chunkRunIds = /* @__PURE__ */ new WeakMap();
15
16
  * (RUN_STARTED / RUN_FINISHED / RUN_ERROR carry one) and falling back to the
16
17
  * run the connect wrapper stamped it with.
17
18
  */
18
- function getChunkRunId(chunk) {
19
- return chunkRunIds.get(chunk) ?? ("runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : void 0);
19
+ function getChunkRunId$1(chunk) {
20
+ return chunkRunIds.get(chunk) ?? getChunkRunId(chunk);
20
21
  }
21
22
  /**
22
23
  * Thrown when an SSE/HTTP stream ends with a non-empty unterminated buffer.
@@ -206,6 +207,19 @@ async function* readStreamLines(reader, abortSignal) {
206
207
  function isNdjsonEnvelope(value) {
207
208
  return typeof value === "object" && value !== null && "chunk" in value && "id" in value && typeof value.id === "string" && !("type" in value);
208
209
  }
210
+ /** Rebuild pre-wire extras after SSE/NDJSON ingest. */
211
+ function restoreInboundUsage(chunk) {
212
+ return restoreInboundChunk(chunk);
213
+ }
214
+ function sseChunkModel(chunk) {
215
+ const tanstackModel = tanstackMetadata(chunk)?.model;
216
+ if (typeof tanstackModel === "string") return tanstackModel;
217
+ const usage = "usage" in chunk ? chunk.usage : void 0;
218
+ if (Array.isArray(usage)) {
219
+ const model = usage[0]?.model;
220
+ if (typeof model === "string") return model;
221
+ }
222
+ }
209
223
  /**
210
224
  * Parse SSE-format lines into stream events, pairing each chunk with the `id:`
211
225
  * offset of the event it arrived on. Shared by the fetch- and XHR-backed SSE
@@ -234,20 +248,22 @@ async function* linesToSSEEvents(lines, fallbackIds) {
234
248
  if (line.startsWith(":") || line.startsWith("event:") || line.startsWith("retry:")) continue;
235
249
  const data = parseSseDataLine(line);
236
250
  if (data === "[DONE]") {
237
- yield { chunk: {
251
+ yield { chunk: withTanstackMetadata({
238
252
  type: EventType.RUN_FINISHED,
239
253
  threadId: lastThreadId ?? fallbackIds?.threadId ?? "",
240
254
  runId: lastRunId ?? fallbackIds?.runId ?? "",
241
- model: lastModel ?? "",
242
- timestamp: Date.now(),
243
- finishReason: "stop"
244
- } };
255
+ timestamp: Date.now()
256
+ }, {
257
+ finishReason: "stop",
258
+ ...lastModel !== void 0 ? { model: lastModel } : {}
259
+ }) };
245
260
  return;
246
261
  }
247
- const chunk = JSON.parse(data);
262
+ const chunk = restoreInboundUsage(JSON.parse(data));
248
263
  if ("threadId" in chunk && typeof chunk.threadId === "string") lastThreadId = chunk.threadId;
249
264
  if ("runId" in chunk && typeof chunk.runId === "string") lastRunId = chunk.runId;
250
- if ("model" in chunk && typeof chunk.model === "string") lastModel = chunk.model;
265
+ const model = sseChunkModel(chunk);
266
+ if (model !== void 0) lastModel = model;
251
267
  const id = pendingId;
252
268
  pendingId = void 0;
253
269
  yield {
@@ -266,14 +282,31 @@ async function* linesToNdjsonEvents(lines) {
266
282
  for await (const line of lines) {
267
283
  const parsed = JSON.parse(line);
268
284
  if (isNdjsonEnvelope(parsed)) yield {
269
- chunk: parsed.chunk,
285
+ chunk: restoreInboundUsage(parsed.chunk),
270
286
  id: parsed.id
271
287
  };
272
- else yield { chunk: parsed };
288
+ else yield { chunk: restoreInboundUsage(parsed) };
273
289
  }
274
290
  }
275
- function assertResponseOk(response) {
276
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`);
291
+ async function assertResponseOk(response) {
292
+ if (response.ok) return;
293
+ if (response.status === 401) {
294
+ const body = await response.clone().json().catch(() => null);
295
+ if (isByokMissingBody(body)) throw new ByokMissingError(body.error.provider);
296
+ }
297
+ throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`);
298
+ }
299
+ function errorFromXhrStatus(xhr) {
300
+ if (xhr.status === 401) {
301
+ let parsed = null;
302
+ try {
303
+ parsed = JSON.parse(xhr.responseText);
304
+ } catch {
305
+ parsed = null;
306
+ }
307
+ if (isByokMissingBody(parsed)) return new ByokMissingError(parsed.error.provider);
308
+ }
309
+ return /* @__PURE__ */ new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`);
277
310
  }
278
311
  /**
279
312
  * GET the hydration endpoint for a thread and parse its JSON `{ messages,
@@ -291,7 +324,7 @@ async function fetchThreadHydration(fetchClient, url, headers, credentials, thre
291
324
  },
292
325
  credentials
293
326
  });
294
- assertResponseOk(response);
327
+ await assertResponseOk(response);
295
328
  const data = await response.json();
296
329
  const activeRun = data.activeRun && typeof data.activeRun.runId === "string" ? { runId: data.activeRun.runId } : null;
297
330
  const interrupts = data.interrupts && typeof data.interrupts.runId === "string" && Array.isArray(data.interrupts.pending) && data.interrupts.pending.length > 0 ? {
@@ -320,7 +353,7 @@ async function fetchGenerationHydration(fetchClient, url, headers, credentials,
320
353
  },
321
354
  credentials
322
355
  });
323
- assertResponseOk(response);
356
+ await assertResponseOk(response);
324
357
  const raw = await response.json();
325
358
  if (raw === null) return {
326
359
  resumeSnapshot: null,
@@ -336,12 +369,12 @@ async function fetchGenerationHydration(fetchClient, url, headers, credentials,
336
369
  }
337
370
  /** Yield SSE stream events (chunk + offset) from a fetch Response body. */
338
371
  async function* responseToSSEEvents(response, abortSignal, fallbackIds) {
339
- assertResponseOk(response);
372
+ await assertResponseOk(response);
340
373
  yield* linesToSSEEvents(readStreamLines(getResponseStreamReader(response), abortSignal), fallbackIds);
341
374
  }
342
375
  /** Yield NDJSON stream events (chunk + offset) from a fetch Response body. */
343
376
  async function* responseToNdjsonEvents(response, abortSignal) {
344
- assertResponseOk(response);
377
+ await assertResponseOk(response);
345
378
  yield* linesToNdjsonEvents(readStreamLines(getResponseStreamReader(response), abortSignal));
346
379
  }
347
380
  async function* responseToSSEChunks(response, abortSignal) {
@@ -481,14 +514,15 @@ function normalizeConnectionAdapter(connection) {
481
514
  if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") hasTerminalEvent = true;
482
515
  push(chunk, runContext?.runId);
483
516
  }
484
- if (!abortSignal?.aborted && !hasTerminalEvent) push({
517
+ if (!abortSignal?.aborted && !hasTerminalEvent) push(withTanstackMetadata({
485
518
  type: EventType.RUN_FINISHED,
486
519
  threadId: requireSyntheticId(upstreamThreadId ?? runContext?.threadId, "threadId"),
487
520
  runId: requireSyntheticId(upstreamRunId ?? runContext?.runId, "runId"),
488
- model: "connect-wrapper",
489
- timestamp: Date.now(),
490
- finishReason: "stop"
491
- }, runContext?.runId);
521
+ timestamp: Date.now()
522
+ }, {
523
+ finishReason: "stop",
524
+ model: "connect-wrapper"
525
+ }), runContext?.runId);
492
526
  } catch (err) {
493
527
  if (!abortSignal?.aborted && !hasTerminalEvent) try {
494
528
  const message = err instanceof Error ? err.message : "Unknown error in connect()";
@@ -572,6 +606,7 @@ function fetchServerSentEvents(url, options = {}) {
572
606
  const requestHeaders = {
573
607
  "Content-Type": "application/json",
574
608
  ...mergeHeaders(resolvedOptions.headers),
609
+ ...mergeHeaders(runContext?.headers),
575
610
  ...runIdHeader(runContext?.runId)
576
611
  };
577
612
  const requestBody = buildRunAgentInputBody(messages, data, runContext, resolvedOptions);
@@ -657,6 +692,7 @@ function fetchHttpStream(url, options = {}) {
657
692
  const requestHeaders = {
658
693
  "Content-Type": "application/json",
659
694
  ...mergeHeaders(resolvedOptions.headers),
695
+ ...mergeHeaders(runContext?.headers),
660
696
  ...runIdHeader(runContext?.runId)
661
697
  };
662
698
  const requestBody = buildRunAgentInputBody(messages, data, runContext, resolvedOptions);
@@ -723,7 +759,7 @@ function readXhrLines(xhr, abortSignal) {
723
759
  };
724
760
  const enqueueDelta = () => {
725
761
  if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {
726
- error = /* @__PURE__ */ new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`);
762
+ error = errorFromXhrStatus(xhr);
727
763
  done = true;
728
764
  return;
729
765
  }
@@ -740,7 +776,7 @@ function readXhrLines(xhr, abortSignal) {
740
776
  };
741
777
  const finish = () => {
742
778
  enqueueDelta();
743
- if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) error = /* @__PURE__ */ new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`);
779
+ if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) error = errorFromXhrStatus(xhr);
744
780
  else if (buffer.trim() && !aborted) error = new StreamTruncatedError();
745
781
  done = true;
746
782
  wake();
@@ -797,6 +833,7 @@ function createConfiguredXhrRequest(url, options, messages, data, runContext, me
797
833
  const requestHeaders = {
798
834
  "Content-Type": "application/json",
799
835
  ...mergeHeaders(options.headers),
836
+ ...mergeHeaders(method === "POST" ? runContext?.headers : void 0),
800
837
  ...method === "POST" ? runIdHeader(runContext?.runId) : {},
801
838
  ...extraHeaders
802
839
  };
@@ -1018,12 +1055,12 @@ function webSocket(url, options = {}) {
1018
1055
  }
1019
1056
  if (isPingFrame(parsed)) return;
1020
1057
  const envelopeId = isNdjsonEnvelope(parsed) ? parsed.id : void 0;
1021
- const chunk = isNdjsonEnvelope(parsed) ? parsed.chunk : parsed;
1058
+ const chunk = restoreInboundUsage(isNdjsonEnvelope(parsed) ? parsed.chunk : parsed);
1022
1059
  const session = currentSession;
1023
1060
  if (session) {
1024
1061
  if (session.tracker.note(envelopeId) === "duplicate") return;
1025
1062
  session.progressed = true;
1026
- if (session.runId === void 0) session.runId = getChunkRunId(chunk);
1063
+ if (session.runId === void 0) session.runId = getChunkRunId$1(chunk);
1027
1064
  if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") session.sawTerminal = true;
1028
1065
  }
1029
1066
  for (const l of listeners) l.push(chunk);
@@ -1126,7 +1163,7 @@ function webSocket(url, options = {}) {
1126
1163
  return;
1127
1164
  }
1128
1165
  if (isPingFrame(parsed)) return;
1129
- pipe.push(isNdjsonEnvelope(parsed) ? parsed.chunk : parsed);
1166
+ pipe.push(restoreInboundUsage(isNdjsonEnvelope(parsed) ? parsed.chunk : parsed));
1130
1167
  };
1131
1168
  ws.onclose = (event) => {
1132
1169
  if (event?.code === 1e3) {
@@ -1195,7 +1232,10 @@ function fetcherToConnectionAdapter(fetcher) {
1195
1232
  runId: runContext.runId,
1196
1233
  ...runContext.parentRunId !== void 0 ? { parentRunId: runContext.parentRunId } : {},
1197
1234
  ...runContext.resume !== void 0 ? { resume: runContext.resume } : {}
1198
- }, { signal: abortSignal });
1235
+ }, {
1236
+ signal: abortSignal,
1237
+ headers: runContext.headers
1238
+ });
1199
1239
  if (result instanceof Response) yield* responseToSSEChunks(result, abortSignal);
1200
1240
  else yield* abortableIterable(result, abortSignal);
1201
1241
  } };
@@ -1263,6 +1303,6 @@ function rpcStream(rpcCall, handlers) {
1263
1303
  };
1264
1304
  }
1265
1305
  //#endregion
1266
- export { DurableStreamIncompleteError, StreamReconnectLimitError, StreamTruncatedError, createReconnectTracker, fetchHttpStream, fetchServerSentEvents, fetcherToConnectionAdapter, getChunkRunId, normalizeConnectionAdapter, rpcStream, stream, webSocket, xhrHttpStream, xhrServerSentEvents };
1306
+ export { DurableStreamIncompleteError, StreamReconnectLimitError, StreamTruncatedError, createReconnectTracker, fetchHttpStream, fetchServerSentEvents, fetcherToConnectionAdapter, getChunkRunId$1 as getChunkRunId, normalizeConnectionAdapter, rpcStream, stream, webSocket, xhrHttpStream, xhrServerSentEvents };
1267
1307
 
1268
1308
  //# sourceMappingURL=connection-adapters.js.map