ai-functions 2.1.3 → 2.4.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 (284) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +90 -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 +176 -0
  93. package/dist/function-registry.d.ts.map +1 -0
  94. package/dist/function-registry.js +685 -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/sandbox.d.ts +36 -0
  137. package/dist/sandbox.d.ts.map +1 -0
  138. package/dist/sandbox.js +44 -0
  139. package/dist/sandbox.js.map +1 -0
  140. package/dist/schema.js +2 -2
  141. package/dist/schema.js.map +1 -1
  142. package/dist/telemetry.d.ts +128 -0
  143. package/dist/telemetry.d.ts.map +1 -0
  144. package/dist/telemetry.js +285 -0
  145. package/dist/telemetry.js.map +1 -0
  146. package/dist/template.d.ts.map +1 -1
  147. package/dist/template.js +6 -1
  148. package/dist/template.js.map +1 -1
  149. package/dist/tool-orchestration.d.ts +66 -4
  150. package/dist/tool-orchestration.d.ts.map +1 -1
  151. package/dist/tool-orchestration.js +123 -23
  152. package/dist/tool-orchestration.js.map +1 -1
  153. package/dist/type-guards.d.ts +28 -0
  154. package/dist/type-guards.d.ts.map +1 -0
  155. package/dist/type-guards.js +29 -0
  156. package/dist/type-guards.js.map +1 -0
  157. package/dist/types.d.ts +155 -19
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/types.js +36 -1
  160. package/dist/types.js.map +1 -1
  161. package/dist/wrap-for-v3.d.ts +80 -0
  162. package/dist/wrap-for-v3.d.ts.map +1 -0
  163. package/dist/wrap-for-v3.js +89 -0
  164. package/dist/wrap-for-v3.js.map +1 -0
  165. package/examples/00-quickstart.ts +232 -0
  166. package/examples/01-rag-chatbot.ts +212 -0
  167. package/examples/02-multi-agent-research.ts +290 -0
  168. package/examples/03-email-classification.ts +379 -0
  169. package/examples/04-content-moderation.ts +400 -0
  170. package/examples/05-document-extraction.ts +455 -0
  171. package/examples/06-streaming-chat-nextjs.ts +437 -0
  172. package/examples/07-cloudflare-worker.ts +483 -0
  173. package/examples/08-batch-processing.ts +491 -0
  174. package/examples/09-budget-constrained.ts +527 -0
  175. package/examples/10-tool-orchestration.ts +565 -0
  176. package/examples/11-retry-resilience.ts +403 -0
  177. package/examples/12-caching-strategies.ts +422 -0
  178. package/examples/README.md +145 -0
  179. package/package.json +29 -25
  180. package/src/ai-promise.ts +226 -140
  181. package/src/ai-schemas.ts +122 -0
  182. package/src/ai.ts +71 -1176
  183. package/src/batch/anthropic.ts +96 -161
  184. package/src/batch/bedrock.ts +203 -454
  185. package/src/batch/cloudflare.ts +99 -282
  186. package/src/batch/google.ts +91 -297
  187. package/src/batch/index.ts +4 -1
  188. package/src/batch/memory.ts +15 -10
  189. package/src/batch/openai.ts +65 -193
  190. package/src/batch/provider.ts +336 -0
  191. package/src/batch-map.ts +29 -24
  192. package/src/batch-queue.ts +200 -11
  193. package/src/budget.ts +31 -18
  194. package/src/cache.ts +45 -17
  195. package/src/context.ts +106 -77
  196. package/src/digital-objects-registry.ts +750 -0
  197. package/src/errors.ts +37 -0
  198. package/src/eval/runner.ts +60 -36
  199. package/src/eval-log/in-memory.ts +90 -0
  200. package/src/eval-log/index.ts +46 -0
  201. package/src/eval-log/types.ts +110 -0
  202. package/src/function-registry.ts +874 -0
  203. package/src/generate.ts +33 -28
  204. package/src/index.ts +122 -21
  205. package/src/logger.ts +232 -0
  206. package/src/middleware/budget.ts +171 -0
  207. package/src/middleware/cache.ts +299 -0
  208. package/src/middleware/embed-cache.ts +195 -0
  209. package/src/middleware/index.ts +23 -0
  210. package/src/middleware/trace.ts +248 -0
  211. package/src/primitives.ts +589 -62
  212. package/src/retry.ts +144 -18
  213. package/src/sandbox.ts +52 -0
  214. package/src/schema.ts +8 -8
  215. package/src/telemetry.ts +403 -0
  216. package/src/template.ts +8 -4
  217. package/src/tool-orchestration.ts +213 -48
  218. package/src/type-guards.ts +31 -0
  219. package/src/types.ts +186 -27
  220. package/src/wrap-for-v3.ts +105 -0
  221. package/test/ai-promise.test.ts +1080 -0
  222. package/test/ai-proxy.test.ts +1 -1
  223. package/test/batch-autosubmit-errors.test.ts +49 -37
  224. package/test/batch-blog-posts.test.ts +87 -129
  225. package/test/core-functions.test.ts +183 -579
  226. package/test/decide.test.ts +154 -322
  227. package/test/define.test.ts +211 -8
  228. package/test/digital-objects-registry.test.ts +760 -0
  229. package/test/embedding-cache-middleware.test.ts +140 -0
  230. package/test/fill-template.test.ts +89 -0
  231. package/test/generate-core.test.ts +140 -229
  232. package/test/implicit-batch.test.ts +22 -65
  233. package/test/retry-policy-integration.test.ts +117 -0
  234. package/test/sandbox-execution.test.ts +155 -0
  235. package/test/schema.test.ts +55 -19
  236. package/test/template.test.ts +1164 -0
  237. package/test/tool-orchestration.test.ts +270 -0
  238. package/test/wrap-for-v3.test.ts +612 -0
  239. package/vitest.config.js +6 -0
  240. package/vitest.config.ts +20 -0
  241. package/LICENSE +0 -21
  242. package/dist/rpc/auth.d.ts +0 -69
  243. package/dist/rpc/auth.d.ts.map +0 -1
  244. package/dist/rpc/auth.js +0 -136
  245. package/dist/rpc/auth.js.map +0 -1
  246. package/dist/rpc/client.d.ts +0 -62
  247. package/dist/rpc/client.d.ts.map +0 -1
  248. package/dist/rpc/client.js +0 -103
  249. package/dist/rpc/client.js.map +0 -1
  250. package/dist/rpc/deferred.d.ts +0 -60
  251. package/dist/rpc/deferred.d.ts.map +0 -1
  252. package/dist/rpc/deferred.js +0 -96
  253. package/dist/rpc/deferred.js.map +0 -1
  254. package/dist/rpc/index.d.ts +0 -22
  255. package/dist/rpc/index.d.ts.map +0 -1
  256. package/dist/rpc/index.js +0 -38
  257. package/dist/rpc/index.js.map +0 -1
  258. package/dist/rpc/local.d.ts +0 -42
  259. package/dist/rpc/local.d.ts.map +0 -1
  260. package/dist/rpc/local.js +0 -50
  261. package/dist/rpc/local.js.map +0 -1
  262. package/dist/rpc/server.d.ts +0 -165
  263. package/dist/rpc/server.d.ts.map +0 -1
  264. package/dist/rpc/server.js +0 -405
  265. package/dist/rpc/server.js.map +0 -1
  266. package/dist/rpc/session.d.ts +0 -32
  267. package/dist/rpc/session.d.ts.map +0 -1
  268. package/dist/rpc/session.js +0 -43
  269. package/dist/rpc/session.js.map +0 -1
  270. package/dist/rpc/transport.d.ts +0 -306
  271. package/dist/rpc/transport.d.ts.map +0 -1
  272. package/dist/rpc/transport.js +0 -731
  273. package/dist/rpc/transport.js.map +0 -1
  274. package/src/batch/anthropic.js +0 -256
  275. package/src/batch/bedrock.js +0 -584
  276. package/src/batch/cloudflare.js +0 -287
  277. package/src/batch/google.js +0 -359
  278. package/src/batch/index.js +0 -30
  279. package/src/batch/memory.js +0 -187
  280. package/src/batch/openai.js +0 -402
  281. package/src/eval/index.js +0 -7
  282. package/src/eval/models.js +0 -119
  283. package/src/eval/runner.js +0 -147
  284. package/test/schema.test.js +0 -96
