@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.
@@ -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,11 @@
1
+ function clientTools(...tools) {
2
+ return tools;
3
+ }
4
+ function createChatClientOptions(options) {
5
+ return options;
6
+ }
7
+ export {
8
+ clientTools,
9
+ createChatClientOptions
10
+ };
11
+ //# sourceMappingURL=types.js.map
@@ -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
+ }