ai-tool-set 0.1.0-alpha.0 → 0.1.0-alpha.2

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 CHANGED
@@ -9,16 +9,16 @@
9
9
 
10
10
  </div>
11
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.
12
+ This library provides a 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
13
 
14
14
  ### Why?
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
- - **Activate/deactivate tools**: Some tools should be inactive by default and should only be available after being explicitily activated
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 metadata and provides a chainable API to resolve `tools` and `activeTools` for any AI SDK function.
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
- 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.
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 toolSet = createToolSet({
41
- // Plain tool — always active
40
+ const tools = {
42
41
  search: tool({
43
42
  description: 'Search for products',
44
43
  inputSchema: z.object({ query: z.string() }),
45
44
  execute: async ({ query }) => searchProducts(query),
46
45
  }),
46
+ 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
+ cancel_order: tool({
52
+ description: 'Cancel an order',
53
+ inputSchema: z.object({ orderId: z.string() }),
54
+ execute: async ({ orderId }) => cancelOrder(orderId),
55
+ }),
56
+ };
47
57
 
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
- });
58
+ const toolSet = createToolSet({ tools });
77
59
  ```
78
60
 
79
- ### Use with `generateText` / `streamText` / `ToolLoopAgent`
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,10 @@ const result = await generateText({
97
79
 
98
80
  ### Activate and Deactivate Tools
99
81
 
100
- Use `activateTools()` and `deactivateTools()` to override static activation. Both methods are type-safe and only accept valid tool names:
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
- const { tools, activeTools } = toolSet
105
- .activateTools(['list_orders'])
106
- .deactivateTools(['search']);
85
+ const { tools, activeTools } = toolSet.deactivate(['cancel_order']).activate(['list_orders']);
107
86
 
108
87
  const result = await generateText({
109
88
  model,
@@ -113,9 +92,24 @@ const result = await generateText({
113
92
  });
114
93
  ```
115
94
 
116
- ### Infer Active Tools
95
+ ### Conditional Activation
96
+
97
+ Use `.activateWhen()` and `.deactivateWhen()` to conditionally control tools based on messages and context. The predicate receives an `ActivationInput` with `messages` and `context`.
98
+
99
+ ```typescript
100
+ const toolSet = createToolSet({ tools }).activateWhen('cancel_order', ({ messages }) =>
101
+ messages.some((m) =>
102
+ m.parts.some(
103
+ (p) =>
104
+ p.type === 'tool-list_orders' &&
105
+ p.state === 'output-available' &&
106
+ p.output.orders?.some((order) => order.status !== 'fulfilled'),
107
+ ),
108
+ ),
109
+ );
110
+ ```
117
111
 
118
- Use `inferTools()` to evaluate callback-based tools against the current conversation context:
112
+ Use `.inferTools()` to evaluate predicates with the current messages and context. It returns `{ tools, activeTools }` that can be spread directly into `generateText()`:
119
113
 
120
114
  ```typescript
121
115
  const messages = [
@@ -133,8 +127,8 @@ const messages = [
133
127
  input: { customerId: 'cust-123' },
134
128
  output: {
135
129
  orders: [
136
- { orderId: 'order-1', status: 'fulfilled' },
137
- { orderId: 'order-2', status: 'pending' },
130
+ { orderId: '1000', status: 'fulfilled' },
131
+ { orderId: '1001', status: 'pending' },
138
132
  ],
139
133
  },
140
134
  },
@@ -142,87 +136,168 @@ const messages = [
142
136
  },
143
137
  ];
144
138
 
145
- // Active tools will include 'cancel_order' based on the messages context
146
- const { tools, activeTools } = toolSet.inferTools({ messages });
139
+ // cancel_order is now active because list_orders returned unfulfilled orders
140
+ const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
147
141
 
148
- const result = await generateText({
149
- model,
150
- tools,
151
- activeTools,
152
- messages,
142
+ const result = await generateText({ model, tools, activeTools, messages });
143
+ ```
144
+
145
+ You can also activate multiple tools at once:
146
+
147
+ ```typescript
148
+ const toolSet = createToolSet({ tools }).activateWhen({
149
+ list_orders: ({ context }) => context.isAuthenticated,
150
+ cancel_order: ({ messages }) => hasUnfulfilledOrders(messages),
153
151
  });
154
152
  ```
155
153
 
