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.
- package/.turbo/turbo-build.log +1 -4
- package/CHANGELOG.md +68 -1
- package/README.md +397 -157
- package/dist/ai-promise.d.ts +50 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +410 -51
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +54 -837
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts +272 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +513 -0
- package/dist/budget.js.map +1 -0
- package/dist/cache.d.ts +295 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +433 -0
- package/dist/cache.js.map +1 -0
- package/dist/context.d.ts +42 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +10 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +116 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +546 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -22
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +35 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -42
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +368 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +646 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -10
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +453 -0
- package/dist/tool-orchestration.d.ts.map +1 -0
- package/dist/tool-orchestration.js +763 -0
- package/dist/tool-orchestration.js.map +1 -0
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +135 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +10 -6
- package/src/ai-promise.ts +528 -99
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +69 -1153
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +740 -0
- package/src/cache.ts +681 -0
- package/src/context.ts +122 -76
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +63 -38
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +671 -0
- package/src/generate.ts +33 -33
- package/src/index.ts +325 -49
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +902 -0
- package/src/schema.ts +8 -17
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +1173 -0
- package/src/type-guards.ts +31 -0
- package/src/types.ts +164 -25
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/backward-compat.test.ts +147 -0
- package/test/batch-autosubmit-errors.test.ts +610 -0
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/budget-tracking.test.ts +800 -0
- package/test/cache.test.ts +712 -0
- package/test/context-isolation.test.ts +687 -0
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/evals/deterministic.eval.test.ts +376 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/json-parse-error-handling.test.ts +463 -0
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/retry.test.ts +1016 -0
- package/test/schema.test.ts +55 -19
- package/test/streaming.test.ts +316 -0
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +1040 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
package/src/ai-promise.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AIPromise -
|
|
2
|
+
* AIPromise - Promise pipelining for AI functions
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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 -
|
|
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 =
|
|
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 (
|
|
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 (
|
|
237
|
-
|
|
238
|
-
value
|
|
239
|
-
|
|
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 {
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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 (
|
|
536
|
-
|
|
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 (
|
|
541
|
-
|
|
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
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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,
|
|
684
|
+
apply(target, _thisArg, args) {
|
|
574
685
|
// If the target is callable (e.g., from a template function), call it
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
663
|
-
|
|
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>(
|
|
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(
|
|
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(
|
|
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>(
|
|
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
|
-
|
|
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 =
|
|
783
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
}
|