ai-tool-set 0.0.1 → 0.1.0-alpha.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 +315 -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,317 @@
|
|
|
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
|
+
- **Activate/deactivate tools**: Some tools should be inactive by default and should only be available after being explicitily 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 activation metadata and provides a chainable API to resolve `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
|
+
Define your tools using the standard AI SDK `tool()` function and wrap them with `createToolSet()`. Each entry is either a plain `Tool` (always active) or a `{ tool, active }` object with explicit activation control.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { tool } from 'ai';
|
|
37
|
+
import { z } from 'zod';
|
|
38
|
+
import { createToolSet } from 'ai-tool-set';
|
|
39
|
+
|
|
40
|
+
const toolSet = createToolSet({
|
|
41
|
+
// Plain tool — always active
|
|
42
|
+
search: tool({
|
|
43
|
+
description: 'Search for products',
|
|
44
|
+
inputSchema: z.object({ query: z.string() }),
|
|
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
|
+
},
|
|
57
|
+
|
|
58
|
+
// Callback-based — activated dynamically based on messages
|
|
59
|
+
// Only available when list_orders was called and returned unfulfilled orders
|
|
60
|
+
cancel_order: {
|
|
61
|
+
tool: tool({
|
|
62
|
+
description: 'Cancel an order',
|
|
63
|
+
inputSchema: z.object({ orderId: z.string() }),
|
|
64
|
+
execute: async ({ orderId }) => cancelOrder(orderId),
|
|
65
|
+
}),
|
|
66
|
+
active: ({ messages }) =>
|
|
67
|
+
messages.some((m) =>
|
|
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
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Use with `generateText` / `streamText` / `ToolLoopAgent`
|
|
80
|
+
|
|
81
|
+
The tool set exposes `tools` and `activeTools` as properties that can be used directly:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { generateText } from 'ai';
|
|
85
|
+
|
|
86
|
+
const { tools, activeTools } = toolSet;
|
|
87
|
+
|
|
88
|
+
const result = await generateText({
|
|
89
|
+
model,
|
|
90
|
+
tools,
|
|
91
|
+
activeTools,
|
|
92
|
+
// Or spread tool set directly:
|
|
93
|
+
// ...toolSet,
|
|
94
|
+
prompt: 'Hello',
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Activate and Deactivate Tools
|
|
99
|
+
|
|
100
|
+
Use `activateTools()` and `deactivateTools()` to override static activation. Both methods are type-safe and only accept valid tool names:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Active tools will include 'list_orders' and exclude 'search'
|
|
104
|
+
const { tools, activeTools } = toolSet
|
|
105
|
+
.activateTools(['list_orders'])
|
|
106
|
+
.deactivateTools(['search']);
|
|
107
|
+
|
|
108
|
+
const result = await generateText({
|
|
109
|
+
model,
|
|
110
|
+
tools,
|
|
111
|
+
activeTools,
|
|
112
|
+
prompt: 'Show me my orders',
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Infer Active Tools
|
|
117
|
+
|
|
118
|
+
Use `inferTools()` to evaluate callback-based tools against the current conversation context:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const messages = [
|
|
122
|
+
{
|
|
123
|
+
role: 'user',
|
|
124
|
+
parts: [{ type: 'text', text: 'Show me my orders' }],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
role: 'assistant',
|
|
128
|
+
parts: [
|
|
129
|
+
{
|
|
130
|
+
type: 'tool-list_orders',
|
|
131
|
+
state: 'output-available',
|
|
132
|
+
toolCallId: 'call-1',
|
|
133
|
+
input: { customerId: 'cust-123' },
|
|
134
|
+
output: {
|
|
135
|
+
orders: [
|
|
136
|
+
{ orderId: 'order-1', status: 'fulfilled' },
|
|
137
|
+
{ orderId: 'order-2', status: 'pending' },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
// Active tools will include 'cancel_order' based on the messages context
|
|
146
|
+
const { tools, activeTools } = toolSet.inferTools({ messages });
|
|
147
|
+
|
|
148
|
+
const result = await generateText({
|
|
149
|
+
model,
|
|
150
|
+
tools,
|
|
151
|
+
activeTools,
|
|
152
|
+
messages,
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Immutable
|
|
157
|
+
|
|
158
|
+
All methods return new tool set instances, allowing you to maintain different tool states across different calls or steps without side effects:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const toolSet1 = createToolSet({ ... });
|
|
162
|
+
const toolSet2 = toolSet1.activateTools(['list_orders']);
|
|
163
|
+
const toolSet3 = toolSet2.deactivateTools(['search', 'list_orders']);
|
|
164
|
+
|
|
165
|
+
toolSet1.activeTools; // ['search']
|
|
166
|
+
toolSet2.activeTools; // ['search', 'list_orders']
|
|
167
|
+
toolSet3.activeTools; // []
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Typed UI Messages
|
|
171
|
+
|
|
172
|
+
Use `InferUIToolSet` to get fully typed UI messages from your tool set:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import type { UIMessage } from 'ai';
|
|
176
|
+
import type { InferUIToolSet, ToolSetConfig } from 'ai-tool-set';
|
|
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;
|
|
189
|
+
|
|
190
|
+
const toolSet = createToolSet(tools);
|
|
191
|
+
|
|
192
|
+
// Use tool set config
|
|
193
|
+
type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof tools>>;
|
|
194
|
+
|
|
195
|
+
// Or use ToolSet instance
|
|
196
|
+
type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof toolSet>>;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## API
|
|
200
|
+
|
|
201
|
+
## `createToolSet(config)`
|
|
202
|
+
|
|
203
|
+
- `config`, a record where each key maps to either:
|
|
204
|
+
- a plain AI SDK `Tool` (always active), or
|
|
205
|
+
- a `{ tool, active }` object with activation control (`active` can be `boolean` or `(context) => boolean`)
|
|
206
|
+
|
|
207
|
+
Returns a `ToolSet` instance.
|
|
208
|
+
|
|
209
|
+
```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
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### `.tools`
|
|
224
|
+
|
|
225
|
+
All tools as a standard AI SDK tool record (`Record<string, Tool>`), regardless of activation state.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const { tools } = toolSet;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### `.activeTools`
|
|
232
|
+
|
|
233
|
+
Resolved array of active tool names based on the current state. Accounts for static activation, explicit overrides, and callback evaluation.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const { activeTools } = toolSet;
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `.activateTools(names)`
|
|
240
|
+
|
|
241
|
+
Activate statically inactive tools by name. Returns a new `ToolSet` instance. Type-safe — only accepts names of tools with `active: false` or tools previously deactivated in the chain.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const toolSetWithOrders = toolSet.activateTools(['list_orders']);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### `.deactivateTools(names)`
|
|
248
|
+
|
|
249
|
+
Deactivate statically active tools by name. Returns a new `ToolSet` instance. Type-safe — only accepts names of plain tools, tools with `active: true`, or tools previously activated in the chain.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
const toolSetWithoutSearch = toolSet.deactivateTools(['search']);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### `.inferTools(context)`
|
|
256
|
+
|
|
257
|
+
Evaluate callback-based tools against the provided context. Returns a new `ToolSet` instance.
|
|
258
|
+
|
|
259
|
+
- `context`:
|
|
260
|
+
- `messages`, `Array<UIMessage>`, the current conversation messages used by activation callbacks to determine tool availability
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
const toolSetInferred = toolSet.inferTools({ messages });
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Types
|
|
267
|
+
|
|
268
|
+
### `ToolSetConfig`
|
|
269
|
+
|
|
270
|
+
The config record type accepted by `createToolSet()`. Each key maps to a plain `Tool` or a `{ tool, active }` object. Use with `satisfies` to get contextual typing for activation callbacks:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
import { type ToolSetConfig } from 'ai-tool-set';
|
|
274
|
+
|
|
275
|
+
const tools = {
|
|
276
|
+
search: tool({ ... }),
|
|
277
|
+
list_orders: { tool: tool({ ... }), active: false },
|
|
278
|
+
cancel_order: { tool: tool({ ... }), active: ({ messages }) => /* ... */ },
|
|
279
|
+
} satisfies ToolSetConfig;
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### `InferToolSet`
|
|
283
|
+
|
|
284
|
+
Extract a `Record<string, Tool>` from a `ToolSetConfig` object or `ToolSet` instance, stripping activation metadata:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { type InferToolSet } from 'ai-tool-set';
|
|
288
|
+
|
|
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
|
+
type Tools = InferToolSet<typeof toolSet>;
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### `InferUIToolSet`
|
|
298
|
+
|
|
299
|
+
Derive typed UI tool parts from a `ToolSetConfig` object or `ToolSet` instance. Use with `UIMessage` to get type-safe access to tool invocation parts in the conversation:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import type { UIMessage } from 'ai';
|
|
303
|
+
import { type InferUIToolSet } from 'ai-tool-set';
|
|
304
|
+
|
|
305
|
+
type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof tools>>;
|
|
306
|
+
|
|
307
|
+
// Also works with a ToolSet instance
|
|
308
|
+
type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof toolSet>>;
|
|
309
|
+
|
|
310
|
+
// Parts are now typed per tool:
|
|
311
|
+
// message.parts[0].type === 'tool-search'
|
|
312
|
+
// message.parts[0].output // typed as search tool's return type
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
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.
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
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",
|