@assistant-ui/core 0.2.8 → 0.2.10

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.
Files changed (123) hide show
  1. package/dist/model-context/tool.d.ts +1 -1
  2. package/dist/model-context/tool.js +1 -1
  3. package/dist/model-context/tool.js.map +1 -1
  4. package/dist/model-context/types.js +17 -2
  5. package/dist/model-context/types.js.map +1 -1
  6. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  7. package/dist/react/adapters/LocalStorageThreadListAdapter.js +12 -2
  8. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  9. package/dist/react/client/Tools.d.ts.map +1 -1
  10. package/dist/react/client/Tools.js +9 -3
  11. package/dist/react/client/Tools.js.map +1 -1
  12. package/dist/react/index.d.ts +8 -2
  13. package/dist/react/index.js +7 -1
  14. package/dist/react/model-context/define-mcp-toolkit.d.ts +12 -0
  15. package/dist/react/model-context/define-mcp-toolkit.d.ts.map +1 -0
  16. package/dist/react/model-context/define-mcp-toolkit.js +14 -0
  17. package/dist/react/model-context/define-mcp-toolkit.js.map +1 -0
  18. package/dist/react/model-context/define-toolkit.d.ts +21 -0
  19. package/dist/react/model-context/define-toolkit.d.ts.map +1 -0
  20. package/dist/react/model-context/define-toolkit.js +8 -0
  21. package/dist/react/model-context/define-toolkit.js.map +1 -0
  22. package/dist/react/model-context/hitl.d.ts +23 -0
  23. package/dist/react/model-context/hitl.d.ts.map +1 -0
  24. package/dist/react/model-context/hitl.js +26 -0
  25. package/dist/react/model-context/hitl.js.map +1 -0
  26. package/dist/react/model-context/makeAssistantTool.d.ts +8 -0
  27. package/dist/react/model-context/makeAssistantTool.d.ts.map +1 -1
  28. package/dist/react/model-context/makeAssistantTool.js +4 -0
  29. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  30. package/dist/react/model-context/makeAssistantToolUI.d.ts +8 -0
  31. package/dist/react/model-context/makeAssistantToolUI.d.ts.map +1 -1
  32. package/dist/react/model-context/makeAssistantToolUI.js +4 -0
  33. package/dist/react/model-context/makeAssistantToolUI.js.map +1 -1
  34. package/dist/react/model-context/provider-tool.d.ts +15 -0
  35. package/dist/react/model-context/provider-tool.d.ts.map +1 -0
  36. package/dist/react/model-context/provider-tool.js +12 -0
  37. package/dist/react/model-context/provider-tool.js.map +1 -0
  38. package/dist/react/model-context/stub-tool.d.ts +12 -0
  39. package/dist/react/model-context/stub-tool.d.ts.map +1 -0
  40. package/dist/react/model-context/stub-tool.js +15 -0
  41. package/dist/react/model-context/stub-tool.js.map +1 -0
  42. package/dist/react/model-context/toolbox.d.ts +62 -15
  43. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  44. package/dist/react/model-context/toolbox.js +19 -1
  45. package/dist/react/model-context/toolbox.js.map +1 -1
  46. package/dist/react/model-context/useAssistantTool.d.ts +11 -1
  47. package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
  48. package/dist/react/model-context/useAssistantTool.js +12 -6
  49. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  50. package/dist/react/model-context/useAssistantToolUI.d.ts +13 -4
  51. package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
  52. package/dist/react/model-context/useAssistantToolUI.js +6 -3
  53. package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
  54. package/dist/react/model-context/useAuiToolOverrides.d.ts +22 -0
  55. package/dist/react/model-context/useAuiToolOverrides.d.ts.map +1 -0
  56. package/dist/react/model-context/useAuiToolOverrides.js +31 -0
  57. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -0
  58. package/dist/react/primitives/part/PartMessages.d.ts +13 -11
  59. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  60. package/dist/react/primitives/part/PartMessages.js +13 -11
  61. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  62. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +1 -0
  63. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  64. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +28 -0
  65. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  66. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  67. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +9 -2
  68. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  69. package/dist/runtime/api/thread-list-item-runtime.d.ts +2 -0
  70. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  71. package/dist/runtime/api/thread-list-item-runtime.js +6 -0
  72. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  73. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +1 -0
  74. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  75. package/dist/runtimes/external-store/external-store-adapter.d.ts +2 -0
  76. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  77. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -0
  78. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  79. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +5 -0
  80. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  81. package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts +1 -0
  82. package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts.map +1 -1
  83. package/dist/runtimes/remote-thread-list/adapter/in-memory.js +3 -0
  84. package/dist/runtimes/remote-thread-list/adapter/in-memory.js.map +1 -1
  85. package/dist/runtimes/remote-thread-list/types.d.ts +1 -0
  86. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  87. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -0
  88. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  89. package/dist/store/scopes/thread-list-item.d.ts +1 -0
  90. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  91. package/package.json +5 -5
  92. package/src/model-context/tool.ts +1 -1
  93. package/src/model-context/types.ts +21 -3
  94. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +15 -2
  95. package/src/react/client/Tools.ts +22 -7
  96. package/src/react/index.ts +15 -2
  97. package/src/react/model-context/define-mcp-toolkit.ts +16 -0
  98. package/src/react/model-context/define-toolkit.test.ts +101 -0
  99. package/src/react/model-context/define-toolkit.ts +41 -0
  100. package/src/react/model-context/hitl.ts +27 -0
  101. package/src/react/model-context/makeAssistantTool.ts +8 -0
  102. package/src/react/model-context/makeAssistantToolUI.ts +8 -0
  103. package/src/react/model-context/provider-tool.ts +30 -0
  104. package/src/react/model-context/stub-tool.ts +14 -0
  105. package/src/react/model-context/toolbox.test.ts +182 -0
  106. package/src/react/model-context/toolbox.ts +189 -21
  107. package/src/react/model-context/useAssistantTool.ts +28 -8
  108. package/src/react/model-context/useAssistantToolUI.ts +13 -4
  109. package/src/react/model-context/useAuiToolOverrides.ts +38 -0
  110. package/src/react/primitives/part/PartMessages.tsx +13 -11
  111. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +43 -0
  112. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +9 -0
  113. package/src/runtime/api/thread-list-item-runtime.ts +15 -0
  114. package/src/runtime/interfaces/thread-list-runtime-core.ts +4 -0
  115. package/src/runtimes/external-store/external-store-adapter.ts +7 -0
  116. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +13 -0
  117. package/src/runtimes/remote-thread-list/adapter/in-memory.ts +4 -0
  118. package/src/runtimes/remote-thread-list/types.ts +4 -0
  119. package/src/store/clients/model-context-client.test.ts +87 -2
  120. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +1 -0
  121. package/src/store/scopes/thread-list-item.ts +1 -0
  122. package/src/tests/RemoteThreadListThreadListRuntimeCore-custom-metadata.test.ts +69 -1
  123. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/core",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Framework-agnostic core runtime for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -59,7 +59,7 @@
