ai-functions 0.3.0 → 0.4.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 +5 -0
- package/.turbo/turbo-test.log +105 -0
- package/README.md +190 -86
- package/TODO.md +138 -0
- package/dist/ai-promise.d.ts +219 -0
- package/dist/ai-promise.d.ts.map +1 -0
- package/dist/ai-promise.js +610 -0
- package/dist/ai-promise.js.map +1 -0
- package/dist/ai.d.ts +285 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +842 -0
- package/dist/ai.js.map +1 -0
- package/dist/batch/anthropic.d.ts +23 -0
- package/dist/batch/anthropic.d.ts.map +1 -0
- package/dist/batch/anthropic.js +257 -0
- package/dist/batch/anthropic.js.map +1 -0
- package/dist/batch/bedrock.d.ts +64 -0
- package/dist/batch/bedrock.d.ts.map +1 -0
- package/dist/batch/bedrock.js +586 -0
- package/dist/batch/bedrock.js.map +1 -0
- package/dist/batch/cloudflare.d.ts +37 -0
- package/dist/batch/cloudflare.d.ts.map +1 -0
- package/dist/batch/cloudflare.js +289 -0
- package/dist/batch/cloudflare.js.map +1 -0
- package/dist/batch/google.d.ts +41 -0
- package/dist/batch/google.d.ts.map +1 -0
- package/dist/batch/google.js +360 -0
- package/dist/batch/google.js.map +1 -0
- package/dist/batch/index.d.ts +31 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +31 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/batch/memory.d.ts +44 -0
- package/dist/batch/memory.d.ts.map +1 -0
- package/dist/batch/memory.js +188 -0
- package/dist/batch/memory.js.map +1 -0
- package/dist/batch/openai.d.ts +37 -0
- package/dist/batch/openai.d.ts.map +1 -0
- package/dist/batch/openai.js +403 -0
- package/dist/batch/openai.js.map +1 -0
- package/dist/batch-map.d.ts +125 -0
- package/dist/batch-map.d.ts.map +1 -0
- package/dist/batch-map.js +406 -0
- package/dist/batch-map.js.map +1 -0
- package/dist/batch-queue.d.ts +273 -0
- package/dist/batch-queue.d.ts.map +1 -0
- package/dist/batch-queue.js +271 -0
- package/dist/batch-queue.js.map +1 -0
- package/dist/context.d.ts +133 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +267 -0
- package/dist/context.js.map +1 -0
- package/dist/embeddings.d.ts +123 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +170 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/eval/index.d.ts +8 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +8 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/models.d.ts +66 -0
- package/dist/eval/models.d.ts.map +1 -0
- package/dist/eval/models.js +120 -0
- package/dist/eval/models.js.map +1 -0
- package/dist/eval/runner.d.ts +64 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/runner.js +148 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/generate.d.ts +168 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +174 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -52
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +292 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +471 -0
- package/dist/primitives.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +9 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +9 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/schema.d.ts +54 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +109 -0
- package/dist/schema.js.map +1 -0
- package/dist/template.d.ts +73 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +129 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +474 -106
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -8
- package/dist/types.js.map +1 -1
- package/evalite.config.ts +19 -0
- package/evals/README.md +212 -0
- package/evals/classification.eval.ts +108 -0
- package/evals/marketing.eval.ts +370 -0
- package/evals/math.eval.ts +94 -0
- package/evals/run-evals.ts +166 -0
- package/evals/structured-output.eval.ts +143 -0
- package/evals/writing.eval.ts +117 -0
- package/examples/batch-blog-posts.ts +160 -0
- package/package.json +57 -57
- package/src/ai-promise.ts +784 -0
- package/src/ai.ts +1183 -0
- package/src/batch/anthropic.ts +375 -0
- package/src/batch/bedrock.ts +801 -0
- package/src/batch/cloudflare.ts +421 -0
- package/src/batch/google.ts +491 -0
- package/src/batch/index.ts +31 -0
- package/src/batch/memory.ts +253 -0
- package/src/batch/openai.ts +557 -0
- package/src/batch-map.ts +534 -0
- package/src/batch-queue.ts +493 -0
- package/src/context.ts +332 -0
- package/src/embeddings.ts +244 -0
- package/src/eval/index.ts +8 -0
- package/src/eval/models.ts +158 -0
- package/src/eval/runner.ts +217 -0
- package/src/generate.ts +245 -0
- package/src/index.ts +154 -0
- package/src/primitives.ts +612 -0
- package/src/providers/cloudflare.ts +15 -0
- package/src/providers/index.ts +14 -0
- package/src/schema.ts +147 -0
- package/src/template.ts +209 -0
- package/src/types.ts +540 -0
- package/test/README.md +105 -0
- package/test/ai-proxy.test.ts +192 -0
- package/test/async-iterators.test.ts +327 -0
- package/test/batch-background.test.ts +482 -0
- package/test/batch-blog-posts.test.ts +387 -0
- package/test/blog-generation.test.ts +510 -0
- package/test/browse-read.test.ts +611 -0
- package/test/core-functions.test.ts +694 -0
- package/test/decide.test.ts +393 -0
- package/test/define.test.ts +274 -0
- package/test/e2e-bedrock-manual.ts +163 -0
- package/test/e2e-bedrock.test.ts +191 -0
- package/test/e2e-flex-gateway.ts +157 -0
- package/test/e2e-flex-manual.ts +183 -0
- package/test/e2e-flex.test.ts +209 -0
- package/test/e2e-google-manual.ts +178 -0
- package/test/e2e-google.test.ts +216 -0
- package/test/embeddings.test.ts +284 -0
- package/test/evals/define-function.eval.test.ts +379 -0
- package/test/evals/primitives.eval.test.ts +384 -0
- package/test/function-types.test.ts +492 -0
- package/test/generate-core.test.ts +319 -0
- package/test/generate.test.ts +163 -0
- package/test/implicit-batch.test.ts +422 -0
- package/test/schema.test.ts +109 -0
- package/test/tagged-templates.test.ts +302 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +42 -0
- package/LICENSE +0 -21
- package/bin/cli.js +0 -5
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -38
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/index.test.d.ts +0 -2
- package/dist/cli/index.test.d.ts.map +0 -1
- package/dist/cli/index.test.js +0 -35
- package/dist/cli/index.test.js.map +0 -1
- package/dist/constants/models.d.ts +0 -10
- package/dist/constants/models.d.ts.map +0 -1
- package/dist/constants/models.js +0 -12
- package/dist/constants/models.js.map +0 -1
- package/dist/converters/index.d.ts +0 -3
- package/dist/converters/index.d.ts.map +0 -1
- package/dist/converters/index.js +0 -3
- package/dist/converters/index.js.map +0 -1
- package/dist/converters/model.d.ts +0 -4
- package/dist/converters/model.d.ts.map +0 -1
- package/dist/converters/model.js +0 -19
- package/dist/converters/model.js.map +0 -1
- package/dist/converters/schema.d.ts +0 -4
- package/dist/converters/schema.d.ts.map +0 -1
- package/dist/converters/schema.js +0 -25
- package/dist/converters/schema.js.map +0 -1
- package/dist/core/responses.d.ts +0 -5
- package/dist/core/responses.d.ts.map +0 -1
- package/dist/core/responses.js +0 -16
- package/dist/core/responses.js.map +0 -1
- package/dist/core/responses.test.d.ts +0 -2
- package/dist/core/responses.test.d.ts.map +0 -1
- package/dist/core/responses.test.js +0 -31
- package/dist/core/responses.test.js.map +0 -1
- package/dist/errors.d.ts +0 -6
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -9
- package/dist/errors.js.map +0 -1
- package/dist/examples/streaming.test.d.ts +0 -2
- package/dist/examples/streaming.test.d.ts.map +0 -1
- package/dist/examples/streaming.test.js +0 -176
- package/dist/examples/streaming.test.js.map +0 -1
- package/dist/factory/__tests__/index.test.d.ts +0 -2
- package/dist/factory/__tests__/index.test.d.ts.map +0 -1
- package/dist/factory/__tests__/index.test.js +0 -430
- package/dist/factory/__tests__/index.test.js.map +0 -1
- package/dist/factory/__tests__/list.test.d.ts +0 -2
- package/dist/factory/__tests__/list.test.d.ts.map +0 -1
- package/dist/factory/__tests__/list.test.js +0 -92
- package/dist/factory/__tests__/list.test.js.map +0 -1
- package/dist/factory/index.d.ts +0 -20
- package/dist/factory/index.d.ts.map +0 -1
- package/dist/factory/index.js +0 -287
- package/dist/factory/index.js.map +0 -1
- package/dist/factory/index.test.d.ts +0 -2
- package/dist/factory/index.test.d.ts.map +0 -1
- package/dist/factory/index.test.js +0 -287
- package/dist/factory/index.test.js.map +0 -1
- package/dist/factory/list.d.ts +0 -3
- package/dist/factory/list.d.ts.map +0 -1
- package/dist/factory/list.js +0 -221
- package/dist/factory/list.js.map +0 -1
- package/dist/factory/list.test.d.ts +0 -2
- package/dist/factory/list.test.d.ts.map +0 -1
- package/dist/factory/list.test.js +0 -84
- package/dist/factory/list.test.js.map +0 -1
- package/dist/generate/index.d.ts +0 -5
- package/dist/generate/index.d.ts.map +0 -1
- package/dist/generate/index.js +0 -17
- package/dist/generate/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -59
- package/dist/index.test.js.map +0 -1
- package/dist/list/await.d.ts +0 -3
- package/dist/list/await.d.ts.map +0 -1
- package/dist/list/await.js +0 -28
- package/dist/list/await.js.map +0 -1
- package/dist/list/constants.d.ts +0 -4
- package/dist/list/constants.d.ts.map +0 -1
- package/dist/list/constants.js +0 -5
- package/dist/list/constants.js.map +0 -1
- package/dist/list/create-function.d.ts +0 -3
- package/dist/list/create-function.d.ts.map +0 -1
- package/dist/list/create-function.js +0 -11
- package/dist/list/create-function.js.map +0 -1
- package/dist/list/index.d.ts +0 -4
- package/dist/list/index.d.ts.map +0 -1
- package/dist/list/index.js +0 -5
- package/dist/list/index.js.map +0 -1
- package/dist/list/prompt.d.ts +0 -3
- package/dist/list/prompt.d.ts.map +0 -1
- package/dist/list/prompt.js +0 -6
- package/dist/list/prompt.js.map +0 -1
- package/dist/list/schemas.d.ts +0 -4
- package/dist/list/schemas.d.ts.map +0 -1
- package/dist/list/schemas.js +0 -8
- package/dist/list/schemas.js.map +0 -1
- package/dist/list/stream.d.ts +0 -3
- package/dist/list/stream.d.ts.map +0 -1
- package/dist/list/stream.js +0 -33
- package/dist/list/stream.js.map +0 -1
- package/dist/list/types.d.ts +0 -11
- package/dist/list/types.d.ts.map +0 -1
- package/dist/list/types.js +0 -2
- package/dist/list/types.js.map +0 -1
- package/dist/list/validation.d.ts +0 -3
- package/dist/list/validation.d.ts.map +0 -1
- package/dist/list/validation.js +0 -12
- package/dist/list/validation.js.map +0 -1
- package/dist/providers/config.d.ts +0 -4
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -21
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/config.test.d.ts +0 -2
- package/dist/providers/config.test.d.ts.map +0 -1
- package/dist/providers/config.test.js +0 -37
- package/dist/providers/config.test.js.map +0 -1
- package/dist/proxy/constants.d.ts +0 -4
- package/dist/proxy/constants.d.ts.map +0 -1
- package/dist/proxy/constants.js +0 -5
- package/dist/proxy/constants.js.map +0 -1
- package/dist/proxy/create-function.d.ts +0 -4
- package/dist/proxy/create-function.d.ts.map +0 -1
- package/dist/proxy/create-function.js +0 -24
- package/dist/proxy/create-function.js.map +0 -1
- package/dist/proxy/create-proxy.d.ts +0 -2
- package/dist/proxy/create-proxy.d.ts.map +0 -1
- package/dist/proxy/create-proxy.js +0 -11
- package/dist/proxy/create-proxy.js.map +0 -1
- package/dist/proxy/function-generator.d.ts +0 -9
- package/dist/proxy/function-generator.d.ts.map +0 -1
- package/dist/proxy/function-generator.js +0 -29
- package/dist/proxy/function-generator.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -4
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -4
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/prompt.d.ts +0 -2
- package/dist/proxy/prompt.d.ts.map +0 -1
- package/dist/proxy/prompt.js +0 -6
- package/dist/proxy/prompt.js.map +0 -1
- package/dist/proxy/types.d.ts +0 -7
- package/dist/proxy/types.d.ts.map +0 -1
- package/dist/proxy/types.js +0 -2
- package/dist/proxy/types.js.map +0 -1
- package/dist/queue/manager.d.ts +0 -5
- package/dist/queue/manager.d.ts.map +0 -1
- package/dist/queue/manager.js +0 -37
- package/dist/queue/manager.js.map +0 -1
- package/dist/queue/manager.test.d.ts +0 -2
- package/dist/queue/manager.test.d.ts.map +0 -1
- package/dist/queue/manager.test.js +0 -52
- package/dist/queue/manager.test.js.map +0 -1
- package/dist/schema-converter.d.ts +0 -4
- package/dist/schema-converter.d.ts.map +0 -1
- package/dist/schema-converter.js +0 -30
- package/dist/schema-converter.js.map +0 -1
- package/dist/stream/index.d.ts +0 -7
- package/dist/stream/index.d.ts.map +0 -1
- package/dist/stream/index.js +0 -23
- package/dist/stream/index.js.map +0 -1
- package/dist/streaming/utils.d.ts +0 -4
- package/dist/streaming/utils.d.ts.map +0 -1
- package/dist/streaming/utils.js +0 -131
- package/dist/streaming/utils.js.map +0 -1
- package/dist/streaming/utils.test.d.ts +0 -2
- package/dist/streaming/utils.test.d.ts.map +0 -1
- package/dist/streaming/utils.test.js +0 -84
- package/dist/streaming/utils.test.js.map +0 -1
- package/dist/templates/result.d.ts +0 -7
- package/dist/templates/result.d.ts.map +0 -1
- package/dist/templates/result.js +0 -40
- package/dist/templates/result.js.map +0 -1
- package/dist/templates/result.test.d.ts +0 -2
- package/dist/templates/result.test.d.ts.map +0 -1
- package/dist/templates/result.test.js +0 -75
- package/dist/templates/result.test.js.map +0 -1
- package/dist/test/setup.d.ts +0 -2
- package/dist/test/setup.d.ts.map +0 -1
- package/dist/test/setup.js +0 -21
- package/dist/test/setup.js.map +0 -1
- package/dist/test-types.d.ts +0 -13
- package/dist/test-types.d.ts.map +0 -1
- package/dist/test-types.js +0 -55
- package/dist/test-types.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/list.d.ts +0 -10
- package/dist/types/list.d.ts.map +0 -1
- package/dist/types/list.js +0 -2
- package/dist/types/list.js.map +0 -1
- package/dist/types/model.d.ts +0 -7
- package/dist/types/model.d.ts.map +0 -1
- package/dist/types/model.js +0 -2
- package/dist/types/model.js.map +0 -1
- package/dist/types/options.d.ts +0 -25
- package/dist/types/options.d.ts.map +0 -1
- package/dist/types/options.js +0 -2
- package/dist/types/options.js.map +0 -1
- package/dist/types/schema.d.ts +0 -5
- package/dist/types/schema.d.ts.map +0 -1
- package/dist/types/schema.js +0 -2
- package/dist/types/schema.js.map +0 -1
- package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
- package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
- package/dist/utils/__tests__/request-handler.test.js +0 -134
- package/dist/utils/__tests__/request-handler.test.js.map +0 -1
- package/dist/utils/__tests__/schema.test.d.ts +0 -2
- package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
- package/dist/utils/__tests__/schema.test.js +0 -49
- package/dist/utils/__tests__/schema.test.js.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
- package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.js +0 -85
- package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/request-handler.d.ts +0 -17
- package/dist/utils/request-handler.d.ts.map +0 -1
- package/dist/utils/request-handler.js +0 -105
- package/dist/utils/request-handler.js.map +0 -1
- package/dist/utils/schema.d.ts +0 -11
- package/dist/utils/schema.d.ts.map +0 -1
- package/dist/utils/schema.js +0 -51
- package/dist/utils/schema.js.map +0 -1
- package/dist/utils/stream-progress.d.ts +0 -17
- package/dist/utils/stream-progress.d.ts.map +0 -1
- package/dist/utils/stream-progress.js +0 -86
- package/dist/utils/stream-progress.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -30
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIPromise - RPC-style promise pipelining for AI functions
|
|
3
|
+
*
|
|
4
|
+
* Inspired by capnweb's RpcPromise, this enables:
|
|
5
|
+
* - Property access tracking for dynamic schema inference
|
|
6
|
+
* - Promise pipelining without await
|
|
7
|
+
* - Magical .map() for batch processing
|
|
8
|
+
* - Dependency graph resolution
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Dynamic schema from destructuring
|
|
13
|
+
* const { summary, keyPoints, conclusion } = ai`write about ${topic}`
|
|
14
|
+
*
|
|
15
|
+
* // Pipeline without await
|
|
16
|
+
* const isValid = is`${conclusion} is solid given ${keyPoints}`
|
|
17
|
+
*
|
|
18
|
+
* // Batch process with map
|
|
19
|
+
* const ideas = list`startup ideas`
|
|
20
|
+
* const evaluated = await ideas.map(idea => ({
|
|
21
|
+
* idea,
|
|
22
|
+
* viable: is`${idea} is viable`,
|
|
23
|
+
* market: ai`market size for ${idea}`,
|
|
24
|
+
* }))
|
|
25
|
+
*
|
|
26
|
+
* // Only await at the end
|
|
27
|
+
* if (await isValid) { ... }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @packageDocumentation
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { generateObject } from './generate.js'
|
|
34
|
+
import type { SimpleSchema } from './schema.js'
|
|
35
|
+
import type { FunctionOptions } from './template.js'
|
|
36
|
+
import {
|
|
37
|
+
isInRecordingMode,
|
|
38
|
+
getCurrentItemPlaceholder,
|
|
39
|
+
captureOperation,
|
|
40
|
+
createBatchMap,
|
|
41
|
+
BatchMapPromise,
|
|
42
|
+
} from './batch-map.js'
|
|
43
|
+
import { getModel } from './context.js'
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Types
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/** Symbol to identify AIPromise instances */
|
|
50
|
+
export const AI_PROMISE_SYMBOL = Symbol.for('ai-promise')
|
|
51
|
+
|
|
52
|
+
/** Symbol to get the raw AIPromise from a proxy */
|
|
53
|
+
export const RAW_PROMISE_SYMBOL = Symbol.for('ai-promise-raw')
|
|
54
|
+
|
|
55
|
+
/** Recording mode for map() */
|
|
56
|
+
export const RECORDING_MODE = Symbol.for('ai-promise-recording')
|
|
57
|
+
|
|
58
|
+
/** Dependency tracking */
|
|
59
|
+
interface Dependency {
|
|
60
|
+
promise: AIPromise<unknown>
|
|
61
|
+
path: string[]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Map callback recording */
|
|
65
|
+
interface MapRecording {
|
|
66
|
+
operations: Array<{
|
|
67
|
+
type: 'ai' | 'is' | 'list' | 'lists' | 'extract' | 'other'
|
|
68
|
+
prompt: string
|
|
69
|
+
dependencies: Dependency[]
|
|
70
|
+
}>
|
|
71
|
+
capturedStubs: AIPromise<unknown>[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Options for AIPromise creation */
|
|
75
|
+
export interface AIPromiseOptions extends FunctionOptions {
|
|
76
|
+
/** The type of generation */
|
|
77
|
+
type?: 'text' | 'object' | 'list' | 'lists' | 'boolean' | 'extract'
|
|
78
|
+
/** Base schema (can be extended by property access) */
|
|
79
|
+
baseSchema?: SimpleSchema
|
|
80
|
+
/** Parent promise this was derived from */
|
|
81
|
+
parent?: AIPromise<unknown>
|
|
82
|
+
/** Property path from parent */
|
|
83
|
+
propertyPath?: string[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Global State
|
|
88
|
+
// ============================================================================
|
|
89
|
+
|
|
90
|
+
/** Current recording context for map() */
|
|
91
|
+
let currentRecording: MapRecording | null = null
|
|
92
|
+
|
|
93
|
+
/** Pending promises for batch resolution */
|
|
94
|
+
const pendingPromises = new Set<AIPromise<unknown>>()
|
|
95
|
+
|
|
96
|
+
/** Promise resolution queue */
|
|
97
|
+
let resolutionScheduled = false
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// AIPromise Implementation
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* AIPromise - Like capnweb's RpcPromise but for AI functions
|
|
105
|
+
*
|
|
106
|
+
* Acts as both a Promise AND a stub that:
|
|
107
|
+
* - Tracks property accesses for dynamic schema inference
|
|
108
|
+
* - Records dependencies for promise pipelining
|
|
109
|
+
* - Supports .map() for batch processing
|
|
110
|
+
*/
|
|
111
|
+
export class AIPromise<T> implements PromiseLike<T> {
|
|
112
|
+
/** Marker to identify AIPromise instances */
|
|
113
|
+
readonly [AI_PROMISE_SYMBOL] = true
|
|
114
|
+
|
|
115
|
+
/** The prompt that will generate this value */
|
|
116
|
+
private _prompt: string
|
|
117
|
+
|
|
118
|
+
/** Options for generation */
|
|
119
|
+
private _options: AIPromiseOptions
|
|
120
|
+
|
|
121
|
+
/** Properties accessed on this promise (for schema inference) */
|
|
122
|
+
private _accessedProps = new Set<string>()
|
|
123
|
+
|
|
124
|
+
/** Property path from parent (for nested access) */
|
|
125
|
+
private _propertyPath: string[]
|
|
126
|
+
|
|
127
|
+
/** Parent promise (if this is a property access) */
|
|
128
|
+
private _parent: AIPromise<unknown> | null
|
|
129
|
+
|
|
130
|
+
/** Dependencies (other AIPromises used in our prompt) */
|
|
131
|
+
private _dependencies: Dependency[] = []
|
|
132
|
+
|
|
133
|
+
/** Cached resolver promise */
|
|
134
|
+
private _resolver: Promise<T> | null = null
|
|
135
|
+
|
|
136
|
+
/** Resolved value (cached after first resolution) */
|
|
137
|
+
private _resolvedValue: T | undefined
|
|
138
|
+
|
|
139
|
+
/** Whether this promise has been resolved */
|
|
140
|
+
private _isResolved = false
|
|
141
|
+
|
|
142
|
+
/** Whether we're in recording mode */
|
|
143
|
+
private _isRecording = false
|
|
144
|
+
|
|
145
|
+
constructor(prompt: string, options: AIPromiseOptions = {}) {
|
|
146
|
+
this._prompt = prompt
|
|
147
|
+
this._options = options
|
|
148
|
+
this._propertyPath = options.propertyPath || []
|
|
149
|
+
this._parent = options.parent || null
|
|
150
|
+
|
|
151
|
+
// Track this promise for batch resolution
|
|
152
|
+
pendingPromises.add(this)
|
|
153
|
+
|
|
154
|
+
// Return a proxy that intercepts property access
|
|
155
|
+
return new Proxy(this, PROXY_HANDLERS) as AIPromise<T>
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Get the prompt */
|
|
159
|
+
get prompt(): string {
|
|
160
|
+
return this._prompt
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Get the property path */
|
|
164
|
+
get path(): string[] {
|
|
165
|
+
return this._propertyPath
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Check if resolved */
|
|
169
|
+
get isResolved(): boolean {
|
|
170
|
+
return this._isResolved
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Get accessed properties */
|
|
174
|
+
get accessedProps(): Set<string> {
|
|
175
|
+
return this._accessedProps
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Add a dependency (another AIPromise used in this one's prompt)
|
|
180
|
+
*/
|
|
181
|
+
addDependency(promise: AIPromise<unknown>, path: string[] = []): void {
|
|
182
|
+
this._dependencies.push({ promise, path })
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Resolve this promise
|
|
187
|
+
*/
|
|
188
|
+
async resolve(): Promise<T> {
|
|
189
|
+
if (this._isResolved) {
|
|
190
|
+
return this._resolvedValue as T
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// If this is a property access on a parent, resolve the parent first
|
|
194
|
+
if (this._parent) {
|
|
195
|
+
const parentValue = await this._parent.resolve()
|
|
196
|
+
const value = getNestedValue(parentValue, this._propertyPath)
|
|
197
|
+
this._resolvedValue = value as T
|
|
198
|
+
this._isResolved = true
|
|
199
|
+
return this._resolvedValue
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Resolve dependencies first
|
|
203
|
+
const resolvedDeps: Record<string, unknown> = {}
|
|
204
|
+
for (const dep of this._dependencies) {
|
|
205
|
+
const value = await dep.promise.resolve()
|
|
206
|
+
const key = dep.path.length > 0 ? dep.path.join('.') : `dep_${this._dependencies.indexOf(dep)}`
|
|
207
|
+
resolvedDeps[key] = value
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Substitute resolved dependencies into prompt
|
|
211
|
+
let finalPrompt = this._prompt
|
|
212
|
+
for (const [key, value] of Object.entries(resolvedDeps)) {
|
|
213
|
+
finalPrompt = finalPrompt.replace(
|
|
214
|
+
new RegExp(`\\$\\{${key}\\}`, 'g'),
|
|
215
|
+
String(value)
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Build schema from accessed properties
|
|
220
|
+
const schema = this._buildSchema()
|
|
221
|
+
|
|
222
|
+
// Generate the result
|
|
223
|
+
const result = await generateObject({
|
|
224
|
+
model: this._options.model || 'sonnet',
|
|
225
|
+
schema,
|
|
226
|
+
prompt: finalPrompt,
|
|
227
|
+
system: this._options.system,
|
|
228
|
+
temperature: this._options.temperature,
|
|
229
|
+
maxTokens: this._options.maxTokens,
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
this._resolvedValue = result.object as T
|
|
233
|
+
this._isResolved = true
|
|
234
|
+
pendingPromises.delete(this)
|
|
235
|
+
|
|
236
|
+
return this._resolvedValue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Build schema from accessed properties and base schema
|
|
241
|
+
*/
|
|
242
|
+
private _buildSchema(): SimpleSchema {
|
|
243
|
+
const baseSchema = this._options.baseSchema || {}
|
|
244
|
+
|
|
245
|
+
// If no properties accessed, use base schema or infer from type
|
|
246
|
+
if (this._accessedProps.size === 0) {
|
|
247
|
+
if (typeof baseSchema === 'object' && Object.keys(baseSchema).length > 0) {
|
|
248
|
+
return baseSchema
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Infer from type
|
|
252
|
+
switch (this._options.type) {
|
|
253
|
+
case 'list':
|
|
254
|
+
return { items: ['List items'] }
|
|
255
|
+
case 'lists':
|
|
256
|
+
return { categories: ['Category names'], data: 'JSON object with categorized lists' }
|
|
257
|
+
case 'boolean':
|
|
258
|
+
return { answer: 'true | false' }
|
|
259
|
+
case 'text':
|
|
260
|
+
return { text: 'The generated text' }
|
|
261
|
+
default:
|
|
262
|
+
return { result: 'The result' }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Build schema from accessed properties
|
|
267
|
+
const schema: { [key: string]: SimpleSchema } = {}
|
|
268
|
+
|
|
269
|
+
for (const prop of this._accessedProps) {
|
|
270
|
+
// Check if base schema has this property
|
|
271
|
+
if (typeof baseSchema === 'object' && !Array.isArray(baseSchema) && prop in baseSchema) {
|
|
272
|
+
const propSchema = (baseSchema as { [key: string]: SimpleSchema })[prop]
|
|
273
|
+
if (propSchema !== undefined) {
|
|
274
|
+
schema[prop] = propSchema
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Infer type from property name patterns
|
|
280
|
+
const lowerProp = prop.toLowerCase()
|
|
281
|
+
if (
|
|
282
|
+
lowerProp.endsWith('s') ||
|
|
283
|
+
lowerProp.includes('list') ||
|
|
284
|
+
lowerProp.includes('items') ||
|
|
285
|
+
lowerProp.includes('array')
|
|
286
|
+
) {
|
|
287
|
+
schema[prop] = [`List of ${prop}`]
|
|
288
|
+
} else if (
|
|
289
|
+
lowerProp.includes('is') ||
|
|
290
|
+
lowerProp.includes('has') ||
|
|
291
|
+
lowerProp.includes('can') ||
|
|
292
|
+
lowerProp.includes('should')
|
|
293
|
+
) {
|
|
294
|
+
schema[prop] = `Whether ${prop} (true/false)`
|
|
295
|
+
} else if (
|
|
296
|
+
lowerProp.includes('count') ||
|
|
297
|
+
lowerProp.includes('number') ||
|
|
298
|
+
lowerProp.includes('total') ||
|
|
299
|
+
lowerProp.includes('amount')
|
|
300
|
+
) {
|
|
301
|
+
schema[prop] = `The ${prop} (number)`
|
|
302
|
+
} else {
|
|
303
|
+
schema[prop] = `The ${prop}`
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return schema
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Map over array results - automatically batches operations!
|
|
312
|
+
*
|
|
313
|
+
* When you map over a list, the operations are captured and
|
|
314
|
+
* automatically batched when resolved. Uses provider batch APIs
|
|
315
|
+
* for cost savings (50% discount) when beneficial.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* // Simple map - each title becomes a blog post
|
|
320
|
+
* const titles = await list`10 blog post titles`
|
|
321
|
+
* const posts = titles.map(title => write`blog post: # ${title}`)
|
|
322
|
+
* console.log(await posts) // 10 blog posts via batch API
|
|
323
|
+
*
|
|
324
|
+
* // Complex map - multiple operations per item
|
|
325
|
+
* const ideas = await list`startup ideas`
|
|
326
|
+
* const evaluated = await ideas.map(idea => ({
|
|
327
|
+
* idea,
|
|
328
|
+
* viable: is`${idea} is viable`,
|
|
329
|
+
* market: ai`market size for ${idea}`,
|
|
330
|
+
* }))
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
map<U>(
|
|
334
|
+
callback: (item: T extends (infer I)[] ? I : T, index: number) => U
|
|
335
|
+
): BatchMapPromise<U> {
|
|
336
|
+
// Create a wrapper that resolves this promise first, then maps
|
|
337
|
+
const mapPromise = new BatchMapPromise<U>([], [], {})
|
|
338
|
+
|
|
339
|
+
// Override the resolve to first get the list items
|
|
340
|
+
const originalResolve = mapPromise.resolve.bind(mapPromise)
|
|
341
|
+
;(mapPromise as any).resolve = async () => {
|
|
342
|
+
// First, resolve the list
|
|
343
|
+
const items = await this.resolve()
|
|
344
|
+
|
|
345
|
+
if (!Array.isArray(items)) {
|
|
346
|
+
throw new Error('Cannot map over non-array result')
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Now create the actual batch map with the resolved items
|
|
350
|
+
const actualBatchMap = createBatchMap(
|
|
351
|
+
items as (T extends (infer I)[] ? I : T)[],
|
|
352
|
+
callback
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
return actualBatchMap.resolve()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return mapPromise
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Map with explicit batch options
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* // Force immediate execution (no batch API)
|
|
367
|
+
* const posts = titles.mapImmediate(title => write`blog post: ${title}`)
|
|
368
|
+
*
|
|
369
|
+
* // Force batch API (even for small lists)
|
|
370
|
+
* const posts = titles.mapDeferred(title => write`blog post: ${title}`)
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
mapImmediate<U>(
|
|
374
|
+
callback: (item: T extends (infer I)[] ? I : T, index: number) => U
|
|
375
|
+
): BatchMapPromise<U> {
|
|
376
|
+
const mapPromise = new BatchMapPromise<U>([], [], { immediate: true })
|
|
377
|
+
|
|
378
|
+
;(mapPromise as any).resolve = async () => {
|
|
379
|
+
const items = await this.resolve()
|
|
380
|
+
if (!Array.isArray(items)) {
|
|
381
|
+
throw new Error('Cannot map over non-array result')
|
|
382
|
+
}
|
|
383
|
+
const actualBatchMap = createBatchMap(
|
|
384
|
+
items as (T extends (infer I)[] ? I : T)[],
|
|
385
|
+
callback,
|
|
386
|
+
{ immediate: true }
|
|
387
|
+
)
|
|
388
|
+
return actualBatchMap.resolve()
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return mapPromise
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
mapDeferred<U>(
|
|
395
|
+
callback: (item: T extends (infer I)[] ? I : T, index: number) => U
|
|
396
|
+
): BatchMapPromise<U> {
|
|
397
|
+
const mapPromise = new BatchMapPromise<U>([], [], { deferred: true })
|
|
398
|
+
|
|
399
|
+
;(mapPromise as any).resolve = async () => {
|
|
400
|
+
const items = await this.resolve()
|
|
401
|
+
if (!Array.isArray(items)) {
|
|
402
|
+
throw new Error('Cannot map over non-array result')
|
|
403
|
+
}
|
|
404
|
+
const actualBatchMap = createBatchMap(
|
|
405
|
+
items as (T extends (infer I)[] ? I : T)[],
|
|
406
|
+
callback,
|
|
407
|
+
{ deferred: true }
|
|
408
|
+
)
|
|
409
|
+
return actualBatchMap.resolve()
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return mapPromise
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* ForEach with automatic batching
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* await list`startup ideas`.forEach(async idea => {
|
|
421
|
+
* console.log(await is`${idea} is viable`)
|
|
422
|
+
* })
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
async forEach(
|
|
426
|
+
callback: (item: T extends (infer I)[] ? I : T, index: number) => void | Promise<void>
|
|
427
|
+
): Promise<void> {
|
|
428
|
+
const items = await this.resolve()
|
|
429
|
+
if (Array.isArray(items)) {
|
|
430
|
+
for (let i = 0; i < items.length; i++) {
|
|
431
|
+
await callback(items[i], i)
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
await callback(items as any, 0)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Async iterator support with smart batching
|
|
440
|
+
*/
|
|
441
|
+
async *[Symbol.asyncIterator](): AsyncIterator<T extends (infer I)[] ? I : T> {
|
|
442
|
+
const items = await this.resolve()
|
|
443
|
+
if (Array.isArray(items)) {
|
|
444
|
+
for (const item of items) {
|
|
445
|
+
yield item as any
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
yield items as any
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Promise interface - then()
|
|
454
|
+
*/
|
|
455
|
+
then<TResult1 = T, TResult2 = never>(
|
|
456
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
457
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
|
|
458
|
+
): Promise<TResult1 | TResult2> {
|
|
459
|
+
if (!this._resolver) {
|
|
460
|
+
// Schedule batch resolution on next microtask
|
|
461
|
+
this._resolver = new Promise<T>((resolve, reject) => {
|
|
462
|
+
queueMicrotask(async () => {
|
|
463
|
+
try {
|
|
464
|
+
const value = await this.resolve()
|
|
465
|
+
resolve(value)
|
|
466
|
+
} catch (error) {
|
|
467
|
+
reject(error)
|
|
468
|
+
}
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return this._resolver.then(onfulfilled, onrejected)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Promise interface - catch()
|
|
478
|
+
*/
|
|
479
|
+
catch<TResult = never>(
|
|
480
|
+
onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
|
|
481
|
+
): Promise<T | TResult> {
|
|
482
|
+
return this.then(null, onrejected)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Promise interface - finally()
|
|
487
|
+
*/
|
|
488
|
+
finally(onfinally?: (() => void) | null): Promise<T> {
|
|
489
|
+
return this.then(
|
|
490
|
+
(value) => {
|
|
491
|
+
onfinally?.()
|
|
492
|
+
return value
|
|
493
|
+
},
|
|
494
|
+
(reason) => {
|
|
495
|
+
onfinally?.()
|
|
496
|
+
throw reason
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ============================================================================
|
|
503
|
+
// Proxy Handlers
|
|
504
|
+
// ============================================================================
|
|
505
|
+
|
|
506
|
+
const PROXY_HANDLERS: ProxyHandler<AIPromise<unknown>> = {
|
|
507
|
+
get(target, prop: string | symbol, receiver) {
|
|
508
|
+
// Handle symbols
|
|
509
|
+
if (typeof prop === 'symbol') {
|
|
510
|
+
if (prop === AI_PROMISE_SYMBOL) return true
|
|
511
|
+
if (prop === RAW_PROMISE_SYMBOL) return target
|
|
512
|
+
if (prop === Symbol.asyncIterator) return target[Symbol.asyncIterator].bind(target)
|
|
513
|
+
return (target as any)[prop]
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Handle promise methods
|
|
517
|
+
if (prop === 'then' || prop === 'catch' || prop === 'finally') {
|
|
518
|
+
return (target as any)[prop].bind(target)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Handle AIPromise methods
|
|
522
|
+
if (prop === 'map' || prop === 'forEach' || prop === 'resolve') {
|
|
523
|
+
return (target as any)[prop].bind(target)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Handle internal properties
|
|
527
|
+
if (prop.startsWith('_') || prop === 'prompt' || prop === 'path' || prop === 'isResolved' || prop === 'accessedProps') {
|
|
528
|
+
return (target as any)[prop]
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Track property access for schema inference
|
|
532
|
+
target.accessedProps.add(prop)
|
|
533
|
+
|
|
534
|
+
// If we're in recording mode, record this access
|
|
535
|
+
if (currentRecording) {
|
|
536
|
+
// Just track the access, don't create new promise
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Return a new AIPromise for the property path
|
|
540
|
+
return new AIPromise<unknown>(
|
|
541
|
+
target.prompt,
|
|
542
|
+
{
|
|
543
|
+
...target['_options'],
|
|
544
|
+
parent: target,
|
|
545
|
+
propertyPath: [...target.path, prop],
|
|
546
|
+
}
|
|
547
|
+
)
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
// Prevent mutation
|
|
551
|
+
set() {
|
|
552
|
+
throw new Error('AIPromise properties are read-only')
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
deleteProperty() {
|
|
556
|
+
throw new Error('AIPromise properties cannot be deleted')
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
// Handle function calls (for chained methods)
|
|
560
|
+
apply(target, thisArg, args) {
|
|
561
|
+
// If the target is callable (e.g., from a template function), call it
|
|
562
|
+
if (typeof (target as any)._call === 'function') {
|
|
563
|
+
return (target as any)._call(...args)
|
|
564
|
+
}
|
|
565
|
+
throw new Error('AIPromise is not callable')
|
|
566
|
+
},
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ============================================================================
|
|
570
|
+
// Helper Functions
|
|
571
|
+
// ============================================================================
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Get a nested value from an object by path
|
|
575
|
+
*/
|
|
576
|
+
function getNestedValue(obj: unknown, path: string[]): unknown {
|
|
577
|
+
let current = obj
|
|
578
|
+
for (const key of path) {
|
|
579
|
+
if (current === null || current === undefined) return undefined
|
|
580
|
+
if (key === '__item__') continue // Skip internal markers
|
|
581
|
+
current = (current as Record<string, unknown>)[key]
|
|
582
|
+
}
|
|
583
|
+
return current
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Analyze the result of a map callback to build batch schema
|
|
588
|
+
*/
|
|
589
|
+
function analyzeRecordingResult(result: unknown, recording: MapRecording): SimpleSchema {
|
|
590
|
+
if (result === null || result === undefined) {
|
|
591
|
+
return { result: 'The result' }
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (typeof result !== 'object') {
|
|
595
|
+
return { result: 'The result' }
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Build schema from the result structure
|
|
599
|
+
const schema: Record<string, SimpleSchema> = {}
|
|
600
|
+
|
|
601
|
+
for (const [key, value] of Object.entries(result)) {
|
|
602
|
+
if (isAIPromise(value)) {
|
|
603
|
+
// This is a reference to an AI operation
|
|
604
|
+
const aiPromise = getRawPromise(value as AIPromise<unknown>)
|
|
605
|
+
|
|
606
|
+
// Infer schema from the promise's accessed properties or type
|
|
607
|
+
if (aiPromise.accessedProps.size > 0) {
|
|
608
|
+
schema[key] = Object.fromEntries(
|
|
609
|
+
Array.from(aiPromise.accessedProps).map(p => [p, `The ${p}`])
|
|
610
|
+
)
|
|
611
|
+
} else {
|
|
612
|
+
const type = (aiPromise as any)._options?.type
|
|
613
|
+
if (type === 'boolean') {
|
|
614
|
+
schema[key] = 'true | false'
|
|
615
|
+
} else if (type === 'list') {
|
|
616
|
+
schema[key] = ['List items']
|
|
617
|
+
} else {
|
|
618
|
+
schema[key] = `The ${key}`
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
622
|
+
// Recursively analyze nested objects
|
|
623
|
+
schema[key] = analyzeRecordingResult(value, recording) as SimpleSchema
|
|
624
|
+
} else {
|
|
625
|
+
// Literal value - include as-is
|
|
626
|
+
schema[key] = `Value: ${JSON.stringify(value)}`
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return schema
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Check if a value is an AIPromise
|
|
635
|
+
*/
|
|
636
|
+
export function isAIPromise(value: unknown): value is AIPromise<unknown> {
|
|
637
|
+
return (
|
|
638
|
+
value !== null &&
|
|
639
|
+
typeof value === 'object' &&
|
|
640
|
+
AI_PROMISE_SYMBOL in value &&
|
|
641
|
+
(value as any)[AI_PROMISE_SYMBOL] === true
|
|
642
|
+
)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Get the raw AIPromise from a proxied value
|
|
647
|
+
*/
|
|
648
|
+
export function getRawPromise<T>(value: AIPromise<T>): AIPromise<T> {
|
|
649
|
+
if (RAW_PROMISE_SYMBOL in value) {
|
|
650
|
+
return (value as any)[RAW_PROMISE_SYMBOL]
|
|
651
|
+
}
|
|
652
|
+
return value
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// ============================================================================
|
|
656
|
+
// Factory Functions
|
|
657
|
+
// ============================================================================
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Create an AIPromise for text generation
|
|
661
|
+
*/
|
|
662
|
+
export function createTextPromise(prompt: string, options?: FunctionOptions): AIPromise<string> {
|
|
663
|
+
return new AIPromise<string>(prompt, { ...options, type: 'text' })
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Create an AIPromise for object generation with dynamic schema
|
|
668
|
+
*/
|
|
669
|
+
export function createObjectPromise<T = unknown>(prompt: string, options?: FunctionOptions): AIPromise<T> {
|
|
670
|
+
return new AIPromise<T>(prompt, { ...options, type: 'object' })
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Create an AIPromise for list generation
|
|
675
|
+
*/
|
|
676
|
+
export function createListPromise(prompt: string, options?: FunctionOptions): AIPromise<string[]> {
|
|
677
|
+
return new AIPromise<string[]>(prompt, { ...options, type: 'list' })
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Create an AIPromise for multiple lists generation
|
|
682
|
+
*/
|
|
683
|
+
export function createListsPromise(prompt: string, options?: FunctionOptions): AIPromise<Record<string, string[]>> {
|
|
684
|
+
return new AIPromise<Record<string, string[]>>(prompt, { ...options, type: 'lists' })
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Create an AIPromise for boolean/is check
|
|
689
|
+
*/
|
|
690
|
+
export function createBooleanPromise(prompt: string, options?: FunctionOptions): AIPromise<boolean> {
|
|
691
|
+
return new AIPromise<boolean>(prompt, { ...options, type: 'boolean' })
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Create an AIPromise for extraction
|
|
696
|
+
*/
|
|
697
|
+
export function createExtractPromise<T = unknown>(prompt: string, options?: FunctionOptions): AIPromise<T[]> {
|
|
698
|
+
return new AIPromise<T[]>(prompt, { ...options, type: 'extract' })
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ============================================================================
|
|
702
|
+
// Template Tag Helpers
|
|
703
|
+
// ============================================================================
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Parse template literals and track AIPromise dependencies
|
|
707
|
+
*/
|
|
708
|
+
export function parseTemplateWithDependencies(
|
|
709
|
+
strings: TemplateStringsArray,
|
|
710
|
+
...values: unknown[]
|
|
711
|
+
): { prompt: string; dependencies: Dependency[] } {
|
|
712
|
+
const dependencies: Dependency[] = []
|
|
713
|
+
let prompt = ''
|
|
714
|
+
|
|
715
|
+
for (let i = 0; i < strings.length; i++) {
|
|
716
|
+
prompt += strings[i]
|
|
717
|
+
|
|
718
|
+
if (i < values.length) {
|
|
719
|
+
const value = values[i]
|
|
720
|
+
|
|
721
|
+
if (isAIPromise(value)) {
|
|
722
|
+
// Track as dependency
|
|
723
|
+
const rawPromise = getRawPromise(value)
|
|
724
|
+
const depKey = `dep_${dependencies.length}`
|
|
725
|
+
dependencies.push({ promise: rawPromise, path: rawPromise.path })
|
|
726
|
+
prompt += `\${${depKey}}`
|
|
727
|
+
} else {
|
|
728
|
+
// Inline the value
|
|
729
|
+
prompt += String(value)
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return { prompt, dependencies }
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Create a template function that returns AIPromise
|
|
739
|
+
*/
|
|
740
|
+
export function createAITemplateFunction<T>(
|
|
741
|
+
type: AIPromiseOptions['type'],
|
|
742
|
+
baseOptions?: FunctionOptions
|
|
743
|
+
): ((strings: TemplateStringsArray, ...values: unknown[]) => AIPromise<T>) &
|
|
744
|
+
((prompt: string, options?: FunctionOptions) => AIPromise<T>) {
|
|
745
|
+
|
|
746
|
+
function templateFn(
|
|
747
|
+
promptOrStrings: string | TemplateStringsArray,
|
|
748
|
+
...args: unknown[]
|
|
749
|
+
): AIPromise<T> {
|
|
750
|
+
let prompt: string
|
|
751
|
+
let dependencies: Dependency[] = []
|
|
752
|
+
let options: FunctionOptions = { ...baseOptions }
|
|
753
|
+
|
|
754
|
+
if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
|
|
755
|
+
// Tagged template literal
|
|
756
|
+
const parsed = parseTemplateWithDependencies(promptOrStrings, ...args)
|
|
757
|
+
prompt = parsed.prompt
|
|
758
|
+
dependencies = parsed.dependencies
|
|
759
|
+
} else {
|
|
760
|
+
// Regular function call
|
|
761
|
+
prompt = promptOrStrings as string
|
|
762
|
+
if (args.length > 0 && typeof args[0] === 'object') {
|
|
763
|
+
options = { ...options, ...(args[0] as FunctionOptions) }
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// If we're in recording mode (inside a .map() callback), capture this operation
|
|
768
|
+
if (isInRecordingMode()) {
|
|
769
|
+
const batchType = type === 'text' ? 'text' : type === 'boolean' ? 'boolean' : type === 'list' ? 'list' : 'object'
|
|
770
|
+
captureOperation(prompt, batchType, (options as any).baseSchema, options.system)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const promise = new AIPromise<T>(prompt, { ...options, type })
|
|
774
|
+
|
|
775
|
+
// Add dependencies
|
|
776
|
+
for (const dep of dependencies) {
|
|
777
|
+
promise.addDependency(dep.promise, dep.path)
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return promise
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return templateFn as any
|
|
784
|
+
}
|