156
- ### Immutable
154
+ ### Last-Call Wins
157
155
 
158
- All methods return new tool set instances, allowing you to maintain different tool states across different calls or steps without side effects:
156
+ Each method appends to an internal list. For each tool, the **last entry** determines its state. This makes ordering explicit and predictable:
159
157
 
160
158
  ```typescript
161
- const toolSet1 = createToolSet({ ... });
162
- const toolSet2 = toolSet1.activateTools(['list_orders']);
163
- const toolSet3 = toolSet2.deactivateTools(['search', 'list_orders']);
159
+ const toolSet = createToolSet({ tools })
160
+ .activate(['cancel_order']) // cancel_order: activated
161
+ .deactivate(['cancel_order']) // cancel_order: deactivated
162
+ .activateWhen(
163
+ 'cancel_order',
164
+ (
165
+ { messages }, // cancel_order: conditional activation
166
+ ) => hasUnfulfilledOrders(messages),
167
+ );
168
+ ```
169
+
170
+ ### Immutable vs Mutable
171
+
172
+ By default, `createToolSet()` returns an **immutable** tool set, that means every method returns a new instance and the original is never modified. This is ideal when the tool set is created once in the global scope and shared across requests:
173
+
174
+ ```typescript
175
+ // Global scope: created once, shared across requests
176
+ const toolSet = createToolSet({ tools }).deactivate(['list_order', 'cancel_order']);
177
+
178
+ export async function POST(req: Request) {
179
+ const { messages } = await req.json();
180
+
181
+ // Activate list_orders only for this request
182
+ // myToolSet !== toolSet, original toolSet is unchanged for next request
183
+ const myToolSet = toolSet.activate(['list_orders']);
184
+
185
+ const { tools, activeTools } = myToolSet;
186
+
187
+ const result = await generateText({
188
+ model,
189
+ tools,
190
+ activeTools,
191
+ messages,
192
+ });
193
+ }
194
+ ```
164
195
 
165
- toolSet1.activeTools; // ['search']
166
- toolSet2.activeTools; // ['search', 'list_orders']
167
- toolSet3.activeTools; // []
196
+ Pass `mutable: true` to get a **mutable** tool set where each method mutates in-place and returns `this` for chaining. This is useful when the tool set is created per-request in a local scope:
197
+
198
+ ```typescript
199
+ export async function POST(req: Request) {
200
+ const { messages } = await req.json();
201
+
202
+ // Local scope: created and mutated per request
203
+ const toolSet = createToolSet({ tools, mutable: true }).deactivate(['list_order', 'cancel_order']);
204
+
205
+ // Activate list_orders only for this request
206
+ // myToolSet === toolSet, original toolSet is mutated for this request
207
+ const myToolSet = toolSet.activate(['list_orders']);
208
+
209
+ const { tools, activeTools } = myToolSet;
210
+
211
+ const result = await generateText({
212
+ model,
213
+ tools,
214
+ activeTools,
215
+ messages,
216
+ });
217
+ }
168
218
  ```
169
219
 
170
- ### Typed UI Messages
220
+ ### Typed UI Tool Set
171
221
 
172
222
  Use `InferUIToolSet` to get fully typed UI messages from your tool set:
173
223
 