package/src/retry.ts CHANGED
@@ -9,9 +9,17 @@
9
9
  * - Error classification for intelligent retry decisions
10
10
  * - Partial retry for batch operations
11
11
  *
12
+ * Per-model policy data (which models retry how, who falls back to whom,
13
+ * which batch tiers each model supports) lives in `language-models`'s
14
+ * `policyFor()`. The classes in this file are the *machinery* that reads
15
+ * that policy. See `RetryPolicy.forModel`, `CircuitBreaker.forModel`,
16
+ * `FallbackChain.forModel`.
17
+ *
12
18
  * @packageDocumentation
13
19
  */
14
20
 
21
+ import { policyFor, type ModelPolicy, type ErrorCategoryName } from 'language-models'
22
+
15
23
  // ============================================================================
16
24
  // ERROR TYPES AND CLASSIFICATION
17
25
  // ============================================================================
@@ -83,13 +91,18 @@ export class RateLimitError extends RetryableError {
83
91
  constructor(message: string, options?: { retryAfter?: number }) {
84
92
  super(message, ErrorCategory.RateLimit)
85
93
  this.name = 'RateLimitError'
86
- this.retryAfter = options?.retryAfter
94
+ if (options?.retryAfter !== undefined) {
95
+ this.retryAfter = options.retryAfter
96
+ }
87
97
  }
88
98
 
89
99
  /**
90
100
  * Create RateLimitError from HTTP response
91
101
  */
92
- static fromResponse(response: { status: number; headers?: Record<string, string> }): RateLimitError {
102
+ static fromResponse(response: {
103
+ status: number
104
+ headers?: Record<string, string>
105
+ }): RateLimitError {
93
106
  const retryAfterHeader = response.headers?.['retry-after']
94
107
  let retryAfter: number | undefined
95
108
 
@@ -100,7 +113,10 @@ export class RateLimitError extends RetryableError {
100
113
  }
101
114
  }
102
115
 
103
- return new RateLimitError(`Rate limited (${response.status})`, { retryAfter })
116
+ return new RateLimitError(
117
+ `Rate limited (${response.status})`,
118
+ retryAfter !== undefined ? { retryAfter } : undefined
119
+ )
104
120
  }
105
121
  }
