ai-functions 2.1.3 → 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 (277) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +55 -1
  3. package/README.md +38 -0
  4. package/dist/ai-promise.d.ts +3 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +135 -64
  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 +51 -858
  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.map +1 -1
  56. package/dist/budget.js +27 -14
  57. package/dist/budget.js.map +1 -1
  58. package/dist/cache.d.ts +23 -0
  59. package/dist/cache.d.ts.map +1 -1
  60. package/dist/cache.js +36 -15
  61. package/dist/cache.js.map +1 -1
  62. package/dist/context.d.ts +26 -8
  63. package/dist/context.d.ts.map +1 -1
  64. package/dist/context.js +64 -62
  65. package/dist/context.js.map +1 -1
  66. package/dist/digital-objects-registry.d.ts +229 -0
  67. package/dist/digital-objects-registry.d.ts.map +1 -0
  68. package/dist/digital-objects-registry.js +617 -0
  69. package/dist/digital-objects-registry.js.map +1 -0
  70. package/dist/embeddings.d.ts +2 -2
  71. package/dist/embeddings.d.ts.map +1 -1
  72. package/dist/errors.d.ts +22 -0
  73. package/dist/errors.d.ts.map +1 -0
  74. package/dist/errors.js +35 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/eval/runner.d.ts +8 -0
  77. package/dist/eval/runner.d.ts.map +1 -1
  78. package/dist/eval/runner.js +41 -35
  79. package/dist/eval/runner.js.map +1 -1
  80. package/dist/eval-log/in-memory.d.ts +34 -0
  81. package/dist/eval-log/in-memory.d.ts.map +1 -0
  82. package/dist/eval-log/in-memory.js +84 -0
  83. package/dist/eval-log/in-memory.js.map +1 -0
  84. package/dist/eval-log/index.d.ts +29 -0
  85. package/dist/eval-log/index.d.ts.map +1 -0
  86. package/dist/eval-log/index.js +39 -0
  87. package/dist/eval-log/index.js.map +1 -0
  88. package/dist/eval-log/types.d.ts +101 -0
  89. package/dist/eval-log/types.d.ts.map +1 -0
  90. package/dist/eval-log/types.js +16 -0
  91. package/dist/eval-log/types.js.map +1 -0
  92. package/dist/function-registry.d.ts +116 -0
  93. package/dist/function-registry.d.ts.map +1 -0
  94. package/dist/function-registry.js +546 -0
  95. package/dist/function-registry.js.map +1 -0
  96. package/dist/generate.d.ts +9 -3
  97. package/dist/generate.d.ts.map +1 -1
  98. package/dist/generate.js +18 -18
  99. package/dist/generate.js.map +1 -1
  100. package/dist/index.d.ts +18 -11
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +35 -18
  103. package/dist/index.js.map +1 -1
  104. package/dist/logger.d.ts +118 -0
  105. package/dist/logger.d.ts.map +1 -0
  106. package/dist/logger.js +187 -0
  107. package/dist/logger.js.map +1 -0
  108. package/dist/middleware/budget.d.ts +84 -0
  109. package/dist/middleware/budget.d.ts.map +1 -0
  110. package/dist/middleware/budget.js +110 -0
  111. package/dist/middleware/budget.js.map +1 -0
  112. package/dist/middleware/cache.d.ts +103 -0
  113. package/dist/middleware/cache.d.ts.map +1 -0
  114. package/dist/middleware/cache.js +228 -0
  115. package/dist/middleware/cache.js.map +1 -0
  116. package/dist/middleware/embed-cache.d.ts +99 -0
  117. package/dist/middleware/embed-cache.d.ts.map +1 -0
  118. package/dist/middleware/embed-cache.js +128 -0
  119. package/dist/middleware/embed-cache.js.map +1 -0
  120. package/dist/middleware/index.d.ts +11 -0
  121. package/dist/middleware/index.d.ts.map +1 -0
  122. package/dist/middleware/index.js +11 -0
  123. package/dist/middleware/index.js.map +1 -0
  124. package/dist/middleware/trace.d.ts +103 -0
  125. package/dist/middleware/trace.d.ts.map +1 -0
  126. package/dist/middleware/trace.js +176 -0
  127. package/dist/middleware/trace.js.map +1 -0
  128. package/dist/primitives.d.ts +120 -1
  129. package/dist/primitives.d.ts.map +1 -1
  130. package/dist/primitives.js +398 -26
  131. package/dist/primitives.js.map +1 -1
  132. package/dist/retry.d.ts +66 -1
  133. package/dist/retry.d.ts.map +1 -1
  134. package/dist/retry.js +115 -8
  135. package/dist/retry.js.map +1 -1
  136. package/dist/schema.js +2 -2
  137. package/dist/schema.js.map +1 -1
  138. package/dist/telemetry.d.ts +128 -0
  139. package/dist/telemetry.d.ts.map +1 -0
  140. package/dist/telemetry.js +285 -0
  141. package/dist/telemetry.js.map +1 -0
  142. package/dist/template.d.ts.map +1 -1
  143. package/dist/template.js +6 -1
  144. package/dist/template.js.map +1 -1
  145. package/dist/tool-orchestration.d.ts +66 -4
  146. package/dist/tool-orchestration.d.ts.map +1 -1
  147. package/dist/tool-orchestration.js +123 -23
  148. package/dist/tool-orchestration.js.map +1 -1
  149. package/dist/type-guards.d.ts +28 -0
  150. package/dist/type-guards.d.ts.map +1 -0
  151. package/dist/type-guards.js +29 -0
  152. package/dist/type-guards.js.map +1 -0
  153. package/dist/types.d.ts +135 -17
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +36 -1
  156. package/dist/types.js.map +1 -1
  157. package/dist/wrap-for-v3.d.ts +80 -0
  158. package/dist/wrap-for-v3.d.ts.map +1 -0
  159. package/dist/wrap-for-v3.js +89 -0
  160. package/dist/wrap-for-v3.js.map +1 -0
  161. package/examples/00-quickstart.ts +232 -0
  162. package/examples/01-rag-chatbot.ts +212 -0
  163. package/examples/02-multi-agent-research.ts +290 -0
  164. package/examples/03-email-classification.ts +379 -0
  165. package/examples/04-content-moderation.ts +400 -0
  166. package/examples/05-document-extraction.ts +455 -0
  167. package/examples/06-streaming-chat-nextjs.ts +437 -0
  168. package/examples/07-cloudflare-worker.ts +483 -0
  169. package/examples/08-batch-processing.ts +491 -0
  170. package/examples/09-budget-constrained.ts +527 -0
  171. package/examples/10-tool-orchestration.ts +565 -0
  172. package/examples/11-retry-resilience.ts +403 -0
  173. package/examples/12-caching-strategies.ts +422 -0
  174. package/examples/README.md +145 -0
  175. package/package.json +28 -25
  176. package/src/ai-promise.ts +226 -140
  177. package/src/ai-schemas.ts +122 -0
  178. package/src/ai.ts +69 -1176
  179. package/src/batch/anthropic.ts +96 -161
  180. package/src/batch/bedrock.ts +203 -454
  181. package/src/batch/cloudflare.ts +99 -282
  182. package/src/batch/google.ts +91 -297
  183. package/src/batch/index.ts +4 -1
  184. package/src/batch/memory.ts +15 -10
  185. package/src/batch/openai.ts +65 -193
  186. package/src/batch/provider.ts +336 -0
  187. package/src/batch-map.ts +29 -24
  188. package/src/batch-queue.ts +200 -11
  189. package/src/budget.ts +31 -18
  190. package/src/cache.ts +45 -17
  191. package/src/context.ts +106 -77
  192. package/src/digital-objects-registry.ts +750 -0
  193. package/src/errors.ts +37 -0
  194. package/src/eval/runner.ts +60 -36
  195. package/src/eval-log/in-memory.ts +90 -0
  196. package/src/eval-log/index.ts +46 -0
  197. package/src/eval-log/types.ts +110 -0
  198. package/src/function-registry.ts +671 -0
  199. package/src/generate.ts +33 -28
  200. package/src/index.ts +119 -21
  201. package/src/logger.ts +232 -0
  202. package/src/middleware/budget.ts +171 -0
  203. package/src/middleware/cache.ts +299 -0
  204. package/src/middleware/embed-cache.ts +195 -0
  205. package/src/middleware/index.ts +23 -0
  206. package/src/middleware/trace.ts +248 -0
  207. package/src/primitives.ts +589 -62
  208. package/src/retry.ts +144 -18
  209. package/src/schema.ts +8 -8
  210. package/src/telemetry.ts +403 -0
  211. package/src/template.ts +8 -4
  212. package/src/tool-orchestration.ts +213 -48
  213. package/src/type-guards.ts +31 -0
  214. package/src/types.ts +164 -25
  215. package/src/wrap-for-v3.ts +105 -0
  216. package/test/ai-promise.test.ts +1080 -0
  217. package/test/ai-proxy.test.ts +1 -1
  218. package/test/batch-autosubmit-errors.test.ts +49 -37
  219. package/test/batch-blog-posts.test.ts +87 -129
  220. package/test/core-functions.test.ts +183 -579
  221. package/test/decide.test.ts +154 -322
  222. package/test/define.test.ts +211 -8
  223. package/test/digital-objects-registry.test.ts +760 -0
  224. package/test/embedding-cache-middleware.test.ts +140 -0
  225. package/test/generate-core.test.ts +140 -229
  226. package/test/implicit-batch.test.ts +22 -65
  227. package/test/retry-policy-integration.test.ts +117 -0
  228. package/test/schema.test.ts +55 -19
  229. package/test/template.test.ts +1164 -0
  230. package/test/tool-orchestration.test.ts +270 -0
  231. package/test/wrap-for-v3.test.ts +612 -0
  232. package/vitest.config.js +6 -0
  233. package/vitest.config.ts +20 -0
  234. package/LICENSE +0 -21
  235. package/dist/rpc/auth.d.ts +0 -69
  236. package/dist/rpc/auth.d.ts.map +0 -1
  237. package/dist/rpc/auth.js +0 -136
  238. package/dist/rpc/auth.js.map +0 -1
  239. package/dist/rpc/client.d.ts +0 -62
  240. package/dist/rpc/client.d.ts.map +0 -1
  241. package/dist/rpc/client.js +0 -103
  242. package/dist/rpc/client.js.map +0 -1
  243. package/dist/rpc/deferred.d.ts +0 -60
  244. package/dist/rpc/deferred.d.ts.map +0 -1
  245. package/dist/rpc/deferred.js +0 -96
  246. package/dist/rpc/deferred.js.map +0 -1
  247. package/dist/rpc/index.d.ts +0 -22
  248. package/dist/rpc/index.d.ts.map +0 -1
  249. package/dist/rpc/index.js +0 -38
  250. package/dist/rpc/index.js.map +0 -1
  251. package/dist/rpc/local.d.ts +0 -42
  252. package/dist/rpc/local.d.ts.map +0 -1
  253. package/dist/rpc/local.js +0 -50
  254. package/dist/rpc/local.js.map +0 -1
  255. package/dist/rpc/server.d.ts +0 -165
  256. package/dist/rpc/server.d.ts.map +0 -1
  257. package/dist/rpc/server.js +0 -405
  258. package/dist/rpc/server.js.map +0 -1
  259. package/dist/rpc/session.d.ts +0 -32
  260. package/dist/rpc/session.d.ts.map +0 -1
  261. package/dist/rpc/session.js +0 -43
  262. package/dist/rpc/session.js.map +0 -1
  263. package/dist/rpc/transport.d.ts +0 -306
  264. package/dist/rpc/transport.d.ts.map +0 -1
  265. package/dist/rpc/transport.js +0 -731
  266. package/dist/rpc/transport.js.map +0 -1
  267. package/src/batch/anthropic.js +0 -256
  268. package/src/batch/bedrock.js +0 -584
  269. package/src/batch/cloudflare.js +0 -287
  270. package/src/batch/google.js +0 -359
  271. package/src/batch/index.js +0 -30
  272. package/src/batch/memory.js +0 -187
  273. package/src/batch/openai.js +0 -402
  274. package/src/eval/index.js +0 -7
  275. package/src/eval/models.js +0 -119
  276. package/src/eval/runner.js +0 -147
  277. package/test/schema.test.js +0 -96
