@tanstack/ai 0.45.0 → 0.46.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 +10 -1
- package/dist/esm/activities/chat/index.js +55 -5
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/tools/unique-tool-names.d.ts +20 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js +57 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js.map +1 -0
- package/dist/esm/adapter-internals.d.ts +1 -0
- package/dist/esm/adapter-internals.js +2 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +4 -2
- package/dist/esm/middlewares/usage-attributes.d.ts +2 -2
- package/dist/esm/middlewares/usage-attributes.js +9 -2
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/stream-to-response.d.ts +26 -0
- package/dist/esm/stream-to-response.js +1 -1
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/stream-to-websocket.d.ts +123 -0
- package/dist/esm/stream-to-websocket.js +249 -0
- package/dist/esm/stream-to-websocket.js.map +1 -0
- package/dist/esm/types.d.ts +8 -7
- package/package.json +2 -2
- package/skills/ai-core/media-generation/SKILL.md +9 -8
- package/src/activities/chat/index.ts +96 -13
- package/src/activities/chat/tools/unique-tool-names.ts +73 -0
- package/src/adapter-internals.ts +4 -0
- package/src/index.ts +17 -0
- package/src/middlewares/usage-attributes.ts +12 -2
- package/src/stream-to-response.ts +2 -2
- package/src/stream-to-websocket.ts +418 -0
- package/src/types.ts +10 -5
package/README.md
CHANGED
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
</a>
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
|
+
<br />
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
<a href="https://tanstack.com/blog/tanstack-open-source-awards-2026">
|
|
43
|
+
<img src="https://raw.githubusercontent.com/TanStack/ai/16826d81cade868956df240d6239a671e689193c/media/js-open-source-award-2026-ai-project-of-the-year.svg" alt="Winner of the 2026 JavaScript Open Source Award for AI Project of the Year" width="250" />
|
|
44
|
+
</a>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
39
47
|
# TanStack AI
|
|
40
48
|
|
|
41
49
|
Type-safe, provider-agnostic TypeScript SDK for building streaming chat,
|
|
@@ -191,7 +199,7 @@ Learn more in the
|
|
|
191
199
|
build low-latency realtime voice experiences.
|
|
192
200
|
- [Code Mode](https://tanstack.com/ai/latest/docs/code-mode/code-mode) - let
|
|
193
201
|
models write and execute TypeScript inside a secure isolate.
|
|
194
|
-
- [Code Mode with
|
|
202
|
+
- [Code Mode with Snippets](https://tanstack.com/ai/latest/docs/code-mode/code-mode-with-snippets) -
|
|
195
203
|
give Code Mode reusable runtime capabilities.
|
|
196
204
|
|
|
197
205
|
## Providers
|
|
@@ -208,6 +216,7 @@ Official adapters include:
|
|
|
208
216
|
| [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
|
|
209
217
|
| [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
|
|
210
218
|
| [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
|
|
219
|
+
| [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
|
|
211
220
|
| [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
|
|
212
221
|
|
|
213
222
|
The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
|
|
@@ -15,9 +15,10 @@ import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readUno
|
|
|
15
15
|
import { normalizeToolResult } from "../../utilities/tool-result.js";
|
|
16
16
|
import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
|
|
17
17
|
import { LazyToolManager } from "./tools/lazy-tool-manager.js";
|
|
18
|
+
import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
|
|
18
19
|
import { MiddlewareAbortError, ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
|
|
19
20
|
import { maxIterations } from "./agent-loop-strategies.js";
|
|
20
|
-
import { convertMessagesToModelMessages, generateMessageId } from "./messages.js";
|
|
21
|
+
import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage } from "./messages.js";
|
|
21
22
|
import { MiddlewareRunner } from "./middleware/compose.js";
|
|
22
23
|
import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
|
|
23
24
|
import { provideRunDisconnect } from "./middleware/run-disconnect.js";
|
|
@@ -148,9 +149,11 @@ var TextEngine = class {
|
|
|
148
149
|
accumulatedThinking = [];
|
|
149
150
|
currentThinkingContent = "";
|
|
150
151
|
currentThinkingSignature = "";
|
|
152
|
+
hasSeenReasoningEvents = false;
|
|
151
153
|
eventOptions;
|
|
152
154
|
eventToolNames;
|
|
153
155
|
finishedEvent = null;
|
|
156
|
+
streamedToolErrorResults = /* @__PURE__ */ new Map();
|
|
154
157
|
deferredToolCallRunFinishedChunks = [];
|
|
155
158
|
earlyTermination = false;
|
|
156
159
|
toolPhase = "continue";
|
|
@@ -201,6 +204,7 @@ var TextEngine = class {
|
|
|
201
204
|
this.initialApprovals = approvals;
|
|
202
205
|
this.initialClientToolResults = clientToolResults;
|
|
203
206
|
this.messages = convertMessagesToModelMessages(config.params.messages);
|
|
207
|
+
assertUniqueToolNames(config.params.tools || []);
|
|
204
208
|
this.lazyToolManager = new LazyToolManager(config.params.tools || [], this.messages, config.params.lazyToolsConfig);
|
|
205
209
|
this.tools = this.lazyToolManager.getActiveTools();
|
|
206
210
|
this.toolCallManager = new ToolCallManager(this.tools);
|
|
@@ -354,6 +358,7 @@ var TextEngine = class {
|
|
|
354
358
|
duration: Date.now() - this.streamStartTime
|
|
355
359
|
});
|
|
356
360
|
} else {
|
|
361
|
+
this.addTerminalReasoningMessage();
|
|
357
362
|
this.terminalHookCalled = true;
|
|
358
363
|
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
359
364
|
finishReason: this.lastFinishReason,
|
|
@@ -457,7 +462,9 @@ var TextEngine = class {
|
|
|
457
462
|
this.accumulatedThinking = [];
|
|
458
463
|
this.currentThinkingContent = "";
|
|
459
464
|
this.currentThinkingSignature = "";
|
|
465
|
+
this.hasSeenReasoningEvents = false;
|
|
460
466
|
this.finishedEvent = null;
|
|
467
|
+
this.streamedToolErrorResults.clear();
|
|
461
468
|
this.middlewareCtx.currentMessageId = this.currentMessageId;
|
|
462
469
|
this.middlewareCtx.accumulatedContent = "";
|
|
463
470
|
await this.middlewareRunner.runOnIteration(this.middlewareCtx, {
|
|
@@ -591,7 +598,10 @@ var TextEngine = class {
|
|
|
591
598
|
case "STEP_STARTED":
|
|
592
599
|
this.handleStepStartedEvent();
|
|
593
600
|
break;
|
|
594
|
-
case "STEP_FINISHED":
|
|
601
|
+
case "STEP_FINISHED":
|
|
602
|
+
this.handleStepFinishedEvent(chunk);
|
|
603
|
+
break;
|
|
604
|
+
case "REASONING_MESSAGE_CONTENT": this.handleReasoningMessageContentEvent(chunk);
|
|
595
605
|
}
|
|
596
606
|
}
|
|
597
607
|
handleTextMessageContentEvent(chunk) {
|
|
@@ -616,6 +626,16 @@ var TextEngine = class {
|
|
|
616
626
|
}
|
|
617
627
|
handleToolCallEndEvent(chunk) {
|
|
618
628
|
this.toolCallManager.completeToolCall(chunk);
|
|
629
|
+
if (chunk.state !== "output-error" || chunk.result === void 0) return;
|
|
630
|
+
const toolCall = this.toolCallManager.getToolCalls().find((candidate) => candidate.id === chunk.toolCallId);
|
|
631
|
+
if (!toolCall) return;
|
|
632
|
+
this.streamedToolErrorResults.set(chunk.toolCallId, {
|
|
633
|
+
toolCallId: chunk.toolCallId,
|
|
634
|
+
toolName: toolCall.function.name,
|
|
635
|
+
result: chunk.result,
|
|
636
|
+
...chunk.input !== void 0 && { input: chunk.input },
|
|
637
|
+
state: "output-error"
|
|
638
|
+
});
|
|
619
639
|
}
|
|
620
640
|
handleRunFinishedEvent(chunk) {
|
|
621
641
|
this.finishedEvent = chunk;
|
|
@@ -645,9 +665,19 @@ var TextEngine = class {
|
|
|
645
665
|
this.finalizeCurrentThinkingStep();
|
|
646
666
|
}
|
|
647
667
|
handleStepFinishedEvent(chunk) {
|
|
648
|
-
if (
|
|
668
|
+
if (!this.hasSeenReasoningEvents) {
|
|
669
|
+
if (chunk.delta) this.currentThinkingContent += chunk.delta;
|
|
670
|
+
else if (chunk.content) {
|
|
671
|
+
if (chunk.content.startsWith(this.currentThinkingContent)) this.currentThinkingContent = chunk.content;
|
|
672
|
+
else if (!this.currentThinkingContent.startsWith(chunk.content)) this.currentThinkingContent += chunk.content;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
649
675
|
if (chunk.signature) this.currentThinkingSignature = chunk.signature;
|
|
650
676
|
}
|
|
677
|
+
handleReasoningMessageContentEvent(chunk) {
|
|
678
|
+
this.hasSeenReasoningEvents = true;
|
|
679
|
+
this.currentThinkingContent += chunk.delta;
|
|
680
|
+
}
|
|
651
681
|
/**
|
|
652
682
|
* Tools available for execution this turn. The discovery tool is dropped
|
|
653
683
|
* from the advertised set (`this.tools`) once every lazy tool is discovered,
|
|
@@ -751,6 +781,7 @@ var TextEngine = class {
|
|
|
751
781
|
this.addAssistantToolCallMessage(toolCalls);
|
|
752
782
|
const undiscoveredLazyResults = [];
|
|
753
783
|
const executableToolCalls = toolCalls.filter((tc) => {
|
|
784
|
+
if (this.streamedToolErrorResults.has(tc.id)) return false;
|
|
754
785
|
if (this.lazyToolManager.isUndiscoveredLazyTool(tc.function.name)) {
|
|
755
786
|
undiscoveredLazyResults.push({
|
|
756
787
|
toolCallId: tc.id,
|
|
@@ -762,7 +793,7 @@ var TextEngine = class {
|
|
|
762
793
|
}
|
|
763
794
|
return true;
|
|
764
795
|
});
|
|
765
|
-
const deferredErrorResults = [...undiscoveredLazyResults];
|
|
796
|
+
const deferredErrorResults = [...this.streamedToolErrorResults.values(), ...undiscoveredLazyResults];
|
|
766
797
|
if (executableToolCalls.length === 0) {
|
|
767
798
|
yield* this.flushDeferredToolCallRunFinishedChunks();
|
|
768
799
|
if (deferredErrorResults.length > 0) for (const chunk of this.buildToolResultChunks(deferredErrorResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
|
|
@@ -858,6 +889,20 @@ var TextEngine = class {
|
|
|
858
889
|
}];
|
|
859
890
|
this.middlewareCtx.messages = this.messages;
|
|
860
891
|
}
|
|
892
|
+
addTerminalReasoningMessage() {
|
|
893
|
+
this.finalizeCurrentThinkingStep();
|
|
894
|
+
if (this.accumulatedThinking.length === 0) return;
|
|
895
|
+
const messages = this.middlewareCtx.messages;
|
|
896
|
+
if (messages.some((message) => message.role === "assistant" && message.id === this.currentMessageId)) return;
|
|
897
|
+
this.messages = [...messages, {
|
|
898
|
+
role: "assistant",
|
|
899
|
+
content: this.accumulatedContent || null,
|
|
900
|
+
id: this.currentMessageId ?? void 0,
|
|
901
|
+
createdAt: this.currentMessageCreatedAt ?? void 0,
|
|
902
|
+
thinking: this.accumulatedThinking
|
|
903
|
+
}];
|
|
904
|
+
this.middlewareCtx.messages = this.messages;
|
|
905
|
+
}
|
|
861
906
|
/**
|
|
862
907
|
* Extract client state (approvals and client tool results) from original messages.
|
|
863
908
|
* This is called in the constructor BEFORE converting to ModelMessage format,
|
|
@@ -967,10 +1012,13 @@ var TextEngine = class {
|
|
|
967
1012
|
buildMessagesSnapshotChunk() {
|
|
968
1013
|
const messages = this.messages.map((message, index) => {
|
|
969
1014
|
const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
|
|
1015
|
+
const id = message.id || `snapshot_${this.runIdOverride ?? this.requestId}_${index}`;
|
|
1016
|
+
const parts = message.role === "assistant" && message.thinking?.length ? modelMessageToUIMessage(message, id).parts : void 0;
|
|
970
1017
|
return {
|
|
971
|
-
id
|
|
1018
|
+
id,
|
|
972
1019
|
role: message.role,
|
|
973
1020
|
...content !== void 0 ? { content } : {},
|
|
1021
|
+
...parts ? { parts } : {},
|
|
974
1022
|
..."toolCalls" in message && message.toolCalls ? { toolCalls: message.toolCalls } : {},
|
|
975
1023
|
..."toolCallId" in message && message.toolCallId ? { toolCallId: message.toolCallId } : {}
|
|
976
1024
|
};
|
|
@@ -1742,6 +1790,7 @@ var TextEngine = class {
|
|
|
1742
1790
|
this.applyResumeToolState(config.resumeToolState);
|
|
1743
1791
|
this.messages = config.messages;
|
|
1744
1792
|
this.systemPrompts = config.systemPrompts;
|
|
1793
|
+
assertUniqueToolNames(config.tools);
|
|
1745
1794
|
this.tools = config.tools;
|
|
1746
1795
|
this.params = {
|
|
1747
1796
|
...this.params,
|
|
@@ -1866,6 +1915,7 @@ var TextEngine = class {
|
|
|
1866
1915
|
*/
|
|
1867
1916
|
function chat(options) {
|
|
1868
1917
|
validateCapabilities(options.middleware ?? [], options.adapter);
|
|
1918
|
+
if (options.tools) assertUniqueToolNames(options.tools);
|
|
1869
1919
|
const { outputSchema, stream } = options;
|
|
1870
1920
|
if (outputSchema && stream === true) return runStreamingStructuredOutput({
|
|
1871
1921
|
...options,
|