@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,660 @@
|
|
|
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
|
+
// Track execution order and timing
|
|
6
|
+
interface ExecutionLog {
|
|
7
|
+
event: string;
|
|
8
|
+
time: number;
|
|
9
|
+
prompt?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Create AI provider that logs execution and uses delays
|
|
13
|
+
function createLoggingMockAI(
|
|
14
|
+
delayMs: number,
|
|
15
|
+
responses: Record<string, unknown>,
|
|
16
|
+
log: ExecutionLog[]
|
|
17
|
+
): AIProvider {
|
|
18
|
+
const startTime = Date.now();
|
|
19
|
+
return {
|
|
20
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
21
|
+
log.push({ event: 'ai_start', time: Date.now() - startTime, prompt });
|
|
22
|
+
await Bun.sleep(delayMs);
|
|
23
|
+
log.push({ event: 'ai_end', time: Date.now() - startTime, prompt });
|
|
24
|
+
|
|
25
|
+
// Find matching response
|
|
26
|
+
for (const [key, value] of Object.entries(responses)) {
|
|
27
|
+
if (prompt.includes(key)) {
|
|
28
|
+
return { value };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { value: `response_to_${prompt}` };
|
|
32
|
+
},
|
|
33
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
34
|
+
return { value: '' };
|
|
35
|
+
},
|
|
36
|
+
async askUser(): Promise<string> {
|
|
37
|
+
return '';
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('Nested Async Execution', () => {
|
|
43
|
+
describe('async function with async operations inside', () => {
|
|
44
|
+
test('vibe function with single async do inside', async () => {
|
|
45
|
+
const log: ExecutionLog[] = [];
|
|
46
|
+
|
|
47
|
+
const ast = parse(`
|
|
48
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
49
|
+
|
|
50
|
+
function getData() {
|
|
51
|
+
async let result = do "fetch_data" m default
|
|
52
|
+
return result
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let data = getData()
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
const aiProvider = createLoggingMockAI(50, {
|
|
59
|
+
'fetch_data': 'fetched_data'
|
|
60
|
+
}, log);
|
|
61
|
+
|
|
62
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
63
|
+
await runtime.run();
|
|
64
|
+
|
|
65
|
+
expect(runtime.getValue('data')).toBe('fetched_data');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('vibe function with multiple async operations inside (parallel)', async () => {
|
|
69
|
+
const log: ExecutionLog[] = [];
|
|
70
|
+
|
|
71
|
+
const ast = parse(`
|
|
72
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
73
|
+
|
|
74
|
+
function fetchAll() {
|
|
75
|
+
async let a = do "fetch_a" m default
|
|
76
|
+
async let b = do "fetch_b" m default
|
|
77
|
+
async let c = do "fetch_c" m default
|
|
78
|
+
return a + b + c
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let result = fetchAll()
|
|
82
|
+
`);
|
|
83
|
+
|
|
84
|
+
const aiProvider = createLoggingMockAI(50, {
|
|
85
|
+
'fetch_a': 'A',
|
|
86
|
+
'fetch_b': 'B',
|
|
87
|
+
'fetch_c': 'C',
|
|
88
|
+
}, log);
|
|
89
|
+
|
|
90
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
91
|
+
const startTime = Date.now();
|
|
92
|
+
await runtime.run();
|
|
93
|
+
const elapsed = Date.now() - startTime;
|
|
94
|
+
|
|
95
|
+
expect(runtime.getValue('result')).toBe('ABC');
|
|
96
|
+
// Should run in parallel inside function (~50ms, not 150ms)
|
|
97
|
+
expect(elapsed).toBeLessThan(150);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('async call to vibe function', async () => {
|
|
101
|
+
const log: ExecutionLog[] = [];
|
|
102
|
+
|
|
103
|
+
const ast = parse(`
|
|
104
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
105
|
+
|
|
106
|
+
function slowOperation() {
|
|
107
|
+
let x = do "slow_op" m default
|
|
108
|
+
return x + "_processed"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async let result = slowOperation()
|
|
112
|
+
let other = "done"
|
|
113
|
+
`);
|
|
114
|
+
|
|
115
|
+
const aiProvider = createLoggingMockAI(50, {
|
|
116
|
+
'slow_op': 'slow_result'
|
|
117
|
+
}, log);
|
|
118
|
+
|
|
119
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
120
|
+
await runtime.run();
|
|
121
|
+
|
|
122
|
+
expect(runtime.getValue('result')).toBe('slow_result_processed');
|
|
123
|
+
expect(runtime.getValue('other')).toBe('done');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('multiple async function calls in parallel', () => {
|
|
128
|
+
test('two async function calls run in parallel', async () => {
|
|
129
|
+
const log: ExecutionLog[] = [];
|
|
130
|
+
|
|
131
|
+
const ast = parse(`
|
|
132
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
133
|
+
|
|
134
|
+
function getA() {
|
|
135
|
+
let x = do "get_A" m default
|
|
136
|
+
return x
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getB() {
|
|
140
|
+
let x = do "get_B" m default
|
|
141
|
+
return x
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async let a = getA()
|
|
145
|
+
async let b = getB()
|
|
146
|
+
let combined = a + b
|
|
147
|
+
`);
|
|
148
|
+
|
|
149
|
+
const aiProvider = createLoggingMockAI(75, {
|
|
150
|
+
'get_A': 'ValueA',
|
|
151
|
+
'get_B': 'ValueB',
|
|
152
|
+
}, log);
|
|
153
|
+
|
|
154
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
155
|
+
const startTime = Date.now();
|
|
156
|
+
await runtime.run();
|
|
157
|
+
const elapsed = Date.now() - startTime;
|
|
158
|
+
|
|
159
|
+
expect(runtime.getValue('combined')).toBe('ValueAValueB');
|
|
160
|
+
// Two parallel calls should take ~75ms, not 150ms (with some margin for overhead)
|
|
161
|
+
expect(elapsed).toBeLessThan(200);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('three async function calls with internal async ops', async () => {
|
|
165
|
+
const log: ExecutionLog[] = [];
|
|
166
|
+
|
|
167
|
+
const ast = parse(`
|
|
168
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
169
|
+
|
|
170
|
+
function process1() {
|
|
171
|
+
async let x = do "proc1_step1" m default
|
|
172
|
+
async let y = do "proc1_step2" m default
|
|
173
|
+
return x + y
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function process2() {
|
|
177
|
+
async let x = do "proc2_step1" m default
|
|
178
|
+
async let y = do "proc2_step2" m default
|
|
179
|
+
return x + y
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function process3() {
|
|
183
|
+
async let x = do "proc3_step1" m default
|
|
184
|
+
return x
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async let r1 = process1()
|
|
188
|
+
async let r2 = process2()
|
|
189
|
+
async let r3 = process3()
|
|
190
|
+
let final = r1 + "_" + r2 + "_" + r3
|
|
191
|
+
`);
|
|
192
|
+
|
|
193
|
+
const aiProvider = createLoggingMockAI(40, {
|
|
194
|
+
'proc1_step1': 'A',
|
|
195
|
+
'proc1_step2': 'B',
|
|
196
|
+
'proc2_step1': 'C',
|
|
197
|
+
'proc2_step2': 'D',
|
|
198
|
+
'proc3_step1': 'E',
|
|
199
|
+
}, log);
|
|
200
|
+
|
|
201
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
202
|
+
const startTime = Date.now();
|
|
203
|
+
await runtime.run();
|
|
204
|
+
const elapsed = Date.now() - startTime;
|
|
205
|
+
|
|
206
|
+
expect(runtime.getValue('final')).toBe('AB_CD_E');
|
|
207
|
+
// All operations should run with parallelism
|
|
208
|
+
// 3 functions with 2+2+1=5 ops at 40ms each
|
|
209
|
+
// With maxParallel=4, should be ~80ms (2 waves)
|
|
210
|
+
expect(elapsed).toBeLessThan(200);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('nested function calls', () => {
|
|
215
|
+
test('function calling another function with async inside', async () => {
|
|
216
|
+
const ast = parse(`
|
|
217
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
218
|
+
|
|
219
|
+
function inner() {
|
|
220
|
+
async let x = do "inner_call" m default
|
|
221
|
+
return x + "_inner"
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function outer() {
|
|
225
|
+
let i = inner()
|
|
226
|
+
return i + "_outer"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let result = outer()
|
|
230
|
+
`);
|
|
231
|
+
|
|
232
|
+
const aiProvider = createLoggingMockAI(50, {
|
|
233
|
+
'inner_call': 'data'
|
|
234
|
+
}, []);
|
|
235
|
+
|
|
236
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
237
|
+
await runtime.run();
|
|
238
|
+
|
|
239
|
+
expect(runtime.getValue('result')).toBe('data_inner_outer');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('deeply nested functions with async at each level', async () => {
|
|
243
|
+
const ast = parse(`
|
|
244
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
245
|
+
|
|
246
|
+
function level3() {
|
|
247
|
+
async let x = do "level3_op" m default
|
|
248
|
+
return x
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function level2() {
|
|
252
|
+
let inner = level3()
|
|
253
|
+
async let y = do "level2_op" m default
|
|
254
|
+
return inner + y
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function level1() {
|
|
258
|
+
let inner = level2()
|
|
259
|
+
async let z = do "level1_op" m default
|
|
260
|
+
return inner + z
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let result = level1()
|
|
264
|
+
`);
|
|
265
|
+
|
|
266
|
+
const aiProvider = createLoggingMockAI(30, {
|
|
267
|
+
'level3_op': 'L3',
|
|
268
|
+
'level2_op': 'L2',
|
|
269
|
+
'level1_op': 'L1',
|
|
270
|
+
}, []);
|
|
271
|
+
|
|
272
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
273
|
+
await runtime.run();
|
|
274
|
+
|
|
275
|
+
expect(runtime.getValue('result')).toBe('L3L2L1');
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('async with loops inside functions', () => {
|
|
280
|
+
test('function with for loop containing async', async () => {
|
|
281
|
+
const ast = parse(`
|
|
282
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
283
|
+
|
|
284
|
+
function processItems() {
|
|
285
|
+
let results = []
|
|
286
|
+
for item in [1, 2, 3] {
|
|
287
|
+
async let processed = do "process_!{item}" m default
|
|
288
|
+
results.push(processed)
|
|
289
|
+
}
|
|
290
|
+
return results
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let output = processItems()
|
|
294
|
+
`);
|
|
295
|
+
|
|
296
|
+
const aiProvider = createLoggingMockAI(30, {
|
|
297
|
+
'process_1': 'P1',
|
|
298
|
+
'process_2': 'P2',
|
|
299
|
+
'process_3': 'P3',
|
|
300
|
+
}, []);
|
|
301
|
+
|
|
302
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
303
|
+
await runtime.run();
|
|
304
|
+
|
|
305
|
+
expect(runtime.getValue('output')).toEqual(['P1', 'P2', 'P3']);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('async function call inside loop', async () => {
|
|
309
|
+
const log: ExecutionLog[] = [];
|
|
310
|
+
|
|
311
|
+
// Use !{id} to expand the value in the prompt for unique mock matching
|
|
312
|
+
const ast = parse(`
|
|
313
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
314
|
+
|
|
315
|
+
function getData(id: number) {
|
|
316
|
+
async let x = do "get_data_!{id}" m default
|
|
317
|
+
return x
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let results = []
|
|
321
|
+
for i in [1, 2, 3] {
|
|
322
|
+
async let r = getData(i)
|
|
323
|
+
results.push(r)
|
|
324
|
+
}
|
|
325
|
+
`);
|
|
326
|
+
|
|
327
|
+
const aiProvider = createLoggingMockAI(30, {
|
|
328
|
+
'get_data_1': 'D1',
|
|
329
|
+
'get_data_2': 'D2',
|
|
330
|
+
'get_data_3': 'D3',
|
|
331
|
+
}, log);
|
|
332
|
+
|
|
333
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
334
|
+
await runtime.run();
|
|
335
|
+
|
|
336
|
+
expect(runtime.getValue('results')).toEqual(['D1', 'D2', 'D3']);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('async destructuring in functions', () => {
|
|
341
|
+
test('function with async destructuring', async () => {
|
|
342
|
+
const ast = parse(`
|
|
343
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
344
|
+
|
|
345
|
+
function getPerson() {
|
|
346
|
+
async let {name: text, age: number} = do "get_person" m default
|
|
347
|
+
return name + " is " + age
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let description = getPerson()
|
|
351
|
+
`);
|
|
352
|
+
|
|
353
|
+
const aiProvider = createLoggingMockAI(50, {
|
|
354
|
+
'get_person': { name: 'Alice', age: 30 }
|
|
355
|
+
}, []);
|
|
356
|
+
|
|
357
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
358
|
+
await runtime.run();
|
|
359
|
+
|
|
360
|
+
expect(runtime.getValue('description')).toBe('Alice is 30');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test('multiple async destructurings in parallel inside function', async () => {
|
|
364
|
+
const log: ExecutionLog[] = [];
|
|
365
|
+
|
|
366
|
+
const ast = parse(`
|
|
367
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
368
|
+
|
|
369
|
+
function fetchData() {
|
|
370
|
+
async let {x: number, y: number} = do "get_coords" m default
|
|
371
|
+
async let {name: text, value: number} = do "get_item" m default
|
|
372
|
+
return name + " at " + x + "," + y + " = " + value
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let result = fetchData()
|
|
376
|
+
`);
|
|
377
|
+
|
|
378
|
+
const aiProvider = createLoggingMockAI(50, {
|
|
379
|
+
'get_coords': { x: 10, y: 20 },
|
|
380
|
+
'get_item': { name: 'Item', value: 100 },
|
|
381
|
+
}, log);
|
|
382
|
+
|
|
383
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
384
|
+
const startTime = Date.now();
|
|
385
|
+
await runtime.run();
|
|
386
|
+
const elapsed = Date.now() - startTime;
|
|
387
|
+
|
|
388
|
+
expect(runtime.getValue('result')).toBe('Item at 10,20 = 100');
|
|
389
|
+
// Both destructurings should run in parallel (with margin for overhead)
|
|
390
|
+
expect(elapsed).toBeLessThan(175);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe('error handling in nested async', () => {
|
|
395
|
+
test('error in async function propagates correctly', async () => {
|
|
396
|
+
const ast = parse(`
|
|
397
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
398
|
+
|
|
399
|
+
function failing() {
|
|
400
|
+
async let x = do "fail_op" m default
|
|
401
|
+
return x
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async let result = failing()
|
|
405
|
+
let used = result + "_suffix"
|
|
406
|
+
`);
|
|
407
|
+
|
|
408
|
+
const aiProvider: AIProvider = {
|
|
409
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
410
|
+
if (prompt.includes('fail_op')) {
|
|
411
|
+
throw new Error('Simulated failure');
|
|
412
|
+
}
|
|
413
|
+
return { value: 'ok' };
|
|
414
|
+
},
|
|
415
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
416
|
+
return { value: '' };
|
|
417
|
+
},
|
|
418
|
+
async askUser(): Promise<string> {
|
|
419
|
+
return '';
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
424
|
+
await runtime.run();
|
|
425
|
+
|
|
426
|
+
// The error should be captured in the VibeValue
|
|
427
|
+
const state = runtime.getState();
|
|
428
|
+
expect(state.callStack[0].locals['result'].err).toBe(true); // err is now boolean
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test('one failing async in parallel does not block others', async () => {
|
|
432
|
+
const ast = parse(`
|
|
433
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
434
|
+
|
|
435
|
+
function good1() {
|
|
436
|
+
let x = do "good_1" m default
|
|
437
|
+
return x
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function failing() {
|
|
441
|
+
let x = do "fail" m default
|
|
442
|
+
return x
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function good2() {
|
|
446
|
+
let x = do "good_2" m default
|
|
447
|
+
return x
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async let r1 = good1()
|
|
451
|
+
async let r2 = failing()
|
|
452
|
+
async let r3 = good2()
|
|
453
|
+
`);
|
|
454
|
+
|
|
455
|
+
let callCount = 0;
|
|
456
|
+
const aiProvider: AIProvider = {
|
|
457
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
458
|
+
callCount++;
|
|
459
|
+
await Bun.sleep(30);
|
|
460
|
+
if (prompt.includes('fail')) {
|
|
461
|
+
throw new Error('Simulated failure');
|
|
462
|
+
}
|
|
463
|
+
if (prompt.includes('good_1')) return { value: 'G1' };
|
|
464
|
+
if (prompt.includes('good_2')) return { value: 'G2' };
|
|
465
|
+
return { value: 'unknown' };
|
|
466
|
+
},
|
|
467
|
+
async generateCode(): Promise<AIExecutionResult> {
|
|
468
|
+
return { value: '' };
|
|
469
|
+
},
|
|
470
|
+
async askUser(): Promise<string> {
|
|
471
|
+
return '';
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
476
|
+
await runtime.run();
|
|
477
|
+
|
|
478
|
+
// All three should have been called
|
|
479
|
+
expect(callCount).toBe(3);
|
|
480
|
+
|
|
481
|
+
// Good ones should have values
|
|
482
|
+
expect(runtime.getValue('r1')).toBe('G1');
|
|
483
|
+
expect(runtime.getValue('r3')).toBe('G2');
|
|
484
|
+
|
|
485
|
+
// Failing one should have error
|
|
486
|
+
const state = runtime.getState();
|
|
487
|
+
expect(state.callStack[0].locals['r2'].err).toBe(true); // err is now boolean
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe('mixed sync and async in nested calls', () => {
|
|
492
|
+
test('sync function calling async function', async () => {
|
|
493
|
+
const ast = parse(`
|
|
494
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
495
|
+
|
|
496
|
+
function asyncWork() {
|
|
497
|
+
async let x = do "async_op" m default
|
|
498
|
+
return x
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function syncWrapper() {
|
|
502
|
+
let prefix = "PREFIX_"
|
|
503
|
+
let asyncResult = asyncWork()
|
|
504
|
+
let suffix = "_SUFFIX"
|
|
505
|
+
return prefix + asyncResult + suffix
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let result = syncWrapper()
|
|
509
|
+
`);
|
|
510
|
+
|
|
511
|
+
const aiProvider = createLoggingMockAI(50, {
|
|
512
|
+
'async_op': 'ASYNC'
|
|
513
|
+
}, []);
|
|
514
|
+
|
|
515
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
516
|
+
await runtime.run();
|
|
517
|
+
|
|
518
|
+
expect(runtime.getValue('result')).toBe('PREFIX_ASYNC_SUFFIX');
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test('interleaved sync and async at multiple levels', async () => {
|
|
522
|
+
const ast = parse(`
|
|
523
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
524
|
+
|
|
525
|
+
function level2() {
|
|
526
|
+
let sync1 = "S1"
|
|
527
|
+
async let async1 = do "A1" m default
|
|
528
|
+
let sync2 = "S2"
|
|
529
|
+
return sync1 + async1 + sync2
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function level1() {
|
|
533
|
+
let before = "B_"
|
|
534
|
+
let middle = level2()
|
|
535
|
+
async let after = do "A2" m default
|
|
536
|
+
return before + middle + "_" + after
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
let result = level1()
|
|
540
|
+
`);
|
|
541
|
+
|
|
542
|
+
const aiProvider = createLoggingMockAI(30, {
|
|
543
|
+
'A1': 'ASYNC1',
|
|
544
|
+
'A2': 'ASYNC2',
|
|
545
|
+
}, []);
|
|
546
|
+
|
|
547
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
548
|
+
await runtime.run();
|
|
549
|
+
|
|
550
|
+
expect(runtime.getValue('result')).toBe('B_S1ASYNC1S2_ASYNC2');
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
describe('deeply nested async Vibe function calls', () => {
|
|
555
|
+
test('three levels of async function calls', async () => {
|
|
556
|
+
// Use !{prefix} to expand the value in the prompt for unique mock matching
|
|
557
|
+
const ast = parse(`
|
|
558
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
559
|
+
|
|
560
|
+
function level3(prefix: text) {
|
|
561
|
+
async let x = do "L3_!{prefix}" m default
|
|
562
|
+
return prefix + "_" + x
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function level2(id: number) {
|
|
566
|
+
async let r = level3("L2_" + id)
|
|
567
|
+
return "L2:" + r
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function level1() {
|
|
571
|
+
async let a = level2(1)
|
|
572
|
+
async let b = level2(2)
|
|
573
|
+
return a + "|" + b
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let result = level1()
|
|
577
|
+
`);
|
|
578
|
+
|
|
579
|
+
const aiProvider = createLoggingMockAI(30, {
|
|
580
|
+
'L3_L2_1': 'A',
|
|
581
|
+
'L3_L2_2': 'B',
|
|
582
|
+
}, []);
|
|
583
|
+
|
|
584
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
585
|
+
await runtime.run();
|
|
586
|
+
|
|
587
|
+
expect(runtime.getValue('result')).toBe('L2:L2_1_A|L2:L2_2_B');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test('recursive async function with base case', async () => {
|
|
591
|
+
// Use !{n} to expand the value in the prompt for unique mock matching
|
|
592
|
+
const ast = parse(`
|
|
593
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
594
|
+
|
|
595
|
+
function countdown(n: number) {
|
|
596
|
+
if n <= 0 {
|
|
597
|
+
return "done"
|
|
598
|
+
}
|
|
599
|
+
async let prefix = do "step_!{n}" m default
|
|
600
|
+
let rest = countdown(n - 1)
|
|
601
|
+
return prefix + "-" + rest
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
let result = countdown(3)
|
|
605
|
+
`);
|
|
606
|
+
|
|
607
|
+
const aiProvider = createLoggingMockAI(30, {
|
|
608
|
+
'step_3': 'S3',
|
|
609
|
+
'step_2': 'S2',
|
|
610
|
+
'step_1': 'S1',
|
|
611
|
+
}, []);
|
|
612
|
+
|
|
613
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
614
|
+
await runtime.run();
|
|
615
|
+
|
|
616
|
+
expect(runtime.getValue('result')).toBe('S3-S2-S1-done');
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
test('parallel async calls within nested functions', async () => {
|
|
620
|
+
const log: ExecutionLog[] = [];
|
|
621
|
+
|
|
622
|
+
// Use !{id} to expand the value in the prompt for unique mock matching
|
|
623
|
+
const ast = parse(`
|
|
624
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
625
|
+
|
|
626
|
+
function innerWork(id: text) {
|
|
627
|
+
async let x = do "work_!{id}" m default
|
|
628
|
+
return x
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function middleLevel() {
|
|
632
|
+
async let a = innerWork("A")
|
|
633
|
+
async let b = innerWork("B")
|
|
634
|
+
return a + "+" + b
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
let results = []
|
|
638
|
+
for i in [1, 2] {
|
|
639
|
+
async let r = middleLevel()
|
|
640
|
+
results.push(r)
|
|
641
|
+
}
|
|
642
|
+
`);
|
|
643
|
+
|
|
644
|
+
const aiProvider = createLoggingMockAI(30, {
|
|
645
|
+
'work_A': 'WA',
|
|
646
|
+
'work_B': 'WB',
|
|
647
|
+
}, log);
|
|
648
|
+
|
|
649
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
650
|
+
const startTime = Date.now();
|
|
651
|
+
await runtime.run();
|
|
652
|
+
const elapsed = Date.now() - startTime;
|
|
653
|
+
|
|
654
|
+
expect(runtime.getValue('results')).toEqual(['WA+WB', 'WA+WB']);
|
|
655
|
+
// Two outer calls in parallel, each with two inner parallel calls
|
|
656
|
+
// Should be ~60ms (30ms for outer level + 30ms for inner level), not ~120ms
|
|
657
|
+
expect(elapsed).toBeLessThan(200);
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
});
|