ai-functions 2.1.1 → 2.3.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.
Files changed (286) hide show
  1. package/.turbo/turbo-build.log +1 -4
  2. package/CHANGELOG.md +68 -1
  3. package/README.md +397 -157
  4. package/dist/ai-promise.d.ts +50 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +410 -51
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +54 -837
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts +272 -0
  56. package/dist/budget.d.ts.map +1 -0
  57. package/dist/budget.js +513 -0
  58. package/dist/budget.js.map +1 -0
  59. package/dist/cache.d.ts +295 -0
  60. package/dist/cache.d.ts.map +1 -0
  61. package/dist/cache.js +433 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/context.d.ts +42 -8
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +64 -62
  66. package/dist/context.js.map +1 -1
  67. package/dist/digital-objects-registry.d.ts +229 -0
  68. package/dist/digital-objects-registry.d.ts.map +1 -0
  69. package/dist/digital-objects-registry.js +617 -0
  70. package/dist/digital-objects-registry.js.map +1 -0
  71. package/dist/embeddings.d.ts +2 -2
  72. package/dist/embeddings.d.ts.map +1 -1
  73. package/dist/errors.d.ts +22 -0
  74. package/dist/errors.d.ts.map +1 -0
  75. package/dist/errors.js +35 -0
  76. package/dist/errors.js.map +1 -0
  77. package/dist/eval/runner.d.ts +10 -1
  78. package/dist/eval/runner.d.ts.map +1 -1
  79. package/dist/eval/runner.js +41 -35
  80. package/dist/eval/runner.js.map +1 -1
  81. package/dist/eval-log/in-memory.d.ts +34 -0
  82. package/dist/eval-log/in-memory.d.ts.map +1 -0
  83. package/dist/eval-log/in-memory.js +84 -0
  84. package/dist/eval-log/in-memory.js.map +1 -0
  85. package/dist/eval-log/index.d.ts +29 -0
  86. package/dist/eval-log/index.d.ts.map +1 -0
  87. package/dist/eval-log/index.js +39 -0
  88. package/dist/eval-log/index.js.map +1 -0
  89. package/dist/eval-log/types.d.ts +101 -0
  90. package/dist/eval-log/types.d.ts.map +1 -0
  91. package/dist/eval-log/types.js +16 -0
  92. package/dist/eval-log/types.js.map +1 -0
  93. package/dist/function-registry.d.ts +116 -0
  94. package/dist/function-registry.d.ts.map +1 -0
  95. package/dist/function-registry.js +546 -0
  96. package/dist/function-registry.js.map +1 -0
  97. package/dist/generate.d.ts +9 -3
  98. package/dist/generate.d.ts.map +1 -1
  99. package/dist/generate.js +18 -22
  100. package/dist/generate.js.map +1 -1
  101. package/dist/index.d.ts +35 -20
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +89 -42
  104. package/dist/index.js.map +1 -1
  105. package/dist/logger.d.ts +118 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +187 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/middleware/budget.d.ts +84 -0
  110. package/dist/middleware/budget.d.ts.map +1 -0
  111. package/dist/middleware/budget.js +110 -0
  112. package/dist/middleware/budget.js.map +1 -0
  113. package/dist/middleware/cache.d.ts +103 -0
  114. package/dist/middleware/cache.d.ts.map +1 -0
  115. package/dist/middleware/cache.js +228 -0
  116. package/dist/middleware/cache.js.map +1 -0
  117. package/dist/middleware/embed-cache.d.ts +99 -0
  118. package/dist/middleware/embed-cache.d.ts.map +1 -0
  119. package/dist/middleware/embed-cache.js +128 -0
  120. package/dist/middleware/embed-cache.js.map +1 -0
  121. package/dist/middleware/index.d.ts +11 -0
  122. package/dist/middleware/index.d.ts.map +1 -0
  123. package/dist/middleware/index.js +11 -0
  124. package/dist/middleware/index.js.map +1 -0
  125. package/dist/middleware/trace.d.ts +103 -0
  126. package/dist/middleware/trace.d.ts.map +1 -0
  127. package/dist/middleware/trace.js +176 -0
  128. package/dist/middleware/trace.js.map +1 -0
  129. package/dist/primitives.d.ts +120 -1
  130. package/dist/primitives.d.ts.map +1 -1
  131. package/dist/primitives.js +398 -26
  132. package/dist/primitives.js.map +1 -1
  133. package/dist/retry.d.ts +368 -0
  134. package/dist/retry.d.ts.map +1 -0
  135. package/dist/retry.js +646 -0
  136. package/dist/retry.js.map +1 -0
  137. package/dist/schema.d.ts.map +1 -1
  138. package/dist/schema.js +2 -10
  139. package/dist/schema.js.map +1 -1
  140. package/dist/telemetry.d.ts +128 -0
  141. package/dist/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry.js +285 -0
  143. package/dist/telemetry.js.map +1 -0
  144. package/dist/template.d.ts.map +1 -1
  145. package/dist/template.js +6 -1
  146. package/dist/template.js.map +1 -1
  147. package/dist/tool-orchestration.d.ts +453 -0
  148. package/dist/tool-orchestration.d.ts.map +1 -0
  149. package/dist/tool-orchestration.js +763 -0
  150. package/dist/tool-orchestration.js.map +1 -0
  151. package/dist/type-guards.d.ts +28 -0
  152. package/dist/type-guards.d.ts.map +1 -0
  153. package/dist/type-guards.js +29 -0
  154. package/dist/type-guards.js.map +1 -0
  155. package/dist/types.d.ts +135 -17
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js +36 -1
  158. package/dist/types.js.map +1 -1
  159. package/dist/wrap-for-v3.d.ts +80 -0
  160. package/dist/wrap-for-v3.d.ts.map +1 -0
  161. package/dist/wrap-for-v3.js +89 -0
  162. package/dist/wrap-for-v3.js.map +1 -0
  163. package/examples/00-quickstart.ts +232 -0
  164. package/examples/01-rag-chatbot.ts +212 -0
  165. package/examples/02-multi-agent-research.ts +290 -0
  166. package/examples/03-email-classification.ts +379 -0
  167. package/examples/04-content-moderation.ts +400 -0
  168. package/examples/05-document-extraction.ts +455 -0
  169. package/examples/06-streaming-chat-nextjs.ts +437 -0
  170. package/examples/07-cloudflare-worker.ts +483 -0
  171. package/examples/08-batch-processing.ts +491 -0
  172. package/examples/09-budget-constrained.ts +527 -0
  173. package/examples/10-tool-orchestration.ts +565 -0
  174. package/examples/11-retry-resilience.ts +403 -0
  175. package/examples/12-caching-strategies.ts +422 -0
  176. package/examples/README.md +145 -0
  177. package/package.json +10 -6
  178. package/src/ai-promise.ts +528 -99
  179. package/src/ai-schemas.ts +122 -0
  180. package/src/ai.ts +69 -1153
  181. package/src/batch/anthropic.ts +96 -161
  182. package/src/batch/bedrock.ts +203 -454
  183. package/src/batch/cloudflare.ts +99 -282
  184. package/src/batch/google.ts +91 -297
  185. package/src/batch/index.ts +4 -1
  186. package/src/batch/memory.ts +15 -10
  187. package/src/batch/openai.ts +65 -193
  188. package/src/batch/provider.ts +336 -0
  189. package/src/batch-map.ts +29 -24
  190. package/src/batch-queue.ts +200 -11
  191. package/src/budget.ts +740 -0
  192. package/src/cache.ts +681 -0
  193. package/src/context.ts +122 -76
  194. package/src/digital-objects-registry.ts +750 -0
  195. package/src/errors.ts +37 -0
  196. package/src/eval/runner.ts +63 -38
  197. package/src/eval-log/in-memory.ts +90 -0
  198. package/src/eval-log/index.ts +46 -0
  199. package/src/eval-log/types.ts +110 -0
  200. package/src/function-registry.ts +671 -0
  201. package/src/generate.ts +33 -33
  202. package/src/index.ts +325 -49
  203. package/src/logger.ts +232 -0
  204. package/src/middleware/budget.ts +171 -0
  205. package/src/middleware/cache.ts +299 -0
  206. package/src/middleware/embed-cache.ts +195 -0
  207. package/src/middleware/index.ts +23 -0
  208. package/src/middleware/trace.ts +248 -0
  209. package/src/primitives.ts +589 -62
  210. package/src/retry.ts +902 -0
  211. package/src/schema.ts +8 -17
  212. package/src/telemetry.ts +403 -0
  213. package/src/template.ts +8 -4
  214. package/src/tool-orchestration.ts +1173 -0
  215. package/src/type-guards.ts +31 -0
  216. package/src/types.ts +164 -25
  217. package/src/wrap-for-v3.ts +105 -0
  218. package/test/ai-promise.test.ts +1080 -0
  219. package/test/ai-proxy.test.ts +1 -1
  220. package/test/backward-compat.test.ts +147 -0
  221. package/test/batch-autosubmit-errors.test.ts +610 -0
  222. package/test/batch-blog-posts.test.ts +87 -129
  223. package/test/budget-tracking.test.ts +800 -0
  224. package/test/cache.test.ts +712 -0
  225. package/test/context-isolation.test.ts +687 -0
  226. package/test/core-functions.test.ts +183 -579
  227. package/test/decide.test.ts +154 -322
  228. package/test/define.test.ts +211 -8
  229. package/test/digital-objects-registry.test.ts +760 -0
  230. package/test/embedding-cache-middleware.test.ts +140 -0
  231. package/test/evals/deterministic.eval.test.ts +376 -0
  232. package/test/generate-core.test.ts +140 -229
  233. package/test/implicit-batch.test.ts +22 -65
  234. package/test/json-parse-error-handling.test.ts +463 -0
  235. package/test/retry-policy-integration.test.ts +117 -0
  236. package/test/retry.test.ts +1016 -0
  237. package/test/schema.test.ts +55 -19
  238. package/test/streaming.test.ts +316 -0
  239. package/test/template.test.ts +1164 -0
  240. package/test/tool-orchestration.test.ts +1040 -0
  241. package/test/wrap-for-v3.test.ts +612 -0
  242. package/vitest.config.js +6 -0
  243. package/vitest.config.ts +20 -0
  244. package/dist/rpc/auth.d.ts +0 -69
  245. package/dist/rpc/auth.d.ts.map +0 -1
  246. package/dist/rpc/auth.js +0 -136
  247. package/dist/rpc/auth.js.map +0 -1
  248. package/dist/rpc/client.d.ts +0 -62
  249. package/dist/rpc/client.d.ts.map +0 -1
  250. package/dist/rpc/client.js +0 -103
  251. package/dist/rpc/client.js.map +0 -1
  252. package/dist/rpc/deferred.d.ts +0 -60
  253. package/dist/rpc/deferred.d.ts.map +0 -1
  254. package/dist/rpc/deferred.js +0 -96
  255. package/dist/rpc/deferred.js.map +0 -1
  256. package/dist/rpc/index.d.ts +0 -22
  257. package/dist/rpc/index.d.ts.map +0 -1
  258. package/dist/rpc/index.js +0 -38
  259. package/dist/rpc/index.js.map +0 -1
  260. package/dist/rpc/local.d.ts +0 -42
  261. package/dist/rpc/local.d.ts.map +0 -1
  262. package/dist/rpc/local.js +0 -50
  263. package/dist/rpc/local.js.map +0 -1
  264. package/dist/rpc/server.d.ts +0 -165
  265. package/dist/rpc/server.d.ts.map +0 -1
  266. package/dist/rpc/server.js +0 -405
  267. package/dist/rpc/server.js.map +0 -1
  268. package/dist/rpc/session.d.ts +0 -32
  269. package/dist/rpc/session.d.ts.map +0 -1
  270. package/dist/rpc/session.js +0 -43
  271. package/dist/rpc/session.js.map +0 -1
  272. package/dist/rpc/transport.d.ts +0 -306
  273. package/dist/rpc/transport.d.ts.map +0 -1
  274. package/dist/rpc/transport.js +0 -731
  275. package/dist/rpc/transport.js.map +0 -1
  276. package/src/batch/anthropic.js +0 -256
  277. package/src/batch/bedrock.js +0 -584
  278. package/src/batch/cloudflare.js +0 -287
  279. package/src/batch/google.js +0 -359
  280. package/src/batch/index.js +0 -30
  281. package/src/batch/memory.js +0 -187
  282. package/src/batch/openai.js +0 -402
  283. package/src/eval/index.js +0 -7
  284. package/src/eval/models.js +0 -119
  285. package/src/eval/runner.js +0 -147
  286. package/test/schema.test.js +0 -96
