@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,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
+ });