174
224
  ```typescript
175
225
  import type { UIMessage } from 'ai';
176
- import type { InferUIToolSet, ToolSetConfig } from 'ai-tool-set';
226
+ import type { InferUIToolSet } from 'ai-tool-set';
177
227
 
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;
228
+ const tools = { search, list_orders, cancel_order };
229
+ const toolSet = createToolSet({ tools });
189
230
 
190
- const toolSet = createToolSet(tools);
231
+ // From the tools record
232
+ type MyToolSet = InferUIToolSet<typeof tools>;
191
233
 
192
- // Use tool set config
193
- type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof tools>>;
234
+ // Or from the ToolSet instance
235
+ type MyToolSet = InferUIToolSet<typeof toolSet>;
194
236
 
195
- // Or use ToolSet instance
196
- type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof toolSet>>;
237
+ // Use MyToolSet in your UIMessage type for type-safe access to tool invocation parts:
238
+ type MyUIMessage = UIMessage<unknown, any, MyToolSet>;
239
+ ```
240
+
241
+ ### Custom UIMessage
242
+
243
+ 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`:
244
+
245
+ ```typescript
246
+ import { myTools } from './my-tools.js';
247
+ import { MyUIMessage } from './my-ui-message.js';
248
+
249
+ const toolSet = createToolSet<typeof myTools, MyUIMessage>({ tools: myTools }).activateWhen(
250
+ 'cancel_order',
251
+ ({ messages }) => hasUnfulfilledOrders(messages),
252
+ );
253
+ // ~~~~~~~~
254
+ // Messages are now typed as MyUIMessage, so you get type safety and autocompletion in predicates!
255
+
256
+ const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
257
+ ```
258
+
259
+ ### Custom Context
260
+
261
+ Pass a `CONTEXT` generic to `createToolSet()` to type the `context` field in predicates and `inferTools`:
262
+
263
+ ```typescript
264
+ import { myTools } from './my-tools.js';
265
+ import { MyUIMessage } from './my-ui-message.js';
266
+
267
+ type MyContext = { userId: string; isAdmin: boolean };
268
+
269
+ const toolSet = createToolSet<typeof myTools, MyUIMessage, MyContext>({ tools: myTools }).activateWhen(
270
+ 'cancel_order',
271
+ ({ context }) => context.isAdmin,
272
+ );
273
+ // ~~~~~~~~
274
+ // Context is now typed as MyContext, so you get type safety and autocompletion
275
+
276
+ const { tools, activeTools } = toolSet.inferTools({
277
+ messages,
278
+ context: { isAdmin: true },
279
+ });
197
280
  ```
198
281
 
199
282
  ## API
200
283
 
201
- ## `createToolSet(config)`
284
+ ## `createToolSet(options)`
202
285
 
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`)
286
+ - `options.tools`, a plain `Record<string, Tool>` of AI SDK tools
287
+ - `options.mutable` (optional), set to `true` for a mutable tool set (default: `false`)
206
288
 
207
- Returns a `ToolSet` instance.
289
+ Returns a `ToolSet` instance. All tools are active by default.
208
290
 
209
291
  ```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
- });
292
+ const toolSet = createToolSet({ tools: { search, list_orders, cancel_order } });
293
+
294
+ // Mutable mode — methods mutate in-place and return `this`
295
+ const toolSet = createToolSet({ tools: { search, list_orders, cancel_order }, mutable: true });
221
296
  ```
222
297
 
223
298
  #### `.tools`
224
299
 
225
- All tools as a standard AI SDK tool record (`Record<string, Tool>`), regardless of activation state.
300
+ All tools as a standard AI SDK tool record, regardless of activation state.
226
301
 
227
302
  ```ts
228
303
  const { tools } = toolSet;
@@ -230,81 +305,95 @@ const { tools } = toolSet;
230
305
 
231
306
  #### `.activeTools`
232
307
 
233
- Resolved array of active tool names based on the current state. Accounts for static activation, explicit overrides, and callback evaluation.
308
+ Resolved array of active tool names based on the current state.
234
309
 
235
310
  ```ts
236
311
  const { activeTools } = toolSet;
237
312
  ```
238
313
 
239
- #### `.activateTools(names)`
314
+ #### `.activate(names)`
240
315
 
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.
316
+ Statically activate tools by name. Returns a new instance (immutable) or `this` (mutable).
242
317
 
243
318
  ```ts
244
- const toolSetWithOrders = toolSet.activateTools(['list_orders']);
319
+ toolSet.activate(['cancel_order']);
245
320
  ```
246
321
 
247
- #### `.deactivateTools(names)`
322
+ #### `.deactivate(names)`
248
323
 
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.
324
+ Statically deactivate tools by name. Returns a new instance (immutable) or `this` (mutable).
250
325
 
251
326
  ```ts
252
- const toolSetWithoutSearch = toolSet.deactivateTools(['search']);
327
+ toolSet.deactivate(['search']);
253
328
  ```
254
329
 
255
- #### `.inferTools(context)`
330
+ #### `.activateWhen(name, predicate)` / `.activateWhen(predicates)`
331
+
332
+ Conditionally activate tools. The predicate receives `{ messages, context }` and returns `true` to activate.
333
+
334
+ ```ts
335
+ toolSet.activateWhen('cancel_order', ({ messages }) => hasOrders(messages));
336
+
337
+ toolSet.activateWhen({
338
+ cancel_order: ({ messages }) => hasOrders(messages),
339
+ list_orders: ({ context }) => context.isAuthenticated,
340
+ });
341
+ ```
256
342
 
257
- Evaluate callback-based tools against the provided context. Returns a new `ToolSet` instance.
343
+ #### `.deactivateWhen(name, predicate)` / `.deactivateWhen(predicates)`
258
344
 
259
- - `context`:
260
- - `messages`, `Array<UIMessage>`, the current conversation messages used by activation callbacks to determine tool availability
345
+ Conditionally deactivate tools. The predicate receives `{ messages, context }` and returns `true` to deactivate.
261
346
 
262
347
  ```ts
