@tanstack/ai-client 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/esm/byok/client.d.ts +52 -0
  2. package/dist/esm/byok/client.js +300 -0
  3. package/dist/esm/byok/client.js.map +1 -0
  4. package/dist/esm/byok/passkey.d.ts +43 -0
  5. package/dist/esm/byok/passkey.js +251 -0
  6. package/dist/esm/byok/passkey.js.map +1 -0
  7. package/dist/esm/byok/resolve.d.ts +8 -0
  8. package/dist/esm/byok/resolve.js +20 -0
  9. package/dist/esm/byok/resolve.js.map +1 -0
  10. package/dist/esm/byok/storage.d.ts +15 -0
  11. package/dist/esm/byok/storage.js +16 -0
  12. package/dist/esm/byok/storage.js.map +1 -0
  13. package/dist/esm/byok.d.ts +8 -0
  14. package/dist/esm/byok.js +5 -0
  15. package/dist/esm/chat-client.d.ts +16 -3
  16. package/dist/esm/chat-client.js +49 -31
  17. package/dist/esm/chat-client.js.map +1 -1
  18. package/dist/esm/connection-adapters.d.ts +2 -0
  19. package/dist/esm/connection-adapters.js +33 -9
  20. package/dist/esm/connection-adapters.js.map +1 -1
  21. package/dist/esm/generation-client.d.ts +3 -1
  22. package/dist/esm/generation-client.js +23 -4
  23. package/dist/esm/generation-client.js.map +1 -1
  24. package/dist/esm/generation-types.d.ts +14 -0
  25. package/dist/esm/generation-types.js.map +1 -1
  26. package/dist/esm/interrupt-manager.d.ts +1 -0
  27. package/dist/esm/interrupt-manager.js +15 -6
  28. package/dist/esm/interrupt-manager.js.map +1 -1
  29. package/dist/esm/sse-parser.js +8 -1
  30. package/dist/esm/sse-parser.js.map +1 -1
  31. package/dist/esm/types.d.ts +29 -3
  32. package/dist/esm/types.js.map +1 -1
  33. package/dist/esm/video-generation-client.d.ts +3 -1
  34. package/dist/esm/video-generation-client.js +25 -6
  35. package/dist/esm/video-generation-client.js.map +1 -1
  36. package/package.json +6 -2
  37. package/src/byok/client.ts +360 -0
  38. package/src/byok/passkey.ts +399 -0
  39. package/src/byok/resolve.ts +30 -0
  40. package/src/byok/storage.ts +29 -0
  41. package/src/byok.ts +20 -0
  42. package/src/chat-client.ts +95 -65
  43. package/src/connection-adapters.ts +41 -12
  44. package/src/generation-client.ts +48 -4
  45. package/src/generation-types.ts +16 -0
  46. package/src/interrupt-manager.ts +20 -6
  47. package/src/sse-parser.ts +10 -0
  48. package/src/types.ts +31 -3
  49. package/src/video-generation-client.ts +45 -4
package/src/types.ts CHANGED
@@ -25,6 +25,7 @@ import type {
25
25
  UIResourcePart,
26
26
  VideoPart,
27
27
  } from '@tanstack/ai/client'
28
+ import type { ByokClient } from './byok'
28
29
  import type { ConnectionAdapter } from './connection-adapters'
29
30
  import type { AIDevtoolsClientMetadata } from './devtools'
30
31
  import type { ChatDevtoolsBridgeFactory } from './devtools-noop'
