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/test/schema.test.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Tests for schema conversion
|
|
3
3
|
*
|
|
4
4
|
* These are pure unit tests - no AI calls needed.
|
|
5
|
+
* Uses type-safe instanceof checks instead of accessing internal _def property.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { describe, it, expect } from 'vitest'
|
|
@@ -12,54 +13,67 @@ describe('schema', () => {
|
|
|
12
13
|
describe('string types', () => {
|
|
13
14
|
it('converts simple string description to z.string()', () => {
|
|
14
15
|
const result = schema('User name')
|
|
15
|
-
expect(result.
|
|
16
|
-
expect(result.
|
|
16
|
+
expect(result instanceof z.ZodString).toBe(true)
|
|
17
|
+
expect(result.description).toBe('User name')
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
it('converts (number) hint to z.number()', () => {
|
|
20
21
|
const result = schema('User age (number)')
|
|
21
|
-
expect(result.
|
|
22
|
-
expect(result.
|
|
22
|
+
expect(result instanceof z.ZodNumber).toBe(true)
|
|
23
|
+
expect(result.description).toBe('User age')
|
|
23
24
|
})
|
|
24
25
|
|
|
25
26
|
it('converts (boolean) hint to z.boolean()', () => {
|
|
26
27
|
const result = schema('Is active (boolean)')
|
|
27
|
-
expect(result.
|
|
28
|
-
expect(result.
|
|
28
|
+
expect(result instanceof z.ZodBoolean).toBe(true)
|
|
29
|
+
expect(result.description).toBe('Is active')
|
|
29
30
|
})
|
|
30
31
|
|
|
31
32
|
it('converts (integer) hint to z.number().int()', () => {
|
|
32
33
|
const result = schema('Item count (integer)')
|
|
33
|
-
expect(result.
|
|
34
|
-
|
|
34
|
+
expect(result instanceof z.ZodNumber).toBe(true)
|
|
35
|
+
// Verify it's an integer by testing validation behavior
|
|
36
|
+
expect(result.safeParse(5).success).toBe(true)
|
|
37
|
+
expect(result.safeParse(5.5).success).toBe(false)
|
|
35
38
|
})
|
|
36
39
|
|
|
37
40
|
it('converts (date) hint to z.string().datetime()', () => {
|
|
38
41
|
const result = schema('Created at (date)')
|
|
39
|
-
expect(result.
|
|
40
|
-
|
|
42
|
+
expect(result instanceof z.ZodString).toBe(true)
|
|
43
|
+
// Verify it validates datetime format
|
|
44
|
+
expect(result.safeParse('2024-01-15T10:30:00Z').success).toBe(true)
|
|
45
|
+
expect(result.safeParse('not-a-date').success).toBe(false)
|
|
41
46
|
})
|
|
42
47
|
})
|
|
43
48
|
|
|
44
49
|
describe('enum types', () => {
|
|
45
50
|
it('converts pipe-separated values to z.enum()', () => {
|
|
46
51
|
const result = schema('pending | done | cancelled')
|
|
47
|
-
expect(result.
|
|
48
|
-
|
|
52
|
+
expect(result instanceof z.ZodEnum).toBe(true)
|
|
53
|
+
// Verify enum values through validation
|
|
54
|
+
expect(result.safeParse('pending').success).toBe(true)
|
|
55
|
+
expect(result.safeParse('done').success).toBe(true)
|
|
56
|
+
expect(result.safeParse('cancelled').success).toBe(true)
|
|
57
|
+
expect(result.safeParse('invalid').success).toBe(false)
|
|
49
58
|
})
|
|
50
59
|
|
|
51
60
|
it('handles spaces around pipe', () => {
|
|
52
61
|
const result = schema('yes | no | maybe')
|
|
53
|
-
expect(result.
|
|
62
|
+
expect(result instanceof z.ZodEnum).toBe(true)
|
|
63
|
+
expect(result.safeParse('yes').success).toBe(true)
|
|
64
|
+
expect(result.safeParse('no').success).toBe(true)
|
|
65
|
+
expect(result.safeParse('maybe').success).toBe(true)
|
|
54
66
|
})
|
|
55
67
|
})
|
|
56
68
|
|
|
57
69
|
describe('array types', () => {
|
|
58
70
|
it('converts [string] to z.array(z.string())', () => {
|
|
59
71
|
const result = schema(['List of items'])
|
|
60
|
-
expect(result.
|
|
61
|
-
expect(result.
|
|
62
|
-
|
|
72
|
+
expect(result instanceof z.ZodArray).toBe(true)
|
|
73
|
+
expect(result.description).toBe('List of items')
|
|
74
|
+
// Verify array of strings validation
|
|
75
|
+
expect(result.safeParse(['a', 'b', 'c']).success).toBe(true)
|
|
76
|
+
expect(result.safeParse([1, 2, 3]).success).toBe(false)
|
|
63
77
|
})
|
|
64
78
|
})
|
|
65
79
|
|
|
@@ -69,7 +83,10 @@ describe('schema', () => {
|
|
|
69
83
|
name: 'User name',
|
|
70
84
|
age: 'Age (number)',
|
|
71
85
|
})
|
|
72
|
-
expect(result.
|
|
86
|
+
expect(result instanceof z.ZodObject).toBe(true)
|
|
87
|
+
// Verify object validation
|
|
88
|
+
expect(result.safeParse({ name: 'John', age: 30 }).success).toBe(true)
|
|
89
|
+
expect(result.safeParse({ name: 'John', age: 'thirty' }).success).toBe(false)
|
|
73
90
|
})
|
|
74
91
|
|
|
75
92
|
it('handles nested objects', () => {
|
|
@@ -81,7 +98,16 @@ describe('schema', () => {
|
|
|
81
98
|
},
|
|
82
99
|
},
|
|
83
100
|
})
|
|
84
|
-
expect(result.
|
|
101
|
+
expect(result instanceof z.ZodObject).toBe(true)
|
|
102
|
+
// Verify nested object validation
|
|
103
|
+
expect(
|
|
104
|
+
result.safeParse({
|
|
105
|
+
user: {
|
|
106
|
+
name: 'John',
|
|
107
|
+
profile: { bio: 'A developer' },
|
|
108
|
+
},
|
|
109
|
+
}).success
|
|
110
|
+
).toBe(true)
|
|
85
111
|
})
|
|
86
112
|
|
|
87
113
|
it('handles mixed types in object', () => {
|
|
@@ -92,7 +118,17 @@ describe('schema', () => {
|
|
|
92
118
|
status: 'pending | done',
|
|
93
119
|
tags: ['Tags'],
|
|
94
120
|
})
|
|
95
|
-
expect(result.
|
|
121
|
+
expect(result instanceof z.ZodObject).toBe(true)
|
|
122
|
+
// Verify mixed type validation
|
|
123
|
+
expect(
|
|
124
|
+
result.safeParse({
|
|
125
|
+
name: 'Test',
|
|
126
|
+
count: 5,
|
|
127
|
+
active: true,
|
|
128
|
+
status: 'pending',
|
|
129
|
+
tags: ['tag1', 'tag2'],
|
|
130
|
+
}).success
|
|
131
|
+
).toBe(true)
|
|
96
132
|
})
|
|
97
133
|
})
|
|
98
134
|
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for streaming integration with AIPromise
|
|
3
|
+
*
|
|
4
|
+
* These tests verify the streaming API for AI functions:
|
|
5
|
+
* - ai().stream() returns AsyncIterable of chunks
|
|
6
|
+
* - Streaming with property access
|
|
7
|
+
* - Streaming with dependencies
|
|
8
|
+
* - Backpressure handling
|
|
9
|
+
* - Stream transformation (map/filter)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
13
|
+
import { AIPromise, createTextPromise, createObjectPromise, createListPromise } from '../src/ai-promise.js'
|
|
14
|
+
import { ai, list, write } from '../src/primitives.js'
|
|
15
|
+
|
|
16
|
+
// Skip integration tests if no gateway configured
|
|
17
|
+
const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Unit Tests (Mock-based)
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
describe('AIPromise.stream() - Unit Tests', () => {
|
|
24
|
+
describe('Basic Streaming', () => {
|
|
25
|
+
it('should have a stream() method on AIPromise', () => {
|
|
26
|
+
const promise = new AIPromise<string>('test prompt', { type: 'text' })
|
|
27
|
+
expect(typeof promise.stream).toBe('function')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('stream() should return an object with AsyncIterable interface', async () => {
|
|
31
|
+
const promise = new AIPromise<string>('test prompt', { type: 'text' })
|
|
32
|
+
const stream = promise.stream()
|
|
33
|
+
|
|
34
|
+
// Should have Symbol.asyncIterator
|
|
35
|
+
expect(typeof stream[Symbol.asyncIterator]).toBe('function')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('stream() should return an object with textStream property', async () => {
|
|
39
|
+
const promise = new AIPromise<string>('test prompt', { type: 'text' })
|
|
40
|
+
const stream = promise.stream()
|
|
41
|
+
|
|
42
|
+
// Should have textStream for text generation
|
|
43
|
+
expect(stream.textStream).toBeDefined()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('stream() should return an object with partialObjectStream property', async () => {
|
|
47
|
+
const promise = new AIPromise<{ name: string }>('test prompt', { type: 'object' })
|
|
48
|
+
const stream = promise.stream()
|
|
49
|
+
|
|
50
|
+
// Should have partialObjectStream for object generation
|
|
51
|
+
expect(stream.partialObjectStream).toBeDefined()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('StreamingAIPromise Interface', () => {
|
|
56
|
+
it('should be awaitable to get final result', async () => {
|
|
57
|
+
const promise = new AIPromise<string>('test prompt', { type: 'text' })
|
|
58
|
+
const stream = promise.stream()
|
|
59
|
+
|
|
60
|
+
// Should be thenable (Promise-like)
|
|
61
|
+
expect(typeof stream.then).toBe('function')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should have result property for final value', async () => {
|
|
65
|
+
const promise = new AIPromise<string>('test prompt', { type: 'text' })
|
|
66
|
+
const stream = promise.stream()
|
|
67
|
+
|
|
68
|
+
// result should be a Promise
|
|
69
|
+
expect(stream.result).toBeDefined()
|
|
70
|
+
expect(typeof stream.result.then).toBe('function')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should support abort controller', async () => {
|
|
74
|
+
const promise = new AIPromise<string>('test prompt', { type: 'text' })
|
|
75
|
+
const controller = new AbortController()
|
|
76
|
+
const stream = promise.stream({ abortSignal: controller.signal })
|
|
77
|
+
|
|
78
|
+
expect(stream).toBeDefined()
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('Property Access on Streaming Results', () => {
|
|
83
|
+
it('should track property access for schema inference on stream', () => {
|
|
84
|
+
const promise = new AIPromise<{ summary: string; points: string[] }>('test', { type: 'object' })
|
|
85
|
+
|
|
86
|
+
// Access properties
|
|
87
|
+
const { summary, points } = promise
|
|
88
|
+
|
|
89
|
+
// The stream should include these properties
|
|
90
|
+
const stream = promise.stream()
|
|
91
|
+
expect(stream).toBeDefined()
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Integration Tests (Real AI calls)
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
describe.skipIf(!hasGateway)('AIPromise.stream() - Integration Tests', () => {
|
|
101
|
+
describe('Text Streaming', () => {
|
|
102
|
+
it('should stream text chunks from ai template', async () => {
|
|
103
|
+
const promise = write`Say hello in 3 words`
|
|
104
|
+
const stream = promise.stream()
|
|
105
|
+
|
|
106
|
+
const chunks: string[] = []
|
|
107
|
+
for await (const chunk of stream.textStream) {
|
|
108
|
+
chunks.push(chunk)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
expect(chunks.length).toBeGreaterThan(0)
|
|
112
|
+
const fullText = chunks.join('')
|
|
113
|
+
expect(fullText.length).toBeGreaterThan(0)
|
|
114
|
+
}, 60000)
|
|
115
|
+
|
|
116
|
+
it('should allow awaiting final result after streaming', async () => {
|
|
117
|
+
const promise = write`Say hello`
|
|
118
|
+
const stream = promise.stream()
|
|
119
|
+
|
|
120
|
+
// Consume stream
|
|
121
|
+
for await (const _ of stream.textStream) {
|
|
122
|
+
// consume
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Get final result
|
|
126
|
+
const result = await stream.result
|
|
127
|
+
expect(typeof result).toBe('string')
|
|
128
|
+
expect(result.length).toBeGreaterThan(0)
|
|
129
|
+
}, 60000)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('Object Streaming', () => {
|
|
133
|
+
it('should stream partial objects', async () => {
|
|
134
|
+
const promise = ai`Generate a person with name and age`
|
|
135
|
+
|
|
136
|
+
// Access properties to set schema
|
|
137
|
+
const { name, age } = promise
|
|
138
|
+
|
|
139
|
+
const stream = promise.stream()
|
|
140
|
+
|
|
141
|
+
const partials: unknown[] = []
|
|
142
|
+
for await (const partial of stream.partialObjectStream) {
|
|
143
|
+
partials.push(partial)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
expect(partials.length).toBeGreaterThan(0)
|
|
147
|
+
|
|
148
|
+
// Last partial should have complete object
|
|
149
|
+
const final = partials[partials.length - 1] as { name?: string; age?: number }
|
|
150
|
+
expect(final.name).toBeDefined()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should support property access on stream result', async () => {
|
|
154
|
+
const promise = ai`Generate recipe: name, ingredients list, steps list`
|
|
155
|
+
const { name, ingredients, steps } = promise
|
|
156
|
+
|
|
157
|
+
const stream = promise.stream()
|
|
158
|
+
const result = await stream.result
|
|
159
|
+
|
|
160
|
+
expect(result).toHaveProperty('name')
|
|
161
|
+
expect(result).toHaveProperty('ingredients')
|
|
162
|
+
expect(result).toHaveProperty('steps')
|
|
163
|
+
}, 120000)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
describe('List Streaming', () => {
|
|
167
|
+
it('should stream list items one by one', async () => {
|
|
168
|
+
const promise = list`3 colors`
|
|
169
|
+
const stream = promise.stream()
|
|
170
|
+
|
|
171
|
+
const items: string[] = []
|
|
172
|
+
for await (const item of stream) {
|
|
173
|
+
items.push(item as string)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
expect(items.length).toBeGreaterThanOrEqual(3)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('Streaming with Dependencies', () => {
|
|
181
|
+
it('should resolve dependencies before streaming', async () => {
|
|
182
|
+
const topic = ai`Pick a random topic: science, art, or music`
|
|
183
|
+
const essay = write`Write a paragraph about ${topic}`
|
|
184
|
+
|
|
185
|
+
const stream = essay.stream()
|
|
186
|
+
|
|
187
|
+
const chunks: string[] = []
|
|
188
|
+
for await (const chunk of stream.textStream) {
|
|
189
|
+
chunks.push(chunk)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const fullText = chunks.join('')
|
|
193
|
+
expect(fullText.length).toBeGreaterThan(50)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('should handle mixed streaming and non-streaming in pipeline', async () => {
|
|
197
|
+
// First get a topic (non-streaming)
|
|
198
|
+
const topic = await ai`Pick: TypeScript or Python`
|
|
199
|
+
|
|
200
|
+
// Then stream based on that result
|
|
201
|
+
const explanation = write`Explain why ${topic} is great`
|
|
202
|
+
const stream = explanation.stream()
|
|
203
|
+
|
|
204
|
+
const result = await stream.result
|
|
205
|
+
expect(result.length).toBeGreaterThan(0)
|
|
206
|
+
}, 120000)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('Backpressure Handling', () => {
|
|
210
|
+
it('should handle slow consumers without memory issues', async () => {
|
|
211
|
+
const promise = write`Generate a short story about a dragon in 2 sentences`
|
|
212
|
+
const stream = promise.stream()
|
|
213
|
+
|
|
214
|
+
const chunks: string[] = []
|
|
215
|
+
for await (const chunk of stream.textStream) {
|
|
216
|
+
// Simulate slow consumer
|
|
217
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
218
|
+
chunks.push(chunk)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
expect(chunks.length).toBeGreaterThan(0)
|
|
222
|
+
}, 120000)
|
|
223
|
+
|
|
224
|
+
it('should support early termination/cancellation', async () => {
|
|
225
|
+
const controller = new AbortController()
|
|
226
|
+
const promise = write`Generate a very long story`
|
|
227
|
+
const stream = promise.stream({ abortSignal: controller.signal })
|
|
228
|
+
|
|
229
|
+
const chunks: string[] = []
|
|
230
|
+
let count = 0
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
for await (const chunk of stream.textStream) {
|
|
234
|
+
chunks.push(chunk)
|
|
235
|
+
count++
|
|
236
|
+
if (count >= 5) {
|
|
237
|
+
controller.abort()
|
|
238
|
+
break
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
// AbortError is expected
|
|
243
|
+
if (!(error instanceof Error && error.name === 'AbortError')) {
|
|
244
|
+
throw error
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Should have stopped early
|
|
249
|
+
expect(count).toBeLessThanOrEqual(10)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
describe('Stream Transformation', () => {
|
|
254
|
+
it('should support map on streaming list', async () => {
|
|
255
|
+
const colors = list`3 colors`
|
|
256
|
+
|
|
257
|
+
// Map should work on stream results
|
|
258
|
+
const result = await colors.map(color => ({
|
|
259
|
+
color,
|
|
260
|
+
hex: ai`hex code for ${color}`,
|
|
261
|
+
}))
|
|
262
|
+
|
|
263
|
+
expect(Array.isArray(result)).toBe(true)
|
|
264
|
+
expect(result.length).toBeGreaterThanOrEqual(3)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should support filter-like operations via streaming', async () => {
|
|
268
|
+
const numbers = list`5 random numbers between 1-100`
|
|
269
|
+
const stream = numbers.stream()
|
|
270
|
+
|
|
271
|
+
const allItems: string[] = []
|
|
272
|
+
for await (const item of stream) {
|
|
273
|
+
allItems.push(item as string)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
expect(allItems.length).toBe(5)
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// Stream Batching Tests
|
|
283
|
+
// ============================================================================
|
|
284
|
+
|
|
285
|
+
describe('Stream Batching', () => {
|
|
286
|
+
it('should support streaming mode in batch operations', async () => {
|
|
287
|
+
const promise = new AIPromise<string[]>('test', { type: 'list' })
|
|
288
|
+
const stream = promise.stream()
|
|
289
|
+
|
|
290
|
+
// Stream batching should be supported
|
|
291
|
+
expect(stream).toBeDefined()
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// Type Safety Tests
|
|
297
|
+
// ============================================================================
|
|
298
|
+
|
|
299
|
+
describe('Streaming Type Safety', () => {
|
|
300
|
+
it('should preserve types through streaming', () => {
|
|
301
|
+
// Text streaming should yield strings
|
|
302
|
+
const textPromise = createTextPromise('test')
|
|
303
|
+
const textStream = textPromise.stream()
|
|
304
|
+
expect(textStream.textStream).toBeDefined()
|
|
305
|
+
|
|
306
|
+
// Object streaming should yield partial objects
|
|
307
|
+
const objectPromise = createObjectPromise<{ name: string }>('test')
|
|
308
|
+
const objectStream = objectPromise.stream()
|
|
309
|
+
expect(objectStream.partialObjectStream).toBeDefined()
|
|
310
|
+
|
|
311
|
+
// List streaming should yield items
|
|
312
|
+
const listPromise = createListPromise('test')
|
|
313
|
+
const listStream = listPromise.stream()
|
|
314
|
+
expect(listStream).toBeDefined()
|
|
315
|
+
})
|
|
316
|
+
})
|