ai-functions 2.1.3 → 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 -1
- package/CHANGELOG.md +55 -1
- package/README.md +38 -0
- package/dist/ai-promise.d.ts +3 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +135 -64
- 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 +51 -858
- 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.map +1 -1
- package/dist/budget.js +27 -14
- package/dist/budget.js.map +1 -1
- package/dist/cache.d.ts +23 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +36 -15
- package/dist/cache.js.map +1 -1
- package/dist/context.d.ts +26 -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 +8 -0
- 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 -18
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -18
- 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 +66 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +115 -8
- package/dist/retry.js.map +1 -1
- package/dist/schema.js +2 -2
- 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 +66 -4
- package/dist/tool-orchestration.d.ts.map +1 -1
- package/dist/tool-orchestration.js +123 -23
- package/dist/tool-orchestration.js.map +1 -1
- 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 +28 -25
- package/src/ai-promise.ts +226 -140
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +69 -1176
- 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 +31 -18
- package/src/cache.ts +45 -17
- package/src/context.ts +106 -77
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +60 -36
- 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 -28
- package/src/index.ts +119 -21
- 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 +144 -18
- package/src/schema.ts +8 -8
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +213 -48
- 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/batch-autosubmit-errors.test.ts +49 -37
- package/test/batch-blog-posts.test.ts +87 -129
- 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/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/schema.test.ts +55 -19
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +270 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/LICENSE +0 -21
- 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
|
@@ -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
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests: retry/CB/fallback machinery reads policy data from
|
|
3
|
+
* `language-models`. Validates that the per-model `ModelPolicy` flows through
|
|
4
|
+
* the resilience classes correctly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
8
|
+
import { RetryPolicy, CircuitBreaker, FallbackChain } from '../src/retry.js'
|
|
9
|
+
import { tiersForModel, modelSupportsTier, modelPolicyFor } from '../src/index.js'
|
|
10
|
+
|
|
11
|
+
describe('RetryPolicy.forModel', () => {
|
|
12
|
+
it('uses frontier-provider settings for sonnet', async () => {
|
|
13
|
+
const policy = RetryPolicy.forModel('sonnet')
|
|
14
|
+
let attempts = 0
|
|
15
|
+
const op = vi.fn(async () => {
|
|
16
|
+
attempts++
|
|
17
|
+
if (attempts < 2) {
|
|
18
|
+
const err = new Error('rate limit')
|
|
19
|
+
;(err as Error & { status?: number }).status = 429
|
|
20
|
+
throw err
|
|
21
|
+
}
|
|
22
|
+
return 'ok'
|
|
23
|
+
})
|
|
24
|
+
const result = await policy.execute(() => op())
|
|
25
|
+
expect(result).toBe('ok')
|
|
26
|
+
expect(attempts).toBe(2)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('forModel respects per-call overrides', async () => {
|
|
30
|
+
const policy = RetryPolicy.forModel('sonnet', { maxRetries: 0 })
|
|
31
|
+
let attempts = 0
|
|
32
|
+
const op = async () => {
|
|
33
|
+
attempts++
|
|
34
|
+
const err = new Error('rate limit')
|
|
35
|
+
;(err as Error & { status?: number }).status = 429
|
|
36
|
+
throw err
|
|
37
|
+
}
|
|
38
|
+
await expect(policy.execute(() => op())).rejects.toThrow()
|
|
39
|
+
expect(attempts).toBe(1) // No retries
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('CircuitBreaker.forModel', () => {
|
|
44
|
+
it('uses frontier-provider failure threshold for sonnet', async () => {
|
|
45
|
+
const breaker = CircuitBreaker.forModel('sonnet')
|
|
46
|
+
// Frontier threshold is 8 — eight failures should still report state
|
|
47
|
+
// before opening on the 8th.
|
|
48
|
+
for (let i = 0; i < 7; i++) {
|
|
49
|
+
try {
|
|
50
|
+
await breaker.execute(async () => {
|
|
51
|
+
throw new Error('fail')
|
|
52
|
+
})
|
|
53
|
+
} catch {
|
|
54
|
+
// expected
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Still closed (threshold is 8)
|
|
58
|
+
expect(breaker.state).toBe('closed')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('forModel respects per-call overrides', async () => {
|
|
62
|
+
const breaker = CircuitBreaker.forModel('sonnet', { failureThreshold: 1 })
|
|
63
|
+
try {
|
|
64
|
+
await breaker.execute(async () => {
|
|
65
|
+
throw new Error('fail')
|
|
66
|
+
})
|
|
67
|
+
} catch {
|
|
68
|
+
// expected
|
|
69
|
+
}
|
|
70
|
+
expect(breaker.state).toBe('open')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('FallbackChain.forModel', () => {
|
|
75
|
+
it('builds a chain from policy.fallbackChain plus the primary model id', async () => {
|
|
76
|
+
const seen: string[] = []
|
|
77
|
+
const chain = FallbackChain.forModel<string, void>('sonnet', async (modelId) => {
|
|
78
|
+
seen.push(modelId)
|
|
79
|
+
throw new Error('fail')
|
|
80
|
+
})
|
|
81
|
+
await expect(chain.execute()).rejects.toThrow('All fallback models failed')
|
|
82
|
+
// First call is the primary alias, rest are fallbacks
|
|
83
|
+
expect(seen[0]).toBe('anthropic/claude-sonnet-4.5')
|
|
84
|
+
expect(seen.length).toBeGreaterThan(1)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('returns first successful model result', async () => {
|
|
88
|
+
let calls = 0
|
|
89
|
+
const chain = FallbackChain.forModel<string, void>('sonnet', async (modelId) => {
|
|
90
|
+
calls++
|
|
91
|
+
if (calls === 1) throw new Error('first fails')
|
|
92
|
+
return modelId
|
|
93
|
+
})
|
|
94
|
+
const result = await chain.execute()
|
|
95
|
+
expect(calls).toBe(2)
|
|
96
|
+
expect(result).toBeTruthy()
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('tier helpers', () => {
|
|
101
|
+
it('tiersForModel returns expected tiers', () => {
|
|
102
|
+
expect(tiersForModel('sonnet')).toContain('immediate')
|
|
103
|
+
expect(tiersForModel('sonnet')).toContain('batch')
|
|
104
|
+
expect(tiersForModel('gpt-4o')).toContain('flex')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('modelSupportsTier checks eligibility', () => {
|
|
108
|
+
expect(modelSupportsTier('gpt-4o', 'flex')).toBe(true)
|
|
109
|
+
expect(modelSupportsTier('sonnet', 'flex')).toBe(false)
|
|
110
|
+
expect(modelSupportsTier('sonnet', 'immediate')).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('modelPolicyFor is re-exported', () => {
|
|
114
|
+
const p = modelPolicyFor('sonnet')
|
|
115
|
+
expect(p.$type).toBe('ModelPolicy')
|
|
116
|
+
})
|
|
117
|
+
})
|
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
|
|