@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.
- package/README.md +2 -1
- package/dist/esm/byok/client.d.ts +52 -0
- package/dist/esm/byok/client.js +300 -0
- package/dist/esm/byok/client.js.map +1 -0
- package/dist/esm/byok/passkey.d.ts +43 -0
- package/dist/esm/byok/passkey.js +251 -0
- package/dist/esm/byok/passkey.js.map +1 -0
- package/dist/esm/byok/resolve.d.ts +8 -0
- package/dist/esm/byok/resolve.js +20 -0
- package/dist/esm/byok/resolve.js.map +1 -0
- package/dist/esm/byok/storage.d.ts +15 -0
- package/dist/esm/byok/storage.js +16 -0
- package/dist/esm/byok/storage.js.map +1 -0
- package/dist/esm/byok.d.ts +8 -0
- package/dist/esm/byok.js +5 -0
- package/dist/esm/chat-client.d.ts +18 -5
- package/dist/esm/chat-client.js +96 -59
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +2 -0
- package/dist/esm/connection-adapters.js +70 -30
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/generation-client.d.ts +3 -1
- package/dist/esm/generation-client.js +27 -6
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +14 -0
- package/dist/esm/generation-types.js +4 -2
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/interrupt-manager.d.ts +1 -0
- package/dist/esm/interrupt-manager.js +15 -6
- package/dist/esm/interrupt-manager.js.map +1 -1
- package/dist/esm/sse-parser.js +8 -1
- package/dist/esm/sse-parser.js.map +1 -1
- package/dist/esm/types.d.ts +46 -3
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +3 -1
- package/dist/esm/video-generation-client.js +29 -8
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +7 -3
- package/src/byok/client.ts +360 -0
- package/src/byok/passkey.ts +399 -0
- package/src/byok/resolve.ts +30 -0
- package/src/byok/storage.ts +29 -0
- package/src/byok.ts +20 -0
- package/src/chat-client.ts +138 -77
- package/src/connection-adapters.ts +108 -53
- package/src/generation-client.ts +53 -9
- package/src/generation-types.ts +24 -2
- package/src/interrupt-manager.ts +20 -6
- package/src/sse-parser.ts +10 -0
- package/src/types.ts +48 -3
- package/src/video-generation-client.ts +50 -9
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
|
/**
|
|
@@ -397,6 +400,18 @@ export interface MultimodalContent {
|
|
|
397
400
|
* If not provided, a unique ID will be generated.
|
|
398
401
|
*/
|
|
399
402
|
id?: string
|
|
403
|
+
/**
|
|
404
|
+
* Optional AG-UI metadata bag copied onto the resulting UIMessage.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* await client.sendMessage({
|
|
409
|
+
* content: 'Show me failed logins',
|
|
410
|
+
* metadata: { author: { id: 'user-42', name: 'Dana' } },
|
|
411
|
+
* })
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
metadata?: Record<string, any>
|
|
400
415
|
}
|
|
401
416
|
|
|
402
417
|
/**
|
|
@@ -462,9 +477,9 @@ export interface QueueConfig {
|
|
|
462
477
|
* function form (no `batch` via function). Per-call `sendOptions.whenBusy`
|
|
463
478
|
* overrides the strategy for that send.
|
|
464
479
|
*
|
|
465
|
-
* Actions match {@link WhenBusy}
|
|
466
|
-
*
|
|
467
|
-
*
|
|
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`).
|
|
468
483
|
*/
|
|
469
484
|
export type QueueStrategy = (ctx: {
|
|
470
485
|
pending: QueuedMessage
|
|
@@ -479,6 +494,16 @@ export type QueueOption = WhenBusy | QueueConfig | QueueStrategy
|
|
|
479
494
|
export interface SendMessageOptions {
|
|
480
495
|
/** Overrides the configured `whenBusy` for this one send. */
|
|
481
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>
|
|
482
507
|
}
|
|
483
508
|
|
|
484
509
|
/**
|
|
@@ -616,6 +641,11 @@ export interface UIMessage<
|
|
|
616
641
|
role: 'system' | 'user' | 'assistant'
|
|
617
642
|
parts: Array<MessagePart<TTools, TData>>
|
|
618
643
|
createdAt?: Date
|
|
644
|
+
/**
|
|
645
|
+
* Optional AG-UI metadata bag. TanStack writes the `tanstack` key.
|
|
646
|
+
* User keys stay at the top.
|
|
647
|
+
*/
|
|
648
|
+
metadata?: Record<string, any>
|
|
619
649
|
}
|
|
620
650
|
|
|
621
651
|
/**
|
|
@@ -845,6 +875,21 @@ export interface ChatClientBaseOptions<
|
|
|
845
875
|
*/
|
|
846
876
|
body?: Record<string, any>
|
|
847
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
|
+
|
|
848
893
|
/**
|
|
849
894
|
* Client-local runtime context passed to client tool implementations.
|
|
850
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,
|
|
@@ -10,7 +15,9 @@ import {
|
|
|
10
15
|
} from './generation-types'
|
|
11
16
|
import { createNoOpVideoDevtoolsBridge } from './devtools-noop'
|
|
12
17
|
import { parseSSEResponse } from './sse-parser'
|
|
18
|
+
import { restoreInboundChunk } from '@tanstack/ai/client'
|
|
13
19
|
import type { StreamChunk } from '@tanstack/ai/client'
|
|
20
|
+
import type { ByokClient } from './byok'
|
|
14
21
|
import type {
|
|
15
22
|
ConnectConnectionAdapter,
|
|
16
23
|
GenerationHydrationResult,
|
|
@@ -121,6 +128,8 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
121
128
|
// the client hydrates the last generation for `threadId` from the server.
|
|
122
129
|
private readonly serverDriven: boolean = false
|
|
123
130
|
private body: Record<string, any>
|
|
131
|
+
private byok: ByokClient | undefined
|
|
132
|
+
private byokProvider: (() => string | undefined) | undefined
|
|
124
133
|
|
|
125
134
|
private result: TOutput | null = null
|
|
126
135
|
private input: VideoGenerateInput | null = null
|
|
@@ -155,6 +164,8 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
155
164
|
this.hydrateGenerationHandler = options.hydrateGeneration
|
|
156
165
|
this.joinRunHandler = options.joinRun
|
|
157
166
|
this.body = options.body ?? {}
|
|
167
|
+
this.byok = options.byok
|
|
168
|
+
this.byokProvider = options.byokProvider
|
|
158
169
|
// `persistence` is `false`/omitted (ephemeral) or `true` (server-driven:
|
|
159
170
|
// hydrate the last generation for `threadId` from the server on mount).
|
|
160
171
|
this.serverDriven = options.persistence === true
|
|
@@ -257,15 +268,24 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
257
268
|
const { signal } = abortController
|
|
258
269
|
|
|
259
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
|
+
|
|
260
280
|
if (this.fetcher) {
|
|
261
|
-
await this.generateWithFetcher(input, signal, runId)
|
|
281
|
+
await this.generateWithFetcher(input, signal, runId, headers)
|
|
262
282
|
} else if (this.connection) {
|
|
263
283
|
const mergedData = { ...this.body, ...input }
|
|
264
284
|
const stream = this.connection.connect(
|
|
265
285
|
[],
|
|
266
286
|
mergedData,
|
|
267
287
|
signal,
|
|
268
|
-
this.createRunContext(runId),
|
|
288
|
+
this.createRunContext(runId, headers),
|
|
269
289
|
)
|
|
270
290
|
await this.processStream(stream, runId, signal)
|
|
271
291
|
} else {
|
|
@@ -283,6 +303,12 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
283
303
|
} catch (err: unknown) {
|
|
284
304
|
if (signal.aborted) return
|
|
285
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
|
+
}
|
|
286
312
|
this.setError(error)
|
|
287
313
|
this.setStatus('error')
|
|
288
314
|
this.recordResumeSnapshotError(error)
|
|
@@ -308,11 +334,15 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
308
334
|
input: VideoGenerateInput,
|
|
309
335
|
signal: AbortSignal,
|
|
310
336
|
runId: string,
|
|
337
|
+
headers?: Record<string, string>,
|
|
311
338
|
): Promise<void> {
|
|
312
339
|
if (!this.fetcher) return
|
|
313
340
|
|
|
314
341
|
// Fetcher returns a completed result directly, or a Response with SSE body
|
|
315
|
-
const result = await this.fetcher(
|
|
342
|
+
const result = await this.fetcher(
|
|
343
|
+
input,
|
|
344
|
+
headers === undefined ? { signal } : { signal, headers },
|
|
345
|
+
)
|
|
316
346
|
if (signal.aborted) return
|
|
317
347
|
|
|
318
348
|
if (result instanceof Response) {
|
|
@@ -343,9 +373,10 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
343
373
|
let streamRunId: string | undefined
|
|
344
374
|
let sawTerminalChunk = false
|
|
345
375
|
|
|
346
|
-
for await (const
|
|
376
|
+
for await (const raw of source) {
|
|
347
377
|
if (signal.aborted) break
|
|
348
378
|
|
|
379
|
+
const chunk = restoreInboundChunk(raw)
|
|
349
380
|
this.callbacksRef.onChunk?.(chunk)
|
|
350
381
|
this.observeResumeSnapshot(chunk)
|
|
351
382
|
const chunkRunId =
|
|
@@ -395,11 +426,9 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
395
426
|
this.devtoolsBridge.ensureRunStarted(
|
|
396
427
|
chunkRunId ?? streamRunId ?? fallbackRunId,
|
|
397
428
|
)
|
|
398
|
-
//
|
|
429
|
+
// Spec RUN_ERROR message. Missing message uses this fallback.
|
|
399
430
|
const msg =
|
|
400
|
-
(chunk.message as string | undefined) ||
|
|
401
|
-
chunk.error?.message ||
|
|
402
|
-
'An error occurred'
|
|
431
|
+
(chunk.message as string | undefined) || 'An error occurred'
|
|
403
432
|
throw new Error(msg)
|
|
404
433
|
}
|
|
405
434
|
default:
|
|
@@ -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(
|
|
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
|
|