@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.
- package/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- 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
|
+
});
|