@@ -156,13 +156,26 @@ export interface LoopOptions {
156
156
  onStep?: (step: StepInfo) => void
157
157
  }
158
158
 
159
+ /**
160
+ * Model generation options passed to the model
161
+ */
162
+ export interface ModelGenerationOptions {
163
+ /** Messages for the conversation */
164
+ messages: Message[]
165
+ /** Tools available for use */
166
+ tools: Record<
167
+ string,
168
+ { description: string; parameters: unknown; execute: (args: unknown) => Promise<unknown> }
169
+ >
170
+ }
171
+
159
172
  /**
160
173
  * Options for running the loop
161
174
  */
162
175
  export interface RunOptions {
163
176
  /** Model to use for generation */
164
177
  model: {
165
- generate: (options: any) => Promise<ModelResponse>
178
+ generate: (options: ModelGenerationOptions) => Promise<ModelResponse>
166
179
  }
167
180
  /** Initial prompt */
168
181
  prompt: string
@@ -263,7 +276,7 @@ export class ToolValidator {
263
276
  if (error instanceof z.ZodError) {
264
277
  return {
265
278
  valid: false,
266
- errors: error.errors.map(e => `${e.path.join('.')}: ${e.message}`),
279
+ errors: error.errors.map((e) => `${e.path.join('.')}: ${e.message}`),
267
280
  }
268
281
  }
269
282
  return {
@@ -277,7 +290,7 @@ export class ToolValidator {
277
290
  * Validate multiple tool calls at once
278
291
  */
279
292
  validateAll(calls: ToolCall[]): ValidationResult[] {
280
- return calls.map(call => this.validate(call.name, call.arguments))
293
+ return calls.map((call) => this.validate(call.name, call.arguments))
281
294
  }
282
295
  }
283
296
 
@@ -287,6 +300,13 @@ export class ToolValidator {
287
300
 
288
301
  /**
289
302
  * Routes tool calls to registered handlers
303
+ *
304
+ * @deprecated Phase C Week 2 — `ToolRouter` has zero production callers in
305
+ * primitives.org.ai (audited 2026-05-06; see `bd show aip-ibid`). Only the
306
+ * `ai-primitives` umbrella re-export tests reference it. AI SDK 6's native
307
+ * tool-routing under `generateText({ tools })` and `Agent` / `ToolLoopAgent`
308
+ * cover the same surface. Will be removed in the Phase C semver bump
309
+ * alongside `AgenticLoop` and `createAgenticLoop`.
290
310
  */
291
311
  export class ToolRouter {
292
312
  private tools = new Map<string, Tool>()
@@ -353,7 +373,7 @@ export class ToolRouter {
353
373
  * Route multiple tool calls in parallel
354
374
  */
355
375
  async routeAllParallel(calls: ToolCall[]): Promise<ToolResult[]> {
356
- return Promise.all(calls.map(call => this.route(call)))
376
+ return Promise.all(calls.map((call) => this.route(call)))
357
377
  }
358
378
 
359
379
  /**
@@ -364,13 +384,13 @@ export class ToolRouter {
364
384
  return {
365
385
  role: 'tool',
366
386
  content: JSON.stringify(result.result),
367
- tool_call_id: result.toolCall?.id,
387
+ ...(result.toolCall?.id !== undefined && { tool_call_id: result.toolCall.id }),
368
388
  }
369
389
  }
370
390
  return {
371
391
  role: 'tool',
372
392
  content: JSON.stringify({ error: result.error }),
373
- tool_call_id: result.toolCall?.id,
393
+ ...(result.toolCall?.id !== undefined && { tool_call_id: result.toolCall.id }),
374
394
  isError: true,
375
395
  }
376
396
  }
@@ -382,6 +402,15 @@ export class ToolRouter {
382
402
 
383
403
  /**
384
404
  * Orchestrates multi-turn model→tools→model loops
405
+ *
406
+ * @deprecated Phase C Week 2 — `AgenticLoop` has zero production callers in
407
+ * primitives.org.ai (audited 2026-05-06; see `bd show aip-ibid`). Only the
408
+ * `ai-primitives` umbrella re-export tests reference it. The production
409
+ * cascade walker (`services-as-software/v3/invoke/cascade-walker.ts:178`)
410
+ * already uses AI SDK 6's `generateText({ tools, maxSteps: 10 })` directly
411
+ * for agentic steps — no consumer code paths through this class. AI SDK 6's
412
+ * `Agent` / `ToolLoopAgent` (`stopWhen: stepCountIs(N)`) are the going-
413
+ * forward primitives. Will be removed in the Phase C semver bump.
385
414
  */
386
415
  export class AgenticLoop {
387
416
  private options: LoopOptions
@@ -412,8 +441,14 @@ export class AgenticLoop {
412
441
  /**
413
442
  * Get tools in AI SDK format
414
443
  */
415
- getToolsForSDK(): Record<string, { description: string; parameters: unknown; execute: (args: unknown) => Promise<unknown> }> {
416
- const tools: Record<string, any> = {}
444
+ getToolsForSDK(): Record<
445
+ string,
446
+ { description: string; parameters: unknown; execute: (args: unknown) => Promise<unknown> }
447
+ > {
448
+ const tools: Record<
449
+ string,
450
+ { description: string; parameters: unknown; execute: (args: unknown) => Promise<unknown> }
451
+ > = {}
417
452
  for (const tool of this.options.tools) {
418
453
  tools[tool.name] = {
419
454
  description: tool.description,
@@ -484,7 +519,7 @@ export class AgenticLoop {
484
519
  return {
485
520
  name: call.name,
486
521
  arguments: call.arguments,
487
- error: lastError,
522
+ ...(lastError !== undefined && { error: lastError }),
488
523
  retryCount: retryCount > 0 ? retryCount - 1 : 0,
489
524
  }
490
525
  }
@@ -517,7 +552,7 @@ export class AgenticLoop {
517
552
 
518
553
  for (const chunk of chunks) {
519
554
  const chunkResults = await Promise.all(
520
- chunk.map(call => this.executeToolCall(call, abortSignal))
555
+ chunk.map((call) => this.executeToolCall(call, abortSignal))
521
556
  )
522
557
  results.push(...chunkResults)
523
558
  }
@@ -576,7 +611,9 @@ export class AgenticLoop {
576
611
  let steps = 0
577
612
  let stopReason: LoopResult['stopReason'] = 'stop'
578
613
  let finalText = ''
579
- let totalUsage = trackUsage ? { promptTokens: 0, completionTokens: 0, totalTokens: 0 } : undefined
614
+ let totalUsage = trackUsage
615
+ ? { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
616
+ : undefined
580
617
 
581
618
  try {
582
619
  while (steps < maxSteps) {
@@ -622,7 +659,7 @@ export class AgenticLoop {
622
659
  const toolResults = await this.executeToolCalls(response.toolCalls, abortSignal)
623
660
 
624
661
  // Check for errors
625
- const hasErrors = toolResults.some(r => r.error)
662
+ const hasErrors = toolResults.some((r) => r.error)
626
663
  if (hasErrors && !continueOnError) {
627
664
  // Still record the results but note the errors
628
665
  }
@@ -656,8 +693,8 @@ export class AgenticLoop {
656
693
  stepNumber: steps,
657
694
  toolCalls: response.toolCalls.map((tc, i) => ({
658
695
  ...tc,
659
- result: toolResults[i]?.result,
660
- error: toolResults[i]?.error,
696
+ ...(toolResults[i]?.result !== undefined && { result: toolResults[i]?.result }),
697
+ ...(toolResults[i]?.error !== undefined && { error: toolResults[i]?.error }),
661
698
  })),
662
699
  response,
663
700
  messages: [...messages],
@@ -697,7 +734,7 @@ export class AgenticLoop {
697
734
  toolCalls: allToolCalls,
698
735
  toolResults: allToolResults,
699
736
  stopReason,
700
- usage: totalUsage,
737
+ ...(totalUsage !== undefined && { usage: totalUsage }),
701
738
  messages,
702
739
  }
703
740
  }
@@ -717,7 +754,9 @@ export class AgenticLoop {
717
754
  let steps = 0
718
755
  let stopReason: LoopResult['stopReason'] = 'stop'
719
756
  let finalText = ''
720
- let totalUsage = trackUsage ? { promptTokens: 0, completionTokens: 0, totalTokens: 0 } : undefined
757
+ let totalUsage = trackUsage
758
+ ? { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
759
+ : undefined
721
760
 
722
761
  yield { type: 'start', prompt, timestamp: Date.now() }
723
762
 
@@ -766,8 +805,8 @@ export class AgenticLoop {
766
805
  yield {
767
806
  type: 'tool_result',
768
807
  toolName: result.name,
769
- result: result.result,
770
- error: result.error,
808
+ ...(result.result !== undefined && { result: result.result }),
809
+ ...(result.error !== undefined && { error: result.error }),
771
810
  stepNumber: steps,
772
811
  timestamp: Date.now(),
773
812
  }
@@ -821,7 +860,7 @@ export class AgenticLoop {
821
860
  toolCalls: allToolCalls,
822
861
  toolResults: allToolResults,
823
862
  stopReason,
824
- usage: totalUsage,
863
+ ...(totalUsage !== undefined && { usage: totalUsage }),
825
864
  messages,
826
865
  }
827
866
  }
@@ -840,7 +879,14 @@ export type LoopStreamEvent =
840
879
  | { type: 'step_end'; stepNumber: number; hasToolCalls: boolean; timestamp: number }
841
880
  | { type: 'text'; text: string; stepNumber: number; timestamp: number }
842
881
  | { type: 'tool_calls'; toolCalls: ToolCall[]; stepNumber: number; timestamp: number }
843
- | { type: 'tool_result'; toolName: string; result?: unknown; error?: string; stepNumber: number; timestamp: number }
882
+ | {
883
+ type: 'tool_result'
884
+ toolName: string
885
+ result?: unknown
886
+ error?: string
887
+ stepNumber: number
888
+ timestamp: number
889
+ }
844
890
  | { type: 'max_steps'; steps: number; timestamp: number }
845
891
  | { type: 'aborted'; steps: number; timestamp: number }
846
892
  | { type: 'error'; error: string; timestamp: number }
@@ -853,14 +899,12 @@ export type LoopStreamEvent =
853
899
  /**
854
900
  * Create a tool from a simple function
855
901
  */
856
- export function createTool<TParams extends z.ZodRawShape, TResult>(
857
- config: {
858
- name: string
859
- description: string
860
- parameters: TParams
861
- execute: (params: z.infer<z.ZodObject<TParams>>) => Promise<TResult>
862
- }
863
- ): Tool<z.ZodObject<TParams>, TResult> {
902
+ export function createTool<TParams extends z.ZodRawShape, TResult>(config: {
903
+ name: string
904
+ description: string
905
+ parameters: TParams
906
+ execute: (params: z.infer<z.ZodObject<TParams>>) => Promise<TResult>
907
+ }): Tool<z.ZodObject<TParams>, TResult> {
864
908
  return {
865
909
  name: config.name,
866
910
  description: config.description,
@@ -891,9 +935,7 @@ export function wrapTool<T extends Tool>(
891
935
  ...tool,
892
936
  execute: async (params: unknown) => {
893
937
  try {
894
- const modifiedParams = middleware.before
895
- ? await middleware.before(params)
896
- : params
938
+ const modifiedParams = middleware.before ? await middleware.before(params) : params
897
939
  const result = await tool.execute(modifiedParams)
898
940
  return middleware.after ? await middleware.after(result) : result
899
941
  } catch (error) {
@@ -906,34 +948,151 @@ export function wrapTool<T extends Tool>(
906
948
  }
907
949
  }
908
950
 
951
+ /**
952
+ * Options for cachedTool
953
+ */
954
+ export interface CachedToolOptions {
955
+ /** Time-to-live in milliseconds (default: 60000) */
956
+ ttl?: number
957
+ /** Function to generate cache key from params (default: JSON.stringify) */
958
+ keyFn?: (params: unknown) => string
959
+ /** Interval in ms for automatic cleanup of expired entries (default: 0 = disabled) */
960
+ cleanupIntervalMs?: number
961
+ /** Maximum cache size before LRU eviction kicks in (default: 0 = unlimited) */
962
+ maxSize?: number
963
+ }
964
+
965
+ /**
966
+ * Extended tool interface with cache management methods
967
+ */
968
+ export interface CachedTool extends Tool {
969
+ /** Get the current number of entries in the cache */
970
+ cacheSize(): number
971
+ /** Clear all cache entries */
972
+ clearCache(): void
973
+ /** Stop cleanup timer and clear cache */
974
+ destroy(): void
975
+ }
976
+
909
977
  /**
910
978
  * Create a tool with caching support
979
+ *
980
+ * Features:
981
+ * - TTL-based expiration
982
+ * - Optional periodic cleanup of expired entries (prevents memory leaks)
983
+ * - Optional max size with LRU eviction
984
+ * - Manual cache control (clear, destroy)
911
985
  */
912
- export function cachedTool<T extends Tool>(
913
- tool: T,
914
- options: {
915
- ttl?: number
916
- keyFn?: (params: unknown) => string
917
- } = {}
918
- ): Tool {
919
- const cache = new Map<string, { value: unknown; expires: number }>()
920
- const { ttl = 60000, keyFn = JSON.stringify } = options
986
+ export function cachedTool<T extends Tool>(tool: T, options: CachedToolOptions = {}): CachedTool {
987
+ const { ttl = 60000, keyFn = JSON.stringify, cleanupIntervalMs = 0, maxSize = 0 } = options
921
988
 
922
- return {
989
+ interface CacheEntry {
990
+ value: unknown
991
+ expires: number
992
+ lastAccessed: number
993
+ }
994
+
995
+ const cache = new Map<string, CacheEntry>()
996
+ let cleanupTimer: ReturnType<typeof setInterval> | null = null
997
+ let destroyed = false
998
+
999
+ // Cleanup function to remove expired entries
1000
+ const cleanupExpired = () => {
1001
+ const now = Date.now()
1002
+ for (const [key, entry] of cache) {
1003
+ if (entry.expires <= now) {
1004
+ cache.delete(key)
1005
+ }
1006
+ }
1007
+ }
1008
+
1009
+ // Start periodic cleanup if configured
1010
+ if (cleanupIntervalMs > 0) {
1011
+ cleanupTimer = setInterval(cleanupExpired, cleanupIntervalMs)
1012
+ // Unref the timer so it doesn't keep the process alive (Node.js)
1013
+ if (typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {
1014
+ cleanupTimer.unref()
1015
+ }
1016
+ }
1017
+
1018
+ // Evict oldest entries based on lastAccessed (LRU)
1019
+ const evictOldest = () => {
1020
+ if (maxSize <= 0 || cache.size < maxSize) return
1021
+
1022
+ // Find the entry with oldest lastAccessed
1023
+ let oldestKey: string | null = null
1024
+ let oldestTime = Infinity
1025
+
1026
+ for (const [key, entry] of cache) {
1027
+ if (entry.lastAccessed < oldestTime) {
1028
+ oldestTime = entry.lastAccessed
1029
+ oldestKey = key
1030
+ }
1031
+ }
1032
+
1033
+ if (oldestKey) {
1034
+ cache.delete(oldestKey)
1035
+ }
1036
+ }
1037
+
1038
+ const cachedToolInstance: CachedTool = {
923
1039
  ...tool,
924
1040
  execute: async (params: unknown) => {
1041
+ if (destroyed) {
1042
+ // If destroyed, just execute without caching
1043
+ return tool.execute(params)
1044
+ }
1045
+
925
1046
  const key = keyFn(params)
926
1047
  const cached = cache.get(key)
1048
+ const now = Date.now()
927
1049
 
928
- if (cached && cached.expires > Date.now()) {
1050
+ if (cached && cached.expires > now) {
1051
+ // Cache hit - update last accessed time for LRU
1052
+ cached.lastAccessed = now
929
1053
  return cached.value
930
1054
  }
931
1055
 
1056
+ // Cache miss or expired - remove expired entry if present
1057
+ if (cached) {
1058
+ cache.delete(key)
1059
+ }
1060
+
932
1061
  const result = await tool.execute(params)
933
- cache.set(key, { value: result, expires: Date.now() + ttl })
1062
+
1063
+ // Evict oldest if we're at max size
1064
+ if (maxSize > 0 && cache.size >= maxSize) {
1065
+ evictOldest()
1066
+ }
1067
+
1068
+ cache.set(key, {
1069
+ value: result,
1070
+ expires: now + ttl,
1071
+ lastAccessed: now,
1072
+ })
1073
+
934
1074
  return result
935
1075
  },
1076
+
1077
+ cacheSize(): number {
1078
+ return cache.size
1079
+ },
1080
+
1081
+ clearCache(): void {
1082
+ cache.clear()
1083
+ },
1084
+
1085
+ destroy(): void {
1086
+ destroyed = true
1087
+ if (cleanupTimer !== null) {
1088
+ clearInterval(cleanupTimer)
1089
+ cleanupTimer = null
1090
+ }
1091
+ cache.clear()
1092
+ },
936
1093
  }
1094
+
1095
+ return cachedToolInstance
937
1096
  }
938
1097
 
939
1098
  /**
@@ -971,16 +1130,16 @@ export function rateLimitedTool<T extends Tool>(
971
1130
  /**
972
1131
  * Create a tool that times out after a specified duration
973
1132
  */
974
- export function timeoutTool<T extends Tool>(
975
- tool: T,
976
- timeoutMs: number
977
- ): Tool {
1133
+ export function timeoutTool<T extends Tool>(tool: T, timeoutMs: number): Tool {
978
1134
  return {
979
1135
  ...tool,
980
1136
  execute: async (params: unknown) => {
981
1137
  let timeoutId: ReturnType<typeof setTimeout> | undefined
982
1138
  const timeoutPromise = new Promise<never>((_, reject) => {
983
- timeoutId = setTimeout(() => reject(new Error(`Tool '${tool.name}' timed out after ${timeoutMs}ms`)), timeoutMs)
1139
+ timeoutId = setTimeout(
1140
+ () => reject(new Error(`Tool '${tool.name}' timed out after ${timeoutMs}ms`)),
1141
+ timeoutMs
1142
+ )
984
1143
  })
985
1144
  try {
986
1145
  return await Promise.race([tool.execute(params), timeoutPromise])
@@ -995,6 +1154,12 @@ export function timeoutTool<T extends Tool>(
995
1154
 
996
1155
  /**
997
1156
  * Create an agentic loop with sensible defaults
1157
+ *
1158
+ * @deprecated Phase C Week 2 — `createAgenticLoop` has zero production
1159
+ * callers in primitives.org.ai (only `ai-primitives` umbrella re-export
1160
+ * tests). Use AI SDK 6's `Agent` / `ToolLoopAgent` with
1161
+ * `stopWhen: stepCountIs(N)` instead. Will be removed alongside
1162
+ * `AgenticLoop` in the Phase C semver bump. See `bd show aip-ibid`.
998
1163
  */
999
1164
  export function createAgenticLoop(options: Partial<LoopOptions> & { tools: Tool[] }): AgenticLoop {
1000
1165
  return new AgenticLoop({
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Type Guards for AI Functions
3
+ *
4
+ * Shared type guard utilities used across AI packages.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import type { ZodTypeAny } from 'zod'
10
+
11
+ /**
12
+ * Check if value is a Zod schema
13
+ *
14
+ * Uses duck-typing to detect Zod schemas by checking for the
15
+ * `_def` property (internal Zod schema definition) and `parse` method.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { isZodSchema } from 'ai-functions'
20
+ * import { z } from 'zod'
21
+ *
22
+ * const schema = z.object({ name: z.string() })
23
+ * if (isZodSchema(schema)) {
24
+ * // TypeScript knows schema is ZodTypeAny
25
+ * schema.parse({ name: 'test' })
26
+ * }
27
+ * ```
28
+ */
29
+ export function isZodSchema(value: unknown): value is ZodTypeAny {
30
+ return value !== null && typeof value === 'object' && '_def' in value && 'parse' in value
31
+ }