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/dist/ai-promise.js
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
|
|
@@ -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 -
|
|
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
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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,
|
|
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
|
-
|
|
471
|
+
const method = target[prop];
|
|
472
|
+
return method?.bind(target);
|
|
391
473
|
}
|
|
392
474
|
// Handle AIPromise methods
|
|
393
|
-
if (prop === 'map' ||
|
|
394
|
-
|
|
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('_') ||
|
|
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,
|
|
514
|
+
apply(target, _thisArg, args) {
|
|
422
515
|
// If the target is callable (e.g., from a template function), call it
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
504
|
-
|
|
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'
|
|
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, {
|
|
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
|