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 CHANGED
@@ -1,3 +1,317 @@
1
+ <div align='center'>
2
+
1
3
  # ai-tool-set
2
4
 
3
- > **Work in progress** this package is under active development and not yet ready for use.
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.1",
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.3",
25
- "ai": "^6.0.141",
24
+ "@types/node": "^25.5.2",
25
+ "ai": "^6.0.153",
26
26
  "husky": "^9.1.7",
27
- "lint-staged": "^16.2.7",
28
- "oxfmt": "^0.30.0",
29
- "oxlint": "^1.45.0",
30
- "oxlint-tsgolint": "^0.11.5",
31
- "pkg-pr-new": "^0.0.63",
32
- "publint": "^0.3.17",
33
- "tsdown": "^0.20.3",
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": "^5.9.3",
36
- "vitest": "^4.0.18",
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",