@tanstack/ai-client 0.35.1 → 0.36.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 +1 -0
- package/dist/esm/chat-client.d.ts +3 -2
- package/dist/esm/chat-client.js +30 -14
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/interrupt-manager.d.ts +3 -0
- package/dist/esm/interrupt-manager.js +62 -5
- package/dist/esm/interrupt-manager.js.map +1 -1
- package/dist/esm/types.d.ts +3 -1
- package/dist/esm/types.js.map +1 -1
- package/package.json +4 -4
- package/src/chat-client.ts +75 -36
- package/src/interrupt-manager.ts +91 -6
- package/src/types.ts +3 -0
package/README.md
CHANGED
|
@@ -240,6 +240,7 @@ Official adapters include:
|
|
|
240
240
|
| [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
|
|
241
241
|
| [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
|
|
242
242
|
| [`@tanstack/ai-reactor`](https://tanstack.com/ai/latest/docs/adapters/reactor) | Reactor live world and video generation (Orbis, Happy Oyster, LingBot, Helios, FastH3) |
|
|
243
|
+
| [`@tanstack/ai-worldlabs`](https://tanstack.com/ai/latest/docs/adapters/worldlabs) | World Labs Marble persistent 3D world generation |
|
|
243
244
|
| [`@tanstack/ai-cloudflare`](https://tanstack.com/ai/latest/docs/adapters/cloudflare) | Cloudflare Workers AI chat, embeddings, image, speech, transcription, and AI Gateway |
|
|
244
245
|
|
|
245
246
|
The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
|
|
@@ -310,7 +310,8 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
310
310
|
*/
|
|
311
311
|
private startSubscription;
|
|
312
312
|
/**
|
|
313
|
-
* Consume chunks from the connection subscription.
|
|
313
|
+
* Consume chunks from the connection subscription. Chunks are processed in
|
|
314
|
+
* order; the loop yields to the host after each processing budget.
|
|
314
315
|
*/
|
|
315
316
|
private consumeSubscription;
|
|
316
317
|
/**
|
|
@@ -332,7 +333,7 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
|
|
|
332
333
|
* give up after {@link REJOIN_CONNECT_DEADLINE_MS} if no chunk arrives and
|
|
333
334
|
* clear the dead pointer so it does not retry on the next load.
|
|
334
335
|
*
|
|
335
|
-
* Replay chunks are processed WITHOUT the
|
|
336
|
+
* Replay chunks are processed WITHOUT the time-slice yield the live path uses,
|
|
336
337
|
* so the buffered prefix snaps in and only the genuinely-live tail streams at
|
|
337
338
|
* network speed — a reload looks like the run continued, not like it re-typed.
|
|
338
339
|
*/
|
package/dist/esm/chat-client.js
CHANGED
|
@@ -6,9 +6,15 @@ import { fetcherToConnectionAdapter, getChunkRunId as getChunkRunId$1, normalize
|
|
|
6
6
|
import { ChatPersistor } from "./client-persistor.js";
|
|
7
7
|
import { ClearedStreamTracker } from "./cleared-stream-tracker.js";
|
|
8
8
|
import { InterruptManager } from "./interrupt-manager.js";
|
|
9
|
-
import { EventType, StreamProcessor, convertSchemaToJsonSchema, generateMessageId, isStandardSchema, mergeMetadata, normalizeToUIMessage,
|
|
9
|
+
import { EventType, StreamProcessor, cloneAndDeepFreezeJson, convertSchemaToJsonSchema, generateMessageId, isStandardSchema, mergeMetadata, normalizeToUIMessage, restoreInboundChunk, tanstackMetadata, validateWithStandardSchema } from "@tanstack/ai/client";
|
|
10
10
|
import { ByokBlockedError, ByokMissingError, ByokUnresolvedProviderError } from "@tanstack/ai/byok";
|
|
11
11
|
//#region src/chat-client.ts
|
|
12
|
+
var STREAM_PROCESSING_BUDGET_MS = 8;
|
|
13
|
+
function yieldToHost() {
|
|
14
|
+
const { scheduler } = globalThis;
|
|
15
|
+
if (scheduler?.yield) return scheduler.yield();
|
|
16
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
17
|
+
}
|
|
12
18
|
function assertUniqueInterruptDefinitions(interrupts) {
|
|
13
19
|
const ids = /* @__PURE__ */ new Set();
|
|
14
20
|
for (const interrupt of interrupts ?? []) {
|
|
@@ -582,7 +588,7 @@ var ChatClient = class {
|
|
|
582
588
|
this.processor.setMessages(windowMessages);
|
|
583
589
|
this.rememberServerMessageIds(windowMessages);
|
|
584
590
|
}
|
|
585
|
-
if (result.interrupts && result.interrupts.pending.length > 0) this.applyResumeSnapshot({
|
|
591
|
+
if (result.interrupts && result.interrupts.pending.length > 0 && (!result.activeRun?.runId || result.activeRun.runId === result.interrupts.runId)) this.applyResumeSnapshot({
|
|
586
592
|
resumeState: {
|
|
587
593
|
threadId: this.threadId,
|
|
588
594
|
runId: result.interrupts.runId
|
|
@@ -1011,13 +1017,21 @@ var ChatClient = class {
|
|
|
1011
1017
|
});
|
|
1012
1018
|
}
|
|
1013
1019
|
/**
|
|
1014
|
-
* Consume chunks from the connection subscription.
|
|
1020
|
+
* Consume chunks from the connection subscription. Chunks are processed in
|
|
1021
|
+
* order; the loop yields to the host after each processing budget.
|
|
1015
1022
|
*/
|
|
1016
1023
|
async consumeSubscription(signal) {
|
|
1017
1024
|
const stream = this.connection.subscribe(signal);
|
|
1025
|
+
let chunkProcessingTime = 0;
|
|
1018
1026
|
for await (const chunk of stream) {
|
|
1019
1027
|
if (signal.aborted) break;
|
|
1020
|
-
|
|
1028
|
+
const startedAt = performance.now();
|
|
1029
|
+
this.processIncomingChunk(chunk);
|
|
1030
|
+
chunkProcessingTime += performance.now() - startedAt;
|
|
1031
|
+
if (chunkProcessingTime < STREAM_PROCESSING_BUDGET_MS) continue;
|
|
1032
|
+
chunkProcessingTime = 0;
|
|
1033
|
+
if (typeof document !== "undefined" && document.hidden) continue;
|
|
1034
|
+
await yieldToHost();
|
|
1021
1035
|
}
|
|
1022
1036
|
}
|
|
1023
1037
|
/**
|
|
@@ -1039,7 +1053,7 @@ var ChatClient = class {
|
|
|
1039
1053
|
* give up after {@link REJOIN_CONNECT_DEADLINE_MS} if no chunk arrives and
|
|
1040
1054
|
* clear the dead pointer so it does not retry on the next load.
|
|
1041
1055
|
*
|
|
1042
|
-
* Replay chunks are processed WITHOUT the
|
|
1056
|
+
* Replay chunks are processed WITHOUT the time-slice yield the live path uses,
|
|
1043
1057
|
* so the buffered prefix snaps in and only the genuinely-live tail streams at
|
|
1044
1058
|
* network speed — a reload looks like the run continued, not like it re-typed.
|
|
1045
1059
|
*/
|
|
@@ -1074,7 +1088,7 @@ var ChatClient = class {
|
|
|
1074
1088
|
rebuilt = true;
|
|
1075
1089
|
this.dropTrailingInFlightAssistant();
|
|
1076
1090
|
}
|
|
1077
|
-
|
|
1091
|
+
this.processIncomingChunk(chunk);
|
|
1078
1092
|
}
|
|
1079
1093
|
if (this.pendingToolExecutions.size > 0) await Promise.all(this.pendingToolExecutions.values());
|
|
1080
1094
|
} catch (error) {
|
|
@@ -1107,7 +1121,7 @@ var ChatClient = class {
|
|
|
1107
1121
|
const last = messages[messages.length - 1];
|
|
1108
1122
|
if (last && last.role === "assistant") this.processor.setMessages(messages.slice(0, -1));
|
|
1109
1123
|
}
|
|
1110
|
-
|
|
1124
|
+
processIncomingChunk(chunk) {
|
|
1111
1125
|
chunk = restoreInboundChunk(chunk);
|
|
1112
1126
|
if (chunk.type === "RUN_ERROR" && this.isActiveInterruptSubmissionFailure(chunk)) {
|
|
1113
1127
|
const interruptErrors = tanstackMetadata(chunk)?.interruptErrors;
|
|
@@ -1136,7 +1150,6 @@ var ChatClient = class {
|
|
|
1136
1150
|
this.syncSubagentHandles();
|
|
1137
1151
|
this.updateRunLifecycle(chunk);
|
|
1138
1152
|
this.observeInterruptState(chunk);
|
|
1139
|
-
if (options?.defer !== false && (typeof document === "undefined" || !document.hidden)) await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1140
1153
|
this.resolveJoinedRun(chunk);
|
|
1141
1154
|
}
|
|
1142
1155
|
isActiveInterruptSubmissionFailure(chunk) {
|
|
@@ -1591,10 +1604,12 @@ var ChatClient = class {
|
|
|
1591
1604
|
await this.addToolResultForClientTool(result, clientTool, this.streamContinuationGeneration);
|
|
1592
1605
|
}
|
|
1593
1606
|
async addToolResultForClientTool(result, clientTool, continuationGeneration, context) {
|
|
1594
|
-
if (
|
|
1607
|
+
if (result.state !== "output-error") try {
|
|
1608
|
+
let output = clientTool?.outputSchema && isStandardSchema(clientTool.outputSchema) ? await this.validateClientToolOutput(clientTool, result.output) : result.output;
|
|
1609
|
+
if (this.interruptManager.getDescriptors().some((interrupt) => interrupt.toolCallId === result.toolCallId)) output = cloneAndDeepFreezeJson(output);
|
|
1595
1610
|
result = {
|
|
1596
1611
|
...result,
|
|
1597
|
-
output
|
|
1612
|
+
output
|
|
1598
1613
|
};
|
|
1599
1614
|
} catch (error) {
|
|
1600
1615
|
result = {
|
|
@@ -1608,16 +1623,17 @@ var ChatClient = class {
|
|
|
1608
1623
|
if (continuationGeneration !== this.continuationGeneration) return;
|
|
1609
1624
|
this.processor.addToolResult(result.toolCallId, result.output, result.state === "output-error" ? result.errorText || "Tool execution failed" : void 0);
|
|
1610
1625
|
this.devtoolsBridge.emitSnapshot();
|
|
1611
|
-
if (
|
|
1626
|
+
if (result.state === "output-error" ? this.interruptManager.resolveClientToolError(result.toolCallId, result.errorText || "Tool execution failed") : this.interruptManager.resolveClientToolOutput(result.toolCallId, result.output)) return;
|
|
1612
1627
|
if (this.isLoading) {
|
|
1613
1628
|
this.queuePostStreamAction(() => continuationGeneration === this.continuationGeneration ? this.checkForContinuation() : Promise.resolve());
|
|
1614
1629
|
return;
|
|
1615
1630
|
}
|
|
1616
1631
|
await this.checkForContinuation();
|
|
1617
1632
|
}
|
|
1618
|
-
validateClientToolOutput(clientTool, output) {
|
|
1619
|
-
|
|
1620
|
-
|
|
1633
|
+
async validateClientToolOutput(clientTool, output) {
|
|
1634
|
+
const validation = await validateWithStandardSchema(clientTool.outputSchema, output);
|
|
1635
|
+
if (!validation.success) throw new Error(validation.issues.map((issue) => issue.message).join(", "));
|
|
1636
|
+
return validation.data;
|
|
1621
1637
|
}
|
|
1622
1638
|
/**
|
|
1623
1639
|
* Respond to a tool approval request
|