@tanstack/ai-client 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tanner Linsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ <div align="center">
2
+ <img src="./media/header_ai.png" >
3
+ </div>
4
+
5
+ <br />
6
+
7
+ <div align="center">
8
+ <a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
9
+ <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
10
+ </a>
11
+ <a href="https://github.com/TanStack/ai" target="\_parent">
12
+ <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
13
+ </a>
14
+ <a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
15
+ <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
16
+ </a>
17
+ </div>
18
+
19
+ <div align="center">
20
+ <a href="#badge">
21
+ <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
22
+ </a>
23
+ <a href="#badge">
24
+ <img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
25
+ </a>
26
+ <a href="https://twitter.com/tan_stack">
27
+ <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
28
+ </a>
29
+ </div>
30
+
31
+ <div align="center">
32
+
33
+ ### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
34
+ </div>
35
+
36
+ # TanStack AI
37
+
38
+ A powerful, type-safe AI SDK for building AI-powered applications.
39
+
40
+ - Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
41
+ - **Multimodal content support** - Send images, audio, video, and documents
42
+ - Chat completion, streaming, and agent loop strategies
43
+ - Headless chat state management with adapters (SSE, HTTP stream, custom)
44
+ - Isomorphic type-safe tools with server/client execution
45
+ - **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions
46
+
47
+ ### <a href="https://tanstack.com/ai">Read the docs →</b></a>
48
+
49
+ ## Bonus: TanStack Start Integration
50
+
51
+ TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).
52
+
53
+ **With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:
54
+
55
+ ```typescript
56
+ import { createServerFnTool } from '@tanstack/ai-react'
57
+
58
+ // Define once, get AI tool AND server function (TanStack Start only)
59
+ const getProducts = createServerFnTool({
60
+ name: 'getProducts',
61
+ inputSchema: z.object({ query: z.string() }),
62
+ execute: async ({ query }) => db.products.search(query),
63
+ })
64
+
65
+ // Use in AI chat
66
+ chat({ tools: [getProducts.server] })
67
+
68
+ // Call directly from components (no API endpoint needed!)
69
+ const products = await getProducts.serverFn({ query: 'laptop' })
70
+ ```
71
+
72
+ No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.
73
+
74
+ ## Get Involved
75
+
76
+ - We welcome issues and pull requests!
77
+ - Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
78
+ - Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
79
+ - See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions
80
+
81
+ ## Partners
82
+
83
+ <table align="center">
84
+ <tr>
85
+ <td>
86
+ <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
87
+ <picture>
88
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
89
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
90
+ <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
91
+ </picture>
92
+ </a>
93
+ </td>
94
+ <td>
95
+ <a href="https://www.cloudflare.com?utm_source=tanstack">
96
+ <picture>
97
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
98
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
99
+ <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
100
+ </picture>
101
+ </a>
102
+ </td>
103
+ </tr>
104
+ </table>
105
+
106
+ <div align="center">
107
+ <img src="./media/partner_logo.svg" alt="AI & you?" height="65">
108
+ <p>
109
+ We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
110
+ </p>
111
+ <a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
112
+ </div>
113
+
114
+ ## Explore the TanStack Ecosystem
115
+
116
+ - <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
117
+ - <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
118
+ - <a href="https://github.com/tanstack/devtools">TanStack Devtools</a> – Unified devtools panel
119
+ - <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
120
+ - <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
121
+ - <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
122
+ - <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
123
+ - <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
124
+ - <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
125
+ - <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
126
+ - <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
127
+ - <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering
128
+
129
+ … and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>
130
+
131
+ <!-- USE THE FORCE LUKE -->
@@ -0,0 +1,102 @@
1
+ import { ChatClientOptions, UIMessage } from './types.js';
2
+ import { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai';
3
+ import { ConnectionAdapter } from './connection-adapters.js';
4
+ export declare class ChatClient {
5
+ private processor;
6
+ private connection;
7
+ private uniqueId;
8
+ private body;
9
+ private isLoading;
10
+ private error;
11
+ private abortController;
12
+ private events;
13
+ private clientToolsRef;
14
+ private currentStreamId;
15
+ private currentMessageId;
16
+ private callbacksRef;
17
+ constructor(options: ChatClientOptions);
18
+ private generateUniqueId;
19
+ private setIsLoading;
20
+ private setError;
21
+ /**
22
+ * Process a stream through the StreamProcessor
23
+ */
24
+ private processStream;
25
+ /**
26
+ * Send a message and stream the response
27
+ */
28
+ sendMessage(content: string): Promise<void>;
29
+ /**
30
+ * Append a message and stream the response
31
+ */
32
+ append(message: UIMessage | ModelMessage): Promise<void>;
33
+ /**
34
+ * Stream a response from the LLM
35
+ */
36
+ private streamResponse;
37
+ /**
38
+ * Reload the last assistant message
39
+ */
40
+ reload(): Promise<void>;
41
+ /**
42
+ * Stop the current stream
43
+ */
44
+ stop(): void;
45
+ /**
46
+ * Clear all messages
47
+ */
48
+ clear(): void;
49
+ /**
50
+ * Add the result of a client-side tool execution
51
+ */
52
+ addToolResult(result: {
53
+ toolCallId: string;
54
+ tool: string;
55
+ output: any;
56
+ state?: 'output-available' | 'output-error';
57
+ errorText?: string;
58
+ }): Promise<void>;
59
+ /**
60
+ * Respond to a tool approval request
61
+ */
62
+ addToolApprovalResponse(response: {
63
+ id: string;
64
+ approved: boolean;
65
+ }): Promise<void>;
66
+ /**
67
+ * Continue the agent flow with current messages
68
+ */
69
+ private continueFlow;
70
+ /**
71
+ * Check if all tool calls are complete and we should auto-send
72
+ */
73
+ private shouldAutoSend;
74
+ /**
75
+ * Get current messages
76
+ */
77
+ getMessages(): Array<UIMessage>;
78
+ /**
79
+ * Get loading state
80
+ */
81
+ getIsLoading(): boolean;
82
+ /**
83
+ * Get current error
84
+ */
85
+ getError(): Error | undefined;
86
+ /**
87
+ * Manually set messages
88
+ */
89
+ setMessagesManually(messages: Array<UIMessage>): void;
90
+ /**
91
+ * Update options refs (for use in React hooks to avoid recreating client)
92
+ */
93
+ updateOptions(options: {
94
+ connection?: ConnectionAdapter;
95
+ body?: Record<string, any>;
96
+ tools?: ReadonlyArray<AnyClientTool>;
97
+ onResponse?: (response?: Response) => void | Promise<void>;
98
+ onChunk?: (chunk: StreamChunk) => void;
99
+ onFinish?: (message: UIMessage) => void;
100
+ onError?: (error: Error) => void;
101
+ }): void;
102
+ }
@@ -0,0 +1,375 @@
1
+ import { StreamProcessor, normalizeToUIMessage, generateMessageId } from "@tanstack/ai";
2
+ import { DefaultChatClientEventEmitter } from "./events.js";
3
+ class ChatClient {
4
+ constructor(options) {
5
+ this.body = {};
6
+ this.isLoading = false;
7
+ this.error = void 0;
8
+ this.abortController = null;
9
+ this.currentStreamId = null;
10
+ this.currentMessageId = null;
11
+ this.uniqueId = options.id || this.generateUniqueId("chat");
12
+ this.body = options.body || {};
13
+ this.connection = options.connection;
14
+ this.events = new DefaultChatClientEventEmitter(this.uniqueId);
15
+ this.clientToolsRef = { current: /* @__PURE__ */ new Map() };
16
+ if (options.tools) {
17
+ for (const tool of options.tools) {
18
+ this.clientToolsRef.current.set(tool.name, tool);
19
+ }
20
+ }
21
+ this.callbacksRef = {
22
+ current: {
23
+ onResponse: options.onResponse || (() => {
24
+ }),
25
+ onChunk: options.onChunk || (() => {
26
+ }),
27
+ onFinish: options.onFinish || (() => {
28
+ }),
29
+ onError: options.onError || (() => {
30
+ }),
31
+ onMessagesChange: options.onMessagesChange || (() => {
32
+ }),
33
+ onLoadingChange: options.onLoadingChange || (() => {
34
+ }),
35
+ onErrorChange: options.onErrorChange || (() => {
36
+ })
37
+ }
38
+ };
39
+ this.processor = new StreamProcessor({
40
+ chunkStrategy: options.streamProcessor?.chunkStrategy,
41
+ initialMessages: options.initialMessages,
42
+ events: {
43
+ onMessagesChange: (messages) => {
44
+ this.callbacksRef.current.onMessagesChange(messages);
45
+ },
46
+ onStreamStart: () => {
47
+ },
48
+ onStreamEnd: (message) => {
49
+ this.callbacksRef.current.onFinish(message);
50
+ },
51
+ onError: (error) => {
52
+ this.setError(error);
53
+ this.callbacksRef.current.onError(error);
54
+ },
55
+ onTextUpdate: (messageId, content) => {
56
+ if (this.currentStreamId) {
57
+ this.events.textUpdated(this.currentStreamId, messageId, content);
58
+ }
59
+ },
60
+ onThinkingUpdate: (messageId, content) => {
61
+ if (this.currentStreamId) {
62
+ this.events.thinkingUpdated(
63
+ this.currentStreamId,
64
+ messageId,
65
+ content
66
+ );
67
+ }
68
+ },
69
+ onToolCallStateChange: (messageId, toolCallId, state, args) => {
70
+ const messages = this.processor.getMessages();
71
+ const message = messages.find((m) => m.id === messageId);
72
+ const toolCallPart = message?.parts.find(
73
+ (p) => p.type === "tool-call" && p.id === toolCallId
74
+ );
75
+ const toolName = toolCallPart?.name || "unknown";
76
+ if (this.currentStreamId) {
77
+ this.events.toolCallStateChanged(
78
+ this.currentStreamId,
79
+ messageId,
80
+ toolCallId,
81
+ toolName,
82
+ state,
83
+ args
84
+ );
85
+ }
86
+ },
87
+ onToolCall: async (args) => {
88
+ const clientTool = this.clientToolsRef.current.get(args.toolName);
89
+ if (clientTool?.execute) {
90
+ try {
91
+ const output = await clientTool.execute(args.input);
92
+ await this.addToolResult({
93
+ toolCallId: args.toolCallId,
94
+ tool: args.toolName,
95
+ output,
96
+ state: "output-available"
97
+ });
98
+ } catch (error) {
99
+ await this.addToolResult({
100
+ toolCallId: args.toolCallId,
101
+ tool: args.toolName,
102
+ output: null,
103
+ state: "output-error",
104
+ errorText: error.message
105
+ });
106
+ }
107
+ }
108
+ },
109
+ onApprovalRequest: (args) => {
110
+ this.events.approvalRequested(
111
+ this.currentMessageId || "",
112
+ args.toolCallId,
113
+ args.toolName,
114
+ args.input,
115
+ args.approvalId
116
+ );
117
+ }
118
+ }
119
+ });
120
+ this.events.clientCreated(this.processor.getMessages().length);
121
+ }
122
+ generateUniqueId(prefix) {
123
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
124
+ }
125
+ setIsLoading(isLoading) {
126
+ this.isLoading = isLoading;
127
+ this.callbacksRef.current.onLoadingChange(isLoading);
128
+ this.events.loadingChanged(isLoading);
129
+ }
130
+ setError(error) {
131
+ this.error = error;
132
+ this.callbacksRef.current.onErrorChange(error);
133
+ this.events.errorChanged(error?.message || null);
134
+ }
135
+ /**
136
+ * Process a stream through the StreamProcessor
137
+ */
138
+ async processStream(source) {
139
+ this.currentStreamId = this.generateUniqueId("stream");
140
+ const messageId = this.processor.startAssistantMessage();
141
+ this.currentMessageId = messageId;
142
+ const assistantMessage = {
143
+ id: messageId,
144
+ role: "assistant",
145
+ parts: [],
146
+ createdAt: /* @__PURE__ */ new Date()
147
+ };
148
+ this.events.messageAppended(assistantMessage);
149
+ for await (const chunk of source) {
150
+ this.callbacksRef.current.onChunk(chunk);
151
+ this.processor.processChunk(chunk);
152
+ await new Promise((resolve) => setTimeout(resolve, 0));
153
+ }
154
+ this.processor.finalizeStream();
155
+ this.currentStreamId = null;
156
+ this.currentMessageId = null;
157
+ const messages = this.processor.getMessages();
158
+ const finalAssistantMessage = messages.find(
159
+ (m) => m.id === messageId
160
+ );
161
+ return finalAssistantMessage || {
162
+ id: messageId,
163
+ role: "assistant",
164
+ parts: [],
165
+ createdAt: /* @__PURE__ */ new Date()
166
+ };
167
+ }
168
+ /**
169
+ * Send a message and stream the response
170
+ */
171
+ async sendMessage(content) {
172
+ if (!content.trim() || this.isLoading) {
173
+ return;
174
+ }
175
+ const userMessage = this.processor.addUserMessage(content.trim());
176
+ this.events.messageSent(userMessage.id, content.trim());
177
+ await this.streamResponse();
178
+ }
179
+ /**
180
+ * Append a message and stream the response
181
+ */
182
+ async append(message) {
183
+ const normalizedMessage = normalizeToUIMessage(message, generateMessageId);
184
+ if (normalizedMessage.role === "system") {
185
+ return;
186
+ }
187
+ const uiMessage = normalizedMessage;
188
+ this.events.messageAppended(uiMessage);
189
+ const messages = this.processor.getMessages();
190
+ this.processor.setMessages([...messages, uiMessage]);
191
+ await this.streamResponse();
192
+ }
193
+ /**
194
+ * Stream a response from the LLM
195
+ */
196
+ async streamResponse() {
197
+ this.setIsLoading(true);
198
+ this.setError(void 0);
199
+ this.abortController = new AbortController();
200
+ try {
201
+ const modelMessages = this.processor.toModelMessages();
202
+ await this.callbacksRef.current.onResponse();
203
+ const bodyWithConversationId = {
204
+ ...this.body,
205
+ conversationId: this.uniqueId
206
+ };
207
+ const stream = this.connection.connect(
208
+ modelMessages,
209
+ bodyWithConversationId,
210
+ this.abortController.signal
211
+ );
212
+ await this.processStream(stream);
213
+ } catch (err) {
214
+ if (err instanceof Error) {
215
+ if (err.name === "AbortError") {
216
+ return;
217
+ }
218
+ this.setError(err);
219
+ this.callbacksRef.current.onError(err);
220
+ }
221
+ } finally {
222
+ this.abortController = null;
223
+ this.setIsLoading(false);
224
+ }
225
+ }
226
+ /**
227
+ * Reload the last assistant message
228
+ */
229
+ async reload() {
230
+ const messages = this.processor.getMessages();
231
+ if (messages.length === 0) return;
232
+ const lastUserMessageIndex = messages.findLastIndex(
233
+ (m) => m.role === "user"
234
+ );
235
+ if (lastUserMessageIndex === -1) return;
236
+ this.events.reloaded(lastUserMessageIndex);
237
+ this.processor.removeMessagesAfter(lastUserMessageIndex);
238
+ await this.streamResponse();
239
+ }
240
+ /**
241
+ * Stop the current stream
242
+ */
243
+ stop() {
244
+ if (this.abortController) {
245
+ this.abortController.abort();
246
+ this.abortController = null;
247
+ }
248
+ this.setIsLoading(false);
249
+ this.events.stopped();
250
+ }
251
+ /**
252
+ * Clear all messages
253
+ */
254
+ clear() {
255
+ this.processor.clearMessages();
256
+ this.setError(void 0);
257
+ this.events.messagesCleared();
258
+ }
259
+ /**
260
+ * Add the result of a client-side tool execution
261
+ */
262
+ async addToolResult(result) {
263
+ this.events.toolResultAdded(
264
+ result.toolCallId,
265
+ result.tool,
266
+ result.output,
267
+ result.state || "output-available"
268
+ );
269
+ this.processor.addToolResult(
270
+ result.toolCallId,
271
+ result.output,
272
+ result.errorText
273
+ );
274
+ if (this.shouldAutoSend()) {
275
+ await this.continueFlow();
276
+ }
277
+ }
278
+ /**
279
+ * Respond to a tool approval request
280
+ */
281
+ async addToolApprovalResponse(response) {
282
+ const messages = this.processor.getMessages();
283
+ let foundToolCallId;
284
+ for (const msg of messages) {
285
+ const toolCallPart = msg.parts.find(
286
+ (p) => p.type === "tool-call" && p.approval?.id === response.id
287
+ );
288
+ if (toolCallPart) {
289
+ foundToolCallId = toolCallPart.id;
290
+ break;
291
+ }
292
+ }
293
+ if (foundToolCallId) {
294
+ this.events.toolApprovalResponded(
295
+ response.id,
296
+ foundToolCallId,
297
+ response.approved
298
+ );
299
+ }
300
+ this.processor.addToolApprovalResponse(response.id, response.approved);
301
+ if (this.shouldAutoSend()) {
302
+ await this.continueFlow();
303
+ }
304
+ }
305
+ /**
306
+ * Continue the agent flow with current messages
307
+ */
308
+ async continueFlow() {
309
+ if (this.isLoading) return;
310
+ await this.streamResponse();
311
+ }
312
+ /**
313
+ * Check if all tool calls are complete and we should auto-send
314
+ */
315
+ shouldAutoSend() {
316
+ return this.processor.areAllToolsComplete();
317
+ }
318
+ /**
319
+ * Get current messages
320
+ */
321
+ getMessages() {
322
+ return this.processor.getMessages();
323
+ }
324
+ /**
325
+ * Get loading state
326
+ */
327
+ getIsLoading() {
328
+ return this.isLoading;
329
+ }
330
+ /**
331
+ * Get current error
332
+ */
333
+ getError() {
334
+ return this.error;
335
+ }
336
+ /**
337
+ * Manually set messages
338
+ */
339
+ setMessagesManually(messages) {
340
+ this.processor.setMessages(messages);
341
+ }
342
+ /**
343
+ * Update options refs (for use in React hooks to avoid recreating client)
344
+ */
345
+ updateOptions(options) {
346
+ if (options.connection !== void 0) {
347
+ this.connection = options.connection;
348
+ }
349
+ if (options.body !== void 0) {
350
+ this.body = options.body;
351
+ }
352
+ if (options.tools !== void 0) {
353
+ this.clientToolsRef.current = /* @__PURE__ */ new Map();
354
+ for (const tool of options.tools) {
355
+ this.clientToolsRef.current.set(tool.name, tool);
356
+ }
357
+ }
358
+ if (options.onResponse !== void 0) {
359
+ this.callbacksRef.current.onResponse = options.onResponse;
360
+ }
361
+ if (options.onChunk !== void 0) {
362
+ this.callbacksRef.current.onChunk = options.onChunk;
363
+ }
364
+ if (options.onFinish !== void 0) {
365
+ this.callbacksRef.current.onFinish = options.onFinish;
366
+ }
367
+ if (options.onError !== void 0) {
368
+ this.callbacksRef.current.onError = options.onError;
369
+ }
370
+ }
371
+ }
372
+ export {
373
+ ChatClient
374
+ };
375
+ //# sourceMappingURL=chat-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-client.js","sources":["../../src/chat-client.ts"],"sourcesContent":["import {\n StreamProcessor,\n generateMessageId,\n normalizeToUIMessage,\n} from '@tanstack/ai'\nimport { DefaultChatClientEventEmitter } from './events'\nimport type {\n ChatClientOptions,\n MessagePart,\n ToolCallPart,\n UIMessage,\n} from './types'\nimport type { AnyClientTool, ModelMessage, StreamChunk } from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\nimport type { ChatClientEventEmitter } from './events'\n\nexport class ChatClient {\n private processor: StreamProcessor\n private connection: ConnectionAdapter\n private uniqueId: string\n private body: Record<string, any> = {}\n private isLoading = false\n private error: Error | undefined = undefined\n private abortController: AbortController | null = null\n private events: ChatClientEventEmitter\n private clientToolsRef: { current: Map<string, AnyClientTool> }\n private currentStreamId: string | null = null\n private currentMessageId: string | null = null\n\n private callbacksRef: {\n current: {\n onResponse: (response?: Response) => void | Promise<void>\n onChunk: (chunk: StreamChunk) => void\n onFinish: (message: UIMessage) => void\n onError: (error: Error) => void\n onMessagesChange: (messages: Array<UIMessage>) => void\n onLoadingChange: (isLoading: boolean) => void\n onErrorChange: (error: Error | undefined) => void\n }\n }\n\n constructor(options: ChatClientOptions) {\n this.uniqueId = options.id || this.generateUniqueId('chat')\n this.body = options.body || {}\n this.connection = options.connection\n this.events = new DefaultChatClientEventEmitter(this.uniqueId)\n\n // Build client tools map\n this.clientToolsRef = { current: new Map() }\n if (options.tools) {\n for (const tool of options.tools) {\n this.clientToolsRef.current.set(tool.name, tool)\n }\n }\n\n this.callbacksRef = {\n current: {\n onResponse: options.onResponse || (() => {}),\n onChunk: options.onChunk || (() => {}),\n onFinish: options.onFinish || (() => {}),\n onError: options.onError || (() => {}),\n onMessagesChange: options.onMessagesChange || (() => {}),\n onLoadingChange: options.onLoadingChange || (() => {}),\n onErrorChange: options.onErrorChange || (() => {}),\n },\n }\n\n // Create StreamProcessor with event handlers\n this.processor = new StreamProcessor({\n chunkStrategy: options.streamProcessor?.chunkStrategy,\n initialMessages: options.initialMessages,\n events: {\n onMessagesChange: (messages: Array<UIMessage>) => {\n this.callbacksRef.current.onMessagesChange(messages)\n },\n onStreamStart: () => {\n // Stream started\n },\n onStreamEnd: (message: UIMessage) => {\n this.callbacksRef.current.onFinish(message)\n },\n onError: (error: Error) => {\n this.setError(error)\n this.callbacksRef.current.onError(error)\n },\n onTextUpdate: (messageId: string, content: string) => {\n // Emit text update to devtools\n if (this.currentStreamId) {\n this.events.textUpdated(this.currentStreamId, messageId, content)\n }\n },\n onThinkingUpdate: (messageId: string, content: string) => {\n // Emit thinking update to devtools\n if (this.currentStreamId) {\n this.events.thinkingUpdated(\n this.currentStreamId,\n messageId,\n content,\n )\n }\n },\n onToolCallStateChange: (\n messageId: string,\n toolCallId: string,\n state: string,\n args: string,\n ) => {\n // Get the tool name from the messages\n const messages = this.processor.getMessages()\n const message = messages.find((m: UIMessage) => m.id === messageId)\n const toolCallPart = message?.parts.find(\n (p: MessagePart): p is ToolCallPart =>\n p.type === 'tool-call' && p.id === toolCallId,\n )\n const toolName = toolCallPart?.name || 'unknown'\n\n // Emit tool call state change to devtools\n if (this.currentStreamId) {\n this.events.toolCallStateChanged(\n this.currentStreamId,\n messageId,\n toolCallId,\n toolName,\n state,\n args,\n )\n }\n },\n onToolCall: async (args: {\n toolCallId: string\n toolName: string\n input: any\n }) => {\n // Handle client-side tool execution automatically\n const clientTool = this.clientToolsRef.current.get(args.toolName)\n if (clientTool?.execute) {\n try {\n const output = await clientTool.execute(args.input)\n await this.addToolResult({\n toolCallId: args.toolCallId,\n tool: args.toolName,\n output,\n state: 'output-available',\n })\n } catch (error: any) {\n await this.addToolResult({\n toolCallId: args.toolCallId,\n tool: args.toolName,\n output: null,\n state: 'output-error',\n errorText: error.message,\n })\n }\n }\n },\n onApprovalRequest: (args: {\n toolCallId: string\n toolName: string\n input: any\n approvalId: string\n }) => {\n this.events.approvalRequested(\n this.currentMessageId || '',\n args.toolCallId,\n args.toolName,\n args.input,\n args.approvalId,\n )\n },\n },\n })\n\n this.events.clientCreated(this.processor.getMessages().length)\n }\n\n private generateUniqueId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n }\n\n private setIsLoading(isLoading: boolean): void {\n this.isLoading = isLoading\n this.callbacksRef.current.onLoadingChange(isLoading)\n this.events.loadingChanged(isLoading)\n }\n\n private setError(error: Error | undefined): void {\n this.error = error\n this.callbacksRef.current.onErrorChange(error)\n this.events.errorChanged(error?.message || null)\n }\n\n /**\n * Process a stream through the StreamProcessor\n */\n private async processStream(\n source: AsyncIterable<StreamChunk>,\n ): Promise<UIMessage> {\n // Generate a stream ID for this streaming operation\n this.currentStreamId = this.generateUniqueId('stream')\n\n // Start a new assistant message\n const messageId = this.processor.startAssistantMessage()\n this.currentMessageId = messageId\n\n // Emit message appended event for the new assistant message\n const assistantMessage: UIMessage = {\n id: messageId,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n this.events.messageAppended(assistantMessage)\n\n // Process each chunk\n for await (const chunk of source) {\n this.callbacksRef.current.onChunk(chunk)\n this.processor.processChunk(chunk)\n\n // Yield control back to event loop to allow UI updates\n await new Promise((resolve) => setTimeout(resolve, 0))\n }\n\n // Finalize the stream\n this.processor.finalizeStream()\n\n // Clear the current stream and message IDs\n this.currentStreamId = null\n this.currentMessageId = null\n\n // Return the assistant message\n const messages = this.processor.getMessages()\n const finalAssistantMessage = messages.find(\n (m: UIMessage) => m.id === messageId,\n )\n\n return (\n finalAssistantMessage || {\n id: messageId,\n role: 'assistant',\n parts: [],\n createdAt: new Date(),\n }\n )\n }\n\n /**\n * Send a message and stream the response\n */\n async sendMessage(content: string): Promise<void> {\n if (!content.trim() || this.isLoading) {\n return\n }\n\n // Add user message via processor\n const userMessage = this.processor.addUserMessage(content.trim())\n this.events.messageSent(userMessage.id, content.trim())\n\n await this.streamResponse()\n }\n\n /**\n * Append a message and stream the response\n */\n async append(message: UIMessage | ModelMessage): Promise<void> {\n // Normalize the message to ensure it has id and createdAt\n const normalizedMessage = normalizeToUIMessage(message, generateMessageId)\n\n // Skip system messages - they're handled via systemPrompts, not UIMessages\n if (normalizedMessage.role === 'system') {\n return\n }\n\n // Type assertion: after checking for system, we know it's user or assistant\n const uiMessage = normalizedMessage as UIMessage\n\n // Emit message appended event\n this.events.messageAppended(uiMessage)\n\n // Add to messages\n const messages = this.processor.getMessages()\n this.processor.setMessages([...messages, uiMessage])\n\n await this.streamResponse()\n }\n\n /**\n * Stream a response from the LLM\n */\n private async streamResponse(): Promise<void> {\n this.setIsLoading(true)\n this.setError(undefined)\n this.abortController = new AbortController()\n\n try {\n // Get model messages for the LLM\n const modelMessages = this.processor.toModelMessages()\n\n // Call onResponse callback\n await this.callbacksRef.current.onResponse()\n\n // Include conversationId in the body for server-side event correlation\n const bodyWithConversationId = {\n ...this.body,\n conversationId: this.uniqueId,\n }\n\n // Connect and stream\n const stream = this.connection.connect(\n modelMessages,\n bodyWithConversationId,\n this.abortController.signal,\n )\n\n await this.processStream(stream)\n } catch (err) {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return\n }\n this.setError(err)\n this.callbacksRef.current.onError(err)\n }\n } finally {\n this.abortController = null\n this.setIsLoading(false)\n }\n }\n\n /**\n * Reload the last assistant message\n */\n async reload(): Promise<void> {\n const messages = this.processor.getMessages()\n if (messages.length === 0) return\n\n // Find the last user message\n const lastUserMessageIndex = messages.findLastIndex(\n (m: UIMessage) => m.role === 'user',\n )\n\n if (lastUserMessageIndex === -1) return\n\n this.events.reloaded(lastUserMessageIndex)\n\n // Remove all messages after the last user message\n this.processor.removeMessagesAfter(lastUserMessageIndex)\n\n // Resend\n await this.streamResponse()\n }\n\n /**\n * Stop the current stream\n */\n stop(): void {\n if (this.abortController) {\n this.abortController.abort()\n this.abortController = null\n }\n this.setIsLoading(false)\n this.events.stopped()\n }\n\n /**\n * Clear all messages\n */\n clear(): void {\n this.processor.clearMessages()\n this.setError(undefined)\n this.events.messagesCleared()\n }\n\n /**\n * Add the result of a client-side tool execution\n */\n async addToolResult(result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }): Promise<void> {\n this.events.toolResultAdded(\n result.toolCallId,\n result.tool,\n result.output,\n result.state || 'output-available',\n )\n\n // Add result via processor\n this.processor.addToolResult(\n result.toolCallId,\n result.output,\n result.errorText,\n )\n\n // Check if we should auto-send\n if (this.shouldAutoSend()) {\n await this.continueFlow()\n }\n }\n\n /**\n * Respond to a tool approval request\n */\n async addToolApprovalResponse(response: {\n id: string // approval.id, not toolCallId\n approved: boolean\n }): Promise<void> {\n // Find the tool call ID from the approval ID\n const messages = this.processor.getMessages()\n let foundToolCallId: string | undefined\n\n for (const msg of messages) {\n const toolCallPart = msg.parts.find(\n (p: MessagePart): p is ToolCallPart =>\n p.type === 'tool-call' && p.approval?.id === response.id,\n )\n if (toolCallPart) {\n foundToolCallId = toolCallPart.id\n break\n }\n }\n\n if (foundToolCallId) {\n this.events.toolApprovalResponded(\n response.id,\n foundToolCallId,\n response.approved,\n )\n }\n\n // Add response via processor\n this.processor.addToolApprovalResponse(response.id, response.approved)\n\n // Check if we should auto-send\n if (this.shouldAutoSend()) {\n await this.continueFlow()\n }\n }\n\n /**\n * Continue the agent flow with current messages\n */\n private async continueFlow(): Promise<void> {\n if (this.isLoading) return\n await this.streamResponse()\n }\n\n /**\n * Check if all tool calls are complete and we should auto-send\n */\n private shouldAutoSend(): boolean {\n return this.processor.areAllToolsComplete()\n }\n\n /**\n * Get current messages\n */\n getMessages(): Array<UIMessage> {\n return this.processor.getMessages()\n }\n\n /**\n * Get loading state\n */\n getIsLoading(): boolean {\n return this.isLoading\n }\n\n /**\n * Get current error\n */\n getError(): Error | undefined {\n return this.error\n }\n\n /**\n * Manually set messages\n */\n setMessagesManually(messages: Array<UIMessage>): void {\n this.processor.setMessages(messages)\n }\n\n /**\n * Update options refs (for use in React hooks to avoid recreating client)\n */\n updateOptions(options: {\n connection?: ConnectionAdapter\n body?: Record<string, any>\n tools?: ReadonlyArray<AnyClientTool>\n onResponse?: (response?: Response) => void | Promise<void>\n onChunk?: (chunk: StreamChunk) => void\n onFinish?: (message: UIMessage) => void\n onError?: (error: Error) => void\n }): void {\n if (options.connection !== undefined) {\n this.connection = options.connection\n }\n if (options.body !== undefined) {\n this.body = options.body\n }\n if (options.tools !== undefined) {\n this.clientToolsRef.current = new Map()\n for (const tool of options.tools) {\n this.clientToolsRef.current.set(tool.name, tool)\n }\n }\n if (options.onResponse !== undefined) {\n this.callbacksRef.current.onResponse = options.onResponse\n }\n if (options.onChunk !== undefined) {\n this.callbacksRef.current.onChunk = options.onChunk\n }\n if (options.onFinish !== undefined) {\n this.callbacksRef.current.onFinish = options.onFinish\n }\n if (options.onError !== undefined) {\n this.callbacksRef.current.onError = options.onError\n }\n }\n}\n"],"names":[],"mappings":"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"}