263
- const toolSetInferred = toolSet.inferTools({ messages });
348
+ toolSet.deactivateWhen('search', ({ messages }) => messages.length > 10);
349
+ ```
350
+
351
+ #### `.inferTools(input)`
352
+
353
+ Evaluate all predicates with the provided messages and context. Returns `{ tools, activeTools }` — directly spreadable into `generateText()` or `streamText()`. This is a terminal operation: messages and context are consumed but not stored.
354
+
355
+ - `input`:
356
+ - `messages`, the current conversation messages
357
+ - `context`, arbitrary values passed to predicates
358
+
359
+ ```ts
360
+ const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
361
+
362
+ const result = await generateText({ model, tools, activeTools, messages });
264
363
  ```
265
364
 
266
365
  ## Types
267
366
 
268
- ### `ToolSetConfig`
367
+ ### `ActivationInput`
269
368
 
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:
369
+ Input passed to activation predicates. Generic over `MESSAGE` and `CONTEXT`:
271
370
 
272
371
  ```ts
273
- import { type ToolSetConfig } from 'ai-tool-set';
372
+ import type { ActivationInput } from 'ai-tool-set';
274
373
 
275
- const tools = {
276
- search: tool({ ... }),
277
- list_orders: { tool: tool({ ... }), active: false },
278
- cancel_order: { tool: tool({ ... }), active: ({ messages }) => /* ... */ },
279
- } satisfies ToolSetConfig;
374
+ type MyInput = ActivationInput<MyUIMessage, { isAdmin: boolean }>;
375
+ // { messages: Array<MyUIMessage>; context: { isAdmin: boolean } }
280
376
  ```
281
377
 
282
378
  ### `InferToolSet`
283
379
 
284
- Extract a `Record<string, Tool>` from a `ToolSetConfig` object or `ToolSet` instance, stripping activation metadata:
380
+ Extract the raw tool record from a tool record or `ToolSet` instance:
285
381
 
286
382
  ```ts
287
- import { type InferToolSet } from 'ai-tool-set';
383
+ import type { InferToolSet } from 'ai-tool-set';
288
384
 
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
385
  type Tools = InferToolSet<typeof toolSet>;
386
+ // { search: Tool<...>, list_orders: Tool<...>, cancel_order: Tool<...> }
295
387
  ```
296
388
 
297
389
  ### `InferUIToolSet`
298
390
 
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:
391
+ Derive typed UI tool parts from a tool record or `ToolSet` instance. Use with `UIMessage` for type-safe access to tool invocation parts:
300
392
 
301
393
  ```ts
302
394
  import type { UIMessage } from 'ai';
303
- import { type InferUIToolSet } from 'ai-tool-set';
304
-
305
- type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof tools>>;
395
+ import type { InferUIToolSet } from 'ai-tool-set';
306
396
 
307
- // Also works with a ToolSet instance
308
397
  type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof toolSet>>;
309
398
 
310
399
  // Parts are now typed per tool:
package/dist/index.d.mts CHANGED
@@ -8,9 +8,19 @@ type MessageType = UIMessage | ModelMessage;
8
8
  /** The fully-typed UIMessage for a given tool record. */
9
9
  type InferUIMessage<TOOLS extends ToolRecord> = UIMessage<unknown, any, InferUIToolSet<TOOLS>>;
