ai-tool-set 0.0.1 → 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 +364 -1
- package/dist/index.d.mts +89 -0
- package/dist/index.mjs +108 -0
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -1,3 +1,366 @@
|
|
|
1
|
+
<div align='center'>
|
|
2
|
+
|
|
1
3
|
# ai-tool-set
|
|
2
4
|
|
|
3
|
-
>
|
|
5
|
+
<p align="center">AI SDK: Manage tool activation with type-safe, chainable tool sets</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/ai-tool-set" alt="ai-tool-set"><img src="https://img.shields.io/npm/dt/ai-tool-set?label=ai-tool-set"></a> <a href="https://github.com/zirkelc/ai-tool-set/actions/workflows/ci.yml" alt="CI"><img src="https://img.shields.io/github/actions/workflow/status/zirkelc/ai-tool-set/ci.yml?branch=main"></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
This library provides an immutable and type-safe API to manage [`activeTools`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text#active-tools) for [`generateText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text) and [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) in the AI SDK.
|
|
13
|
+
|
|
14
|
+
### Why?
|
|
15
|
+
|
|
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
|
+
|
|
18
|
+
- **Statically activate/deactivate tools**: Some tools should be inactive by default and only available after being explicitly activated
|
|
19
|
+
- **Dynamically infer tool activation**: Some tools should be activated based on runtime context like the conversation history
|
|
20
|
+
|
|
21
|
+
This library wraps standard AI SDK `tool()` definitions with chainable activation methods and resolves `tools` and `activeTools` for any AI SDK function.
|
|
22
|
+
|
|
23
|
+
### Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install ai-tool-set
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Creating a Tool Set
|
|
32
|
+
|
|
33
|
+
Pass a plain record of AI SDK `tool()` definitions to `createToolSet()`. All tools are active by default.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { tool } from 'ai';
|
|
37
|
+
import { z } from 'zod';
|
|
38
|
+
import { createToolSet } from 'ai-tool-set';
|
|
39
|
+
|
|
40
|
+
const search = tool({
|
|
41
|
+
description: 'Search for products',
|
|
42
|
+
inputSchema: z.object({ query: z.string() }),
|
|
43
|
+
execute: async ({ query }) => searchProducts(query),
|
|
44
|
+
});
|
|
45
|
+
|
|
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),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const toolSet = createToolSet({ search, list_orders, cancel_order });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Use with `generateText` / `streamText`
|
|
62
|
+
|
|
63
|
+
The tool set exposes `tools` and `activeTools` as properties that can be used directly:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { generateText } from 'ai';
|
|
67
|
+
|
|
68
|
+
const { tools, activeTools } = toolSet;
|
|
69
|
+
|
|
70
|
+
const result = await generateText({
|
|
71
|
+
model,
|
|
72
|
+
tools,
|
|
73
|
+
activeTools,
|
|
74
|
+
// Or spread tool set directly:
|
|
75
|
+
// ...toolSet,
|
|
76
|
+
prompt: 'Hello',
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Activate and Deactivate Tools
|
|
81
|
+
|
|
82
|
+
Use `.activate()` and `.deactivate()` to statically control which tools are available:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const { tools, activeTools } = toolSet
|
|
86
|
+
.deactivate(['cancel_order'])
|
|
87
|
+
.activate(['list_orders']);
|
|
88
|
+
|
|
89
|
+
const result = await generateText({
|
|
90
|
+
model,
|
|
91
|
+
tools,
|
|
92
|
+
activeTools,
|
|
93
|
+
prompt: 'Show me my orders',
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
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
|
+
```
|
|
121
|
+
|
|
122
|
+
Use `.inferTools()` to evaluate predicates with the current messages and context:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const messages = [
|
|
126
|
+
{
|
|
127
|
+
role: 'user',
|
|
128
|
+
parts: [{ type: 'text', text: 'Show me my orders' }],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
role: 'assistant',
|
|
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
|
+
],
|
|
142
|
+
},
|
|
143
|
+
}],
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
// cancel_order is now active because list_orders returned unfulfilled orders
|
|
148
|
+
const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
|
|
149
|
+
|
|
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
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Immutable
|
|
177
|
+
|
|
178
|
+
All methods return new tool set instances. The original is never modified:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const base = createToolSet({ search, list_orders, cancel_order });
|
|
182
|
+
const withoutCancel = base.deactivate(['cancel_order']);
|
|
183
|
+
const withCancel = withoutCancel.activate(['cancel_order']);
|
|
184
|
+
|
|
185
|
+
base.activeTools; // ['search', 'list_orders', 'cancel_order']
|
|
186
|
+
withoutCancel.activeTools; // ['search', 'list_orders']
|
|
187
|
+
withCancel.activeTools; // ['search', 'list_orders', 'cancel_order']
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Typed UI Tool Set
|
|
191
|
+
|
|
192
|
+
Use `InferUIToolSet` to get fully typed UI messages from your tool set:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import type { UIMessage } from 'ai';
|
|
196
|
+
import type { InferUIToolSet } from 'ai-tool-set';
|
|
197
|
+
|
|
198
|
+
const tools = { search, list_orders, cancel_order };
|
|
199
|
+
const toolSet = createToolSet(tools);
|
|
200
|
+
|
|
201
|
+
// From the tools record
|
|
202
|
+
type MyToolSet = InferUIToolSet<typeof tools>;
|
|
203
|
+
|
|
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
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## API
|
|
249
|
+
|
|
250
|
+
## `createToolSet(tools)`
|
|
251
|
+
|
|
252
|
+
- `tools`, a plain `Record<string, Tool>` of AI SDK tools
|
|
253
|
+
|
|
254
|
+
Returns a `ToolSet` instance. All tools are active by default.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
const toolSet = createToolSet({ search, list_orders, cancel_order });
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### `.tools`
|
|
261
|
+
|
|
262
|
+
All tools as a standard AI SDK tool record, regardless of activation state.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
const { tools } = toolSet;
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### `.activeTools`
|
|
269
|
+
|
|
270
|
+
Resolved array of active tool names based on the current state.
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const { activeTools } = toolSet;
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### `.activate(names)`
|
|
277
|
+
|
|
278
|
+
Statically activate tools by name. Returns a new `ToolSet`.
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
toolSet.activate(['cancel_order']);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### `.deactivate(names)`
|
|
285
|
+
|
|
286
|
+
Statically deactivate tools by name. Returns a new `ToolSet`.
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
toolSet.deactivate(['search']);
|
|
290
|
+
```
|
|
291
|
+
|
|
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
|
+
```
|
|
304
|
+
|
|
305
|
+
#### `.deactivateWhen(name, predicate)` / `.deactivateWhen(predicates)`
|
|
306
|
+
|
|
307
|
+
Conditionally deactivate tools. The predicate receives `{ messages, context }` and returns `true` to deactivate.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
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: {} });
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Types
|
|
326
|
+
|
|
327
|
+
### `ActivationInput`
|
|
328
|
+
|
|
329
|
+
Input passed to activation predicates. Generic over `MESSAGE` and `CONTEXT`:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import type { ActivationInput } from 'ai-tool-set';
|
|
333
|
+
|
|
334
|
+
type MyInput = ActivationInput<MyUIMessage, { isAdmin: boolean }>;
|
|
335
|
+
// { messages: Array<MyUIMessage>; context: { isAdmin: boolean } }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### `InferToolSet`
|
|
339
|
+
|
|
340
|
+
Extract the raw tool record from a tool record or `ToolSet` instance:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import type { InferToolSet } from 'ai-tool-set';
|
|
344
|
+
|
|
345
|
+
type Tools = InferToolSet<typeof toolSet>;
|
|
346
|
+
// { search: Tool<...>, list_orders: Tool<...>, cancel_order: Tool<...> }
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### `InferUIToolSet`
|
|
350
|
+
|
|
351
|
+
Derive typed UI tool parts from a tool record or `ToolSet` instance. Use with `UIMessage` for type-safe access to tool invocation parts:
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import type { UIMessage } from 'ai';
|
|
355
|
+
import type { InferUIToolSet } from 'ai-tool-set';
|
|
356
|
+
|
|
357
|
+
type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof toolSet>>;
|
|
358
|
+
|
|
359
|
+
// Parts are now typed per tool:
|
|
360
|
+
// message.parts[0].type === 'tool-search'
|
|
361
|
+
// message.parts[0].output // typed as search tool's return type
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## License
|
|
365
|
+
|
|
366
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { InferUITool, ModelMessage, Tool, UIMessage } from "ai";
|
|
2
|
+
|
|
3
|
+
//#region src/tool-set.d.ts
|
|
4
|
+
/** A plain record of tools. */
|
|
5
|
+
type ToolRecord = Record<string, Tool>;
|
|
6
|
+
/** Supported message types for activation callbacks. */
|
|
7
|
+
type MessageType = UIMessage | ModelMessage;
|
|
8
|
+
/** The fully-typed UIMessage for a given tool record. */
|
|
9
|
+
type InferUIMessage<TOOLS extends ToolRecord> = UIMessage<unknown, any, InferUIToolSet<TOOLS>>;
|
|
10
|
+
/** Infer the raw tool record from a ToolRecord or ToolSet instance. */
|
|
11
|
+
type InferToolSet<T extends ToolRecord | ToolSet<any>> = T extends ToolSet<infer TOOLS, any, any> ? TOOLS : T;
|
|
12
|
+
/** Infer the UI tool types from a tool record or ToolSet instance. */
|
|
13
|
+
type InferUIToolSet<T extends ToolRecord | ToolSet<any>> = { [K in keyof InferToolSet<T> & string]: InferUITool<InferToolSet<T>[K]> };
|
|
14
|
+
/**
|
|
15
|
+
* Input passed to activation predicates.
|
|
16
|
+
* Use `ActivationInput<MyMsg>` to get per-tool narrowing in callbacks.
|
|
17
|
+
*/
|
|
18
|
+
type ActivationInput<MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>> = {
|
|
19
|
+
messages: Array<MESSAGE>;
|
|
20
|
+
context: CONTEXT;
|
|
21
|
+
};
|
|
22
|
+
/** Activation predicate — returns true if tool should be active. */
|
|
23
|
+
type ActivationPredicate<MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>> = (input: ActivationInput<MESSAGE, CONTEXT>) => boolean;
|
|
24
|
+
type ActivationEntry = {
|
|
25
|
+
toolName: string;
|
|
26
|
+
resolve: (input: ActivationInput<any, any>) => boolean;
|
|
27
|
+
};
|
|
28
|
+
type ToolSetState<TOOLS extends ToolRecord, MESSAGE extends MessageType, CONTEXT extends Record<string, unknown>> = {
|
|
29
|
+
tools: TOOLS;
|
|
30
|
+
entries: Array<ActivationEntry>;
|
|
31
|
+
input: ActivationInput<MESSAGE, CONTEXT>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* An immutable tool set with chainable activation methods.
|
|
35
|
+
*
|
|
36
|
+
* Resolution follows "last-call wins": each method appends an entry,
|
|
37
|
+
* and the last entry for each tool determines its state.
|
|
38
|
+
* Default (no entry) is active.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const toolSet = createToolSet({ search, list_orders, cancel_order })
|
|
43
|
+
* .deactivate(['cancel_order'])
|
|
44
|
+
* .activateWhen('cancel_order', ({ messages }) => hasUnfulfilledOrders(messages));
|
|
45
|
+
*
|
|
46
|
+
* const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare class ToolSet<TOOLS extends ToolRecord, MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>, ACTIVATED extends string = never, DEACTIVATED extends string = never> {
|
|
50
|
+
#private;
|
|
51
|
+
/** All tools as a standard AI SDK tool record. */
|
|
52
|
+
readonly tools: TOOLS;
|
|
53
|
+
/** Resolved list of active tool names based on current state. */
|
|
54
|
+
readonly activeTools: Array<keyof TOOLS & string>;
|
|
55
|
+
constructor(state: ToolSetState<TOOLS, MESSAGE, CONTEXT>);
|
|
56
|
+
/** Statically activate tools by name. */
|
|
57
|
+
activate<NAMES extends (Exclude<keyof TOOLS & string, ACTIVATED> | DEACTIVATED) & string>(names: Array<NAMES>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED | NAMES, DEACTIVATED>;
|
|
58
|
+
/** Statically deactivate tools by name. */
|
|
59
|
+
deactivate<NAMES extends ((keyof TOOLS & string) | ACTIVATED) & string>(names: Array<NAMES>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED | NAMES>;
|
|
60
|
+
/**
|
|
61
|
+
* Conditionally activate a tool — becomes active when predicate returns true.
|
|
62
|
+
*
|
|
63
|
+
* - `.activateWhen('tool_name', (input) => ...)`
|
|
64
|
+
* - `.activateWhen({ tool_name: (input) => ... })`
|
|
65
|
+
*/
|
|
66
|
+
activateWhen<NAME extends keyof TOOLS & string>(name: NAME, predicate: ActivationPredicate<MESSAGE, CONTEXT>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED>;
|
|
67
|
+
activateWhen(predicates: Partial<Record<keyof TOOLS & string, ActivationPredicate<MESSAGE, CONTEXT>>>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED>;
|
|
68
|
+
/**
|
|
69
|
+
* Conditionally deactivate a tool — becomes inactive when predicate returns true.
|
|
70
|
+
* Internally wraps with negation so resolve returns true (active) when pred is false.
|
|
71
|
+
*
|
|
72
|
+
* - `.deactivateWhen('tool_name', (input) => ...)`
|
|
73
|
+
* - `.deactivateWhen({ tool_name: (input) => ... })`
|
|
74
|
+
*/
|
|
75
|
+
deactivateWhen<NAME extends keyof TOOLS & string>(name: NAME, predicate: ActivationPredicate<MESSAGE, CONTEXT>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED>;
|
|
76
|
+
deactivateWhen(predicates: Partial<Record<keyof TOOLS & string, ActivationPredicate<MESSAGE, CONTEXT>>>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED>;
|
|
77
|
+
/** Provide messages and context for predicate-based tools. Returns a new ToolSet. */
|
|
78
|
+
inferTools(input: ActivationInput<MESSAGE, CONTEXT>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a chainable, immutable tool set.
|
|
82
|
+
*
|
|
83
|
+
* @typeParam TOOLS — inferred from the argument
|
|
84
|
+
* @typeParam MESSAGE — defaults to the fully-typed UIMessage derived from TOOLS
|
|
85
|
+
* @typeParam CONTEXT — defaults to Record<string, unknown>
|
|
86
|
+
*/
|
|
87
|
+
declare const createToolSet: <const TOOLS extends ToolRecord, MESSAGE extends MessageType = InferUIMessage<TOOLS>, CONTEXT extends Record<string, unknown> = Record<string, unknown>>(tools: TOOLS) => ToolSet<TOOLS, MESSAGE, CONTEXT>;
|
|
88
|
+
//#endregion
|
|
89
|
+
export { type ActivationInput, type InferToolSet, type InferUIToolSet, createToolSet };
|
package/dist/index.mjs
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/tool-set.ts
|
|
2
|
+
const toEntries = (nameOrPredicates, predicate) => {
|
|
3
|
+
if (typeof nameOrPredicates === "string") return [{
|
|
4
|
+
toolName: nameOrPredicates,
|
|
5
|
+
resolve: predicate
|
|
6
|
+
}];
|
|
7
|
+
return Object.entries(nameOrPredicates).filter(([, pred]) => pred != null).map(([name, pred]) => ({
|
|
8
|
+
toolName: name,
|
|
9
|
+
resolve: pred
|
|
10
|
+
}));
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* An immutable tool set with chainable activation methods.
|
|
14
|
+
*
|
|
15
|
+
* Resolution follows "last-call wins": each method appends an entry,
|
|
16
|
+
* and the last entry for each tool determines its state.
|
|
17
|
+
* Default (no entry) is active.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const toolSet = createToolSet({ search, list_orders, cancel_order })
|
|
22
|
+
* .deactivate(['cancel_order'])
|
|
23
|
+
* .activateWhen('cancel_order', ({ messages }) => hasUnfulfilledOrders(messages));
|
|
24
|
+
*
|
|
25
|
+
* const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
var ToolSet = class ToolSet {
|
|
29
|
+
#state;
|
|
30
|
+
/** All tools as a standard AI SDK tool record. */
|
|
31
|
+
tools;
|
|
32
|
+
/** Resolved list of active tool names based on current state. */
|
|
33
|
+
activeTools;
|
|
34
|
+
constructor(state) {
|
|
35
|
+
this.#state = state;
|
|
36
|
+
this.tools = state.tools;
|
|
37
|
+
this.activeTools = Object.keys(state.tools).filter((name) => {
|
|
38
|
+
const lastEntry = state.entries.findLast((e) => e.toolName === name);
|
|
39
|
+
if (!lastEntry) return true;
|
|
40
|
+
return lastEntry.resolve(state.input);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Statically activate tools by name. */
|
|
44
|
+
activate(names) {
|
|
45
|
+
const newEntries = names.map((name) => ({
|
|
46
|
+
toolName: name,
|
|
47
|
+
resolve: () => true
|
|
48
|
+
}));
|
|
49
|
+
return new ToolSet({
|
|
50
|
+
...this.#state,
|
|
51
|
+
entries: [...this.#state.entries, ...newEntries]
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Statically deactivate tools by name. */
|
|
55
|
+
deactivate(names) {
|
|
56
|
+
const newEntries = names.map((name) => ({
|
|
57
|
+
toolName: name,
|
|
58
|
+
resolve: () => false
|
|
59
|
+
}));
|
|
60
|
+
return new ToolSet({
|
|
61
|
+
...this.#state,
|
|
62
|
+
entries: [...this.#state.entries, ...newEntries]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
activateWhen(nameOrPredicates, predicate) {
|
|
66
|
+
const newEntries = toEntries(nameOrPredicates, predicate);
|
|
67
|
+
return new ToolSet({
|
|
68
|
+
...this.#state,
|
|
69
|
+
entries: [...this.#state.entries, ...newEntries]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
deactivateWhen(nameOrPredicates, predicate) {
|
|
73
|
+
const newEntries = toEntries(nameOrPredicates, predicate).map((e) => ({
|
|
74
|
+
...e,
|
|
75
|
+
resolve: (input) => !e.resolve(input)
|
|
76
|
+
}));
|
|
77
|
+
return new ToolSet({
|
|
78
|
+
...this.#state,
|
|
79
|
+
entries: [...this.#state.entries, ...newEntries]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Provide messages and context for predicate-based tools. Returns a new ToolSet. */
|
|
83
|
+
inferTools(input) {
|
|
84
|
+
return new ToolSet({
|
|
85
|
+
...this.#state,
|
|
86
|
+
input
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Create a chainable, immutable tool set.
|
|
92
|
+
*
|
|
93
|
+
* @typeParam TOOLS — inferred from the argument
|
|
94
|
+
* @typeParam MESSAGE — defaults to the fully-typed UIMessage derived from TOOLS
|
|
95
|
+
* @typeParam CONTEXT — defaults to Record<string, unknown>
|
|
96
|
+
*/
|
|
97
|
+
const createToolSet = (tools) => {
|
|
98
|
+
return new ToolSet({
|
|
99
|
+
tools,
|
|
100
|
+
entries: [],
|
|
101
|
+
input: {
|
|
102
|
+
messages: [],
|
|
103
|
+
context: {}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
export { createToolSet };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-tool-set",
|
|
3
|
-
"version": "0.0.1",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"description": "Tool set utilities for the Vercel AI SDK",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,21 +21,24 @@
|
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
23
23
|
"@total-typescript/tsconfig": "^1.0.4",
|
|
24
|
-
"@types/node": "^25.2
|
|
25
|
-
"ai": "^6.0.
|
|
24
|
+
"@types/node": "^25.5.2",
|
|
25
|
+
"ai": "^6.0.153",
|
|
26
26
|
"husky": "^9.1.7",
|
|
27
|
-
"lint-staged": "^16.
|
|
28
|
-
"oxfmt": "^0.
|
|
29
|
-
"oxlint": "^1.
|
|
30
|
-
"oxlint-tsgolint": "^0.
|
|
31
|
-
"pkg-pr-new": "^0.0.
|
|
32
|
-
"publint": "^0.3.
|
|
33
|
-
"tsdown": "^0.
|
|
27
|
+
"lint-staged": "^16.4.0",
|
|
28
|
+
"oxfmt": "^0.44.0",
|
|
29
|
+
"oxlint": "^1.59.0",
|
|
30
|
+
"oxlint-tsgolint": "^0.20.0",
|
|
31
|
+
"pkg-pr-new": "^0.0.66",
|
|
32
|
+
"publint": "^0.3.18",
|
|
33
|
+
"tsdown": "^0.21.7",
|
|
34
34
|
"tsx": "^4.21.0",
|
|
35
|
-
"typescript": "^
|
|
36
|
-
"vitest": "^4.
|
|
35
|
+
"typescript": "^6.0.2",
|
|
36
|
+
"vitest": "^4.1.3",
|
|
37
37
|
"zod": "^4.3.6"
|
|
38
38
|
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"ai": "^6.0.141"
|
|
41
|
+
},
|
|
39
42
|
"lint-staged": {
|
|
40
43
|
"*.{js,ts,tsx,jsx}": [
|
|
41
44
|
"pnpm lint",
|
|
@@ -45,9 +48,6 @@
|
|
|
45
48
|
"pnpm format"
|
|
46
49
|
]
|
|
47
50
|
},
|
|
48
|
-
"peerDependencies": {
|
|
49
|
-
"ai": "^6.0.141"
|
|
50
|
-
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsdown",
|
|
53
53
|
"test": "vitest",
|