@tanstack/ai-remix 0.4.3 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-remix",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "Remix 3 bindings for TanStack AI streaming chat, structured outputs, and media generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -47,11 +47,11 @@
47
47
  "README.md"
48
48
  ],
49
49
  "dependencies": {
50
- "@tanstack/ai-client": "^0.33.2"
50
+ "@tanstack/ai-client": "^0.35.1"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "remix": "^3.0.0-rc.1",
54
- "@tanstack/ai": "^0.58.0"
54
+ "@tanstack/ai": "^0.61.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^24.10.1",
@@ -60,7 +60,7 @@
60
60
  "remix": "^3.0.0-rc.1",
61
61
  "vite": "^8.2.1",
62
62
  "vitest": "^4.1.10",
63
- "@tanstack/ai": "0.58.0"
63
+ "@tanstack/ai": "0.61.0"
64
64
  },
65
65
  "scripts": {
66
66
  "lint:fix": "oxlint src --type-aware --fix",
@@ -245,10 +245,20 @@ export function createChat<
245
245
  client.mountDevtools()
246
246
  }
247
247
 
248
+ // Remix has no effects, so read `options.tools` before each call that can
249
+ // start a run. A getter such as `get tools() { return page.tools }` then
250
+ // sends the current tools.
251
+ const syncTools = () => {
252
+ if (options.tools !== undefined) {
253
+ client.updateOptions({ tools: options.tools })
254
+ }
255
+ }
256
+
248
257
  const sendMessage = async (
249
258
  content: string | MultimodalContent,
250
259
  sendOptions?: SendMessageOptions,
251
260
  ) => {
261
+ syncTools()
252
262
  try {
253
263
  await client.sendMessage(content, undefined, sendOptions)
254
264
  } finally {
@@ -259,6 +269,7 @@ export function createChat<
259
269
  const cancelQueued = (id: string) => client.cancelQueued(id)
260
270
 
261
271
  const append = async (message: ModelMessage | UIMessage<TTools>) => {
272
+ syncTools()
262
273
  try {
263
274
  await client.append(message)
264
275
  } finally {
@@ -267,6 +278,7 @@ export function createChat<
267
278
  }
268
279
 
269
280
  const reload = async () => {
281
+ syncTools()
270
282
  try {
271
283
  await client.reload()
272
284
  } finally {
@@ -300,6 +312,7 @@ export function createChat<
300
312
  state?: 'output-available' | 'output-error'
301
313
  errorText?: string
302
314
  }) => {
315
+ syncTools()
303
316
  await client.addToolResult(result)
304
317
  }
305
318
 
@@ -308,6 +321,7 @@ export function createChat<
308
321
  id: string
309
322
  approved: boolean
310
323
  }) => {
324
+ syncTools()
311
325
  await client.addToolApprovalResponse(response)
312
326
  syncResumeState()
313
327
  }
@@ -316,6 +330,7 @@ export function createChat<
316
330
  resumeItems: Array<RunAgentResumeItem>,
317
331
  state?: ChatResumeState,
318
332
  ) => {
333
+ syncTools()
319
334
  const result = await client.resumeInterrupts(resumeItems, state)
320
335
  syncResumeState()
321
336
  return result
@@ -328,6 +343,7 @@ export function createChat<
328
343
  interrupt: ResolvableChatInterrupt<TTools, TInterrupts>,
329
344
  ) => undefined),
330
345
  ) => {
346
+ syncTools()
331
347
  if (typeof resolution === 'boolean') {
332
348
  client.resolveInterrupts(resolution)
333
349
  } else {
@@ -340,13 +356,17 @@ export function createChat<
340
356
  }
341
357
 
342
358
  const retryInterrupts = () => {
359
+ syncTools()
343
360
  client.retryInterrupts()
344
361
  }
345
362
 
346
363
  const resumeInterruptsUnsafe = (
347
364
  resumeItems: Array<RunAgentResumeItem>,
348
365
  state?: ChatResumeState,
349
- ) => client.resumeInterruptsUnsafe(resumeItems, state)
366
+ ) => {
367
+ syncTools()
368
+ return client.resumeInterruptsUnsafe(resumeItems, state)
369
+ }
350
370
 
