ai-functions 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/.turbo/turbo-build.log +1 -4
  2. package/CHANGELOG.md +68 -1
  3. package/README.md +397 -157
  4. package/dist/ai-promise.d.ts +50 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +410 -51
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +54 -837
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts +272 -0
  56. package/dist/budget.d.ts.map +1 -0
  57. package/dist/budget.js +513 -0
  58. package/dist/budget.js.map +1 -0
  59. package/dist/cache.d.ts +295 -0
  60. package/dist/cache.d.ts.map +1 -0
  61. package/dist/cache.js +433 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/context.d.ts +42 -8
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +64 -62
  66. package/dist/context.js.map +1 -1
  67. package/dist/digital-objects-registry.d.ts +229 -0
  68. package/dist/digital-objects-registry.d.ts.map +1 -0
  69. package/dist/digital-objects-registry.js +617 -0
  70. package/dist/digital-objects-registry.js.map +1 -0
  71. package/dist/embeddings.d.ts +2 -2
  72. package/dist/embeddings.d.ts.map +1 -1
  73. package/dist/errors.d.ts +22 -0
  74. package/dist/errors.d.ts.map +1 -0
  75. package/dist/errors.js +35 -0
  76. package/dist/errors.js.map +1 -0
  77. package/dist/eval/runner.d.ts +10 -1
  78. package/dist/eval/runner.d.ts.map +1 -1
  79. package/dist/eval/runner.js +41 -35
  80. package/dist/eval/runner.js.map +1 -1
  81. package/dist/eval-log/in-memory.d.ts +34 -0
  82. package/dist/eval-log/in-memory.d.ts.map +1 -0
  83. package/dist/eval-log/in-memory.js +84 -0
  84. package/dist/eval-log/in-memory.js.map +1 -0
  85. package/dist/eval-log/index.d.ts +29 -0
  86. package/dist/eval-log/index.d.ts.map +1 -0
  87. package/dist/eval-log/index.js +39 -0
  88. package/dist/eval-log/index.js.map +1 -0
  89. package/dist/eval-log/types.d.ts +101 -0
  90. package/dist/eval-log/types.d.ts.map +1 -0
  91. package/dist/eval-log/types.js +16 -0
  92. package/dist/eval-log/types.js.map +1 -0
  93. package/dist/function-registry.d.ts +116 -0
  94. package/dist/function-registry.d.ts.map +1 -0
  95. package/dist/function-registry.js +546 -0
  96. package/dist/function-registry.js.map +1 -0
  97. package/dist/generate.d.ts +9 -3
  98. package/dist/generate.d.ts.map +1 -1
  99. package/dist/generate.js +18 -22
  100. package/dist/generate.js.map +1 -1
  101. package/dist/index.d.ts +35 -20
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +89 -42
  104. package/dist/index.js.map +1 -1
  105. package/dist/logger.d.ts +118 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +187 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/middleware/budget.d.ts +84 -0
  110. package/dist/middleware/budget.d.ts.map +1 -0
  111. package/dist/middleware/budget.js +110 -0
  112. package/dist/middleware/budget.js.map +1 -0
  113. package/dist/middleware/cache.d.ts +103 -0
  114. package/dist/middleware/cache.d.ts.map +1 -0
  115. package/dist/middleware/cache.js +228 -0
  116. package/dist/middleware/cache.js.map +1 -0
  117. package/dist/middleware/embed-cache.d.ts +99 -0
  118. package/dist/middleware/embed-cache.d.ts.map +1 -0
  119. package/dist/middleware/embed-cache.js +128 -0
  120. package/dist/middleware/embed-cache.js.map +1 -0
  121. package/dist/middleware/index.d.ts +11 -0
  122. package/dist/middleware/index.d.ts.map +1 -0
  123. package/dist/middleware/index.js +11 -0
  124. package/dist/middleware/index.js.map +1 -0
  125. package/dist/middleware/trace.d.ts +103 -0
  126. package/dist/middleware/trace.d.ts.map +1 -0
  127. package/dist/middleware/trace.js +176 -0
  128. package/dist/middleware/trace.js.map +1 -0
  129. package/dist/primitives.d.ts +120 -1
  130. package/dist/primitives.d.ts.map +1 -1
  131. package/dist/primitives.js +398 -26
  132. package/dist/primitives.js.map +1 -1
  133. package/dist/retry.d.ts +368 -0
  134. package/dist/retry.d.ts.map +1 -0
  135. package/dist/retry.js +646 -0
  136. package/dist/retry.js.map +1 -0
  137. package/dist/schema.d.ts.map +1 -1
  138. package/dist/schema.js +2 -10
  139. package/dist/schema.js.map +1 -1
  140. package/dist/telemetry.d.ts +128 -0
  141. package/dist/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry.js +285 -0
  143. package/dist/telemetry.js.map +1 -0
  144. package/dist/template.d.ts.map +1 -1
  145. package/dist/template.js +6 -1
  146. package/dist/template.js.map +1 -1
  147. package/dist/tool-orchestration.d.ts +453 -0
  148. package/dist/tool-orchestration.d.ts.map +1 -0
  149. package/dist/tool-orchestration.js +763 -0
  150. package/dist/tool-orchestration.js.map +1 -0
  151. package/dist/type-guards.d.ts +28 -0
  152. package/dist/type-guards.d.ts.map +1 -0
  153. package/dist/type-guards.js +29 -0
  154. package/dist/type-guards.js.map +1 -0
  155. package/dist/types.d.ts +135 -17
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js +36 -1
  158. package/dist/types.js.map +1 -1
  159. package/dist/wrap-for-v3.d.ts +80 -0
  160. package/dist/wrap-for-v3.d.ts.map +1 -0
  161. package/dist/wrap-for-v3.js +89 -0
  162. package/dist/wrap-for-v3.js.map +1 -0
  163. package/examples/00-quickstart.ts +232 -0
  164. package/examples/01-rag-chatbot.ts +212 -0
  165. package/examples/02-multi-agent-research.ts +290 -0
  166. package/examples/03-email-classification.ts +379 -0
  167. package/examples/04-content-moderation.ts +400 -0
  168. package/examples/05-document-extraction.ts +455 -0
  169. package/examples/06-streaming-chat-nextjs.ts +437 -0
  170. package/examples/07-cloudflare-worker.ts +483 -0
  171. package/examples/08-batch-processing.ts +491 -0
  172. package/examples/09-budget-constrained.ts +527 -0
  173. package/examples/10-tool-orchestration.ts +565 -0
  174. package/examples/11-retry-resilience.ts +403 -0
  175. package/examples/12-caching-strategies.ts +422 -0
  176. package/examples/README.md +145 -0
  177. package/package.json +10 -6
  178. package/src/ai-promise.ts +528 -99
  179. package/src/ai-schemas.ts +122 -0
  180. package/src/ai.ts +69 -1153
  181. package/src/batch/anthropic.ts +96 -161
  182. package/src/batch/bedrock.ts +203 -454
  183. package/src/batch/cloudflare.ts +99 -282
  184. package/src/batch/google.ts +91 -297
  185. package/src/batch/index.ts +4 -1
  186. package/src/batch/memory.ts +15 -10
  187. package/src/batch/openai.ts +65 -193
  188. package/src/batch/provider.ts +336 -0
  189. package/src/batch-map.ts +29 -24
  190. package/src/batch-queue.ts +200 -11
  191. package/src/budget.ts +740 -0
  192. package/src/cache.ts +681 -0
  193. package/src/context.ts +122 -76
  194. package/src/digital-objects-registry.ts +750 -0
  195. package/src/errors.ts +37 -0
  196. package/src/eval/runner.ts +63 -38
  197. package/src/eval-log/in-memory.ts +90 -0
  198. package/src/eval-log/index.ts +46 -0
  199. package/src/eval-log/types.ts +110 -0
  200. package/src/function-registry.ts +671 -0
  201. package/src/generate.ts +33 -33
  202. package/src/index.ts +325 -49
  203. package/src/logger.ts +232 -0
  204. package/src/middleware/budget.ts +171 -0
  205. package/src/middleware/cache.ts +299 -0
  206. package/src/middleware/embed-cache.ts +195 -0
  207. package/src/middleware/index.ts +23 -0
  208. package/src/middleware/trace.ts +248 -0
  209. package/src/primitives.ts +589 -62
  210. package/src/retry.ts +902 -0
  211. package/src/schema.ts +8 -17
  212. package/src/telemetry.ts +403 -0
  213. package/src/template.ts +8 -4
  214. package/src/tool-orchestration.ts +1173 -0
  215. package/src/type-guards.ts +31 -0
  216. package/src/types.ts +164 -25
  217. package/src/wrap-for-v3.ts +105 -0
  218. package/test/ai-promise.test.ts +1080 -0
  219. package/test/ai-proxy.test.ts +1 -1
  220. package/test/backward-compat.test.ts +147 -0
  221. package/test/batch-autosubmit-errors.test.ts +610 -0
  222. package/test/batch-blog-posts.test.ts +87 -129
  223. package/test/budget-tracking.test.ts +800 -0
  224. package/test/cache.test.ts +712 -0
  225. package/test/context-isolation.test.ts +687 -0
  226. package/test/core-functions.test.ts +183 -579
  227. package/test/decide.test.ts +154 -322
  228. package/test/define.test.ts +211 -8
  229. package/test/digital-objects-registry.test.ts +760 -0
  230. package/test/embedding-cache-middleware.test.ts +140 -0
  231. package/test/evals/deterministic.eval.test.ts +376 -0
  232. package/test/generate-core.test.ts +140 -229
  233. package/test/implicit-batch.test.ts +22 -65
  234. package/test/json-parse-error-handling.test.ts +463 -0
  235. package/test/retry-policy-integration.test.ts +117 -0
  236. package/test/retry.test.ts +1016 -0
  237. package/test/schema.test.ts +55 -19
  238. package/test/streaming.test.ts +316 -0
  239. package/test/template.test.ts +1164 -0
  240. package/test/tool-orchestration.test.ts +1040 -0
  241. package/test/wrap-for-v3.test.ts +612 -0
  242. package/vitest.config.js +6 -0
  243. package/vitest.config.ts +20 -0
  244. package/dist/rpc/auth.d.ts +0 -69
  245. package/dist/rpc/auth.d.ts.map +0 -1
  246. package/dist/rpc/auth.js +0 -136
  247. package/dist/rpc/auth.js.map +0 -1
  248. package/dist/rpc/client.d.ts +0 -62
  249. package/dist/rpc/client.d.ts.map +0 -1
  250. package/dist/rpc/client.js +0 -103
  251. package/dist/rpc/client.js.map +0 -1
  252. package/dist/rpc/deferred.d.ts +0 -60
  253. package/dist/rpc/deferred.d.ts.map +0 -1
  254. package/dist/rpc/deferred.js +0 -96
  255. package/dist/rpc/deferred.js.map +0 -1
  256. package/dist/rpc/index.d.ts +0 -22
  257. package/dist/rpc/index.d.ts.map +0 -1
  258. package/dist/rpc/index.js +0 -38
  259. package/dist/rpc/index.js.map +0 -1
  260. package/dist/rpc/local.d.ts +0 -42
  261. package/dist/rpc/local.d.ts.map +0 -1
  262. package/dist/rpc/local.js +0 -50
  263. package/dist/rpc/local.js.map +0 -1
  264. package/dist/rpc/server.d.ts +0 -165
  265. package/dist/rpc/server.d.ts.map +0 -1
  266. package/dist/rpc/server.js +0 -405
  267. package/dist/rpc/server.js.map +0 -1
  268. package/dist/rpc/session.d.ts +0 -32
  269. package/dist/rpc/session.d.ts.map +0 -1
  270. package/dist/rpc/session.js +0 -43
  271. package/dist/rpc/session.js.map +0 -1
  272. package/dist/rpc/transport.d.ts +0 -306
  273. package/dist/rpc/transport.d.ts.map +0 -1
  274. package/dist/rpc/transport.js +0 -731
  275. package/dist/rpc/transport.js.map +0 -1
  276. package/src/batch/anthropic.js +0 -256
  277. package/src/batch/bedrock.js +0 -584
  278. package/src/batch/cloudflare.js +0 -287
  279. package/src/batch/google.js +0 -359
  280. package/src/batch/index.js +0 -30
  281. package/src/batch/memory.js +0 -187
  282. package/src/batch/openai.js +0 -402
  283. package/src/eval/index.js +0 -7
  284. package/src/eval/models.js +0 -119
  285. package/src/eval/runner.js +0 -147
  286. package/test/schema.test.js +0 -96
