@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,419 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { parse } from '../../parser/parse';
3
+ import { createInitialState, resumeWithAIResponse } from '../state';
4
+ import { runUntilPause } from '../step';
5
+ import { isVibeValue } from '../types';
6
+ import type { ToolRoundResult } from '../ai/tool-loop';
7
+
8
+ // Helper to run with mock AI response
9
+ function runWithMockAI(
10
+ state: ReturnType<typeof createInitialState>,
11
+ response: unknown,
12
+ toolRounds?: ToolRoundResult[]
13
+ ) {
14
+ state = runUntilPause(state);
15
+ while (state.status === 'awaiting_ai') {
16
+ state = resumeWithAIResponse(state, response, undefined, toolRounds);
17
+ state = runUntilPause(state);
18
+ }
19
+ return state;
20
+ }
21
+
22
+ describe('VibeValue AI Results', () => {
23
+ describe('basic structure', () => {
24
+ it('AI response returns VibeValue with value and empty toolCalls', () => {
25
+ const ast = parse(`
26
+ model m = { name: "test", apiKey: "key", url: "http://test" }
27
+ let result = do "test prompt" m default
28
+ `);
29
+ let state = createInitialState(ast);
30
+ state = runWithMockAI(state, 'test response');
31
+
32
+ const vibeValue = state.callStack[0].locals['result'];
33
+ expect(isVibeValue(vibeValue)).toBe(true);
34
+ expect(vibeValue.value).toBe('test response');
35
+ expect(vibeValue.toolCalls).toEqual([]);
36
+ expect(vibeValue.err).toBe(false); // err is now boolean
37
+ expect(vibeValue.errDetails).toBe(null);
38
+ expect(vibeValue.source).toBe('ai');
39
+ });
40
+
41
+ it('toolCalls array contains tool execution records with duration', () => {
42
+ const ast = parse(`
43
+ model m = { name: "test", apiKey: "key", url: "http://test" }
44
+ let result = vibe "test prompt" m default
45
+ `);
46
+ let state = createInitialState(ast);
47
+
48
+ // Simulate tool rounds
49
+ const toolRounds: ToolRoundResult[] = [
50
+ {
51
+ toolCalls: [
52
+ { id: 'call_1', toolName: 'fetchData', args: { url: 'http://api.com' } },
53
+ ],
54
+ results: [
55
+ { toolCallId: 'call_1', result: 'data from api', duration: 150 },
56
+ ],
57
+ },
58
+ ];
59
+
60
+ state = runUntilPause(state);
61
+ state = resumeWithAIResponse(state, 'final response', undefined, toolRounds);
62
+ state = runUntilPause(state);
63
+
64
+ const vibeValue = state.callStack[0].locals['result'];
65
+ expect(isVibeValue(vibeValue)).toBe(true);
66
+ expect(vibeValue.toolCalls).toHaveLength(1);
67
+ expect(vibeValue.toolCalls[0]).toEqual({
68
+ toolName: 'fetchData',
69
+ args: { url: 'http://api.com' },
70
+ result: 'data from api',
71
+ err: false,
72
+ errDetails: null,
73
+ duration: 150,
74
+ });
75
+ });
76
+
77
+ it('captures tool errors in toolCalls', () => {
78
+ const ast = parse(`
79
+ model m = { name: "test", apiKey: "key", url: "http://test" }
80
+ let result = vibe "test prompt" m default
81
+ `);
82
+ let state = createInitialState(ast);
83
+
84
+ const toolRounds: ToolRoundResult[] = [
85
+ {
86
+ toolCalls: [
87
+ { id: 'call_1', toolName: 'failingTool', args: {} },
88
+ ],
89
+ results: [
90
+ { toolCallId: 'call_1', error: 'Connection failed', duration: 50 },
91
+ ],
92
+ },
93
+ ];
94
+
95
+ state = runUntilPause(state);
96
+ state = resumeWithAIResponse(state, 'error handled', undefined, toolRounds);
97
+ state = runUntilPause(state);
98
+
99
+ const vibeValue = state.callStack[0].locals['result'];
100
+ expect(vibeValue.toolCalls[0]).toEqual({
101
+ toolName: 'failingTool',
102
+ args: {},
103
+ result: null,
104
+ err: true,
105
+ errDetails: { message: 'Connection failed' },
106
+ duration: 50,
107
+ });
108
+ });
109
+
110
+ it('accumulates all tool calls across multiple rounds (vibe)', () => {
111
+ const ast = parse(`
112
+ model m = { name: "test", apiKey: "key", url: "http://test" }
113
+ let result = vibe "multi-step task" m default
114
+ `);
115
+ let state = createInitialState(ast);
116
+
117
+ const toolRounds: ToolRoundResult[] = [
118
+ {
119
+ toolCalls: [{ id: 'call_1', toolName: 'step1', args: { n: 1 } }],
120
+ results: [{ toolCallId: 'call_1', result: 'step1 done', duration: 10 }],
121
+ },
122
+ {
123
+ toolCalls: [{ id: 'call_2', toolName: 'step2', args: { n: 2 } }],
124
+ results: [{ toolCallId: 'call_2', result: 'step2 done', duration: 20 }],
125
+ },
126
+ {
127
+ toolCalls: [{ id: 'call_3', toolName: 'step3', args: { n: 3 } }],
128
+ results: [{ toolCallId: 'call_3', result: 'step3 done', duration: 30 }],
129
+ },
130
+ ];
131
+
132
+ state = runUntilPause(state);
133
+ state = resumeWithAIResponse(state, 'all steps complete', undefined, toolRounds);
134
+ state = runUntilPause(state);
135
+
136
+ const vibeValue = state.callStack[0].locals['result'];
137
+ expect(vibeValue.toolCalls).toHaveLength(3);
138
+ expect(vibeValue.toolCalls.map((tc: { toolName: string }) => tc.toolName)).toEqual(['step1', 'step2', 'step3']);
139
+ expect(vibeValue.value).toBe('all steps complete');
140
+ });
141
+ });
142
+
143
+ describe('primitive coercion', () => {
144
+ it('string interpolation uses value', () => {
145
+ const ast = parse(`
146
+ model m = { name: "test", apiKey: "key", url: "http://test" }
147
+ let result = do "get name" m default
148
+ let msg = "Hello {result}"
149
+ `);
150
+ let state = createInitialState(ast);
151
+ state = runWithMockAI(state, 'Alice');
152
+
153
+ expect(state.callStack[0].locals['msg'].value).toBe('Hello Alice');
154
+ });
155
+
156
+ it('binary operations use value', () => {
157
+ const ast = parse(`
158
+ model m = { name: "test", apiKey: "key", url: "http://test" }
159
+ let result = do "get number" m default
160
+ let doubled = result + result
161
+ `);
162
+ let state = createInitialState(ast);
163
+ state = runWithMockAI(state, 5);
164
+
165
+ expect(state.callStack[0].locals['doubled'].value).toBe(10);
166
+ });
167
+
168
+ it('comparison uses value', () => {
169
+ const ast = parse(`
170
+ model m = { name: "test", apiKey: "key", url: "http://test" }
171
+ let result = do "get answer" m default
172
+ let isYes = result == "yes"
173
+ `);
174
+ let state = createInitialState(ast);
175
+ state = runWithMockAI(state, 'yes');
176
+
177
+ expect(state.callStack[0].locals['isYes'].value).toBe(true);
178
+ });
179
+ });
180
+
181
+ describe('property access', () => {
182
+ it('.toolCalls returns the tool calls array', () => {
183
+ const ast = parse(`
184
+ model m = { name: "test", apiKey: "key", url: "http://test" }
185
+ let result = vibe "do something" m default
186
+ let calls = result.toolCalls
187
+ `);
188
+ let state = createInitialState(ast);
189
+
190
+ const toolRounds: ToolRoundResult[] = [
191
+ {
192
+ toolCalls: [{ id: 'call_1', toolName: 'myTool', args: { x: 1 } }],
193
+ results: [{ toolCallId: 'call_1', result: 'done', duration: 100 }],
194
+ },
195
+ ];
196
+
197
+ state = runUntilPause(state);
198
+ state = resumeWithAIResponse(state, 'completed', undefined, toolRounds);
199
+ state = runUntilPause(state);
200
+
201
+ const calls = state.callStack[0].locals['calls'].value;
202
+ expect(Array.isArray(calls)).toBe(true);
203
+ expect(calls).toHaveLength(1);
204
+ expect(calls[0].toolName).toBe('myTool');
205
+ });
206
+
207
+ it('accessing properties on value works (when value is object)', () => {
208
+ const ast = parse(`
209
+ model m = { name: "test", apiKey: "key", url: "http://test" }
210
+ let result = do "get user" m default
211
+ let name = result.name
212
+ `);
213
+ let state = createInitialState(ast);
214
+ state = runWithMockAI(state, { name: 'Bob', age: 30 });
215
+
216
+ expect(state.callStack[0].locals['name'].value).toBe('Bob');
217
+ });
218
+
219
+ it('accessing individual toolCalls by index works', () => {
220
+ const ast = parse(`
221
+ model m = { name: "test", apiKey: "key", url: "http://test" }
222
+ let result = vibe "do tasks" m default
223
+ let firstCall = result.toolCalls[0]
224
+ let secondCall = result.toolCalls[1]
225
+ `);
226
+ let state = createInitialState(ast);
227
+
228
+ const toolRounds: ToolRoundResult[] = [
229
+ {
230
+ toolCalls: [
231
+ { id: 'call_1', toolName: 'tool1', args: { x: 1 } },
232
+ { id: 'call_2', toolName: 'tool2', args: { x: 2 } },
233
+ ],
234
+ results: [
235
+ { toolCallId: 'call_1', result: 'result1', duration: 10 },
236
+ { toolCallId: 'call_2', result: 'result2', duration: 20 },
237
+ ],
238
+ },
239
+ ];
240
+
241
+ state = runUntilPause(state);
242
+ state = resumeWithAIResponse(state, 'done', undefined, toolRounds);
243
+ state = runUntilPause(state);
244
+
245
+ const firstCall = state.callStack[0].locals['firstCall'].value;
246
+ const secondCall = state.callStack[0].locals['secondCall'].value;
247
+ expect(firstCall.toolName).toBe('tool1');
248
+ expect(secondCall.toolName).toBe('tool2');
249
+ });
250
+
251
+ it('accessing toolCall properties by chained index works', () => {
252
+ const ast = parse(`
253
+ model m = { name: "test", apiKey: "key", url: "http://test" }
254
+ let result = vibe "do tasks" m default
255
+ let firstCall = result.toolCalls[0]
256
+ let secondCall = result.toolCalls[1]
257
+ let firstName = firstCall.toolName
258
+ let secondName = secondCall.toolName
259
+ `);
260
+ let state = createInitialState(ast);
261
+
262
+ const toolRounds: ToolRoundResult[] = [
263
+ {
264
+ toolCalls: [
265
+ { id: 'call_1', toolName: 'fetchData', args: { url: 'http://api.com' } },
266
+ { id: 'call_2', toolName: 'processData', args: { format: 'json' } },
267
+ ],
268
+ results: [
269
+ { toolCallId: 'call_1', result: 'data', duration: 10 },
270
+ { toolCallId: 'call_2', result: 'processed', duration: 20 },
271
+ ],
272
+ },
273
+ ];
274
+
275
+ state = runUntilPause(state);
276
+ state = resumeWithAIResponse(state, 'done', undefined, toolRounds);
277
+ state = runUntilPause(state);
278
+
279
+ // Verify indexing works
280
+ expect(state.callStack[0].locals['firstCall'].value.toolName).toBe('fetchData');
281
+ expect(state.callStack[0].locals['secondCall'].value.toolName).toBe('processData');
282
+ // Verify property access on indexed element works
283
+ expect(state.callStack[0].locals['firstName'].value).toBe('fetchData');
284
+ expect(state.callStack[0].locals['secondName'].value).toBe('processData');
285
+ });
286
+
287
+ it('slicing toolCalls array works', () => {
288
+ const ast = parse(`
289
+ model m = { name: "test", apiKey: "key", url: "http://test" }
290
+ let result = vibe "do tasks" m default
291
+ let firstTwo = result.toolCalls[0:2]
292
+ let lastTwo = result.toolCalls[1:]
293
+ let fromStart = result.toolCalls[:2]
294
+ let allButLast = result.toolCalls[:-1]
295
+ `);
296
+ let state = createInitialState(ast);
297
+
298
+ const toolRounds: ToolRoundResult[] = [
299
+ {
300
+ toolCalls: [
301
+ { id: 'call_1', toolName: 'tool1', args: {} },
302
+ { id: 'call_2', toolName: 'tool2', args: {} },
303
+ { id: 'call_3', toolName: 'tool3', args: {} },
304
+ ],
305
+ results: [
306
+ { toolCallId: 'call_1', result: 'r1', duration: 10 },
307
+ { toolCallId: 'call_2', result: 'r2', duration: 20 },
308
+ { toolCallId: 'call_3', result: 'r3', duration: 30 },
309
+ ],
310
+ },
311
+ ];
312
+
313
+ state = runUntilPause(state);
314
+ state = resumeWithAIResponse(state, 'done', undefined, toolRounds);
315
+ state = runUntilPause(state);
316
+
317
+ const firstTwo = state.callStack[0].locals['firstTwo'].value;
318
+ const lastTwo = state.callStack[0].locals['lastTwo'].value;
319
+ const fromStart = state.callStack[0].locals['fromStart'].value;
320
+ const allButLast = state.callStack[0].locals['allButLast'].value;
321
+
322
+ // [0:2] - indices 0, 1 (Python-style exclusive end)
323
+ expect(firstTwo).toHaveLength(2);
324
+ expect(firstTwo.map((c: { toolName: string }) => c.toolName)).toEqual(['tool1', 'tool2']);
325
+
326
+ // [1:] - from index 1 to end
327
+ expect(lastTwo).toHaveLength(2);
328
+ expect(lastTwo.map((c: { toolName: string }) => c.toolName)).toEqual(['tool2', 'tool3']);
329
+
330
+ // [:2] - from start to index 2 (exclusive)
331
+ expect(fromStart).toHaveLength(2);
332
+ expect(fromStart.map((c: { toolName: string }) => c.toolName)).toEqual(['tool1', 'tool2']);
333
+
334
+ // [:-1] - all but last (Python-style)
335
+ expect(allButLast).toHaveLength(2);
336
+ expect(allButLast.map((c: { toolName: string }) => c.toolName)).toEqual(['tool1', 'tool2']);
337
+ });
338
+ });
339
+
340
+ describe('iteration', () => {
341
+ it('iterating over VibeValue with array value works', () => {
342
+ const ast = parse(`
343
+ model m = { name: "test", apiKey: "key", url: "http://test" }
344
+ let items = do "get list" m default
345
+ let sum = 0
346
+ for n in items {
347
+ sum = sum + n
348
+ }
349
+ `);
350
+ let state = createInitialState(ast);
351
+ state = runWithMockAI(state, [1, 2, 3, 4, 5]);
352
+
353
+ expect(state.callStack[0].locals['sum'].value).toBe(15);
354
+ });
355
+
356
+ it('iterating over VibeValue with non-array value throws', () => {
357
+ const ast = parse(`
358
+ model m = { name: "test", apiKey: "key", url: "http://test" }
359
+ let result = do "get string" m default
360
+ for c in result {
361
+ let x = c
362
+ }
363
+ `);
364
+ let state = createInitialState(ast);
365
+ state = runWithMockAI(state, 'hello');
366
+
367
+ expect(state.status).toBe('error');
368
+ expect(state.error).toContain('Cannot iterate over VibeValue');
369
+ expect(state.error).toContain('string');
370
+ });
371
+
372
+ it('accessing .toolCalls.len() works', () => {
373
+ const ast = parse(`
374
+ model m = { name: "test", apiKey: "key", url: "http://test" }
375
+ let result = vibe "do tasks" m default
376
+ let count = result.toolCalls.len()
377
+ `);
378
+ let state = createInitialState(ast);
379
+
380
+ const toolRounds: ToolRoundResult[] = [
381
+ {
382
+ toolCalls: [
383
+ { id: 'call_1', toolName: 'tool1', args: {} },
384
+ { id: 'call_2', toolName: 'tool2', args: {} },
385
+ ],
386
+ results: [
387
+ { toolCallId: 'call_1', result: 'r1', duration: 10 },
388
+ { toolCallId: 'call_2', result: 'r2', duration: 20 },
389
+ ],
390
+ },
391
+ ];
392
+
393
+ state = runUntilPause(state);
394
+ state = resumeWithAIResponse(state, 'done', undefined, toolRounds);
395
+ state = runUntilPause(state);
396
+
397
+ expect(state.callStack[0].locals['count'].value).toBe(2);
398
+ });
399
+ });
400
+
401
+ describe('context display', () => {
402
+ it('context shows only the value, not the full VibeValue', () => {
403
+ const ast = parse(`
404
+ model m = { name: "test", apiKey: "key", url: "http://test" }
405
+ let result = do "get data" m default
406
+ `);
407
+ let state = createInitialState(ast);
408
+ state = runWithMockAI(state, 'the response');
409
+
410
+ // Check that localContext shows just the value
411
+ const resultEntry = state.localContext.find(
412
+ (e) => e.kind === 'variable' && e.name === 'result'
413
+ );
414
+ expect(resultEntry).toBeDefined();
415
+ // The value in context should be resolved, not VibeValue
416
+ expect((resultEntry as { value: unknown }).value).toBe('the response');
417
+ });
418
+ });
419
+ });