351
371
  function activeStructuredPart(): StructuredOutputPart | null {
352
372
  let lastUserIndex = -1
@@ -1,13 +1,14 @@
1
- import { registerWebMCPTools } from '@tanstack/ai-client'
1
+ import { registerWebMCPTools, subscribeWebMCPTools } from '@tanstack/ai-client'
2
2
  import type {
3
3
  AnyClientTool,
4
4
  InferredClientContext,
5
5
  RegisterWebMCPToolsOptions,
6
+ SubscribeWebMCPToolsOptions,
6
7
  } from '@tanstack/ai-client'
7
8
  import type { Handle } from 'remix/ui'
8
9
 
9
- /** Options for {@link createWebMCPTools}. */
10
- export type CreateWebMCPToolsOptions<
10
+ /** Options for {@link createRegisterWebMCPTools}. */
11
+ export type CreateRegisterWebMCPToolsOptions<
11
12
  TTools extends ReadonlyArray<AnyClientTool>,
12
13
  TContext = InferredClientContext<TTools>,
13
14
  > = Omit<RegisterWebMCPToolsOptions<TTools, TContext>, 'signal'> & {
@@ -15,13 +16,19 @@ export type CreateWebMCPToolsOptions<
15
16
  onError?: (error: unknown) => void
16
17
  }
17
18
 
18
- type CreateWebMCPToolsArguments<
19
+ /** @deprecated Use `CreateRegisterWebMCPToolsOptions`. Removed in 1.0.0. */
20
+ export type CreateWebMCPToolsOptions<
21
+ TTools extends ReadonlyArray<AnyClientTool>,
22
+ TContext = InferredClientContext<TTools>,
23
+ > = CreateRegisterWebMCPToolsOptions<TTools, TContext>
24
+
25
+ type CreateRegisterWebMCPToolsArguments<
19
26
  TTools extends ReadonlyArray<AnyClientTool>,
20
27
  TContext,
21
28
  > =
22
- {} extends CreateWebMCPToolsOptions<TTools, TContext>
23
- ? [options?: CreateWebMCPToolsOptions<TTools, TContext>]
24
- : [options: CreateWebMCPToolsOptions<TTools, TContext>]
29
+ {} extends CreateRegisterWebMCPToolsOptions<TTools, TContext>
30
+ ? [options?: CreateRegisterWebMCPToolsOptions<TTools, TContext>]
31
+ : [options: CreateRegisterWebMCPToolsOptions<TTools, TContext>]
25
32
 
26
33
  /**
27
34
  * Registers executable client tools with WebMCP for a Remix component.
@@ -35,18 +42,18 @@ type CreateWebMCPToolsArguments<
35
42
  * @example
36
43
  * ```tsx
37
44
  * function Products(handle: Handle) {
38
- * createWebMCPTools(handle, [searchProducts])
45
+ * createRegisterWebMCPTools(handle, [searchProducts])
39
46
  * return () => <ProductList />
40
47
  * }
41
48
  * ```
42
49
  */
43
- export function createWebMCPTools<
50
+ export function createRegisterWebMCPTools<
44
51
  const TTools extends ReadonlyArray<AnyClientTool>,
45
52
  TContext = InferredClientContext<TTools>,
46
53
  >(
47
54
  handle: Pick<Handle, 'signal'>,
48
55
  tools: TTools,
49
- ...[options]: CreateWebMCPToolsArguments<TTools, TContext>
56
+ ...[options]: CreateRegisterWebMCPToolsArguments<TTools, TContext>
50
57
  ) {
51
58
  void registerWebMCPTools(tools, {
52
59
  ...options,
@@ -57,3 +64,65 @@ export function createWebMCPTools<
57
64
  }
58
65
  })
59
66
  }
