ai-tool-set 0.1.0-alpha.0 → 0.1.0-alpha.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/README.md +189 -140
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,10 +15,10 @@ This library provides an immutable and type-safe API to manage [`activeTools`](h
|
|
|
15
15
|
|
|
16
16
|
The AI SDK provides an `activeTools` parameter to control which tools the model can use at any given time. However, managing tool activation becomes complex when you need to:
|
|
17
17
|
|
|
18
|
-
- **
|
|
18
|
+
- **Statically activate/deactivate tools**: Some tools should be inactive by default and only available after being explicitly activated
|
|
19
19
|
- **Dynamically infer tool activation**: Some tools should be activated based on runtime context like the conversation history
|
|
20
20
|
|
|
21
|
-
This library wraps standard AI SDK `tool()` definitions with activation
|
|
21
|
+
This library wraps standard AI SDK `tool()` definitions with chainable activation methods and resolves `tools` and `activeTools` for any AI SDK function.
|
|
22
22
|
|
|
23
23
|
### Installation
|
|
24
24
|
|
|
@@ -30,53 +30,35 @@ npm install ai-tool-set
|
|
|
30
30
|
|
|
31
31
|
### Creating a Tool Set
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Pass a plain record of AI SDK `tool()` definitions to `createToolSet()`. All tools are active by default.
|
|
34
34
|
|
|
35
35
|
```typescript
|
|
36
36
|
import { tool } from 'ai';
|
|
37
37
|
import { z } from 'zod';
|
|
38
38
|
import { createToolSet } from 'ai-tool-set';
|
|
39
39
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
execute: async ({ query }) => searchProducts(query),
|
|
46
|
-
}),
|
|
47
|
-
|
|
48
|
-
// Explicitly inactive — must be activated manually
|
|
49
|
-
list_orders: {
|
|
50
|
-
tool: tool({
|
|
51
|
-
description: 'List orders for a customer',
|
|
52
|
-
inputSchema: z.object({ customerId: z.string() }),
|
|
53
|
-
execute: async ({ customerId }) => listOrders(customerId),
|
|
54
|
-
}),
|
|
55
|
-
active: false,
|
|
56
|
-
},
|
|
40
|
+
const search = tool({
|
|
41
|
+
description: 'Search for products',
|
|
42
|
+
inputSchema: z.object({ query: z.string() }),
|
|
43
|
+
execute: async ({ query }) => searchProducts(query),
|
|
44
|
+
});
|
|
57
45
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
m.parts.some(
|
|
69
|
-
(p) =>
|
|
70
|
-
p.type === 'tool-list_orders' &&
|
|
71
|
-
p.state === 'output-available' &&
|
|
72
|
-
p.output.orders?.some((order) => order.status !== 'fulfilled'),
|
|
73
|
-
),
|
|
74
|
-
),
|
|
75
|
-
},
|
|
46
|
+
const list_orders = tool({
|
|
47
|
+
description: 'List orders for a customer',
|
|
48
|
+
inputSchema: z.object({ customerId: z.string() }),
|
|
49
|
+
execute: async ({ customerId }) => listOrders(customerId),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const cancel_order = tool({
|
|
53
|
+
description: 'Cancel an order',
|
|
54
|
+
inputSchema: z.object({ orderId: z.string() }),
|
|
55
|
+
execute: async ({ orderId }) => cancelOrder(orderId),
|
|
76
56
|
});
|
|
57
|
+
|
|
58
|
+
const toolSet = createToolSet({ search, list_orders, cancel_order });
|
|
77
59
|
```
|
|
78
60
|
|
|
79
|
-
### Use with `generateText` / `streamText`
|
|
61
|
+
### Use with `generateText` / `streamText`
|
|
80
62
|
|
|
81
63
|
The tool set exposes `tools` and `activeTools` as properties that can be used directly:
|
|
82
64
|
|
|
@@ -97,13 +79,12 @@ const result = await generateText({
|
|
|
97
79
|
|
|
98
80
|
### Activate and Deactivate Tools
|
|
99
81
|
|
|
100
|
-
Use
|
|
82
|
+
Use `.activate()` and `.deactivate()` to statically control which tools are available:
|
|
101
83
|
|
|
102
84
|
```typescript
|
|
103
|
-
// Active tools will include 'list_orders' and exclude 'search'
|
|
104
85
|
const { tools, activeTools } = toolSet
|
|
105
|
-
.
|
|
106
|
-
.
|
|
86
|
+
.deactivate(['cancel_order'])
|
|
87
|
+
.activate(['list_orders']);
|
|
107
88
|
|
|
108
89
|
const result = await generateText({
|
|
109
90
|
model,
|
|
@@ -113,9 +94,32 @@ const result = await generateText({
|
|
|
113
94
|
});
|
|
114
95
|
```
|
|
115
96
|
|
|
116
|
-
###
|
|
97
|
+
### Conditional Activation
|
|
98
|
+
|
|
99
|
+
Use `.activateWhen()` and `.deactivateWhen()` to conditionally control tools based on messages and context. The predicate receives an `ActivationInput` with `messages` and `context`.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const { tools, activeTools } = toolSet
|
|
103
|
+
.activateWhen('cancel_order', ({ messages }) =>
|
|
104
|
+
messages.some((m) =>
|
|
105
|
+
m.parts.some(
|
|
106
|
+
(p) =>
|
|
107
|
+
p.type === 'tool-list_orders' &&
|
|
108
|
+
p.state === 'output-available' &&
|
|
109
|
+
p.output.orders?.some((order) => order.status !== 'fulfilled'),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const result = await generateText({
|
|
115
|
+
model,
|
|
116
|
+
tools,
|
|
117
|
+
activeTools,
|
|
118
|
+
prompt: 'Cancel my order #1001',
|
|
119
|
+
});
|
|
120
|
+
```
|
|
117
121
|
|
|
118
|
-
Use
|
|
122
|
+
Use `.inferTools()` to evaluate predicates with the current messages and context:
|
|
119
123
|
|
|
120
124
|
```typescript
|
|
121
125
|
const messages = [
|
|
@@ -125,104 +129,137 @@ const messages = [
|
|
|
125
129
|
},
|
|
126
130
|
{
|
|
127
131
|
role: 'assistant',
|
|
128
|
-
parts: [
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
],
|
|
139
|
-
},
|
|
132
|
+
parts: [{
|
|
133
|
+
type: 'tool-list_orders',
|
|
134
|
+
state: 'output-available',
|
|
135
|
+
toolCallId: 'call-1',
|
|
136
|
+
input: { customerId: 'cust-123' },
|
|
137
|
+
output: {
|
|
138
|
+
orders: [
|
|
139
|
+
{ orderId: '1000', status: 'fulfilled' },
|
|
140
|
+
{ orderId: '1001', status: 'pending' },
|
|
141
|
+
],
|
|
140
142
|
},
|
|
141
|
-
],
|
|
143
|
+
}],
|
|
142
144
|
},
|
|
143
145
|
];
|
|
144
146
|
|
|
145
|
-
//
|
|
146
|
-
const { tools, activeTools } = toolSet.inferTools({ messages });
|
|
147
|
+
// cancel_order is now active because list_orders returned unfulfilled orders
|
|
148
|
+
const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
|
|
147
149
|
|
|
148
|
-
const result = await generateText({
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
const result = await generateText({ model, tools, activeTools, messages });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
You can also activate multiple tools at once:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const toolSet = createToolSet({ search, list_orders, cancel_order })
|
|
157
|
+
.activateWhen({
|
|
158
|
+
list_orders: ({ context }) => context.isAuthenticated,
|
|
159
|
+
cancel_order: ({ messages }) => hasUnfulfilledOrders(messages),
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Last-Call Wins
|
|
164
|
+
|
|
165
|
+
Each method appends to an internal list. For each tool, the **last entry** determines its state. This makes ordering explicit and predictable:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const toolSet = createToolSet({ search, list_orders, cancel_order })
|
|
169
|
+
.activate(['cancel_order']) // cancel_order: activated
|
|
170
|
+
.deactivate(['cancel_order']) // cancel_order: deactivated
|
|
171
|
+
.activateWhen('cancel_order', ({ messages }) => // cancel_order: conditional activation
|
|
172
|
+
hasUnfulfilledOrders(messages),
|
|
173
|
+
);
|
|
154
174
|
```
|
|
155
175
|
|
|
156
176
|
### Immutable
|
|
157
177
|
|
|
158
|
-
All methods return new tool set instances
|
|
178
|
+
All methods return new tool set instances. The original is never modified:
|
|
159
179
|
|
|
160
180
|
```typescript
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
181
|
+
const base = createToolSet({ search, list_orders, cancel_order });
|
|
182
|
+
const withoutCancel = base.deactivate(['cancel_order']);
|
|
183
|
+
const withCancel = withoutCancel.activate(['cancel_order']);
|
|
164
184
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
185
|
+
base.activeTools; // ['search', 'list_orders', 'cancel_order']
|
|
186
|
+
withoutCancel.activeTools; // ['search', 'list_orders']
|
|
187
|
+
withCancel.activeTools; // ['search', 'list_orders', 'cancel_order']
|
|
168
188
|
```
|
|
169
189
|
|
|
170
|
-
### Typed UI
|
|
190
|
+
### Typed UI Tool Set
|
|
171
191
|
|
|
172
192
|
Use `InferUIToolSet` to get fully typed UI messages from your tool set:
|
|
173
193
|
|
|
174
194
|
```typescript
|
|
175
195
|
import type { UIMessage } from 'ai';
|
|
176
|
-
import type { InferUIToolSet
|
|
177
|
-
|
|
178
|
-
const tools = {
|
|
179
|
-
search: tool({ ... }),
|
|
180
|
-
list_orders: {
|
|
181
|
-
tool: tool({ ... }),
|
|
182
|
-
active: false,
|
|
183
|
-
},
|
|
184
|
-
cancel_order: {
|
|
185
|
-
tool: tool({ ... }),
|
|
186
|
-
active: ({ messages }) => /* callback logic */,
|
|
187
|
-
},
|
|
188
|
-
} satisfies ToolSetConfig;
|
|
196
|
+
import type { InferUIToolSet } from 'ai-tool-set';
|
|
189
197
|
|
|
198
|
+
const tools = { search, list_orders, cancel_order };
|
|
190
199
|
const toolSet = createToolSet(tools);
|
|
191
200
|
|
|
192
|
-
//
|
|
193
|
-
type
|
|
201
|
+
// From the tools record
|
|
202
|
+
type MyToolSet = InferUIToolSet<typeof tools>;
|
|
194
203
|
|
|
195
|
-
// Or
|
|
196
|
-
type
|
|
204
|
+
// Or from the ToolSet instance
|
|
205
|
+
type MyToolSet = InferUIToolSet<typeof toolSet>;
|
|
206
|
+
|
|
207
|
+
// Use MyToolSet in your UIMessage type for type-safe access to tool invocation parts:
|
|
208
|
+
type MyUIMessage = UIMessage<unknown, any, MyToolSet>;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Custom UIMessage
|
|
212
|
+
|
|
213
|
+
If you already have a custom `UIMessage` type, you can pass it as `MESSAGE` generic to `createToolSet()` and it will be used in predicates and `inferTools`:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { myTools } from './my-tools.js';
|
|
217
|
+
import { MyUIMessage } from './my-ui-message.js';
|
|
218
|
+
|
|
219
|
+
const toolSet = createToolSet<typeof myTools, MyUIMessage>(myTools)
|
|
220
|
+
.activateWhen('cancel_order', ({ messages }) => hasUnfulfilledOrders(messages));
|
|
221
|
+
// ~~~~~~~~
|
|
222
|
+
// Messages are now typed as MyUIMessage, so you get type safety and autocompletion in predicates!
|
|
223
|
+
|
|
224
|
+
const { tools, activeTools } = toolSet.inferTools({ messages });
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Custom Context
|
|
228
|
+
|
|
229
|
+
Pass a `CONTEXT` generic to `createToolSet()` to type the `context` field in predicates and `inferTools`:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { myTools } from './my-tools.js';
|
|
233
|
+
import { MyUIMessage } from './my-ui-message.js';
|
|
234
|
+
|
|
235
|
+
type MyContext = { userId: string; isAdmin: boolean };
|
|
236
|
+
|
|
237
|
+
const toolSet = createToolSet<typeof myTools, MyUIMessage, MyContext>(myTools)
|
|
238
|
+
.activateWhen('cancel_order', ({ context }) => context.isAdmin);
|
|
239
|
+
// ~~~~~~~~
|
|
240
|
+
// Context is now typed as MyContext, so you get type safety and autocompletion
|
|
241
|
+
|
|
242
|
+
const { tools, activeTools } = toolSet.inferTools({
|
|
243
|
+
messages,
|
|
244
|
+
context: { isAdmin: true },
|
|
245
|
+
});
|
|
197
246
|
```
|
|
198
247
|
|
|
199
248
|
## API
|
|
200
249
|
|
|
201
|
-
## `createToolSet(
|
|
250
|
+
## `createToolSet(tools)`
|
|
202
251
|
|
|
203
|
-
- `
|
|
204
|
-
- a plain AI SDK `Tool` (always active), or
|
|
205
|
-
- a `{ tool, active }` object with activation control (`active` can be `boolean` or `(context) => boolean`)
|
|
252
|
+
- `tools`, a plain `Record<string, Tool>` of AI SDK tools
|
|
206
253
|
|
|
207
|
-
Returns a `ToolSet` instance.
|
|
254
|
+
Returns a `ToolSet` instance. All tools are active by default.
|
|
208
255
|
|
|
209
256
|
```ts
|
|
210
|
-
const toolSet = createToolSet({
|
|
211
|
-
search: tool({ ... }),
|
|
212
|
-
list_orders: {
|
|
213
|
-
tool: tool({ ... }),
|
|
214
|
-
active: false,
|
|
215
|
-
},
|
|
216
|
-
cancel_order: {
|
|
217
|
-
tool: tool({ ... }),
|
|
218
|
-
active: ({ messages }) => /* callback logic */,
|
|
219
|
-
},
|
|
220
|
-
});
|
|
257
|
+
const toolSet = createToolSet({ search, list_orders, cancel_order });
|
|
221
258
|
```
|
|
222
259
|
|
|
223
260
|
#### `.tools`
|
|
224
261
|
|
|
225
|
-
All tools as a standard AI SDK tool record
|
|
262
|
+
All tools as a standard AI SDK tool record, regardless of activation state.
|
|
226
263
|
|
|
227
264
|
```ts
|
|
228
265
|
const { tools } = toolSet;
|
|
@@ -230,81 +267,93 @@ const { tools } = toolSet;
|
|
|
230
267
|
|
|
231
268
|
#### `.activeTools`
|
|
232
269
|
|
|
233
|
-
Resolved array of active tool names based on the current state.
|
|
270
|
+
Resolved array of active tool names based on the current state.
|
|
234
271
|
|
|
235
272
|
```ts
|
|
236
273
|
const { activeTools } = toolSet;
|
|
237
274
|
```
|
|
238
275
|
|
|
239
|
-
#### `.
|
|
276
|
+
#### `.activate(names)`
|
|
240
277
|
|
|
241
|
-
|
|
278
|
+
Statically activate tools by name. Returns a new `ToolSet`.
|
|
242
279
|
|
|
243
280
|
```ts
|
|
244
|
-
|
|
281
|
+
toolSet.activate(['cancel_order']);
|
|
245
282
|
```
|
|
246
283
|
|
|
247
|
-
#### `.
|
|
284
|
+
#### `.deactivate(names)`
|
|
248
285
|
|
|
249
|
-
|
|
286
|
+
Statically deactivate tools by name. Returns a new `ToolSet`.
|
|
250
287
|
|
|
251
288
|
```ts
|
|
252
|
-
|
|
289
|
+
toolSet.deactivate(['search']);
|
|
253
290
|
```
|
|
254
291
|
|
|
255
|
-
#### `.
|
|
292
|
+
#### `.activateWhen(name, predicate)` / `.activateWhen(predicates)`
|
|
293
|
+
|
|
294
|
+
Conditionally activate tools. The predicate receives `{ messages, context }` and returns `true` to activate.
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
toolSet.activateWhen('cancel_order', ({ messages }) => hasOrders(messages));
|
|
298
|
+
|
|
299
|
+
toolSet.activateWhen({
|
|
300
|
+
cancel_order: ({ messages }) => hasOrders(messages),
|
|
301
|
+
list_orders: ({ context }) => context.isAuthenticated,
|
|
302
|
+
});
|
|
303
|
+
```
|
|
256
304
|
|
|
257
|
-
|
|
305
|
+
#### `.deactivateWhen(name, predicate)` / `.deactivateWhen(predicates)`
|
|
258
306
|
|
|
259
|
-
|
|
260
|
-
- `messages`, `Array<UIMessage>`, the current conversation messages used by activation callbacks to determine tool availability
|
|
307
|
+
Conditionally deactivate tools. The predicate receives `{ messages, context }` and returns `true` to deactivate.
|
|
261
308
|
|
|
262
309
|
```ts
|
|
263
|
-
|
|
310
|
+
toolSet.deactivateWhen('search', ({ messages }) => messages.length > 10);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### `.inferTools(input)`
|
|
314
|
+
|
|
315
|
+
Evaluate all predicates with the provided messages and context. Returns a new `ToolSet`.
|
|
316
|
+
|
|
317
|
+
- `input`:
|
|
318
|
+
- `messages`, the current conversation messages
|
|
319
|
+
- `context`, arbitrary values passed to predicates
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
toolSet.inferTools({ messages, context: {} });
|
|
264
323
|
```
|
|
265
324
|
|
|
266
325
|
## Types
|
|
267
326
|
|
|
268
|
-
### `
|
|
327
|
+
### `ActivationInput`
|
|
269
328
|
|
|
270
|
-
|
|
329
|
+
Input passed to activation predicates. Generic over `MESSAGE` and `CONTEXT`:
|
|
271
330
|
|
|
272
331
|
```ts
|
|
273
|
-
import {
|
|
332
|
+
import type { ActivationInput } from 'ai-tool-set';
|
|
274
333
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
list_orders: { tool: tool({ ... }), active: false },
|
|
278
|
-
cancel_order: { tool: tool({ ... }), active: ({ messages }) => /* ... */ },
|
|
279
|
-
} satisfies ToolSetConfig;
|
|
334
|
+
type MyInput = ActivationInput<MyUIMessage, { isAdmin: boolean }>;
|
|
335
|
+
// { messages: Array<MyUIMessage>; context: { isAdmin: boolean } }
|
|
280
336
|
```
|
|
281
337
|
|
|
282
338
|
### `InferToolSet`
|
|
283
339
|
|
|
284
|
-
Extract
|
|
340
|
+
Extract the raw tool record from a tool record or `ToolSet` instance:
|
|
285
341
|
|
|
286
342
|
```ts
|
|
287
|
-
import {
|
|
343
|
+
import type { InferToolSet } from 'ai-tool-set';
|
|
288
344
|
|
|
289
|
-
type Tools = InferToolSet<typeof tools>;
|
|
290
|
-
// { search: Tool<...>, list_orders: Tool<...>, cancel_order: Tool<...> }
|
|
291
|
-
|
|
292
|
-
// Also works with a ToolSet instance
|
|
293
|
-
const toolSet = createToolSet(tools);
|
|
294
345
|
type Tools = InferToolSet<typeof toolSet>;
|
|
346
|
+
// { search: Tool<...>, list_orders: Tool<...>, cancel_order: Tool<...> }
|
|
295
347
|
```
|
|
296
348
|
|
|
297
349
|
### `InferUIToolSet`
|
|
298
350
|
|
|
299
|
-
Derive typed UI tool parts from a
|
|
351
|
+
Derive typed UI tool parts from a tool record or `ToolSet` instance. Use with `UIMessage` for type-safe access to tool invocation parts:
|
|
300
352
|
|
|
301
353
|
```ts
|
|
302
354
|
import type { UIMessage } from 'ai';
|
|
303
|
-
import {
|
|
304
|
-
|
|
305
|
-
type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof tools>>;
|
|
355
|
+
import type { InferUIToolSet } from 'ai-tool-set';
|
|
306
356
|
|
|
307
|
-
// Also works with a ToolSet instance
|
|
308
357
|
type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof toolSet>>;
|
|
309
358
|
|
|
310
359
|
// Parts are now typed per tool:
|