@@ -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
@@ -29,7 +29,7 @@
29
29
  *
30
30
  * @packageDocumentation
31
31
  */
32
- import { generateObject } from './generate.js';
32
+ import { generateObject, streamObject, streamText } from './generate.js';
33
33
  import { isInRecordingMode, getCurrentItemPlaceholder, captureOperation, createBatchMap, BatchMapPromise, } from './batch-map.js';
34
34
  import { getModel } from './context.js';
35
35
  // ============================================================================
@@ -54,7 +54,7 @@ let resolutionScheduled = false;
54
54
  // AIPromise Implementation
55
55
  // ============================================================================
56
56
  /**
57
- * AIPromise - Like capnweb's RpcPromise but for AI functions
57
+ * AIPromise - Promise wrapper for AI functions
58
58
  *
59
59
  * Acts as both a Promise AND a stub that:
60
60
  * - Tracks property accesses for dynamic schema inference
@@ -150,11 +150,39 @@ export class AIPromise {
150
150
  model: this._options.model || 'sonnet',
151
151
  schema,
152
152
  prompt: finalPrompt,
153
- system: this._options.system,
154
- temperature: this._options.temperature,
155
- maxTokens: this._options.maxTokens,
153
+ ...(this._options.system !== undefined && { system: this._options.system }),
154
+ ...(this._options.temperature !== undefined && { temperature: this._options.temperature }),
155
+ ...(this._options.maxTokens !== undefined && { maxTokens: this._options.maxTokens }),
156
156
  });
157
- this._resolvedValue = result.object;
157
+ // Extract the value based on type
158
+ // Type assertions here are safe because:
159
+ // 1. Runtime type checking validates the response structure
160
+ // 2. The type parameter T corresponds to the expected output type for each mode
161
+ let value = result.object;
162
+ if (this._options.type === 'text' &&
163
+ typeof value === 'object' &&
164
+ value !== null &&
165
+ 'text' in value) {
166
+ value = value.text;
167
+ }
168
+ else if (this._options.type === 'boolean' &&
169
+ typeof value === 'object' &&
170
+ value !== null &&
171
+ 'answer' in value) {
172
+ const answer = value.answer;
173
+ // When type === 'boolean', T is constrained to boolean at the call site.
174
+ // TypeScript can't express this dependent relationship, so we use a simple cast.
175
+ // Runtime validation: answer is verified to be 'true', 'false', or boolean.
176
+ const booleanValue = answer === 'true' || answer === true;
177
+ value = booleanValue;
178
+ }
179
+ else if ((this._options.type === 'list' || this._options.type === 'extract') &&
180
+ typeof value === 'object' &&
181
+ value !== null &&
182
+ 'items' in value) {
183
+ value = value.items;
184
+ }
185
+ this._resolvedValue = value;
158
186
  this._isResolved = true;
159
187
  pendingPromises.delete(this);
160
188
  return this._resolvedValue;
@@ -173,6 +201,12 @@ export class AIPromise {
173
201
  switch (this._options.type) {
174
202
  case 'list':
175
203
  return { items: ['List items'] };
204
+ case 'extract':
205
+ return {
206
+ items: [
207
+ 'Array of extracted items as strings - extract ALL matching items from the text',
208
+ ],
209
+ };
176
210
  case 'lists':
177
211
  return { categories: ['Category names'], data: 'JSON object with categorized lists' };
178
212
  case 'boolean':
@@ -247,17 +281,23 @@ export class AIPromise {
247
281
  // Create a wrapper that resolves this promise first, then maps
248
282
  const mapPromise = new BatchMapPromise([], [], {});
249
283
  // Override the resolve to first get the list items
250
- const originalResolve = mapPromise.resolve.bind(mapPromise);
251
- mapPromise.resolve = async () => {
252
- // First, resolve the list
253
- const items = await this.resolve();
254
- if (!Array.isArray(items)) {
255
- throw new Error('Cannot map over non-array result');
256
- }
257
- // Now create the actual batch map with the resolved items
258
- const actualBatchMap = createBatchMap(items, callback);
259
- return actualBatchMap.resolve();
260
- };
284
+ // Type assertion: BatchMapPromise.resolve is a public method that we're replacing
285
+ // with a compatible async function returning Promise<U[]>
286
+ const self = this;
287
+ Object.defineProperty(mapPromise, 'resolve', {
288
+ value: async function () {
289
+ // First, resolve the list
290
+ const items = await self.resolve();
291
+ if (!Array.isArray(items)) {
292
+ throw new Error('Cannot map over non-array result');
293
+ }
294
+ // Now create the actual batch map with the resolved items
295
+ const actualBatchMap = createBatchMap(items, callback);
296
+ return actualBatchMap.resolve();
297
+ },
298
+ writable: true,
299
+ configurable: true,
300
+ });
261
301
  return mapPromise;
262
302
  }
263
303
  /**
@@ -274,26 +314,36 @@ export class AIPromise {
274
314
  */
