@tanstack/ai-solid 0.3.0 → 0.5.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/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Accessor } from "solid-js";
2
- import { ChatClientOptions, ChatRequestBody, UIMessage } from "@tanstack/ai-client";
2
+ import { ChatClientOptions, ChatClientState, ChatRequestBody, MultimodalContent, UIMessage } from "@tanstack/ai-client";
3
3
  import { AnyClientTool, ModelMessage } from "@tanstack/ai";
4
4
 
5
5
  //#region src/types.d.ts
@@ -12,6 +12,7 @@ import { AnyClientTool, ModelMessage } from "@tanstack/ai";
12
12
  * - `onMessagesChange` - Managed by Solid signal (exposed as `messages`)
13
13
  * - `onLoadingChange` - Managed by Solid signal (exposed as `isLoading`)
14
14
  * - `onErrorChange` - Managed by Solid signal (exposed as `error`)
15
+ * - `onStatusChange` - Managed by Solid signal (exposed as `status`)
15
16
  *
16
17
  * All other callbacks (onResponse, onChunk, onFinish, onError) are
17
18
  * passed through to the underlying ChatClient and can be used for side effects.
@@ -19,16 +20,17 @@ import { AnyClientTool, ModelMessage } from "@tanstack/ai";
19
20
  * Note: Connection and body changes will recreate the ChatClient instance.
20
21
  * To update these options, remount the component or use a key prop.
21
22
  */