106
122
 
@@ -116,6 +132,11 @@ export class CircuitOpenError extends Error {
116
132
  }
117
133
  }
118
134
 
135
+ /** Error with status property (e.g., HTTP errors) */
136
+ interface ErrorWithStatus extends Error {
137
+ status?: number
138
+ }
139
+
119
140
  /**
120
141
  * Classify an error into a category for retry decisions
121
142
  */
@@ -125,7 +146,7 @@ export function classifyError(error: unknown): ErrorCategory {
125
146
  }
126
147
 
127
148
  const message = error.message.toLowerCase()
128
- const status = (error as any).status as number | undefined
149
+ const status = (error as ErrorWithStatus).status
129
150
 
130
151
  // Network errors
131
152
  if (
@@ -309,9 +330,20 @@ export interface BatchItemResult<T, R> {
309
330
 
310
331
  /**
311
332
  * Retry policy for executing operations with exponential backoff
333
+ *
334
+ * @deprecated Phase C Week 3 — `RetryPolicy` has 1 real production caller
335
+ * (audited 2026-05-06; see `bd show aip-ibid`):
336
+ * `ai-database/src/cascade-orchestrator.ts:1235` (loose coupling — dynamic
337
+ * import + graceful try/catch fallback when ai-functions not available).
338
+ * AI SDK 6's `customProvider({ retryPolicy })` and `wrapLanguageModel(model,
339
+ * retryMiddleware)` cover the same surface. Migration tracked in aip-ibid;
340
+ * the one callsite can move on a separate commit. Will be removed in the
341
+ * Phase C semver bump.
312
342
  */
313
343
  export class RetryPolicy {
314
- private readonly options: Required<Omit<RetryOptions, 'shouldRetry'>> & { shouldRetry?: (error: unknown) => boolean }
344
+ private readonly options: Required<Omit<RetryOptions, 'shouldRetry'>> & {
345
+ shouldRetry?: (error: unknown) => boolean
346
+ }
315
347
 
316
348
  constructor(options: RetryOptions = {}) {
317
349
  this.options = {
@@ -322,16 +354,55 @@ export class RetryPolicy {
322
354
  jitter: options.jitter ?? 0,
323
355
  jitterStrategy: options.jitterStrategy ?? 'equal',
324
356
  respectRetryAfter: options.respectRetryAfter ?? true,
325
- shouldRetry: options.shouldRetry,
357
+ ...(options.shouldRetry !== undefined && { shouldRetry: options.shouldRetry }),
326
358
  }
327
359
  }
328
360
 
361
+ /**
362
+ * Build a RetryPolicy from a model's `ModelPolicy` (loaded via
363
+ * `language-models`). Per-call `overrides` win over policy data.
364
+ *
365
+ * @example
366
+ * ```ts
367
+ * const policy = RetryPolicy.forModel('sonnet')
368
+ * // Uses retry settings derived for anthropic/claude-sonnet-4.5
369
+ * ```
370
+ */
371
+ static forModel(alias: string, overrides: RetryOptions = {}): RetryPolicy {
372
+ const policy = policyFor(alias)
373
+ return RetryPolicy.fromPolicy(policy, overrides)
374
+ }
375
+
376
+ /**
377
+ * Build a RetryPolicy directly from a `ModelPolicy`. Useful when the policy
378
+ * is already in hand (e.g. from a request context).
379
+ */
380
+ static fromPolicy(policy: ModelPolicy, overrides: RetryOptions = {}): RetryPolicy {
381
+ const retryable = new Set<ErrorCategoryName>(policy.retry.retryableCategories)
382
+ const shouldRetry = (error: unknown): boolean => {
383
+ // Honour error's own retryable property when present.
384
+ if (error && typeof error === 'object' && 'retryable' in error) {
385
+ const flag = (error as { retryable: boolean }).retryable
386
+ if (flag === false) return false
387
+ }
388
+ const category = classifyError(error)
389
+ return retryable.has(category as ErrorCategoryName)
390
+ }
391
+ return new RetryPolicy({
392
+ maxRetries: policy.retry.maxRetries,
393
+ baseDelay: policy.retry.baseDelay,
394
+ maxDelay: policy.retry.maxDelay,
395
+ multiplier: policy.retry.multiplier,
396
+ jitter: policy.retry.jitter,
397
+ shouldRetry,
398
+ ...overrides,
399
+ })
400
+ }
401
+
329
402
  /**
330
403
  * Execute an operation with retry logic
331
404
  */
332
- async execute<T>(
333
- operation: (info: RetryInfo) => Promise<T>
334
- ): Promise<T> {
405
+ async execute<T>(operation: (info: RetryInfo) => Promise<T>): Promise<T> {
335
406
  let lastError: unknown
336
407
  let previousDelay = this.options.baseDelay
337
408
 
@@ -386,7 +457,7 @@ export class RetryPolicy {
386
457
  const attemptCounts = new Map<T, number>()
387
458
 
388
459
  // Initialize attempt counts
389
- items.forEach(item => attemptCounts.set(item, 0))
460
+ items.forEach((item) => attemptCounts.set(item, 0))
390
461
 
391
462
  for (let round = 0; round <= this.options.maxRetries && pendingItems.length > 0; round++) {
392
463
  // Wait before retry (not on first attempt)
@@ -427,7 +498,7 @@ export class RetryPolicy {
427
498
  }
428
499
 
429
500
  // Return results in original order
430
- return items.map(item => finalResults.get(item)!)
501
+ return items.map((item) => finalResults.get(item)!)
431
502
  }
432
503
 
433
504
  private isRetryable(error: unknown): boolean {
@@ -438,7 +509,7 @@ export class RetryPolicy {
438
509
 
439
510
  // Check error's own retryable property
440
511
  if (error && typeof error === 'object' && 'retryable' in error) {
441
- return (error as any).retryable === true
512
+ return (error as { retryable: boolean }).retryable === true
442
513
  }
443
514
 
444
515
  // Classify error and determine retryability
@@ -452,7 +523,7 @@ export class RetryPolicy {
452
523
  }
453
524
 
454
525
  private sleep(ms: number): Promise<void> {
455
- return new Promise(resolve => setTimeout(resolve, ms))
526
+ return new Promise((resolve) => setTimeout(resolve, ms))
456
527
  }
457
528
  }
458
529
 
@@ -497,6 +568,12 @@ export interface CircuitBreakerMetrics {
497
568
  * - CLOSED: Normal operation, failures tracked
498
569
  * - OPEN: Fail fast, reject all requests
499
570
  * - HALF-OPEN: Allow single test request
571
+ *
572
+ * @deprecated Phase C Week 3 — `CircuitBreaker` has zero real callers in
573
+ * primitives.org.ai (audited 2026-05-06; only comment-only references in
574
+ * `language-models/src/index.ts`; see `bd show aip-ibid`). AI SDK 6's
575
+ * `wrapLanguageModel(model, circuitMiddleware)` replacement is the going-
576
+ * forward primitive. Will be removed in the Phase C semver bump.
500
577
  */
501
578
  export class CircuitBreaker {
502
579
  private _state: CircuitState = 'closed'
@@ -517,6 +594,20 @@ export class CircuitBreaker {
517
594
  }
518
595
  }
519
596
 
597
+ /**
598
+ * Build a CircuitBreaker for a specific model, using its `ModelPolicy`.
599
+ * Per-call overrides win over policy data.
600
+ */
601
+ static forModel(alias: string, overrides: CircuitBreakerOptions = {}): CircuitBreaker {
602
+ const policy = policyFor(alias)
603
+ return new CircuitBreaker({
604
+ failureThreshold: policy.circuitBreaker.failureThreshold,
605
+ resetTimeout: policy.circuitBreaker.resetTimeout,
606
+ successThreshold: policy.circuitBreaker.successThreshold,
607
+ ...overrides,
608
+ })
609
+ }
610
+
520
611
  /**
521
612
  * Current circuit state
522
613
  */
@@ -658,6 +749,14 @@ export interface FallbackMetrics {
658
749
  *
659
750
  * Tries models in order until one succeeds:
660
751
  * sonnet -> opus -> gpt-4o -> gemini
752
+ *
753
+ * @deprecated Phase C Week 3 — `FallbackChain` (LLM model failover) has
754
+ * zero real callers in primitives.org.ai (audited 2026-05-06; the
755
+ * `human-in-the-loop` package's `FallbackChain` is a different class for
756
+ * HITL fallback resolution, not LLM failover). AI SDK 4.3+ ships native
757
+ * `customProvider({ fallbackProvider })` which is the going-forward
758
+ * primitive. See `bd show aip-ibid`. Will be removed in the Phase C
759
+ * semver bump.
661
760
  */
662
761
  export class FallbackChain<T = unknown, P = unknown> {
663
762
  private readonly models: FallbackModel<T, P>[]
@@ -672,6 +771,33 @@ export class FallbackChain<T = unknown, P = unknown> {
672
771
  this.options = options
673
772
  }
674
773
 
774
+ /**
775
+ * Build a FallbackChain from a model's `ModelPolicy`. The caller supplies
776
+ * an `executor` that takes a model id and returns a promise — the chain
777
+ * applies it to the primary model first, then to each fallback in order.
778
+ *
779
+ * @example
780
+ * ```ts
781
+ * const chain = FallbackChain.forModel('sonnet', (modelId, params) =>
782
+ * ai({ model: modelId, prompt: params!.prompt })
783
+ * )
784
+ * await chain.execute({ prompt: 'Hello' })
785
+ * ```
786
+ */
787
+ static forModel<T = unknown, P = unknown>(
788
+ alias: string,
789
+ executor: (modelId: string, params?: P) => Promise<T>,
790
+ options: FallbackOptions = {}
791
+ ): FallbackChain<T, P> {
792
+ const policy = policyFor(alias)
793
+ const ids = [policy.$id, ...policy.fallbackChain]
794
+ const models: FallbackModel<T, P>[] = ids.map((id) => ({
795
+ name: id,
796
+ execute: (params?: P) => executor(id, params),
797
+ }))
798
+ return new FallbackChain<T, P>(models, options)
799
+ }
800
+
675
801
  /**
676
802
  * Execute the fallback chain
677
803
  */
@@ -758,15 +884,15 @@ export class FallbackChain<T = unknown, P = unknown> {
758
884
  * const response = await reliableFetch('https://api.example.com')
759
885
  * ```
760
886
  */
761
- export function withRetry<T extends (...args: any[]) => Promise<any>>(
762
- fn: T,
887
+ export function withRetry<TArgs extends unknown[], TResult>(
888
+ fn: (...args: TArgs) => Promise<TResult>,
763
889
  options: RetryOptions = {}
764
- ): T {
890
+ ): (...args: TArgs) => Promise<TResult> {
765
891
  const policy = new RetryPolicy(options)
766
892
 
767
- return (async (...args: Parameters<T>) => {
893
+ return async (...args: TArgs): Promise<TResult> => {
768
894
  return policy.execute(() => fn(...args))
769
- }) as T
895
+ }
770
896
  }
771
897
 
772
898
  // ============================================================================
package/src/sandbox.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Sandbox execution boundary for ai-functions.
3
+ *
4
+ * ALL dynamic code execution in ai-functions is delegated to ai-evaluate's
5
+ * V8-isolate sandbox (Cloudflare Dynamic Workers). `new Function`/`eval` are
6
+ * banned in this package — they are broken under Workers and unsandboxed under
7
+ * Node.
8
+ *
9
+ * ## The env boundary
10
+ *
11
+ * "Zero env plumbing" is NOT achievable:
12
+ * - The Workers entry (`ai-evaluate`) requires a `LOADER` binding (and, for the
13
+ * test path, a `TEST` service binding) to be passed in `env`.
14
+ * - That entry imports `cloudflare:workers`, which is Node-incompatible, so it
15
+ * cannot be imported eagerly in a Node/dev process.
16
+ *
17
+ * The clean boundary is therefore an **explicit, optional `env`**:
18
+ * - When a host Workers `env` (carrying `LOADER` + `TEST`) is supplied, run on
19
+ * the real Dynamic Workers loader via `ai-evaluate`.
20
+ * - When absent (Node / dev / tests), import from `ai-evaluate/node`, which
21
+ * falls back to Miniflare and runs with no live Worker.
22
+ *
23
+ * The `ai-evaluate/node` module is only imported when no `env` is present, so a
24
+ * Node process never pulls in `cloudflare:workers`.
25
+ */
26
+
27
+ import type { EvaluateOptions, EvaluateResult, SandboxEnv } from 'ai-evaluate'
28
+
29
+ export type { SandboxEnv } from 'ai-evaluate'
30
+
31
+ /**
32
+ * Run an evaluation in the appropriate sandbox.
33
+ *
34
+ * @param options - What to evaluate (`script`, or `module` + `tests`, etc.)
35
+ * @param env - Optional host Workers env carrying `LOADER` (+ `TEST` for the
36
+ * test path). When omitted, falls back to the Miniflare-backed Node entry.
37
+ */
38
+ export async function runInSandbox(
39
+ options: EvaluateOptions,
40
+ env?: SandboxEnv
41
+ ): Promise<EvaluateResult> {
42
+ if (env && (env.loader || env.LOADER)) {
43
+ // Host Workers env present — use the Dynamic Workers loader entry.
44
+ const { evaluate } = await import('ai-evaluate')
45
+ return evaluate(options, env)
46
+ }
47
+
48
+ // No live Worker — use the Node entry (Miniflare fallback). This module is
49
+ // imported lazily so Node processes never eagerly pull in `cloudflare:workers`.
50
+ const { evaluate } = await import('ai-evaluate/node')
51
+ return evaluate(options, env)
52
+ }
package/src/schema.ts CHANGED
@@ -15,18 +15,18 @@
15
15
  */
16
16
 
17
17
  import { z, type ZodTypeAny } from 'zod'
18
- import { isZodSchema } from '@org.ai/core'
18
+ import { isZodSchema } from './type-guards.js'
19
19
 
20
20
  /**
21
21
  * Simplified schema types
22
22
  */
23
23
  export type SimpleSchema =
24
- | string // z.string().describe(value)
25
- | [string] // z.array(z.string()).describe(value)
26
- | [number] // z.array(z.number()).describe(value)
27
- | [SimpleSchema] // z.array(converted).describe(value)
28
- | { [key: string]: SimpleSchema } // z.object() recursively
29
- | ZodTypeAny // Pass-through for actual Zod schemas
24
+ | string // z.string().describe(value)
25
+ | [string] // z.array(z.string()).describe(value)
26
+ | [number] // z.array(z.number()).describe(value)
27
+ | [SimpleSchema] // z.array(converted).describe(value)
28
+ | { [key: string]: SimpleSchema } // z.object() recursively
29
+ | ZodTypeAny // Pass-through for actual Zod schemas
30
30
 
31
31
  /**
32
32
  * Convert a simplified schema to a Zod schema
@@ -64,7 +64,7 @@ function convertToZod(input: SimpleSchema): ZodTypeAny {
64
64
  if (typeof input === 'string') {
65
65
  // Enum syntax: 'option1 | option2 | option3'
66
66
  if (input.includes(' | ')) {
67
- const options = input.split(' | ').map(s => s.trim())
67
+ const options = input.split(' | ').map((s) => s.trim())
68
68
  return z.enum(options as [string, ...string[]])
69
69
  }
70
70