275
315
  mapImmediate(callback) {
276
316
  const mapPromise = new BatchMapPromise([], [], { immediate: true });
277
- mapPromise.resolve = async () => {
278
- const items = await this.resolve();
279
- if (!Array.isArray(items)) {
280
- throw new Error('Cannot map over non-array result');
281
- }
282
- const actualBatchMap = createBatchMap(items, callback, { immediate: true });
283
- return actualBatchMap.resolve();
284
- };
317
+ const self = this;
318
+ Object.defineProperty(mapPromise, 'resolve', {
319
+ value: async function () {
320
+ const items = await self.resolve();
321
+ if (!Array.isArray(items)) {
322
+ throw new Error('Cannot map over non-array result');
323
+ }
324
+ const actualBatchMap = createBatchMap(items, callback, { immediate: true });
325
+ return actualBatchMap.resolve();
326
+ },
327
+ writable: true,
328
+ configurable: true,
329
+ });
285
330
  return mapPromise;
286
331
  }
287
332
  mapDeferred(callback) {
288
333
  const mapPromise = new BatchMapPromise([], [], { deferred: true });
289
- mapPromise.resolve = async () => {
290
- const items = await this.resolve();
291
- if (!Array.isArray(items)) {
292
- throw new Error('Cannot map over non-array result');
293
- }
294
- const actualBatchMap = createBatchMap(items, callback, { deferred: true });
295
- return actualBatchMap.resolve();
296
- };
334
+ const self = this;
335
+ Object.defineProperty(mapPromise, 'resolve', {
336
+ value: async function () {
337
+ const items = await self.resolve();
338
+ if (!Array.isArray(items)) {
339
+ throw new Error('Cannot map over non-array result');
340
+ }
341
+ const actualBatchMap = createBatchMap(items, callback, { deferred: true });
342
+ return actualBatchMap.resolve();
343
+ },
344
+ writable: true,
345
+ configurable: true,
346
+ });
297
347
  return mapPromise;
298
348
  }
