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
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
@@ -30,7 +30,7 @@
30
30
  * @packageDocumentation
31
31
  */
32
32
 
33
- import { generateObject } from './generate.js'
33
+ import { generateObject, streamObject, streamText } from './generate.js'
34
34
  import type { SimpleSchema } from './schema.js'
35
35
  import type { FunctionOptions } from './template.js'
36
36
  import {
@@ -42,6 +42,37 @@ import {
42
42
  } from './batch-map.js'
43
43
  import { getModel } from './context.js'
44
44
 
45
+ // ============================================================================
46
+ // Streaming Types
47
+ // ============================================================================
48
+
49
+ /**
50
+ * Options for streaming
51
+ */
52
+ export interface StreamOptions {
53
+ /** Abort signal for cancellation */
54
+ abortSignal?: AbortSignal
55
+ }
56
+
57
+ /**
58
+ * Streaming result wrapper that provides both AsyncIterable interface
59
+ * and access to the final result
60
+ */
61
+ export interface StreamingAIPromise<T>
62
+ extends AsyncIterable<T extends string ? string : Partial<T>> {
63
+ /** Stream of text chunks (for text generation) */
64
+ textStream: AsyncIterable<string>
65
+ /** Stream of partial objects (for object generation) */
66
+ partialObjectStream: AsyncIterable<Partial<T>>
67
+ /** Promise that resolves to the final complete result */
68
+ result: Promise<T>
69
+ /** Promise interface - then() */
70
+ then<TResult1 = T, TResult2 = never>(
71
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
72
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
73
+ ): Promise<TResult1 | TResult2>
74
+ }
75
+
45
76
  // ============================================================================
46
77
  // Types
47
78
  // ============================================================================
@@ -101,7 +132,7 @@ let resolutionScheduled = false
101
132
  // ============================================================================
102
133
 
103
134
  /**
104
- * AIPromise - Like capnweb's RpcPromise but for AI functions
135
+ * AIPromise - Promise wrapper for AI functions
105
136
  *
106
137
  * Acts as both a Promise AND a stub that:
107
138
  * - Tracks property accesses for dynamic schema inference
@@ -203,17 +234,15 @@ export class AIPromise<T> implements PromiseLike<T> {
203
234
  const resolvedDeps: Record<string, unknown> = {}
204
235
  for (const dep of this._dependencies) {
205
236
  const value = await dep.promise.resolve()
206
- 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)}`
207
239
  resolvedDeps[key] = value
208
240
  }
209
241
 
210
242
  // Substitute resolved dependencies into prompt
211
243
  let finalPrompt = this._prompt
212
244
  for (const [key, value] of Object.entries(resolvedDeps)) {
213
- finalPrompt = finalPrompt.replace(
214
- new RegExp(`\\$\\{${key}\\}`, 'g'),
215
- String(value)
216
- )
245
+ finalPrompt = finalPrompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value))
217
246
  }
218
247
 
219
248
  // Build schema from accessed properties
@@ -224,19 +253,41 @@ export class AIPromise<T> implements PromiseLike<T> {
224
253
  model: this._options.model || 'sonnet',
225
254
  schema,
226
255
  prompt: finalPrompt,
227
- system: this._options.system,
228
- temperature: this._options.temperature,
229
- 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 }),
230
259
  })
231
260
 
232
261
  // Extract the value based on type
262
+ // Type assertions here are safe because:
263
+ // 1. Runtime type checking validates the response structure
264
+ // 2. The type parameter T corresponds to the expected output type for each mode
233
265
  let value = result.object as T
234
- 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
+ ) {
235
272
  value = (value as { text: T }).text
236
- } else if (this._options.type === 'boolean' && typeof value === 'object' && value !== null && 'answer' in value) {
237
- const answer = (value as { answer: string }).answer
238
- value = (answer === 'true' || answer === true) as unknown as T
239
- } else if ((this._options.type === 'list' || this._options.type === 'extract') && typeof value === 'object' && value !== null && 'items' in value) {
273
+ } else if (
274
+ this._options.type === 'boolean' &&
275
+ typeof value === 'object' &&
276
+ value !== null &&
277
+ 'answer' in value
278
+ ) {
279
+ const answer = (value as { answer: string | boolean }).answer
280
+ // When type === 'boolean', T is constrained to boolean at the call site.
281
+ // TypeScript can't express this dependent relationship, so we use a simple cast.
282
+ // Runtime validation: answer is verified to be 'true', 'false', or boolean.
283
+ const booleanValue = answer === 'true' || answer === true
284
+ value = booleanValue as T
285
+ } else if (
286
+ (this._options.type === 'list' || this._options.type === 'extract') &&
287
+ typeof value === 'object' &&
288
+ value !== null &&
289
+ 'items' in value
290
+ ) {
240
291
  value = (value as { items: T }).items
241
292
  }
242
293
 
@@ -264,7 +315,11 @@ export class AIPromise<T> implements PromiseLike<T> {
264
315
  case 'list':
265
316
  return { items: ['List items'] }
266
317
  case 'extract':
267
- return { items: ['Extracted items'] }
318
+ return {
319
+ items: [
320
+ 'Array of extracted items as strings - extract ALL matching items from the text',
321
+ ],
322
+ }
268
323
  case 'lists':
269
324
  return { categories: ['Category names'], data: 'JSON object with categorized lists' }
270
325
  case 'boolean':
@@ -343,30 +398,31 @@ export class AIPromise<T> implements PromiseLike<T> {
343
398
  * }))
344
399
  * ```
345
400
  */
346
- map<U>(
347
- callback: (item: T extends (infer I)[] ? I : T, index: number) => U
348
- ): BatchMapPromise<U> {
401
+ map<U>(callback: (item: T extends (infer I)[] ? I : T, index: number) => U): BatchMapPromise<U> {
349
402
  // Create a wrapper that resolves this promise first, then maps
350
403
  const mapPromise = new BatchMapPromise<U>([], [], {})
351
404
 
352
405
  // Override the resolve to first get the list items
353
- const originalResolve = mapPromise.resolve.bind(mapPromise)
354
- ;(mapPromise as any).resolve = async () => {
355
- // First, resolve the list
356
- const items = await this.resolve()
357
-
358
- if (!Array.isArray(items)) {
359
- throw new Error('Cannot map over non-array result')
360
- }
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
+ }
361
417
 
362
- // Now create the actual batch map with the resolved items
363
- const actualBatchMap = createBatchMap(
364
- items as (T extends (infer I)[] ? I : T)[],
365
- callback
366
- )
418
+ // Now create the actual batch map with the resolved items
419
+ const actualBatchMap = createBatchMap(items as (T extends (infer I)[] ? I : T)[], callback)
367
420
 
368
- return actualBatchMap.resolve()
369
- }
421
+ return actualBatchMap.resolve()
422
+ },
423
+ writable: true,
424
+ configurable: true,
425
+ })
370
426
 