10
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;
11
+ type InferToolSet<T extends ToolRecord | AnyToolSet> = T extends ImmutableToolSet<infer TOOLS, any, any> ? TOOLS : T extends MutableToolSet<infer TOOLS, any, any> ? TOOLS : T;
12
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]> };
13
+ type InferUIToolSet<T extends ToolRecord | AnyToolSet> = { [K in keyof InferToolSet<T> & string]: InferUITool<InferToolSet<T>[K]> };
14
+ /**
15
+ * Extract tool names tracked as active from an ImmutableToolSet instance.
16
+ * Returns `never` for MutableToolSet (cannot be determined at compile time).
17
+ */
18
+ type ActiveTools<T extends AnyToolSet> = T extends ImmutableToolSet<any, any, any, infer A, any> ? A : never;
19
+ /**
20
+ * Extract tool names tracked as inactive from an ImmutableToolSet instance.
21
+ * Returns `never` for MutableToolSet (cannot be determined at compile time).
22
+ */
23
+ type InactiveTools<T extends AnyToolSet> = T extends ImmutableToolSet<any, any, any, any, infer D> ? D : never;
14
24
  /**
15
25
  * Input passed to activation predicates.
16
26
  * Use `ActivationInput<MyMsg>` to get per-tool narrowing in callbacks.
@@ -25,28 +35,42 @@ type ActivationEntry = {
25
35
  toolName: string;
26
36
  resolve: (input: ActivationInput<any, any>) => boolean;
27
37
  };
28
- type ToolSetState<TOOLS extends ToolRecord, MESSAGE extends MessageType, CONTEXT extends Record<string, unknown>> = {
38
+ /** Resolved tools and active tool names returned by `inferTools()`. */
39
+ type ResolvedToolSet<TOOLS extends ToolRecord> = {
29
40
  tools: TOOLS;
30
- entries: Array<ActivationEntry>;
31
- input: ActivationInput<MESSAGE, CONTEXT>;
41
+ activeTools: Array<keyof TOOLS & string>;
32
42
  };