@@ -291,6 +292,8 @@ export interface ChatFetcherInput {
291
292
  export interface ChatFetcherOptions {
292
293
  /** Fires when `stop()` is called or the request is superseded. */
293
294
  signal: AbortSignal
295
+ /** Extra request headers for this run (e.g. BYOK keys). */
296
+ headers?: Record<string, string>
294
297
  }
295
298
 
296
299
  /**
@@ -474,9 +477,9 @@ export interface QueueConfig {
474
477
  * function form (no `batch` via function). Per-call `sendOptions.whenBusy`
475
478
  * overrides the strategy for that send.
476
479
  *
477
- * Actions match {@link WhenBusy}: `'queue' | 'drop' | 'interrupt'`. Concurrent
478
- * streams are not supported. `pending.id` is the id that will be stored if the
479
- * action is `'queue'` (safe to pass to `cancelQueued`).
480
+ * Actions match {@link WhenBusy}. Concurrent streams are not supported.
481
+ * `pending.id` is the id that will be stored if the action is `'queue'`
482
+ * (safe to pass to `cancelQueued`).
480
483
  */
481
484
  export type QueueStrategy = (ctx: {
482
485
  pending: QueuedMessage
@@ -491,6 +494,16 @@ export type QueueOption = WhenBusy | QueueConfig | QueueStrategy
491
494
  export interface SendMessageOptions {
492
495
  /** Overrides the configured `whenBusy` for this one send. */
493
496
  whenBusy?: WhenBusy
497
+ /**
498
+ * Extra JSON merged into this request's wire `forwardedProps`.
499
+ * Shallow merge: `{ ...chatBody, ...positionalBody, ...body }`.
500
+ * This field wins on key collisions.
501
+ *
502
+ * Framework hooks (`useChat`, `injectChat`, `createChat`) expose
503
+ * `sendMessage(content, options)` with no positional body, so this field
504
+ * is the per-call body channel on those surfaces.
505
+ */
506
+ body?: Record<string, any>
494
507
  }
495
508
 
496
509
  /**
@@ -862,6 +875,21 @@ export interface ChatClientBaseOptions<
862
875
  */
863
876
  body?: Record<string, any>
864
877
 
878
+ /**
879
+ * Optional BYOK keyring. On each send the client prepares the resolved
880
+ * provider and stamps `x-byok-*` request headers. Keys never go in the body.
881
+ */
882
+ byok?: ByokClient
883
+
884
+ /**
885
+ * Optional provider id for this chat. If it returns a provider slug,
886
+ * only that key is prepared and sent. Otherwise the merged `provider`
887
+ * from `forwardedProps`, `body`, and per-call `sendMessage` `body` is
888
+ * used. Later sources win. If no slug resolves, the send throws
889
+ * instead of attaching every stored key.
890
+ */
891
+ byokProvider?: () => string | undefined
892
+
865
893
  /**
866
894
  * Client-local runtime context passed to client tool implementations.
867
895
  *
@@ -1,3 +1,8 @@
1
+ import { ByokBlockedError, ByokMissingError } from '@tanstack/ai/byok'
2
+ import {
3
+ prepareResolvedByokHeaders,
4
+ resolveByokProviderId,
5
+ } from './byok/resolve'
1
6
  import {
2
7
  GENERATION_EVENTS,
3
8
  GENERATION_STREAM_TRUNCATED_MESSAGE,
@@ -12,6 +17,7 @@ import { createNoOpVideoDevtoolsBridge } from './devtools-noop'
12
17
  import { parseSSEResponse } from './sse-parser'
13
18
  import { restoreInboundChunk } from '@tanstack/ai/client'
14
19
  import type { StreamChunk } from '@tanstack/ai/client'
20
+ import type { ByokClient } from './byok'
15
21
  import type {
16
22
  ConnectConnectionAdapter,
17
23
  GenerationHydrationResult,
@@ -122,6 +128,8 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
122
128
  // the client hydrates the last generation for `threadId` from the server.
123
129
  private readonly serverDriven: boolean = false
124
130
  private body: Record<string, any>
131
+ private byok: ByokClient | undefined
132
+ private byokProvider: (() => string | undefined) | undefined
125
133
 
126
134
  private result: TOutput | null = null
127
135
  private input: VideoGenerateInput | null = null
@@ -156,6 +164,8 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
156
164
  this.hydrateGenerationHandler = options.hydrateGeneration
157
165
  this.joinRunHandler = options.joinRun
158
166
  this.body = options.body ?? {}
167
+ this.byok = options.byok
168
+ this.byokProvider = options.byokProvider
159
169
  // `persistence` is `false`/omitted (ephemeral) or `true` (server-driven:
160
170
  // hydrate the last generation for `threadId` from the server on mount).
161
171
  this.serverDriven = options.persistence === true
@@ -258,15 +268,24 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
258
268
  const { signal } = abortController
259
269
 
260
270
  try {
271
+ let headers: Record<string, string> | undefined
272
+ if (this.byok) {
273
+ const provider = resolveByokProviderId(
274
+ this.byokProvider,
275
+ this.body.provider,
276
+ )
277
+ headers = await prepareResolvedByokHeaders(this.byok, provider)
278
+ }
279
+
261
280
  if (this.fetcher) {
262
- await this.generateWithFetcher(input, signal, runId)
281
+ await this.generateWithFetcher(input, signal, runId, headers)
263
282
  } else if (this.connection) {
264
283
  const mergedData = { ...this.body, ...input }
265
284
  const stream = this.connection.connect(
266
285
  [],
267
286
  mergedData,
268
287
  signal,
269
- this.createRunContext(runId),
288
+ this.createRunContext(runId, headers),
270
289
  )
271
290
  await this.processStream(stream, runId, signal)
272
291
  } else {
@@ -284,6 +303,12 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
284
303
  } catch (err: unknown) {
285
304
  if (signal.aborted) return
286
305
  const error = err instanceof Error ? err : new Error(String(err))
306
+ if (error instanceof ByokMissingError) {
307
+ this.byok?.request(error.provider, 'missing')
308
+ }
309
+ if (error instanceof ByokBlockedError && error.reason === 'locked') {
310
+ this.byok?.request(error.provider, 'locked')
311
+ }
287
312
  this.setError(error)
288
313
  this.setStatus('error')
289
314
  this.recordResumeSnapshotError(error)
@@ -309,11 +334,15 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
309
334
  input: VideoGenerateInput,
310
335
  signal: AbortSignal,
311
336
  runId: string,
337
+ headers?: Record<string, string>,
312
338
  ): Promise<void> {
313
339
  if (!this.fetcher) return
314
340
 
315
341
  // Fetcher returns a completed result directly, or a Response with SSE body
316
- const result = await this.fetcher(input, { signal })
342
+ const result = await this.fetcher(
343
+ input,
344
+ headers === undefined ? { signal } : { signal, headers },
345
+ )
317
346
  if (signal.aborted) return
318
347
 
319
348
  if (result instanceof Response) {
@@ -470,6 +499,8 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
470
499
  Pick<
471
500
  VideoGenerationClientOptions<TOutput>,
472
501
  | 'body'
502
+ | 'byok'
503
+ | 'byokProvider'
473
504
  | 'onResult'
474
505
  | 'onError'
475
506
  | 'onProgress'
@@ -482,6 +513,12 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
482
513
  if (options.body !== undefined) {
483
514
  this.body = options.body ?? {}
484
515
  }
516
+ if (options.byok !== undefined) {
517
+ this.byok = options.byok
518
+ }
519
+ if (options.byokProvider !== undefined) {
520
+ this.byokProvider = options.byokProvider
521
+ }
485
522
  if (options.onResult !== undefined) {
486
523
  this.callbacksRef.onResult = options.onResult
487
524
  }
@@ -694,10 +731,14 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
694
731
  return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`
695
732
  }
696
733
 
697
- private createRunContext(runId: string): RunAgentInputContext {
734
+ private createRunContext(
735
+ runId: string,
736
+ headers?: Record<string, string>,
737
+ ): RunAgentInputContext {
698
738
  return {
699
739
  threadId: this.threadId,
700
740
  runId,
741
+ ...(headers ? { headers } : {}),
701
742
  }
702
743
  }
703
744