371
427
  return mapPromise
372
428
  }
@@ -387,19 +443,24 @@ export class AIPromise<T> implements PromiseLike<T> {
387
443
  callback: (item: T extends (infer I)[] ? I : T, index: number) => U
388
444
  ): BatchMapPromise<U> {
389
445
  const mapPromise = new BatchMapPromise<U>([], [], { immediate: true })
446
+ const self = this
390
447
 
391
- ;(mapPromise as any).resolve = async () => {
392
- const items = await this.resolve()
393
- if (!Array.isArray(items)) {
394
- throw new Error('Cannot map over non-array result')
395
- }
396
- const actualBatchMap = createBatchMap(
397
- items as (T extends (infer I)[] ? I : T)[],
398
- callback,
399
- { immediate: true }
400
- )
401
- return actualBatchMap.resolve()
402
- }
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
+ })
403
464
 
404
465
  return mapPromise
405
466
  }
@@ -408,19 +469,24 @@ export class AIPromise<T> implements PromiseLike<T> {
408
469
  callback: (item: T extends (infer I)[] ? I : T, index: number) => U
409
470
  ): BatchMapPromise<U> {
410
471
  const mapPromise = new BatchMapPromise<U>([], [], { deferred: true })
472
+ const self = this
411
473
 
412
- ;(mapPromise as any).resolve = async () => {
413
- const items = await this.resolve()
414
- if (!Array.isArray(items)) {
415
- throw new Error('Cannot map over non-array result')
416
- }
417
- const actualBatchMap = createBatchMap(
418
- items as (T extends (infer I)[] ? I : T)[],
419
- callback,
420
- { deferred: true }
421
- )
422
- return actualBatchMap.resolve()
423
- }
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
+ })
424
490
 
