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
package/src/ai-promise.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * AIPromise - RPC-style promise pipelining for AI functions
2
+ * AIPromise - Promise pipelining for AI functions
3
3
  *
4
- * Inspired by capnweb's RpcPromise, this enables:
4
+ * This enables:
5
5
  * - Property access tracking for dynamic schema inference
6
6
  * - Promise pipelining without await
7
7
  * - Magical .map() for batch processing
@@ -58,7 +58,8 @@ export interface StreamOptions {
58
58
  * Streaming result wrapper that provides both AsyncIterable interface
59
59
  * and access to the final result
60
60
  */
61
- export interface StreamingAIPromise<T> extends AsyncIterable<T extends string ? string : Partial<T>> {
61
+ export interface StreamingAIPromise<T>
62
+ extends AsyncIterable<T extends string ? string : Partial<T>> {
62
63
  /** Stream of text chunks (for text generation) */
63
64
  textStream: AsyncIterable<string>
64
65
  /** Stream of partial objects (for object generation) */
@@ -131,7 +132,7 @@ let resolutionScheduled = false
131
132
  // ============================================================================
132
133
 
133
134
  /**
134
- * AIPromise - Like capnweb's RpcPromise but for AI functions
135
+ * AIPromise - Promise wrapper for AI functions
135
136
  *
136
137
  * Acts as both a Promise AND a stub that:
137
138
  * - Tracks property accesses for dynamic schema inference
@@ -233,17 +234,15 @@ export class AIPromise<T> implements PromiseLike<T> {
233
234
  const resolvedDeps: Record<string, unknown> = {}
234
235
  for (const dep of this._dependencies) {
235
236
  const value = await dep.promise.resolve()
236
- const key = dep.path.length > 0 ? dep.path.join('.') : `dep_${this._dependencies.indexOf(dep)}`
237
+ const key =
238
+ dep.path.length > 0 ? dep.path.join('.') : `dep_${this._dependencies.indexOf(dep)}`
237
239
  resolvedDeps[key] = value
238
240
  }
239
241
 
240
242
  // Substitute resolved dependencies into prompt
241
243
  let finalPrompt = this._prompt
242
244
  for (const [key, value] of Object.entries(resolvedDeps)) {
243
- finalPrompt = finalPrompt.replace(
244
- new RegExp(`\\$\\{${key}\\}`, 'g'),
245
- String(value)
246
- )
245
+ finalPrompt = finalPrompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value))
247
246
  }
248
247
 
249
248
  // Build schema from accessed properties
@@ -254,9 +253,9 @@ export class AIPromise<T> implements PromiseLike<T> {
254
253
  model: this._options.model || 'sonnet',
255
254
  schema,
256
255
  prompt: finalPrompt,
257
- system: this._options.system,
258
- temperature: this._options.temperature,
259
- maxTokens: this._options.maxTokens,
256
+ ...(this._options.system !== undefined && { system: this._options.system }),
257
+ ...(this._options.temperature !== undefined && { temperature: this._options.temperature }),
258
+ ...(this._options.maxTokens !== undefined && { maxTokens: this._options.maxTokens }),
260
259
  })
261
260
 
262
261
  // Extract the value based on type
@@ -264,16 +263,31 @@ export class AIPromise<T> implements PromiseLike<T> {
264
263
  // 1. Runtime type checking validates the response structure
265
264
  // 2. The type parameter T corresponds to the expected output type for each mode
266
265
  let value = result.object as T
267
- if (this._options.type === 'text' && typeof value === 'object' && value !== null && 'text' in value) {
266
+ if (
267
+ this._options.type === 'text' &&
268
+ typeof value === 'object' &&
269
+ value !== null &&
270
+ 'text' in value
271
+ ) {
268
272
  value = (value as { text: T }).text
269
- } else if (this._options.type === 'boolean' && typeof value === 'object' && value !== null && 'answer' in value) {
273
+ } else if (
274
+ this._options.type === 'boolean' &&
275
+ typeof value === 'object' &&
276
+ value !== null &&
277
+ 'answer' in value
278
+ ) {
270
279
  const answer = (value as { answer: string | boolean }).answer
271
280
  // When type === 'boolean', T is constrained to boolean at the call site.
272
281
  // TypeScript can't express this dependent relationship, so we use a simple cast.
273
282
  // Runtime validation: answer is verified to be 'true', 'false', or boolean.
274
283
  const booleanValue = answer === 'true' || answer === true
275
284
  value = booleanValue as T
276
- } else if ((this._options.type === 'list' || this._options.type === 'extract') && typeof value === 'object' && value !== null && 'items' in value) {
285
+ } else if (
286
+ (this._options.type === 'list' || this._options.type === 'extract') &&
287
+ typeof value === 'object' &&
288
+ value !== null &&
289
+ 'items' in value
290
+ ) {
277
291
  value = (value as { items: T }).items
278
292
  }
279
293
 
@@ -301,7 +315,11 @@ export class AIPromise<T> implements PromiseLike<T> {
301
315
  case 'list':
302
316
  return { items: ['List items'] }
303
317
  case 'extract':
304
- return { items: ['Extracted items'] }
318
+ return {
319
+ items: [
320
+ 'Array of extracted items as strings - extract ALL matching items from the text',
321
+ ],
322
+ }
305
323
  case 'lists':
306
324
  return { categories: ['Category names'], data: 'JSON object with categorized lists' }
307
325
  case 'boolean':
@@ -380,30 +398,31 @@ export class AIPromise<T> implements PromiseLike<T> {
380
398
  * }))
381
399
  * ```
382
400
  */
383
- map<U>(
384
- callback: (item: T extends (infer I)[] ? I : T, index: number) => U
385
- ): BatchMapPromise<U> {
401
+ map<U>(callback: (item: T extends (infer I)[] ? I : T, index: number) => U): BatchMapPromise<U> {
386
402
  // Create a wrapper that resolves this promise first, then maps
387
403
  const mapPromise = new BatchMapPromise<U>([], [], {})
388
404
 
389
405
  // Override the resolve to first get the list items
390
- const originalResolve = mapPromise.resolve.bind(mapPromise)
391
- ;(mapPromise as any).resolve = async () => {
392
- // First, resolve the list
393
- const items = await this.resolve()
394
-
395
- if (!Array.isArray(items)) {
396
- throw new Error('Cannot map over non-array result')
397
- }
406
+ // Type assertion: BatchMapPromise.resolve is a public method that we're replacing
407
+ // with a compatible async function returning Promise<U[]>
408
+ const self = this
409
+ Object.defineProperty(mapPromise, 'resolve', {
410
+ value: async function (): Promise<U[]> {
411
+ // First, resolve the list
412
+ const items = await self.resolve()
413
+
414
+ if (!Array.isArray(items)) {
415
+ throw new Error('Cannot map over non-array result')
416
+ }
398
417
 
399
- // Now create the actual batch map with the resolved items
400
- const actualBatchMap = createBatchMap(
401
- items as (T extends (infer I)[] ? I : T)[],
402
- callback
403
- )
418
+ // Now create the actual batch map with the resolved items
419
+ const actualBatchMap = createBatchMap(items as (T extends (infer I)[] ? I : T)[], callback)
404
420
 
405
- return actualBatchMap.resolve()
406
- }
421
+ return actualBatchMap.resolve()
422
+ },
423
+ writable: true,
424
+ configurable: true,
425
+ })
407
426
 
408
427
  return mapPromise
409
428
  }
@@ -424,19 +443,24 @@ export class AIPromise<T> implements PromiseLike<T> {
424
443
  callback: (item: T extends (infer I)[] ? I : T, index: number) => U
425
444
  ): BatchMapPromise<U> {
426
445
  const mapPromise = new BatchMapPromise<U>([], [], { immediate: true })
446
+ const self = this
427
447
 
428
- ;(mapPromise as any).resolve = async () => {
429
- const items = await this.resolve()
430
- if (!Array.isArray(items)) {
431
- throw new Error('Cannot map over non-array result')
432
- }
433
- const actualBatchMap = createBatchMap(
434
- items as (T extends (infer I)[] ? I : T)[],
435
- callback,
436
- { immediate: true }
437
- )
438
- return actualBatchMap.resolve()
439
- }
448
+ Object.defineProperty(mapPromise, 'resolve', {
449
+ value: async function (): Promise<U[]> {
450
+ const items = await self.resolve()
451
+ if (!Array.isArray(items)) {
452
+ throw new Error('Cannot map over non-array result')
453
+ }
454
+ const actualBatchMap = createBatchMap(
455
+ items as (T extends (infer I)[] ? I : T)[],
456
+ callback,
457
+ { immediate: true }
458
+ )
459
+ return actualBatchMap.resolve()
460
+ },
461
+ writable: true,
462
+ configurable: true,
463
+ })
440
464
 
441
465
  return mapPromise
442
466
  }
@@ -445,19 +469,24 @@ export class AIPromise<T> implements PromiseLike<T> {
445
469
  callback: (item: T extends (infer I)[] ? I : T, index: number) => U
446
470
  ): BatchMapPromise<U> {
447
471
  const mapPromise = new BatchMapPromise<U>([], [], { deferred: true })
472
+ const self = this
448
473
 
449
- ;(mapPromise as any).resolve = async () => {
450
- const items = await this.resolve()
451
- if (!Array.isArray(items)) {
452
- throw new Error('Cannot map over non-array result')
453
- }
454
- const actualBatchMap = createBatchMap(
455
- items as (T extends (infer I)[] ? I : T)[],
456
- callback,
457
- { deferred: true }
458
- )
459
- return actualBatchMap.resolve()
460
- }
474
+ Object.defineProperty(mapPromise, 'resolve', {
475
+ value: async function (): Promise<U[]> {
476
+ const items = await self.resolve()
477
+ if (!Array.isArray(items)) {
478
+ throw new Error('Cannot map over non-array result')
479
+ }
480
+ const actualBatchMap = createBatchMap(
481
+ items as (T extends (infer I)[] ? I : T)[],
482
+ callback,
483
+ { deferred: true }
484
+ )
485
+ return actualBatchMap.resolve()
486
+ },
487
+ writable: true,
488
+ configurable: true,
489
+ })
461
490
 
462
491
  return mapPromise
463
492
  }
@@ -481,7 +510,8 @@ export class AIPromise<T> implements PromiseLike<T> {
481
510
  await callback(items[i], i)
482
511
  }
483
512
  } else {
484
- await callback(items as any, 0)
513
+ // When T is not an array, the conditional type T extends (infer I)[] ? I : T resolves to T
514
+ await callback(items as T extends (infer I)[] ? I : T, 0)
485
515
  }
486
516
  }
487
517
 
@@ -492,10 +522,12 @@ export class AIPromise<T> implements PromiseLike<T> {
492
522
  const items = await this.resolve()
493
523
  if (Array.isArray(items)) {
494
524
  for (const item of items) {
495
- yield item as any
525
+ // Each array item is the inferred element type I when T extends I[]
526
+ yield item as T extends (infer I)[] ? I : T
496
527
  }
497
528
  } else {
498
- yield items as any
529
+ // When T is not an array, the item type is T itself
530
+ yield items as T extends (infer I)[] ? I : T
499
531
  }
500
532
  }
501
533
 
@@ -583,28 +615,44 @@ export class AIPromise<T> implements PromiseLike<T> {
583
615
  // ============================================================================
584
616
 
585
617
  const PROXY_HANDLERS: ProxyHandler<AIPromise<unknown>> = {
586
- get(target, prop: string | symbol, receiver) {
618
+ get(target, prop: string | symbol, _receiver) {
587
619
  // Handle symbols
588
620
  if (typeof prop === 'symbol') {
589
621
  if (prop === AI_PROMISE_SYMBOL) return true
590
622
  if (prop === RAW_PROMISE_SYMBOL) return target
591
623
  if (prop === Symbol.asyncIterator) return target[Symbol.asyncIterator].bind(target)
592
- return (target as any)[prop]
624
+ return (target as unknown as Record<symbol, unknown>)[prop]
593
625
  }
594
626
 
595
627
  // Handle promise methods
596
628
  if (prop === 'then' || prop === 'catch' || prop === 'finally') {
597
- return (target as any)[prop].bind(target)
629
+ const method = (target as unknown as Record<string, (...args: unknown[]) => unknown>)[prop]
630
+ return method?.bind(target)
598
631
  }
599
632
 
600
633
  // Handle AIPromise methods
601
- if (prop === 'map' || prop === 'forEach' || prop === 'resolve' || prop === 'stream' || prop === 'addDependency' || prop === 'mapImmediate' || prop === 'mapDeferred') {
602
- return (target as any)[prop].bind(target)
634
+ if (
635
+ prop === 'map' ||
636
+ prop === 'forEach' ||
637
+ prop === 'resolve' ||
638
+ prop === 'stream' ||
639
+ prop === 'addDependency' ||
640
+ prop === 'mapImmediate' ||
641
+ prop === 'mapDeferred'
642
+ ) {
643
+ const method = (target as unknown as Record<string, (...args: unknown[]) => unknown>)[prop]
644
+ return method?.bind(target)
603
645
  }
604
646
 
605
647
  // Handle internal properties
606
- if (prop.startsWith('_') || prop === 'prompt' || prop === 'path' || prop === 'isResolved' || prop === 'accessedProps') {
607
- return (target as any)[prop]
648
+ if (
649
+ prop.startsWith('_') ||
650
+ prop === 'prompt' ||
651
+ prop === 'path' ||
652
+ prop === 'isResolved' ||
653
+ prop === 'accessedProps'
654
+ ) {
655
+ return (target as unknown as Record<string, unknown>)[prop]
608
656
  }
609
657
 
610
658
  // Track property access for schema inference
@@ -616,14 +664,11 @@ const PROXY_HANDLERS: ProxyHandler<AIPromise<unknown>> = {
616
664
  }
617
665
 
618
666
  // Return a new AIPromise for the property path
619
- return new AIPromise<unknown>(
620
- target.prompt,
621
- {
622
- ...target['_options'],
623
- parent: target,
624
- propertyPath: [...target.path, prop],
625
- }
626
- )
667
+ return new AIPromise<unknown>(target.prompt, {
668
+ ...target['_options'],
669
+ parent: target,
670
+ propertyPath: [...target.path, prop],
671
+ })
627
672
  },
628
673
 
629
674
  // Prevent mutation
@@ -636,10 +681,11 @@ const PROXY_HANDLERS: ProxyHandler<AIPromise<unknown>> = {
636
681
  },
637
682
 
638
683
  // Handle function calls (for chained methods)
639
- apply(target, thisArg, args) {
684
+ apply(target, _thisArg, args) {
640
685
  // If the target is callable (e.g., from a template function), call it
641
- if (typeof (target as any)._call === 'function') {
642
- return (target as any)._call(...args)
686
+ const call = (target as unknown as Record<string, unknown>)['_call']
687
+ if (typeof call === 'function') {
688
+ return (call as (...args: unknown[]) => unknown)(...args)
643
689
  }
644
690
  throw new Error('AIPromise is not callable')
645
691
  },
@@ -685,10 +731,12 @@ function analyzeRecordingResult(result: unknown, recording: MapRecording): Simpl
685
731
  // Infer schema from the promise's accessed properties or type
686
732
  if (aiPromise.accessedProps.size > 0) {
687
733
  schema[key] = Object.fromEntries(
688
- Array.from(aiPromise.accessedProps).map(p => [p, `The ${p}`])
734
+ Array.from(aiPromise.accessedProps).map((p) => [p, `The ${p}`])
689
735
  )
690
736
  } else {
691
- const type = (aiPromise as any)._options?.type
737
+ // Access private _options through type-safe assertion
738
+ const options = (aiPromise as unknown as { _options: AIPromiseOptions })._options
739
+ const type = options?.type
692
740
  if (type === 'boolean') {
693
741
  schema[key] = 'true | false'
694
742
  } else if (type === 'list') {
@@ -717,7 +765,7 @@ export function isAIPromise(value: unknown): value is AIPromise<unknown> {
717
765
  value !== null &&
718
766
  typeof value === 'object' &&
719
767
  AI_PROMISE_SYMBOL in value &&
720
- (value as any)[AI_PROMISE_SYMBOL] === true
768
+ (value as Record<symbol, unknown>)[AI_PROMISE_SYMBOL] === true
721
769
  )
722
770
  }
723
771
 
@@ -725,10 +773,8 @@ export function isAIPromise(value: unknown): value is AIPromise<unknown> {
725
773
  * Get the raw AIPromise from a proxied value
726
774
  */
727
775
  export function getRawPromise<T>(value: AIPromise<T>): AIPromise<T> {
728
- if (RAW_PROMISE_SYMBOL in value) {
729
- return (value as any)[RAW_PROMISE_SYMBOL]
730
- }
731
- return value
776
+ const raw = (value as unknown as Record<symbol, AIPromise<T> | undefined>)[RAW_PROMISE_SYMBOL]
777
+ return raw ?? value
732
778
  }
733
779
 
734
780
  // ============================================================================
@@ -745,7 +791,10 @@ export function createTextPromise(prompt: string, options?: FunctionOptions): AI
745
791
  /**
746
792
  * Create an AIPromise for object generation with dynamic schema
747
793
  */
748
- export function createObjectPromise<T = unknown>(prompt: string, options?: FunctionOptions): AIPromise<T> {
794
+ export function createObjectPromise<T = unknown>(
795
+ prompt: string,
796
+ options?: FunctionOptions
797
+ ): AIPromise<T> {
749
798
  return new AIPromise<T>(prompt, { ...options, type: 'object' })
750
799
  }
751
800
 
@@ -759,21 +808,30 @@ export function createListPromise(prompt: string, options?: FunctionOptions): AI
759
808
  /**
760
809
  * Create an AIPromise for multiple lists generation
761
810
  */
762
- export function createListsPromise(prompt: string, options?: FunctionOptions): AIPromise<Record<string, string[]>> {
811
+ export function createListsPromise(
812
+ prompt: string,
813
+ options?: FunctionOptions
814
+ ): AIPromise<Record<string, string[]>> {
763
815
  return new AIPromise<Record<string, string[]>>(prompt, { ...options, type: 'lists' })
764
816
  }
765
817
 
766
818
  /**
767
819
  * Create an AIPromise for boolean/is check
768
820
  */
769
- export function createBooleanPromise(prompt: string, options?: FunctionOptions): AIPromise<boolean> {
821
+ export function createBooleanPromise(
822
+ prompt: string,
823
+ options?: FunctionOptions
824
+ ): AIPromise<boolean> {
770
825
  return new AIPromise<boolean>(prompt, { ...options, type: 'boolean' })
771
826
  }
772
827
 
773
828
  /**
774
829
  * Create an AIPromise for extraction
775
830
  */
776
- export function createExtractPromise<T = unknown>(prompt: string, options?: FunctionOptions): AIPromise<T[]> {
831
+ export function createExtractPromise<T = unknown>(
832
+ prompt: string,
833
+ options?: FunctionOptions
834
+ ): AIPromise<T[]> {
777
835
  return new AIPromise<T[]>(prompt, { ...options, type: 'extract' })
778
836
  }
779
837
 
@@ -820,15 +878,14 @@ export function createAITemplateFunction<T>(
820
878
  type: AIPromiseOptions['type'],
821
879
  baseOptions?: FunctionOptions
822
880
  ): ((strings: TemplateStringsArray, ...values: unknown[]) => AIPromise<T>) &
823
- ((prompt: string, options?: FunctionOptions) => AIPromise<T>) {
824
-
881
+ ((prompt: string, options?: FunctionOptions) => AIPromise<T>) {
825
882
  function templateFn(
826
883
  promptOrStrings: string | TemplateStringsArray,
827
884
  ...args: unknown[]
828
885
  ): AIPromise<T> {
829
886
  let prompt: string
830
887
  let dependencies: Dependency[] = []
831
- let options: FunctionOptions = { ...baseOptions }
888
+ let options: FunctionOptions & { baseSchema?: SimpleSchema } = { ...baseOptions }
832
889
 
833
890
  if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
834
891
  // Tagged template literal
@@ -845,11 +902,21 @@ export function createAITemplateFunction<T>(
845
902
 
846
903
  // If we're in recording mode (inside a .map() callback), capture this operation
847
904
  if (isInRecordingMode()) {
848
- const batchType = type === 'text' ? 'text' : type === 'boolean' ? 'boolean' : type === 'list' ? 'list' : 'object'
849
- captureOperation(prompt, batchType, (options as any).baseSchema, options.system)
905
+ const batchType =
906
+ type === 'text'
907
+ ? 'text'
908
+ : type === 'boolean'
909
+ ? 'boolean'
910
+ : type === 'list'
911
+ ? 'list'
912
+ : 'object'
913
+ captureOperation(prompt, batchType, options.baseSchema, options.system)
850
914
  }
851
915
 
852
- const promise = new AIPromise<T>(prompt, { ...options, type })
916
+ const promise = new AIPromise<T>(prompt, {
917
+ ...options,
918
+ ...(type !== undefined && { type }),
919
+ })
853
920
 
854
921
  // Add dependencies
855
922
  for (const dep of dependencies) {
@@ -859,7 +926,9 @@ export function createAITemplateFunction<T>(
859
926
  return promise
860
927
  }
861
928
 
862
- return templateFn as any
929
+ // Return type matches the declared intersection type
930
+ return templateFn as ((strings: TemplateStringsArray, ...values: unknown[]) => AIPromise<T>) &
931
+ ((prompt: string, options?: FunctionOptions) => AIPromise<T>)
863
932
  }
864
933
 
865
934
  // ============================================================================
@@ -880,8 +949,8 @@ function createStreamingAIPromise<T>(
880
949
  options?: StreamOptions
881
950
  ): StreamingAIPromise<T> {
882
951
  const rawPromise = getRawPromise(promise)
883
- const promiseOptions = (rawPromise as any)._options as AIPromiseOptions
884
- const dependencies = (rawPromise as any)._dependencies as Dependency[]
952
+ const promiseOptions = (rawPromise as unknown as { _options: AIPromiseOptions })._options
953
+ const dependencies = (rawPromise as unknown as { _dependencies: Dependency[] })._dependencies
885
954
 
886
955
  // Result promise state
887
956
  let resultResolve: (value: T) => void
@@ -910,10 +979,7 @@ function createStreamingAIPromise<T>(
910
979
 
911
980
  let finalPrompt = rawPromise.prompt
912
981
  for (const [key, value] of Object.entries(resolvedDeps)) {
913
- finalPrompt = finalPrompt.replace(
914
- new RegExp(`\\$\\{${key}\\}`, 'g'),
915
- String(value)
916
- )
982
+ finalPrompt = finalPrompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value))
917
983
  }
918
984
 
919
985
  return finalPrompt
@@ -921,7 +987,7 @@ function createStreamingAIPromise<T>(
921
987
 
922
988
  // Build schema from accessed properties
923
989
  const buildSchema = (): SimpleSchema => {
924
- return (rawPromise as any)._buildSchema()
990
+ return (rawPromise as unknown as { _buildSchema: () => SimpleSchema })._buildSchema()
925
991
  }
926
992
 
927
993
  // Extract value based on type (same logic as resolve())
@@ -930,16 +996,31 @@ function createStreamingAIPromise<T>(
930
996
  // 2. The type parameter T corresponds to the expected output type for each mode
931
997
  const extractFinalValue = (obj: unknown): T => {
932
998
  let value = obj as T
933
- if (promiseOptions.type === 'text' && typeof value === 'object' && value !== null && 'text' in value) {
999
+ if (
1000
+ promiseOptions.type === 'text' &&
1001
+ typeof value === 'object' &&
1002
+ value !== null &&
1003
+ 'text' in value
1004
+ ) {
934
1005
  value = (value as { text: T }).text
935
- } else if (promiseOptions.type === 'boolean' && typeof value === 'object' && value !== null && 'answer' in value) {
1006
+ } else if (
1007
+ promiseOptions.type === 'boolean' &&
1008
+ typeof value === 'object' &&
1009
+ value !== null &&
1010
+ 'answer' in value
1011
+ ) {
936
1012
  const answer = (value as { answer: string | boolean }).answer
937
1013
  // When type === 'boolean', T is constrained to boolean at the call site.
938
1014
  // TypeScript can't express this dependent relationship, so we use a simple cast.
939
1015
  // Runtime validation: answer is verified to be 'true', 'false', or boolean.
940
1016
  const booleanValue = answer === 'true' || answer === true
941
1017
  value = booleanValue as T
942
- } else if ((promiseOptions.type === 'list' || promiseOptions.type === 'extract') && typeof value === 'object' && value !== null && 'items' in value) {
1018
+ } else if (
1019
+ (promiseOptions.type === 'list' || promiseOptions.type === 'extract') &&
1020
+ typeof value === 'object' &&
1021
+ value !== null &&
1022
+ 'items' in value
1023
+ ) {
943
1024
  value = (value as { items: T }).items
944
1025
  }
945
1026
  return value
@@ -968,10 +1049,12 @@ function createStreamingAIPromise<T>(
968
1049
  const result = await streamText({
969
1050
  model: promiseOptions.model || 'sonnet',
970
1051
  prompt: finalPrompt,
971
- system: promiseOptions.system,
972
- temperature: promiseOptions.temperature,
973
- maxTokens: promiseOptions.maxTokens,
974
- abortSignal: options?.abortSignal,
1052
+ ...(promiseOptions.system !== undefined && { system: promiseOptions.system }),
1053
+ ...(promiseOptions.temperature !== undefined && {
1054
+ temperature: promiseOptions.temperature,
1055
+ }),
1056
+ ...(promiseOptions.maxTokens !== undefined && { maxTokens: promiseOptions.maxTokens }),
1057
+ ...(options?.abortSignal !== undefined && { abortSignal: options.abortSignal }),
975
1058
  })
976
1059
 
977
1060
  let fullText = ''
@@ -1015,10 +1098,12 @@ function createStreamingAIPromise<T>(
1015
1098
  model: promiseOptions.model || 'sonnet',
1016
1099
  schema,
1017
1100
  prompt: finalPrompt,
1018
- system: promiseOptions.system,
1019
- temperature: promiseOptions.temperature,
1020
- maxTokens: promiseOptions.maxTokens,
1021
- abortSignal: options?.abortSignal,
1101
+ ...(promiseOptions.system !== undefined && { system: promiseOptions.system }),
1102
+ ...(promiseOptions.temperature !== undefined && {
1103
+ temperature: promiseOptions.temperature,
1104
+ }),
1105
+ ...(promiseOptions.maxTokens !== undefined && { maxTokens: promiseOptions.maxTokens }),
1106
+ ...(options?.abortSignal !== undefined && { abortSignal: options.abortSignal }),
1022
1107
  })
1023
1108
 
1024
1109
  let lastPartial: Partial<T> = {} as Partial<T>
@@ -1041,7 +1126,8 @@ function createStreamingAIPromise<T>(
1041
1126
  async function* createMainStream(): AsyncGenerator<T extends string ? string : Partial<T>> {
1042
1127
  if (promiseOptions.type === 'text') {
1043
1128
  for await (const chunk of createTextStream()) {
1044
- yield chunk as any
1129
+ // When type is 'text', T is string, so the conditional type resolves to string
1130
+ yield chunk as T extends string ? string : Partial<T>
1045
1131
  }
1046
1132
  } else if (promiseOptions.type === 'list') {
1047
1133
  // For lists, yield new items as they appear
@@ -1049,13 +1135,15 @@ function createStreamingAIPromise<T>(
1049
1135
  for await (const partial of createPartialObjectStream()) {
1050
1136
  const items = (partial as { items?: string[] }).items || []
1051
1137
  for (let i = lastLength; i < items.length; i++) {
1052
- yield items[i] as any
1138
+ // List items are strings, cast to the conditional return type
1139
+ yield items[i] as T extends string ? string : Partial<T>
1053
1140
  }
1054
1141
  lastLength = items.length
1055
1142
  }
1056
1143
  } else {
1057
1144
  for await (const partial of createPartialObjectStream()) {
1058
- yield partial as any
1145
+ // For object types, T is not string, so conditional type resolves to Partial<T>
1146
+ yield partial as T extends string ? string : Partial<T>
1059
1147
  }
1060
1148
  }
1061
1149
  }
@@ -1089,28 +1177,26 @@ function createStreamingAIPromise<T>(
1089
1177
  }
1090
1178
 
1091
1179
  // Create a lazy result promise that starts streaming when accessed
1092
- const lazyResult: Promise<T> & { _started?: boolean } = Object.assign(
1093
- {
1094
- then<TResult1 = T, TResult2 = never>(
1095
- onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
1096
- onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
1097
- ): Promise<TResult1 | TResult2> {
1098
- ensureStreamStarted()
1099
- return resultPromise.then(onfulfilled, onrejected)
1100
- },
1101
- catch<TResult = never>(
1102
- onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
1103
- ): Promise<T | TResult> {
1104
- ensureStreamStarted()
1105
- return resultPromise.catch(onrejected)
1106
- },
1107
- finally(onfinally?: (() => void) | null): Promise<T> {
1108
- ensureStreamStarted()
1109
- return resultPromise.finally(onfinally)
1110
- },
1111
- [Symbol.toStringTag]: 'Promise' as const,
1112
- }
1113
- ) as Promise<T> & { _started?: boolean }
1180
+ const lazyResult: Promise<T> & { _started?: boolean } = Object.assign({
1181
+ then<TResult1 = T, TResult2 = never>(
1182
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
1183
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
1184
+ ): Promise<TResult1 | TResult2> {
1185
+ ensureStreamStarted()
1186
+ return resultPromise.then(onfulfilled, onrejected)
1187
+ },
1188
+ catch<TResult = never>(
1189
+ onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
1190
+ ): Promise<T | TResult> {
1191
+ ensureStreamStarted()
1192
+ return resultPromise.catch(onrejected)
1193
+ },
1194
+ finally(onfinally?: (() => void) | null): Promise<T> {
1195
+ ensureStreamStarted()
1196
+ return resultPromise.finally(onfinally)
1197
+ },
1198
+ [Symbol.toStringTag]: 'Promise' as const,
1199
+ }) as Promise<T> & { _started?: boolean }
1114
1200
 
1115
1201
  // Create the streaming object
1116
1202
  const streamingPromise: StreamingAIPromise<T> = {