@tanstack/ai-solid 0.0.3 → 0.2.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/README.md CHANGED
@@ -158,13 +158,13 @@ Your backend should use the `chat()` method which **automatically handles tool e
158
158
 
159
159
  ```typescript
160
160
  import { chat, toStreamResponse } from '@tanstack/ai'
161
- import { openai } from '@tanstack/ai-openai'
161
+ import { openaiText } from '@tanstack/ai-openai'
162
162
 
163
163
  export async function POST(request: Request) {
164
164
  const { messages } = await request.json()
165
165
 
166
166
  const stream = chat({
167
- adapter: openai(),
167
+ adapter: openaiText(),
168
168
  model: 'gpt-4o',
169
169
  messages,
170
170
  tools: [weatherTool], // Optional: auto-executed in loop
@@ -311,20 +311,19 @@ function App() {
311
311
 
312
312
  ```typescript
313
313
  import express from 'express'
314
- import { AI, toStreamResponse } from '@tanstack/ai'
315
- import { OpenAIAdapter } from '@tanstack/ai-openai'
314
+ import { chat, toStreamResponse } from '@tanstack/ai'
315
+ import { openaiText } from '@tanstack/ai-openai'
316
316
 
317
317
  const app = express()
318
318
  app.use(express.json())
319
319
 
320
- const ai = new AI(new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY }))
321
-
322
320
  app.post('/api/chat', async (req, res) => {
323
321
  const { messages } = req.body
324
322
 
325
323
  // One line to create streaming response!
326
- const stream = ai.streamChat({
327
- model: 'gpt-3.5-turbo',
324
+ const stream = chat({
325
+ adapter: openaiText(),
326
+ model: 'gpt-4o',
328
327
  messages,
329
328
  })
330
329
 
@@ -353,20 +352,19 @@ app.listen(3000)
353
352
 
354
353
  ```typescript
355
354
  // app/api/chat/route.ts
356
- import { AI, toStreamResponse } from '@tanstack/ai'
357
- import { OpenAIAdapter } from '@tanstack/ai-openai'
355
+ import { chat, toStreamResponse } from '@tanstack/ai'
356
+ import { openaiText } from '@tanstack/ai-openai'
358
357
 
359
358
  export const runtime = 'edge'
360
359
 
361
- const ai = new AI(new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY }))
362
-
363
360
  export async function POST(req: Request) {
364
361
  const { messages } = await req.json()
365
362
 
366
363
  // One line!
367
364
  return toStreamResponse(
368
- ai.streamChat({
369
- model: 'gpt-3.5-turbo',
365
+ chat({
366
+ adapter: openaiText(),
367
+ model: 'gpt-4o',
370
368
  messages,
371
369
  }),
372
370
  )
@@ -377,12 +375,8 @@ export async function POST(req: Request) {
377
375
 
378
376
  ```typescript
379
377
  import { createFileRoute } from '@tanstack/react-router'
380
- import { AI, toStreamResponse } from '@tanstack/ai'
381
- import { AnthropicAdapter } from '@tanstack/ai-anthropic'
382
-
383
- const ai = new AI(
384
- new AnthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY }),
385
- )
378
+ import { chat, toStreamResponse } from '@tanstack/ai'
379
+ import { anthropicText } from '@tanstack/ai-anthropic'
386
380
 
387
381
  export const Route = createFileRoute('/api/chat')({
388
382
  server: {
@@ -392,8 +386,9 @@ export const Route = createFileRoute('/api/chat')({
392
386
 
393
387
  // One line with automatic tool execution!
394
388
  return toStreamResponse(
395
- ai.streamChat({
396
- model: 'claude-3-5-sonnet-20241022',
389
+ chat({
390
+ adapter: anthropicText(),
391
+ model: 'claude-sonnet-4-20250514',
397
392
  messages,
398
393
  tools, // Tools with execute functions
399
394
  }),
package/dist/use-chat.js CHANGED
@@ -38,9 +38,9 @@ function useChat(options = {}) {
38
38
  });
39
39
  createEffect(() => {
40
40
  return () => {
41
- if (isLoading()) client().stop();
41
+ client().stop();
42
42
  };
43
- }, [client, isLoading]);
43
+ });
44
44
  const sendMessage = async (content) => {
45
45
  await client().sendMessage(content);
46
46
  };
@@ -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 createEffect(() => {\n return () => {\n // Stop any active generation when component unmounts\n if (isLoading()) {\n client().stop()\n }\n }\n }, [client, isLoading])\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;AAGF,oBAAmB;AACjB,eAAa;AAEX,OAAI,WAAW,CACb,SAAQ,CAAC,MAAM;;IAGlB,CAAC,QAAQ,UAAU,CAAC;CAMvB,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'\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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-solid",
3
- "version": "0.0.3",
3
+ "version": "0.2.0",
4
4
  "description": "Solid hooks for TanStack AI",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -31,9 +31,7 @@
31
31
  "streaming"
32
32
  ],
33
33
  "dependencies": {
34
- "zod": "^4.1.13",
35
- "@tanstack/ai": "0.0.3",
36
- "@tanstack/ai-client": "0.0.3"
34
+ "@tanstack/ai-client": "0.2.0"
37
35
  },
38
36
  "devDependencies": {
39
37
  "@solidjs/testing-library": "^0.8.10",
@@ -47,8 +45,7 @@
47
45
  },
48
46
  "peerDependencies": {
49
47
  "solid-js": ">=1.9.10",
50
- "@tanstack/ai": "0.0.3",
51
- "@tanstack/ai-client": "0.0.3"
48
+ "@tanstack/ai": "^0.2.0"
52
49
  },
53
50
  "scripts": {
54
51
  "clean": "premove ./build ./dist",
package/src/use-chat.ts CHANGED
@@ -63,14 +63,14 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
63
63
  }) // Only run on mount - initialMessages are handled by ChatClient constructor
64
64
 
65
65
  // Cleanup on unmount: stop any in-flight requests
66
+ // Note: We use createEffect with a cleanup return to handle component unmount.
67
+ // The cleanup only runs on disposal (unmount), not on signal changes.
66
68
  createEffect(() => {
67
69
  return () => {
68
70
  // Stop any active generation when component unmounts
69
- if (isLoading()) {
70
- client().stop()
71
- }
71
+ client().stop()
72
72
  }
73
- }, [client, isLoading])
73
+ })
74
74
 
75
75
  // Note: Callback options (onResponse, onChunk, onFinish, onError, onToolCall)
76
76
  // are captured at client creation time. Changes to these callbacks require