425
491
  return mapPromise
426
492
  }
@@ -444,7 +510,8 @@ export class AIPromise<T> implements PromiseLike<T> {
444
510
  await callback(items[i], i)
445
511
  }
446
512
  } else {
447
- 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)
448
515
  }
449
516
  }
450
517
 
@@ -455,13 +522,44 @@ export class AIPromise<T> implements PromiseLike<T> {
455
522
  const items = await this.resolve()
456
523
  if (Array.isArray(items)) {
457
524
  for (const item of items) {
458
- 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
459
527
  }
460
528
  } else {
461
- 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
462
531
  }
463
532
  }
464
533
 
534
+ /**
535
+ * Stream the AI generation - returns chunks as they arrive
536
+ *
537
+ * For text generation, yields string chunks.
538
+ * For object generation, yields partial objects as they build up.
539
+ * For list generation, yields items as they're generated.
540
+ *
541
+ * @example
542
+ * ```ts
543
+ * // Text streaming
544
+ * const stream = write`Write a story`.stream()
545
+ * for await (const chunk of stream.textStream) {
546
+ * process.stdout.write(chunk)
547
+ * }
548
+ *
549
+ * // Object streaming with partial updates
550
+ * const stream = ai`Generate a recipe`.stream()
551
+ * for await (const partial of stream.partialObjectStream) {
552
+ * console.log('Building:', partial)
553
+ * }
554
+ *
555
+ * // Get final result after streaming
556
+ * const finalResult = await stream.result
557
+ * ```
558
+ */
559
+ stream(options?: StreamOptions): StreamingAIPromise<T> {
560
+ return createStreamingAIPromise(this, options)
561
+ }
562
+
465
563
  /**
466
564
  * Promise interface - then()
467
565
  */
@@ -517,28 +615,44 @@ export class AIPromise<T> implements PromiseLike<T> {
517
615
  // ============================================================================
518
616
 
519
617
  const PROXY_HANDLERS: ProxyHandler<AIPromise<unknown>> = {
520
- get(target, prop: string | symbol, receiver) {
618
+ get(target, prop: string | symbol, _receiver) {
521
619
  // Handle symbols
522
620
  if (typeof prop === 'symbol') {
523
621
  if (prop === AI_PROMISE_SYMBOL) return true
524
622
  if (prop === RAW_PROMISE_SYMBOL) return target
525
623
  if (prop === Symbol.asyncIterator) return target[Symbol.asyncIterator].bind(target)
526
- return (target as any)[prop]
624
+ return (target as unknown as Record<symbol, unknown>)[prop]
527
625
  }
528
626
 
529
627
  // Handle promise methods
530
628
  if (prop === 'then' || prop === 'catch' || prop === 'finally') {
531
- 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)
532
631
  }
533
632
 
534
633
  // Handle AIPromise methods
535
- if (prop === 'map' || prop === 'forEach' || prop === 'resolve') {
536
- 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)
537
645
  }
538
646
 
539
647
  // Handle internal properties
540
- if (prop.startsWith('_') || prop === 'prompt' || prop === 'path' || prop === 'isResolved' || prop === 'accessedProps') {
541
- 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]
542
656
  }
543
657
 
544
658
  // Track property access for schema inference
@@ -550,14 +664,11 @@ const PROXY_HANDLERS: ProxyHandler<AIPromise<unknown>> = {
550
664
  }
551
665
 
552
666
  // Return a new AIPromise for the property path
553
- return new AIPromise<unknown>(
554
- target.prompt,
555
- {
556
- ...target['_options'],
557
- parent: target,
558
- propertyPath: [...target.path, prop],
559
- }
560
- )
667
+ return new AIPromise<unknown>(target.prompt, {
668
+ ...target['_options'],
669
+ parent: target,
670
+ propertyPath: [...target.path, prop],
671
+ })
561
672
  },