67
+
68
+ /**
69
+ * @deprecated Use `createRegisterWebMCPTools`. Removed in 1.0.0.
70
+ * @alias
71
+ */
72
+ export const createWebMCPTools = createRegisterWebMCPTools
73
+
74
+ /** Options for {@link createPageWebMCPTools}. */
75
+ export type CreatePageWebMCPToolsOptions = Omit<
76
+ SubscribeWebMCPToolsOptions,
77
+ 'signal'
78
+ >
79
+
80
+ /**
81
+ * Reads the WebMCP tools on the page as client tools for `createChat`.
82
+ *
83
+ * `tools` starts empty. When the page registers or removes a tool, the
84
+ * helper updates `tools` and calls `handle.update()`. The Handle signal stops
85
+ * the updates. Unsupported browsers and server rendering keep an empty array.
86
+ *
87
+ * @param handle - The Remix component Handle from setup.
88
+ * @param options - A filter that skips tools, and an error callback.
89
+ *
90
+ * @example
91
+ * ```tsx
92
+ * function Chat(handle: Handle) {
93
+ * const page = createPageWebMCPTools(handle)
94
+ * const chat = createChat(handle, {
95
+ * connection,
96
+ * get tools() {
97
+ * return page.tools
98
+ * },
99
+ * })
100
+ * return () => <Messages chat={chat} />
101
+ * }
102
+ * ```
103
+ */
104
+ export function createPageWebMCPTools(
105
+ handle: Pick<Handle, 'signal' | 'update'>,
106
+ options?: CreatePageWebMCPToolsOptions,
107
+ ) {
108
+ let tools: Array<AnyClientTool> = []
109
+
110
+ subscribeWebMCPTools(
111
+ (nextTools) => {
112
+ tools = nextTools
113
+ void handle.update()
114
+ },
115
+ {
116
+ signal: handle.signal,
117
+ filter: (tool) => options?.filter?.(tool) ?? true,
118
+ onError: (error) => options?.onError?.(error),
119
+ },
120
+ )
121
+
122
+ return {
123
+ /** The current page tools. */
124
+ get tools() {
125
+ return tools
126
+ },
127
+ }
128
+ }
package/src/index.ts CHANGED
@@ -3,8 +3,16 @@ export { createByok } from './create-byok.ts'
3
3
  export { createRealtimeChat } from './create-realtime-chat.ts'
4
4
  export { createMcpAppBridge } from './create-mcp-app-bridge.ts'
5
5
  export type { CreateMcpAppBridgeOptions } from './create-mcp-app-bridge.ts'
6
- export { createWebMCPTools } from './create-web-mcp-tools.ts'
7
- export type { CreateWebMCPToolsOptions } from './create-web-mcp-tools.ts'
6
+ export {
7
+ createPageWebMCPTools,
8
+ createRegisterWebMCPTools,
9
+ createWebMCPTools,
10
+ } from './create-web-mcp-tools.ts'
11
+ export type {
12
+ CreatePageWebMCPToolsOptions,
13
+ CreateRegisterWebMCPToolsOptions,
14
+ CreateWebMCPToolsOptions,
15
+ } from './create-web-mcp-tools.ts'
8
16
  export type {
9
17
  DeepPartial,
10
18
  CreateChatOptions,
@@ -97,6 +105,11 @@ export {
97
105
  PartialJSONParser,
98
106
  defaultJSONParser,
99
107
  registerWebMCPTools,
108
+ getWebMCPTools,
109
+ subscribeWebMCPTools,
110
+ type GetWebMCPToolsOptions,
111
+ type SubscribeWebMCPToolsOptions,
112
+ type WebMCPPageTool,
100
113
  clientTools,
101
114
  webSocket,
102
115
  fetchServerSentEvents,