@tanstack/ai-client 0.31.0 → 0.32.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 +14 -13
- package/dist/esm/byok/passkey.js +16 -0
- package/dist/esm/byok/passkey.js.map +1 -1
- package/dist/esm/chat-client.d.ts +27 -0
- package/dist/esm/chat-client.js +100 -4
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +25 -4
- package/dist/esm/connection-adapters.js +50 -28
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/types.d.ts +9 -0
- package/dist/esm/types.js.map +1 -1
- package/package.json +3 -3
- package/src/byok/passkey.ts +38 -0
- package/src/chat-client.ts +130 -3
- package/src/connection-adapters.ts +115 -33
- package/src/types.ts +9 -0
|
@@ -130,7 +130,7 @@ export interface ConnectConnectionAdapter {
|
|
|
130
130
|
* `fetchServerSentEvents` / `fetchHttpStream`, and on `stream()` /
|
|
131
131
|
* `rpcStream()` when supplied via {@link StreamConnectionHandlers}.
|
|
132
132
|
*/
|
|
133
|
-
hydrate?: (threadId: string) => Promise<ChatHydrationResult>;
|
|
133
|
+
hydrate?: (threadId: string, options?: ChatHydrateOptions) => Promise<ChatHydrationResult>;
|
|
134
134
|
}
|
|
135
135
|
/**
|
|
136
136
|
* Server-resolved hydration for a generation thread. `resumeSnapshot` is the
|
|
@@ -166,11 +166,22 @@ export interface GenerationHydrationResult {
|
|
|
166
166
|
runId: string;
|
|
167
167
|
} | null;
|
|
168
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Optional paging for a chat hydrate GET.
|
|
171
|
+
* Omit both fields to load the full thread.
|
|
172
|
+
*/
|
|
173
|
+
export interface ChatHydrateOptions {
|
|
174
|
+
limit?: number;
|
|
175
|
+
before?: string;
|
|
176
|
+
}
|
|
169
177
|
/**
|
|
170
178
|
* Server-resolved hydration for a thread. `messages` is the stored transcript;
|
|
171
179
|
* `activeRun` is a cursor to a run still generating for the thread (or `null`).
|
|
172
180
|
* Keyed on the STABLE thread id — the client never handles a run id, so a turn
|
|
173
181
|
* that spans several runs (interrupt/tool continuations) reconnects correctly.
|
|
182
|
+
*
|
|
183
|
+
* `page` is additive. Old servers omit it; treat that as a full list
|
|
184
|
+
* (`truncated: false`).
|
|
174
185
|
*/
|
|
175
186
|
export interface ChatHydrationResult {
|
|
176
187
|
messages: Array<UIMessage>;
|
|
@@ -186,6 +197,16 @@ export interface ChatHydrationResult {
|
|
|
186
197
|
runId: string;
|
|
187
198
|
pending: Array<ChatPendingInterrupt>;
|
|
188
199
|
} | null;
|
|
200
|
+
/**
|
|
201
|
+
* Whether this window is a slice of a longer thread.
|
|
202
|
+
* `cursor` is set when `truncated` is true and is sent back as `before`.
|
|
203
|
+
*/
|
|
204
|
+
page?: {
|
|
205
|
+
truncated: false;
|
|
206
|
+
} | {
|
|
207
|
+
truncated: true;
|
|
208
|
+
cursor: string;
|
|
209
|
+
};
|
|
189
210
|
}
|
|
190
211
|
/**
|
|
191
212
|
* A {@link ConnectConnectionAdapter} that also supports joining an existing run
|
|
@@ -205,7 +226,7 @@ export interface ResumableConnectConnectionAdapter extends ConnectConnectionAdap
|
|
|
205
226
|
* JSON GET (`?threadId`), so it is transport-agnostic regardless of how the
|
|
206
227
|
* delivery stream is served.
|
|
207
228
|
*/
|
|
208
|
-
hydrate?: (threadId: string) => Promise<ChatHydrationResult>;
|
|
229
|
+
hydrate?: (threadId: string, options?: ChatHydrateOptions) => Promise<ChatHydrationResult>;
|
|
209
230
|
}
|
|
210
231
|
export interface SubscribeConnectionAdapter {
|
|
211
232
|
/**
|
|
@@ -228,7 +249,7 @@ export interface SubscribeConnectionAdapter {
|
|
|
228
249
|
* cursor). Present only when the underlying connection supports it. The client
|
|
229
250
|
* calls it on mount to re-hydrate without any app-side loader or prop.
|
|
230
251
|
*/
|
|
231
|
-
hydrate?: (threadId: string) => Promise<ChatHydrationResult>;
|
|
252
|
+
hydrate?: (threadId: string, options?: ChatHydrateOptions) => Promise<ChatHydrationResult>;
|
|
232
253
|
}
|
|
233
254
|
/**
|
|
234
255
|
* Connection adapter union.
|
|
@@ -394,7 +415,7 @@ export interface StreamConnectionHandlers {
|
|
|
394
415
|
* Server-driven chat hydration for `persistence: true`: the stored
|
|
395
416
|
* transcript for `threadId` plus a cursor to an in-flight run.
|
|
396
417
|
*/
|
|
397
|
-
hydrate?: (threadId: string) => Promise<ChatHydrationResult>;
|
|
418
|
+
hydrate?: (threadId: string, options?: ChatHydrateOptions) => Promise<ChatHydrationResult>;
|
|
398
419
|
/**
|
|
399
420
|
* Server-driven generation hydration for `persistence: true`: the last
|
|
400
421
|
* generation's resume snapshot for `threadId` plus a cursor to a run still
|
|
@@ -309,15 +309,33 @@ function errorFromXhrStatus(xhr) {
|
|
|
309
309
|
}
|
|
310
310
|
return /* @__PURE__ */ new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`);
|
|
311
311
|
}
|
|
312
|
+
function chatHydrationSearchParams(threadId, options) {
|
|
313
|
+
const values = { threadId };
|
|
314
|
+
if (options?.limit !== void 0) values.limit = String(options.limit);
|
|
315
|
+
if (options?.before !== void 0) values.before = options.before;
|
|
316
|
+
return values;
|
|
317
|
+
}
|
|
318
|
+
function readChatHydrationPage(value) {
|
|
319
|
+
if (value === null || typeof value !== "object") return { truncated: false };
|
|
320
|
+
if (!("truncated" in value) || value.truncated !== true) return { truncated: false };
|
|
321
|
+
if (!("cursor" in value) || typeof value.cursor !== "string" || value.cursor === "") return { truncated: false };
|
|
322
|
+
return {
|
|
323
|
+
truncated: true,
|
|
324
|
+
cursor: value.cursor
|
|
325
|
+
};
|
|
326
|
+
}
|
|
312
327
|
/**
|
|
313
328
|
* GET the hydration endpoint for a thread and parse its JSON `{ messages,
|
|
314
329
|
* activeRun }` body. This is the transport-agnostic reconnect probe: keyed on
|
|
315
330
|
* the STABLE thread id, it returns the stored transcript and — if a run is still
|
|
316
331
|
* generating — a cursor the caller tails via `joinRun`. Shared by every fetch/
|
|
317
332
|
* XHR adapter so the client never has to know which transport is in use.
|
|
333
|
+
*
|
|
334
|
+
* Pass `limit` / `before` to page. Old servers that omit `page` are treated as
|
|
335
|
+
* a full list (`truncated: false`).
|
|
318
336
|
*/
|
|
319
|
-
async function fetchThreadHydration(fetchClient, url, headers, credentials, threadId) {
|
|
320
|
-
const response = await fetchClient(withSearchParams(url,
|
|
337
|
+
async function fetchThreadHydration(fetchClient, url, headers, credentials, threadId, options) {
|
|
338
|
+
const response = await fetchClient(withSearchParams(url, chatHydrationSearchParams(threadId, options)), {
|
|
321
339
|
method: "GET",
|
|
322
340
|
headers: {
|
|
323
341
|
Accept: "application/json",
|
|
@@ -335,7 +353,8 @@ async function fetchThreadHydration(fetchClient, url, headers, credentials, thre
|
|
|
335
353
|
return {
|
|
336
354
|
messages: Array.isArray(data.messages) ? normalizeMessagesDates(data.messages) : [],
|
|
337
355
|
activeRun,
|
|
338
|
-
interrupts
|
|
356
|
+
interrupts,
|
|
357
|
+
page: readChatHydrationPage(data.page)
|
|
339
358
|
};
|
|
340
359
|
}
|
|
341
360
|
/**
|
|
@@ -474,6 +493,7 @@ function normalizeConnectionAdapter(connection) {
|
|
|
474
493
|
if (!hasConnect) throw new Error("Connection adapter must provide either connect or both subscribe and send");
|
|
475
494
|
let activeBuffer = [];
|
|
476
495
|
let activeWaiters = [];
|
|
496
|
+
let activeSubscriber;
|
|
477
497
|
function push(chunk, runId) {
|
|
478
498
|
if (runId) chunkRunIds.set(chunk, runId);
|
|
479
499
|
const waiter = activeWaiters.shift();
|
|
@@ -481,17 +501,14 @@ function normalizeConnectionAdapter(connection) {
|
|
|
481
501
|
else activeBuffer.push(chunk);
|
|
482
502
|
}
|
|
483
503
|
async function waitUntilSubscriberIdle(abortSignal) {
|
|
484
|
-
const idle = () => activeBuffer.length === 0 && (activeWaiters.length > 0 || abortSignal?.aborted);
|
|
504
|
+
const idle = () => activeSubscriber !== activeWaiters || activeBuffer.length === 0 && (activeWaiters.length > 0 || abortSignal?.aborted);
|
|
485
505
|
for (let i = 0; i < 16 && !abortSignal?.aborted; i++) {
|
|
486
506
|
if (idle()) return;
|
|
487
507
|
await Promise.resolve();
|
|
488
508
|
}
|
|
489
|
-
let macrotaskWaits = 0;
|
|
490
509
|
while (!abortSignal?.aborted) {
|
|
491
510
|
if (idle()) return;
|
|
492
511
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
493
|
-
macrotaskWaits++;
|
|
494
|
-
if (activeWaiters.length === 0 && macrotaskWaits >= 32) return;
|
|
495
512
|
}
|
|
496
513
|
}
|
|
497
514
|
return {
|
|
@@ -501,19 +518,24 @@ function normalizeConnectionAdapter(connection) {
|
|
|
501
518
|
activeBuffer = myBuffer;
|
|
502
519
|
activeWaiters = myWaiters;
|
|
503
520
|
return (async function* () {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
521
|
+
activeSubscriber = myWaiters;
|
|
522
|
+
try {
|
|
523
|
+
while (!abortSignal?.aborted) {
|
|
524
|
+
let chunk;
|
|
525
|
+
const buffered = myBuffer.shift();
|
|
526
|
+
if (buffered !== void 0) chunk = buffered;
|
|
527
|
+
else chunk = await new Promise((resolve) => {
|
|
528
|
+
const onAbort = () => resolve(null);
|
|
529
|
+
myWaiters.push((c) => {
|
|
530
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
531
|
+
resolve(c);
|
|
532
|
+
});
|
|
533
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
513
534
|
});
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
535
|
+
if (chunk !== null) yield chunk;
|
|
536
|
+
}
|
|
537
|
+
} finally {
|
|
538
|
+
if (activeSubscriber === myWaiters) activeSubscriber = void 0;
|
|
517
539
|
}
|
|
518
540
|
})();
|
|
519
541
|
},
|
|
@@ -556,7 +578,7 @@ function normalizeConnectionAdapter(connection) {
|
|
|
556
578
|
...typeof connection.joinRun === "function" ? { joinRun: (runId, abortSignal) => connection.joinRun(runId, abortSignal) } : {},
|
|
557
579
|
...(() => {
|
|
558
580
|
const hydrate = connection.hydrate;
|
|
559
|
-
return typeof hydrate === "function" ? { hydrate: (threadId) => hydrate(threadId) } : {};
|
|
581
|
+
return typeof hydrate === "function" ? { hydrate: (threadId, options) => hydrate(threadId, options) } : {};
|
|
560
582
|
})()
|
|
561
583
|
};
|
|
562
584
|
}
|
|
@@ -654,10 +676,10 @@ function fetchServerSentEvents(url, options = {}) {
|
|
|
654
676
|
credentials: resolvedOptions.credentials || "same-origin"
|
|
655
677
|
}, (response, sseSignal) => responseToSSEEvents(response, sseSignal, { runId })), signal, resolvedOptions.reconnect);
|
|
656
678
|
},
|
|
657
|
-
async hydrate(threadId) {
|
|
679
|
+
async hydrate(threadId, hydrateOptions) {
|
|
658
680
|
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
659
681
|
const resolvedOptions = typeof options === "function" ? await options() : options;
|
|
660
|
-
return fetchThreadHydration(resolvedOptions.fetchClient ?? fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.credentials || "same-origin", threadId);
|
|
682
|
+
return fetchThreadHydration(resolvedOptions.fetchClient ?? fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.credentials || "same-origin", threadId, hydrateOptions);
|
|
661
683
|
},
|
|
662
684
|
async hydrateGeneration(threadId) {
|
|
663
685
|
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
@@ -737,10 +759,10 @@ function fetchHttpStream(url, options = {}) {
|
|
|
737
759
|
credentials: resolvedOptions.credentials || "same-origin"
|
|
738
760
|
}, responseToNdjsonEvents), signal, resolvedOptions.reconnect);
|
|
739
761
|
},
|
|
740
|
-
async hydrate(threadId) {
|
|
762
|
+
async hydrate(threadId, hydrateOptions) {
|
|
741
763
|
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
742
764
|
const resolvedOptions = typeof options === "function" ? await options() : options;
|
|
743
|
-
return fetchThreadHydration(resolvedOptions.fetchClient ?? fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.credentials || "same-origin", threadId);
|
|
765
|
+
return fetchThreadHydration(resolvedOptions.fetchClient ?? fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.credentials || "same-origin", threadId, hydrateOptions);
|
|
744
766
|
},
|
|
745
767
|
async hydrateGeneration(threadId) {
|
|
746
768
|
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
@@ -918,10 +940,10 @@ function xhrServerSentEvents(url, options = {}) {
|
|
|
918
940
|
runId
|
|
919
941
|
}), resolvedOptions, "GET", [], void 0, void 0, (lines) => linesToSSEEvents(lines, { runId })), signal, resolvedOptions.reconnect);
|
|
920
942
|
},
|
|
921
|
-
async hydrate(threadId) {
|
|
943
|
+
async hydrate(threadId, hydrateOptions) {
|
|
922
944
|
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
923
945
|
const resolvedOptions = await resolveXhrConnectionOptions(options);
|
|
924
|
-
return fetchThreadHydration(fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.withCredentials ? "include" : "same-origin", threadId);
|
|
946
|
+
return fetchThreadHydration(fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.withCredentials ? "include" : "same-origin", threadId, hydrateOptions);
|
|
925
947
|
},
|
|
926
948
|
async hydrateGeneration(threadId) {
|
|
927
949
|
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
@@ -955,10 +977,10 @@ function xhrHttpStream(url, options = {}) {
|
|
|
955
977
|
runId
|
|
956
978
|
}), resolvedOptions, "GET", [], void 0, void 0, linesToNdjsonEvents), signal, resolvedOptions.reconnect);
|
|
957
979
|
},
|
|
958
|
-
async hydrate(threadId) {
|
|
980
|
+
async hydrate(threadId, hydrateOptions) {
|
|
959
981
|
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
960
982
|
const resolvedOptions = await resolveXhrConnectionOptions(options);
|
|
961
|
-
return fetchThreadHydration(fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.withCredentials ? "include" : "same-origin", threadId);
|
|
983
|
+
return fetchThreadHydration(fetch, resolvedUrl, mergeHeaders(resolvedOptions.headers), resolvedOptions.withCredentials ? "include" : "same-origin", threadId, hydrateOptions);
|
|
962
984
|
},
|
|
963
985
|
async hydrateGeneration(threadId) {
|
|
964
986
|
const resolvedUrl = typeof url === "function" ? url() : url;
|