562
673
 
563
674
  // Prevent mutation
@@ -570,10 +681,11 @@ const PROXY_HANDLERS: ProxyHandler<AIPromise<unknown>> = {
570
681
  },
571
682
 
572
683
  // Handle function calls (for chained methods)
573
- apply(target, thisArg, args) {
684
+ apply(target, _thisArg, args) {
574
685
  // If the target is callable (e.g., from a template function), call it
575
- if (typeof (target as any)._call === 'function') {
576
- 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)
577
689
  }
578
690
  throw new Error('AIPromise is not callable')
579
691
  },
@@ -619,10 +731,12 @@ function analyzeRecordingResult(result: unknown, recording: MapRecording): Simpl
619
731
  // Infer schema from the promise's accessed properties or type
620
732
  if (aiPromise.accessedProps.size > 0) {
621
733
  schema[key] = Object.fromEntries(
622
- Array.from(aiPromise.accessedProps).map(p => [p, `The ${p}`])
734
+ Array.from(aiPromise.accessedProps).map((p) => [p, `The ${p}`])
623
735
  )
624
736
  } else {
625
- 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
626
740
  if (type === 'boolean') {
627
741
  schema[key] = 'true | false'
628
742
  } else if (type === 'list') {
@@ -651,7 +765,7 @@ export function isAIPromise(value: unknown): value is AIPromise<unknown> {
651
765
  value !== null &&
652
766
  typeof value === 'object' &&
653
767
  AI_PROMISE_SYMBOL in value &&
654
- (value as any)[AI_PROMISE_SYMBOL] === true
768
+ (value as Record<symbol, unknown>)[AI_PROMISE_SYMBOL] === true
655
769
  )
656
770
  }
657
771
 
@@ -659,10 +773,8 @@ export function isAIPromise(value: unknown): value is AIPromise<unknown> {
659
773
  * Get the raw AIPromise from a proxied value
660
774
  */
661
775
  export function getRawPromise<T>(value: AIPromise<T>): AIPromise<T> {
662
- if (RAW_PROMISE_SYMBOL in value) {
663
- return (value as any)[RAW_PROMISE_SYMBOL]
664
- }
665
- return value
776
+ const raw = (value as unknown as Record<symbol, AIPromise<T> | undefined>)[RAW_PROMISE_SYMBOL]
777
+ return raw ?? value
666
778
  }
667
779
 
668
780
  // ============================================================================
@@ -679,7 +791,10 @@ export function createTextPromise(prompt: string, options?: FunctionOptions): AI
679
791
  /**
680
792
  * Create an AIPromise for object generation with dynamic schema
681
793
  */
682
- 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> {
683
798
  return new AIPromise<T>(prompt, { ...options, type: 'object' })
684
799
  }
685
800
 
@@ -693,21 +808,30 @@ export function createListPromise(prompt: string, options?: FunctionOptions): AI
693
808
  /**
694
809
  * Create an AIPromise for multiple lists generation
695
810
  */
696
- 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[]>> {
697
815
  return new AIPromise<Record<string, string[]>>(prompt, { ...options, type: 'lists' })
698
816
  }
699
817
 
700
818
  /**
701
819
  * Create an AIPromise for boolean/is check
702
820
  */
703
- export function createBooleanPromise(prompt: string, options?: FunctionOptions): AIPromise<boolean> {
821
+ export function createBooleanPromise(
822
+ prompt: string,
823
+ options?: FunctionOptions
824
+ ): AIPromise<boolean> {
704
825
  return new AIPromise<boolean>(prompt, { ...options, type: 'boolean' })
705
826
  }
706
827
 
707
828
  /**
708
829
  * Create an AIPromise for extraction
709
830
  */
710
- 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[]> {
711
835
  return new AIPromise<T[]>(prompt, { ...options, type: 'extract' })
712
836
  }
713
837
 
@@ -754,15 +878,14 @@ export function createAITemplateFunction<T>(
754
878
  type: AIPromiseOptions['type'],
755
879
  baseOptions?: FunctionOptions
756
880
  ): ((strings: TemplateStringsArray, ...values: unknown[]) => AIPromise<T>) &
757
- ((prompt: string, options?: FunctionOptions) => AIPromise<T>) {
758
-
881
+ ((prompt: string, options?: FunctionOptions) => AIPromise<T>) {
759
882
  function templateFn(
760
883
  promptOrStrings: string | TemplateStringsArray,
761
884
  ...args: unknown[]
762
885
  ): AIPromise<T> {
763
886
  let prompt: string
764
887
  let dependencies: Dependency[] = []
765
- let options: FunctionOptions = { ...baseOptions }
888
+ let options: FunctionOptions & { baseSchema?: SimpleSchema } = { ...baseOptions }
766
889
 
767
890
  if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
768
891
  // Tagged template literal
@@ -779,11 +902,21 @@ export function createAITemplateFunction<T>(
779
902
 
780
903
  // If we're in recording mode (inside a .map() callback), capture this operation
781
904
  if (isInRecordingMode()) {
782
- const batchType = type === 'text' ? 'text' : type === 'boolean' ? 'boolean' : type === 'list' ? 'list' : 'object'
783
- 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)
784
914
  }
785
915
 
786
- const promise = new AIPromise<T>(prompt, { ...options, type })
916
+ const promise = new AIPromise<T>(prompt, {
917
+ ...options,
918
+ ...(type !== undefined && { type }),
919
+ })
787
920
 
788
921
  // Add dependencies
789
922
  for (const dep of dependencies) {
@@ -793,5 +926,301 @@ export function createAITemplateFunction<T>(
793
926
  return promise
794
927
  }
795
928
 
796
- 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>)
932
+ }
933
+
934
+ // ============================================================================
935
+ // Streaming Implementation
936
+ // ============================================================================
937
+
938
+ /**
939
+ * Create a streaming wrapper for an AIPromise
940
+ *
941
+ * This function creates a StreamingAIPromise that:
942
+ * - Resolves dependencies before streaming
943
+ * - Streams text or partial objects based on the promise type
944
+ * - Collects the final result as stream is consumed
945
+ * - Supports cancellation via AbortSignal
946
+ */
947
+ function createStreamingAIPromise<T>(
948
+ promise: AIPromise<T>,
949
+ options?: StreamOptions
950
+ ): StreamingAIPromise<T> {
951
+ const rawPromise = getRawPromise(promise)
952
+ const promiseOptions = (rawPromise as unknown as { _options: AIPromiseOptions })._options
953
+ const dependencies = (rawPromise as unknown as { _dependencies: Dependency[] })._dependencies
954
+
955
+ // Result promise state
956
+ let resultResolve: (value: T) => void
957
+ let resultReject: (error: unknown) => void
958
+ const resultPromise = new Promise<T>((resolve, reject) => {
959
+ resultResolve = resolve
960
+ resultReject = reject
961
+ })
962
+
963
+ // Shared state to prevent multiple API calls
964
+ let streamStarted = false
965
+ let cachedTextChunks: string[] | null = null
966
+ let cachedPartialObjects: Partial<T>[] | null = null
967
+ let streamError: unknown = null
968
+ let finalValue: T | undefined
969
+
970
+ // Resolve dependencies and prepare the final prompt
971
+ const preparePrompt = async (): Promise<string> => {
972
+ const resolvedDeps: Record<string, unknown> = {}
973
+
974
+ for (const dep of dependencies) {
975
+ const value = await dep.promise.resolve()
976
+ const key = dep.path.length > 0 ? dep.path.join('.') : `dep_${dependencies.indexOf(dep)}`
977
+ resolvedDeps[key] = value
978
+ }
979
+
980
+ let finalPrompt = rawPromise.prompt
981
+ for (const [key, value] of Object.entries(resolvedDeps)) {
982
+ finalPrompt = finalPrompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value))
983
+ }
984
+
985
+ return finalPrompt
986
+ }
987
+
988
+ // Build schema from accessed properties
989
+ const buildSchema = (): SimpleSchema => {
990
+ return (rawPromise as unknown as { _buildSchema: () => SimpleSchema })._buildSchema()
991
+ }
992
+
993
+ // Extract value based on type (same logic as resolve())
994
+ // Type assertions here are safe because:
995
+ // 1. Runtime type checking validates the response structure
996
+ // 2. The type parameter T corresponds to the expected output type for each mode
997
+ const extractFinalValue = (obj: unknown): T => {
998
+ let value = obj as T
999
+ if (
1000
+ promiseOptions.type === 'text' &&
1001
+ typeof value === 'object' &&
1002
+ value !== null &&
1003
+ 'text' in value
1004
+ ) {
1005
+ value = (value as { text: T }).text
1006
+ } else if (
1007
+ promiseOptions.type === 'boolean' &&
1008
+ typeof value === 'object' &&
1009
+ value !== null &&
1010
+ 'answer' in value
1011
+ ) {
1012
+ const answer = (value as { answer: string | boolean }).answer
1013
+ // When type === 'boolean', T is constrained to boolean at the call site.
1014
+ // TypeScript can't express this dependent relationship, so we use a simple cast.
1015
+ // Runtime validation: answer is verified to be 'true', 'false', or boolean.
1016
+ const booleanValue = answer === 'true' || answer === true
1017
+ value = booleanValue as T
1018
+ } else if (
1019
+ (promiseOptions.type === 'list' || promiseOptions.type === 'extract') &&
1020
+ typeof value === 'object' &&
1021
+ value !== null &&
1022
+ 'items' in value
1023
+ ) {
1024
+ value = (value as { items: T }).items
1025
+ }
1026
+ return value
1027
+ }
1028
+
1029
+ // Create text stream that collects chunks for result
1030
+ async function* createTextStream(): AsyncGenerator<string> {
1031
+ if (cachedTextChunks !== null) {
1032
+ // Return cached chunks if we already streamed
1033
+ for (const chunk of cachedTextChunks) {
1034
+ yield chunk
1035
+ }
1036
+ return
1037
+ }
1038
+
1039
+ if (streamStarted && streamError) {
1040
+ throw streamError
1041
+ }
1042
+
1043
+ streamStarted = true
1044
+ cachedTextChunks = []
1045
+
1046
+ try {
1047
+ const finalPrompt = await preparePrompt()
1048
+
1049
+ const result = await streamText({
1050
+ model: promiseOptions.model || 'sonnet',
1051
+ prompt: finalPrompt,
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 }),
1058
+ })
1059
+
1060
+ let fullText = ''
1061
+ for await (const chunk of result.textStream) {
1062
+ cachedTextChunks!.push(chunk)
1063
+ fullText += chunk
1064
+ yield chunk
1065
+ }
1066
+
1067
+ finalValue = fullText as T
1068
+ resultResolve!(finalValue)
1069
+ } catch (error) {
1070
+ streamError = error
1071
+ resultReject!(error)
1072
+ throw error
1073
+ }
1074
+ }
1075
+
1076
+ // Create partial object stream that collects objects for result
1077
+ async function* createPartialObjectStream(): AsyncGenerator<Partial<T>> {
1078
+ if (cachedPartialObjects !== null) {
1079
+ // Return cached partials if we already streamed
1080
+ for (const partial of cachedPartialObjects) {
1081
+ yield partial
1082
+ }
1083
+ return
1084
+ }
1085
+
1086
+ if (streamStarted && streamError) {
1087
+ throw streamError
1088
+ }
1089
+
1090
+ streamStarted = true
1091
+ cachedPartialObjects = []
1092
+
1093
+ try {
1094
+ const finalPrompt = await preparePrompt()
1095
+ const schema = buildSchema()
1096
+
1097
+ const result = await streamObject({
1098
+ model: promiseOptions.model || 'sonnet',
1099
+ schema,
1100
+ prompt: finalPrompt,
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 }),
1107
+ })
1108
+
1109
+ let lastPartial: Partial<T> = {} as Partial<T>
1110
+ for await (const partial of result.partialObjectStream) {
1111
+ cachedPartialObjects!.push(partial as Partial<T>)
1112
+ lastPartial = partial as Partial<T>
1113
+ yield partial as Partial<T>
1114
+ }
1115
+
1116
+ finalValue = extractFinalValue(lastPartial)
1117
+ resultResolve!(finalValue)
1118
+ } catch (error) {
1119
+ streamError = error
1120
+ resultReject!(error)
1121
+ throw error
1122
+ }
1123
+ }
1124
+
1125
+ // Create main stream based on type
1126
+ async function* createMainStream(): AsyncGenerator<T extends string ? string : Partial<T>> {
1127
+ if (promiseOptions.type === 'text') {
1128
+ for await (const chunk of createTextStream()) {
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>
1131
+ }
1132
+ } else if (promiseOptions.type === 'list') {
1133
+ // For lists, yield new items as they appear
1134
+ let lastLength = 0
1135
+ for await (const partial of createPartialObjectStream()) {
1136
+ const items = (partial as { items?: string[] }).items || []
1137
+ for (let i = lastLength; i < items.length; i++) {
1138
+ // List items are strings, cast to the conditional return type
1139
+ yield items[i] as T extends string ? string : Partial<T>
1140
+ }
1141
+ lastLength = items.length
1142
+ }
1143
+ } else {
1144
+ for await (const partial of createPartialObjectStream()) {
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>
1147
+ }
1148
+ }
1149
+ }
1150
+
1151
+ // Start the stream collection in background if result is awaited
1152
+ const ensureStreamStarted = (): void => {
1153
+ if (!streamStarted) {
1154
+ // Start consuming the appropriate stream to populate result
1155
+ if (promiseOptions.type === 'text') {
1156
+ ;(async () => {
1157
+ try {
1158
+ for await (const _ of createTextStream()) {
1159
+ // consume
1160
+ }
1161
+ } catch {
1162
+ // Error already handled in stream
1163
+ }
1164
+ })()
1165
+ } else {
1166
+ ;(async () => {
1167
+ try {
1168
+ for await (const _ of createPartialObjectStream()) {
1169
+ // consume
1170
+ }
1171
+ } catch {
1172
+ // Error already handled in stream
1173
+ }
1174
+ })()
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ // Create a lazy result promise that starts streaming when accessed
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 }
1200
+
1201
+ // Create the streaming object
1202
+ const streamingPromise: StreamingAIPromise<T> = {
1203
+ textStream: {
1204
+ [Symbol.asyncIterator]: createTextStream,
1205
+ },
1206
+
1207
+ partialObjectStream: {
1208
+ [Symbol.asyncIterator]: createPartialObjectStream,
1209
+ },
1210
+
1211
+ result: lazyResult,
1212
+
1213
+ [Symbol.asyncIterator]: createMainStream,
1214
+
1215
+ then<TResult1 = T, TResult2 = never>(
1216
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
1217
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
1218
+ ): Promise<TResult1 | TResult2> {
1219
+ // If result is awaited before stream consumption, start the stream
1220
+ ensureStreamStarted()
1221
+ return resultPromise.then(onfulfilled, onrejected)
1222
+ },
1223
+ }
1224
+
1225
+ return streamingPromise
797
1226
  }