43
+ /** Union of both toolset classes for type utility constraints. */
44
+ type AnyToolSet = ImmutableToolSet<any> | MutableToolSet<any>;
45
+ /**
46
+ * Immutable state container for tool activation.
47
+ *
48
+ * All mutation methods return a new ToolSetState instance.
49
+ * Resolution follows "last-call wins": each method appends entries,
50
+ * and the last entry for each tool determines its state.
51
+ */
52
+ declare class ToolSetState<TOOLS extends ToolRecord, MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>> {
53
+ #private;
54
+ constructor(tools: TOOLS, entries: Array<ActivationEntry>);
55
+ /** All tools as a standard AI SDK tool record. */
56
+ get tools(): TOOLS;
57
+ /** Resolved list of active tool names based on current entries (no predicates, default input). */
58
+ get activeTools(): Array<keyof TOOLS & string>;
59
+ activate(names: Array<string>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
60
+ deactivate(names: Array<string>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
61
+ activateWhen(nameOrPredicates: string | Partial<Record<string, ActivationPredicate<MESSAGE, CONTEXT>>>, predicate?: ActivationPredicate<MESSAGE, CONTEXT>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
62
+ deactivateWhen(nameOrPredicates: string | Partial<Record<string, ActivationPredicate<MESSAGE, CONTEXT>>>, predicate?: ActivationPredicate<MESSAGE, CONTEXT>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
63
+ /** Evaluate all predicates with the provided input and return resolved tools + activeTools. */
64
+ inferTools(input: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
65
+ }
33
66
  /**
34
67
  * An immutable tool set with chainable activation methods.
35
68
  *
36
69
  * Resolution follows "last-call wins": each method appends an entry,
37
70
  * and the last entry for each tool determines its state.
38
71
  * 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
72
  */
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> {
73
+ declare class ImmutableToolSet<TOOLS extends ToolRecord, MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>, ACTIVATED extends string = never, DEACTIVATED extends string = never> {
50
74
  #private;
51
75
  /** All tools as a standard AI SDK tool record. */
52
76
  readonly tools: TOOLS;
@@ -54,36 +78,70 @@ declare class ToolSet<TOOLS extends ToolRecord, MESSAGE extends MessageType = UI
54
78
  readonly activeTools: Array<keyof TOOLS & string>;
55
79
  constructor(state: ToolSetState<TOOLS, MESSAGE, CONTEXT>);
56
80
  /** 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>;
81
+ activate<NAMES extends keyof TOOLS & string>(names: Array<NAMES>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED | NAMES, Exclude<DEACTIVATED, NAMES>>;
82
+ /** Statically deactivate tools by name. */
83
+ deactivate<NAMES extends keyof TOOLS & string>(names: Array<NAMES>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, Exclude<ACTIVATED, NAMES>, DEACTIVATED | NAMES>;
84
+ /**
85
+ * Conditionally activate a tool — inactive by default, becomes active when predicate returns true.
86
+ * Tracks names in DEACTIVATED since the tool starts inactive.
87
+ */
88
+ activateWhen<NAME extends keyof TOOLS & string>(name: NAME, predicate: ActivationPredicate<MESSAGE, CONTEXT>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, Exclude<ACTIVATED, NAME>, DEACTIVATED | NAME>;
89
+ activateWhen<NAMES extends keyof TOOLS & string>(predicates: Partial<Record<NAMES, ActivationPredicate<MESSAGE, CONTEXT>>>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, Exclude<ACTIVATED, NAMES>, DEACTIVATED | NAMES>;
90
+ /**
91
+ * Conditionally deactivate a tool — active by default, becomes inactive when predicate returns true.
92
+ * Tracks names in ACTIVATED since the tool starts active.
93
+ */
94
+ deactivateWhen<NAME extends keyof TOOLS & string>(name: NAME, predicate: ActivationPredicate<MESSAGE, CONTEXT>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED | NAME, Exclude<DEACTIVATED, NAME>>;
95
+ deactivateWhen<NAMES extends keyof TOOLS & string>(predicates: Partial<Record<NAMES, ActivationPredicate<MESSAGE, CONTEXT>>>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED | NAMES, Exclude<DEACTIVATED, NAMES>>;
96
+ /** Evaluate all predicates with the provided input. Returns resolved `{ tools, activeTools }`. */
97
+ inferTools(input: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
98
+ }
99
+ /**
100
+ * A mutable tool set with chainable activation methods.
101
+ *
102
+ * Same resolution semantics as ImmutableToolSet, but methods mutate
103
+ * in-place and return `this` instead of creating new instances.
104
+ */
105
+ declare class MutableToolSet<TOOLS extends ToolRecord, MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>> {
106
+ #private;
107
+ /** All tools as a standard AI SDK tool record. */
108
+ readonly tools: TOOLS;
109
+ /** Resolved list of active tool names based on current state. */
110
+ activeTools: Array<keyof TOOLS & string>;
111
+ constructor(state: ToolSetState<TOOLS, MESSAGE, CONTEXT>);
112
+ /** Statically activate tools by name. */
113
+ activate(names: Array<keyof TOOLS & string>): this;
58
114
  /** Statically deactivate tools by name. */
59
- deactivate<NAMES extends ((keyof TOOLS & string) | ACTIVATED) & string>(names: Array<NAMES>): ToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED | NAMES>;
115
+ deactivate(names: Array<keyof TOOLS & string>): this;
60
116
  /**
61
- * Conditionally activate a tool — becomes active when predicate returns true.
62
- *
63
- * - `.activateWhen('tool_name', (input) => ...)`
64
- * - `.activateWhen({ tool_name: (input) => ... })`
117
+ * Conditionally activate a tool — inactive by default, becomes active when predicate returns true.
65
118
  */
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>;
119
+ activateWhen(name: keyof TOOLS & string, predicate: ActivationPredicate<MESSAGE, CONTEXT>): this;
120
+ activateWhen(predicates: Partial<Record<keyof TOOLS & string, ActivationPredicate<MESSAGE, CONTEXT>>>): this;
68
121
  /**
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) => ... })`
122
+ * Conditionally deactivate a tool — active by default, becomes inactive when predicate returns true.
74
123
  */
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>;
124
+ deactivateWhen(name: keyof TOOLS & string, predicate: ActivationPredicate<MESSAGE, CONTEXT>): this;
125
+ deactivateWhen(predicates: Partial<Record<keyof TOOLS & string, ActivationPredicate<MESSAGE, CONTEXT>>>): this;
126
+ /** Evaluate all predicates with the provided input. Returns resolved `{ tools, activeTools }`. */
127
+ inferTools(input: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
79
128
  }
129
+ type CreateToolSetOptions<TOOLS extends ToolRecord> = {
130
+ tools: TOOLS;
131
+ mutable?: boolean;
132
+ };
80
133
  /**
81
- * Create a chainable, immutable tool set.
134
+ * Create a chainable tool set.
82
135
  *
83
136
  * @typeParam TOOLS — inferred from the argument
84
137
  * @typeParam MESSAGE — defaults to the fully-typed UIMessage derived from TOOLS
85
138
  * @typeParam CONTEXT — defaults to Record<string, unknown>
86
139
  */
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>;
140
+ declare function createToolSet<const TOOLS extends ToolRecord, MESSAGE extends MessageType = InferUIMessage<TOOLS>, CONTEXT extends Record<string, unknown> = Record<string, unknown>>(options: CreateToolSetOptions<TOOLS> & {
141
+ mutable: true;
142
+ }): MutableToolSet<TOOLS, MESSAGE, CONTEXT>;
143
+ declare function createToolSet<const TOOLS extends ToolRecord, MESSAGE extends MessageType = InferUIMessage<TOOLS>, CONTEXT extends Record<string, unknown> = Record<string, unknown>>(options: CreateToolSetOptions<TOOLS> & {
144
+ mutable?: false;
145
+ }): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, keyof TOOLS & string>;
88
146
  //#endregion
89
- export { type ActivationInput, type InferToolSet, type InferUIToolSet, createToolSet };
147
+ export { type ActivationInput, type ActiveTools, type InactiveTools, type InferToolSet, type InferUIToolSet, createToolSet };
package/dist/index.mjs CHANGED
@@ -10,99 +10,151 @@ const toEntries = (nameOrPredicates, predicate) => {
10
10
  }));
11
11
  };
12
12
  /**
13
- * An immutable tool set with chainable activation methods.
13
+ * Immutable state container for tool activation.
14
14
  *
15
- * Resolution follows "last-call wins": each method appends an entry,
15
+ * All mutation methods return a new ToolSetState instance.
16
+ * Resolution follows "last-call wins": each method appends entries,
16
17
  * 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
18
  */
28
- var ToolSet = class ToolSet {
29
- #state;
19
+ var ToolSetState = class ToolSetState {
20
+ #tools;
21
+ #entries;
22
+ constructor(tools, entries) {
23
+ this.#tools = tools;
24
+ this.#entries = entries;
25
+ }
30
26
  /** 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
- });
27
+ get tools() {
28
+ return this.#tools;
29
+ }
30
+ /** Resolved list of active tool names based on current entries (no predicates, default input). */
31
+ get activeTools() {
32
+ return this.inferTools({
33
+ messages: [],
34
+ context: {}
35
+ }).activeTools;
42
36
  }
43
- /** Statically activate tools by name. */
44
37
  activate(names) {
45
38
  const newEntries = names.map((name) => ({
46
39
  toolName: name,
47
40
  resolve: () => true
48
41
  }));
49
- return new ToolSet({
50
- ...this.#state,
51
- entries: [...this.#state.entries, ...newEntries]
52
- });
42
+ return new ToolSetState(this.#tools, [...this.#entries, ...newEntries]);
53
43
  }
54
- /** Statically deactivate tools by name. */
55
44
  deactivate(names) {
56
45
  const newEntries = names.map((name) => ({
57
46
  toolName: name,
58
47
  resolve: () => false
59
48
  }));
60
- return new ToolSet({
61
- ...this.#state,
62
- entries: [...this.#state.entries, ...newEntries]
63
- });
49
+ return new ToolSetState(this.#tools, [...this.#entries, ...newEntries]);
64
50
  }
65
51
  activateWhen(nameOrPredicates, predicate) {
66
- const newEntries = toEntries(nameOrPredicates, predicate);
67
- return new ToolSet({
68
- ...this.#state,
69
- entries: [...this.#state.entries, ...newEntries]
70
- });
52
+ return new ToolSetState(this.#tools, [...this.#entries, ...toEntries(nameOrPredicates, predicate)]);
71
53
  }
72
54
  deactivateWhen(nameOrPredicates, predicate) {
73
55
  const newEntries = toEntries(nameOrPredicates, predicate).map((e) => ({
74
56
  ...e,
75
57
  resolve: (input) => !e.resolve(input)
76
58
  }));
77
- return new ToolSet({
78
- ...this.#state,
79
- entries: [...this.#state.entries, ...newEntries]
80
- });
59
+ return new ToolSetState(this.#tools, [...this.#entries, ...newEntries]);
81
60
  }
82
- /** Provide messages and context for predicate-based tools. Returns a new ToolSet. */
61
+ /** Evaluate all predicates with the provided input and return resolved tools + activeTools. */
83
62
  inferTools(input) {
84
- return new ToolSet({
85
- ...this.#state,
86
- input
63
+ const activeTools = Object.keys(this.#tools).filter((name) => {
64
+ const lastEntry = this.#entries.findLast((e) => e.toolName === name);
65
+ if (!lastEntry) return true;
66
+ return lastEntry.resolve(input);
87
67
  });
68
+ return {
69
+ tools: this.#tools,
70
+ activeTools
71
+ };
88
72
  }
89
73
  };
90
74
  /**
91
- * Create a chainable, immutable tool set.
75
+ * An immutable tool set with chainable activation methods.
92
76
  *
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>
77
+ * Resolution follows "last-call wins": each method appends an entry,
78
+ * and the last entry for each tool determines its state.
79
+ * Default (no entry) is active.
96
80
  */
97
- const createToolSet = (tools) => {
98
- return new ToolSet({
99
- tools,
100
- entries: [],
101
- input: {
102
- messages: [],
103
- context: {}
104
- }
105
- });
81
+ var ImmutableToolSet = class ImmutableToolSet {
82
+ #state;
83
+ /** All tools as a standard AI SDK tool record. */
84
+ tools;
85
+ /** Resolved list of active tool names based on current state. */
86
+ activeTools;
87
+ constructor(state) {
88
+ this.#state = state;
89
+ this.tools = state.tools;
90
+ this.activeTools = state.activeTools;
91
+ }
92
+ /** Statically activate tools by name. */
93
+ activate(names) {
94
+ return new ImmutableToolSet(this.#state.activate(names));
95
+ }
96
+ /** Statically deactivate tools by name. */
97
+ deactivate(names) {
98
+ return new ImmutableToolSet(this.#state.deactivate(names));
99
+ }
100
+ activateWhen(nameOrPredicates, predicate) {
101
+ return new ImmutableToolSet(this.#state.activateWhen(nameOrPredicates, predicate));
102
+ }
103
+ deactivateWhen(nameOrPredicates, predicate) {
104
+ return new ImmutableToolSet(this.#state.deactivateWhen(nameOrPredicates, predicate));
105
+ }
106
+ /** Evaluate all predicates with the provided input. Returns resolved `{ tools, activeTools }`. */
107
+ inferTools(input) {
108
+ return this.#state.inferTools(input);
109
+ }
110
+ };
111
+ /**
112
+ * A mutable tool set with chainable activation methods.
113
+ *
114
+ * Same resolution semantics as ImmutableToolSet, but methods mutate
115
+ * in-place and return `this` instead of creating new instances.
116
+ */
117
+ var MutableToolSet = class {
118
+ #state;
119
+ /** All tools as a standard AI SDK tool record. */
120
+ tools;
121
+ /** Resolved list of active tool names based on current state. */
122
+ activeTools;
123
+ constructor(state) {
124
+ this.#state = state;
125
+ this.tools = state.tools;
126
+ this.activeTools = state.activeTools;
127
+ }
128
+ #apply(state) {
129
+ this.#state = state;
130
+ this.activeTools = state.activeTools;
131
+ }
132
+ /** Statically activate tools by name. */
133
+ activate(names) {
134
+ this.#apply(this.#state.activate(names));
135
+ return this;
136
+ }
137
+ /** Statically deactivate tools by name. */
138
+ deactivate(names) {
139
+ this.#apply(this.#state.deactivate(names));
140
+ return this;
141
+ }
142
+ activateWhen(nameOrPredicates, predicate) {
143
+ this.#apply(this.#state.activateWhen(nameOrPredicates, predicate));
144
+ return this;
145
+ }
146
+ deactivateWhen(nameOrPredicates, predicate) {
147
+ this.#apply(this.#state.deactivateWhen(nameOrPredicates, predicate));
148
+ return this;
149
+ }
150
+ /** Evaluate all predicates with the provided input. Returns resolved `{ tools, activeTools }`. */
151
+ inferTools(input) {
152
+ return this.#state.inferTools(input);
153
+ }
106
154
  };
155
+ function createToolSet(options) {
156
+ const state = new ToolSetState(options.tools, []);
157
+ return options.mutable ? new MutableToolSet(state) : new ImmutableToolSet(state);
158
+ }
107
159
  //#endregion
108
160
  export { createToolSet };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-tool-set",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "Tool set utilities for the Vercel AI SDK",
5
5
  "keywords": [],
6
6
  "license": "MIT",