@tanstack/ai-client 0.31.1 → 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/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 +31 -12
- 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 +2 -2
- package/src/chat-client.ts +130 -3
- package/src/connection-adapters.ts +92 -14
- package/src/types.ts +9 -0
|
@@ -144,6 +144,16 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
144
144
|
/** Constructor inputs `attach()` needs on every re-attach, not just the first. */
|
|
145
145
|
private readonly rejoinRunId;
|
|
146
146
|
private readonly cachesMessages;
|
|
147
|
+
/**
|
|
148
|
+
* Newest-window size from `history.pageSize`. Only set when
|
|
149
|
+
* `persistence === true`.
|
|
150
|
+
*/
|
|
151
|
+
private readonly historyPageSize;
|
|
152
|
+
private hasOlderMessages;
|
|
153
|
+
private olderMessagesCursor;
|
|
154
|
+
private readonly knownServerMessageIds;
|
|
155
|
+
private loadOlderMessagesInFlight;
|
|
156
|
+
private historyGeneration;
|
|
147
157
|
private devtoolsMounted;
|
|
148
158
|
private readonly callbacksRef;
|
|
149
159
|
constructor(options: ChatClientOptions<TTools, TContext, TInterrupts>);
|
|
@@ -470,6 +480,23 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
470
480
|
* Get current messages
|
|
471
481
|
*/
|
|
472
482
|
getMessages(): Array<UIMessage<TTools>>;
|
|
483
|
+
/**
|
|
484
|
+
* True when the last hydrate or older-page response said more messages exist.
|
|
485
|
+
*/
|
|
486
|
+
getHasOlderMessages(): boolean;
|
|
487
|
+
/**
|
|
488
|
+
* Fetch the next older window and put it in front of the painted messages.
|
|
489
|
+
*
|
|
490
|
+
* No-op when there is no older page, no cursor, or a page load is already
|
|
491
|
+
* running. A network failure rejects and leaves the painted messages as they
|
|
492
|
+
* are; {@link getHasOlderMessages} stays true.
|
|
493
|
+
*/
|
|
494
|
+
loadOlderMessages(): Promise<void>;
|
|
495
|
+
private resetHistoryPaging;
|
|
496
|
+
private applyHydrationPage;
|
|
497
|
+
private rememberServerMessageIds;
|
|
498
|
+
private rememberProcessorMessageIds;
|
|
499
|
+
private messagesForSend;
|
|
473
500
|
/**
|
|
474
501
|
* True when an interrupt (or another direct send) claimed the client during
|
|
475
502
|
* a drain. Read via a method so cross-await mutations are not constant-folded
|
package/dist/esm/chat-client.js
CHANGED
|
@@ -235,6 +235,16 @@ var ChatClient = class {
|
|
|
235
235
|
/** Constructor inputs `attach()` needs on every re-attach, not just the first. */
|
|
236
236
|
rejoinRunId;
|
|
237
237
|
cachesMessages;
|
|
238
|
+
/**
|
|
239
|
+
* Newest-window size from `history.pageSize`. Only set when
|
|
240
|
+
* `persistence === true`.
|
|
241
|
+
*/
|
|
242
|
+
historyPageSize;
|
|
243
|
+
hasOlderMessages = false;
|
|
244
|
+
olderMessagesCursor;
|
|
245
|
+
knownServerMessageIds = /* @__PURE__ */ new Set();
|
|
246
|
+
loadOlderMessagesInFlight = false;
|
|
247
|
+
historyGeneration = 0;
|
|
238
248
|
devtoolsMounted = false;
|
|
239
249
|
callbacksRef;
|
|
240
250
|
constructor(options) {
|
|
@@ -245,10 +255,12 @@ var ChatClient = class {
|
|
|
245
255
|
if (options.persistence === true) {
|
|
246
256
|
if (!options.threadId) throw new Error("[TanStack AI] persistence needs a stable `threadId` to key on. Pass a threadId from your app (for example support-42).");
|
|
247
257
|
cachesMessages = false;
|
|
258
|
+
this.historyPageSize = options.history?.pageSize;
|
|
248
259
|
} else if (options.persistence) {
|
|
249
260
|
if (!options.threadId) throw new Error("[TanStack AI] persistence needs a stable `threadId` to key on. Pass a threadId from your app (for example support-42).");
|
|
250
261
|
this.persistor = new ChatPersistor(options.persistence, options.threadId, (messages) => this.processor.setMessages(messages), (snapshot) => this.applyPersistedResume(snapshot));
|
|
251
|
-
|
|
262
|
+
this.historyPageSize = void 0;
|
|
263
|
+
} else this.historyPageSize = void 0;
|
|
252
264
|
this.bodyOption = options.body || {};
|
|
253
265
|
this.forwardedPropsOption = options.forwardedProps || {};
|
|
254
266
|
this.byok = options.byok;
|
|
@@ -506,16 +518,25 @@ var ChatClient = class {
|
|
|
506
518
|
if (!hydrate) return;
|
|
507
519
|
if (this.isLoading || this.abortController) return;
|
|
508
520
|
if (this.disposed) return;
|
|
521
|
+
const pageSize = this.historyPageSize;
|
|
522
|
+
const hydrateOptions = pageSize === void 0 ? void 0 : { limit: pageSize };
|
|
509
523
|
(async () => {
|
|
510
524
|
let result;
|
|
525
|
+
const generation = this.historyGeneration;
|
|
511
526
|
try {
|
|
512
|
-
result = await hydrate(this.threadId);
|
|
527
|
+
result = await hydrate(this.threadId, hydrateOptions);
|
|
513
528
|
} catch {
|
|
514
529
|
return;
|
|
515
530
|
}
|
|
531
|
+
if (generation !== this.historyGeneration) return;
|
|
516
532
|
if (this.disposed || !this.tailing) return;
|
|
517
533
|
if (this.isLoading || this.abortController) return;
|
|
518
|
-
|
|
534
|
+
this.applyHydrationPage(result.page);
|
|
535
|
+
if (result.messages.length > 0) {
|
|
536
|
+
const windowMessages = normalizeMessagesDates(result.messages);
|
|
537
|
+
this.processor.setMessages(windowMessages);
|
|
538
|
+
this.rememberServerMessageIds(windowMessages);
|
|
539
|
+
}
|
|
519
540
|
if (result.interrupts && result.interrupts.pending.length > 0) this.applyResumeSnapshot({
|
|
520
541
|
resumeState: {
|
|
521
542
|
threadId: this.threadId,
|
|
@@ -1275,7 +1296,7 @@ var ChatClient = class {
|
|
|
1275
1296
|
let activeDevtoolsRunId = null;
|
|
1276
1297
|
let runTerminalEventEmitted = false;
|
|
1277
1298
|
try {
|
|
1278
|
-
const messages = this.processor.getMessages();
|
|
1299
|
+
const messages = this.messagesForSend(this.processor.getMessages());
|
|
1279
1300
|
const clientTools = new Map(this.clientToolsRef.current);
|
|
1280
1301
|
const runtimeContext = this.context;
|
|
1281
1302
|
await this.callbacksRef.current.onResponse();
|
|
@@ -1368,6 +1389,7 @@ var ChatClient = class {
|
|
|
1368
1389
|
}
|
|
1369
1390
|
await this.drainPostStreamActions();
|
|
1370
1391
|
if (streamCompletedSuccessfully) {
|
|
1392
|
+
this.rememberProcessorMessageIds();
|
|
1371
1393
|
if (this.status !== "ready") this.setStatus("ready");
|
|
1372
1394
|
if (!this.messageQueueDraining) await this.drainQueue();
|
|
1373
1395
|
} else this.flushQueue();
|
|
@@ -1446,6 +1468,7 @@ var ChatClient = class {
|
|
|
1446
1468
|
} else if (this.activeRunIds.size > 0) this.resetSessionGenerating({ preserveClearedStreamTracking: true });
|
|
1447
1469
|
this.persistor?.beginClear();
|
|
1448
1470
|
this.processor.clearMessages();
|
|
1471
|
+
this.resetHistoryPaging();
|
|
1449
1472
|
this.discardPendingSends();
|
|
1450
1473
|
this.persistor?.remove();
|
|
1451
1474
|
this.lastResume = null;
|
|
@@ -1586,6 +1609,79 @@ var ChatClient = class {
|
|
|
1586
1609
|
return this.processor.getMessages();
|
|
1587
1610
|
}
|
|
1588
1611
|
/**
|
|
1612
|
+
* True when the last hydrate or older-page response said more messages exist.
|
|
1613
|
+
*/
|
|
1614
|
+
getHasOlderMessages() {
|
|
1615
|
+
return this.hasOlderMessages;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Fetch the next older window and put it in front of the painted messages.
|
|
1619
|
+
*
|
|
1620
|
+
* No-op when there is no older page, no cursor, or a page load is already
|
|
1621
|
+
* running. A network failure rejects and leaves the painted messages as they
|
|
1622
|
+
* are; {@link getHasOlderMessages} stays true.
|
|
1623
|
+
*/
|
|
1624
|
+
async loadOlderMessages() {
|
|
1625
|
+
const hydrate = this.connection.hydrate;
|
|
1626
|
+
if (hydrate === void 0) return;
|
|
1627
|
+
if (this.disposed) return;
|
|
1628
|
+
if (!this.hasOlderMessages) return;
|
|
1629
|
+
const cursor = this.olderMessagesCursor;
|
|
1630
|
+
if (cursor === void 0) return;
|
|
1631
|
+
if (this.loadOlderMessagesInFlight) return;
|
|
1632
|
+
const hydrateOptions = { before: cursor };
|
|
1633
|
+
if (this.historyPageSize !== void 0) hydrateOptions.limit = this.historyPageSize;
|
|
1634
|
+
this.loadOlderMessagesInFlight = true;
|
|
1635
|
+
const generation = this.historyGeneration;
|
|
1636
|
+
try {
|
|
1637
|
+
const result = await hydrate(this.threadId, hydrateOptions);
|
|
1638
|
+
if (this.disposed) return;
|
|
1639
|
+
if (generation !== this.historyGeneration) return;
|
|
1640
|
+
const olderMessages = normalizeMessagesDates(result.messages);
|
|
1641
|
+
if (olderMessages.length === 0) throw new Error("Older page was empty");
|
|
1642
|
+
this.applyHydrationPage(result.page);
|
|
1643
|
+
this.processor.prependMessages(olderMessages);
|
|
1644
|
+
this.rememberServerMessageIds(olderMessages);
|
|
1645
|
+
} finally {
|
|
1646
|
+
this.loadOlderMessagesInFlight = false;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
resetHistoryPaging() {
|
|
1650
|
+
this.hasOlderMessages = false;
|
|
1651
|
+
this.olderMessagesCursor = void 0;
|
|
1652
|
+
this.knownServerMessageIds.clear();
|
|
1653
|
+
this.historyGeneration++;
|
|
1654
|
+
}
|
|
1655
|
+
applyHydrationPage(page) {
|
|
1656
|
+
if (page?.truncated === true) {
|
|
1657
|
+
this.hasOlderMessages = true;
|
|
1658
|
+
this.olderMessagesCursor = page.cursor;
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
this.hasOlderMessages = false;
|
|
1662
|
+
this.olderMessagesCursor = void 0;
|
|
1663
|
+
}
|
|
1664
|
+
rememberServerMessageIds(messages) {
|
|
1665
|
+
const ids = messages.map((message) => message.id);
|
|
1666
|
+
for (const id of ids) this.knownServerMessageIds.add(id);
|
|
1667
|
+
}
|
|
1668
|
+
rememberProcessorMessageIds() {
|
|
1669
|
+
if (this.historyPageSize === void 0) return;
|
|
1670
|
+
this.rememberServerMessageIds(this.processor.getMessages());
|
|
1671
|
+
}
|
|
1672
|
+
messagesForSend(messages) {
|
|
1673
|
+
if (this.historyPageSize === void 0) return messages;
|
|
1674
|
+
const unknownMessages = [];
|
|
1675
|
+
for (const message of messages) {
|
|
1676
|
+
if (this.knownServerMessageIds.has(message.id)) continue;
|
|
1677
|
+
unknownMessages.push(message);
|
|
1678
|
+
}
|
|
1679
|
+
if (unknownMessages.length > 0) return unknownMessages;
|
|
1680
|
+
const lastUserIndex = messages.findLastIndex((message) => message.role === "user");
|
|
1681
|
+
if (lastUserIndex === -1) return [];
|
|
1682
|
+
return messages.slice(lastUserIndex);
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1589
1685
|
* True when an interrupt (or another direct send) claimed the client during
|
|
1590
1686
|
* a drain. Read via a method so cross-await mutations are not constant-folded
|
|
1591
1687
|
* by control-flow analysis.
|