@vibe-lang/runtime 0.2.5
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/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { Runtime, type AIProvider, type AIExecutionResult } from '../index';
|
|
4
|
+
|
|
5
|
+
// Mock AI provider that uses delays to simulate network latency
|
|
6
|
+
function createDelayedMockAI(delayMs: number, responseValue: unknown = 'response'): AIProvider {
|
|
7
|
+
return {
|
|
8
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
9
|
+
await Bun.sleep(delayMs);
|
|
10
|
+
return { value: responseValue };
|
|
11
|
+
},
|
|
12
|
+
async generateCode(prompt: string): Promise<AIExecutionResult> {
|
|
13
|
+
await Bun.sleep(delayMs);
|
|
14
|
+
return { value: `let x = 1;` };
|
|
15
|
+
},
|
|
16
|
+
async askUser(prompt: string): Promise<string> {
|
|
17
|
+
return 'user input';
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Mock AI provider that returns different values based on prompt
|
|
23
|
+
function createMultiResponseMockAI(delayMs: number, responses: Record<string, unknown>): AIProvider {
|
|
24
|
+
return {
|
|
25
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
26
|
+
await Bun.sleep(delayMs);
|
|
27
|
+
// Find matching response key in prompt
|
|
28
|
+
for (const [key, value] of Object.entries(responses)) {
|
|
29
|
+
if (prompt.includes(key)) {
|
|
30
|
+
return { value };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { value: 'default' };
|
|
34
|
+
},
|
|
35
|
+
async generateCode(prompt: string): Promise<AIExecutionResult> {
|
|
36
|
+
await Bun.sleep(delayMs);
|
|
37
|
+
return { value: `let x = 1;` };
|
|
38
|
+
},
|
|
39
|
+
async askUser(prompt: string): Promise<string> {
|
|
40
|
+
return 'user input';
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('Async Parallel Execution Timing', () => {
|
|
46
|
+
describe('parallel AI calls', () => {
|
|
47
|
+
test('multiple async AI calls run in parallel (timing)', async () => {
|
|
48
|
+
const ast = parse(`
|
|
49
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
50
|
+
async let a = do "prompt1" m default
|
|
51
|
+
async let b = do "prompt2" m default
|
|
52
|
+
async let c = do "prompt3" m default
|
|
53
|
+
let result = a + " " + b + " " + c
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
const delayMs = 100;
|
|
57
|
+
const aiProvider = createDelayedMockAI(delayMs, 'result');
|
|
58
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
59
|
+
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
await runtime.run();
|
|
62
|
+
const elapsed = Date.now() - startTime;
|
|
63
|
+
|
|
64
|
+
// If running in parallel, should take ~100ms (+ overhead)
|
|
65
|
+
// If running sequentially, would take ~300ms
|
|
66
|
+
// Allow some margin for overhead
|
|
67
|
+
expect(elapsed).toBeLessThan(250); // Much less than 300ms sequential time
|
|
68
|
+
expect(runtime.getValue('result')).toBe('result result result');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('async calls with different responses', async () => {
|
|
72
|
+
const ast = parse(`
|
|
73
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
74
|
+
async let a = do "get_A" m default
|
|
75
|
+
async let b = do "get_B" m default
|
|
76
|
+
async let c = do "get_C" m default
|
|
77
|
+
let result = a + b + c
|
|
78
|
+
`);
|
|
79
|
+
|
|
80
|
+
const delayMs = 50;
|
|
81
|
+
const aiProvider = createMultiResponseMockAI(delayMs, {
|
|
82
|
+
'get_A': 'A',
|
|
83
|
+
'get_B': 'B',
|
|
84
|
+
'get_C': 'C',
|
|
85
|
+
});
|
|
86
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
87
|
+
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
await runtime.run();
|
|
90
|
+
const elapsed = Date.now() - startTime;
|
|
91
|
+
|
|
92
|
+
// Parallel execution should be fast
|
|
93
|
+
expect(elapsed).toBeLessThan(150);
|
|
94
|
+
expect(runtime.getValue('result')).toBe('ABC');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('six parallel calls with maxParallel=4 (throttling)', async () => {
|
|
98
|
+
const ast = parse(`
|
|
99
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
100
|
+
async let a = do "1" m default
|
|
101
|
+
async let b = do "2" m default
|
|
102
|
+
async let c = do "3" m default
|
|
103
|
+
async let d = do "4" m default
|
|
104
|
+
async let e = do "5" m default
|
|
105
|
+
async let f = do "6" m default
|
|
106
|
+
let result = a + b + c + d + e + f
|
|
107
|
+
`);
|
|
108
|
+
|
|
109
|
+
const delayMs = 50;
|
|
110
|
+
const aiProvider = createDelayedMockAI(delayMs, 'x');
|
|
111
|
+
const runtime = new Runtime(ast, aiProvider, { maxParallel: 4 });
|
|
112
|
+
|
|
113
|
+
const startTime = Date.now();
|
|
114
|
+
await runtime.run();
|
|
115
|
+
const elapsed = Date.now() - startTime;
|
|
116
|
+
|
|
117
|
+
// With maxParallel=4 and 6 operations each taking 50ms:
|
|
118
|
+
// Wave 1: 4 parallel (50ms)
|
|
119
|
+
// Wave 2: 2 parallel (50ms)
|
|
120
|
+
// Total: ~100ms (+ overhead), definitely less than 6*50=300ms
|
|
121
|
+
expect(elapsed).toBeLessThan(200);
|
|
122
|
+
expect(runtime.getValue('result')).toBe('xxxxxx');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('execution order', () => {
|
|
127
|
+
test('sync operations before async are executed first', async () => {
|
|
128
|
+
// Track execution order
|
|
129
|
+
const executionOrder: string[] = [];
|
|
130
|
+
|
|
131
|
+
const ast = parse(`
|
|
132
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
133
|
+
let sync1 = "first"
|
|
134
|
+
async let async1 = do "async_op" m default
|
|
135
|
+
let sync2 = "second"
|
|
136
|
+
let result = sync1 + " " + sync2
|
|
137
|
+
`);
|
|
138
|
+
|
|
139
|
+
const aiProvider: AIProvider = {
|
|
140
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
141
|
+
executionOrder.push('ai_start');
|
|
142
|
+
await Bun.sleep(50);
|
|
143
|
+
executionOrder.push('ai_end');
|
|
144
|
+
return { value: 'async_result' };
|
|
145
|
+
},
|
|
146
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
147
|
+
return { value: '' };
|
|
148
|
+
},
|
|
149
|
+
async askUser(): Promise<string> {
|
|
150
|
+
return '';
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
155
|
+
await runtime.run();
|
|
156
|
+
|
|
157
|
+
// Sync operations should complete before waiting for async
|
|
158
|
+
expect(runtime.getValue('sync1')).toBe('first');
|
|
159
|
+
expect(runtime.getValue('sync2')).toBe('second');
|
|
160
|
+
expect(runtime.getValue('result')).toBe('first second');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('implicit await when using async variable', async () => {
|
|
164
|
+
const executionLog: string[] = [];
|
|
165
|
+
|
|
166
|
+
const ast = parse(`
|
|
167
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
168
|
+
async let x = do "get_x" m default
|
|
169
|
+
let y = x + "_used"
|
|
170
|
+
`);
|
|
171
|
+
|
|
172
|
+
const aiProvider: AIProvider = {
|
|
173
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
174
|
+
executionLog.push('ai_called');
|
|
175
|
+
await Bun.sleep(50);
|
|
176
|
+
executionLog.push('ai_returned');
|
|
177
|
+
return { value: 'async_value' };
|
|
178
|
+
},
|
|
179
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
180
|
+
return { value: '' };
|
|
181
|
+
},
|
|
182
|
+
async askUser(): Promise<string> {
|
|
183
|
+
return '';
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
188
|
+
await runtime.run();
|
|
189
|
+
|
|
190
|
+
// Variable y should have awaited x
|
|
191
|
+
expect(runtime.getValue('y')).toBe('async_value_used');
|
|
192
|
+
expect(executionLog).toContain('ai_returned');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('destructuring waits for single AI call', async () => {
|
|
196
|
+
const ast = parse(`
|
|
197
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
198
|
+
async let {name: text, age: number} = do "get_person" m default
|
|
199
|
+
let greeting = "Hello " + name
|
|
200
|
+
`);
|
|
201
|
+
|
|
202
|
+
const aiProvider: AIProvider = {
|
|
203
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
204
|
+
await Bun.sleep(50);
|
|
205
|
+
return { value: { name: 'Alice', age: 30 } };
|
|
206
|
+
},
|
|
207
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
208
|
+
return { value: '' };
|
|
209
|
+
},
|
|
210
|
+
async askUser(): Promise<string> {
|
|
211
|
+
return '';
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
216
|
+
await runtime.run();
|
|
217
|
+
|
|
218
|
+
expect(runtime.getValue('name')).toBe('Alice');
|
|
219
|
+
expect(runtime.getValue('age')).toBe(30);
|
|
220
|
+
expect(runtime.getValue('greeting')).toBe('Hello Alice');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('mixed async operations', () => {
|
|
225
|
+
test('async and sync declarations interleaved', async () => {
|
|
226
|
+
const ast = parse(`
|
|
227
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
228
|
+
let a = 1
|
|
229
|
+
async let b = do "get_b" m default
|
|
230
|
+
let c = 2
|
|
231
|
+
async let d = do "get_d" m default
|
|
232
|
+
let e = 3
|
|
233
|
+
let sum = a + c + e
|
|
234
|
+
`);
|
|
235
|
+
|
|
236
|
+
const delayMs = 50;
|
|
237
|
+
const aiProvider = createMultiResponseMockAI(delayMs, {
|
|
238
|
+
'get_b': 'B',
|
|
239
|
+
'get_d': 'D',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
243
|
+
const startTime = Date.now();
|
|
244
|
+
await runtime.run();
|
|
245
|
+
const elapsed = Date.now() - startTime;
|
|
246
|
+
|
|
247
|
+
// Sync operations should complete quickly
|
|
248
|
+
expect(runtime.getValue('sum')).toBe(6);
|
|
249
|
+
// Async operations should complete in parallel
|
|
250
|
+
expect(elapsed).toBeLessThan(150);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('multiple async destructurings run in parallel', async () => {
|
|
254
|
+
const ast = parse(`
|
|
255
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
256
|
+
async let {x: number, y: number} = do "get_coords" m default
|
|
257
|
+
async let {name: text, age: number} = do "get_person" m default
|
|
258
|
+
let result = name + " at " + x + "," + y
|
|
259
|
+
`);
|
|
260
|
+
|
|
261
|
+
const delayMs = 75;
|
|
262
|
+
const aiProvider: AIProvider = {
|
|
263
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
264
|
+
await Bun.sleep(delayMs);
|
|
265
|
+
if (prompt.includes('coords')) {
|
|
266
|
+
return { value: { x: 10, y: 20 } };
|
|
267
|
+
}
|
|
268
|
+
return { value: { name: 'Bob', age: 25 } };
|
|
269
|
+
},
|
|
270
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
271
|
+
return { value: '' };
|
|
272
|
+
},
|
|
273
|
+
async askUser(): Promise<string> {
|
|
274
|
+
return '';
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
279
|
+
const startTime = Date.now();
|
|
280
|
+
await runtime.run();
|
|
281
|
+
const elapsed = Date.now() - startTime;
|
|
282
|
+
|
|
283
|
+
// Both should run in parallel
|
|
284
|
+
expect(elapsed).toBeLessThan(200); // Less than 150ms * 2 = 300ms
|
|
285
|
+
expect(runtime.getValue('result')).toBe('Bob at 10,20');
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('async const declarations', () => {
|
|
290
|
+
test('async const preserves const property', async () => {
|
|
291
|
+
const ast = parse(`
|
|
292
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
293
|
+
async const x = do "get_value" m default
|
|
294
|
+
let y = x + "_suffix"
|
|
295
|
+
`);
|
|
296
|
+
|
|
297
|
+
const aiProvider = createDelayedMockAI(50, 'const_value');
|
|
298
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
299
|
+
await runtime.run();
|
|
300
|
+
|
|
301
|
+
expect(runtime.getValue('x')).toBe('const_value');
|
|
302
|
+
expect(runtime.getValue('y')).toBe('const_value_suffix');
|
|
303
|
+
|
|
304
|
+
// Verify const property is preserved
|
|
305
|
+
const state = runtime.getState();
|
|
306
|
+
expect(state.callStack[0].locals['x'].isConst).toBe(true);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('multiple async consts in parallel', async () => {
|
|
310
|
+
const ast = parse(`
|
|
311
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
312
|
+
async const a = do "get_a" m default
|
|
313
|
+
async const b = do "get_b" m default
|
|
314
|
+
async const c = do "get_c" m default
|
|
315
|
+
let result = a + b + c
|
|
316
|
+
`);
|
|
317
|
+
|
|
318
|
+
const delayMs = 60;
|
|
319
|
+
const aiProvider = createMultiResponseMockAI(delayMs, {
|
|
320
|
+
'get_a': '1',
|
|
321
|
+
'get_b': '2',
|
|
322
|
+
'get_c': '3',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
326
|
+
const startTime = Date.now();
|
|
327
|
+
await runtime.run();
|
|
328
|
+
const elapsed = Date.now() - startTime;
|
|
329
|
+
|
|
330
|
+
expect(elapsed).toBeLessThan(150); // Parallel, not 180ms
|
|
331
|
+
expect(runtime.getValue('result')).toBe('123');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('private async declarations', () => {
|
|
336
|
+
test('async let private preserves private property', async () => {
|
|
337
|
+
const ast = parse(`
|
|
338
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
339
|
+
async let private secret = do "get_secret" m default
|
|
340
|
+
let visible = "public"
|
|
341
|
+
`);
|
|
342
|
+
|
|
343
|
+
const aiProvider = createDelayedMockAI(50, 'secret_value');
|
|
344
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
345
|
+
await runtime.run();
|
|
346
|
+
|
|
347
|
+
expect(runtime.getValue('secret')).toBe('secret_value');
|
|
348
|
+
|
|
349
|
+
// Verify private property is preserved
|
|
350
|
+
const state = runtime.getState();
|
|
351
|
+
expect(state.callStack[0].locals['secret'].isPrivate).toBe(true);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe('standalone async (fire-and-forget)', () => {
|
|
356
|
+
test('standalone async do executes', async () => {
|
|
357
|
+
let aiCalled = false;
|
|
358
|
+
|
|
359
|
+
const ast = parse(`
|
|
360
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
361
|
+
async do "fire_and_forget" m default
|
|
362
|
+
let x = "done"
|
|
363
|
+
`);
|
|
364
|
+
|
|
365
|
+
const aiProvider: AIProvider = {
|
|
366
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
367
|
+
aiCalled = true;
|
|
368
|
+
await Bun.sleep(50);
|
|
369
|
+
return { value: 'ignored' };
|
|
370
|
+
},
|
|
371
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
372
|
+
return { value: '' };
|
|
373
|
+
},
|
|
374
|
+
async askUser(): Promise<string> {
|
|
375
|
+
return '';
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
380
|
+
await runtime.run();
|
|
381
|
+
|
|
382
|
+
expect(aiCalled).toBe(true);
|
|
383
|
+
expect(runtime.getValue('x')).toBe('done');
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('error handling in parallel', () => {
|
|
388
|
+
test('error in one async does not block others', async () => {
|
|
389
|
+
const ast = parse(`
|
|
390
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
391
|
+
async let good1 = do "good1" m default
|
|
392
|
+
async let bad = do "bad" m default
|
|
393
|
+
async let good2 = do "good2" m default
|
|
394
|
+
`);
|
|
395
|
+
|
|
396
|
+
let callCount = 0;
|
|
397
|
+
const aiProvider: AIProvider = {
|
|
398
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
399
|
+
callCount++;
|
|
400
|
+
await Bun.sleep(30);
|
|
401
|
+
if (prompt.includes('bad')) {
|
|
402
|
+
throw new Error('Simulated failure');
|
|
403
|
+
}
|
|
404
|
+
return { value: 'success' };
|
|
405
|
+
},
|
|
406
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
407
|
+
return { value: '' };
|
|
408
|
+
},
|
|
409
|
+
async askUser(): Promise<string> {
|
|
410
|
+
return '';
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
415
|
+
await runtime.run();
|
|
416
|
+
|
|
417
|
+
// All three should have been called
|
|
418
|
+
expect(callCount).toBe(3);
|
|
419
|
+
|
|
420
|
+
// Good ones should have results
|
|
421
|
+
expect(runtime.getValue('good1')).toBe('success');
|
|
422
|
+
expect(runtime.getValue('good2')).toBe('success');
|
|
423
|
+
|
|
424
|
+
// Bad one should have error in VibeValue
|
|
425
|
+
const state = runtime.getState();
|
|
426
|
+
expect(state.callStack[0].locals['bad'].err).toBe(true); // err is now boolean
|
|
427
|
+
expect(state.callStack[0].locals['bad'].errDetails?.message).toBe('Simulated failure');
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
describe('async TS blocks', () => {
|
|
432
|
+
test('async TS block runs in parallel', async () => {
|
|
433
|
+
const ast = parse(`
|
|
434
|
+
async let x = ts() {
|
|
435
|
+
await Bun.sleep(50);
|
|
436
|
+
return 42;
|
|
437
|
+
}
|
|
438
|
+
async let y = ts() {
|
|
439
|
+
await Bun.sleep(50);
|
|
440
|
+
return 100;
|
|
441
|
+
}
|
|
442
|
+
let sum = x + y
|
|
443
|
+
`);
|
|
444
|
+
|
|
445
|
+
const aiProvider = createDelayedMockAI(50);
|
|
446
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
447
|
+
const startTime = Date.now();
|
|
448
|
+
await runtime.run();
|
|
449
|
+
const elapsed = Date.now() - startTime;
|
|
450
|
+
|
|
451
|
+
// Two 50ms operations should complete in ~50ms if parallel, not 100ms
|
|
452
|
+
expect(elapsed).toBeLessThan(120);
|
|
453
|
+
expect(runtime.getValue('sum')).toBe(142);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test('multiple async TS blocks with max parallel', async () => {
|
|
457
|
+
const ast = parse(`
|
|
458
|
+
async let a = ts() { await Bun.sleep(40); return 1; }
|
|
459
|
+
async let b = ts() { await Bun.sleep(40); return 2; }
|
|
460
|
+
async let c = ts() { await Bun.sleep(40); return 3; }
|
|
461
|
+
async let d = ts() { await Bun.sleep(40); return 4; }
|
|
462
|
+
async let e = ts() { await Bun.sleep(40); return 5; }
|
|
463
|
+
let sum = a + b + c + d + e
|
|
464
|
+
`);
|
|
465
|
+
|
|
466
|
+
const aiProvider = createDelayedMockAI(50);
|
|
467
|
+
const runtime = new Runtime(ast, aiProvider, { maxParallel: 4 });
|
|
468
|
+
const startTime = Date.now();
|
|
469
|
+
await runtime.run();
|
|
470
|
+
const elapsed = Date.now() - startTime;
|
|
471
|
+
|
|
472
|
+
// With maxParallel=4: wave1 (4 ops, 40ms) + wave2 (1 op, 40ms) = ~80ms
|
|
473
|
+
expect(elapsed).toBeLessThan(150);
|
|
474
|
+
expect(runtime.getValue('sum')).toBe(15);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test('fire-and-forget async TS block schedules execution', async () => {
|
|
478
|
+
const ast = parse(`
|
|
479
|
+
async ts() {
|
|
480
|
+
await Bun.sleep(30);
|
|
481
|
+
return "ignored";
|
|
482
|
+
}
|
|
483
|
+
let x = "done"
|
|
484
|
+
`);
|
|
485
|
+
|
|
486
|
+
const aiProvider = createDelayedMockAI(50);
|
|
487
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
488
|
+
await runtime.run();
|
|
489
|
+
|
|
490
|
+
// The program should complete and the TS block should have been awaited
|
|
491
|
+
// at block boundary (program end)
|
|
492
|
+
expect(runtime.getValue('x')).toBe('done');
|
|
493
|
+
// State should be completed (all async ops finished)
|
|
494
|
+
expect(runtime.getState().status).toBe('completed');
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe('mixed async types (AI + TS)', () => {
|
|
499
|
+
test('async AI and TS run in parallel', async () => {
|
|
500
|
+
const ast = parse(`
|
|
501
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
502
|
+
async let aiResult = do "get_value" m default
|
|
503
|
+
async let tsResult = ts() {
|
|
504
|
+
await Bun.sleep(50);
|
|
505
|
+
return "ts_value";
|
|
506
|
+
}
|
|
507
|
+
let combined = aiResult + "_" + tsResult
|
|
508
|
+
`);
|
|
509
|
+
|
|
510
|
+
const delayMs = 50;
|
|
511
|
+
const aiProvider = createDelayedMockAI(delayMs, 'ai_value');
|
|
512
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
513
|
+
const startTime = Date.now();
|
|
514
|
+
await runtime.run();
|
|
515
|
+
const elapsed = Date.now() - startTime;
|
|
516
|
+
|
|
517
|
+
// Both 50ms operations should run in parallel
|
|
518
|
+
expect(elapsed).toBeLessThan(120);
|
|
519
|
+
expect(runtime.getValue('combined')).toBe('ai_value_ts_value');
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test('three different async types (AI, TS block, implicit await)', async () => {
|
|
523
|
+
const ast = parse(`
|
|
524
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
525
|
+
async let a = do "get_a" m default
|
|
526
|
+
async let b = ts() { await Bun.sleep(40); return "B"; }
|
|
527
|
+
async let c = do "get_c" m default
|
|
528
|
+
let result = a + b + c
|
|
529
|
+
`);
|
|
530
|
+
|
|
531
|
+
const aiProvider = createMultiResponseMockAI(40, {
|
|
532
|
+
'get_a': 'A',
|
|
533
|
+
'get_c': 'C',
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
537
|
+
const startTime = Date.now();
|
|
538
|
+
await runtime.run();
|
|
539
|
+
const elapsed = Date.now() - startTime;
|
|
540
|
+
|
|
541
|
+
// All three should run in parallel (~40ms, not 120ms)
|
|
542
|
+
expect(elapsed).toBeLessThan(100);
|
|
543
|
+
expect(runtime.getValue('result')).toBe('ABC');
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { Runtime, AIProvider } from '../index';
|
|
4
|
+
|
|
5
|
+
// Mock AI provider for testing
|
|
6
|
+
function createMockProvider(doResponse: string): AIProvider {
|
|
7
|
+
return {
|
|
8
|
+
async execute() {
|
|
9
|
+
return { value: doResponse };
|
|
10
|
+
},
|
|
11
|
+
async generateCode() {
|
|
12
|
+
return { value: `let result = "generated"` };
|
|
13
|
+
},
|
|
14
|
+
async askUser(): Promise<string> {
|
|
15
|
+
return 'user input';
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('Runtime - Basic 1', () => {
|
|
21
|
+
test('vibe expression returns AI response into variable', async () => {
|
|
22
|
+
const ast = parse(`
|
|
23
|
+
model myModel = { name: "test", apiKey: "key", url: "http://test" }
|
|
24
|
+
let answer = vibe "what is 2 + 2?" myModel default
|
|
25
|
+
answer
|
|
26
|
+
`);
|
|
27
|
+
const provider = createMockProvider('4');
|
|
28
|
+
const runtime = new Runtime(ast, provider);
|
|
29
|
+
const result = await runtime.run();
|
|
30
|
+
|
|
31
|
+
expect(result).toBe('4');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('getValue returns variable value after run', async () => {
|
|
35
|
+
const ast = parse(`
|
|
36
|
+
model myModel = { name: "test", apiKey: "key", url: "http://test" }
|
|
37
|
+
let answer = vibe "what is 2 + 2?" myModel default
|
|
38
|
+
`);
|
|
39
|
+
const provider = createMockProvider('42');
|
|
40
|
+
const runtime = new Runtime(ast, provider);
|
|
41
|
+
await runtime.run();
|
|
42
|
+
|
|
43
|
+
expect(runtime.getValue('answer')).toBe('42');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('json variable with object literal used in prompt', async () => {
|
|
47
|
+
const ast = parse(`
|
|
48
|
+
model myModel = { name: "test", apiKey: "key", url: "http://test" }
|
|
49
|
+
let userData: json = {name: "alice", role: "admin"}
|
|
50
|
+
let result = vibe "Process this user" myModel default
|
|
51
|
+
result
|
|
52
|
+
`);
|
|
53
|
+
const provider = createMockProvider('User processed successfully');
|
|
54
|
+
const runtime = new Runtime(ast, provider);
|
|
55
|
+
const result = await runtime.run();
|
|
56
|
+
|
|
57
|
+
// Verify the json variable was created correctly
|
|
58
|
+
expect(runtime.getValue('userData')).toEqual({ name: 'alice', role: 'admin' });
|
|
59
|
+
expect(result).toBe('User processed successfully');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('AI returns JSON string parsed into json variable', async () => {
|
|
63
|
+
const ast = parse(`
|
|
64
|
+
model myModel = { name: "test", apiKey: "key", url: "http://test" }
|
|
65
|
+
let response: json = vibe "Return user data as JSON" myModel default
|
|
66
|
+
`);
|
|
67
|
+
// AI returns a JSON string
|
|
68
|
+
const provider = createMockProvider('{"id": 123, "name": "bob", "active": true}');
|
|
69
|
+
const runtime = new Runtime(ast, provider);
|
|
70
|
+
await runtime.run();
|
|
71
|
+
|
|
72
|
+
// The json type should auto-parse the string into an object
|
|
73
|
+
const response = runtime.getValue('response') as any;
|
|
74
|
+
expect(response).toEqual({ id: 123, name: 'bob', active: true });
|
|
75
|
+
expect(response.id).toBe(123);
|
|
76
|
+
expect(response.name).toBe('bob');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('full program: create data, call AI, store json[] result', async () => {
|
|
80
|
+
const ast = parse(`
|
|
81
|
+
model gpt = { name: "gpt-4", apiKey: "sk-test", url: "https://api.example.com" }
|
|
82
|
+
|
|
83
|
+
let config: json = {
|
|
84
|
+
maxItems: "10",
|
|
85
|
+
filter: "active"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let users: json[] = vibe "fetch users with config" gpt default
|
|
89
|
+
`);
|
|
90
|
+
// AI returns an array of users
|
|
91
|
+
const provider = createMockProvider('[{"name": "alice"}, {"name": "bob"}]');
|
|
92
|
+
const runtime = new Runtime(ast, provider);
|
|
93
|
+
await runtime.run();
|
|
94
|
+
|
|
95
|
+
// Verify config object
|
|
96
|
+
const config = runtime.getValue('config') as any;
|
|
97
|
+
expect(config.maxItems).toBe('10');
|
|
98
|
+
expect(config.filter).toBe('active');
|
|
99
|
+
|
|
100
|
+
// Verify parsed JSON array from AI
|
|
101
|
+
const users = runtime.getValue('users') as any;
|
|
102
|
+
expect(users).toHaveLength(2);
|
|
103
|
+
expect(users[0].name).toBe('alice');
|
|
104
|
+
expect(users[1].name).toBe('bob');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Note: 'model config resolves variable references' test moved to model-config.test.ts
|
|
108
|
+
|
|
109
|
+
test('complex json structure matches expected exactly', async () => {
|
|
110
|
+
const ast = parse(`
|
|
111
|
+
model api = { name: "gpt-4", apiKey: "key", url: "https://api.test.com" }
|
|
112
|
+
|
|
113
|
+
let appState: json = {
|
|
114
|
+
user: {
|
|
115
|
+
id: "123",
|
|
116
|
+
profile: {
|
|
117
|
+
name: "alice",
|
|
118
|
+
settings: {
|
|
119
|
+
theme: "dark",
|
|
120
|
+
notifications: true
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
roles: ["admin", "editor"]
|
|
124
|
+
},
|
|
125
|
+
metadata: {
|
|
126
|
+
version: "1.0",
|
|
127
|
+
features: ["export", "import", "share"]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
`);
|
|
131
|
+
const provider = createMockProvider('');
|
|
132
|
+
const runtime = new Runtime(ast, provider);
|
|
133
|
+
await runtime.run();
|
|
134
|
+
|
|
135
|
+
// Verify entire JSON structure matches expected
|
|
136
|
+
expect(runtime.getValue('appState')).toEqual({
|
|
137
|
+
user: {
|
|
138
|
+
id: '123',
|
|
139
|
+
profile: {
|
|
140
|
+
name: 'alice',
|
|
141
|
+
settings: {
|
|
142
|
+
theme: 'dark',
|
|
143
|
+
notifications: true,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
roles: ['admin', 'editor'],
|
|
147
|
+
},
|
|
148
|
+
metadata: {
|
|
149
|
+
version: '1.0',
|
|
150
|
+
features: ['export', 'import', 'share'],
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|