299
349
  /**
@@ -314,6 +364,7 @@ export class AIPromise {
314
364
  }
315
365
  }
316
366
  else {
367
+ // When T is not an array, the conditional type T extends (infer I)[] ? I : T resolves to T
317
368
  await callback(items, 0);
318
369
  }
319
370
  }
@@ -324,13 +375,43 @@ export class AIPromise {
324
375
  const items = await this.resolve();
325
376
  if (Array.isArray(items)) {
326
377
  for (const item of items) {
378
+ // Each array item is the inferred element type I when T extends I[]
327
379
  yield item;
328
380
  }
329
381
  }
330
382
  else {
383
+ // When T is not an array, the item type is T itself
331
384
  yield items;
332
385
  }
333
386
  }
387
+ /**
388
+ * Stream the AI generation - returns chunks as they arrive
389
+ *
390
+ * For text generation, yields string chunks.
391
+ * For object generation, yields partial objects as they build up.
392
+ * For list generation, yields items as they're generated.
393
+ *
394
+ * @example
395
+ * ```ts
396
+ * // Text streaming
397
+ * const stream = write`Write a story`.stream()
398
+ * for await (const chunk of stream.textStream) {
399
+ * process.stdout.write(chunk)
400
+ * }
401
+ *
402
+ * // Object streaming with partial updates
403
+ * const stream = ai`Generate a recipe`.stream()
404
+ * for await (const partial of stream.partialObjectStream) {
405
+ * console.log('Building:', partial)
406
+ * }
407
+ *
408
+ * // Get final result after streaming
409
+ * const finalResult = await stream.result
410
+ * ```
411
+ */
412
+ stream(options) {
413
+ return createStreamingAIPromise(this, options);
414
+ }
334
415
  /**
335
416
  * Promise interface - then()
336
417
  */
