@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,357 @@
1
+ import type { RuntimeState, ContextEntry, ContextVariable, ContextPrompt, ContextToolCall, FrameEntry } from './types';
2
+ import { resolveValue } from './types';
3
+
4
+ // Types that are filtered from context (config/instructions, not data for AI)
5
+ const FILTERED_TYPES = ['model', 'prompt'];
6
+
7
+ // Build local context - entries from current frame only
8
+ // Pure function: takes full state, returns context array
9
+ // Uses snapshotted values from entries for accurate history
10
+ // Note: Model, prompt, and private variables are filtered out (they are config/instructions, not data for AI context)
11
+ export function buildLocalContext(state: RuntimeState): ContextEntry[] {
12
+ const frameIndex = state.callStack.length - 1;
13
+ const frame = state.callStack[frameIndex];
14
+ if (!frame) return [];
15
+
16
+ return frame.orderedEntries
17
+ .flatMap((entry): ContextEntry[] => {
18
+ if (entry.kind === 'variable') {
19
+ // Filter out model, prompt types, and private variables
20
+ if (FILTERED_TYPES.includes(entry.type ?? '') || entry.isPrivate) {
21
+ return [];
22
+ }
23
+ // Use snapshotted value from entry, resolving VibeValue to its value
24
+ const contextVar: ContextVariable = {
25
+ kind: 'variable',
26
+ name: entry.name,
27
+ value: resolveValue(entry.value), // Resolve VibeValue to primitive
28
+ type: entry.type as 'text' | 'json' | 'boolean' | 'number' | null,
29
+ isConst: entry.isConst, // Use snapshotted isConst
30
+ frameName: frame.name,
31
+ frameDepth: frameIndex,
32
+ source: entry.source ?? null, // Always include source, use null as default
33
+ };
34
+ return [contextVar];
35
+ } else if (entry.kind === 'prompt') {
36
+ const contextPrompt: ContextPrompt = {
37
+ kind: 'prompt',
38
+ aiType: entry.aiType,
39
+ prompt: entry.prompt,
40
+ frameName: frame.name,
41
+ frameDepth: frameIndex,
42
+ };
43
+ // Include toolCalls if present
44
+ if (entry.toolCalls && entry.toolCalls.length > 0) {
45
+ contextPrompt.toolCalls = entry.toolCalls;
46
+ }
47
+ // Only include response if defined
48
+ if (entry.response !== undefined) {
49
+ contextPrompt.response = entry.response;
50
+ }
51
+ return [contextPrompt];
52
+ } else if (entry.kind === 'scope-enter' || entry.kind === 'scope-exit') {
53
+ return [{
54
+ kind: entry.kind,
55
+ scopeType: entry.scopeType,
56
+ label: entry.label,
57
+ frameName: frame.name,
58
+ frameDepth: frameIndex,
59
+ }];
60
+ } else if (entry.kind === 'summary') {
61
+ return [{
62
+ kind: 'summary',
63
+ text: entry.text,
64
+ frameName: frame.name,
65
+ frameDepth: frameIndex,
66
+ }];
67
+ } else if (entry.kind === 'tool-call') {
68
+ const toolEntry: ContextToolCall = {
69
+ kind: 'tool-call',
70
+ toolName: entry.toolName,
71
+ args: entry.args,
72
+ frameName: frame.name,
73
+ frameDepth: frameIndex,
74
+ };
75
+ if (entry.result !== undefined) {
76
+ toolEntry.result = entry.result;
77
+ }
78
+ if (entry.error !== undefined) {
79
+ toolEntry.error = entry.error;
80
+ }
81
+ return [toolEntry];
82
+ }
83
+ return [];
84
+ });
85
+ }
86
+
87
+ // Build global context - entries from all frames in call stack
88
+ // Pure function: takes full state, returns context array
89
+ // Uses snapshotted values from entries for accurate history
90
+ // Note: Model, prompt, and private variables are filtered out (they are config/instructions, not data for AI context)
91
+ // Entries are returned with frameDepth: 0 = entry frame, higher = deeper in call stack
92
+ export function buildGlobalContext(state: RuntimeState): ContextEntry[] {
93
+ return state.callStack.flatMap((frame, frameIndex) => {
94
+ const frameDepth = frameIndex;
95
+
96
+ return frame.orderedEntries.flatMap((entry): ContextEntry[] => {
97
+ if (entry.kind === 'variable') {
98
+ // Filter out model, prompt types, and private variables
99
+ if (FILTERED_TYPES.includes(entry.type ?? '') || entry.isPrivate) {
100
+ return [];
101
+ }
102
+ // Use snapshotted value from entry, resolving VibeValue to its value
103
+ const contextVar: ContextVariable = {
104
+ kind: 'variable',
105
+ name: entry.name,
106
+ value: resolveValue(entry.value), // Resolve VibeValue to primitive
107
+ type: entry.type as 'text' | 'json' | 'boolean' | 'number' | null,
108
+ isConst: entry.isConst, // Use snapshotted isConst
109
+ frameName: frame.name,
110
+ frameDepth,
111
+ source: entry.source ?? null, // Always include source, use null as default
112
+ };
113
+ return [contextVar];
114
+ } else if (entry.kind === 'prompt') {
115
+ const contextPrompt: ContextPrompt = {
116
+ kind: 'prompt',
117
+ aiType: entry.aiType,
118
+ prompt: entry.prompt,
119
+ frameName: frame.name,
120
+ frameDepth,
121
+ };
122
+ // Include toolCalls if present
123
+ if (entry.toolCalls && entry.toolCalls.length > 0) {
124
+ contextPrompt.toolCalls = entry.toolCalls;
125
+ }
126
+ // Only include response if defined
127
+ if (entry.response !== undefined) {
128
+ contextPrompt.response = entry.response;
129
+ }
130
+ return [contextPrompt];
131
+ } else if (entry.kind === 'scope-enter' || entry.kind === 'scope-exit') {
132
+ return [{
133
+ kind: entry.kind,
134
+ scopeType: entry.scopeType,
135
+ label: entry.label,
136
+ frameName: frame.name,
137
+ frameDepth,
138
+ }];
139
+ } else if (entry.kind === 'summary') {
140
+ return [{
141
+ kind: 'summary',
142
+ text: entry.text,
143
+ frameName: frame.name,
144
+ frameDepth,
145
+ }];
146
+ } else if (entry.kind === 'tool-call') {
147
+ const toolEntry: ContextToolCall = {
148
+ kind: 'tool-call',
149
+ toolName: entry.toolName,
150
+ args: entry.args,
151
+ frameName: frame.name,
152
+ frameDepth,
153
+ };
154
+ if (entry.result !== undefined) {
155
+ toolEntry.result = entry.result;
156
+ }
157
+ if (entry.error !== undefined) {
158
+ toolEntry.error = entry.error;
159
+ }
160
+ return [toolEntry];
161
+ }
162
+ return [];
163
+ });
164
+ });
165
+ }
166
+
167
+ // Formatted context for AI consumption
168
+ export interface FormattedContext {
169
+ text: string;
170
+ variables: ContextEntry[];
171
+ }
172
+
173
+ // Format context for AI calls with instructional wrapping
174
+ export function formatContextForAI(
175
+ context: ContextEntry[],
176
+ options?: { includeInstructions?: boolean }
177
+ ): FormattedContext {
178
+ const text = formatContextText(context, options?.includeInstructions ?? true);
179
+ return { text, variables: context };
180
+ }
181
+
182
+ // Group entries by frame for structured output
183
+ function groupByFrame(entries: ContextEntry[]): Map<string, ContextEntry[]> {
184
+ const groups = new Map<string, ContextEntry[]>();
185
+
186
+ for (const entry of entries) {
187
+ const key = `${entry.frameDepth}:${entry.frameName}`;
188
+ const existing = groups.get(key) ?? [];
189
+ groups.set(key, [...existing, entry]);
190
+ }
191
+
192
+ return groups;
193
+ }
194
+
195
+ // Format context as text with optional instruction header
196
+ // Entries are grouped by call stack frame in execution order
197
+ function formatContextText(
198
+ entries: ContextEntry[],
199
+ includeInstructions: boolean
200
+ ): string {
201
+ const lines: string[] = [];
202
+
203
+ if (includeInstructions) {
204
+ lines.push('## VIBE Program Context');
205
+ lines.push('Variables from the VIBE language call stack.');
206
+ lines.push('');
207
+ }
208
+
209
+ if (entries.length > 0) {
210
+ formatFrameGroups(entries, lines);
211
+ }
212
+
213
+ return lines.join('\n').trimEnd();
214
+ }
215
+
216
+ // Format entries grouped by frame with indentation
217
+ // Depth 0 = entry (leftmost), higher depth = deeper in call stack (more indented)
218
+ function formatFrameGroups(entries: ContextEntry[], lines: string[]): void {
219
+ const frameGroups = groupByFrame(entries);
220
+ const sortedKeys = [...frameGroups.keys()].sort((a, b) => {
221
+ const depthA = parseInt(a.split(':')[0]);
222
+ const depthB = parseInt(b.split(':')[0]);
223
+ return depthA - depthB; // Lower depth (entry) first
224
+ });
225
+
226
+ // Find max depth to identify current scope
227
+ const maxDepth = Math.max(...entries.map((e) => e.frameDepth));
228
+
229
+ for (const key of sortedKeys) {
230
+ const frameEntries = frameGroups.get(key) ?? [];
231
+ if (frameEntries.length === 0) continue;
232
+
233
+ const { frameName, frameDepth } = frameEntries[0];
234
+ // Label: entry at depth 0, current scope at max depth, otherwise show depth
235
+ const scopeLabel =
236
+ frameDepth === maxDepth
237
+ ? '(current scope)'
238
+ : frameDepth === 0
239
+ ? '(entry)'
240
+ : `(depth ${frameDepth})`;
241
+ // Indent: base indent for section + extra indent per frame depth
242
+ const indent = ' ' + ' '.repeat(frameDepth);
243
+ lines.push(`${indent}${frameName} ${scopeLabel}`);
244
+
245
+ for (const entry of frameEntries) {
246
+ if (entry.kind === 'variable') {
247
+ const typeStr = entry.type ? ` (${entry.type})` : '';
248
+ const valueStr =
249
+ typeof entry.value === 'object' ? JSON.stringify(entry.value) : String(entry.value);
250
+ // Use <-- for AI/user responses, - for regular variables
251
+ const prefix = entry.source === 'ai' || entry.source === 'user' ? '<--' : '-';
252
+ lines.push(`${indent} ${prefix} ${entry.name}${typeStr}: ${valueStr}`);
253
+ } else if (entry.kind === 'prompt') {
254
+ // Prompt entry: AI call → tool calls → response
255
+ lines.push(`${indent} --> ${entry.aiType}: "${entry.prompt}"`);
256
+ // Format tool calls in order (between prompt and response)
257
+ if (entry.toolCalls && entry.toolCalls.length > 0) {
258
+ for (const toolCall of entry.toolCalls) {
259
+ const argsStr = JSON.stringify(toolCall.args);
260
+ lines.push(`${indent} [tool] ${toolCall.toolName}(${argsStr})`);
261
+ if (toolCall.error) {
262
+ lines.push(`${indent} [error] ${toolCall.error}`);
263
+ } else if (toolCall.result !== undefined) {
264
+ const resultStr =
265
+ typeof toolCall.result === 'object' ? JSON.stringify(toolCall.result) : String(toolCall.result);
266
+ lines.push(`${indent} [result] ${resultStr}`);
267
+ }
268
+ }
269
+ }
270
+ // Note: We don't show the response here because it will be shown
271
+ // with the variable assignment that follows (with source: 'ai')
272
+ // This avoids duplication like:
273
+ // --> do: "prompt"
274
+ // <-- response <- would be duplicate
275
+ // <-- varName: response
276
+ } else if (entry.kind === 'scope-enter') {
277
+ // Scope enter marker
278
+ const labelStr = entry.label ? ` ${entry.label}` : '';
279
+ lines.push(`${indent} ==> ${entry.scopeType}${labelStr}`);
280
+ } else if (entry.kind === 'scope-exit') {
281
+ // Scope exit marker
282
+ const labelStr = entry.label ? ` ${entry.label}` : '';
283
+ lines.push(`${indent} <== ${entry.scopeType}${labelStr}`);
284
+ } else if (entry.kind === 'summary') {
285
+ // Summary from compress mode
286
+ lines.push(`${indent} [summary] ${entry.text}`);
287
+ } else if (entry.kind === 'tool-call') {
288
+ // Tool call with args and result/error
289
+ const argsStr = JSON.stringify(entry.args);
290
+ lines.push(`${indent} [tool] ${entry.toolName}(${argsStr})`);
291
+ if (entry.error) {
292
+ lines.push(`${indent} [error] ${entry.error}`);
293
+ } else if (entry.result !== undefined) {
294
+ const resultStr =
295
+ typeof entry.result === 'object' ? JSON.stringify(entry.result) : String(entry.result);
296
+ lines.push(`${indent} [result] ${resultStr}`);
297
+ }
298
+ }
299
+ }
300
+
301
+ lines.push('');
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Format FrameEntries for AI summarization (used by compress mode).
307
+ * Converts frame entries into a human-readable format suitable for AI summarization.
308
+ */
309
+ export function formatEntriesForSummarization(entries: FrameEntry[]): string {
310
+ const lines: string[] = [];
311
+
312
+ for (const entry of entries) {
313
+ if (entry.kind === 'variable') {
314
+ const typeStr = entry.type ? ` (${entry.type})` : '';
315
+ const valueStr = typeof entry.value === 'object' ? JSON.stringify(entry.value) : String(entry.value);
316
+ const sourceStr = entry.source === 'ai' ? ' [from AI]' : entry.source === 'user' ? ' [from user]' : '';
317
+ lines.push(`Variable ${entry.name}${typeStr} = ${valueStr}${sourceStr}`);
318
+ } else if (entry.kind === 'prompt') {
319
+ lines.push(`AI ${entry.aiType}: "${entry.prompt}"`);
320
+ if (entry.toolCalls && entry.toolCalls.length > 0) {
321
+ for (const toolCall of entry.toolCalls) {
322
+ const argsStr = JSON.stringify(toolCall.args);
323
+ lines.push(` Tool call: ${toolCall.toolName}(${argsStr})`);
324
+ if (toolCall.error) {
325
+ lines.push(` Error: ${toolCall.error}`);
326
+ } else if (toolCall.result !== undefined) {
327
+ const resultStr = typeof toolCall.result === 'object' ? JSON.stringify(toolCall.result) : String(toolCall.result);
328
+ lines.push(` Result: ${resultStr}`);
329
+ }
330
+ }
331
+ }
332
+ if (entry.response !== undefined) {
333
+ const responseStr = typeof entry.response === 'object' ? JSON.stringify(entry.response) : String(entry.response);
334
+ lines.push(` Response: ${responseStr}`);
335
+ }
336
+ } else if (entry.kind === 'scope-enter') {
337
+ const labelStr = entry.label ? ` (${entry.label})` : '';
338
+ lines.push(`==> ${entry.scopeType}${labelStr} started`);
339
+ } else if (entry.kind === 'scope-exit') {
340
+ const labelStr = entry.label ? ` (${entry.label})` : '';
341
+ lines.push(`<== ${entry.scopeType}${labelStr} ended`);
342
+ } else if (entry.kind === 'summary') {
343
+ lines.push(`Summary: ${entry.text}`);
344
+ } else if (entry.kind === 'tool-call') {
345
+ const argsStr = JSON.stringify(entry.args);
346
+ lines.push(`Tool call: ${entry.toolName}(${argsStr})`);
347
+ if (entry.error) {
348
+ lines.push(` Error: ${entry.error}`);
349
+ } else if (entry.result !== undefined) {
350
+ const resultStr = typeof entry.result === 'object' ? JSON.stringify(entry.result) : String(entry.result);
351
+ lines.push(` Result: ${resultStr}`);
352
+ }
353
+ }
354
+ }
355
+
356
+ return lines.join('\n');
357
+ }
@@ -0,0 +1,139 @@
1
+ // AI operations: vibe expression and execution
2
+
3
+ import * as AST from '../../ast';
4
+ import type { RuntimeState } from '../types';
5
+ import { resolveValue } from '../types';
6
+ import { currentFrame } from '../state';
7
+ import { scheduleAsyncOperation, isInAsyncContext } from '../async/scheduling';
8
+
9
+ /**
10
+ * Extract model name from expression (must be an identifier), or null if not provided.
11
+ */
12
+ export function extractModelName(expr: AST.Expression | null): string | null {
13
+ if (expr === null) return null;
14
+ if (expr.type === 'Identifier') return expr.name;
15
+ throw new Error('Model must be an identifier');
16
+ }
17
+
18
+ /**
19
+ * Vibe/Do expression - push instructions for AI call.
20
+ * operationType determines tool loop behavior: 'vibe' = multi-turn, 'do' = single round.
21
+ * Sets inPromptContext flag so string literals use prompt interpolation semantics.
22
+ */
23
+ export function execVibeExpression(state: RuntimeState, expr: AST.VibeExpression): RuntimeState {
24
+ return {
25
+ ...state,
26
+ inPromptContext: true, // Prompt string interpolation mode
27
+ instructionStack: [
28
+ { op: 'exec_expression', expr: expr.prompt, location: expr.prompt.location },
29
+ { op: 'clear_prompt_context', location: expr.location }, // Clear flag after prompt eval
30
+ { op: 'ai_vibe', model: extractModelName(expr.model), context: expr.context, operationType: expr.operationType, location: expr.location },
31
+ ...state.instructionStack,
32
+ ],
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Get context data for AI based on context specifier.
38
+ * If context is null, defaults to 'default' (full execution history).
39
+ */
40
+ export function getContextForAI(state: RuntimeState, context: AST.ContextSpecifier | null): unknown[] {
41
+ // Default to full execution history if no context specified
42
+ if (context === null) {
43
+ return state.executionLog;
44
+ }
45
+
46
+ switch (context.kind) {
47
+ case 'local':
48
+ // Current frame's execution log only
49
+ return state.executionLog.filter((_, i) => {
50
+ // Filter to just recent entries (simplified - could be smarter)
51
+ return i >= state.executionLog.length - 10;
52
+ });
53
+
54
+ case 'default':
55
+ // All execution history
56
+ return state.executionLog;
57
+
58
+ case 'variable':
59
+ // Use variable value as context
60
+ if (context.variable) {
61
+ const frame = currentFrame(state);
62
+ const variable = frame.locals[context.variable];
63
+ if (variable && Array.isArray(variable.value)) {
64
+ return variable.value as unknown[];
65
+ }
66
+ }
67
+ return [];
68
+
69
+ default:
70
+ return state.executionLog;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * AI Vibe/Do - pause for AI response.
76
+ * Note: The prompt is added to orderedEntries in resumeWithAIResponse (after completion),
77
+ * not here, so it doesn't appear in context before the AI call completes.
78
+ *
79
+ * If model is null, uses lastUsedModel from state.
80
+ * If context is null, defaults to 'default' (full execution history).
81
+ *
82
+ * When currentAsyncVarName is set (async declaration), schedules the operation
83
+ * for non-blocking execution instead of pausing.
84
+ */
85
+ export function execAIVibe(state: RuntimeState, model: string | null, context: AST.ContextSpecifier | null, operationType: 'do' | 'vibe'): RuntimeState {
86
+ // Unwrap VibeValue if needed before converting to string
87
+ const prompt = String(resolveValue(state.lastResult));
88
+
89
+ // Resolve model: use provided model or fall back to lastUsedModel
90
+ const resolvedModel = model ?? state.lastUsedModel;
91
+ if (!resolvedModel) {
92
+ throw new Error('No model specified and no previous model has been used. Please specify a model.');
93
+ }
94
+
95
+ const contextData = getContextForAI(state, context);
96
+ const contextKind = context?.kind ?? 'default';
97
+
98
+ // Check if we're in async context (variable, destructuring, or fire-and-forget)
99
+ if (isInAsyncContext(state)) {
100
+ // Schedule for non-blocking execution using shared helper
101
+ const newState = scheduleAsyncOperation(
102
+ state,
103
+ {
104
+ type: operationType,
105
+ aiDetails: {
106
+ prompt,
107
+ model: resolvedModel,
108
+ context: contextData,
109
+ operationType,
110
+ },
111
+ },
112
+ `async_${operationType}_scheduled`
113
+ );
114
+ // AI operations also update lastUsedModel
115
+ return { ...newState, lastUsedModel: resolvedModel };
116
+ }
117
+
118
+ // Normal blocking execution
119
+ return {
120
+ ...state,
121
+ status: 'awaiting_ai',
122
+ // Update lastUsedModel for compress to use
123
+ lastUsedModel: resolvedModel,
124
+ pendingAI: {
125
+ type: operationType, // 'do' = single round, 'vibe' = multi-turn
126
+ prompt,
127
+ model: resolvedModel,
128
+ context: contextData,
129
+ },
130
+ executionLog: [
131
+ ...state.executionLog,
132
+ {
133
+ timestamp: Date.now(),
134
+ instructionType: operationType === 'do' ? 'ai_do_request' : 'ai_vibe_request',
135
+ details: { prompt, model: resolvedModel, contextKind },
136
+ },
137
+ ],
138
+ };
139
+ }