@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,618 @@
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
+ // Event types for tracking execution order
6
+ type EventType =
7
+ | 'ai_call_start'
8
+ | 'ai_call_end'
9
+ | 'sync_op';
10
+
11
+ interface ExecutionEvent {
12
+ type: EventType;
13
+ id: string;
14
+ timestamp: number;
15
+ }
16
+
17
+ // Create AI provider that logs precise execution events
18
+ function createOrderTrackingAI(
19
+ delayMs: number,
20
+ responses: Record<string, unknown>,
21
+ events: ExecutionEvent[]
22
+ ): AIProvider {
23
+ return {
24
+ async execute(prompt: string): Promise<AIExecutionResult> {
25
+ // Extract ID from prompt for tracking
26
+ const id = prompt.replace(/[^a-zA-Z0-9_]/g, '_').substring(0, 20);
27
+
28
+ events.push({ type: 'ai_call_start', id, timestamp: Date.now() });
29
+ await Bun.sleep(delayMs);
30
+ events.push({ type: 'ai_call_end', id, timestamp: Date.now() });
31
+
32
+ // Find matching response
33
+ for (const [key, value] of Object.entries(responses)) {
34
+ if (prompt.includes(key)) {
35
+ return { value };
36
+ }
37
+ }
38
+ return { value: `response_${id}` };
39
+ },
40
+ async generateCode(): Promise<AIExecutionResult> {
41
+ return { value: '' };
42
+ },
43
+ async askUser(): Promise<string> {
44
+ return '';
45
+ },
46
+ };
47
+ }
48
+
49
+ // Helper to check if events overlap in time (parallel execution)
50
+ function eventsOverlap(events: ExecutionEvent[], id1: string, id2: string): boolean {
51
+ const start1 = events.find(e => e.type === 'ai_call_start' && e.id.includes(id1));
52
+ const end1 = events.find(e => e.type === 'ai_call_end' && e.id.includes(id1));
53
+ const start2 = events.find(e => e.type === 'ai_call_start' && e.id.includes(id2));
54
+ const end2 = events.find(e => e.type === 'ai_call_end' && e.id.includes(id2));
55
+
56
+ if (!start1 || !end1 || !start2 || !end2) return false;
57
+
58
+ // Overlap if one starts before the other ends
59
+ return (start1.timestamp < end2.timestamp && start2.timestamp < end1.timestamp);
60
+ }
61
+
62
+ // Helper to check if event A started before event B ended
63
+ function startedBefore(events: ExecutionEvent[], idA: string, idB: string): boolean {
64
+ const startA = events.find(e => e.type === 'ai_call_start' && e.id.includes(idA));
65
+ const endB = events.find(e => e.type === 'ai_call_end' && e.id.includes(idB));
66
+
67
+ if (!startA || !endB) return false;
68
+ return startA.timestamp < endB.timestamp;
69
+ }
70
+
71
+ // Helper to get event order
72
+ function getEventOrder(events: ExecutionEvent[]): string[] {
73
+ return events
74
+ .sort((a, b) => a.timestamp - b.timestamp)
75
+ .map(e => `${e.type}:${e.id}`);
76
+ }
77
+
78
+ describe('Async Execution Order Verification', () => {
79
+ describe('parallel async operations start before any awaits', () => {
80
+ test('three async lets all START before any END (true parallelism)', async () => {
81
+ const events: ExecutionEvent[] = [];
82
+
83
+ const ast = parse(`
84
+ model m = { name: "test", apiKey: "key", url: "http://test" }
85
+ async let a = do "op_A" m default
86
+ async let b = do "op_B" m default
87
+ async let c = do "op_C" m default
88
+ let result = a + b + c
89
+ `);
90
+
91
+ const aiProvider = createOrderTrackingAI(100, {
92
+ 'op_A': 'A',
93
+ 'op_B': 'B',
94
+ 'op_C': 'C',
95
+ }, events);
96
+
97
+ const runtime = new Runtime(ast, aiProvider);
98
+ await runtime.run();
99
+
100
+ // Get all start events
101
+ const starts = events.filter(e => e.type === 'ai_call_start');
102
+ const ends = events.filter(e => e.type === 'ai_call_end');
103
+
104
+ // All 3 operations should have started
105
+ expect(starts.length).toBe(3);
106
+ expect(ends.length).toBe(3);
107
+
108
+ // Key test: ALL operations should START before ANY operation ENDS
109
+ // This proves true parallel execution
110
+ const lastStartTime = Math.max(...starts.map(e => e.timestamp));
111
+ const firstEndTime = Math.min(...ends.map(e => e.timestamp));
112
+
113
+ expect(lastStartTime).toBeLessThan(firstEndTime);
114
+
115
+ // Verify overlapping execution
116
+ expect(eventsOverlap(events, 'op_A', 'op_B')).toBe(true);
117
+ expect(eventsOverlap(events, 'op_B', 'op_C')).toBe(true);
118
+ expect(eventsOverlap(events, 'op_A', 'op_C')).toBe(true);
119
+
120
+ expect(runtime.getValue('result')).toBe('ABC');
121
+ });
122
+
123
+ test('async operations start immediately, not lazily on use', async () => {
124
+ const events: ExecutionEvent[] = [];
125
+
126
+ const ast = parse(`
127
+ model m = { name: "test", apiKey: "key", url: "http://test" }
128
+ async let a = do "start_A" m default
129
+ async let b = do "start_B" m default
130
+ `);
131
+
132
+ // Use longer delay to make timing clearer
133
+ const aiProvider = createOrderTrackingAI(150, {
134
+ 'start_A': 'A',
135
+ 'start_B': 'B',
136
+ }, events);
137
+
138
+ const runtime = new Runtime(ast, aiProvider);
139
+ await runtime.run();
140
+
141
+ const starts = events.filter(e => e.type === 'ai_call_start');
142
+
143
+ // Both should start almost immediately (within 50ms of each other)
144
+ // not lazily when the variable is used
145
+ const timeDiff = Math.abs(starts[0].timestamp - starts[1].timestamp);
146
+ expect(timeDiff).toBeLessThan(50);
147
+ });
148
+ });
149
+
150
+ describe('implicit await happens at correct point', () => {
151
+ test('await triggers when async variable is used in expression', async () => {
152
+ const events: ExecutionEvent[] = [];
153
+ let syncOpTime = 0;
154
+
155
+ const ast = parse(`
156
+ model m = { name: "test", apiKey: "key", url: "http://test" }
157
+ async let a = do "async_op" m default
158
+ let sync = "before_use"
159
+ let result = a + "_used"
160
+ `);
161
+
162
+ const aiProvider = createOrderTrackingAI(100, {
163
+ 'async_op': 'ASYNC',
164
+ }, events);
165
+
166
+ const runtime = new Runtime(ast, aiProvider);
167
+ await runtime.run();
168
+
169
+ // The sync assignment should happen before async completes
170
+ // (async doesn't block sync operations)
171
+ const asyncStart = events.find(e => e.type === 'ai_call_start');
172
+ const asyncEnd = events.find(e => e.type === 'ai_call_end');
173
+
174
+ expect(asyncStart).toBeDefined();
175
+ expect(asyncEnd).toBeDefined();
176
+
177
+ // Result should be correct (await happened before concatenation)
178
+ expect(runtime.getValue('result')).toBe('ASYNC_used');
179
+ });
180
+
181
+ test('multiple async variables await only when used', async () => {
182
+ const events: ExecutionEvent[] = [];
183
+
184
+ const ast = parse(`
185
+ model m = { name: "test", apiKey: "key", url: "http://test" }
186
+ async let a = do "first" m default
187
+ async let b = do "second" m default
188
+ let useA = a + "!"
189
+ let useB = b + "!"
190
+ `);
191
+
192
+ const aiProvider = createOrderTrackingAI(75, {
193
+ 'first': 'FIRST',
194
+ 'second': 'SECOND',
195
+ }, events);
196
+
197
+ const runtime = new Runtime(ast, aiProvider);
198
+ await runtime.run();
199
+
200
+ // Both should have completed
201
+ const ends = events.filter(e => e.type === 'ai_call_end');
202
+ expect(ends.length).toBe(2);
203
+
204
+ expect(runtime.getValue('useA')).toBe('FIRST!');
205
+ expect(runtime.getValue('useB')).toBe('SECOND!');
206
+ });
207
+ });
208
+
209
+ describe('sequential vs parallel execution patterns', () => {
210
+ test('sync do blocks sequentially, async do runs in parallel', async () => {
211
+ // First: sync pattern (should be sequential)
212
+ const syncEvents: ExecutionEvent[] = [];
213
+ const syncAst = parse(`
214
+ model m = { name: "test", apiKey: "key", url: "http://test" }
215
+ let a = do "sync_1" m default
216
+ let b = do "sync_2" m default
217
+ `);
218
+
219
+ const syncProvider = createOrderTrackingAI(50, {
220
+ 'sync_1': 'S1',
221
+ 'sync_2': 'S2',
222
+ }, syncEvents);
223
+
224
+ const syncRuntime = new Runtime(syncAst, syncProvider);
225
+ const syncStart = Date.now();
226
+ await syncRuntime.run();
227
+ const syncElapsed = Date.now() - syncStart;
228
+
229
+ // Sync: second should start AFTER first ends
230
+ const sync1End = syncEvents.find(e => e.type === 'ai_call_end' && e.id.includes('sync_1'));
231
+ const sync2Start = syncEvents.find(e => e.type === 'ai_call_start' && e.id.includes('sync_2'));
232
+ expect(sync2Start!.timestamp).toBeGreaterThanOrEqual(sync1End!.timestamp);
233
+
234
+ // Second: async pattern (should be parallel)
235
+ const asyncEvents: ExecutionEvent[] = [];
236
+ const asyncAst = parse(`
237
+ model m = { name: "test", apiKey: "key", url: "http://test" }
238
+ async let a = do "async_1" m default
239
+ async let b = do "async_2" m default
240
+ let result = a + b
241
+ `);
242
+
243
+ const asyncProvider = createOrderTrackingAI(50, {
244
+ 'async_1': 'A1',
245
+ 'async_2': 'A2',
246
+ }, asyncEvents);
247
+
248
+ const asyncRuntime = new Runtime(asyncAst, asyncProvider);
249
+ const asyncStart = Date.now();
250
+ await asyncRuntime.run();
251
+ const asyncElapsed = Date.now() - asyncStart;
252
+
253
+ // Async: should overlap
254
+ expect(eventsOverlap(asyncEvents, 'async_1', 'async_2')).toBe(true);
255
+
256
+ // Async should be significantly faster than sync
257
+ expect(asyncElapsed).toBeLessThan(syncElapsed);
258
+ });
259
+
260
+ test('dependent async operations with !{} expansion execute in correct order', async () => {
261
+ const events: ExecutionEvent[] = [];
262
+
263
+ // Both are async, but b depends on a via !{a} expansion
264
+ // !{a} expands the value into the prompt, creating a true dependency
265
+ const ast = parse(`
266
+ model m = { name: "test", apiKey: "key", url: "http://test" }
267
+ async let a = do "get_value" m default
268
+ async let b = do "use_!{a}" m default
269
+ `);
270
+
271
+ const aiProvider = createOrderTrackingAI(50, {
272
+ 'get_value': 'VALUE',
273
+ 'use_VALUE': 'RESULT',
274
+ }, events);
275
+
276
+ const runtime = new Runtime(ast, aiProvider);
277
+ await runtime.run();
278
+
279
+ // a should complete before b starts (b depends on a via !{a} expansion)
280
+ const aEnd = events.find(e => e.type === 'ai_call_end' && e.id.includes('get_value'));
281
+ const bStart = events.find(e => e.type === 'ai_call_start' && e.id.includes('use_'));
282
+
283
+ expect(aEnd).toBeDefined();
284
+ expect(bStart).toBeDefined();
285
+ expect(aEnd!.timestamp).toBeLessThanOrEqual(bStart!.timestamp);
286
+
287
+ expect(runtime.getValue('b')).toBe('RESULT');
288
+ });
289
+
290
+ test('dependent async operations with {} reference execute in correct order', async () => {
291
+ const events: ExecutionEvent[] = [];
292
+
293
+ // Both are async, b references a via {a} (reference syntax)
294
+ // Even though {a} is left as literal in prompt, the context system needs
295
+ // the resolved value, so b should wait for a to complete
296
+ const ast = parse(`
297
+ model m = { name: "test", apiKey: "key", url: "http://test" }
298
+ async let a = do "get_value" m default
299
+ async let b = do "use_{a}" m default
300
+ `);
301
+
302
+ const aiProvider = createOrderTrackingAI(50, {
303
+ 'get_value': 'VALUE',
304
+ // Note: prompt will be "use_{a}" (literal), not "use_VALUE"
305
+ 'use_{a}': 'RESULT',
306
+ }, events);
307
+
308
+ const runtime = new Runtime(ast, aiProvider);
309
+ await runtime.run();
310
+
311
+ // a should complete before b starts (b references a, context needs resolved value)
312
+ const aEnd = events.find(e => e.type === 'ai_call_end' && e.id.includes('get_value'));
313
+ const bStart = events.find(e => e.type === 'ai_call_start' && e.id.includes('use_'));
314
+
315
+ expect(aEnd).toBeDefined();
316
+ expect(bStart).toBeDefined();
317
+ expect(aEnd!.timestamp).toBeLessThanOrEqual(bStart!.timestamp);
318
+
319
+ expect(runtime.getValue('b')).toBe('RESULT');
320
+ });
321
+ });
322
+
323
+ describe('execution order in functions', () => {
324
+ test('async operations in function body run in parallel', async () => {
325
+ const events: ExecutionEvent[] = [];
326
+
327
+ const ast = parse(`
328
+ model m = { name: "test", apiKey: "key", url: "http://test" }
329
+
330
+ function fetchBoth() {
331
+ async let x = do "func_X" m default
332
+ async let y = do "func_Y" m default
333
+ return x + y
334
+ }
335
+
336
+ let result = fetchBoth()
337
+ `);
338
+
339
+ const aiProvider = createOrderTrackingAI(75, {
340
+ 'func_X': 'X',
341
+ 'func_Y': 'Y',
342
+ }, events);
343
+
344
+ const runtime = new Runtime(ast, aiProvider);
345
+ await runtime.run();
346
+
347
+ // Both should overlap (parallel inside function)
348
+ expect(eventsOverlap(events, 'func_X', 'func_Y')).toBe(true);
349
+ expect(runtime.getValue('result')).toBe('XY');
350
+ });
351
+
352
+ test('nested function calls maintain correct order', async () => {
353
+ const events: ExecutionEvent[] = [];
354
+
355
+ const ast = parse(`
356
+ model m = { name: "test", apiKey: "key", url: "http://test" }
357
+
358
+ function inner() {
359
+ async let i = do "inner_op" m default
360
+ return i
361
+ }
362
+
363
+ function outer() {
364
+ let fromInner = inner()
365
+ async let o = do "outer_op" m default
366
+ return fromInner + o
367
+ }
368
+
369
+ let result = outer()
370
+ `);
371
+
372
+ const aiProvider = createOrderTrackingAI(50, {
373
+ 'inner_op': 'I',
374
+ 'outer_op': 'O',
375
+ }, events);
376
+
377
+ const runtime = new Runtime(ast, aiProvider);
378
+ await runtime.run();
379
+
380
+ // inner_op should complete before outer_op starts
381
+ // (because fromInner = inner() is sync assignment that needs inner to complete)
382
+ const innerEnd = events.find(e => e.type === 'ai_call_end' && e.id.includes('inner'));
383
+ const outerStart = events.find(e => e.type === 'ai_call_start' && e.id.includes('outer'));
384
+
385
+ expect(innerEnd).toBeDefined();
386
+ expect(outerStart).toBeDefined();
387
+ expect(innerEnd!.timestamp).toBeLessThanOrEqual(outerStart!.timestamp);
388
+
389
+ expect(runtime.getValue('result')).toBe('IO');
390
+ });
391
+ });
392
+
393
+ describe('execution order with loops', () => {
394
+ test('async in loop iterations - each iteration awaits before next', async () => {
395
+ const events: ExecutionEvent[] = [];
396
+
397
+ const ast = parse(`
398
+ model m = { name: "test", apiKey: "key", url: "http://test" }
399
+ let results = []
400
+ for i in [1, 2, 3] {
401
+ async let r = do "iter_!{i}" m default
402
+ results.push(r)
403
+ }
404
+ `);
405
+
406
+ const aiProvider = createOrderTrackingAI(30, {
407
+ 'iter_1': 'R1',
408
+ 'iter_2': 'R2',
409
+ 'iter_3': 'R3',
410
+ }, events);
411
+
412
+ const runtime = new Runtime(ast, aiProvider);
413
+ await runtime.run();
414
+
415
+ // Each iteration should complete before the next starts
416
+ // (because results.push(r) uses r, triggering await)
417
+ const iter1End = events.find(e => e.type === 'ai_call_end' && e.id.includes('iter_1'));
418
+ const iter2Start = events.find(e => e.type === 'ai_call_start' && e.id.includes('iter_2'));
419
+ const iter2End = events.find(e => e.type === 'ai_call_end' && e.id.includes('iter_2'));
420
+ const iter3Start = events.find(e => e.type === 'ai_call_start' && e.id.includes('iter_3'));
421
+
422
+ expect(iter1End!.timestamp).toBeLessThanOrEqual(iter2Start!.timestamp);
423
+ expect(iter2End!.timestamp).toBeLessThanOrEqual(iter3Start!.timestamp);
424
+
425
+ expect(runtime.getValue('results')).toEqual(['R1', 'R2', 'R3']);
426
+ });
427
+
428
+ test('multiple async in same iteration can run in parallel', async () => {
429
+ const events: ExecutionEvent[] = [];
430
+
431
+ const ast = parse(`
432
+ model m = { name: "test", apiKey: "key", url: "http://test" }
433
+ let results = []
434
+ for i in [1] {
435
+ async let a = do "loop_A" m default
436
+ async let b = do "loop_B" m default
437
+ results.push(a + b)
438
+ }
439
+ `);
440
+
441
+ const aiProvider = createOrderTrackingAI(50, {
442
+ 'loop_A': 'A',
443
+ 'loop_B': 'B',
444
+ }, events);
445
+
446
+ const runtime = new Runtime(ast, aiProvider);
447
+ await runtime.run();
448
+
449
+ // A and B should overlap within the same iteration
450
+ expect(eventsOverlap(events, 'loop_A', 'loop_B')).toBe(true);
451
+ expect(runtime.getValue('results')).toEqual(['AB']);
452
+ });
453
+ });
454
+
455
+ describe('block boundary awaits', () => {
456
+ test('if block awaits pending async before exiting', async () => {
457
+ const events: ExecutionEvent[] = [];
458
+
459
+ const ast = parse(`
460
+ model m = { name: "test", apiKey: "key", url: "http://test" }
461
+ let captured = ""
462
+ if true {
463
+ async let x = do "in_if_block" m default
464
+ captured = x
465
+ }
466
+ let after = "after_if"
467
+ `);
468
+
469
+ const aiProvider = createOrderTrackingAI(75, {
470
+ 'in_if_block': 'IF_VALUE',
471
+ }, events);
472
+
473
+ const runtime = new Runtime(ast, aiProvider);
474
+ await runtime.run();
475
+
476
+ // x should be awaited before exiting if block
477
+ expect(runtime.getValue('captured')).toBe('IF_VALUE');
478
+ expect(runtime.getValue('after')).toBe('after_if');
479
+ });
480
+
481
+ test('for loop awaits pending async at end of each iteration', async () => {
482
+ const events: ExecutionEvent[] = [];
483
+
484
+ const ast = parse(`
485
+ model m = { name: "test", apiKey: "key", url: "http://test" }
486
+ let results = []
487
+ for i in [1, 2] {
488
+ async let x = do "iter_!{i}" m default
489
+ results.push(x)
490
+ }
491
+ `);
492
+
493
+ const aiProvider = createOrderTrackingAI(50, {
494
+ 'iter_1': 'R1',
495
+ 'iter_2': 'R2',
496
+ }, events);
497
+
498
+ const runtime = new Runtime(ast, aiProvider);
499
+ await runtime.run();
500
+
501
+ // Each iteration should await before next iteration
502
+ // So results should be in order
503
+ expect(runtime.getValue('results')).toEqual(['R1', 'R2']);
504
+
505
+ // Verify sequential execution (iter_1 ends before iter_2 starts)
506
+ const iter1End = events.find(e => e.type === 'ai_call_end' && e.id.includes('iter_1'));
507
+ const iter2Start = events.find(e => e.type === 'ai_call_start' && e.id.includes('iter_2'));
508
+ expect(iter1End).toBeDefined();
509
+ expect(iter2Start).toBeDefined();
510
+ expect(iter1End!.timestamp).toBeLessThanOrEqual(iter2Start!.timestamp);
511
+ });
512
+
513
+ test('while loop awaits pending async at end of each iteration', async () => {
514
+ const events: ExecutionEvent[] = [];
515
+
516
+ const ast = parse(`
517
+ model m = { name: "test", apiKey: "key", url: "http://test" }
518
+ let results = []
519
+ let i = 0
520
+ while i < 2 {
521
+ async let x = do "while_!{i}" m default
522
+ results.push(x)
523
+ i = i + 1
524
+ }
525
+ `);
526
+
527
+ const aiProvider = createOrderTrackingAI(50, {
528
+ 'while_0': 'W0',
529
+ 'while_1': 'W1',
530
+ }, events);
531
+
532
+ const runtime = new Runtime(ast, aiProvider);
533
+ await runtime.run();
534
+
535
+ expect(runtime.getValue('results')).toEqual(['W0', 'W1']);
536
+ });
537
+
538
+ test('nested blocks await at each level', async () => {
539
+ const events: ExecutionEvent[] = [];
540
+
541
+ const ast = parse(`
542
+ model m = { name: "test", apiKey: "key", url: "http://test" }
543
+ let outer_val = ""
544
+ let inner_val = ""
545
+ if true {
546
+ async let o = do "outer_async" m default
547
+ outer_val = o
548
+ if true {
549
+ async let i = do "inner_async" m default
550
+ inner_val = i
551
+ }
552
+ }
553
+ `);
554
+
555
+ const aiProvider = createOrderTrackingAI(50, {
556
+ 'outer_async': 'OUTER',
557
+ 'inner_async': 'INNER',
558
+ }, events);
559
+
560
+ const runtime = new Runtime(ast, aiProvider);
561
+ await runtime.run();
562
+
563
+ expect(runtime.getValue('outer_val')).toBe('OUTER');
564
+ expect(runtime.getValue('inner_val')).toBe('INNER');
565
+ });
566
+ });
567
+
568
+ describe('program completion awaits all pending', () => {
569
+ test('pending async operations complete before program ends', async () => {
570
+ const events: ExecutionEvent[] = [];
571
+
572
+ const ast = parse(`
573
+ model m = { name: "test", apiKey: "key", url: "http://test" }
574
+ async let a = do "fire_forget_A" m default
575
+ async let b = do "fire_forget_B" m default
576
+ `);
577
+
578
+ const aiProvider = createOrderTrackingAI(75, {
579
+ 'fire_forget_A': 'A',
580
+ 'fire_forget_B': 'B',
581
+ }, events);
582
+
583
+ const runtime = new Runtime(ast, aiProvider);
584
+ await runtime.run();
585
+
586
+ // Both should have completed (not left pending)
587
+ const ends = events.filter(e => e.type === 'ai_call_end');
588
+ expect(ends.length).toBe(2);
589
+
590
+ // Values should be set
591
+ expect(runtime.getValue('a')).toBe('A');
592
+ expect(runtime.getValue('b')).toBe('B');
593
+ });
594
+
595
+ test('standalone async (fire-and-forget) completes before program ends', async () => {
596
+ const events: ExecutionEvent[] = [];
597
+
598
+ const ast = parse(`
599
+ model m = { name: "test", apiKey: "key", url: "http://test" }
600
+ async do "standalone_op" m default
601
+ let x = "done"
602
+ `);
603
+
604
+ const aiProvider = createOrderTrackingAI(50, {
605
+ 'standalone_op': 'ignored',
606
+ }, events);
607
+
608
+ const runtime = new Runtime(ast, aiProvider);
609
+ await runtime.run();
610
+
611
+ // The standalone async should have completed
612
+ const ends = events.filter(e => e.type === 'ai_call_end');
613
+ expect(ends.length).toBe(1);
614
+
615
+ expect(runtime.getValue('x')).toBe('done');
616
+ });
617
+ });
618
+ });