@tanstack/ai-solid 0.2.2 → 0.4.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, 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,7 +20,7 @@ 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
@@ -74,6 +75,10 @@ interface UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any> {
74
75
  * Clear all messages
75
76
  */
76
77
  clear: () => void;
78
+ /**
79
+ * Current generation status
80
+ */
81
+ status: Accessor<ChatClientState>;
77
82
  }
78
83
  //#endregion
79
84
  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,11 +31,18 @@ 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
  }
32
40
  });
33
41
  }, [clientId]);
42
+ createEffect(() => {
43
+ const currentBody = options.body;
44
+ client().updateOptions({ body: currentBody });
45
+ });
34
46
  createEffect(() => {
35
47
  if (options.initialMessages && options.initialMessages.length > 0) {
36
48
  if (messages().length === 0) client().setMessagesManually(options.initialMessages);
@@ -73,6 +85,7 @@ function useChat(options = {}) {
73
85
  stop,
74
86
  isLoading,
75
87
  error,
88
+ status,
76
89
  setMessages: setMessagesManually,
77
90
  clear,
78
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 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;AAKd,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 { 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 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) => {\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":";;;;AAYA,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,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;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.2.2",
3
+ "version": "0.4.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.2.2"
34
+ "@tanstack/ai-client": "0.4.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "solid-js": ">=1.9.10",
38
- "@tanstack/ai": "^0.2.2"
38
+ "@tanstack/ai": "^0.3.1"
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.2.2"
49
+ "@tanstack/ai": "0.3.1"
50
50
  },
51
51
  "scripts": {
52
52
  "clean": "premove ./build ./dist",
package/src/types.ts CHANGED
@@ -1,13 +1,14 @@
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
6
  UIMessage,
7
7
  } from '@tanstack/ai-client'
8
+ import type { Accessor } from 'solid-js'
8
9
 
9
10
  // Re-export types from ai-client
10
- export type { UIMessage, ChatRequestBody }
11
+ export type { ChatRequestBody, UIMessage }
11
12
 
12
13
  /**
13
14
  * Options for the useChat hook.
@@ -17,6 +18,7 @@ export type { UIMessage, ChatRequestBody }
17
18
  * - `onMessagesChange` - Managed by Solid signal (exposed as `messages`)
18
19
  * - `onLoadingChange` - Managed by Solid signal (exposed as `isLoading`)
19
20
  * - `onErrorChange` - Managed by Solid signal (exposed as `error`)
21
+ * - `onStatusChange` - Managed by Solid signal (exposed as `status`)
20
22
  *
21
23
  * All other callbacks (onResponse, onChunk, onFinish, onError) are
22
24
  * passed through to the underlying ChatClient and can be used for side effects.
@@ -27,7 +29,7 @@ export type { UIMessage, ChatRequestBody }
27
29
  export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> =
28
30
  Omit<
29
31
  ChatClientOptions<TTools>,
30
- 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange'
32
+ 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange'
31
33
  >
32
34
 
33
35
  export interface UseChatReturn<
@@ -96,6 +98,11 @@ export interface UseChatReturn<
96
98
  * Clear all messages
97
99
  */
98
100
  clear: () => void
101
+
102
+ /**
103
+ * Current generation status
104
+ */
105
+ status: Accessor<ChatClientState>
99
106
  }
100
107
 
101
108
  // Note: createChatClientOptions and InferChatMessages are now in @tanstack/ai-client
package/src/use-chat.ts CHANGED
@@ -4,7 +4,9 @@ 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
11
  import type { UIMessage, UseChatOptions, UseChatReturn } from './types'
10
12
 
@@ -19,6 +21,7 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
19
21
  )
20
22
  const [isLoading, setIsLoading] = createSignal(false)
21
23
  const [error, setError] = createSignal<Error | undefined>(undefined)
24
+ const [status, setStatus] = createSignal<ChatClientState>('ready')
22
25
 
23
26
  // Create ChatClient instance with callbacks to sync state
24
27
  // Note: Options are captured at client creation time.
@@ -32,8 +35,12 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
32
35
  body: options.body,
33
36
  onResponse: options.onResponse,
34
37
  onChunk: options.onChunk,
35
- onFinish: options.onFinish,
36
- onError: options.onError,
38
+ onFinish: (message) => {
39
+ options.onFinish?.(message)
40
+ },
41
+ onError: (err) => {
42
+ options.onError?.(err)
43
+ },
37
44
  tools: options.tools,
38
45
  streamProcessor: options.streamProcessor,
39
46
  onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {
@@ -42,6 +49,9 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
42
49
  onLoadingChange: (newIsLoading: boolean) => {
43
50
  setIsLoading(newIsLoading)
44
51
  },
52
+ onStatusChange: (newStatus: ChatClientState) => {
53
+ setStatus(newStatus)
54
+ },
45
55
  onErrorChange: (newError: Error | undefined) => {
46
56
  setError(newError)
47
57
  },
@@ -50,6 +60,13 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
50
60
  // Connection and other options are captured at creation time
51
61
  }, [clientId])
52
62
 
63
+ // Sync body changes to the client
64
+ // This allows dynamic body values (like model selection) to be updated without recreating the client
65
+ createEffect(() => {
66
+ const currentBody = options.body
67
+ client().updateOptions({ body: currentBody })
68
+ })
69
+
53
70
  // Sync initial messages on mount only
54
71
  // Note: initialMessages are passed to ChatClient constructor, but we also
55
72
  // set them here to ensure React state is in sync
@@ -125,6 +142,7 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
125
142
  stop,
126
143
  isLoading,
127
144
  error,
145
+ status,
128
146
  setMessages: setMessagesManually,
129
147
  clear,
130
148
  addToolResult,