@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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { AnyClientTool, ChunkStrategy, InferToolInput, InferToolOutput, ModelMessage, StreamChunk } from '@tanstack/ai';
|
|
2
|
+
import { ConnectionAdapter } from './connection-adapters.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tool call states - track the lifecycle of a tool call
|
|
5
|
+
*/
|
|
6
|
+
export type ToolCallState = 'awaiting-input' | 'input-streaming' | 'input-complete' | 'approval-requested' | 'approval-responded';
|
|
7
|
+
/**
|
|
8
|
+
* Tool result states - track the lifecycle of a tool result
|
|
9
|
+
*/
|
|
10
|
+
export type ToolResultState = 'streaming' | 'complete' | 'error';
|
|
11
|
+
/**
|
|
12
|
+
* Message parts - building blocks of UIMessage
|
|
13
|
+
*/
|
|
14
|
+
export interface TextPart {
|
|
15
|
+
type: 'text';
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Helper type that creates a tool-call part for a specific tool.
|
|
20
|
+
* This is a conditional type to enable proper distribution over union types,
|
|
21
|
+
* creating a discriminated union where `name` is the discriminant.
|
|
22
|
+
*/
|
|
23
|
+
type ToolCallPartForTool<T> = T extends AnyClientTool ? {
|
|
24
|
+
type: 'tool-call';
|
|
25
|
+
id: string;
|
|
26
|
+
name: T['name'];
|
|
27
|
+
arguments: string;
|
|
28
|
+
/** Parsed tool input (typed from inputSchema) */
|
|
29
|
+
input?: InferToolInput<T>;
|
|
30
|
+
state: ToolCallState;
|
|
31
|
+
/** Approval metadata if tool requires user approval */
|
|
32
|
+
approval?: {
|
|
33
|
+
id: string;
|
|
34
|
+
needsApproval: boolean;
|
|
35
|
+
approved?: boolean;
|
|
36
|
+
};
|
|
37
|
+
/** Tool execution output (for client tools or after approval) */
|
|
38
|
+
output?: InferToolOutput<T>;
|
|
39
|
+
} : never;
|
|
40
|
+
/**
|
|
41
|
+
* Fallback tool-call part type when tools are not typed
|
|
42
|
+
*/
|
|
43
|
+
type UntypedToolCallPart = {
|
|
44
|
+
type: 'tool-call';
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
arguments: string;
|
|
48
|
+
input?: any;
|
|
49
|
+
state: ToolCallState;
|
|
50
|
+
approval?: {
|
|
51
|
+
id: string;
|
|
52
|
+
needsApproval: boolean;
|
|
53
|
+
approved?: boolean;
|
|
54
|
+
};
|
|
55
|
+
output?: any;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Tool call part that creates a proper discriminated union.
|
|
59
|
+
* When TTools is typed, checking `part.name === 'toolName'` will narrow
|
|
60
|
+
* `part.output` to the correct type for that tool.
|
|
61
|
+
*
|
|
62
|
+
* The discriminant is `name`, so code like:
|
|
63
|
+
* ```ts
|
|
64
|
+
* if (part.name === 'recommendGuitar') {
|
|
65
|
+
* // part.output is now typed to the recommendGuitar tool's output
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export type ToolCallPart<TTools extends ReadonlyArray<AnyClientTool> = any> = [
|
|
70
|
+
TTools
|
|
71
|
+
] extends [never] ? UntypedToolCallPart : unknown extends TTools ? UntypedToolCallPart : TTools extends ReadonlyArray<infer Tool> ? Tool extends AnyClientTool ? ToolCallPartForTool<Tool> : UntypedToolCallPart : UntypedToolCallPart;
|
|
72
|
+
export interface ToolResultPart {
|
|
73
|
+
type: 'tool-result';
|
|
74
|
+
toolCallId: string;
|
|
75
|
+
content: string;
|
|
76
|
+
state: ToolResultState;
|
|
77
|
+
error?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface ThinkingPart {
|
|
80
|
+
type: 'thinking';
|
|
81
|
+
content: string;
|
|
82
|
+
}
|
|
83
|
+
export type MessagePart<TTools extends ReadonlyArray<AnyClientTool> = any> = TextPart | ToolCallPart<TTools> | ToolResultPart | ThinkingPart;
|
|
84
|
+
/**
|
|
85
|
+
* UIMessage - Domain-specific message format optimized for building chat UIs
|
|
86
|
+
* Contains parts that can be text, tool calls, or tool results
|
|
87
|
+
*/
|
|
88
|
+
export interface UIMessage<TTools extends ReadonlyArray<AnyClientTool> = any> {
|
|
89
|
+
id: string;
|
|
90
|
+
role: 'user' | 'assistant';
|
|
91
|
+
parts: Array<MessagePart<TTools>>;
|
|
92
|
+
createdAt?: Date;
|
|
93
|
+
}
|
|
94
|
+
export interface ChatClientOptions<TTools extends ReadonlyArray<AnyClientTool> = any> {
|
|
95
|
+
/**
|
|
96
|
+
* Connection adapter for streaming
|
|
97
|
+
* Use fetchServerSentEvents(), fetchHttpStream(), or stream() to create adapters
|
|
98
|
+
*/
|
|
99
|
+
connection: ConnectionAdapter;
|
|
100
|
+
/**
|
|
101
|
+
* Initial messages to populate the chat
|
|
102
|
+
*/
|
|
103
|
+
initialMessages?: Array<UIMessage<TTools>>;
|
|
104
|
+
/**
|
|
105
|
+
* Unique identifier for this chat instance
|
|
106
|
+
* Used for managing multiple chats
|
|
107
|
+
*/
|
|
108
|
+
id?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Additional body parameters to send
|
|
111
|
+
*/
|
|
112
|
+
body?: Record<string, any>;
|
|
113
|
+
/**
|
|
114
|
+
* Callback when a response is received
|
|
115
|
+
*/
|
|
116
|
+
onResponse?: (response?: Response) => void | Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Callback when a stream chunk is received
|
|
119
|
+
*/
|
|
120
|
+
onChunk?: (chunk: StreamChunk) => void;
|
|
121
|
+
/**
|
|
122
|
+
* Callback when the response is finished
|
|
123
|
+
*/
|
|
124
|
+
onFinish?: (message: UIMessage<TTools>) => void;
|
|
125
|
+
/**
|
|
126
|
+
* Callback when an error occurs
|
|
127
|
+
*/
|
|
128
|
+
onError?: (error: Error) => void;
|
|
129
|
+
/**
|
|
130
|
+
* Callback when messages change
|
|
131
|
+
*/
|
|
132
|
+
onMessagesChange?: (messages: Array<UIMessage<TTools>>) => void;
|
|
133
|
+
/**
|
|
134
|
+
* Callback when loading state changes
|
|
135
|
+
*/
|
|
136
|
+
onLoadingChange?: (isLoading: boolean) => void;
|
|
137
|
+
/**
|
|
138
|
+
* Callback when error state changes
|
|
139
|
+
*/
|
|
140
|
+
onErrorChange?: (error: Error | undefined) => void;
|
|
141
|
+
/**
|
|
142
|
+
* Client-side tools with execution logic
|
|
143
|
+
* When provided, tools with execute functions will be called automatically
|
|
144
|
+
*/
|
|
145
|
+
tools?: TTools;
|
|
146
|
+
/**
|
|
147
|
+
* Stream processing options (optional)
|
|
148
|
+
* Configure chunking strategy
|
|
149
|
+
*/
|
|
150
|
+
streamProcessor?: {
|
|
151
|
+
/**
|
|
152
|
+
* Strategy for when to emit text updates
|
|
153
|
+
* Defaults to ImmediateStrategy (every chunk)
|
|
154
|
+
*/
|
|
155
|
+
chunkStrategy?: ChunkStrategy;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export interface ChatRequestBody {
|
|
159
|
+
messages: Array<ModelMessage>;
|
|
160
|
+
data?: Record<string, any>;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Create a typed array of client tools with proper type inference.
|
|
164
|
+
* This eliminates the need for `as const` when defining tool arrays.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const tools = clientTools(
|
|
169
|
+
* myTool1.client(() => result1),
|
|
170
|
+
* myTool2.client(() => result2),
|
|
171
|
+
* )
|
|
172
|
+
*
|
|
173
|
+
* // tools is now properly typed as a tuple with literal tool names
|
|
174
|
+
* // This enables type narrowing when checking part.name === 'toolName'
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export declare function clientTools<const T extends Array<AnyClientTool>>(...tools: T): T;
|
|
178
|
+
/**
|
|
179
|
+
* Helper to create typed chat client options
|
|
180
|
+
* Use this to get proper type inference for messages
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const tools = clientTools(myTool1, myTool2)
|
|
185
|
+
*
|
|
186
|
+
* const chatOptions = createChatClientOptions({
|
|
187
|
+
* connection: fetchServerSentEvents('/api/chat'),
|
|
188
|
+
* tools,
|
|
189
|
+
* })
|
|
190
|
+
*
|
|
191
|
+
* type MyMessages = InferChatMessages<typeof chatOptions>
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export declare function createChatClientOptions<const TTools extends ReadonlyArray<AnyClientTool>>(options: ChatClientOptions<TTools>): ChatClientOptions<TTools>;
|
|
195
|
+
/**
|
|
196
|
+
* Extract the message type from chat options
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* const chatOptions = createChatClientOptions({
|
|
201
|
+
* connection: fetchServerSentEvents('/api/chat'),
|
|
202
|
+
* tools: [myTool1, myTool2],
|
|
203
|
+
* })
|
|
204
|
+
*
|
|
205
|
+
* type MyMessages = InferChatMessages<typeof chatOptions>
|
|
206
|
+
* // MyMessages is now Array<UIMessage<[typeof myTool1, typeof myTool2]>>
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export type InferChatMessages<T> = T extends ChatClientOptions<infer TTools> ? Array<UIMessage<TTools>> : never;
|
|
210
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n AnyClientTool,\n ChunkStrategy,\n InferToolInput,\n InferToolOutput,\n ModelMessage,\n StreamChunk,\n} from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\n\n/**\n * Tool call states - track the lifecycle of a tool call\n */\nexport type ToolCallState =\n | 'awaiting-input' // Received start but no arguments yet\n | 'input-streaming' // Partial arguments received\n | 'input-complete' // All arguments received\n | 'approval-requested' // Waiting for user approval\n | 'approval-responded' // User has approved/denied\n\n/**\n * Tool result states - track the lifecycle of a tool result\n */\nexport type ToolResultState =\n | 'streaming' // Placeholder for future streamed output\n | 'complete' // Result is complete\n | 'error' // Error occurred\n\n/**\n * Message parts - building blocks of UIMessage\n */\nexport interface TextPart {\n type: 'text'\n content: string\n}\n\n/**\n * Helper type that creates a tool-call part for a specific tool.\n * This is a conditional type to enable proper distribution over union types,\n * creating a discriminated union where `name` is the discriminant.\n */\ntype ToolCallPartForTool<T> = T extends AnyClientTool\n ? {\n type: 'tool-call'\n id: string\n name: T['name']\n arguments: string // JSON string (may be incomplete)\n /** Parsed tool input (typed from inputSchema) */\n input?: InferToolInput<T>\n state: ToolCallState\n /** Approval metadata if tool requires user approval */\n approval?: {\n id: string // Unique approval ID\n needsApproval: boolean // Always true if present\n approved?: boolean // User's decision (undefined until responded)\n }\n /** Tool execution output (for client tools or after approval) */\n output?: InferToolOutput<T>\n }\n : never\n\n/**\n * Fallback tool-call part type when tools are not typed\n */\ntype UntypedToolCallPart = {\n type: 'tool-call'\n id: string\n name: string\n arguments: string\n input?: any\n state: ToolCallState\n approval?: {\n id: string\n needsApproval: boolean\n approved?: boolean\n }\n output?: any\n}\n\n/**\n * Tool call part that creates a proper discriminated union.\n * When TTools is typed, checking `part.name === 'toolName'` will narrow\n * `part.output` to the correct type for that tool.\n *\n * The discriminant is `name`, so code like:\n * ```ts\n * if (part.name === 'recommendGuitar') {\n * // part.output is now typed to the recommendGuitar tool's output\n * }\n * ```\n */\nexport type ToolCallPart<TTools extends ReadonlyArray<AnyClientTool> = any> =\n // Check if we have a concrete tools array (not 'any' or 'never')\n [TTools] extends [never]\n ? UntypedToolCallPart\n : unknown extends TTools\n ? UntypedToolCallPart\n : TTools extends ReadonlyArray<infer Tool>\n ? Tool extends AnyClientTool\n ? ToolCallPartForTool<Tool>\n : UntypedToolCallPart\n : UntypedToolCallPart\n\nexport interface ToolResultPart {\n type: 'tool-result'\n toolCallId: string\n content: string\n state: ToolResultState\n error?: string // Error message if state is \"error\"\n}\n\nexport interface ThinkingPart {\n type: 'thinking'\n content: string\n}\n\nexport type MessagePart<TTools extends ReadonlyArray<AnyClientTool> = any> =\n | TextPart\n | ToolCallPart<TTools>\n | ToolResultPart\n | ThinkingPart\n\n/**\n * UIMessage - Domain-specific message format optimized for building chat UIs\n * Contains parts that can be text, tool calls, or tool results\n */\nexport interface UIMessage<TTools extends ReadonlyArray<AnyClientTool> = any> {\n id: string\n role: 'user' | 'assistant'\n parts: Array<MessagePart<TTools>>\n createdAt?: Date\n}\n\nexport interface ChatClientOptions<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n> {\n /**\n * Connection adapter for streaming\n * Use fetchServerSentEvents(), fetchHttpStream(), or stream() to create adapters\n */\n connection: ConnectionAdapter\n\n /**\n * Initial messages to populate the chat\n */\n initialMessages?: Array<UIMessage<TTools>>\n\n /**\n * Unique identifier for this chat instance\n * Used for managing multiple chats\n */\n id?: string\n\n /**\n * Additional body parameters to send\n */\n body?: Record<string, any>\n\n /**\n * Callback when a response is received\n */\n onResponse?: (response?: Response) => void | Promise<void>\n\n /**\n * Callback when a stream chunk is received\n */\n onChunk?: (chunk: StreamChunk) => void\n\n /**\n * Callback when the response is finished\n */\n onFinish?: (message: UIMessage<TTools>) => void\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void\n\n /**\n * Callback when messages change\n */\n onMessagesChange?: (messages: Array<UIMessage<TTools>>) => void\n\n /**\n * Callback when loading state changes\n */\n onLoadingChange?: (isLoading: boolean) => void\n\n /**\n * Callback when error state changes\n */\n onErrorChange?: (error: Error | undefined) => void\n\n /**\n * Client-side tools with execution logic\n * When provided, tools with execute functions will be called automatically\n */\n tools?: TTools\n\n /**\n * Stream processing options (optional)\n * Configure chunking strategy\n */\n streamProcessor?: {\n /**\n * Strategy for when to emit text updates\n * Defaults to ImmediateStrategy (every chunk)\n */\n chunkStrategy?: ChunkStrategy\n }\n}\n\nexport interface ChatRequestBody {\n messages: Array<ModelMessage>\n data?: Record<string, any>\n}\n\n/**\n * Create a typed array of client tools with proper type inference.\n * This eliminates the need for `as const` when defining tool arrays.\n *\n * @example\n * ```ts\n * const tools = clientTools(\n * myTool1.client(() => result1),\n * myTool2.client(() => result2),\n * )\n *\n * // tools is now properly typed as a tuple with literal tool names\n * // This enables type narrowing when checking part.name === 'toolName'\n * ```\n */\nexport function clientTools<const T extends Array<AnyClientTool>>(\n ...tools: T\n): T {\n return tools\n}\n\n/**\n * Helper to create typed chat client options\n * Use this to get proper type inference for messages\n *\n * @example\n * ```ts\n * const tools = clientTools(myTool1, myTool2)\n *\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools,\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * ```\n */\nexport function createChatClientOptions<\n const TTools extends ReadonlyArray<AnyClientTool>,\n>(options: ChatClientOptions<TTools>): ChatClientOptions<TTools> {\n return options\n}\n\n/**\n * Extract the message type from chat options\n *\n * @example\n * ```ts\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools: [myTool1, myTool2],\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * // MyMessages is now Array<UIMessage<[typeof myTool1, typeof myTool2]>>\n * ```\n */\nexport type InferChatMessages<T> =\n T extends ChatClientOptions<infer TTools> ? Array<UIMessage<TTools>> : never\n"],"names":[],"mappings":"AAwOO,SAAS,eACX,OACA;AACH,SAAO;AACT;AAkBO,SAAS,wBAEd,SAA+D;AAC/D,SAAO;AACT;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/ai-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Framework-agnostic headless client for TanStack AI",
|
|
5
|
+
"author": "",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/ai.git",
|
|
10
|
+
"directory": "packages/typescript/ai-client"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"client",
|
|
15
|
+
"headless",
|
|
16
|
+
"tanstack",
|
|
17
|
+
"chat",
|
|
18
|
+
"streaming"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"module": "./dist/esm/index.js",
|
|
22
|
+
"types": "./dist/esm/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/esm/index.d.ts",
|
|
26
|
+
"import": "./dist/esm/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"src"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@tanstack/ai": "0.0.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@vitest/coverage-v8": "4.0.14",
|
|
38
|
+
"vite": "^7.2.4",
|
|
39
|
+
"zod": "^4.1.13"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "vite build",
|
|
43
|
+
"clean": "premove ./build ./dist",
|
|
44
|
+
"lint:fix": "eslint ./src --fix",
|
|
45
|
+
"test:build": "publint --strict",
|
|
46
|
+
"test:coverage": "vitest run --coverage",
|
|
47
|
+
"test:coverage:watch": "vitest --coverage --watch",
|
|
48
|
+
"test:eslint": "eslint ./src",
|
|
49
|
+
"test:lib": "vitest",
|
|
50
|
+
"test:lib:dev": "pnpm test:lib --watch",
|
|
51
|
+
"test:types": "tsc"
|
|
52
|
+
}
|
|
53
|
+
}
|