@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.
Files changed (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. 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
+ });