@@ -374,7 +455,7 @@ export class AIPromise {
374
455
  // Proxy Handlers
375
456
  // ============================================================================
376
457
  const PROXY_HANDLERS = {
377
- get(target, prop, receiver) {
458
+ get(target, prop, _receiver) {
378
459
  // Handle symbols
379
460
  if (typeof prop === 'symbol') {
380
461
  if (prop === AI_PROMISE_SYMBOL)
@@ -387,14 +468,26 @@ const PROXY_HANDLERS = {
387
468
  }
388
469
  // Handle promise methods
389
470
  if (prop === 'then' || prop === 'catch' || prop === 'finally') {
390
- return target[prop].bind(target);
471
+ const method = target[prop];
472
+ return method?.bind(target);
391
473
  }
392
474
  // Handle AIPromise methods
393
- if (prop === 'map' || prop === 'forEach' || prop === 'resolve') {
394
- return target[prop].bind(target);
475
+ if (prop === 'map' ||
476
+ prop === 'forEach' ||
477
+ prop === 'resolve' ||
478
+ prop === 'stream' ||
479
+ prop === 'addDependency' ||
480
+ prop === 'mapImmediate' ||
481
+ prop === 'mapDeferred') {
482
+ const method = target[prop];
483
+ return method?.bind(target);
395
484
  }
396
485
  // Handle internal properties
397
- if (prop.startsWith('_') || prop === 'prompt' || prop === 'path' || prop === 'isResolved' || prop === 'accessedProps') {
486
+ if (prop.startsWith('_') ||
487
+ prop === 'prompt' ||
488
+ prop === 'path' ||
489
+ prop === 'isResolved' ||
490
+ prop === 'accessedProps') {
398
491
  return target[prop];
399
492
  }
400
493
  // Track property access for schema inference
@@ -418,10 +511,11 @@ const PROXY_HANDLERS = {
418
511
  throw new Error('AIPromise properties cannot be deleted');
419
512
  },
420
513
  // Handle function calls (for chained methods)
421
- apply(target, thisArg, args) {
514
+ apply(target, _thisArg, args) {
422
515
  // If the target is callable (e.g., from a template function), call it
423
- if (typeof target._call === 'function') {
424
- return target._call(...args);
516
+ const call = target['_call'];
517
+ if (typeof call === 'function') {
518
+ return call(...args);
425
519
  }
426
520
  throw new Error('AIPromise is not callable');
427
521
  },
@@ -461,10 +555,12 @@ function analyzeRecordingResult(result, recording) {
461
555
  const aiPromise = getRawPromise(value);
462
556
  // Infer schema from the promise's accessed properties or type
463
557
  if (aiPromise.accessedProps.size > 0) {
464
- schema[key] = Object.fromEntries(Array.from(aiPromise.accessedProps).map(p => [p, `The ${p}`]));
558
+ schema[key] = Object.fromEntries(Array.from(aiPromise.accessedProps).map((p) => [p, `The ${p}`]));
465
559
  }
466
560
  else {
467
- const type = aiPromise._options?.type;
561
+ // Access private _options through type-safe assertion
562
+ const options = aiPromise._options;
563
+ const type = options?.type;
468
564
  if (type === 'boolean') {
469
565
  schema[key] = 'true | false';
470
566
  }
@@ -500,10 +596,8 @@ export function isAIPromise(value) {
500
596
  * Get the raw AIPromise from a proxied value
501
597
  */
502
598
  export function getRawPromise(value) {
503
- if (RAW_PROMISE_SYMBOL in value) {
504
- return value[RAW_PROMISE_SYMBOL];
505
- }
506
- return value;
599
+ const raw = value[RAW_PROMISE_SYMBOL];
600
+ return raw ?? value;
507
601
  }
508
602
  // ============================================================================
509
603
  // Factory Functions
@@ -595,16 +689,281 @@ export function createAITemplateFunction(type, baseOptions) {
595
689
  }
596
690
  // If we're in recording mode (inside a .map() callback), capture this operation
597
691
  if (isInRecordingMode()) {
598
- const batchType = type === 'text' ? 'text' : type === 'boolean' ? 'boolean' : type === 'list' ? 'list' : 'object';
692
+ const batchType = type === 'text'
693
+ ? 'text'
694
+ : type === 'boolean'
695
+ ? 'boolean'
696
+ : type === 'list'
697
+ ? 'list'
698
+ : 'object';
599
699
  captureOperation(prompt, batchType, options.baseSchema, options.system);
600
700
  }
601
- const promise = new AIPromise(prompt, { ...options, type });
701
+ const promise = new AIPromise(prompt, {
702
+ ...options,
703
+ ...(type !== undefined && { type }),
704
+ });
602
705
  // Add dependencies
603
706
  for (const dep of dependencies) {
604
707
  promise.addDependency(dep.promise, dep.path);
605
708
  }
606
709
  return promise;
607
710
  }
711
+ // Return type matches the declared intersection type
608
712
  return templateFn;
609
713
  }
714
+ // ============================================================================
715
+ // Streaming Implementation
716
+ // ============================================================================
717
+ /**
718
+ * Create a streaming wrapper for an AIPromise
719
+ *
720
+ * This function creates a StreamingAIPromise that:
721
+ * - Resolves dependencies before streaming
722
+ * - Streams text or partial objects based on the promise type
723
+ * - Collects the final result as stream is consumed
724
+ * - Supports cancellation via AbortSignal
725
+ */
726
+ function createStreamingAIPromise(promise, options) {
727
+ const rawPromise = getRawPromise(promise);
728
+ const promiseOptions = rawPromise._options;
729
+ const dependencies = rawPromise._dependencies;
730
+ // Result promise state
731
+ let resultResolve;
732
+ let resultReject;
733
+ const resultPromise = new Promise((resolve, reject) => {
734
+ resultResolve = resolve;
735
+ resultReject = reject;
736
+ });
737
+ // Shared state to prevent multiple API calls
738
+ let streamStarted = false;
739
+ let cachedTextChunks = null;
740
+ let cachedPartialObjects = null;
741
+ let streamError = null;
742
+ let finalValue;
743
+ // Resolve dependencies and prepare the final prompt
744
+ const preparePrompt = async () => {
745
+ const resolvedDeps = {};
746
+ for (const dep of dependencies) {
747
+ const value = await dep.promise.resolve();
748
+ const key = dep.path.length > 0 ? dep.path.join('.') : `dep_${dependencies.indexOf(dep)}`;
749
+ resolvedDeps[key] = value;
750
+ }
751
+ let finalPrompt = rawPromise.prompt;
752
+ for (const [key, value] of Object.entries(resolvedDeps)) {
753
+ finalPrompt = finalPrompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value));
754
+ }
755
+ return finalPrompt;
756
+ };
757
+ // Build schema from accessed properties
758
+ const buildSchema = () => {
759
+ return rawPromise._buildSchema();
760
+ };
761
+ // Extract value based on type (same logic as resolve())
762
+ // Type assertions here are safe because:
763
+ // 1. Runtime type checking validates the response structure
764
+ // 2. The type parameter T corresponds to the expected output type for each mode
765
+ const extractFinalValue = (obj) => {
766
+ let value = obj;
767
+ if (promiseOptions.type === 'text' &&
768
+ typeof value === 'object' &&
769
+ value !== null &&
770
+ 'text' in value) {
771
+ value = value.text;
772
+ }
773
+ else if (promiseOptions.type === 'boolean' &&
774
+ typeof value === 'object' &&
775
+ value !== null &&
776
+ 'answer' in value) {
777
+ const answer = value.answer;
778
+ // When type === 'boolean', T is constrained to boolean at the call site.
779
+ // TypeScript can't express this dependent relationship, so we use a simple cast.
780
+ // Runtime validation: answer is verified to be 'true', 'false', or boolean.
781
+ const booleanValue = answer === 'true' || answer === true;
782
+ value = booleanValue;
783
+ }
784
+ else if ((promiseOptions.type === 'list' || promiseOptions.type === 'extract') &&
785
+ typeof value === 'object' &&
786
+ value !== null &&
787
+ 'items' in value) {
788
+ value = value.items;
789
+ }
790
+ return value;
791
+ };
792
+ // Create text stream that collects chunks for result
793
+ async function* createTextStream() {
794
+ if (cachedTextChunks !== null) {
795
+ // Return cached chunks if we already streamed
796
+ for (const chunk of cachedTextChunks) {
797
+ yield chunk;
798
+ }
799
+ return;
800
+ }
801
+ if (streamStarted && streamError) {
802
+ throw streamError;
803
+ }
804
+ streamStarted = true;
805
+ cachedTextChunks = [];
806
+ try {
807
+ const finalPrompt = await preparePrompt();
808
+ const result = await streamText({
809
+ model: promiseOptions.model || 'sonnet',
810
+ prompt: finalPrompt,
811
+ ...(promiseOptions.system !== undefined && { system: promiseOptions.system }),
812
+ ...(promiseOptions.temperature !== undefined && {
813
+ temperature: promiseOptions.temperature,
814
+ }),
815
+ ...(promiseOptions.maxTokens !== undefined && { maxTokens: promiseOptions.maxTokens }),
816
+ ...(options?.abortSignal !== undefined && { abortSignal: options.abortSignal }),
817
+ });
818
+ let fullText = '';
819
+ for await (const chunk of result.textStream) {
820
+ cachedTextChunks.push(chunk);
821
+ fullText += chunk;
822
+ yield chunk;
823
+ }
824
+ finalValue = fullText;
825
+ resultResolve(finalValue);
826
+ }
827
+ catch (error) {
828
+ streamError = error;
829
+ resultReject(error);
830
+ throw error;
831
+ }
832
+ }
833
+ // Create partial object stream that collects objects for result
834
+ async function* createPartialObjectStream() {
835
+ if (cachedPartialObjects !== null) {
836
+ // Return cached partials if we already streamed
837
+ for (const partial of cachedPartialObjects) {
838
+ yield partial;
839
+ }
840
+ return;
841
+ }
842
+ if (streamStarted && streamError) {
843
+ throw streamError;
844
+ }
845
+ streamStarted = true;
846
+ cachedPartialObjects = [];
847
+ try {
848
+ const finalPrompt = await preparePrompt();
849
+ const schema = buildSchema();
850
+ const result = await streamObject({
851
+ model: promiseOptions.model || 'sonnet',
852
+ schema,
853
+ prompt: finalPrompt,
854
+ ...(promiseOptions.system !== undefined && { system: promiseOptions.system }),
855
+ ...(promiseOptions.temperature !== undefined && {
856
+ temperature: promiseOptions.temperature,
857
+ }),
858
+ ...(promiseOptions.maxTokens !== undefined && { maxTokens: promiseOptions.maxTokens }),
859
+ ...(options?.abortSignal !== undefined && { abortSignal: options.abortSignal }),
860
+ });
861
+ let lastPartial = {};
862
+ for await (const partial of result.partialObjectStream) {
863
+ cachedPartialObjects.push(partial);
864
+ lastPartial = partial;
865
+ yield partial;
866
+ }
867
+ finalValue = extractFinalValue(lastPartial);
868
+ resultResolve(finalValue);
869
+ }
870
+ catch (error) {
871
+ streamError = error;
872
+ resultReject(error);
873
+ throw error;
874
+ }
875
+ }
876
+ // Create main stream based on type
877
+ async function* createMainStream() {
878
+ if (promiseOptions.type === 'text') {
879
+ for await (const chunk of createTextStream()) {
880
+ // When type is 'text', T is string, so the conditional type resolves to string
881
+ yield chunk;
882
+ }
883
+ }
884
+ else if (promiseOptions.type === 'list') {
885
+ // For lists, yield new items as they appear
886
+ let lastLength = 0;
887
+ for await (const partial of createPartialObjectStream()) {
888
+ const items = partial.items || [];
889
+ for (let i = lastLength; i < items.length; i++) {
890
+ // List items are strings, cast to the conditional return type
891
+ yield items[i];
892
+ }
893
+ lastLength = items.length;
894
+ }
895
+ }
896
+ else {
897
+ for await (const partial of createPartialObjectStream()) {
898
+ // For object types, T is not string, so conditional type resolves to Partial<T>
899
+ yield partial;
900
+ }
901
+ }
902
+ }
903
+ // Start the stream collection in background if result is awaited
904
+ const ensureStreamStarted = () => {
905
+ if (!streamStarted) {
906
+ // Start consuming the appropriate stream to populate result
907
+ if (promiseOptions.type === 'text') {
908
+ ;
909
+ (async () => {
910
+ try {
911
+ for await (const _ of createTextStream()) {
912
+ // consume
913
+ }
914
+ }
915
+ catch {
916
+ // Error already handled in stream
917
+ }
918
+ })();
919
+ }
920
+ else {
921
+ ;
922
+ (async () => {
923
+ try {
924
+ for await (const _ of createPartialObjectStream()) {
925
+ // consume
926
+ }
927
+ }
928
+ catch {
929
+ // Error already handled in stream
930
+ }
931
+ })();
932
+ }
933
+ }
934
+ };
935
+ // Create a lazy result promise that starts streaming when accessed
936
+ const lazyResult = Object.assign({
937
+ then(onfulfilled, onrejected) {
938
+ ensureStreamStarted();
939
+ return resultPromise.then(onfulfilled, onrejected);
940
+ },
941
+ catch(onrejected) {
942
+ ensureStreamStarted();
943
+ return resultPromise.catch(onrejected);
944
+ },
945
+ finally(onfinally) {
946
+ ensureStreamStarted();
947
+ return resultPromise.finally(onfinally);
948
+ },
949
+ [Symbol.toStringTag]: 'Promise',
950
+ });
951
+ // Create the streaming object
952
+ const streamingPromise = {
953
+ textStream: {
954
+ [Symbol.asyncIterator]: createTextStream,
955
+ },
956
+ partialObjectStream: {
957
+ [Symbol.asyncIterator]: createPartialObjectStream,
958
+ },
959
+ result: lazyResult,
960
+ [Symbol.asyncIterator]: createMainStream,
961
+ then(onfulfilled, onrejected) {
962
+ // If result is awaited before stream consumption, start the stream
963
+ ensureStreamStarted();
964
+ return resultPromise.then(onfulfilled, onrejected);
965
+ },
966
+ };
967
+ return streamingPromise;
968
+ }
610
969
  //# sourceMappingURL=ai-promise.js.map