22
- type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> = Omit<ChatClientOptions<TTools>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange'>;
23
+ type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> = Omit<ChatClientOptions<TTools>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange'>;
23
24
  interface UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any> {
24
25
  /**
25
26
  * Current messages in the conversation
26
27
  */
27
28
  messages: Accessor<Array<UIMessage<TTools>>>;
28
29
  /**
29
- * Send a message and get a response
30
+ * Send a message and get a response.
31
+ * Can be a simple string or multimodal content with images, audio, etc.
30
32
  */
31
- sendMessage: (content: string) => Promise<void>;
33
+ sendMessage: (content: string | MultimodalContent) => Promise<void>;
32
34
  /**
33
35
  * Append a message to the conversation
34
36
  */
@@ -74,6 +76,10 @@ interface UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any> {
74
76
  * Clear all messages
75
77
  */
76
78
  clear: () => void;
79
+ /**
80
+ * Current generation status
81
+ */
82
+ status: Accessor<ChatClientState>;
77
83
  }
78
84
  //#endregion
79
85
  export { type ChatRequestBody, type UIMessage, UseChatOptions, UseChatReturn };
package/dist/use-chat.js CHANGED
@@ -8,6 +8,7 @@ function useChat(options = {}) {
8
8
  const [messages, setMessages] = createSignal(options.initialMessages || []);
9
9
  const [isLoading, setIsLoading] = createSignal(false);
10
10
  const [error, setError] = createSignal(void 0);
11
+ const [status, setStatus] = createSignal("ready");
11
12
  const client = createMemo(() => {
12
13
  return new ChatClient({
13
14
  connection: options.connection,
@@ -16,8 +17,12 @@ function useChat(options = {}) {
16
17
  body: options.body,
17
18
  onResponse: options.onResponse,
18
19
  onChunk: options.onChunk,
19
- onFinish: options.onFinish,
20
- onError: options.onError,
20
+ onFinish: (message) => {
21
+ options.onFinish?.(message);
22
+ },
23
+ onError: (err) => {
24
+ options.onError?.(err);
25
+ },
21
26
  tools: options.tools,
22
27
  streamProcessor: options.streamProcessor,
23
28
  onMessagesChange: (newMessages) => {
@@ -26,6 +31,9 @@ function useChat(options = {}) {
26
31
  onLoadingChange: (newIsLoading) => {
27
32
  setIsLoading(newIsLoading);
28
33
  },
34
+ onStatusChange: (newStatus) => {
35
+ setStatus(newStatus);
36
+ },
29
37
  onErrorChange: (newError) => {
30
38
  setError(newError);
31
39
  }
@@ -77,6 +85,7 @@ function useChat(options = {}) {
77
85
  stop,
78
86
  isLoading,
79
87
  error,
88
+ status,
80
89
  setMessages: setMessagesManually,
81
90
  clear,
82
91
  addToolResult,
@@ -1 +1 @@
1
- {"version":3,"file":"use-chat.js","names":[],"sources":["../src/use-chat.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\nimport { ChatClient } from '@tanstack/ai-client'\nimport type { AnyClientTool, ModelMessage } from '@tanstack/ai'\nimport type { UIMessage, UseChatOptions, UseChatReturn } from './types'\n\nexport function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(\n options: UseChatOptions<TTools> = {} as UseChatOptions<TTools>,\n): UseChatReturn<TTools> {\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = createSignal<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n\n // Create ChatClient instance with callbacks to sync state\n // Note: Options are captured at client creation time.\n // The connection adapter can use functions for dynamic values (url, headers, etc.)\n // which are evaluated lazily on each request.\n const client = createMemo(() => {\n return new ChatClient({\n connection: options.connection,\n id: clientId,\n initialMessages: options.initialMessages,\n body: options.body,\n onResponse: options.onResponse,\n onChunk: options.onChunk,\n onFinish: options.onFinish,\n onError: options.onError,\n tools: options.tools,\n streamProcessor: options.streamProcessor,\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n setIsLoading(newIsLoading)\n },\n onErrorChange: (newError: Error | undefined) => {\n setError(newError)\n },\n })\n // Only recreate when clientId changes\n // Connection and other options are captured at creation time\n }, [clientId])\n\n // Sync body changes to the client\n // This allows dynamic body values (like model selection) to be updated without recreating the client\n createEffect(() => {\n const currentBody = options.body\n client().updateOptions({ body: currentBody })\n })\n\n // Sync initial messages on mount only\n // Note: initialMessages are passed to ChatClient constructor, but we also\n // set them here to ensure React state is in sync\n createEffect(() => {\n if (options.initialMessages && options.initialMessages.length > 0) {\n // Only set if current messages are empty (initial state)\n if (messages().length === 0) {\n client().setMessagesManually(options.initialMessages)\n }\n }\n }) // Only run on mount - initialMessages are handled by ChatClient constructor\n\n // Cleanup on unmount: stop any in-flight requests\n // Note: We use createEffect with a cleanup return to handle component unmount.\n // The cleanup only runs on disposal (unmount), not on signal changes.\n createEffect(() => {\n return () => {\n // Stop any active generation when component unmounts\n client().stop()\n }\n })\n\n // Note: Callback options (onResponse, onChunk, onFinish, onError, onToolCall)\n // are captured at client creation time. Changes to these callbacks require\n // remounting the component or changing the connection to recreate the client.\n\n const sendMessage = async (content: string) => {\n await client().sendMessage(content)\n }\n\n const append = async (message: ModelMessage | UIMessage<TTools>) => {\n await client().append(message)\n }\n\n const reload = async () => {\n await client().reload()\n }\n\n const stop = () => {\n client().stop()\n }\n\n const clear = () => {\n client().clear()\n }\n\n const setMessagesManually = (newMessages: Array<UIMessage<TTools>>) => {\n client().setMessagesManually(newMessages)\n }\n\n const addToolResult = async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client().addToolResult(result)\n }\n\n const addToolApprovalResponse = async (response: {\n id: string\n approved: boolean\n }) => {\n await client().addToolApprovalResponse(response)\n }\n\n return {\n messages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n }\n}\n"],"mappings":";;;;AAUA,SAAgB,QACd,UAAkC,EAAE,EACb;CACvB,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,QAAQ,MAAM;CAE/B,MAAM,CAAC,UAAU,eAAe,aAC9B,QAAQ,mBAAmB,EAAE,CAC9B;CACD,MAAM,CAAC,WAAW,gBAAgB,aAAa,MAAM;CACrD,MAAM,CAAC,OAAO,YAAY,aAAgC,OAAU;CAMpE,MAAM,SAAS,iBAAiB;AAC9B,SAAO,IAAI,WAAW;GACpB,YAAY,QAAQ;GACpB,IAAI;GACJ,iBAAiB,QAAQ;GACzB,MAAM,QAAQ;GACd,YAAY,QAAQ;GACpB,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,SAAS,QAAQ;GACjB,OAAO,QAAQ;GACf,iBAAiB,QAAQ;GACzB,mBAAmB,gBAA0C;AAC3D,gBAAY,YAAY;;GAE1B,kBAAkB,iBAA0B;AAC1C,iBAAa,aAAa;;GAE5B,gBAAgB,aAAgC;AAC9C,aAAS,SAAS;;GAErB,CAAC;IAGD,CAAC,SAAS,CAAC;AAId,oBAAmB;EACjB,MAAM,cAAc,QAAQ;AAC5B,UAAQ,CAAC,cAAc,EAAE,MAAM,aAAa,CAAC;GAC7C;AAKF,oBAAmB;AACjB,MAAI,QAAQ,mBAAmB,QAAQ,gBAAgB,SAAS,GAE9D;OAAI,UAAU,CAAC,WAAW,EACxB,SAAQ,CAAC,oBAAoB,QAAQ,gBAAgB;;GAGzD;AAKF,oBAAmB;AACjB,eAAa;AAEX,WAAQ,CAAC,MAAM;;GAEjB;CAMF,MAAM,cAAc,OAAO,YAAoB;AAC7C,QAAM,QAAQ,CAAC,YAAY,QAAQ;;CAGrC,MAAM,SAAS,OAAO,YAA8C;AAClE,QAAM,QAAQ,CAAC,OAAO,QAAQ;;CAGhC,MAAM,SAAS,YAAY;AACzB,QAAM,QAAQ,CAAC,QAAQ;;CAGzB,MAAM,aAAa;AACjB,UAAQ,CAAC,MAAM;;CAGjB,MAAM,cAAc;AAClB,UAAQ,CAAC,OAAO;;CAGlB,MAAM,uBAAuB,gBAA0C;AACrE,UAAQ,CAAC,oBAAoB,YAAY;;CAG3C,MAAM,gBAAgB,OAAO,WAMvB;AACJ,QAAM,QAAQ,CAAC,cAAc,OAAO;;CAGtC,MAAM,0BAA0B,OAAO,aAGjC;AACJ,QAAM,QAAQ,CAAC,wBAAwB,SAAS;;AAGlD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;EACb;EACA;EACA;EACD"}
1
+ {"version":3,"file":"use-chat.js","names":[],"sources":["../src/use-chat.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\n\nimport { ChatClient } from '@tanstack/ai-client'\nimport type { ChatClientState } from '@tanstack/ai-client'\nimport type { AnyClientTool, ModelMessage } from '@tanstack/ai'\nimport type {\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nexport function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(\n options: UseChatOptions<TTools> = {} as UseChatOptions<TTools>,\n): UseChatReturn<TTools> {\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = createSignal<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n const [status, setStatus] = createSignal<ChatClientState>('ready')\n\n // Create ChatClient instance with callbacks to sync state\n // Note: Options are captured at client creation time.\n // The connection adapter can use functions for dynamic values (url, headers, etc.)\n // which are evaluated lazily on each request.\n const client = createMemo(() => {\n return new ChatClient({\n connection: options.connection,\n id: clientId,\n initialMessages: options.initialMessages,\n body: options.body,\n onResponse: options.onResponse,\n onChunk: options.onChunk,\n onFinish: (message) => {\n options.onFinish?.(message)\n },\n onError: (err) => {\n options.onError?.(err)\n },\n tools: options.tools,\n streamProcessor: options.streamProcessor,\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n setIsLoading(newIsLoading)\n },\n onStatusChange: (newStatus: ChatClientState) => {\n setStatus(newStatus)\n },\n onErrorChange: (newError: Error | undefined) => {\n setError(newError)\n },\n })\n // Only recreate when clientId changes\n // Connection and other options are captured at creation time\n }, [clientId])\n\n // Sync body changes to the client\n // This allows dynamic body values (like model selection) to be updated without recreating the client\n createEffect(() => {\n const currentBody = options.body\n client().updateOptions({ body: currentBody })\n })\n\n // Sync initial messages on mount only\n // Note: initialMessages are passed to ChatClient constructor, but we also\n // set them here to ensure React state is in sync\n createEffect(() => {\n if (options.initialMessages && options.initialMessages.length > 0) {\n // Only set if current messages are empty (initial state)\n if (messages().length === 0) {\n client().setMessagesManually(options.initialMessages)\n }\n }\n }) // Only run on mount - initialMessages are handled by ChatClient constructor\n\n // Cleanup on unmount: stop any in-flight requests\n // Note: We use createEffect with a cleanup return to handle component unmount.\n // The cleanup only runs on disposal (unmount), not on signal changes.\n createEffect(() => {\n return () => {\n // Stop any active generation when component unmounts\n client().stop()\n }\n })\n\n // Note: Callback options (onResponse, onChunk, onFinish, onError, onToolCall)\n // are captured at client creation time. Changes to these callbacks require\n // remounting the component or changing the connection to recreate the client.\n\n const sendMessage = async (content: string | MultimodalContent) => {\n await client().sendMessage(content)\n }\n\n const append = async (message: ModelMessage | UIMessage<TTools>) => {\n await client().append(message)\n }\n\n const reload = async () => {\n await client().reload()\n }\n\n const stop = () => {\n client().stop()\n }\n\n const clear = () => {\n client().clear()\n }\n\n const setMessagesManually = (newMessages: Array<UIMessage<TTools>>) => {\n client().setMessagesManually(newMessages)\n }\n\n const addToolResult = async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client().addToolResult(result)\n }\n\n const addToolApprovalResponse = async (response: {\n id: string\n approved: boolean\n }) => {\n await client().addToolApprovalResponse(response)\n }\n\n return {\n messages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n }\n}\n"],"mappings":";;;;AAiBA,SAAgB,QACd,UAAkC,EAAE,EACb;CACvB,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,QAAQ,MAAM;CAE/B,MAAM,CAAC,UAAU,eAAe,aAC9B,QAAQ,mBAAmB,EAAE,CAC9B;CACD,MAAM,CAAC,WAAW,gBAAgB,aAAa,MAAM;CACrD,MAAM,CAAC,OAAO,YAAY,aAAgC,OAAU;CACpE,MAAM,CAAC,QAAQ,aAAa,aAA8B,QAAQ;CAMlE,MAAM,SAAS,iBAAiB;AAC9B,SAAO,IAAI,WAAW;GACpB,YAAY,QAAQ;GACpB,IAAI;GACJ,iBAAiB,QAAQ;GACzB,MAAM,QAAQ;GACd,YAAY,QAAQ;GACpB,SAAS,QAAQ;GACjB,WAAW,YAAY;AACrB,YAAQ,WAAW,QAAQ;;GAE7B,UAAU,QAAQ;AAChB,YAAQ,UAAU,IAAI;;GAExB,OAAO,QAAQ;GACf,iBAAiB,QAAQ;GACzB,mBAAmB,gBAA0C;AAC3D,gBAAY,YAAY;;GAE1B,kBAAkB,iBAA0B;AAC1C,iBAAa,aAAa;;GAE5B,iBAAiB,cAA+B;AAC9C,cAAU,UAAU;;GAEtB,gBAAgB,aAAgC;AAC9C,aAAS,SAAS;;GAErB,CAAC;IAGD,CAAC,SAAS,CAAC;AAId,oBAAmB;EACjB,MAAM,cAAc,QAAQ;AAC5B,UAAQ,CAAC,cAAc,EAAE,MAAM,aAAa,CAAC;GAC7C;AAKF,oBAAmB;AACjB,MAAI,QAAQ,mBAAmB,QAAQ,gBAAgB,SAAS,GAE9D;OAAI,UAAU,CAAC,WAAW,EACxB,SAAQ,CAAC,oBAAoB,QAAQ,gBAAgB;;GAGzD;AAKF,oBAAmB;AACjB,eAAa;AAEX,WAAQ,CAAC,MAAM;;GAEjB;CAMF,MAAM,cAAc,OAAO,YAAwC;AACjE,QAAM,QAAQ,CAAC,YAAY,QAAQ;;CAGrC,MAAM,SAAS,OAAO,YAA8C;AAClE,QAAM,QAAQ,CAAC,OAAO,QAAQ;;CAGhC,MAAM,SAAS,YAAY;AACzB,QAAM,QAAQ,CAAC,QAAQ;;CAGzB,MAAM,aAAa;AACjB,UAAQ,CAAC,MAAM;;CAGjB,MAAM,cAAc;AAClB,UAAQ,CAAC,OAAO;;CAGlB,MAAM,uBAAuB,gBAA0C;AACrE,UAAQ,CAAC,oBAAoB,YAAY;;CAG3C,MAAM,gBAAgB,OAAO,WAMvB;AACJ,QAAM,QAAQ,CAAC,cAAc,OAAO;;CAGtC,MAAM,0BAA0B,OAAO,aAGjC;AACJ,QAAM,QAAQ,CAAC,wBAAwB,SAAS;;AAGlD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;EACb;EACA;EACA;EACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-solid",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Solid hooks for TanStack AI",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -31,11 +31,11 @@
31
31
  "streaming"
32
32
  ],
33
33
  "dependencies": {
34
- "@tanstack/ai-client": "0.3.0"
34
+ "@tanstack/ai-client": "0.4.1"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "solid-js": ">=1.9.10",
38
- "@tanstack/ai": "^0.3.0"
38
+ "@tanstack/ai": "^0.4.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@solidjs/testing-library": "^0.8.10",
@@ -46,7 +46,7 @@
46
46
  "tsdown": "^0.17.0-beta.6",
47
47
  "typescript": "5.9.3",
48
48
  "vitest": "^4.0.14",
49
- "@tanstack/ai": "0.3.0"
49
+ "@tanstack/ai": "0.4.0"
50
50
  },
51
51
  "scripts": {
52
52
  "clean": "premove ./build ./dist",
package/src/types.ts CHANGED
@@ -1,13 +1,15 @@
1
- import type { Accessor } from 'solid-js'
2
1
  import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
3
2
  import type {
4
3
  ChatClientOptions,
4
+ ChatClientState,
5
5
  ChatRequestBody,
6
+ MultimodalContent,
6
7
  UIMessage,
7
8
  } from '@tanstack/ai-client'
9
+ import type { Accessor } from 'solid-js'
8
10
 
9
11
  // Re-export types from ai-client
10
- export type { UIMessage, ChatRequestBody }
12
+ export type { ChatRequestBody, MultimodalContent, UIMessage }
11
13
 
12
14
  /**
13
15
  * Options for the useChat hook.
@@ -17,6 +19,7 @@ export type { UIMessage, ChatRequestBody }
17
19
  * - `onMessagesChange` - Managed by Solid signal (exposed as `messages`)
18
20
  * - `onLoadingChange` - Managed by Solid signal (exposed as `isLoading`)
19
21
  * - `onErrorChange` - Managed by Solid signal (exposed as `error`)
22
+ * - `onStatusChange` - Managed by Solid signal (exposed as `status`)
20
23
  *
21
24
  * All other callbacks (onResponse, onChunk, onFinish, onError) are
22
25
  * passed through to the underlying ChatClient and can be used for side effects.
@@ -27,7 +30,7 @@ export type { UIMessage, ChatRequestBody }
27
30
  export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> =
28
31
  Omit<
29
32
  ChatClientOptions<TTools>,
30
- 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange'
33
+ 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange'
31
34
  >
32
35
 
33
36
  export interface UseChatReturn<
@@ -39,9 +42,10 @@ export interface UseChatReturn<
39
42
  messages: Accessor<Array<UIMessage<TTools>>>
40
43
 
41
44
  /**
42
- * Send a message and get a response
45
+ * Send a message and get a response.
46
+ * Can be a simple string or multimodal content with images, audio, etc.
43
47
  */
44
- sendMessage: (content: string) => Promise<void>
48
+ sendMessage: (content: string | MultimodalContent) => Promise<void>
45
49
 
46
50
  /**
47
51
  * Append a message to the conversation
@@ -96,6 +100,11 @@ export interface UseChatReturn<
96
100
  * Clear all messages
97
101
  */
98
102
  clear: () => void
103
+
104
+ /**
105
+ * Current generation status
106
+ */
107
+ status: Accessor<ChatClientState>
99
108
  }
100
109
 
101
110
  // Note: createChatClientOptions and InferChatMessages are now in @tanstack/ai-client
package/src/use-chat.ts CHANGED
@@ -4,9 +4,16 @@ import {
4
4
  createSignal,
5
5
  createUniqueId,
6
6
  } from 'solid-js'
7
+
7
8
  import { ChatClient } from '@tanstack/ai-client'
9
+ import type { ChatClientState } from '@tanstack/ai-client'
8
10
  import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
9
- import type { UIMessage, UseChatOptions, UseChatReturn } from './types'
11
+ import type {
12
+ MultimodalContent,
13
+ UIMessage,
14
+ UseChatOptions,
15
+ UseChatReturn,
16
+ } from './types'
10
17
 
11
18
  export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
12
19
  options: UseChatOptions<TTools> = {} as UseChatOptions<TTools>,
@@ -19,6 +26,7 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
19
26
  )
20
27
  const [isLoading, setIsLoading] = createSignal(false)
21
28
  const [error, setError] = createSignal<Error | undefined>(undefined)
29
+ const [status, setStatus] = createSignal<ChatClientState>('ready')
22
30
 
23
31
  // Create ChatClient instance with callbacks to sync state
24
32
  // Note: Options are captured at client creation time.
@@ -32,8 +40,12 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
32
40
  body: options.body,
33
41
  onResponse: options.onResponse,
34
42
  onChunk: options.onChunk,
35
- onFinish: options.onFinish,
36
- onError: options.onError,
43
+ onFinish: (message) => {
44
+ options.onFinish?.(message)
45
+ },
46
+ onError: (err) => {
47
+ options.onError?.(err)
48
+ },
37
49
  tools: options.tools,
38
50
  streamProcessor: options.streamProcessor,
39
51
  onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {
@@ -42,6 +54,9 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
42
54
  onLoadingChange: (newIsLoading: boolean) => {
43
55
  setIsLoading(newIsLoading)
44
56
  },
57
+ onStatusChange: (newStatus: ChatClientState) => {
58
+ setStatus(newStatus)
59
+ },
45
60
  onErrorChange: (newError: Error | undefined) => {
46
61
  setError(newError)
47
62
  },
@@ -83,7 +98,7 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
83
98
  // are captured at client creation time. Changes to these callbacks require
84
99
  // remounting the component or changing the connection to recreate the client.
85
100
 
86
- const sendMessage = async (content: string) => {
101
+ const sendMessage = async (content: string | MultimodalContent) => {
87
102
  await client().sendMessage(content)
88
103
  }
89
104
 
@@ -132,6 +147,7 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
132
147
  stop,
133
148
  isLoading,
134
149
  error,
150
+ status,
135
151
  setMessages: setMessagesManually,
136
152
  clear,
137
153
  addToolResult,