@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 +21 -0
- package/README.md +131 -0
- package/dist/esm/chat-client.d.ts +102 -0
- package/dist/esm/chat-client.js +375 -0
- package/dist/esm/chat-client.js.map +1 -0
- package/dist/esm/connection-adapters.d.ts +124 -0
- package/dist/esm/connection-adapters.js +149 -0
- package/dist/esm/connection-adapters.js.map +1 -0
- package/dist/esm/events.d.ts +82 -0
- package/dist/esm/events.js +180 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +29 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/tool-types.d.ts +20 -0
- package/dist/esm/types.d.ts +210 -0
- package/dist/esm/types.js +11 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +53 -0
- package/src/chat-client.ts +522 -0
- package/src/connection-adapters.ts +344 -0
- package/src/events.ts +252 -0
- package/src/index.ts +63 -0
- package/src/tool-types.ts +41 -0
- package/src/types.ts +276 -0
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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"}
|