@tanstack/ai-preact 0.10.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/types.d.ts +12 -4
- package/dist/esm/use-chat.d.ts +1 -1
- package/dist/esm/use-chat.js +28 -5
- package/dist/esm/use-chat.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +6 -0
- package/src/types.ts +32 -2
- package/src/use-chat.ts +33 -4
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { useChat } from './use-chat.js';
|
|
2
2
|
export { useMcpAppBridge } from './use-mcp-app-bridge.js';
|
|
3
3
|
export type { UseMcpAppBridgeOptions } from './use-mcp-app-bridge.js';
|
|
4
|
-
export type { UseChatOptions, UseChatReturn, UIMessage, ChatRequestBody, } from './types.js';
|
|
4
|
+
export type { UseChatOptions, UseChatReturn, UIMessage, ChatRequestBody, QueuedMessage, SendMessageOptions, WhenBusy, QueueConfig, QueueStrategy, QueueOption, } from './types.js';
|
|
5
5
|
export { fetchServerSentEvents, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, createMcpAppBridge, type McpAppBridge, type CreateMcpAppBridgeOptions, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type InferChatMessages, } from '@tanstack/ai-client';
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnyClientTool, ModelMessage, SchemaInput } from '@tanstack/ai';
|
|
2
|
-
import { AIDevtoolsDisplayOptions, ChatClientOptions, ChatClientState, ChatRequestBody, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, UIMessage } from '@tanstack/ai-client';
|
|
3
|
-
export type { ChatRequestBody, MultimodalContent, UIMessage };
|
|
2
|
+
import { AIDevtoolsDisplayOptions, ChatClientOptions, ChatClientState, ChatRequestBody, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, QueueConfig, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from '@tanstack/ai-client';
|
|
3
|
+
export type { ChatRequestBody, MultimodalContent, QueueConfig, QueuedMessage, QueueOption, QueueStrategy, SendMessageOptions, UIMessage, WhenBusy, };
|
|
4
4
|
/**
|
|
5
5
|
* Options for the useChat hook.
|
|
6
6
|
*
|
|
@@ -17,7 +17,7 @@ export type { ChatRequestBody, MultimodalContent, UIMessage };
|
|
|
17
17
|
* Note: Connection and body changes will recreate the ChatClient instance.
|
|
18
18
|
* To update these options, remount the component or use a key prop.
|
|
19
19
|
*/
|
|
20
|
-
export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TContext = InferredClientContext<TTools>> = DistributedOmit<ChatClientOptions<TTools, TContext>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'context' | 'devtools'> & {
|
|
20
|
+
export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TContext = InferredClientContext<TTools>> = DistributedOmit<ChatClientOptions<TTools, TContext>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'onQueueChange' | 'context' | 'devtools'> & {
|
|
21
21
|
live?: boolean;
|
|
22
22
|
/** Display options for TanStack AI Devtools. */
|
|
23
23
|
devtools?: AIDevtoolsDisplayOptions;
|
|
@@ -38,7 +38,15 @@ export interface UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any
|
|
|
38
38
|
* Send a message and get a response.
|
|
39
39
|
* Can be a simple string or multimodal content with images, audio, etc.
|
|
40
40
|
*/
|
|
41
|
-
sendMessage: (content: string | MultimodalContent) => Promise<void>;
|
|
41
|
+
sendMessage: (content: string | MultimodalContent, options?: SendMessageOptions) => Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Pending messages queued while a stream is in flight.
|
|
44
|
+
*/
|
|
45
|
+
queue: Array<QueuedMessage>;
|
|
46
|
+
/**
|
|
47
|
+
* Cancel a queued message before it drains. No-op if already sent.
|
|
48
|
+
*/
|
|
49
|
+
cancelQueued: (id: string) => void;
|
|
42
50
|
/**
|
|
43
51
|
* Append a message to the conversation
|
|
44
52
|
*/
|
package/dist/esm/use-chat.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { InferredClientContext } from '@tanstack/ai-client';
|
|
2
2
|
import { AnyClientTool } from '@tanstack/ai';
|
|
3
3
|
import { UseChatOptions, UseChatReturn } from './types.js';
|
|
4
|
-
export declare function useChat<TTools extends ReadonlyArray<AnyClientTool> = any, TContext = InferredClientContext<TTools>>(options: UseChatOptions<TTools, TContext>): UseChatReturn<TTools>;
|
|
4
|
+
export declare function useChat<const TTools extends ReadonlyArray<AnyClientTool> = any, TContext = InferredClientContext<TTools>>(options: UseChatOptions<TTools, TContext>): UseChatReturn<TTools>;
|
package/dist/esm/use-chat.js
CHANGED
|
@@ -13,6 +13,7 @@ function useChat(options) {
|
|
|
13
13
|
const [isSubscribed, setIsSubscribed] = useState(false);
|
|
14
14
|
const [connectionStatus, setConnectionStatus] = useState("disconnected");
|
|
15
15
|
const [sessionGenerating, setSessionGenerating] = useState(false);
|
|
16
|
+
const [queue, setQueue] = useState([]);
|
|
16
17
|
const messagesRef = useRef(
|
|
17
18
|
options.initialMessages || []
|
|
18
19
|
);
|
|
@@ -111,6 +112,13 @@ function useChat(options) {
|
|
|
111
112
|
onSessionGeneratingChange: (isGenerating) => {
|
|
112
113
|
if (activeClientRef.current !== instance) return;
|
|
113
114
|
setSessionGenerating(isGenerating);
|
|
115
|
+
},
|
|
116
|
+
...optionsRef.current.queue !== void 0 && {
|
|
117
|
+
queue: optionsRef.current.queue
|
|
118
|
+
},
|
|
119
|
+
onQueueChange: (nextQueue) => {
|
|
120
|
+
if (activeClientRef.current !== instance) return;
|
|
121
|
+
setQueue(nextQueue);
|
|
114
122
|
}
|
|
115
123
|
});
|
|
116
124
|
activeClientRef.current = instance;
|
|
@@ -128,9 +136,16 @@ function useChat(options) {
|
|
|
128
136
|
...options.forwardedProps !== void 0 && {
|
|
129
137
|
forwardedProps: options.forwardedProps
|
|
130
138
|
},
|
|
131
|
-
context: options.context
|
|
139
|
+
context: options.context,
|
|
140
|
+
...options.queue !== void 0 && { queue: options.queue }
|
|
132
141
|
});
|
|
133
|
-
}, [
|
|
142
|
+
}, [
|
|
143
|
+
client,
|
|
144
|
+
options.body,
|
|
145
|
+
options.forwardedProps,
|
|
146
|
+
options.context,
|
|
147
|
+
options.queue
|
|
148
|
+
]);
|
|
134
149
|
useEffect(() => {
|
|
135
150
|
if (options.live) {
|
|
136
151
|
client.subscribe();
|
|
@@ -161,8 +176,14 @@ function useChat(options) {
|
|
|
161
176
|
};
|
|
162
177
|
}, [client]);
|
|
163
178
|
const sendMessage = useCallback(
|
|
164
|
-
async (content) => {
|
|
165
|
-
await client.sendMessage(content);
|
|
179
|
+
async (content, sendOptions) => {
|
|
180
|
+
await client.sendMessage(content, void 0, sendOptions);
|
|
181
|
+
},
|
|
182
|
+
[client]
|
|
183
|
+
);
|
|
184
|
+
const cancelQueued = useCallback(
|
|
185
|
+
(id) => {
|
|
186
|
+
client.cancelQueued(id);
|
|
166
187
|
},
|
|
167
188
|
[client]
|
|
168
189
|
);
|
|
@@ -215,7 +236,9 @@ function useChat(options) {
|
|
|
215
236
|
setMessages: setMessagesManually,
|
|
216
237
|
clear,
|
|
217
238
|
addToolResult,
|
|
218
|
-
addToolApprovalResponse
|
|
239
|
+
addToolApprovalResponse,
|
|
240
|
+
queue,
|
|
241
|
+
cancelQueued
|
|
219
242
|
};
|
|
220
243
|
}
|
|
221
244
|
export {
|
package/dist/esm/use-chat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat.js","sources":["../../src/use-chat.ts"],"sourcesContent":["import { ChatClient } from '@tanstack/ai-client'\nimport { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools'\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'preact/hooks'\nimport type {\n ChatClientState,\n ConnectionStatus,\n InferredClientContext,\n} from '@tanstack/ai-client'\nimport type { AnyClientTool, ModelMessage } from '@tanstack/ai'\n\nimport type {\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nexport function useChat<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TContext = InferredClientContext<TTools>,\n>(options: UseChatOptions<TTools, TContext>): UseChatReturn<TTools> {\n const hookId = useId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = useState<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | undefined>(undefined)\n const [status, setStatus] = useState<ChatClientState>('ready')\n const [isSubscribed, setIsSubscribed] = useState(false)\n const [connectionStatus, setConnectionStatus] =\n useState<ConnectionStatus>('disconnected')\n const [sessionGenerating, setSessionGenerating] = useState(false)\n\n // Track current messages in a ref to preserve them when client is recreated\n const messagesRef = useRef<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const isFirstMountRef = useRef(true)\n const activeClientRef = useRef<ChatClient | null>(null)\n const cleanupInvalidationRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n const optionsRef = useRef<UseChatOptions<TTools, TContext>>(options)\n\n optionsRef.current = options\n\n useEffect(() => {\n messagesRef.current = messages\n }, [messages])\n\n const client = useMemo(() => {\n const messagesToUse = options.initialMessages || []\n isFirstMountRef.current = false\n\n // Build options with conditional spreads for fields whose source\n // type is `T | undefined` but the ChatClient target uses a strict\n // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects\n // assigning `undefined` to those, so we omit the key when absent.\n const initialOptions = optionsRef.current\n const transport = initialOptions.connection\n ? { connection: initialOptions.connection }\n : { fetcher: initialOptions.fetcher }\n\n const instance = new ChatClient<TTools, TContext>({\n devtoolsBridgeFactory: createChatDevtoolsBridge,\n ...transport,\n id: clientId,\n initialMessages: messagesToUse,\n ...(initialOptions.body !== undefined && { body: initialOptions.body }),\n ...(initialOptions.threadId !== undefined && {\n threadId: initialOptions.threadId,\n }),\n ...(initialOptions.forwardedProps !== undefined && {\n forwardedProps: initialOptions.forwardedProps,\n }),\n ...(initialOptions.persistence !== undefined && {\n persistence: initialOptions.persistence,\n }),\n ...(initialOptions.context !== undefined && {\n context: initialOptions.context,\n }),\n devtools: {\n ...initialOptions.devtools,\n framework: 'preact',\n hookName: 'useChat',\n outputKind: initialOptions.outputSchema ? 'structured' : 'chat',\n },\n // Wrap every callback so the latest options are read at call time.\n // Capturing the function reference directly would freeze it to whatever\n // the parent passed on the first render.\n onResponse: (response) => {\n if (activeClientRef.current !== instance) return\n return optionsRef.current.onResponse?.(response)\n },\n onChunk: (chunk) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onChunk?.(chunk)\n },\n onFinish: (message) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onFinish?.(message)\n },\n onError: (err) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onError?.(err)\n },\n onCustomEvent: (eventType, data, context) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onCustomEvent?.(eventType, data, context)\n },\n ...(initialOptions.tools !== undefined && {\n tools: initialOptions.tools,\n }),\n ...(options.streamProcessor !== undefined && {\n streamProcessor: options.streamProcessor,\n }),\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n if (activeClientRef.current !== instance) return\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n if (activeClientRef.current !== instance) return\n setIsLoading(newIsLoading)\n },\n onStatusChange: (newStatus: ChatClientState) => {\n if (activeClientRef.current !== instance) return\n setStatus(newStatus)\n },\n onErrorChange: (newError: Error | undefined) => {\n if (activeClientRef.current !== instance) return\n setError(newError)\n },\n onSubscriptionChange: (nextIsSubscribed: boolean) => {\n if (activeClientRef.current !== instance) return\n setIsSubscribed(nextIsSubscribed)\n },\n onConnectionStatusChange: (nextStatus: ConnectionStatus) => {\n if (activeClientRef.current !== instance) return\n setConnectionStatus(nextStatus)\n },\n onSessionGeneratingChange: (isGenerating: boolean) => {\n if (activeClientRef.current !== instance) return\n setSessionGenerating(isGenerating)\n },\n })\n activeClientRef.current = instance\n return instance\n }, [clientId])\n\n useEffect(() => {\n const clientMessages = client.getMessages()\n if (clientMessages !== messagesRef.current) {\n setMessages(clientMessages)\n }\n }, [client])\n\n // Sync body / forwardedProps changes to the client.\n // Both populate the same wire payload; `forwardedProps` is preferred\n // and `body` is deprecated but still supported.\n useEffect(() => {\n // Conditional spread: `updateOptions` declares strict-optional\n // fields and rejects explicit `undefined` under EOPT.\n client.updateOptions({\n body: options.body,\n ...(options.forwardedProps !== undefined && {\n forwardedProps: options.forwardedProps,\n }),\n context: options.context,\n })\n }, [client, options.body, options.forwardedProps, options.context])\n\n useEffect(() => {\n if (options.live) {\n client.subscribe()\n } else {\n client.unsubscribe()\n }\n }, [client, options.live])\n\n // Cleanup on unmount: stop any in-flight requests\n // Note: We only cleanup when client changes or component unmounts.\n // DO NOT include isLoading in dependencies - that would cause the cleanup\n // to run when isLoading changes, aborting continuation requests.\n useEffect(() => {\n if (cleanupInvalidationRef.current) {\n clearTimeout(cleanupInvalidationRef.current)\n cleanupInvalidationRef.current = null\n }\n activeClientRef.current = client\n client.mountDevtools()\n\n return () => {\n cleanupInvalidationRef.current = setTimeout(() => {\n if (activeClientRef.current === client) {\n activeClientRef.current = null\n }\n cleanupInvalidationRef.current = null\n }, 0)\n // Subscribe/unsubscribe on `options.live` is owned by the dedicated\n // effect above. This cleanup only fires on unmount or client swap,\n // so read `live` through the ref to avoid disposing the client every\n // time `live` toggles.\n if (optionsRef.current.live) {\n client.unsubscribe()\n } else {\n client.stop()\n }\n client.dispose()\n }\n }, [client])\n\n // All callback options are read through optionsRef at call time, so fresh\n // closures from each render are picked up without recreating the client.\n const sendMessage = useCallback(\n async (content: string | MultimodalContent) => {\n await client.sendMessage(content)\n },\n [client],\n )\n\n const append = useCallback(\n async (message: ModelMessage | UIMessage) => {\n await client.append(message)\n },\n [client],\n )\n\n const reload = useCallback(async () => {\n await client.reload()\n }, [client])\n\n const stop = useCallback(() => {\n client.stop()\n }, [client])\n\n const clear = useCallback(() => {\n client.clear()\n }, [client])\n\n const setMessagesManually = useCallback(\n (newMessages: Array<UIMessage<TTools>>) => {\n client.setMessagesManually(newMessages)\n },\n [client],\n )\n\n const addToolResult = useCallback(\n async (result: {\n toolCallId: string\n tool: string\n output: unknown\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client.addToolResult(result)\n },\n [client],\n )\n\n const addToolApprovalResponse = useCallback(\n async (response: { id: string; approved: boolean }) => {\n await client.addToolApprovalResponse(response)\n },\n [client],\n )\n\n const renderedMessages = client.getMessages()\n\n return {\n messages: renderedMessages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n isSubscribed,\n connectionStatus,\n sessionGenerating,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-chat.js","sources":["../../src/use-chat.ts"],"sourcesContent":["import { ChatClient } from '@tanstack/ai-client'\nimport { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools'\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'preact/hooks'\nimport type {\n ChatClientState,\n ConnectionStatus,\n InferredClientContext,\n QueuedMessage,\n SendMessageOptions,\n} from '@tanstack/ai-client'\nimport type { AnyClientTool, ModelMessage } from '@tanstack/ai'\n\nimport type {\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nexport function useChat<\n const TTools extends ReadonlyArray<AnyClientTool> = any,\n TContext = InferredClientContext<TTools>,\n>(options: UseChatOptions<TTools, TContext>): UseChatReturn<TTools> {\n const hookId = useId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = useState<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | undefined>(undefined)\n const [status, setStatus] = useState<ChatClientState>('ready')\n const [isSubscribed, setIsSubscribed] = useState(false)\n const [connectionStatus, setConnectionStatus] =\n useState<ConnectionStatus>('disconnected')\n const [sessionGenerating, setSessionGenerating] = useState(false)\n const [queue, setQueue] = useState<Array<QueuedMessage>>([])\n\n // Track current messages in a ref to preserve them when client is recreated\n const messagesRef = useRef<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const isFirstMountRef = useRef(true)\n const activeClientRef = useRef<ChatClient | null>(null)\n const cleanupInvalidationRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n const optionsRef = useRef<UseChatOptions<TTools, TContext>>(options)\n\n optionsRef.current = options\n\n useEffect(() => {\n messagesRef.current = messages\n }, [messages])\n\n const client = useMemo(() => {\n const messagesToUse = options.initialMessages || []\n isFirstMountRef.current = false\n\n // Build options with conditional spreads for fields whose source\n // type is `T | undefined` but the ChatClient target uses a strict\n // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects\n // assigning `undefined` to those, so we omit the key when absent.\n const initialOptions = optionsRef.current\n const transport = initialOptions.connection\n ? { connection: initialOptions.connection }\n : { fetcher: initialOptions.fetcher }\n\n const instance = new ChatClient<TTools, TContext>({\n devtoolsBridgeFactory: createChatDevtoolsBridge,\n ...transport,\n id: clientId,\n initialMessages: messagesToUse,\n ...(initialOptions.body !== undefined && { body: initialOptions.body }),\n ...(initialOptions.threadId !== undefined && {\n threadId: initialOptions.threadId,\n }),\n ...(initialOptions.forwardedProps !== undefined && {\n forwardedProps: initialOptions.forwardedProps,\n }),\n ...(initialOptions.persistence !== undefined && {\n persistence: initialOptions.persistence,\n }),\n ...(initialOptions.context !== undefined && {\n context: initialOptions.context,\n }),\n devtools: {\n ...initialOptions.devtools,\n framework: 'preact',\n hookName: 'useChat',\n outputKind: initialOptions.outputSchema ? 'structured' : 'chat',\n },\n // Wrap every callback so the latest options are read at call time.\n // Capturing the function reference directly would freeze it to whatever\n // the parent passed on the first render.\n onResponse: (response) => {\n if (activeClientRef.current !== instance) return\n return optionsRef.current.onResponse?.(response)\n },\n onChunk: (chunk) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onChunk?.(chunk)\n },\n onFinish: (message) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onFinish?.(message)\n },\n onError: (err) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onError?.(err)\n },\n onCustomEvent: (eventType, data, context) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onCustomEvent?.(eventType, data, context)\n },\n ...(initialOptions.tools !== undefined && {\n tools: initialOptions.tools,\n }),\n ...(options.streamProcessor !== undefined && {\n streamProcessor: options.streamProcessor,\n }),\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n if (activeClientRef.current !== instance) return\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n if (activeClientRef.current !== instance) return\n setIsLoading(newIsLoading)\n },\n onStatusChange: (newStatus: ChatClientState) => {\n if (activeClientRef.current !== instance) return\n setStatus(newStatus)\n },\n onErrorChange: (newError: Error | undefined) => {\n if (activeClientRef.current !== instance) return\n setError(newError)\n },\n onSubscriptionChange: (nextIsSubscribed: boolean) => {\n if (activeClientRef.current !== instance) return\n setIsSubscribed(nextIsSubscribed)\n },\n onConnectionStatusChange: (nextStatus: ConnectionStatus) => {\n if (activeClientRef.current !== instance) return\n setConnectionStatus(nextStatus)\n },\n onSessionGeneratingChange: (isGenerating: boolean) => {\n if (activeClientRef.current !== instance) return\n setSessionGenerating(isGenerating)\n },\n ...(optionsRef.current.queue !== undefined && {\n queue: optionsRef.current.queue,\n }),\n onQueueChange: (nextQueue: Array<QueuedMessage>) => {\n if (activeClientRef.current !== instance) return\n setQueue(nextQueue)\n },\n })\n activeClientRef.current = instance\n return instance\n }, [clientId])\n\n useEffect(() => {\n const clientMessages = client.getMessages()\n if (clientMessages !== messagesRef.current) {\n setMessages(clientMessages)\n }\n }, [client])\n\n // Sync body / forwardedProps changes to the client.\n // Both populate the same wire payload; `forwardedProps` is preferred\n // and `body` is deprecated but still supported.\n useEffect(() => {\n // Conditional spread: `updateOptions` declares strict-optional\n // fields and rejects explicit `undefined` under EOPT.\n client.updateOptions({\n body: options.body,\n ...(options.forwardedProps !== undefined && {\n forwardedProps: options.forwardedProps,\n }),\n context: options.context,\n ...(options.queue !== undefined && { queue: options.queue }),\n })\n }, [\n client,\n options.body,\n options.forwardedProps,\n options.context,\n options.queue,\n ])\n\n useEffect(() => {\n if (options.live) {\n client.subscribe()\n } else {\n client.unsubscribe()\n }\n }, [client, options.live])\n\n // Cleanup on unmount: stop any in-flight requests\n // Note: We only cleanup when client changes or component unmounts.\n // DO NOT include isLoading in dependencies - that would cause the cleanup\n // to run when isLoading changes, aborting continuation requests.\n useEffect(() => {\n if (cleanupInvalidationRef.current) {\n clearTimeout(cleanupInvalidationRef.current)\n cleanupInvalidationRef.current = null\n }\n activeClientRef.current = client\n client.mountDevtools()\n\n return () => {\n cleanupInvalidationRef.current = setTimeout(() => {\n if (activeClientRef.current === client) {\n activeClientRef.current = null\n }\n cleanupInvalidationRef.current = null\n }, 0)\n // Subscribe/unsubscribe on `options.live` is owned by the dedicated\n // effect above. This cleanup only fires on unmount or client swap,\n // so read `live` through the ref to avoid disposing the client every\n // time `live` toggles.\n if (optionsRef.current.live) {\n client.unsubscribe()\n } else {\n client.stop()\n }\n client.dispose()\n }\n }, [client])\n\n // All callback options are read through optionsRef at call time, so fresh\n // closures from each render are picked up without recreating the client.\n const sendMessage = useCallback(\n async (\n content: string | MultimodalContent,\n sendOptions?: SendMessageOptions,\n ) => {\n await client.sendMessage(content, undefined, sendOptions)\n },\n [client],\n )\n\n const cancelQueued = useCallback(\n (id: string) => {\n client.cancelQueued(id)\n },\n [client],\n )\n\n const append = useCallback(\n async (message: ModelMessage | UIMessage) => {\n await client.append(message)\n },\n [client],\n )\n\n const reload = useCallback(async () => {\n await client.reload()\n }, [client])\n\n const stop = useCallback(() => {\n client.stop()\n }, [client])\n\n const clear = useCallback(() => {\n client.clear()\n }, [client])\n\n const setMessagesManually = useCallback(\n (newMessages: Array<UIMessage<TTools>>) => {\n client.setMessagesManually(newMessages)\n },\n [client],\n )\n\n const addToolResult = useCallback(\n async (result: {\n toolCallId: string\n tool: string\n output: unknown\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client.addToolResult(result)\n },\n [client],\n )\n\n const addToolApprovalResponse = useCallback(\n async (response: { id: string; approved: boolean }) => {\n await client.addToolApprovalResponse(response)\n },\n [client],\n )\n\n const renderedMessages = client.getMessages()\n\n return {\n messages: renderedMessages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n isSubscribed,\n connectionStatus,\n sessionGenerating,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n queue,\n cancelQueued,\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-preact",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Preact hooks for TanStack AI streaming chat and typed messages.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"tool-calling"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@tanstack/ai-client": "0.
|
|
49
|
+
"@tanstack/ai-client": "0.22.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@mcp-ui/client": "^7.1.1",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
"jsdom": "^27.2.0",
|
|
56
56
|
"preact": "^10.26.9",
|
|
57
57
|
"vite": "^7.3.3",
|
|
58
|
-
"@tanstack/ai": "0.
|
|
58
|
+
"@tanstack/ai": "0.41.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@mcp-ui/client": "^7",
|
|
62
62
|
"preact": ">=10.11.0",
|
|
63
|
-
"@tanstack/ai": "^0.
|
|
63
|
+
"@tanstack/ai": "^0.41.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependenciesMeta": {
|
|
66
66
|
"@mcp-ui/client": {
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -9,11 +9,27 @@ import type {
|
|
|
9
9
|
DistributedOmit,
|
|
10
10
|
InferredClientContext,
|
|
11
11
|
MultimodalContent,
|
|
12
|
+
QueueConfig,
|
|
13
|
+
QueueOption,
|
|
14
|
+
QueueStrategy,
|
|
15
|
+
QueuedMessage,
|
|
16
|
+
SendMessageOptions,
|
|
12
17
|
UIMessage,
|
|
18
|
+
WhenBusy,
|
|
13
19
|
} from '@tanstack/ai-client'
|
|
14
20
|
|
|
15
21
|
// Re-export types from ai-client
|
|
16
|
-
export type {
|
|
22
|
+
export type {
|
|
23
|
+
ChatRequestBody,
|
|
24
|
+
MultimodalContent,
|
|
25
|
+
QueueConfig,
|
|
26
|
+
QueuedMessage,
|
|
27
|
+
QueueOption,
|
|
28
|
+
QueueStrategy,
|
|
29
|
+
SendMessageOptions,
|
|
30
|
+
UIMessage,
|
|
31
|
+
WhenBusy,
|
|
32
|
+
}
|
|
17
33
|
|
|
18
34
|
/**
|
|
19
35
|
* Options for the useChat hook.
|
|
@@ -43,6 +59,7 @@ export type UseChatOptions<
|
|
|
43
59
|
| 'onSubscriptionChange'
|
|
44
60
|
| 'onConnectionStatusChange'
|
|
45
61
|
| 'onSessionGeneratingChange'
|
|
62
|
+
| 'onQueueChange'
|
|
46
63
|
| 'context'
|
|
47
64
|
| 'devtools'
|
|
48
65
|
> & {
|
|
@@ -70,7 +87,20 @@ export interface UseChatReturn<
|
|
|
70
87
|
* Send a message and get a response.
|
|
71
88
|
* Can be a simple string or multimodal content with images, audio, etc.
|
|
72
89
|
*/
|
|
73
|
-
sendMessage: (
|
|
90
|
+
sendMessage: (
|
|
91
|
+
content: string | MultimodalContent,
|
|
92
|
+
options?: SendMessageOptions,
|
|
93
|
+
) => Promise<void>
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Pending messages queued while a stream is in flight.
|
|
97
|
+
*/
|
|
98
|
+
queue: Array<QueuedMessage>
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Cancel a queued message before it drains. No-op if already sent.
|
|
102
|
+
*/
|
|
103
|
+
cancelQueued: (id: string) => void
|
|
74
104
|
|
|
75
105
|
/**
|
|
76
106
|
* Append a message to the conversation
|
package/src/use-chat.ts
CHANGED
|
@@ -12,6 +12,8 @@ import type {
|
|
|
12
12
|
ChatClientState,
|
|
13
13
|
ConnectionStatus,
|
|
14
14
|
InferredClientContext,
|
|
15
|
+
QueuedMessage,
|
|
16
|
+
SendMessageOptions,
|
|
15
17
|
} from '@tanstack/ai-client'
|
|
16
18
|
import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
|
|
17
19
|
|
|
@@ -23,7 +25,7 @@ import type {
|
|
|
23
25
|
} from './types'
|
|
24
26
|
|
|
25
27
|
export function useChat<
|
|
26
|
-
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
28
|
+
const TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
27
29
|
TContext = InferredClientContext<TTools>,
|
|
28
30
|
>(options: UseChatOptions<TTools, TContext>): UseChatReturn<TTools> {
|
|
29
31
|
const hookId = useId()
|
|
@@ -39,6 +41,7 @@ export function useChat<
|
|
|
39
41
|
const [connectionStatus, setConnectionStatus] =
|
|
40
42
|
useState<ConnectionStatus>('disconnected')
|
|
41
43
|
const [sessionGenerating, setSessionGenerating] = useState(false)
|
|
44
|
+
const [queue, setQueue] = useState<Array<QueuedMessage>>([])
|
|
42
45
|
|
|
43
46
|
// Track current messages in a ref to preserve them when client is recreated
|
|
44
47
|
const messagesRef = useRef<Array<UIMessage<TTools>>>(
|
|
@@ -151,6 +154,13 @@ export function useChat<
|
|
|
151
154
|
if (activeClientRef.current !== instance) return
|
|
152
155
|
setSessionGenerating(isGenerating)
|
|
153
156
|
},
|
|
157
|
+
...(optionsRef.current.queue !== undefined && {
|
|
158
|
+
queue: optionsRef.current.queue,
|
|
159
|
+
}),
|
|
160
|
+
onQueueChange: (nextQueue: Array<QueuedMessage>) => {
|
|
161
|
+
if (activeClientRef.current !== instance) return
|
|
162
|
+
setQueue(nextQueue)
|
|
163
|
+
},
|
|
154
164
|
})
|
|
155
165
|
activeClientRef.current = instance
|
|
156
166
|
return instance
|
|
@@ -175,8 +185,15 @@ export function useChat<
|
|
|
175
185
|
forwardedProps: options.forwardedProps,
|
|
176
186
|
}),
|
|
177
187
|
context: options.context,
|
|
188
|
+
...(options.queue !== undefined && { queue: options.queue }),
|
|
178
189
|
})
|
|
179
|
-
}, [
|
|
190
|
+
}, [
|
|
191
|
+
client,
|
|
192
|
+
options.body,
|
|
193
|
+
options.forwardedProps,
|
|
194
|
+
options.context,
|
|
195
|
+
options.queue,
|
|
196
|
+
])
|
|
180
197
|
|
|
181
198
|
useEffect(() => {
|
|
182
199
|
if (options.live) {
|
|
@@ -221,8 +238,18 @@ export function useChat<
|
|
|
221
238
|
// All callback options are read through optionsRef at call time, so fresh
|
|
222
239
|
// closures from each render are picked up without recreating the client.
|
|
223
240
|
const sendMessage = useCallback(
|
|
224
|
-
async (
|
|
225
|
-
|
|
241
|
+
async (
|
|
242
|
+
content: string | MultimodalContent,
|
|
243
|
+
sendOptions?: SendMessageOptions,
|
|
244
|
+
) => {
|
|
245
|
+
await client.sendMessage(content, undefined, sendOptions)
|
|
246
|
+
},
|
|
247
|
+
[client],
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
const cancelQueued = useCallback(
|
|
251
|
+
(id: string) => {
|
|
252
|
+
client.cancelQueued(id)
|
|
226
253
|
},
|
|
227
254
|
[client],
|
|
228
255
|
)
|
|
@@ -291,5 +318,7 @@ export function useChat<
|
|
|
291
318
|
clear,
|
|
292
319
|
addToolResult,
|
|
293
320
|
addToolApprovalResponse,
|
|
321
|
+
queue,
|
|
322
|
+
cancelQueued,
|
|
294
323
|
}
|
|
295
324
|
}
|