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
|
@@ -2,295 +2,206 @@
|
|
|
2
2
|
* Tests for the core generate() primitive
|
|
3
3
|
*
|
|
4
4
|
* generate(type, prompt, opts?) is the foundation that all other functions use.
|
|
5
|
+
* Tests require actual AI calls via the Cloudflare AI Gateway.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import { describe, it, expect
|
|
8
|
+
import { describe, it, expect } from 'vitest'
|
|
9
|
+
import { generateObject, generateText } from '../src/generate.js'
|
|
10
|
+
import { z } from 'zod'
|
|
8
11
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
|
|
13
|
-
const mockAICall = vi.fn()
|
|
14
|
-
|
|
15
|
-
// Mock generate implementation
|
|
16
|
-
async function generate(
|
|
17
|
-
type: string,
|
|
18
|
-
prompt: string,
|
|
19
|
-
options?: Record<string, unknown>
|
|
20
|
-
): Promise<unknown> {
|
|
21
|
-
return mockAICall(type, prompt, options)
|
|
22
|
-
}
|
|
12
|
+
// Skip tests if no gateway configured
|
|
13
|
+
const hasGateway = !!process.env.AI_GATEWAY_URL
|
|
23
14
|
|
|
24
15
|
// ============================================================================
|
|
25
16
|
// generate(type, prompt, opts) signature tests
|
|
26
17
|
// ============================================================================
|
|
27
18
|
|
|
28
|
-
describe('generate(type, prompt, opts)', () => {
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
mockAICall.mockReset()
|
|
31
|
-
})
|
|
32
|
-
|
|
19
|
+
describe.skipIf(!hasGateway)('generate(type, prompt, opts)', () => {
|
|
33
20
|
describe('type: json', () => {
|
|
34
|
-
it('generates JSON without schema (AI infers structure)', async () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
it('generates JSON without explicit schema (AI infers structure)', async () => {
|
|
22
|
+
const result = await generateObject({
|
|
23
|
+
model: 'haiku',
|
|
24
|
+
schema: z.object({
|
|
25
|
+
competitors: z.array(z.string()).describe('List of competitors'),
|
|
26
|
+
marketSize: z.number().describe('Estimated market size'),
|
|
27
|
+
}),
|
|
28
|
+
prompt:
|
|
29
|
+
'Provide a simple competitive analysis of the cloud computing market. List 2 competitors and an estimated market size in billions.',
|
|
39
30
|
})
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
expect(
|
|
44
|
-
expect(result).
|
|
45
|
-
expect(result).toHaveProperty('marketSize')
|
|
32
|
+
expect(result.object).toHaveProperty('competitors')
|
|
33
|
+
expect(result.object).toHaveProperty('marketSize')
|
|
34
|
+
expect(Array.isArray(result.object.competitors)).toBe(true)
|
|
35
|
+
expect(typeof result.object.marketSize).toBe('number')
|
|
46
36
|
})
|
|
47
37
|
|
|
48
38
|
it('generates JSON with schema (typed, validated)', async () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
const result = await generateObject({
|
|
40
|
+
model: 'haiku',
|
|
41
|
+
schema: z.object({
|
|
42
|
+
name: z.string().describe('Recipe name'),
|
|
43
|
+
servings: z.number().describe('Number of servings'),
|
|
44
|
+
ingredients: z.array(z.string()).describe('List of ingredients'),
|
|
45
|
+
steps: z.array(z.string()).describe('Cooking steps'),
|
|
46
|
+
}),
|
|
47
|
+
prompt: 'Generate a simple 3-ingredient recipe with 2 steps.',
|
|
54
48
|
})
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
expect(mockAICall).toHaveBeenCalledWith(
|
|
66
|
-
'json',
|
|
67
|
-
'Italian pasta recipe',
|
|
68
|
-
expect.objectContaining({ schema: expect.any(Object) })
|
|
69
|
-
)
|
|
70
|
-
expect(result).toHaveProperty('name')
|
|
71
|
-
expect(result).toHaveProperty('servings')
|
|
72
|
-
expect(typeof (result as { servings: number }).servings).toBe('number')
|
|
50
|
+
expect(result.object).toHaveProperty('name')
|
|
51
|
+
expect(result.object).toHaveProperty('servings')
|
|
52
|
+
expect(typeof result.object.name).toBe('string')
|
|
53
|
+
expect(typeof result.object.servings).toBe('number')
|
|
54
|
+
expect(Array.isArray(result.object.ingredients)).toBe(true)
|
|
55
|
+
expect(Array.isArray(result.object.steps)).toBe(true)
|
|
73
56
|
})
|
|
74
57
|
})
|
|
75
58
|
|
|
76
59
|
describe('type: text', () => {
|
|
77
60
|
it('generates plain text', async () => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
61
|
+
const result = await generateText({
|
|
62
|
+
model: 'haiku',
|
|
63
|
+
prompt: 'Write one sentence about AI.',
|
|
64
|
+
})
|
|
81
65
|
|
|
82
|
-
expect(
|
|
83
|
-
expect(
|
|
66
|
+
expect(typeof result.text).toBe('string')
|
|
67
|
+
expect(result.text.length).toBeGreaterThan(10)
|
|
84
68
|
})
|
|
85
69
|
})
|
|
86
70
|
|
|
87
71
|
describe('type: code', () => {
|
|
88
|
-
it('generates code with language
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const result = await generate('code', 'email validation function', {
|
|
96
|
-
language: 'typescript',
|
|
72
|
+
it('generates code with language specified in prompt', async () => {
|
|
73
|
+
const result = await generateText({
|
|
74
|
+
model: 'haiku',
|
|
75
|
+
system:
|
|
76
|
+
'You are a code generator. Output only valid TypeScript code, no explanations or markdown.',
|
|
77
|
+
prompt:
|
|
78
|
+
'Write a TypeScript function called validateEmail that takes a string and returns boolean.',
|
|
97
79
|
})
|
|
98
80
|
|
|
99
|
-
expect(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
expect.objectContaining({ language: 'typescript' })
|
|
103
|
-
)
|
|
104
|
-
expect(typeof result).toBe('string')
|
|
105
|
-
expect(result).toContain('function')
|
|
81
|
+
expect(typeof result.text).toBe('string')
|
|
82
|
+
expect(result.text).toContain('function')
|
|
83
|
+
expect(result.text).toMatch(/validateEmail|email/i)
|
|
106
84
|
})
|
|
107
85
|
|
|
108
86
|
it('generates code in different languages', async () => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
87
|
+
const result = await generateText({
|
|
88
|
+
model: 'haiku',
|
|
89
|
+
system:
|
|
90
|
+
'You are a code generator. Output only valid Python code, no explanations or markdown.',
|
|
91
|
+
prompt:
|
|
92
|
+
'Write a Python function called validate_email that takes a string and returns a boolean.',
|
|
93
|
+
})
|
|
112
94
|
|
|
113
|
-
expect(
|
|
114
|
-
|
|
115
|
-
'email validation',
|
|
116
|
-
expect.objectContaining({ language: 'python' })
|
|
117
|
-
)
|
|
95
|
+
expect(typeof result.text).toBe('string')
|
|
96
|
+
expect(result.text).toContain('def')
|
|
118
97
|
})
|
|
119
98
|
})
|
|
120
99
|
|
|
121
100
|
describe('type: markdown', () => {
|
|
122
101
|
it('generates markdown content', async () => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
102
|
+
const result = await generateText({
|
|
103
|
+
model: 'haiku',
|
|
104
|
+
system: 'You write in markdown format.',
|
|
105
|
+
prompt: 'Write a very short README with a heading and 2 bullet points.',
|
|
106
|
+
})
|
|
126
107
|
|
|
127
|
-
expect(
|
|
128
|
-
expect(
|
|
129
|
-
expect(result).toContain('#')
|
|
108
|
+
expect(typeof result.text).toBe('string')
|
|
109
|
+
expect(result.text).toContain('#')
|
|
130
110
|
})
|
|
131
111
|
})
|
|
132
112
|
|
|
133
113
|
describe('type: yaml', () => {
|
|
134
114
|
it('generates YAML content', async () => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
expect(mockAICall).toHaveBeenCalledWith('yaml', 'kubernetes deployment for my-app', undefined)
|
|
145
|
-
expect(typeof result).toBe('string')
|
|
146
|
-
expect(result).toContain('apiVersion')
|
|
115
|
+
const result = await generateText({
|
|
116
|
+
model: 'haiku',
|
|
117
|
+
system: 'You output only valid YAML, no explanations or markdown fences.',
|
|
118
|
+
prompt: 'Generate a simple YAML config with name: "test-app" and port: 3000.',
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
expect(typeof result.text).toBe('string')
|
|
122
|
+
expect(result.text.toLowerCase()).toContain('name')
|
|
147
123
|
})
|
|
148
124
|
})
|
|
149
125
|
|
|
150
126
|
describe('type: list', () => {
|
|
151
127
|
it('generates a list of items', async () => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
128
|
+
const result = await generateObject({
|
|
129
|
+
model: 'haiku',
|
|
130
|
+
schema: z.object({
|
|
131
|
+
items: z.array(z.string()).describe('List of startup ideas'),
|
|
132
|
+
}),
|
|
133
|
+
prompt: 'List exactly 3 startup ideas.',
|
|
134
|
+
})
|
|
155
135
|
|
|
156
|
-
expect(
|
|
157
|
-
expect(
|
|
136
|
+
expect(Array.isArray(result.object.items)).toBe(true)
|
|
137
|
+
expect(result.object.items.length).toBe(3)
|
|
158
138
|
})
|
|
159
139
|
})
|
|
160
140
|
|
|
161
141
|
describe('type: diagram', () => {
|
|
162
142
|
it('generates diagram code', async () => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
expect(mockAICall).toHaveBeenCalledWith(
|
|
170
|
-
'diagram',
|
|
171
|
-
'user flow for authentication',
|
|
172
|
-
expect.objectContaining({ format: 'mermaid' })
|
|
173
|
-
)
|
|
174
|
-
expect(typeof result).toBe('string')
|
|
175
|
-
})
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
describe('type: slides', () => {
|
|
179
|
-
it('generates presentation slides', async () => {
|
|
180
|
-
mockAICall.mockResolvedValue('---\ntheme: default\n---\n\n# Title\n\nContent')
|
|
181
|
-
|
|
182
|
-
const result = await generate('slides', 'quarterly review presentation', {
|
|
183
|
-
format: 'slidev',
|
|
184
|
-
slides: 10,
|
|
143
|
+
const result = await generateText({
|
|
144
|
+
model: 'haiku',
|
|
145
|
+
system:
|
|
146
|
+
'You generate Mermaid diagram code. Output only the diagram code, no explanations or markdown fences.',
|
|
147
|
+
prompt: 'Create a simple flowchart: Start -> Login -> Dashboard.',
|
|
185
148
|
})
|
|
186
149
|
|
|
187
|
-
expect(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
expect.objectContaining({ format: 'slidev', slides: 10 })
|
|
191
|
-
)
|
|
150
|
+
expect(typeof result.text).toBe('string')
|
|
151
|
+
// Mermaid diagrams typically contain --> or -> for connections
|
|
152
|
+
expect(result.text).toMatch(/(flowchart|graph|-->|->)/i)
|
|
192
153
|
})
|
|
193
154
|
})
|
|
194
155
|
})
|
|
195
156
|
|
|
196
|
-
// ============================================================================
|
|
197
|
-
// Tagged template support on generate
|
|
198
|
-
// ============================================================================
|
|
199
|
-
|
|
200
|
-
describe('generate as tagged template', () => {
|
|
201
|
-
beforeEach(() => {
|
|
202
|
-
mockAICall.mockReset()
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
// Note: This tests the concept - actual implementation would need the template wrapper
|
|
206
|
-
it('should support tagged template syntax (conceptual)', async () => {
|
|
207
|
-
mockAICall.mockResolvedValue({ analysis: 'Result' })
|
|
208
|
-
|
|
209
|
-
// This would be: generate`analysis of ${company}`
|
|
210
|
-
const company = 'Acme Corp'
|
|
211
|
-
const prompt = `analysis of ${company}`
|
|
212
|
-
|
|
213
|
-
const result = await generate('json', prompt)
|
|
214
|
-
|
|
215
|
-
expect(result).toHaveProperty('analysis')
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
it('should convert objects to YAML in templates (conceptual)', async () => {
|
|
219
|
-
mockAICall.mockResolvedValue('Generated content')
|
|
220
|
-
|
|
221
|
-
const context = {
|
|
222
|
-
brand: 'TechCo',
|
|
223
|
-
audience: 'developers',
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// This would be: generate`content for ${{ context }}`
|
|
227
|
-
// The object would be converted to YAML
|
|
228
|
-
const prompt = `content for\ncontext:\n brand: TechCo\n audience: developers`
|
|
229
|
-
|
|
230
|
-
await generate('text', prompt)
|
|
231
|
-
|
|
232
|
-
expect(mockAICall).toHaveBeenCalledWith('text', expect.stringContaining('brand: TechCo'), undefined)
|
|
233
|
-
})
|
|
234
|
-
})
|
|
235
|
-
|
|
236
157
|
// ============================================================================
|
|
237
158
|
// Options parameter tests
|
|
238
159
|
// ============================================================================
|
|
239
160
|
|
|
240
|
-
describe('generate options', () => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
expect(mockAICall).toHaveBeenCalledWith(
|
|
250
|
-
'text',
|
|
251
|
-
'test',
|
|
252
|
-
expect.objectContaining({ model: 'claude-opus-4-5' })
|
|
253
|
-
)
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
it('passes temperature option', async () => {
|
|
257
|
-
await generate('text', 'creative writing', { temperature: 0.9 })
|
|
258
|
-
|
|
259
|
-
expect(mockAICall).toHaveBeenCalledWith(
|
|
260
|
-
'text',
|
|
261
|
-
'creative writing',
|
|
262
|
-
expect.objectContaining({ temperature: 0.9 })
|
|
263
|
-
)
|
|
264
|
-
})
|
|
161
|
+
describe.skipIf(!hasGateway)('generate options', () => {
|
|
162
|
+
it('respects temperature option (low temperature = more deterministic)', async () => {
|
|
163
|
+
// Low temperature should give consistent results
|
|
164
|
+
const result1 = await generateText({
|
|
165
|
+
model: 'haiku',
|
|
166
|
+
prompt: 'Say exactly "hello" and nothing else.',
|
|
167
|
+
temperature: 0,
|
|
168
|
+
})
|
|
265
169
|
|
|
266
|
-
|
|
267
|
-
|
|
170
|
+
const result2 = await generateText({
|
|
171
|
+
model: 'haiku',
|
|
172
|
+
prompt: 'Say exactly "hello" and nothing else.',
|
|
173
|
+
temperature: 0,
|
|
174
|
+
})
|
|
268
175
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
expect.objectContaining({ maxTokens: 4000 })
|
|
273
|
-
)
|
|
176
|
+
// With temperature 0, responses should be very similar
|
|
177
|
+
expect(result1.text.toLowerCase()).toContain('hello')
|
|
178
|
+
expect(result2.text.toLowerCase()).toContain('hello')
|
|
274
179
|
})
|
|
275
180
|
|
|
276
|
-
it('
|
|
277
|
-
|
|
181
|
+
it('accepts maxTokens option without error', async () => {
|
|
182
|
+
// This test verifies the maxTokens option is passed through without error
|
|
183
|
+
// The actual truncation behavior is provider-dependent
|
|
184
|
+
const result = await generateText({
|
|
185
|
+
model: 'haiku',
|
|
186
|
+
prompt: 'Say "hello" and nothing else.',
|
|
187
|
+
maxTokens: 50,
|
|
188
|
+
})
|
|
278
189
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
expect.objectContaining({ thinking: 'high' })
|
|
283
|
-
)
|
|
190
|
+
// Just verify we got a response - maxTokens behavior varies by provider/gateway
|
|
191
|
+
expect(result.text).toBeDefined()
|
|
192
|
+
expect(typeof result.text).toBe('string')
|
|
284
193
|
})
|
|
285
194
|
|
|
286
|
-
it('passes
|
|
287
|
-
|
|
195
|
+
it('passes system prompt correctly', async () => {
|
|
196
|
+
const result = await generateText({
|
|
197
|
+
model: 'haiku',
|
|
198
|
+
system: 'You always respond with exactly one word.',
|
|
199
|
+
prompt: 'What is your favorite color?',
|
|
200
|
+
})
|
|
288
201
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
expect.objectContaining({ thinking: 10000 })
|
|
293
|
-
)
|
|
202
|
+
// With the system prompt, response should be short (ideally one word)
|
|
203
|
+
const wordCount = result.text.trim().split(/\s+/).length
|
|
204
|
+
expect(wordCount).toBeLessThanOrEqual(3) // Allow some flexibility
|
|
294
205
|
})
|
|
295
206
|
})
|
|
296
207
|
|
|
@@ -298,22 +209,22 @@ describe('generate options', () => {
|
|
|
298
209
|
// All convenience functions use generate
|
|
299
210
|
// ============================================================================
|
|
300
211
|
|
|
301
|
-
describe('convenience functions
|
|
212
|
+
describe('convenience functions documentation', () => {
|
|
302
213
|
it('documents the mapping', () => {
|
|
303
214
|
// This test documents the expected mappings
|
|
304
215
|
const mappings = {
|
|
305
|
-
'ai(prompt)':
|
|
306
|
-
'write(prompt)':
|
|
307
|
-
'code(prompt)': "
|
|
308
|
-
'list(prompt)':
|
|
309
|
-
'lists(prompt)':
|
|
310
|
-
|
|
311
|
-
'
|
|
312
|
-
'
|
|
313
|
-
'
|
|
314
|
-
'is(prompt)':
|
|
216
|
+
'ai(prompt)': 'generateText({ model, prompt })',
|
|
217
|
+
'write(prompt)': 'generateText({ model, prompt })',
|
|
218
|
+
'code(prompt)': "generateText({ model, system: 'code generator', prompt })",
|
|
219
|
+
'list(prompt)': 'generateObject({ model, schema: { items: [...] }, prompt })',
|
|
220
|
+
'lists(prompt)':
|
|
221
|
+
'generateObject({ model, schema: { listName1: [...], listName2: [...] }, prompt })',
|
|
222
|
+
'extract(prompt)': 'generateObject({ model, schema: { extracted: [...] }, prompt })',
|
|
223
|
+
'summarize(prompt)': "generateText({ model, system: 'summarizer', prompt })",
|
|
224
|
+
'diagram(prompt)': "generateText({ model, system: 'mermaid generator', prompt })",
|
|
225
|
+
'is(prompt)': 'generateObject({ model, schema: { result: boolean }, prompt })',
|
|
315
226
|
}
|
|
316
227
|
|
|
317
|
-
expect(Object.keys(mappings)).toHaveLength(
|
|
228
|
+
expect(Object.keys(mappings)).toHaveLength(9)
|
|
318
229
|
})
|
|
319
230
|
})
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { describe, it, expect,
|
|
19
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
20
20
|
import {
|
|
21
21
|
configure,
|
|
22
22
|
resetContext,
|
|
@@ -29,54 +29,14 @@ import {
|
|
|
29
29
|
getFlexThreshold,
|
|
30
30
|
getBatchThreshold,
|
|
31
31
|
isFlexAvailable,
|
|
32
|
-
} from '../src/context.js'
|
|
33
|
-
import { list, write, ai, is } from '../src/primitives.js'
|
|
34
|
-
import {
|
|
35
32
|
createBatchMap,
|
|
36
33
|
BatchMapPromise,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} from '../src/batch-map.js'
|
|
34
|
+
} from '../src/index.js'
|
|
35
|
+
import { captureOperation } from '../src/batch-map.js'
|
|
40
36
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
import { configureMemoryAdapter, clearBatches } from '../src/batch/memory.
|
|
44
|
-
|
|
45
|
-
// ============================================================================
|
|
46
|
-
// Mock Setup
|
|
47
|
-
// ============================================================================
|
|
48
|
-
|
|
49
|
-
vi.mock('../src/generate.js', () => ({
|
|
50
|
-
generateObject: vi.fn().mockImplementation(async ({ prompt, schema }) => {
|
|
51
|
-
// Simulate list generation
|
|
52
|
-
if (schema?.items) {
|
|
53
|
-
return {
|
|
54
|
-
object: {
|
|
55
|
-
items: [
|
|
56
|
-
'Building AI-First Startups in 2026',
|
|
57
|
-
'The Future of Remote Work',
|
|
58
|
-
'Sustainable Tech Growth',
|
|
59
|
-
'From Idea to MVP in 30 Days',
|
|
60
|
-
'Community-Led Product Development',
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// Simulate boolean
|
|
66
|
-
if (schema?.answer) {
|
|
67
|
-
return {
|
|
68
|
-
object: { answer: 'true' },
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// Default object
|
|
72
|
-
return { object: { result: 'Generated content' } }
|
|
73
|
-
}),
|
|
74
|
-
generateText: vi.fn().mockImplementation(async ({ prompt }) => {
|
|
75
|
-
return {
|
|
76
|
-
text: `Generated blog post for: ${prompt.slice(0, 50)}...`,
|
|
77
|
-
}
|
|
78
|
-
}),
|
|
79
|
-
}))
|
|
37
|
+
// Memory adapter for testing - simulates batch processing locally
|
|
38
|
+
// Import from .ts file for proper vite resolution
|
|
39
|
+
import { configureMemoryAdapter, clearBatches } from '../src/batch/memory.ts'
|
|
80
40
|
|
|
81
41
|
// ============================================================================
|
|
82
42
|
// Tests
|
|
@@ -84,7 +44,6 @@ vi.mock('../src/generate.js', () => ({
|
|
|
84
44
|
|
|
85
45
|
describe('Implicit Batch Processing', () => {
|
|
86
46
|
beforeEach(() => {
|
|
87
|
-
vi.clearAllMocks()
|
|
88
47
|
resetContext()
|
|
89
48
|
clearBatches()
|
|
90
49
|
configureMemoryAdapter({})
|
|
@@ -145,7 +104,7 @@ describe('Implicit Batch Processing', () => {
|
|
|
145
104
|
})
|
|
146
105
|
})
|
|
147
106
|
|
|
148
|
-
describe('Three-Tier Execution (immediate
|
|
107
|
+
describe('Three-Tier Execution (immediate -> flex -> batch)', () => {
|
|
149
108
|
it('getExecutionTier returns immediate for < flexThreshold items', () => {
|
|
150
109
|
configure({ batchMode: 'auto', flexThreshold: 5, batchThreshold: 500 })
|
|
151
110
|
|
|
@@ -267,8 +226,7 @@ describe('Implicit Batch Processing', () => {
|
|
|
267
226
|
|
|
268
227
|
// Create batch map - this enters recording mode for each item
|
|
269
228
|
const batchMap = createBatchMap(items, (item) => {
|
|
270
|
-
//
|
|
271
|
-
// Since we mocked generateText, we need to manually capture
|
|
229
|
+
// Capture operation for each item
|
|
272
230
|
captureOperation(`Write about: ${item}`, 'text', undefined, undefined)
|
|
273
231
|
recordedCount++
|
|
274
232
|
return `result_${item}`
|
|
@@ -306,7 +264,7 @@ describe('Implicit Batch Processing', () => {
|
|
|
306
264
|
})
|
|
307
265
|
})
|
|
308
266
|
|
|
309
|
-
it('supports
|
|
267
|
+
it('supports iteration over results', async () => {
|
|
310
268
|
configure({ batchMode: 'immediate' })
|
|
311
269
|
|
|
312
270
|
const items = ['X', 'Y']
|
|
@@ -334,20 +292,15 @@ describe('Implicit Batch Processing', () => {
|
|
|
334
292
|
})
|
|
335
293
|
|
|
336
294
|
describe('Full Workflow', () => {
|
|
337
|
-
it('list
|
|
295
|
+
it('list -> map -> batch flow works end-to-end', async () => {
|
|
338
296
|
// Configure for immediate execution (for testing)
|
|
339
297
|
configure({ batchMode: 'immediate', provider: 'openai', model: 'gpt-4o' })
|
|
340
298
|
|
|
341
|
-
// Step 1:
|
|
342
|
-
|
|
343
|
-
// so we access .items from the result
|
|
344
|
-
const result = await list`5 blog post titles about startups`
|
|
345
|
-
const titles = (result as any).items || result
|
|
346
|
-
expect(titles).toHaveLength(5)
|
|
299
|
+
// Step 1: Simulate getting titles (in production this would be AI-generated)
|
|
300
|
+
const titles = ['Title 1', 'Title 2', 'Title 3', 'Title 4', 'Title 5']
|
|
347
301
|
|
|
348
302
|
// Step 2: Map to blog posts
|
|
349
303
|
// In the real implementation, this would capture operations
|
|
350
|
-
// For this test, we simulate the batch map behavior
|
|
351
304
|
const batchMap = createBatchMap(titles, (title: string) => {
|
|
352
305
|
// Capture the write operation
|
|
353
306
|
captureOperation(`Write a blog post about: ${title}`, 'text')
|
|
@@ -384,12 +337,16 @@ describe('Implicit Batch Processing', () => {
|
|
|
384
337
|
const items = ['Test']
|
|
385
338
|
const batchMap = new BatchMapPromise<string>(
|
|
386
339
|
items,
|
|
387
|
-
[
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
340
|
+
[
|
|
341
|
+
[
|
|
342
|
+
{
|
|
343
|
+
id: 'op_1',
|
|
344
|
+
prompt: 'Test prompt',
|
|
345
|
+
itemPlaceholder: 'Test',
|
|
346
|
+
type: 'text' as const,
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
],
|
|
393
350
|
{ deferred: true }
|
|
394
351
|
)
|
|
395
352
|
|