@@ -0,0 +1,763 @@
1
+ /**
2
+ * Agentic Tool Orchestration
3
+ *
4
+ * Provides multi-turn model→tools→model loop orchestration for complex AI workflows.
5
+ *
6
+ * Key components:
7
+ * - AgenticLoop: Orchestrates multi-turn conversations with tool execution
8
+ * - ToolRouter: Routes tool calls to registered handlers
9
+ * - ToolValidator: Validates tool arguments before execution
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { z } from 'zod';
14
+ // ============================================================================
15
+ // ToolValidator
16
+ // ============================================================================
17
+ /**
18
+ * Validates tool arguments before execution
19
+ */
20
+ export class ToolValidator {
21
+ tools = new Map();
22
+ /**
23
+ * Register a tool for validation
24
+ */
25
+ register(tool) {
26
+ this.tools.set(tool.name, tool);
27
+ }
28
+ /**
29
+ * Validate arguments for a tool
30
+ */
31
+ validate(toolName, args) {
32
+ const tool = this.tools.get(toolName);
33
+ if (!tool) {
34
+ return {
35
+ valid: false,
36
+ errors: [`Tool '${toolName}' not registered`],
37
+ };
38
+ }
39
+ try {
40
+ const parsed = tool.parameters.parse(args);
41
+ return {
42
+ valid: true,
43
+ parsedArgs: parsed,
44
+ };
45
+ }
46
+ catch (error) {
47
+ if (error instanceof z.ZodError) {
48
+ return {
49
+ valid: false,
50
+ errors: error.errors.map((e) => `${e.path.join('.')}: ${e.message}`),
51
+ };
52
+ }
53
+ return {
54
+ valid: false,
55
+ errors: [error.message],
56
+ };
57
+ }
58
+ }
59
+ /**
60
+ * Validate multiple tool calls at once
61
+ */
62
+ validateAll(calls) {
63
+ return calls.map((call) => this.validate(call.name, call.arguments));
64
+ }
65
+ }
66
+ // ============================================================================
67
+ // ToolRouter
68
+ // ============================================================================
69
+ /**
70
+ * Routes tool calls to registered handlers
71
+ *
72
+ * @deprecated Phase C Week 2 — `ToolRouter` has zero production callers in
73
+ * primitives.org.ai (audited 2026-05-06; see `bd show aip-ibid`). Only the
74
+ * `ai-primitives` umbrella re-export tests reference it. AI SDK 6's native
75
+ * tool-routing under `generateText({ tools })` and `Agent` / `ToolLoopAgent`
76
+ * cover the same surface. Will be removed in the Phase C semver bump
77
+ * alongside `AgenticLoop` and `createAgenticLoop`.
78
+ */
79
+ export class ToolRouter {
80
+ tools = new Map();
81
+ validator = new ToolValidator();
82
+ /**
83
+ * Register a tool
84
+ */
85
+ register(tool) {
86
+ this.tools.set(tool.name, tool);
87
+ this.validator.register(tool);
88
+ }
89
+ /**
90
+ * Route a single tool call
91
+ */
92
+ async route(call) {
93
+ const tool = this.tools.get(call.name);
94
+ if (!tool) {
95
+ return {
96
+ success: false,
97
+ error: `Tool '${call.name}' not found`,
98
+ toolCall: call,
99
+ };
100
+ }
101
+ const validation = this.validator.validate(call.name, call.arguments);
102
+ if (!validation.valid) {
103
+ return {
104
+ success: false,
105
+ error: `Validation failed: ${validation.errors?.join(', ')}`,
106
+ toolCall: call,
107
+ };
108
+ }
109
+ try {
110
+ const result = await tool.execute(validation.parsedArgs);
111
+ return {
112
+ success: true,
113
+ result,
114
+ toolCall: call,
115
+ };
116
+ }
117
+ catch (error) {
118
+ return {
119
+ success: false,
120
+ error: error.message,
121
+ toolCall: call,
122
+ };
123
+ }
124
+ }
125
+ /**
126
+ * Route multiple tool calls sequentially
127
+ */
128
+ async routeAll(calls) {
129
+ const results = [];
130
+ for (const call of calls) {
131
+ results.push(await this.route(call));
132
+ }
133
+ return results;
134
+ }
135
+ /**
136
+ * Route multiple tool calls in parallel
137
+ */
138
+ async routeAllParallel(calls) {
139
+ return Promise.all(calls.map((call) => this.route(call)));
140
+ }
141
+ /**
142
+ * Format a tool result for model consumption
143
+ */
144
+ formatResult(result) {
145
+ if (result.success) {
146
+ return {
147
+ role: 'tool',
148
+ content: JSON.stringify(result.result),
149
+ ...(result.toolCall?.id !== undefined && { tool_call_id: result.toolCall.id }),
150
+ };
151
+ }
152
+ return {
153
+ role: 'tool',
154
+ content: JSON.stringify({ error: result.error }),
155
+ ...(result.toolCall?.id !== undefined && { tool_call_id: result.toolCall.id }),
156
+ isError: true,
157
+ };
158
+ }
159
+ }
160
+ // ============================================================================
161
+ // AgenticLoop
162
+ // ============================================================================
163
+ /**
164
+ * Orchestrates multi-turn model→tools→model loops
165
+ *
166
+ * @deprecated Phase C Week 2 — `AgenticLoop` has zero production callers in
167
+ * primitives.org.ai (audited 2026-05-06; see `bd show aip-ibid`). Only the
168
+ * `ai-primitives` umbrella re-export tests reference it. The production
169
+ * cascade walker (`services-as-software/v3/invoke/cascade-walker.ts:178`)
170
+ * already uses AI SDK 6's `generateText({ tools, maxSteps: 10 })` directly
171
+ * for agentic steps — no consumer code paths through this class. AI SDK 6's
172
+ * `Agent` / `ToolLoopAgent` (`stopWhen: stepCountIs(N)`) are the going-
173
+ * forward primitives. Will be removed in the Phase C semver bump.
174
+ */
175
+ export class AgenticLoop {
176
+ options;
177
+ router;
178
+ validator;
179
+ constructor(options) {
180
+ this.options = {
181
+ strictMaxSteps: false,
182
+ parallelExecution: false,
183
+ maxParallelCalls: 10,
184
+ retryFailedTools: false,
185
+ maxToolRetries: 3,
186
+ continueOnError: false,
187
+ trackUsage: false,
188
+ ...options,
189
+ };
190
+ this.router = new ToolRouter();
191
+ this.validator = new ToolValidator();
192
+ // Register all tools
193
+ for (const tool of options.tools) {
194
+ this.router.register(tool);
195
+ this.validator.register(tool);
196
+ }
197
+ }
198
+ /**
199
+ * Get tools in AI SDK format
200
+ */
201
+ getToolsForSDK() {
202
+ const tools = {};
203
+ for (const tool of this.options.tools) {
204
+ tools[tool.name] = {
205
+ description: tool.description,
206
+ parameters: tool.parameters,
207
+ execute: tool.execute,
208
+ };
209
+ }
210
+ return tools;
211
+ }
212
+ /**
213
+ * Execute a tool call with timeout and retry support
214
+ */
215
+ async executeToolCall(call, abortSignal) {
216
+ const { toolTimeout, retryFailedTools, maxToolRetries = 3 } = this.options;
217
+ let lastError;
218
+ let retryCount = 0;
219
+ const maxAttempts = retryFailedTools ? maxToolRetries : 1;
220
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
221
+ // Check abort signal
222
+ if (abortSignal?.aborted) {
223
+ throw new Error('Aborted');
224
+ }
225
+ try {
226
+ // Create a promise for the tool execution
227
+ const executePromise = this.router.route(call);
228
+ // Apply timeout if configured
229
+ let result;
230
+ if (toolTimeout) {
231
+ let timeoutId;
232
+ const timeoutPromise = new Promise((_, reject) => {
233
+ timeoutId = setTimeout(() => reject(new Error('Tool execution timeout')), toolTimeout);
234
+ });
235
+ try {
236
+ result = await Promise.race([executePromise, timeoutPromise]);
237
+ }
238
+ finally {
239
+ clearTimeout(timeoutId);
240
+ }
241
+ }
242
+ else {
243
+ result = await executePromise;
244
+ }
245
+ if (result.success) {
246
+ return {
247
+ name: call.name,
248
+ arguments: call.arguments,
249
+ result: result.result,
250
+ retryCount,
251
+ };
252
+ }
253
+ lastError = result.error;
254
+ retryCount = attempt + 1;
255
+ }
256
+ catch (error) {
257
+ lastError = error.message;
258
+ if (lastError === 'Aborted')
259
+ throw error;
260
+ retryCount = attempt + 1;
261
+ }
262
+ }
263
+ return {
264
+ name: call.name,
265
+ arguments: call.arguments,
266
+ ...(lastError !== undefined && { error: lastError }),
267
+ retryCount: retryCount > 0 ? retryCount - 1 : 0,
268
+ };
269
+ }
270
+ /**
271
+ * Execute multiple tool calls
272
+ */
273
+ async executeToolCalls(calls, abortSignal) {
274
+ const { parallelExecution, maxParallelCalls = 10 } = this.options;
275
+ if (!parallelExecution) {
276
+ // Sequential execution
277
+ const results = [];
278
+ for (const call of calls) {
279
+ results.push(await this.executeToolCall(call, abortSignal));
280
+ }
281
+ return results;
282
+ }
283
+ // Parallel execution with concurrency limit
284
+ const results = [];
285
+ const chunks = [];
286
+ for (let i = 0; i < calls.length; i += maxParallelCalls) {
287
+ chunks.push(calls.slice(i, i + maxParallelCalls));
288
+ }
289
+ for (const chunk of chunks) {
290
+ const chunkResults = await Promise.all(chunk.map((call) => this.executeToolCall(call, abortSignal)));
291
+ results.push(...chunkResults);
292
+ }
293
+ return results;
294
+ }
295
+ /**
296
+ * Build messages for the next model call
297
+ */
298
+ buildMessages(prompt, system, conversationMessages, toolResults) {
299
+ const messages = [];
300
+ // Add system message if provided
301
+ if (system) {
302
+ messages.push({ role: 'system', content: system });
303
+ }
304
+ // Add conversation history
305
+ messages.push(...conversationMessages);
306
+ // Add tool results as tool messages
307
+ for (const result of toolResults) {
308
+ if (result.error) {
309
+ messages.push({
310
+ role: 'tool',
311
+ content: JSON.stringify({ error: result.error }),
312
+ isError: true,
313
+ });
314
+ }
315
+ else {
316
+ messages.push({
317
+ role: 'tool',
318
+ content: JSON.stringify(result.result),
319
+ });
320
+ }
321
+ }
322
+ return messages;
323
+ }
324
+ /**
325
+ * Run the agentic loop
326
+ */
327
+ async run(runOptions) {
328
+ const { model, prompt, system, abortSignal } = runOptions;
329
+ const { maxSteps, strictMaxSteps, continueOnError, trackUsage, onStep } = this.options;
330
+ const allToolCalls = [];
331
+ const allToolResults = [];
332
+ const messages = [{ role: 'user', content: prompt }];
333
+ let steps = 0;
334
+ let stopReason = 'stop';
335
+ let finalText = '';
336
+ let totalUsage = trackUsage
337
+ ? { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
338
+ : undefined;
339
+ try {
340
+ while (steps < maxSteps) {
341
+ // Check abort signal
342
+ if (abortSignal?.aborted) {
343
+ stopReason = 'aborted';
344
+ throw new Error('Aborted');
345
+ }
346
+ steps++;
347
+ // Call the model
348
+ const response = await model.generate({
349
+ messages: this.buildMessages(prompt, system, messages.slice(1), []),
350
+ tools: this.getToolsForSDK(),
351
+ });
352
+ // Track usage
353
+ if (trackUsage && response.usage) {
354
+ totalUsage.promptTokens += response.usage.promptTokens;
355
+ totalUsage.completionTokens += response.usage.completionTokens;
356
+ totalUsage.totalTokens += response.usage.totalTokens;
357
+ }
358
+ // If no tool calls, we're done
359
+ if (!response.toolCalls || response.toolCalls.length === 0) {
360
+ finalText = response.text || '';
361
+ messages.push({ role: 'assistant', content: finalText });
362
+ stopReason = 'stop';
363
+ if (onStep) {
364
+ onStep({
365
+ stepNumber: steps,
366
+ toolCalls: [],
367
+ response,
368
+ messages: [...messages],
369
+ });
370
+ }
371
+ break;
372
+ }
373
+ // Execute tool calls
374
+ const toolResults = await this.executeToolCalls(response.toolCalls, abortSignal);
375
+ // Check for errors
376
+ const hasErrors = toolResults.some((r) => r.error);
377
+ if (hasErrors && !continueOnError) {
378
+ // Still record the results but note the errors
379
+ }
380
+ // Record tool calls and results
381
+ for (const result of toolResults) {
382
+ allToolCalls.push(result);
383
+ allToolResults.push({
384
+ toolName: result.name,
385
+ result: result.result,
386
+ });
387
+ // Add tool result to messages
388
+ if (result.error) {
389
+ messages.push({
390
+ role: 'tool',
391
+ content: JSON.stringify({ error: result.error }),
392
+ isError: true,
393
+ });
394
+ }
395
+ else {
396
+ messages.push({
397
+ role: 'tool',
398
+ content: JSON.stringify(result.result),
399
+ });
400
+ }
401
+ }
402
+ // Call onStep callback
403
+ if (onStep) {
404
+ onStep({
405
+ stepNumber: steps,
406
+ toolCalls: response.toolCalls.map((tc, i) => ({
407
+ ...tc,
408
+ ...(toolResults[i]?.result !== undefined && { result: toolResults[i]?.result }),
409
+ ...(toolResults[i]?.error !== undefined && { error: toolResults[i]?.error }),
410
+ })),
411
+ response,
412
+ messages: [...messages],
413
+ });
414
+ }
415
+ // Add assistant message with tool calls
416
+ messages.push({
417
+ role: 'assistant',
418
+ content: '',
419
+ tool_calls: response.toolCalls,
420
+ });
421
+ }
422
+ // Check if we hit max steps
423
+ if (steps >= maxSteps && stopReason === 'stop') {
424
+ stopReason = 'max_steps';
425
+ if (strictMaxSteps) {
426
+ throw new Error('Max steps exceeded');
427
+ }
428
+ }
429
+ }
430
+ catch (error) {
431
+ if (error.message === 'Aborted') {
432
+ stopReason = 'aborted';
433
+ throw error;
434
+ }
435
+ if (error.message === 'Max steps exceeded') {
436
+ throw error;
437
+ }
438
+ stopReason = 'error';
439
+ throw error;
440
+ }
441
+ return {
442
+ text: finalText,
443
+ steps,
444
+ toolCalls: allToolCalls,
445
+ toolResults: allToolResults,
446
+ stopReason,
447
+ ...(totalUsage !== undefined && { usage: totalUsage }),
448
+ messages,
449
+ };
450
+ }
451
+ /**
452
+ * Run the agentic loop with streaming support
453
+ *
454
+ * Returns an async generator that yields step events as they occur.
455
+ */
456
+ async *stream(runOptions) {
457
+ const { model, prompt, system, abortSignal } = runOptions;
458
+ const { maxSteps, strictMaxSteps, continueOnError, trackUsage } = this.options;
459
+ const allToolCalls = [];
460
+ const allToolResults = [];
461
+ const messages = [{ role: 'user', content: prompt }];
462
+ let steps = 0;
463
+ let stopReason = 'stop';
464
+ let finalText = '';
465
+ let totalUsage = trackUsage
466
+ ? { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
467
+ : undefined;
468
+ yield { type: 'start', prompt, timestamp: Date.now() };
469
+ try {
470
+ while (steps < maxSteps) {
471
+ if (abortSignal?.aborted) {
472
+ yield { type: 'aborted', steps, timestamp: Date.now() };
473
+ throw new Error('Aborted');
474
+ }
475
+ steps++;
476
+ yield { type: 'step_start', stepNumber: steps, timestamp: Date.now() };
477
+ const response = await model.generate({
478
+ messages: this.buildMessages(prompt, system, messages.slice(1), []),
479
+ tools: this.getToolsForSDK(),
480
+ });
481
+ if (trackUsage && response.usage) {
482
+ totalUsage.promptTokens += response.usage.promptTokens;
483
+ totalUsage.completionTokens += response.usage.completionTokens;
484
+ totalUsage.totalTokens += response.usage.totalTokens;
485
+ }
486
+ if (!response.toolCalls || response.toolCalls.length === 0) {
487
+ finalText = response.text || '';
488
+ messages.push({ role: 'assistant', content: finalText });
489
+ yield { type: 'text', text: finalText, stepNumber: steps, timestamp: Date.now() };
490
+ yield { type: 'step_end', stepNumber: steps, hasToolCalls: false, timestamp: Date.now() };
491
+ break;
492
+ }
493
+ yield {
494
+ type: 'tool_calls',
495
+ toolCalls: response.toolCalls,
496
+ stepNumber: steps,
497
+ timestamp: Date.now(),
498
+ };
499
+ const toolResults = await this.executeToolCalls(response.toolCalls, abortSignal);
500
+ for (const result of toolResults) {
501
+ allToolCalls.push(result);
502
+ allToolResults.push({ toolName: result.name, result: result.result });
503
+ yield {
504
+ type: 'tool_result',
505
+ toolName: result.name,
506
+ ...(result.result !== undefined && { result: result.result }),
507
+ ...(result.error !== undefined && { error: result.error }),
508
+ stepNumber: steps,
509
+ timestamp: Date.now(),
510
+ };
511
+ if (result.error) {
512
+ messages.push({
513
+ role: 'tool',
514
+ content: JSON.stringify({ error: result.error }),
515
+ isError: true,
516
+ });
517
+ }
518
+ else {
519
+ messages.push({
520
+ role: 'tool',
521
+ content: JSON.stringify(result.result),
522
+ });
523
+ }
524
+ }
525
+ yield { type: 'step_end', stepNumber: steps, hasToolCalls: true, timestamp: Date.now() };
526
+ messages.push({
527
+ role: 'assistant',
528
+ content: '',
529
+ tool_calls: response.toolCalls,
530
+ });
531
+ }
532
+ if (steps >= maxSteps && stopReason === 'stop') {
533
+ stopReason = 'max_steps';
534
+ yield { type: 'max_steps', steps, timestamp: Date.now() };
535
+ if (strictMaxSteps)
536
+ throw new Error('Max steps exceeded');
537
+ }
538
+ }
539
+ catch (error) {
540
+ if (error.message === 'Aborted') {
541
+ stopReason = 'aborted';
542
+ throw error;
543
+ }
544
+ if (error.message === 'Max steps exceeded') {
545
+ throw error;
546
+ }
547
+ yield { type: 'error', error: error.message, timestamp: Date.now() };
548
+ stopReason = 'error';
549
+ throw error;
550
+ }
551
+ yield { type: 'end', steps, stopReason, timestamp: Date.now() };
552
+ return {
553
+ text: finalText,
554
+ steps,
555
+ toolCalls: allToolCalls,
556
+ toolResults: allToolResults,
557
+ stopReason,
558
+ ...(totalUsage !== undefined && { usage: totalUsage }),
559
+ messages,
560
+ };
561
+ }
562
+ }
563
+ // ============================================================================
564
+ // Tool Composition Patterns
565
+ // ============================================================================
566
+ /**
567
+ * Create a tool from a simple function
568
+ */
569
+ export function createTool(config) {
570
+ return {
571
+ name: config.name,
572
+ description: config.description,
573
+ parameters: z.object(config.parameters),
574
+ execute: config.execute,
575
+ };
576
+ }
577
+ /**
578
+ * Compose multiple tools into a single toolset
579
+ */
580
+ export function createToolset(...tools) {
581
+ return tools;
582
+ }
583
+ /**
584
+ * Create a tool that wraps another tool with middleware
585
+ */
586
+ export function wrapTool(tool, middleware) {
587
+ return {
588
+ ...tool,
589
+ execute: async (params) => {
590
+ try {
591
+ const modifiedParams = middleware.before ? await middleware.before(params) : params;
592
+ const result = await tool.execute(modifiedParams);
593
+ return middleware.after ? await middleware.after(result) : result;
594
+ }
595
+ catch (error) {
596
+ if (middleware.onError) {
597
+ return middleware.onError(error);
598
+ }
599
+ throw error;
600
+ }
601
+ },
602
+ };
603
+ }
604
+ /**
605
+ * Create a tool with caching support
606
+ *
607
+ * Features:
608
+ * - TTL-based expiration
609
+ * - Optional periodic cleanup of expired entries (prevents memory leaks)
610
+ * - Optional max size with LRU eviction
611
+ * - Manual cache control (clear, destroy)
612
+ */
613
+ export function cachedTool(tool, options = {}) {
614
+ const { ttl = 60000, keyFn = JSON.stringify, cleanupIntervalMs = 0, maxSize = 0 } = options;
615
+ const cache = new Map();
616
+ let cleanupTimer = null;
617
+ let destroyed = false;
618
+ // Cleanup function to remove expired entries
619
+ const cleanupExpired = () => {
620
+ const now = Date.now();
621
+ for (const [key, entry] of cache) {
622
+ if (entry.expires <= now) {
623
+ cache.delete(key);
624
+ }
625
+ }
626
+ };
627
+ // Start periodic cleanup if configured
628
+ if (cleanupIntervalMs > 0) {
629
+ cleanupTimer = setInterval(cleanupExpired, cleanupIntervalMs);
630
+ // Unref the timer so it doesn't keep the process alive (Node.js)
631
+ if (typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {
632
+ cleanupTimer.unref();
633
+ }
634
+ }
635
+ // Evict oldest entries based on lastAccessed (LRU)
636
+ const evictOldest = () => {
637
+ if (maxSize <= 0 || cache.size < maxSize)
638
+ return;
639
+ // Find the entry with oldest lastAccessed
640
+ let oldestKey = null;
641
+ let oldestTime = Infinity;
642
+ for (const [key, entry] of cache) {
643
+ if (entry.lastAccessed < oldestTime) {
644
+ oldestTime = entry.lastAccessed;
645
+ oldestKey = key;
646
+ }
647
+ }
648
+ if (oldestKey) {
649
+ cache.delete(oldestKey);
650
+ }
651
+ };
652
+ const cachedToolInstance = {
653
+ ...tool,
654
+ execute: async (params) => {
655
+ if (destroyed) {
656
+ // If destroyed, just execute without caching
657
+ return tool.execute(params);
658
+ }
659
+ const key = keyFn(params);
660
+ const cached = cache.get(key);
661
+ const now = Date.now();
662
+ if (cached && cached.expires > now) {
663
+ // Cache hit - update last accessed time for LRU
664
+ cached.lastAccessed = now;
665
+ return cached.value;
666
+ }
667
+ // Cache miss or expired - remove expired entry if present
668
+ if (cached) {
669
+ cache.delete(key);
670
+ }
671
+ const result = await tool.execute(params);
672
+ // Evict oldest if we're at max size
673
+ if (maxSize > 0 && cache.size >= maxSize) {
674
+ evictOldest();
675
+ }
676
+ cache.set(key, {
677
+ value: result,
678
+ expires: now + ttl,
679
+ lastAccessed: now,
680
+ });
681
+ return result;
682
+ },
683
+ cacheSize() {
684
+ return cache.size;
685
+ },
686
+ clearCache() {
687
+ cache.clear();
688
+ },
689
+ destroy() {
690
+ destroyed = true;
691
+ if (cleanupTimer !== null) {
692
+ clearInterval(cleanupTimer);
693
+ cleanupTimer = null;
694
+ }
695
+ cache.clear();
696
+ },
697
+ };
698
+ return cachedToolInstance;
699
+ }
700
+ /**
701
+ * Create a tool with rate limiting
702
+ */
703
+ export function rateLimitedTool(tool, options) {
704
+ const calls = [];
705
+ const { maxCalls, windowMs } = options;
706
+ return {
707
+ ...tool,
708
+ execute: async (params) => {
709
+ const now = Date.now();
710
+ // Remove expired calls
711
+ while (calls.length > 0 && calls[0] < now - windowMs) {
712
+ calls.shift();
713
+ }
714
+ if (calls.length >= maxCalls) {
715
+ throw new Error(`Rate limit exceeded: max ${maxCalls} calls per ${windowMs}ms`);
716
+ }
717
+ calls.push(now);
718
+ return tool.execute(params);
719
+ },
720
+ };
721
+ }
722
+ /**
723
+ * Create a tool that times out after a specified duration
724
+ */
725
+ export function timeoutTool(tool, timeoutMs) {
726
+ return {
727
+ ...tool,
728
+ execute: async (params) => {
729
+ let timeoutId;
730
+ const timeoutPromise = new Promise((_, reject) => {
731
+ timeoutId = setTimeout(() => reject(new Error(`Tool '${tool.name}' timed out after ${timeoutMs}ms`)), timeoutMs);
732
+ });
733
+ try {
734
+ return await Promise.race([tool.execute(params), timeoutPromise]);
735
+ }
736
+ finally {
737
+ if (timeoutId !== undefined) {
738
+ clearTimeout(timeoutId);
739
+ }
740
+ }
741
+ },
742
+ };
743
+ }
744
+ /**
745
+ * Create an agentic loop with sensible defaults
746
+ *
747
+ * @deprecated Phase C Week 2 — `createAgenticLoop` has zero production
748
+ * callers in primitives.org.ai (only `ai-primitives` umbrella re-export
749
+ * tests). Use AI SDK 6's `Agent` / `ToolLoopAgent` with
750
+ * `stopWhen: stepCountIs(N)` instead. Will be removed alongside
751
+ * `AgenticLoop` in the Phase C semver bump. See `bd show aip-ibid`.
752
+ */
753
+ export function createAgenticLoop(options) {
754
+ return new AgenticLoop({
755
+ maxSteps: 10,
756
+ parallelExecution: true,
757
+ maxParallelCalls: 5,
758
+ continueOnError: true,
759
+ trackUsage: true,
760
+ ...options,
761
+ });
762
+ }
763
+ //# sourceMappingURL=tool-orchestration.js.map