@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,604 @@
1
+ // Statement execution helpers: declarations, control flow
2
+
3
+ import * as AST from '../../ast';
4
+ import type { SourceLocation } from '../../errors';
5
+ import type { RuntimeState, VibeValue } from '../types';
6
+ import { createVibeValue, resolveValue, isVibeValue } from '../types';
7
+ import { currentFrame } from '../state';
8
+ import { requireBoolean, validateAndCoerce } from '../validation';
9
+ import { execDeclareVar } from './variables';
10
+ import { getImportedVibeFunction } from '../modules';
11
+
12
+ /**
13
+ * Let declaration - push instructions for initializer.
14
+ * For async declarations, sets context flag so AI/TS handlers use non-blocking mode.
15
+ * For prompt-typed variables, sets inPromptContext for string interpolation semantics.
16
+ */
17
+ export function execLetDeclaration(state: RuntimeState, stmt: AST.LetDeclaration): RuntimeState {
18
+ if (stmt.initializer) {
19
+ // For async declarations, set context flag before evaluating
20
+ let baseState = stmt.isAsync ? {
21
+ ...state,
22
+ currentAsyncVarName: stmt.name,
23
+ currentAsyncIsConst: false,
24
+ currentAsyncType: stmt.typeAnnotation,
25
+ currentAsyncIsPrivate: stmt.isPrivate ?? false,
26
+ } : state;
27
+
28
+ // For prompt-typed variables, set inPromptContext for string interpolation
29
+ const isPromptType = stmt.typeAnnotation === 'prompt';
30
+ if (isPromptType) {
31
+ baseState = { ...baseState, inPromptContext: true };
32
+ }
33
+
34
+ // Build instruction stack with optional clear_prompt_context
35
+ const instructions: RuntimeState['instructionStack'] = [
36
+ { op: 'exec_expression', expr: stmt.initializer, location: stmt.initializer.location },
37
+ ];
38
+ if (isPromptType) {
39
+ instructions.push({ op: 'clear_prompt_context', location: stmt.location });
40
+ }
41
+ instructions.push(
42
+ { op: 'declare_var', name: stmt.name, isConst: false, type: stmt.typeAnnotation, isPrivate: stmt.isPrivate, location: stmt.location },
43
+ ...state.instructionStack
44
+ );
45
+
46
+ return {
47
+ ...baseState,
48
+ instructionStack: instructions,
49
+ };
50
+ }
51
+
52
+ // No initializer, declare with null
53
+ return execDeclareVar(state, stmt.name, false, stmt.typeAnnotation, null, stmt.isPrivate);
54
+ }
55
+
56
+ /**
57
+ * Const declaration - push instructions for initializer.
58
+ * For async declarations, sets context flag so AI/TS handlers use non-blocking mode.
59
+ * For prompt-typed variables, sets inPromptContext for string interpolation semantics.
60
+ */
61
+ export function execConstDeclaration(state: RuntimeState, stmt: AST.ConstDeclaration): RuntimeState {
62
+ // For async declarations, set context flag before evaluating
63
+ let baseState = stmt.isAsync ? {
64
+ ...state,
65
+ currentAsyncVarName: stmt.name,
66
+ currentAsyncIsConst: true,
67
+ currentAsyncType: stmt.typeAnnotation,
68
+ currentAsyncIsPrivate: stmt.isPrivate ?? false,
69
+ } : state;
70
+
71
+ // For prompt-typed variables, set inPromptContext for string interpolation
72
+ const isPromptType = stmt.typeAnnotation === 'prompt';
73
+ if (isPromptType) {
74
+ baseState = { ...baseState, inPromptContext: true };
75
+ }
76
+
77
+ // Build instruction stack with optional clear_prompt_context
78
+ const instructions: RuntimeState['instructionStack'] = [
79
+ { op: 'exec_expression', expr: stmt.initializer, location: stmt.initializer.location },
80
+ ];
81
+ if (isPromptType) {
82
+ instructions.push({ op: 'clear_prompt_context', location: stmt.location });
83
+ }
84
+ instructions.push(
85
+ { op: 'declare_var', name: stmt.name, isConst: true, type: stmt.typeAnnotation, isPrivate: stmt.isPrivate, location: stmt.location },
86
+ ...state.instructionStack
87
+ );
88
+
89
+ return {
90
+ ...baseState,
91
+ instructionStack: instructions,
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Destructuring declaration - evaluate initializer (AI expression) and assign fields.
97
+ * const {name: text, age: number} = do "..." model default
98
+ * For async declarations, sets context flag so AI/TS handlers use non-blocking mode.
99
+ * For destructuring, currentAsyncIsDestructure=true tells the async system to NOT update
100
+ * any variable on completion - the actual variables are created by destructure_assign.
101
+ */
102
+ export function execDestructuringDeclaration(
103
+ state: RuntimeState,
104
+ stmt: AST.DestructuringDeclaration
105
+ ): RuntimeState {
106
+ // Convert AST fields to ExpectedField format for runtime (including isPrivate)
107
+ const expectedFields = stmt.fields.map((f) => ({
108
+ name: f.name,
109
+ type: f.type,
110
+ ...(f.isPrivate ? { isPrivate: true } : {}),
111
+ }));
112
+
113
+ // For async declarations, set isDestructure flag to enable async path
114
+ // variableName stays null - destructure_assign creates the actual variables
115
+ const baseState = stmt.isAsync ? {
116
+ ...state,
117
+ currentAsyncVarName: null, // No single variable - destructure_assign handles it
118
+ currentAsyncIsConst: stmt.isConst,
119
+ currentAsyncType: null, // Destructuring doesn't have single type
120
+ currentAsyncIsPrivate: false,
121
+ currentAsyncIsDestructure: true, // Signals async path without variable tracking
122
+ } : state;
123
+
124
+ return {
125
+ ...baseState,
126
+ // Set pendingDestructuring so AI provider knows what fields to expect
127
+ pendingDestructuring: expectedFields,
128
+ instructionStack: [
129
+ { op: 'exec_expression', expr: stmt.initializer, location: stmt.initializer.location },
130
+ { op: 'destructure_assign', fields: expectedFields, isConst: stmt.isConst, location: stmt.location },
131
+ ...state.instructionStack,
132
+ ],
133
+ };
134
+ }
135
+
136
+ // Model config fields in evaluation order
137
+ const MODEL_CONFIG_FIELDS = ['modelName', 'apiKey', 'url', 'provider', 'maxRetriesOnError', 'thinkingLevel', 'tools'] as const;
138
+
139
+ /**
140
+ * Model declaration - evaluate all config expressions through instruction stack.
141
+ * This allows CallExpressions (like env(), ts blocks, function calls) to work in model config.
142
+ */
143
+ export function execModelDeclaration(state: RuntimeState, stmt: AST.ModelDeclaration): RuntimeState {
144
+ const instructions: typeof state.instructionStack = [];
145
+
146
+ // Push evaluation instructions for each config field
147
+ // Fields are evaluated in order and pushed to valueStack
148
+ for (const field of MODEL_CONFIG_FIELDS) {
149
+ const expr = stmt.config[field];
150
+ if (expr) {
151
+ instructions.push({ op: 'exec_expression', expr, location: expr.location });
152
+ } else {
153
+ // Use undefined for missing fields to preserve backward compatibility
154
+ instructions.push({ op: 'literal', value: undefined, location: stmt.location });
155
+ }
156
+ instructions.push({ op: 'push_value', location: stmt.location });
157
+ }
158
+
159
+ // Finally, declare the model (will pop values from stack)
160
+ instructions.push({ op: 'declare_model', stmt, location: stmt.location });
161
+
162
+ return {
163
+ ...state,
164
+ instructionStack: [...instructions, ...state.instructionStack],
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Finalize model declaration by popping evaluated config values from valueStack.
170
+ */
171
+ export function finalizeModelDeclaration(
172
+ state: RuntimeState,
173
+ stmt: AST.ModelDeclaration
174
+ ): RuntimeState {
175
+ // Pop values from stack in reverse order (LIFO)
176
+ // Fields were pushed in order: modelName, apiKey, url, provider, maxRetriesOnError, thinkingLevel, tools
177
+ const fieldCount = MODEL_CONFIG_FIELDS.length;
178
+ const rawValues = state.valueStack.slice(-fieldCount);
179
+ const newValueStack = state.valueStack.slice(0, -fieldCount);
180
+
181
+ // Unwrap VibeValues to get raw values
182
+ const [modelName, apiKey, url, provider, maxRetriesOnError, thinkingLevel, tools] = rawValues.map(
183
+ v => resolveValue(v)
184
+ );
185
+
186
+ const modelValue = {
187
+ __vibeModel: true,
188
+ name: modelName as string | null,
189
+ apiKey: apiKey as string | null,
190
+ url: url as string | null,
191
+ provider: provider as string | null,
192
+ maxRetriesOnError: maxRetriesOnError as number | null,
193
+ thinkingLevel: thinkingLevel as string | null,
194
+ tools: tools as unknown[] | undefined,
195
+ };
196
+
197
+ const frame = currentFrame(state);
198
+ const newLocals = {
199
+ ...frame.locals,
200
+ [stmt.name]: createVibeValue(modelValue, { isConst: true, typeAnnotation: null }),
201
+ };
202
+
203
+ return {
204
+ ...state,
205
+ valueStack: newValueStack,
206
+ // Set lastUsedModel if not already set (first model declaration)
207
+ lastUsedModel: state.lastUsedModel ?? stmt.name,
208
+ callStack: [
209
+ ...state.callStack.slice(0, -1),
210
+ { ...frame, locals: newLocals },
211
+ ],
212
+ };
213
+ }
214
+
215
+ /**
216
+ * If statement - push condition and branch instruction.
217
+ */
218
+ export function execIfStatement(state: RuntimeState, stmt: AST.IfStatement): RuntimeState {
219
+ return {
220
+ ...state,
221
+ instructionStack: [
222
+ { op: 'exec_expression', expr: stmt.condition, location: stmt.condition.location },
223
+ { op: 'if_branch', consequent: stmt.consequent, alternate: stmt.alternate, location: stmt.location },
224
+ ...state.instructionStack,
225
+ ],
226
+ };
227
+ }
228
+
229
+ /**
230
+ * For-in statement - push iterable evaluation and for_in_init.
231
+ */
232
+ export function execForInStatement(state: RuntimeState, stmt: AST.ForInStatement): RuntimeState {
233
+ return {
234
+ ...state,
235
+ instructionStack: [
236
+ { op: 'exec_expression', expr: stmt.iterable, location: stmt.iterable.location },
237
+ { op: 'for_in_init', stmt, location: stmt.location },
238
+ ...state.instructionStack,
239
+ ],
240
+ };
241
+ }
242
+
243
+ /**
244
+ * While statement - evaluate condition and loop.
245
+ */
246
+ export function execWhileStatement(state: RuntimeState, stmt: AST.WhileStatement): RuntimeState {
247
+ const frame = currentFrame(state);
248
+ const savedKeys = Object.keys(frame.locals);
249
+
250
+ return {
251
+ ...state,
252
+ instructionStack: [
253
+ { op: 'exec_expression', expr: stmt.condition, location: stmt.condition.location },
254
+ { op: 'while_init', stmt, savedKeys, location: stmt.location },
255
+ ...state.instructionStack,
256
+ ],
257
+ };
258
+ }
259
+
260
+ /**
261
+ * If branch - decide based on lastResult.
262
+ */
263
+ export function execIfBranch(
264
+ state: RuntimeState,
265
+ consequent: AST.BlockStatement,
266
+ alternate?: AST.Statement | null
267
+ ): RuntimeState {
268
+ const condition = state.lastResult;
269
+
270
+ if (requireBoolean(condition, 'if condition')) {
271
+ return {
272
+ ...state,
273
+ instructionStack: [
274
+ { op: 'exec_statement', stmt: consequent, location: consequent.location },
275
+ ...state.instructionStack,
276
+ ],
277
+ };
278
+ } else if (alternate) {
279
+ return {
280
+ ...state,
281
+ instructionStack: [
282
+ { op: 'exec_statement', stmt: alternate, location: alternate.location },
283
+ ...state.instructionStack,
284
+ ],
285
+ };
286
+ }
287
+
288
+ return state;
289
+ }
290
+
291
+ /**
292
+ * Block statement - push statements with exit_block cleanup.
293
+ */
294
+ export function execBlockStatement(state: RuntimeState, stmt: AST.BlockStatement): RuntimeState {
295
+ const frame = currentFrame(state);
296
+ const savedKeys = Object.keys(frame.locals);
297
+
298
+ // Push statements in order (we pop from front, so first statement first)
299
+ const stmtInstructions = stmt.body
300
+ .map((s) => ({ op: 'exec_statement' as const, stmt: s, location: s.location }));
301
+
302
+ return {
303
+ ...state,
304
+ instructionStack: [
305
+ ...stmtInstructions,
306
+ { op: 'exit_block', savedKeys, location: stmt.location },
307
+ ...state.instructionStack,
308
+ ],
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Enter block scope (placeholder for symmetry).
314
+ */
315
+ export function execEnterBlock(state: RuntimeState, _savedKeys: string[]): RuntimeState {
316
+ return state;
317
+ }
318
+
319
+ /**
320
+ * Exit block scope - remove variables declared in block.
321
+ * Before removing, await any pending async operations for those variables.
322
+ */
323
+ export function execExitBlock(state: RuntimeState, savedKeys: string[], location?: SourceLocation): RuntimeState {
324
+ const frame = currentFrame(state);
325
+ const savedKeySet = new Set(savedKeys);
326
+
327
+ // Await ALL pending async operations at block boundaries
328
+ // This ensures operations started in loops are properly awaited even if their
329
+ // variables are overwritten by subsequent iterations
330
+ const pendingAsyncIds: string[] = [];
331
+ for (const opId of state.pendingAsyncIds) {
332
+ const operation = state.asyncOperations.get(opId);
333
+ if (operation && (operation.status === 'pending' || operation.status === 'running')) {
334
+ pendingAsyncIds.push(opId);
335
+ }
336
+ }
337
+
338
+ // If there are pending async operations, await them before cleaning up
339
+ if (pendingAsyncIds.length > 0) {
340
+ return {
341
+ ...state,
342
+ status: 'awaiting_async',
343
+ awaitingAsyncIds: pendingAsyncIds,
344
+ instructionStack: [
345
+ { op: 'exit_block', savedKeys, location: location ?? { line: 0, column: 0 } },
346
+ ...state.instructionStack,
347
+ ],
348
+ };
349
+ }
350
+
351
+ // No pending async - proceed with cleanup
352
+ const newLocals: Record<string, VibeValue> = {};
353
+ for (const key of Object.keys(frame.locals)) {
354
+ if (savedKeySet.has(key)) {
355
+ newLocals[key] = frame.locals[key];
356
+ }
357
+ }
358
+
359
+ return {
360
+ ...state,
361
+ callStack: [
362
+ ...state.callStack.slice(0, -1),
363
+ { ...frame, locals: newLocals },
364
+ ],
365
+ };
366
+ }
367
+
368
+ /**
369
+ * Return statement - evaluate value and return.
370
+ */
371
+ export function execReturnStatement(state: RuntimeState, stmt: AST.ReturnStatement): RuntimeState {
372
+ if (stmt.value) {
373
+ return {
374
+ ...state,
375
+ instructionStack: [
376
+ { op: 'exec_expression', expr: stmt.value, location: stmt.value.location },
377
+ { op: 'return_value', location: stmt.location },
378
+ ...state.instructionStack,
379
+ ],
380
+ };
381
+ }
382
+
383
+ return execReturnValue({ ...state, lastResult: null });
384
+ }
385
+
386
+ /**
387
+ * Return value - pop frame and skip to after pop_frame instruction.
388
+ */
389
+ export function execReturnValue(state: RuntimeState): RuntimeState {
390
+ const currentFrameRef = state.callStack[state.callStack.length - 1];
391
+ const funcName = currentFrameRef?.name;
392
+
393
+ // Check if return value is a pending async operation - need to await it first
394
+ const returnValue = state.lastResult;
395
+ if (isVibeValue(returnValue) && returnValue.asyncOperationId) {
396
+ const opId = returnValue.asyncOperationId;
397
+ const operation = state.asyncOperations.get(opId);
398
+ if (operation && (operation.status === 'pending' || operation.status === 'running')) {
399
+ // Need to await this operation before returning
400
+ return {
401
+ ...state,
402
+ status: 'awaiting_async',
403
+ awaitingAsyncIds: [opId],
404
+ // Re-queue the return_value instruction to run after await completes
405
+ instructionStack: [
406
+ { op: 'return_value', location: { line: 0, column: 0 } },
407
+ ...state.instructionStack,
408
+ ],
409
+ };
410
+ }
411
+ }
412
+
413
+ // Validate return type if function has one
414
+ let validatedReturnValue = returnValue;
415
+ if (funcName && funcName !== 'main') {
416
+ const func = state.functions[funcName] ?? getImportedVibeFunction(state, funcName);
417
+ if (func?.returnType) {
418
+ const { value: validatedValue } = validateAndCoerce(
419
+ validatedReturnValue,
420
+ func.returnType,
421
+ `return value of ${funcName}`
422
+ );
423
+ validatedReturnValue = validatedValue;
424
+ }
425
+ }
426
+
427
+ // Pop frame
428
+ const newCallStack = state.callStack.slice(0, -1);
429
+
430
+ if (newCallStack.length === 0) {
431
+ return { ...state, status: 'completed', callStack: newCallStack, lastResult: validatedReturnValue };
432
+ }
433
+
434
+ // Find and skip past the pop_frame instruction
435
+ let newInstructionStack = state.instructionStack;
436
+ const popFrameIndex = newInstructionStack.findIndex((i) => i.op === 'pop_frame');
437
+ if (popFrameIndex !== -1) {
438
+ newInstructionStack = newInstructionStack.slice(popFrameIndex + 1);
439
+ }
440
+
441
+ return { ...state, callStack: newCallStack, instructionStack: newInstructionStack, lastResult: validatedReturnValue };
442
+ }
443
+
444
+ /**
445
+ * Execute statements at index - sequential statement execution.
446
+ */
447
+ export function execStatements(state: RuntimeState, stmts: AST.Statement[], index: number, location: SourceLocation): RuntimeState {
448
+ if (index >= stmts.length) {
449
+ return state;
450
+ }
451
+
452
+ const stmt = stmts[index];
453
+ return {
454
+ ...state,
455
+ instructionStack: [
456
+ { op: 'exec_statement', stmt, location: stmt.location },
457
+ { op: 'exec_statements', stmts, index: index + 1, location },
458
+ ...state.instructionStack,
459
+ ],
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Break statement - exit the innermost loop.
465
+ * Awaits pending async operations and triggers compress if needed.
466
+ */
467
+ export function execBreakStatement(state: RuntimeState, stmt: AST.BreakStatement): RuntimeState {
468
+ // Find the innermost loop instruction in the instruction stack
469
+ const loopIndex = state.instructionStack.findIndex(
470
+ (instr) => instr.op === 'for_in_iterate' || instr.op === 'while_iterate' || instr.op === 'while_check'
471
+ );
472
+
473
+ if (loopIndex === -1) {
474
+ // This shouldn't happen if semantic analysis is correct
475
+ throw new Error('break statement outside of loop');
476
+ }
477
+
478
+ const loopInstr = state.instructionStack[loopIndex];
479
+
480
+ // Extract loop info based on instruction type
481
+ let savedKeys: string[];
482
+ let contextMode: AST.ContextMode | undefined;
483
+ let label: string | undefined;
484
+ let entryIndex: number;
485
+ let scopeType: 'for' | 'while';
486
+
487
+ if (loopInstr.op === 'for_in_iterate') {
488
+ savedKeys = loopInstr.savedKeys;
489
+ contextMode = loopInstr.contextMode;
490
+ label = loopInstr.label;
491
+ entryIndex = loopInstr.entryIndex;
492
+ scopeType = 'for';
493
+ } else {
494
+ // while_iterate or while_check
495
+ savedKeys = loopInstr.savedKeys;
496
+ contextMode = loopInstr.contextMode;
497
+ label = loopInstr.label;
498
+ entryIndex = loopInstr.entryIndex;
499
+ scopeType = 'while';
500
+ }
501
+
502
+ // Remove all instructions up to and including the loop instruction
503
+ const newInstructionStack = state.instructionStack.slice(loopIndex + 1);
504
+
505
+ // Push break_loop instruction to handle async await and context mode
506
+ return {
507
+ ...state,
508
+ instructionStack: [
509
+ {
510
+ op: 'break_loop',
511
+ savedKeys,
512
+ contextMode,
513
+ label,
514
+ entryIndex,
515
+ scopeType,
516
+ location: stmt.location,
517
+ },
518
+ ...newInstructionStack,
519
+ ],
520
+ };
521
+ }
522
+
523
+ /**
524
+ * Statement dispatcher - routes to appropriate statement handler.
525
+ */
526
+ export function execStatement(state: RuntimeState, stmt: AST.Statement): RuntimeState {
527
+ switch (stmt.type) {
528
+ case 'ImportDeclaration':
529
+ // Imports are processed during module loading, skip at runtime
530
+ return state;
531
+
532
+ case 'ExportDeclaration':
533
+ // Execute the underlying declaration
534
+ return execStatement(state, stmt.declaration);
535
+
536
+ case 'LetDeclaration':
537
+ return execLetDeclaration(state, stmt);
538
+
539
+ case 'ConstDeclaration':
540
+ return execConstDeclaration(state, stmt);
541
+
542
+ case 'DestructuringDeclaration':
543
+ return execDestructuringDeclaration(state, stmt);
544
+
545
+ case 'FunctionDeclaration':
546
+ // Functions are already collected at init, nothing to do
547
+ return state;
548
+
549
+ case 'ToolDeclaration':
550
+ // Register the tool at runtime
551
+ return {
552
+ ...state,
553
+ instructionStack: [
554
+ { op: 'exec_tool_declaration', decl: stmt, location: stmt.location },
555
+ ...state.instructionStack,
556
+ ],
557
+ };
558
+
559
+ case 'ModelDeclaration':
560
+ return execModelDeclaration(state, stmt);
561
+
562
+ case 'ReturnStatement':
563
+ return execReturnStatement(state, stmt);
564
+
565
+ case 'IfStatement':
566
+ return execIfStatement(state, stmt);
567
+
568
+ case 'ForInStatement':
569
+ return execForInStatement(state, stmt);
570
+
571
+ case 'WhileStatement':
572
+ return execWhileStatement(state, stmt);
573
+
574
+ case 'BlockStatement':
575
+ return execBlockStatement(state, stmt);
576
+
577
+ case 'ExpressionStatement':
578
+ return {
579
+ ...state,
580
+ instructionStack: [
581
+ { op: 'exec_expression', expr: stmt.expression, location: stmt.expression.location },
582
+ ...state.instructionStack,
583
+ ],
584
+ };
585
+
586
+ case 'AsyncStatement':
587
+ // Fire-and-forget async - set flag so handlers schedule async execution
588
+ return {
589
+ ...state,
590
+ currentAsyncIsFireAndForget: true,
591
+ instructionStack: [
592
+ { op: 'exec_expression', expr: stmt.expression, location: stmt.expression.location },
593
+ { op: 'clear_async_context', location: stmt.location },
594
+ ...state.instructionStack,
595
+ ],
596
+ };
597
+
598
+ case 'BreakStatement':
599
+ return execBreakStatement(state, stmt);
600
+
601
+ default:
602
+ throw new Error(`Unknown statement type: ${(stmt as AST.Statement).type}`);
603
+ }
604
+ }