59
59
  ],
60
60
  "sideEffects": false,
61
61
  "dependencies": {
62
- "assistant-stream": "^0.3.18",
62
+ "assistant-stream": "^0.3.20",
63
63
  "nanoid": "^5.1.11"
64
64
  },
65
65
  "peerDependencies": {
@@ -67,7 +67,7 @@
67
67
  "@assistant-ui/tap": "^0.5.14",
68
68
  "@types/react": "*",
69
69
  "react": "^18 || ^19",
70
- "assistant-cloud": "^0.1.30",
70
+ "assistant-cloud": "^0.1.31",
71
71
  "zustand": "^5.0.11"
72
72
  },
73
73
  "peerDependenciesMeta": {
@@ -91,8 +91,8 @@
91
91
  "zustand": "^5.0.14",
92
92
  "@assistant-ui/store": "0.2.13",
93
93
  "@assistant-ui/tap": "0.5.14",
94
- "@assistant-ui/x-buildutils": "0.0.10",
95
- "assistant-cloud": "0.1.30"
94
+ "@assistant-ui/x-buildutils": "0.0.11",
95
+ "assistant-cloud": "0.1.31"
96
96
  },
97
97
  "publishConfig": {
98
98
  "access": "public",
@@ -7,7 +7,7 @@ import type { Tool } from "assistant-stream";
7
7
  * optional model-output conversion.
8
8
  *
9
9
  * This helper keeps reusable tool definitions type-checked and convenient to
10
- * export for a {@link Toolkit}, {@link Tools}, or {@link useAssistantTool}.
10
+ * export for a {@link Toolkit} registered with {@link Tools}.
11
11
  * Inference from parameter schemas is currently limited, so provide generic
12
12
  * arguments when you need precise args or result types.
13
13
  *
@@ -55,7 +55,10 @@ export const mergeModelContexts = (
55
55
  .map((c) => c.getModelContext())
56
56
  .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
57
57
 
58
+ const toolPriorities: Record<string, number> = {};
59
+
58
60
  return configs.reduce((acc, config) => {
61
+ const priority = config.priority ?? 0;
59
62
  if (config.system) {
60
63
  if (acc.system) {
61
64
  acc.system += `\n\n${config.system}`;
@@ -67,13 +70,28 @@ export const mergeModelContexts = (
67
70
  for (const [name, tool] of Object.entries(config.tools)) {
68
71
  const existing = acc.tools?.[name];
69
72
  if (existing && existing !== tool) {
70
- throw new Error(
71
- `You tried to define a tool with the name ${name}, but it already exists.`,
72
- );
73
+ const existingPriority = toolPriorities[name]!;
74
+ if (existingPriority === priority) {
75
+ throw new Error(
76
+ `You tried to define a tool with the name ${name}, but it already exists.`,
77
+ );
78
+ }
79
+
80
+ const higherPriorityTool =
81
+ existingPriority > priority ? existing : tool;
82
+ const lowerPriorityTool =
83
+ existingPriority > priority ? tool : existing;
84
+ acc.tools![name] = {
85
+ ...lowerPriorityTool,
86
+ ...higherPriorityTool,
87
+ } as Tool<any, any>;
88
+ toolPriorities[name] = Math.max(existingPriority, priority);
89
+ continue;
73
90
  }
74
91
 
75
92
  if (!acc.tools) acc.tools = {};
76
93
  acc.tools[name] = tool;
94
+ toolPriorities[name] ??= priority;
77
95
  }
78
96
  }
79
97
  if (config.config) {
@@ -28,13 +28,12 @@ type LocalStorageAdapterOptions = {
28
28
  titleGenerator?: TitleGenerationAdapter | undefined;
29
29
  };
30
30
 
31
- // `RemoteThreadMetadata.custom` is intentionally not persisted; consumers that
32
- // need it across reloads should fork this adapter or use a remote backend.
33
31
  type StoredThreadMetadata = {
34
32
  remoteId: string;
35
33
  externalId?: string;
36
34
  status: "regular" | "archived";
37
35
  title?: string;
36
+ custom?: Record<string, unknown> | undefined;
38
37
  };
39
38
 
40
39
  class AsyncStorageHistoryAdapter implements ThreadHistoryAdapter {
@@ -131,6 +130,7 @@ export const createLocalStorageAdapter = (
131
130
  externalId: t.externalId,
132
131
  status: t.status,
133
132
  title: t.title,
133
+ custom: t.custom,
134
134
  })),
135
135
  };
136
136
  },
@@ -162,6 +162,18 @@ export const createLocalStorageAdapter = (
162
162
  }
163
163
  },
164
164
 
165
+ async updateCustom(
166
+ remoteId: string,
167
+ custom: Record<string, unknown> | undefined,
168
+ ): Promise<void> {
169
+ const threads = await loadThreadMetadata();
170
+ const thread = threads.find((t) => t.remoteId === remoteId);
171
+ if (thread) {
172
+ thread.custom = custom;
173
+ await saveThreadMetadata(threads);
174
+ }
175
+ },
176
+
165
177
  async archive(remoteId: string): Promise<void> {
166
178
  const threads = await loadThreadMetadata();
167
179
  const thread = threads.find((t) => t.remoteId === remoteId);
@@ -196,6 +208,7 @@ export const createLocalStorageAdapter = (
196
208
  externalId: thread.externalId,
197
209
  status: thread.status,
198
210
  title: thread.title,
211
+ custom: thread.custom,
199
212
  };
200
213
  },
201
214
 
@@ -17,6 +17,7 @@ import type { McpAppResourceOutput, ToolsState } from "../types/scopes/tools";
17
17
  import type { Tool } from "assistant-stream";
18
18
  import {
19
19
  isStandaloneToolDisplay,
20
+ makeToolCallTextComponent,
20
21
  type Toolkit,
21
22
  } from "../model-context/toolbox";
22
23
  import type { ToolCallMessagePartComponent } from "../types/MessagePartComponentTypes";
@@ -106,22 +107,36 @@ export const Tools = resource(
106
107
 
107
108
  // Register tool UIs (exclude symbols)
108
109
  for (const [toolName, tool] of Object.entries(toolkit)) {
109
- if (tool.render) {
110
+ const toolRender = "render" in tool ? tool.render : undefined;
111
+ const toolRenderText =
112
+ "renderText" in tool ? tool.renderText : undefined;
113
+ const render =
114
+ toolRender ??
115
+ (toolRenderText
116
+ ? makeToolCallTextComponent(toolRenderText)
117
+ : undefined);
118
+ if (render) {
110
119
  unsubscribes.push(
111
- setToolUI(toolName, tool.render, {
120
+ setToolUI(toolName, render, {
112
121
  standalone: isStandaloneToolDisplay(tool),
113
122
  }),
114
123
  );
115
124
  }
116
125
  }
117
126
 
118
- // Register tools with model context (exclude symbols). `render` and
119
- // `display` are client-only presentation concerns and never reach the
120
- // model.
127
+ // Register tools with model context (exclude symbols). `render`,
128
+ // `renderText`, and `display` are client-only presentation concerns and
129
+ // never reach the model.
121
130
  const toolsWithoutRender = Object.entries(toolkit).reduce(
122
131
  (acc, [name, tool]) => {
123
- const { render, display, ...rest } = tool;
124
- acc[name] = rest;
132
+ if (tool.type === "mcp") return acc;
133
+ const {
134
+ display: _display,
135
+ render: _render,
136
+ renderText: _renderText,
137
+ ...rest
138
+ } = tool as typeof tool & { renderText?: unknown };
139
+ acc[name] = rest as Tool<any, any>;
125
140
  return acc;
126
141
  },
127
142
  {} as Record<string, Tool<any, any>>,
@@ -35,9 +35,22 @@ export { useInlineRender } from "./model-context/useInlineRender";
35
35
  export {
36
36
  type Toolkit,
37
37
  type ToolDefinition,
38
- type ToolkitDeclaration,
39
- type ToolkitDeclarationDefinition,
38
+ type ToolkitDefinition,
39
+ type ToolkitDefinitionEntry,
40
+ type ToolCallText,
40
41
  } from "./model-context/toolbox";
42
+ export { defineToolkit } from "./model-context/define-toolkit";
43
+ export { stubTool } from "./model-context/stub-tool";
44
+ export { useAuiToolOverrides } from "./model-context/useAuiToolOverrides";
45
+ export { hitl, hitlTool } from "./model-context/hitl";
46
+ export {
47
+ providerTool,
48
+ type ProviderToolConfig,
49
+ } from "./model-context/provider-tool";
50
+ export {
51
+ defineMcpToolkit,
52
+ type McpToolkitDefinition,
53
+ } from "./model-context/define-mcp-toolkit";
41
54
  export {
42
55
  useAssistantInteractable,
43
56
  type AssistantInteractableProps,
@@ -0,0 +1,16 @@
1
+ import type { McpServerConfig } from "assistant-stream";
2
+ import type { Toolkit } from "./toolbox";
3
+
4
+ export type McpToolkitDefinition = Record<string, McpServerConfig>;
5
+
6
+ /**
7
+ * Defines MCP server tools as a spreadable toolkit fragment.
8
+ */
9
+ export function defineMcpToolkit(definition: McpToolkitDefinition): Toolkit {
10
+ return Object.fromEntries(
11
+ Object.entries(definition).map(([name, server]) => [
12
+ name,
13
+ { type: "mcp", server },
14
+ ]),
15
+ ) as Toolkit;
16
+ }
@@ -0,0 +1,101 @@
1
+ import { describe, it, expect, expectTypeOf } from "vitest";
2
+ import type { AsyncIterableStream } from "assistant-stream/utils";
3
+ import { defineToolkit } from "./define-toolkit";
4
+ import { hitl, hitlTool } from "./hitl";
5
+ import { providerTool } from "./provider-tool";
6
+ import { stubTool } from "./stub-tool";
7
+ import type { ToolkitDefinition } from "./toolbox";
8
+
9
+ type TestStandardSchema<T> = {
10
+ readonly "~standard": {
11
+ readonly version: 1;
12
+ readonly vendor: "test";
13
+ readonly types?: {
14
+ readonly input: T;
15
+ readonly output: T;
16
+ };
17
+ readonly validate: (value: unknown) => { readonly value: T };
18
+ };
19
+ };
20
+
21
+ const checkDefineToolkitTypes = () => {
22
+ defineToolkit({
23
+ search: {
24
+ parameters: {} as TestStandardSchema<{
25
+ query: string;
26
+ limit?: number;
27
+ tags: string[];
28
+ }>,
29
+ execute: async ({
30
+ query,
31
+ limit,
32
+ }: {
33
+ query: string;
34
+ limit?: number;
35
+ tags: string[];
36
+ }) => ({
37
+ ids: [query],
38
+ count: limit ?? 0,
39
+ }),
40
+ streamCall: async (reader) => {
41
+ const query = await reader.args.get("query");
42
+ expectTypeOf(query).toEqualTypeOf<string>();
43
+
44
+ expectTypeOf(reader.args.streamValues("query")).toEqualTypeOf<
45
+ AsyncIterableStream<string>
46
+ >();
47
+ expectTypeOf(reader.args.streamText("query")).toEqualTypeOf<
48
+ AsyncIterableStream<unknown>
49
+ >();
50
+ expectTypeOf(reader.args.forEach("tags")).toEqualTypeOf<
51
+ AsyncIterableStream<string>
52
+ >();
53
+
54
+ const response = await reader.response.get();
55
+ expectTypeOf(response.result).toEqualTypeOf<unknown>();
56
+
57
+ // @ts-expect-error unknown argument paths should not be accepted
58
+ reader.args.get("missing");
59
+ },
60
+ },
61
+ });
62
+ };
63
+ expectTypeOf(checkDefineToolkitTypes).toEqualTypeOf<() => void>();
64
+
65
+ const checkToolkitDefinitionTypes = () => {
66
+ ({
67
+ invalidMcp: {
68
+ // @ts-expect-error MCP-shaped tools cannot also declare an execute callback
69
+ server: { type: "http", url: "https://example.com/mcp" },
70
+ execute: async () => "invalid",
71
+ },
72
+ }) satisfies ToolkitDefinition;
73
+ };
74
+ expectTypeOf(checkToolkitDefinitionTypes).toEqualTypeOf<() => void>();
75
+
76
+ describe("use-generative markers", () => {
77
+ it("defineToolkit throws at runtime — it must be stripped by the compiler, never called", () => {
78
+ expect(() => defineToolkit({})).toThrow(/no runtime implementation/);
79
+ });
80
+
81
+ it("hitlTool throws at runtime — it must be stripped by the compiler, never called", () => {
82
+ expect(() => hitlTool()).toThrow(/no runtime implementation/);
83
+ });
84
+
85
+ it("hitl remains a compatibility alias", () => {
86
+ expect(hitl).toBe(hitlTool);
87
+ });
88
+
89
+ it("providerTool throws at runtime — it must be stripped by the compiler, never called", () => {
90
+ expect(() =>
91
+ providerTool({
92
+ providerId: "openai.web_search_preview",
93
+ args: {},
94
+ }),
95
+ ).toThrow(/no runtime implementation/);
96
+ });
97
+
98
+ it("stubTool throws at runtime — it must be stripped by the compiler, never called", () => {
99
+ expect(() => stubTool()).toThrow(/no runtime implementation/);
100
+ });
101
+ });
@@ -0,0 +1,41 @@
1
+ import type {
2
+ Toolkit,
3
+ ToolkitDefinition,
4
+ ToolkitDefinitionEntryWithParameters,
5
+ } from "./toolbox";
6
+
7
+ /**
8
+ * Authoring helper for a `"use generative"` toolkit. Accepts the permissive
9
+ * {@link ToolkitDefinition} (a `backend` tool may carry its server `execute`)
10
+ * and types the result as the canonical {@link Toolkit}.
11
+ *
12
+ * It has **no runtime implementation**. A `"use generative"` compiler (e.g.
13
+ * `@assistant-ui/next` or `@assistant-ui/vite`) strips the `defineToolkit(...)`
14
+ * wrapper (and its import) per build, so a correctly compiled
15
+ * `export default defineToolkit({...})` never calls this. If it *does* run, the
16
+ * module was not compiled by a use-generative loader — e.g. `defineToolkit` used
17
+ * outside a `"use generative"` file — which would ship a backend `execute` to the
18
+ * client. So it throws instead of silently leaking.
19
+ */
20
+ export function defineToolkit<
21
+ TArgsByName extends {
22
+ [K in keyof TArgsByName]: Record<string, unknown>;
23
+ },
24
+ TResultByName extends { [K in keyof TArgsByName]: unknown } = {
25
+ [K in keyof TArgsByName]: unknown;
26
+ },
27
+ >(_definition: {
28
+ [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<
29
+ TArgsByName[K],
30
+ TResultByName[K]
31
+ >;
32
+ }): Toolkit;
33
+ export function defineToolkit(_definition: ToolkitDefinition): Toolkit;
34
+ export function defineToolkit(_definition: ToolkitDefinition): Toolkit {
35
+ throw new Error(
36
+ "[assistant-ui] defineToolkit() has no runtime implementation — it is " +
37
+ "stripped at build time by the use-generative compiler. Reaching it means " +
38
+ 'this module was not compiled (e.g. defineToolkit used outside a "use ' +
39
+ 'generative" file). Add the directive, or do not use defineToolkit here.',
40
+ );
41
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Marks a tool as **human-in-the-loop**: the agent pauses and the UI (`render`)
3
+ * supplies the result instead of code. Use it as the tool's `execute`:
4
+ *
5
+ * ```tsx
6
+ * confirm: { execute: hitlTool(), render: (props) => <Confirm {...props} /> }
7
+ * ```
8
+ *
9
+ * Like {@link defineToolkit}, it has **no runtime implementation**: a
10
+ * `"use generative"` compiler (e.g. `@assistant-ui/next` or `@assistant-ui/vite`)
11
+ * detects `execute: hitlTool()`, drops it, and stamps the tool `type: "human"`.
12
+ * Reaching it at runtime means the module wasn't compiled (used outside a
13
+ * `"use generative"` file), so it throws.
14
+ */
15
+ export function hitlTool(): never {
16
+ throw new Error(
17
+ "[assistant-ui] hitlTool() has no runtime implementation — it marks a " +
18
+ "human-in-the-loop tool and is stripped at build time by the " +
19
+ "use-generative compiler. Reaching it means this module was not compiled " +
20
+ '(e.g. hitlTool() used outside a "use generative" file).',
21
+ );
22
+ }
23
+
24
+ /**
25
+ * @deprecated Use {@link hitlTool}.
26
+ */
27
+ export const hitl = hitlTool;
@@ -6,6 +6,10 @@ import { type AssistantToolProps, useAssistantTool } from "./useAssistantTool";
6
6
  *
7
7
  * Rendering the component registers its tool for the lifetime of that render
8
8
  * subtree.
9
+ *
10
+ * @deprecated Use a toolkit with `Tools({ toolkit })` and register it via
11
+ * `useAui({ tools: Tools({ toolkit }) })` instead. See
12
+ * https://assistant-ui.com/docs/migrations/toolkit-tools.
9
13
  */
10
14
  export type AssistantTool = FC & {
11
15
  /** Tool definition registered by this component. */
@@ -19,6 +23,10 @@ export type AssistantTool = FC & {
19
23
  * rather than calling {@link useAssistantTool} directly.
20
24
  *
21
25
  * @param tool - Tool definition and name to register.
26
+ *
27
+ * @deprecated Use a toolkit with `Tools({ toolkit })` and register it via
28
+ * `useAui({ tools: Tools({ toolkit }) })` instead. See
29
+ * https://assistant-ui.com/docs/migrations/toolkit-tools.
22
30
  */
23
31
  export const makeAssistantTool = <
24
32
  TArgs extends Record<string, unknown>,
@@ -9,6 +9,10 @@ import {
9
9
  *
10
10
  * Rendering the component registers a renderer for matching tool-call message
11
11
  * parts.
12
+ *
13
+ * @deprecated Put `render`/`renderText` on the matching toolkit entry, or use
14
+ * `MessagePrimitive.Parts` inline tool render overrides for per-message UI.
15
+ * See https://assistant-ui.com/docs/migrations/toolkit-tools.
12
16
  */
13
17
  export type AssistantToolUI = FC & {
14
18
  /** Tool renderer registered by this component. */
@@ -22,6 +26,10 @@ export type AssistantToolUI = FC & {
22
26
  * are registered elsewhere.
23
27
  *
24
28
  * @param tool - Tool renderer registration.
29
+ *
30
+ * @deprecated Put `render`/`renderText` on the matching toolkit entry, or use
31
+ * `MessagePrimitive.Parts` inline tool render overrides for per-message UI.
32
+ * See https://assistant-ui.com/docs/migrations/toolkit-tools.
25
33
  */
26
34
  export const makeAssistantToolUI = <TArgs, TResult>(
27
35
  tool: AssistantToolUIProps<TArgs, TResult>,
@@ -0,0 +1,30 @@
1
+ import type { Tool } from "assistant-stream";
2
+
3
+ type ProviderToolDefinition<TArgs extends Record<string, unknown>> = Extract<
4
+ Tool<TArgs, unknown>,
5
+ { type: "provider" }
6
+ >;
7
+
8
+ export type ProviderToolConfig<
9
+ TArgs extends Record<string, unknown> = Record<string, unknown>,
10
+ > = Pick<
11
+ ProviderToolDefinition<TArgs>,
12
+ | "providerId"
13
+ | "args"
14
+ | "parameters"
15
+ | "providerOptions"
16
+ | "supportsDeferredResults"
17
+ >;
18
+
19
+ /**
20
+ * Marks a tool as provider-executed. The use-generative compiler converts
21
+ * `execute: providerTool(...)` into a `type: "provider"` tool entry.
22
+ */
23
+ export function providerTool(_config: ProviderToolConfig): never {
24
+ throw new Error(
25
+ "[assistant-ui] providerTool() has no runtime implementation — it marks a " +
26
+ "provider-executed tool and is stripped at build time by the " +
27
+ "use-generative compiler. Reaching it means this module was not compiled " +
28
+ '(e.g. providerTool() used outside a "use generative" file).',
29
+ );
30
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Marks a generative toolkit entry as a frontend tool whose executor will be
3
+ * supplied by `useAuiToolOverrides(...)`.
4
+ *
5
+ * `stubTool()` has no runtime implementation. It must be used inside a
6
+ * `"use generative"` toolkit file so the compiler can strip it.
7
+ */
8
+ export function stubTool(): never {
9
+ throw new Error(
10
+ "[assistant-ui] stubTool() has no runtime implementation - it marks a " +
11
+ "tool executor that must be supplied via useAuiToolOverrides(...). Make " +
12
+ 'sure this module is compiled as "use generative".',
13
+ );
14
+ }
@@ -0,0 +1,182 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { makeToolCallTextComponent } from "./toolbox";
3
+
4
+ describe("makeToolCallTextComponent", () => {
5
+ it("renders static running and complete text", () => {
6
+ const Render = makeToolCallTextComponent({
7
+ running: "Searching...",
8
+ complete: "Done searching",
9
+ });
10
+
11
+ expect(
12
+ Render({
13
+ type: "tool-call",
14
+ toolCallId: "call-1",
15
+ toolName: "search",
16
+ args: {},
17
+ argsText: "{}",
18
+ status: { type: "running" },
19
+ addResult: () => {},
20
+ resume: () => {},
21
+ respondToApproval: () => {},
22
+ }),
23
+ ).toBe("Searching...");
24
+
25
+ expect(
26
+ Render({
27
+ type: "tool-call",
28
+ toolCallId: "call-1",
29
+ toolName: "search",
30
+ args: {},
31
+ argsText: "{}",
32
+ result: "ok",
33
+ status: { type: "complete" },
34
+ addResult: () => {},
35
+ resume: () => {},
36
+ respondToApproval: () => {},
37
+ }),
38
+ ).toBe("Done searching");
39
+ });
40
+
41
+ it("passes args and result to dynamic text functions", () => {
42
+ const Render = makeToolCallTextComponent<
43
+ { query: string },
44
+ { count: number }
45
+ >({
46
+ running: ({ args }) => `Searching ${args.query}...`,
47
+ complete: ({ args, result }) =>
48
+ `Found ${result?.count ?? 0} results for ${args.query}`,
49
+ });
50
+
51
+ expect(
52
+ Render({
53
+ type: "tool-call",
54
+ toolCallId: "call-1",
55
+ toolName: "search",
56
+ args: { query: "docs" },
57
+ argsText: '{"query":"docs"}',
58
+ status: { type: "running" },
59
+ addResult: () => {},
60
+ resume: () => {},
61
+ respondToApproval: () => {},
62
+ }),
63
+ ).toBe("Searching docs...");
64
+
65
+ expect(
66
+ Render({
67
+ type: "tool-call",
68
+ toolCallId: "call-1",
69
+ toolName: "search",
70
+ args: { query: "docs" },
71
+ argsText: '{"query":"docs"}',
72
+ result: { count: 3 },
73
+ status: { type: "complete" },
74
+ addResult: () => {},
75
+ resume: () => {},
76
+ respondToApproval: () => {},
77
+ }),
78
+ ).toBe("Found 3 results for docs");
79
+ });
80
+
81
+ it("treats missing status as complete", () => {
82
+ const Render = makeToolCallTextComponent({
83
+ running: "Running",
84
+ complete: "Complete",
85
+ });
86
+
87
+ expect(
88
+ Render({
89
+ type: "tool-call",
90
+ toolCallId: "call-1",
91
+ toolName: "search",
92
+ args: {},
93
+ argsText: "{}",
94
+ addResult: () => {},
95
+ resume: () => {},
96
+ respondToApproval: () => {},
97
+ }),
98
+ ).toBe("Complete");
99
+ });
100
+
101
+ it("treats incomplete status as a terminal state", () => {
102
+ const Render = makeToolCallTextComponent({
103
+ running: "Searching...",
104
+ complete: "Finished",
105
+ });
106
+
107
+ expect(
108
+ Render({
109
+ type: "tool-call",
110
+ toolCallId: "call-1",
111
+ toolName: "search",
112
+ args: {},
113
+ argsText: "{}",
114
+ status: { type: "incomplete", reason: "error" },
115
+ addResult: () => {},
116
+ resume: () => {},
117
+ respondToApproval: () => {},
118
+ }),
119
+ ).toBe("Finished");
120
+ });
121
+
122
+ it("treats requires-action status as running", () => {
123
+ const Render = makeToolCallTextComponent({
124
+ running: "Waiting for approval...",
125
+ complete: "Done",
126
+ });
127
+
128
+ expect(
129
+ Render({
130
+ type: "tool-call",
131
+ toolCallId: "call-1",
132
+ toolName: "confirm",
133
+ args: {},
134
+ argsText: "{}",
135
+ status: { type: "requires-action", reason: "interrupt" },
136
+ addResult: () => {},
137
+ resume: () => {},
138
+ respondToApproval: () => {},
139
+ }),
140
+ ).toBe("Waiting for approval...");
141
+ });
142
+
143
+ it("renders null for terminal status when only running text is provided", () => {
144
+ const Render = makeToolCallTextComponent({
145
+ running: "Searching...",
146
+ });
147
+
148
+ expect(
149
+ Render({
150
+ type: "tool-call",
151
+ toolCallId: "call-1",
152
+ toolName: "search",
153
+ args: {},
154
+ argsText: "{}",
155
+ status: { type: "complete" },
156
+ addResult: () => {},
157
+ resume: () => {},
158
+ respondToApproval: () => {},
159
+ }),
160
+ ).toBeNull();
161
+ });
162
+
163
+ it("renders null for running status when only complete text is provided", () => {
164
+ const Render = makeToolCallTextComponent({
165
+ complete: "Done",
166
+ });
167
+
168
+ expect(
169
+ Render({
170
+ type: "tool-call",
171
+ toolCallId: "call-1",
172
+ toolName: "search",
173
+ args: {},
174
+ argsText: "{}",
175
+ status: { type: "running" },
176
+ addResult: () => {},
177
+ resume: () => {},
178
+ respondToApproval: () => {},
179
+ }),
180
+ ).toBeNull();
181
+ });
182
+ });