@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,54 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { parse } from '../../parser/parse';
3
+ import { Runtime, AIProvider, AIExecutionResult } from '../index';
4
+
5
+ // Mock AI provider for testing
6
+ class MockAIProvider implements AIProvider {
7
+ public executeCalls: string[] = [];
8
+
9
+ async execute(prompt: string): Promise<AIExecutionResult> {
10
+ this.executeCalls.push(prompt);
11
+ return { value: `[Response to: ${prompt}]` };
12
+ }
13
+
14
+ async generateCode(prompt: string): Promise<AIExecutionResult> {
15
+ // Vibe is now a synonym for do - generateCode just calls execute
16
+ return this.execute(prompt);
17
+ }
18
+
19
+ async askUser(prompt: string): Promise<string> {
20
+ return `[User input for: ${prompt}]`;
21
+ }
22
+ }
23
+
24
+ describe('Runtime - Vibe Expression', () => {
25
+ test('vibe expression works like vibe expression', async () => {
26
+ const ast = parse(`
27
+ model myModel = { name: "test", apiKey: "key", url: "http://test" }
28
+ let result = vibe "hello world" myModel default
29
+ result
30
+ `);
31
+ const provider = new MockAIProvider();
32
+ const runtime = new Runtime(ast, provider);
33
+ const result = await runtime.run();
34
+
35
+ expect(provider.executeCalls).toHaveLength(1);
36
+ expect(provider.executeCalls[0]).toBe('hello world');
37
+ expect(result).toBe('[Response to: hello world]');
38
+ });
39
+
40
+ test('vibe expression with default context', async () => {
41
+ const ast = parse(`
42
+ model myModel = { name: "test", apiKey: "key", url: "http://test" }
43
+ let x = 10
44
+ let result = vibe "what is x" myModel default
45
+ result
46
+ `);
47
+ const provider = new MockAIProvider();
48
+ const runtime = new Runtime(ast, provider);
49
+ const result = await runtime.run();
50
+
51
+ expect(provider.executeCalls).toHaveLength(1);
52
+ expect(provider.executeCalls[0]).toBe('what is x');
53
+ });
54
+ });
@@ -0,0 +1,541 @@
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 { createVibeValue, createVibeError, isVibeValue, propagateErrors } from '../types';
6
+ import type { VibeValue, VibeError } from '../types';
7
+
8
+ // Helper to run with mock AI response
9
+ function runWithMockAI(
10
+ state: ReturnType<typeof createInitialState>,
11
+ response: unknown
12
+ ) {
13
+ state = runUntilPause(state);
14
+ while (state.status === 'awaiting_ai') {
15
+ state = resumeWithAIResponse(state, response);
16
+ state = runUntilPause(state);
17
+ }
18
+ return state;
19
+ }
20
+
21
+ describe('VibeValue Error Handling', () => {
22
+ describe('createVibeError', () => {
23
+ it('creates VibeValue with error from string', () => {
24
+ const vibeValue = createVibeError('Something went wrong');
25
+
26
+ expect(isVibeValue(vibeValue)).toBe(true);
27
+ expect(vibeValue.err).toBe(true); // err is now a boolean
28
+ expect(vibeValue.errDetails).not.toBe(null);
29
+ expect(vibeValue.errDetails?.message).toBe('Something went wrong');
30
+ expect(vibeValue.errDetails?.type).toBe('Error');
31
+ expect(vibeValue.value).toBe(null); // Error values have null, not undefined
32
+ });
33
+
34
+ it('creates VibeValue with error from Error object', () => {
35
+ const error = new TypeError('Invalid type');
36
+ const vibeValue = createVibeError(error);
37
+
38
+ expect(vibeValue.err).toBe(true);
39
+ expect(vibeValue.errDetails?.message).toBe('Invalid type');
40
+ expect(vibeValue.errDetails?.type).toBe('TypeError');
41
+ });
42
+
43
+ it('preserves location in error', () => {
44
+ const location = { line: 10, column: 5, file: 'test.vibe' };
45
+ const vibeValue = createVibeError('Error', location);
46
+
47
+ expect(vibeValue.errDetails?.location).toEqual(location);
48
+ });
49
+
50
+ it('preserves options like isConst and typeAnnotation', () => {
51
+ const vibeValue = createVibeError('Error', null, {
52
+ isConst: true,
53
+ typeAnnotation: 'text',
54
+ });
55
+
56
+ expect(vibeValue.isConst).toBe(true);
57
+ expect(vibeValue.typeAnnotation).toBe('text');
58
+ });
59
+ });
60
+
61
+ describe('propagateErrors', () => {
62
+ it('returns first error when left operand has error', () => {
63
+ const leftError = createVibeError('Left failed');
64
+ const rightValue = createVibeValue(42);
65
+
66
+ const result = propagateErrors([leftError, rightValue], 0);
67
+
68
+ expect(result.err).toBe(true);
69
+ expect(result.errDetails?.message).toBe('Left failed');
70
+ expect(result.value).toBe(null); // Error propagation uses null, not undefined
71
+ });
72
+
73
+ it('returns first error when right operand has error', () => {
74
+ const leftValue = createVibeValue(42);
75
+ const rightError = createVibeError('Right failed');
76
+
77
+ const result = propagateErrors([leftValue, rightError], 0);
78
+
79
+ expect(result.err).toBe(true);
80
+ expect(result.errDetails?.message).toBe('Right failed');
81
+ });
82
+
83
+ it('returns first error when both operands have errors', () => {
84
+ const leftError = createVibeError('Left failed');
85
+ const rightError = createVibeError('Right failed');
86
+
87
+ const result = propagateErrors([leftError, rightError], 0);
88
+
89
+ // First error wins
90
+ expect(result.err).toBe(true);
91
+ expect(result.errDetails?.message).toBe('Left failed');
92
+ });
93
+
94
+ it('creates successful value when no errors', () => {
95
+ const left = createVibeValue(10);
96
+ const right = createVibeValue(20);
97
+
98
+ const result = propagateErrors([left, right], 30);
99
+
100
+ expect(result.err).toBe(false);
101
+ expect(result.value).toBe(30);
102
+ });
103
+
104
+ it('handles mix of VibeValue and primitives', () => {
105
+ const vibeVal = createVibeValue(10);
106
+ const primitive = 20;
107
+
108
+ const result = propagateErrors([vibeVal, primitive], 30);
109
+
110
+ expect(result.err).toBe(false);
111
+ expect(result.value).toBe(30);
112
+ });
113
+ });
114
+
115
+ describe('.err property access', () => {
116
+ it('.err returns false for successful AI response', () => {
117
+ const ast = parse(`
118
+ model m = { name: "test", apiKey: "key", url: "http://test" }
119
+ let result = do "get something" m default
120
+ let error = result.err
121
+ `);
122
+ let state = createInitialState(ast);
123
+ state = runWithMockAI(state, 'success');
124
+
125
+ expect(state.status).toBe('completed');
126
+ expect(state.callStack[0].locals['error'].value).toBe(false); // err is now boolean
127
+ });
128
+
129
+ it('.err is accessible on any VibeValue variable (returns false)', () => {
130
+ const ast = parse(`
131
+ let x = "hello"
132
+ let error = x.err
133
+ `);
134
+ let state = createInitialState(ast);
135
+ state = runUntilPause(state);
136
+
137
+ expect(state.status).toBe('completed');
138
+ expect(state.callStack[0].locals['error'].value).toBe(false); // err is now boolean
139
+ });
140
+
141
+ it('.err is accessible on numeric VibeValue (returns false)', () => {
142
+ const ast = parse(`
143
+ let x = 42
144
+ let error = x.err
145
+ `);
146
+ let state = createInitialState(ast);
147
+ state = runUntilPause(state);
148
+
149
+ expect(state.status).toBe('completed');
150
+ expect(state.callStack[0].locals['error'].value).toBe(false); // err is now boolean
151
+ });
152
+
153
+ it('.err is accessible on boolean VibeValue (returns false)', () => {
154
+ const ast = parse(`
155
+ let x = true
156
+ let error = x.err
157
+ `);
158
+ let state = createInitialState(ast);
159
+ state = runUntilPause(state);
160
+
161
+ expect(state.status).toBe('completed');
162
+ expect(state.callStack[0].locals['error'].value).toBe(false); // err is now boolean
163
+ });
164
+
165
+ it('.err is accessible on object VibeValue (returns false)', () => {
166
+ const ast = parse(`
167
+ let x = { name: "test" }
168
+ let error = x.err
169
+ `);
170
+ let state = createInitialState(ast);
171
+ state = runUntilPause(state);
172
+
173
+ expect(state.status).toBe('completed');
174
+ expect(state.callStack[0].locals['error'].value).toBe(false); // err is now boolean
175
+ });
176
+
177
+ it('.err is accessible on array VibeValue (returns false)', () => {
178
+ const ast = parse(`
179
+ let x = [1, 2, 3]
180
+ let error = x.err
181
+ `);
182
+ let state = createInitialState(ast);
183
+ state = runUntilPause(state);
184
+
185
+ expect(state.status).toBe('completed');
186
+ expect(state.callStack[0].locals['error'].value).toBe(false); // err is now boolean
187
+ });
188
+ });
189
+
190
+ describe('.toolCalls property access', () => {
191
+ it('.toolCalls returns empty array for non-AI values', () => {
192
+ const ast = parse(`
193
+ let x = "hello"
194
+ let calls = x.toolCalls
195
+ `);
196
+ let state = createInitialState(ast);
197
+ state = runUntilPause(state);
198
+
199
+ expect(state.status).toBe('completed');
200
+ const calls = state.callStack[0].locals['calls'].value;
201
+ expect(Array.isArray(calls)).toBe(true);
202
+ expect(calls).toHaveLength(0);
203
+ });
204
+
205
+ it('.toolCalls returns empty array for simple AI response', () => {
206
+ const ast = parse(`
207
+ model m = { name: "test", apiKey: "key", url: "http://test" }
208
+ let result = do "get something" m default
209
+ let calls = result.toolCalls
210
+ `);
211
+ let state = createInitialState(ast);
212
+ state = runWithMockAI(state, 'response');
213
+
214
+ expect(state.status).toBe('completed');
215
+ const calls = state.callStack[0].locals['calls'].value;
216
+ expect(Array.isArray(calls)).toBe(true);
217
+ expect(calls).toHaveLength(0);
218
+ });
219
+ });
220
+
221
+ describe('error propagation in binary operations', () => {
222
+ it('addition propagates error from left operand', () => {
223
+ // This tests the error propagation plumbing in step.ts binary_op case
224
+ // When VibeValue with .err is used in binary operation, error should propagate
225
+ const leftError: VibeValue = {
226
+ value: null,
227
+ err: true,
228
+ errDetails: { message: 'Left error', type: 'Error', location: null },
229
+ toolCalls: [],
230
+ isConst: false,
231
+ typeAnnotation: null,
232
+ source: null,
233
+ };
234
+ const rightValue: VibeValue = {
235
+ value: 5,
236
+ err: false,
237
+ errDetails: null,
238
+ toolCalls: [],
239
+ isConst: false,
240
+ typeAnnotation: null,
241
+ source: null,
242
+ };
243
+
244
+ const result = propagateErrors([leftError, rightValue], null);
245
+ expect(result.err).toBe(true);
246
+ expect(result.errDetails?.message).toBe('Left error');
247
+ });
248
+
249
+ it('subtraction propagates error from right operand', () => {
250
+ const leftValue: VibeValue = {
251
+ value: 10,
252
+ err: false,
253
+ errDetails: null,
254
+ toolCalls: [],
255
+ isConst: false,
256
+ typeAnnotation: null,
257
+ source: null,
258
+ };
259
+ const rightError: VibeValue = {
260
+ value: null,
261
+ err: true,
262
+ errDetails: { message: 'Right error', type: 'Error', location: null },
263
+ toolCalls: [],
264
+ isConst: false,
265
+ typeAnnotation: null,
266
+ source: null,
267
+ };
268
+
269
+ const result = propagateErrors([leftValue, rightError], null);
270
+ expect(result.err).toBe(true);
271
+ expect(result.errDetails?.message).toBe('Right error');
272
+ });
273
+
274
+ it('first error wins when both operands have errors', () => {
275
+ const leftError: VibeValue = {
276
+ value: null,
277
+ err: true,
278
+ errDetails: { message: 'First error', type: 'Error', location: null },
279
+ toolCalls: [],
280
+ isConst: false,
281
+ typeAnnotation: null,
282
+ source: null,
283
+ };
284
+ const rightError: VibeValue = {
285
+ value: null,
286
+ err: true,
287
+ errDetails: { message: 'Second error', type: 'Error', location: null },
288
+ toolCalls: [],
289
+ isConst: false,
290
+ typeAnnotation: null,
291
+ source: null,
292
+ };
293
+
294
+ const result = propagateErrors([leftError, rightError], null);
295
+ expect(result.err).toBe(true);
296
+ expect(result.errDetails?.message).toBe('First error');
297
+ });
298
+ });
299
+
300
+ describe('VibeValue structure', () => {
301
+ it('createVibeValue creates proper structure', () => {
302
+ const vibeValue = createVibeValue('hello', {
303
+ isConst: true,
304
+ typeAnnotation: 'text',
305
+ source: 'ai',
306
+ });
307
+
308
+ expect(vibeValue.value).toBe('hello');
309
+ expect(vibeValue.err).toBe(false); // err is now boolean
310
+ expect(vibeValue.errDetails).toBe(null);
311
+ expect(vibeValue.toolCalls).toEqual([]);
312
+ expect(vibeValue.isConst).toBe(true);
313
+ expect(vibeValue.typeAnnotation).toBe('text');
314
+ expect(vibeValue.source).toBe('ai');
315
+ });
316
+
317
+ it('createVibeValue with toolCalls', () => {
318
+ const toolCalls = [
319
+ { toolName: 'test', args: {}, result: 'ok', err: false, errDetails: null, duration: 100 },
320
+ ];
321
+ const vibeValue = createVibeValue('result', { toolCalls });
322
+
323
+ expect(vibeValue.toolCalls).toHaveLength(1);
324
+ expect(vibeValue.toolCalls[0].toolName).toBe('test');
325
+ expect(vibeValue.toolCalls[0].err).toBe(false);
326
+ });
327
+
328
+ it('isVibeValue correctly identifies VibeValue objects', () => {
329
+ const vibeValue = createVibeValue('test');
330
+ const notVibeValue = { value: 'test' };
331
+ const primitive = 'test';
332
+
333
+ expect(isVibeValue(vibeValue)).toBe(true);
334
+ expect(isVibeValue(notVibeValue)).toBe(false); // Missing errDetails
335
+ expect(isVibeValue(primitive)).toBe(false);
336
+ expect(isVibeValue(null)).toBe(false);
337
+ expect(isVibeValue(undefined)).toBe(false);
338
+ });
339
+ });
340
+
341
+ describe('error type preservation', () => {
342
+ it('preserves TypeError', () => {
343
+ const error = new TypeError('Not a function');
344
+ const vibeValue = createVibeError(error);
345
+
346
+ expect(vibeValue.err).toBe(true);
347
+ expect(vibeValue.errDetails?.type).toBe('TypeError');
348
+ expect(vibeValue.errDetails?.message).toBe('Not a function');
349
+ });
350
+
351
+ it('preserves ReferenceError', () => {
352
+ const error = new ReferenceError('x is not defined');
353
+ const vibeValue = createVibeError(error);
354
+
355
+ expect(vibeValue.err).toBe(true);
356
+ expect(vibeValue.errDetails?.type).toBe('ReferenceError');
357
+ expect(vibeValue.errDetails?.message).toBe('x is not defined');
358
+ });
359
+
360
+ it('preserves RangeError', () => {
361
+ const error = new RangeError('Invalid array length');
362
+ const vibeValue = createVibeError(error);
363
+
364
+ expect(vibeValue.err).toBe(true);
365
+ expect(vibeValue.errDetails?.type).toBe('RangeError');
366
+ });
367
+
368
+ it('preserves custom error types', () => {
369
+ class CustomError extends Error {
370
+ constructor(message: string) {
371
+ super(message);
372
+ this.name = 'CustomError';
373
+ }
374
+ }
375
+ const error = new CustomError('Custom problem');
376
+ const vibeValue = createVibeError(error);
377
+
378
+ expect(vibeValue.err).toBe(true);
379
+ expect(vibeValue.errDetails?.type).toBe('CustomError');
380
+ expect(vibeValue.errDetails?.message).toBe('Custom problem');
381
+ });
382
+ });
383
+
384
+ describe('error location tracking', () => {
385
+ it('includes file, line, and column in error location', () => {
386
+ const location = {
387
+ line: 42,
388
+ column: 10,
389
+ file: 'my-script.vibe',
390
+ };
391
+ const vibeValue = createVibeError('Error occurred', location);
392
+
393
+ expect(vibeValue.err).toBe(true);
394
+ expect(vibeValue.errDetails?.location).toEqual({
395
+ line: 42,
396
+ column: 10,
397
+ file: 'my-script.vibe',
398
+ });
399
+ });
400
+
401
+ it('handles null location gracefully', () => {
402
+ const vibeValue = createVibeError('Error occurred', null);
403
+
404
+ expect(vibeValue.err).toBe(true);
405
+ expect(vibeValue.errDetails?.location).toBe(null);
406
+ });
407
+ });
408
+
409
+ describe('TypeScript block error handling', () => {
410
+ it('ts block runtime error throws TsBlockError', async () => {
411
+ // Import Runtime and TsBlockError dynamically for this test
412
+ const { Runtime, TsBlockError } = await import('../index');
413
+
414
+ const mockProvider = {
415
+ async execute() {
416
+ return { value: 'response' };
417
+ },
418
+ async generateCode() {
419
+ return { value: 'code' };
420
+ },
421
+ async askUser(): Promise<string> {
422
+ return 'input';
423
+ },
424
+ };
425
+
426
+ const ast = parse(`
427
+ let result = ts() { throw new Error("ts block error") }
428
+ `);
429
+ const runtime = new Runtime(ast, mockProvider);
430
+
431
+ try {
432
+ await runtime.run();
433
+ expect.unreachable('should have thrown');
434
+ } catch (error) {
435
+ expect(error).toBeInstanceOf(TsBlockError);
436
+ expect((error as Error).message).toContain('ts block error');
437
+ }
438
+ });
439
+
440
+ it('ts block can catch its own errors and return normally', async () => {
441
+ const { Runtime } = await import('../index');
442
+
443
+ const mockProvider = {
444
+ async execute() {
445
+ return { value: 'response' };
446
+ },
447
+ async generateCode() {
448
+ return { value: 'code' };
449
+ },
450
+ async askUser(): Promise<string> {
451
+ return 'input';
452
+ },
453
+ };
454
+
455
+ const ast = parse(`
456
+ let result = ts() {
457
+ try {
458
+ throw new Error("caught error")
459
+ } catch (e) {
460
+ return { success: false, error: e.message }
461
+ }
462
+ }
463
+ `);
464
+ const runtime = new Runtime(ast, mockProvider);
465
+ await runtime.run();
466
+
467
+ const result = runtime.getValue('result') as { success: boolean; error: string };
468
+ expect(result.success).toBe(false);
469
+ expect(result.error).toBe('caught error');
470
+ });
471
+
472
+ it('ts block TypeError is wrapped in TsBlockError', async () => {
473
+ const { Runtime, TsBlockError } = await import('../index');
474
+
475
+ const mockProvider = {
476
+ async execute() {
477
+ return { value: 'response' };
478
+ },
479
+ async generateCode() {
480
+ return { value: 'code' };
481
+ },
482
+ async askUser(): Promise<string> {
483
+ return 'input';
484
+ },
485
+ };
486
+
487
+ const ast = parse(`
488
+ let result = ts() {
489
+ const obj = null
490
+ return obj.property
491
+ }
492
+ `);
493
+ const runtime = new Runtime(ast, mockProvider);
494
+
495
+ try {
496
+ await runtime.run();
497
+ expect.unreachable('should have thrown');
498
+ } catch (error) {
499
+ expect(error).toBeInstanceOf(TsBlockError);
500
+ // TsBlockError wraps the original error
501
+ const tsError = error as InstanceType<typeof TsBlockError>;
502
+ expect(tsError.originalError).toBeDefined();
503
+ }
504
+ });
505
+
506
+ it('ts block can return error info as a value', async () => {
507
+ // This demonstrates the Go-style pattern where errors are returned, not thrown
508
+ const { Runtime } = await import('../index');
509
+
510
+ const mockProvider = {
511
+ async execute() {
512
+ return { value: 'response' };
513
+ },
514
+ async generateCode() {
515
+ return { value: 'code' };
516
+ },
517
+ async askUser(): Promise<string> {
518
+ return 'input';
519
+ },
520
+ };
521
+
522
+ const ast = parse(`
523
+ let mayFail = ts() {
524
+ try {
525
+ // Simulating an operation that might fail
526
+ const data = JSON.parse("invalid json {")
527
+ return { value: data, err: null }
528
+ } catch (e) {
529
+ return { value: null, err: e.message }
530
+ }
531
+ }
532
+ `);
533
+ const runtime = new Runtime(ast, mockProvider);
534
+ await runtime.run();
535
+
536
+ const result = runtime.getValue('mayFail') as { value: unknown; err: string | null };
537
+ expect(result.value).toBe(null);
538
+ expect(result.err).toContain('JSON');
539
+ });
540
+ });
541
+ });