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

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
@@ -58,58 +58,53 @@ const tools = {
58
58
  const toolSet = createToolSet({ tools });
59
59
  ```
60
60
 
61
- ### Use with `generateText` / `streamText`
61
+ ### Activate and Deactivate Tools
62
62
 
63
- The tool set exposes `tools` and `activeTools` as properties that can be used directly:
63
+ Use `.activate()` and `.deactivate()` to statically control which tools are available. Call `.inferTools()` to resolve `activeTools` and pass into `generateText()` or `streamText()`:
64
64
 
65
65
  ```typescript
66
66
  import { generateText } from 'ai';
67
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:
68
+ // Activate and deactivate tools
69
+ const toolSet = createToolSet({ tools })
70
+ .deactivate(['cancel_order'])
71
+ .activate(['list_orders']);
83
72
 
84
- ```typescript
85
- const { tools, activeTools } = toolSet.deactivate(['cancel_order']).activate(['list_orders']);
73
+ // Infer active tools
74
+ const { tools, activeTools } = toolSet.inferTools();
86
75
 
87
76
  const result = await generateText({
88
77
  model,
78
+ // Pass tools and activeTools:
89
79
  tools,
90
80
  activeTools,
81
+ // Or spread directly:
82
+ // ...toolSet.deactivate(['cancel_order']).activate(['list_orders']).inferTools(),
91
83
  prompt: 'Show me my orders',
92
84
  });
93
85
  ```
94
86
 
95
87
  ### Conditional Activation
96
88
 
97
- Use `.activateWhen()` and `.deactivateWhen()` to conditionally control tools based on messages and context. The predicate receives an `ActivationInput` with `messages` and `context`.
89
+ Use `.activateWhen()` and `.deactivateWhen()` to conditionally control tools based on messages and context. The predicate receives an input with `messages` and `context` (both can be `undefined` if not provided to `inferTools`) and should return a boolean (or undefined) to determine whether the tool should be activated/deactivated.
98
90
 
99
91
  ```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'),
92
+ // Conditional activation with a predicate that checks for unfulfilled orders in the messages
93
+ const toolSet = createToolSet({ tools })
94
+ .activateWhen('list_orders', ({ context }) => context?.isAuthenticated)
95
+ .activateWhen('cancel_order', ({ messages }) =>
96
+ messages?.some((m) =>
97
+ m.parts.some(
98
+ (p) =>
99
+ p.type === 'tool-list_orders' &&
100
+ p.state === 'output-available' &&
101
+ p.output.orders?.some((order) => order.status !== 'fulfilled'),
102
+ ),
107
103
  ),
108
- ),
109
- );
104
+ );
110
105
  ```
111
106
 
112
- Use `.inferTools()` to evaluate predicates with the current messages and context. It returns `{ tools, activeTools }` that can be spread directly into `generateText()`:
107
+ Call `.inferTools()` with messages and/or context to evaluate activation predicates and resolve `activeTools`:
113
108
 
114
109
  ```typescript
115
110
  const messages = [
@@ -136,8 +131,10 @@ const messages = [
136
131
  },
137
132
  ];
138
133
 
134
+ const context = { isAuthenticated: true };
135
+
139
136
  // cancel_order is now active because list_orders returned unfulfilled orders
140
- const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
137
+ const { tools, activeTools } = toolSet.inferTools({ messages, context });
141
138
 
142
139
  const result = await generateText({ model, tools, activeTools, messages });
143
140
  ```
@@ -145,26 +142,53 @@ const result = await generateText({ model, tools, activeTools, messages });
145
142
  You can also activate multiple tools at once:
146
143
 
147
144
  ```typescript
148
- const toolSet = createToolSet({ tools }).activateWhen({
149
- list_orders: ({ context }) => context.isAuthenticated,
150
- cancel_order: ({ messages }) => hasUnfulfilledOrders(messages),
151
- });
145
+ const toolSet = createToolSet({ tools })
146
+ .activateWhen({
147
+ list_orders: ({ context }) => context?.isAuthenticated,
148
+ cancel_order: ({ messages }) => hasUnfulfilledOrders(messages),
149
+ });
150
+ ```
151
+
152
+ ### Activation Defaults
153
+
154
+ `.activateWhen()` marks a tool as **inactive by default**. It only becomes active when the predicate returns `true`. If the predicate returns `undefined` or `false`, the tool stays inactive:
155
+
156
+ ```typescript
157
+ const toolSet = createToolSet({ tools })
158
+ // undefined when messages is not provided → tool stays inactive
159
+ // false when no orders found → tool stays inactive
160
+ // true when orders found → tool becomes active
161
+ .activateWhen('cancel_order', ({ messages }) => messages?.some((m) => hasOrders(m)));
162
+
163
+ toolSet.inferTools().activeTools; // cancel_order is inactive (predicate received undefined)
164
+ toolSet.inferTools({ messages: [] }).activeTools; // cancel_order is inactive (no orders)
165
+ ```
166
+
167
+ `.deactivateWhen()` marks a tool as **active by default**. It only becomes inactive when the predicate returns `true`. If the predicate returns `undefined` or `false`, the tool stays active:
168
+
169
+ ```typescript
170
+ const toolSet = createToolSet({ tools })
171
+ // undefined when messages is not provided → tool stays active
172
+ // false when few messages → tool stays active
173
+ // true when too many messages → tool becomes inactive
174
+ .deactivateWhen('search', ({ messages }) => messages && messages.length > 10);
175
+
176
+ toolSet.inferTools().activeTools; // search is active (predicate received undefined)
177
+ toolSet.inferTools({ messages: [] }).activeTools; // search is active (few messages)
152
178
  ```
153
179
 
154
180
  ### Last-Call Wins
155
181
 
156
- Each method appends to an internal list. For each tool, the **last entry** determines its state. This makes ordering explicit and predictable:
182
+ Each activation method appends to an internal list. For each tool, the **last entry** determines its state. This makes ordering explicit and predictable:
157
183
 
158
184
  ```typescript
159
185
  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
- );
186
+ // cancel_order: activated
187
+ .activate(['cancel_order'])
188
+ // cancel_order: deactivated
189
+ .deactivate(['cancel_order'])
190
+ // cancel_order: deactivated with conditional activation
191
+ .activateWhen('cancel_order', ({ messages }) => hasUnfulfilledOrders(messages));
168
192
  ```
169
193
 
170
194
  ### Immutable vs Mutable
@@ -182,36 +206,63 @@ export async function POST(req: Request) {
182
206
  // myToolSet !== toolSet, original toolSet is unchanged for next request
183
207
  const myToolSet = toolSet.activate(['list_orders']);
184
208
 
185
- const { tools, activeTools } = myToolSet;
186
-
187
209
  const result = await generateText({
188
210
  model,
189
- tools,
190
- activeTools,
211
+ ...myToolSet.inferTools({ messages }),
191
212
  messages,
192
213
  });
193
214
  }
194
215
  ```
195
216
 
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:
217
+ Use `createToolSet({ 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
218
 
198
219
  ```typescript
199
220
  export async function POST(req: Request) {
200
221
  const { messages } = await req.json();
201
222
 
202
223
  // Local scope: created and mutated per request
203
- const toolSet = createToolSet({ tools, mutable: true }).deactivate(['list_order', 'cancel_order']);
224
+ const toolSet = createToolSet({ tools, mutable: true })
225
+ .deactivate(['list_order', 'cancel_order'])
226
+ .activate(['list_orders']);
204
227
 
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']);
228
+ const result = await generateText({
229
+ model,
230
+ ...toolSet.inferTools({ messages }),
231
+ messages,
232
+ });
233
+ }
234
+ ```
235
+
236
+ ### Cloning
208
237
 
209
- const { tools, activeTools } = myToolSet;
238
+ Use `.clone({ mutable?: boolean })` to convert between immutable and mutable, preserving all activation entries:
239
+
240
+ ```typescript
241
+ // Convert an immutable toolset to mutable
242
+ const mutableToolSet = toolSet.clone({ mutable: true });
243
+
244
+ // Convert a mutable toolset back to immutable
245
+ const immutableToolSet = mutableToolSet.clone();
246
+ ```
247
+
248
+ This is useful when you want to create a base tool set in the global scope and clone it per request to add request-specific activation:
249
+
250
+ ```typescript
251
+ // Global scope: base tool set
252
+ const baseToolSet = createToolSet({ tools }).deactivate(['list_order', 'cancel_order']);
253
+
254
+ export async function POST(req: Request) {
255
+ const { messages } = await req.json();
256
+
257
+ // Clone the base tool set into a mutable instance for this request
258
+ const toolSet = baseToolSet.clone({ mutable: true });
259
+
260
+ // Activate list_orders only for this request
261
+ toolSet.activate(['list_orders']);
210
262
 
211
263
  const result = await generateText({
212
264
  model,
213
- tools,
214
- activeTools,
265
+ ...toolSet.inferTools({ messages }),
215
266
  messages,
216
267
  });
217
268
  }
@@ -246,14 +297,16 @@ If you already have a custom `UIMessage` type, you can pass it as `MESSAGE` gene
246
297
  import { myTools } from './my-tools.js';
247
298
  import { MyUIMessage } from './my-ui-message.js';
248
299
 
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!
300
+ const toolSet = createToolSet<typeof myTools, MyUIMessage>({ tools: myTools })
301
+ .activateWhen(
302
+ 'cancel_order',
303
+ ({ messages }) => hasUnfulfilledOrders(messages),
304
+ // ~~~~~~~~
305
+ // Messages are now typed as Array<MyUIMessage> | undefined
306
+ );
307
+
255
308
 
256
- const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
309
+ const { tools, activeTools } = toolSet.inferTools({ messages });
257
310
  ```
258
311
 
259
312
  ### Custom Context
@@ -266,16 +319,18 @@ import { MyUIMessage } from './my-ui-message.js';
266
319
 
267
320
  type MyContext = { userId: string; isAdmin: boolean };
268
321
 
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
322
+ const toolSet = createToolSet<typeof myTools, MyUIMessage, MyContext>({ tools: myTools })
323
+ .activateWhen(
324
+ 'cancel_order',
325
+ ({ context }) => context?.isAdmin,
326
+ // ~~~~~~~
327
+ // Context is typed as MyContext | undefined
328
+ );
329
+
275
330
 
276
331
  const { tools, activeTools } = toolSet.inferTools({
277
332
  messages,
278
- context: { isAdmin: true },
333
+ context: { userId: '1', isAdmin: true },
279
334
  });
280
335
  ```
281
336
 
@@ -303,14 +358,6 @@ All tools as a standard AI SDK tool record, regardless of activation state.
303
358
  const { tools } = toolSet;
304
359
  ```
305
360
 
306
- #### `.activeTools`
307
-
308
- Resolved array of active tool names based on the current state.
309
-
310
- ```ts
311
- const { activeTools } = toolSet;
312
- ```
313
-
314
361
  #### `.activate(names)`
315
362
 
316
363
  Statically activate tools by name. Returns a new instance (immutable) or `this` (mutable).
@@ -329,50 +376,69 @@ toolSet.deactivate(['search']);
329
376
 
330
377
  #### `.activateWhen(name, predicate)` / `.activateWhen(predicates)`
331
378
 
332
- Conditionally activate tools. The predicate receives `{ messages, context }` and returns `true` to activate.
379
+ Conditionally activate tools. The predicate receives `{ messages, context }` and returns `true` to activate. Both `messages` and `context` can be `undefined` if not provided to `inferTools`. Returning `undefined` is treated as `false`.
333
380
 
334
381
  ```ts
335
- toolSet.activateWhen('cancel_order', ({ messages }) => hasOrders(messages));
382
+ toolSet.activateWhen('cancel_order', ({ messages }) => messages?.some((m) => hasOrders(m)));
336
383
 
337
384
  toolSet.activateWhen({
338
- cancel_order: ({ messages }) => hasOrders(messages),
339
- list_orders: ({ context }) => context.isAuthenticated,
385
+ cancel_order: ({ messages }) => messages?.some((m) => hasOrders(m)),
386
+ list_orders: ({ context }) => context?.isAuthenticated,
340
387
  });
341
388
  ```
342
389
 
343
390
  #### `.deactivateWhen(name, predicate)` / `.deactivateWhen(predicates)`
344
391
 
345
- Conditionally deactivate tools. The predicate receives `{ messages, context }` and returns `true` to deactivate.
392
+ Conditionally deactivate tools. The predicate receives `{ messages, context }` and returns `true` to deactivate. Both `messages` and `context` can be `undefined` if not provided to `inferTools`. Returning `undefined` is treated as `false` (tool stays active).
346
393
 
347
394
  ```ts
348
- toolSet.deactivateWhen('search', ({ messages }) => messages.length > 10);
395
+ toolSet.deactivateWhen('search', ({ messages }) => messages && messages.length > 10);
349
396
  ```
350
397
 
351
- #### `.inferTools(input)`
398
+ #### `.inferTools(input?)`
352
399
 
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.
400
+ Evaluate all predicates and return `{ tools, activeTools }`, directly spreadable into `generateText()` or `streamText()`. The input is optional; all fields are optional. Predicates receive `undefined` for fields not provided.
354
401
 
355
- - `input`:
356
- - `messages`, the current conversation messages
357
- - `context`, arbitrary values passed to predicates
402
+ - `input` (optional):
403
+ - `messages` (optional), the current conversation messages
404
+ - `context` (optional), arbitrary values passed to predicates
358
405
 
359
406
  ```ts
360
- const { tools, activeTools } = toolSet.inferTools({ messages, context: {} });
407
+ // Static-only (no predicates)
408
+ const { tools, activeTools } = toolSet.inferTools();
409
+
410
+ // With messages
411
+ const { tools, activeTools } = toolSet.inferTools({ messages });
412
+
413
+ // With context
414
+ const { tools, activeTools } = toolSet.inferTools({ context: { isAdmin: true } });
415
+
416
+ // With both
417
+ const { tools, activeTools } = toolSet.inferTools({ messages, context });
361
418
 
362
419
  const result = await generateText({ model, tools, activeTools, messages });
363
420
  ```
364
421
 
422
+ #### `.clone(options?)`
423
+
424
+ Clone the toolset, preserving all activation entries. Pass `{ mutable: true }` to get a mutable clone, or omit for an immutable clone. Defaults to immutable.
425
+
426
+ ```ts
427
+ const mutableClone = toolSet.clone({ mutable: true });
428
+ const immutableClone = toolSet.clone();
429
+ ```
430
+
365
431
  ## Types
366
432
 
367
433
  ### `ActivationInput`
368
434
 
369
- Input passed to activation predicates. Generic over `MESSAGE` and `CONTEXT`:
435
+ Input passed to activation predicates. Generic over `MESSAGE` and `CONTEXT`. Both `messages` and `context` are optional since they may not be provided to `inferTools`:
370
436
 
371
437
  ```ts
372
438
  import type { ActivationInput } from 'ai-tool-set';
373
439
 
374
440
  type MyInput = ActivationInput<MyUIMessage, { isAdmin: boolean }>;
375
- // { messages: Array<MyUIMessage>; context: { isAdmin: boolean } }
441
+ // { messages?: Array<MyUIMessage>; context?: { isAdmin: boolean } }
376
442
  ```
377
443
 
378
444
  ### `InferToolSet`
@@ -401,6 +467,38 @@ type MyUIMessage = UIMessage<unknown, any, InferUIToolSet<typeof toolSet>>;
401
467
  // message.parts[0].output // typed as search tool's return type
402
468
  ```
403
469
 
470
+ ### `ActiveTools`
471
+
472
+ Extract the tool names tracked as active from an immutable `ToolSet` instance. Tracks tools from `.activate()` and `.deactivateWhen()`.
473
+
474
+ > [!NOTE]
475
+ > `ActiveTools` returns `never` for mutable toolsets, since TypeScript cannot track type changes on the same reference across method calls.
476
+
477
+ ```ts
478
+ import type { ActiveTools } from 'ai-tool-set';
479
+
480
+ const toolSet = createToolSet({ tools }).deactivate(['cancel_order']);
481
+
482
+ type Active = ActiveTools<typeof toolSet>;
483
+ // 'search' | 'list_orders'
484
+ ```
485
+
486
+ ### `InactiveTools`
487
+
488
+ Extract the tool names tracked as inactive from an immutable `ToolSet` instance. Tracks tools from `.deactivate()` and `.activateWhen()`.
489
+
490
+ > [!NOTE]
491
+ > `InactiveTools` returns `never` for mutable toolsets, since TypeScript cannot track type changes on the same reference across method calls.
492
+
493
+ ```ts
494
+ import type { InactiveTools } from 'ai-tool-set';
495
+
496
+ const toolSet = createToolSet({ tools }).deactivate(['cancel_order']);
497
+
498
+ type Inactive = InactiveTools<typeof toolSet>;
499
+ // 'cancel_order'
500
+ ```
501
+
404
502
  ## License
405
503
 
406
504
  MIT
package/dist/index.d.mts CHANGED
@@ -26,14 +26,14 @@ type InactiveTools<T extends AnyToolSet> = T extends ImmutableToolSet<any, any,
26
26
  * Use `ActivationInput<MyMsg>` to get per-tool narrowing in callbacks.
27
27
  */
28
28
  type ActivationInput<MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>> = {
29
- messages: Array<MESSAGE>;
30
- context: CONTEXT;
29
+ messages?: Array<MESSAGE>;
30
+ context?: CONTEXT;
31
31
  };
32
- /** Activation predicate — returns true if tool should be active. */
33
- type ActivationPredicate<MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>> = (input: ActivationInput<MESSAGE, CONTEXT>) => boolean;
32
+ /** Activation predicate — returns true if tool should be active. Undefined is treated as false. */
33
+ type ActivationPredicate<MESSAGE extends MessageType = UIMessage, CONTEXT extends Record<string, unknown> = Record<string, unknown>> = (input: ActivationInput<MESSAGE, CONTEXT>) => boolean | undefined;
34
34
  type ActivationEntry = {
35
35
  toolName: string;
36
- resolve: (input: ActivationInput<any, any>) => boolean;
36
+ resolve: (input: ActivationInput<any, any>) => boolean | undefined;
37
37
  };
38
38
  /** Resolved tools and active tool names returned by `inferTools()`. */
39
39
  type ResolvedToolSet<TOOLS extends ToolRecord> = {
@@ -54,14 +54,12 @@ declare class ToolSetState<TOOLS extends ToolRecord, MESSAGE extends MessageType
54
54
  constructor(tools: TOOLS, entries: Array<ActivationEntry>);
55
55
  /** All tools as a standard AI SDK tool record. */
56
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
57
  activate(names: Array<string>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
60
58
  deactivate(names: Array<string>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
61
59
  activateWhen(nameOrPredicates: string | Partial<Record<string, ActivationPredicate<MESSAGE, CONTEXT>>>, predicate?: ActivationPredicate<MESSAGE, CONTEXT>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
62
60
  deactivateWhen(nameOrPredicates: string | Partial<Record<string, ActivationPredicate<MESSAGE, CONTEXT>>>, predicate?: ActivationPredicate<MESSAGE, CONTEXT>): ToolSetState<TOOLS, MESSAGE, CONTEXT>;
63
61
  /** Evaluate all predicates with the provided input and return resolved tools + activeTools. */
64
- inferTools(input: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
62
+ inferTools(input?: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
65
63
  }
66
64
  /**
67
65
  * An immutable tool set with chainable activation methods.
@@ -74,8 +72,6 @@ declare class ImmutableToolSet<TOOLS extends ToolRecord, MESSAGE extends Message
74
72
  #private;
75
73
  /** All tools as a standard AI SDK tool record. */
76
74
  readonly tools: TOOLS;
77
- /** Resolved list of active tool names based on current state. */
78
- readonly activeTools: Array<keyof TOOLS & string>;
79
75
  constructor(state: ToolSetState<TOOLS, MESSAGE, CONTEXT>);
80
76
  /** Statically activate tools by name. */
81
77
  activate<NAMES extends keyof TOOLS & string>(names: Array<NAMES>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED | NAMES, Exclude<DEACTIVATED, NAMES>>;
@@ -94,7 +90,14 @@ declare class ImmutableToolSet<TOOLS extends ToolRecord, MESSAGE extends Message
94
90
  deactivateWhen<NAME extends keyof TOOLS & string>(name: NAME, predicate: ActivationPredicate<MESSAGE, CONTEXT>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED | NAME, Exclude<DEACTIVATED, NAME>>;
95
91
  deactivateWhen<NAMES extends keyof TOOLS & string>(predicates: Partial<Record<NAMES, ActivationPredicate<MESSAGE, CONTEXT>>>): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED | NAMES, Exclude<DEACTIVATED, NAMES>>;
96
92
  /** Evaluate all predicates with the provided input. Returns resolved `{ tools, activeTools }`. */
97
- inferTools(input: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
93
+ inferTools(input?: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
94
+ /** Clone this toolset, optionally switching between immutable and mutable. */
95
+ clone(options: {
96
+ mutable: true;
97
+ }): MutableToolSet<TOOLS, MESSAGE, CONTEXT>;
98
+ clone(options?: {
99
+ mutable?: false;
100
+ }): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, ACTIVATED, DEACTIVATED>;
98
101
  }
99
102
  /**
100
103
  * A mutable tool set with chainable activation methods.
@@ -106,8 +109,6 @@ declare class MutableToolSet<TOOLS extends ToolRecord, MESSAGE extends MessageTy
106
109
  #private;
107
110
  /** All tools as a standard AI SDK tool record. */
108
111
  readonly tools: TOOLS;
109
- /** Resolved list of active tool names based on current state. */
110
- activeTools: Array<keyof TOOLS & string>;
111
112
  constructor(state: ToolSetState<TOOLS, MESSAGE, CONTEXT>);
112
113
  /** Statically activate tools by name. */
113
114
  activate(names: Array<keyof TOOLS & string>): this;
@@ -124,7 +125,14 @@ declare class MutableToolSet<TOOLS extends ToolRecord, MESSAGE extends MessageTy
124
125
  deactivateWhen(name: keyof TOOLS & string, predicate: ActivationPredicate<MESSAGE, CONTEXT>): this;
125
126
  deactivateWhen(predicates: Partial<Record<keyof TOOLS & string, ActivationPredicate<MESSAGE, CONTEXT>>>): this;
126
127
  /** Evaluate all predicates with the provided input. Returns resolved `{ tools, activeTools }`. */
127
- inferTools(input: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
128
+ inferTools(input?: ActivationInput<MESSAGE, CONTEXT>): ResolvedToolSet<TOOLS>;
129
+ /** Clone this toolset, optionally switching between immutable and mutable. */
130
+ clone(options: {
131
+ mutable: true;
132
+ }): MutableToolSet<TOOLS, MESSAGE, CONTEXT>;
133
+ clone(options?: {
134
+ mutable?: false;
135
+ }): ImmutableToolSet<TOOLS, MESSAGE, CONTEXT, keyof TOOLS & string>;
128
136
  }
129
137
  type CreateToolSetOptions<TOOLS extends ToolRecord> = {
130
138
  tools: TOOLS;
package/dist/index.mjs CHANGED
@@ -27,13 +27,6 @@ var ToolSetState = class ToolSetState {
27
27
  get tools() {
28
28
  return this.#tools;
29
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;
36
- }
37
30
  activate(names) {
38
31
  const newEntries = names.map((name) => ({
39
32
  toolName: name,
@@ -63,7 +56,7 @@ var ToolSetState = class ToolSetState {
63
56
  const activeTools = Object.keys(this.#tools).filter((name) => {
64
57
  const lastEntry = this.#entries.findLast((e) => e.toolName === name);
65
58
  if (!lastEntry) return true;
66
- return lastEntry.resolve(input);
59
+ return lastEntry.resolve(input ?? {});
67
60
  });
68
61
  return {
69
62
  tools: this.#tools,
@@ -82,12 +75,9 @@ var ImmutableToolSet = class ImmutableToolSet {
82
75
  #state;
83
76
  /** All tools as a standard AI SDK tool record. */
84
77
  tools;
85
- /** Resolved list of active tool names based on current state. */
86
- activeTools;
87
78
  constructor(state) {
88
79
  this.#state = state;
89
80
  this.tools = state.tools;
90
- this.activeTools = state.activeTools;
91
81
  }
92
82
  /** Statically activate tools by name. */
93
83
  activate(names) {
@@ -107,6 +97,9 @@ var ImmutableToolSet = class ImmutableToolSet {
107
97
  inferTools(input) {
108
98
  return this.#state.inferTools(input);
109
99
  }
100
+ clone(options) {
101
+ return options?.mutable ? new MutableToolSet(this.#state) : new ImmutableToolSet(this.#state);
102
+ }
110
103
  };
111
104
  /**
112
105
  * A mutable tool set with chainable activation methods.
@@ -114,43 +107,39 @@ var ImmutableToolSet = class ImmutableToolSet {
114
107
  * Same resolution semantics as ImmutableToolSet, but methods mutate
115
108
  * in-place and return `this` instead of creating new instances.
116
109
  */
117
- var MutableToolSet = class {
110
+ var MutableToolSet = class MutableToolSet {
118
111
  #state;
119
112
  /** All tools as a standard AI SDK tool record. */
120
113
  tools;
121
- /** Resolved list of active tool names based on current state. */
122
- activeTools;
123
114
  constructor(state) {
124
115
  this.#state = state;
125
116
  this.tools = state.tools;
126
- this.activeTools = state.activeTools;
127
- }
128
- #apply(state) {
129
- this.#state = state;
130
- this.activeTools = state.activeTools;
131
117
  }
132
118
  /** Statically activate tools by name. */
133
119
  activate(names) {
134
- this.#apply(this.#state.activate(names));
120
+ this.#state = this.#state.activate(names);
135
121
  return this;
136
122
  }
137
123
  /** Statically deactivate tools by name. */
138
124
  deactivate(names) {
139
- this.#apply(this.#state.deactivate(names));
125
+ this.#state = this.#state.deactivate(names);
140
126
  return this;
141
127
  }
142
128
  activateWhen(nameOrPredicates, predicate) {
143
- this.#apply(this.#state.activateWhen(nameOrPredicates, predicate));
129
+ this.#state = this.#state.activateWhen(nameOrPredicates, predicate);
144
130
  return this;
145
131
  }
146
132
  deactivateWhen(nameOrPredicates, predicate) {
147
- this.#apply(this.#state.deactivateWhen(nameOrPredicates, predicate));
133
+ this.#state = this.#state.deactivateWhen(nameOrPredicates, predicate);
148
134
  return this;
149
135
  }
150
136
  /** Evaluate all predicates with the provided input. Returns resolved `{ tools, activeTools }`. */
151
137
  inferTools(input) {
152
138
  return this.#state.inferTools(input);
153
139
  }
140
+ clone(options) {
141
+ return options?.mutable ? new MutableToolSet(this.#state) : new ImmutableToolSet(this.#state);
142
+ }
154
143
  };
155
144
  function createToolSet(options) {
156
145
  const state = new ToolSetState(options.tools, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-tool-set",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "Tool set utilities for the Vercel AI SDK",
5
5
  "keywords": [],
6
6
  "license": "MIT",