@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,679 @@
|
|
|
1
|
+
import { describe, expect, test, beforeAll, afterAll } from 'bun:test';
|
|
2
|
+
import { builtinTools } from '../builtin';
|
|
3
|
+
import { mkdir, rm, writeFile, readFile, readdir } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
// Use project-relative temp directory for test files (gitignored)
|
|
7
|
+
const TEST_TMP_DIR = '.test-tmp';
|
|
8
|
+
|
|
9
|
+
describe('System Tools', () => {
|
|
10
|
+
describe('bash tool', () => {
|
|
11
|
+
test('bash tool is registered', () => {
|
|
12
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash');
|
|
13
|
+
expect(bashTool).toBeDefined();
|
|
14
|
+
expect(bashTool?.kind).toBe('builtin');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('bash tool has correct schema', () => {
|
|
18
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
19
|
+
expect(bashTool.schema.name).toBe('bash');
|
|
20
|
+
expect(bashTool.schema.parameters).toHaveLength(3);
|
|
21
|
+
expect(bashTool.schema.parameters[0].name).toBe('command');
|
|
22
|
+
expect(bashTool.schema.parameters[0].required).toBe(true);
|
|
23
|
+
expect(bashTool.schema.parameters[1].name).toBe('cwd');
|
|
24
|
+
expect(bashTool.schema.parameters[1].required).toBe(false);
|
|
25
|
+
expect(bashTool.schema.parameters[2].name).toBe('timeout');
|
|
26
|
+
expect(bashTool.schema.parameters[2].required).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('executes simple echo command', async () => {
|
|
30
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
31
|
+
const result = (await bashTool.executor({ command: 'echo hello' })) as {
|
|
32
|
+
stdout: string;
|
|
33
|
+
stderr: string;
|
|
34
|
+
exitCode: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
expect(result.exitCode).toBe(0);
|
|
38
|
+
expect(result.stdout.trim()).toBe('hello');
|
|
39
|
+
expect(result.stderr).toBe('');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('captures exit code for failing commands', async () => {
|
|
43
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
44
|
+
// Use 'exit 1' which should work cross-platform in Bun shell
|
|
45
|
+
const result = (await bashTool.executor({ command: 'exit 1' })) as {
|
|
46
|
+
stdout: string;
|
|
47
|
+
stderr: string;
|
|
48
|
+
exitCode: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
expect(result.exitCode).toBe(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('respects cwd parameter', async () => {
|
|
55
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
56
|
+
const testDir = join(TEST_TMP_DIR, 'bash-cwd-test');
|
|
57
|
+
await mkdir(testDir, { recursive: true });
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// pwd command in Bun shell should return current directory
|
|
61
|
+
const result = (await bashTool.executor({ command: 'pwd', cwd: testDir })) as {
|
|
62
|
+
stdout: string;
|
|
63
|
+
stderr: string;
|
|
64
|
+
exitCode: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
expect(result.exitCode).toBe(0);
|
|
68
|
+
// The output should contain our test directory name
|
|
69
|
+
expect(result.stdout).toContain('bash-cwd-test');
|
|
70
|
+
} finally {
|
|
71
|
+
await rm(testDir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('pipes work correctly', async () => {
|
|
76
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
77
|
+
const result = (await bashTool.executor({ command: 'echo "line1\nline2\nline3" | wc -l' })) as {
|
|
78
|
+
stdout: string;
|
|
79
|
+
stderr: string;
|
|
80
|
+
exitCode: number;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
expect(result.exitCode).toBe(0);
|
|
84
|
+
expect(result.stdout.trim()).toBe('3');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('handles command with arguments', async () => {
|
|
88
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
89
|
+
const result = (await bashTool.executor({ command: 'echo one two three' })) as {
|
|
90
|
+
stdout: string;
|
|
91
|
+
stderr: string;
|
|
92
|
+
exitCode: number;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
expect(result.exitCode).toBe(0);
|
|
96
|
+
expect(result.stdout.trim()).toBe('one two three');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('supports file redirection with >', async () => {
|
|
100
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
101
|
+
const testDir = join(TEST_TMP_DIR, 'bash-redirect-test');
|
|
102
|
+
await mkdir(testDir, { recursive: true });
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
// Write to file using >
|
|
106
|
+
await bashTool.executor({
|
|
107
|
+
command: 'echo "hello world" > output.txt',
|
|
108
|
+
cwd: testDir,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Verify file was created with content
|
|
112
|
+
const content = await readFile(join(testDir, 'output.txt'), 'utf8');
|
|
113
|
+
expect(content.trim()).toBe('hello world');
|
|
114
|
+
} finally {
|
|
115
|
+
await rm(testDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('supports append redirection with >>', async () => {
|
|
120
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
121
|
+
const testDir = join(TEST_TMP_DIR, 'bash-append-test');
|
|
122
|
+
await mkdir(testDir, { recursive: true });
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// Write first line
|
|
126
|
+
await bashTool.executor({
|
|
127
|
+
command: 'echo "line1" > output.txt',
|
|
128
|
+
cwd: testDir,
|
|
129
|
+
});
|
|
130
|
+
// Append second line
|
|
131
|
+
await bashTool.executor({
|
|
132
|
+
command: 'echo "line2" >> output.txt',
|
|
133
|
+
cwd: testDir,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const content = await readFile(join(testDir, 'output.txt'), 'utf8');
|
|
137
|
+
expect(content.trim()).toBe('line1\nline2');
|
|
138
|
+
} finally {
|
|
139
|
+
await rm(testDir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('supports complex pipe chains', async () => {
|
|
144
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
145
|
+
// echo lines, grep for ones containing "a", then count them
|
|
146
|
+
const result = (await bashTool.executor({
|
|
147
|
+
command: 'echo "apple\nbanana\ncherry\navocado" | grep a | wc -l',
|
|
148
|
+
})) as { stdout: string; exitCode: number };
|
|
149
|
+
|
|
150
|
+
expect(result.exitCode).toBe(0);
|
|
151
|
+
// apple, banana, avocado all contain 'a' = 3 lines
|
|
152
|
+
expect(result.stdout.trim()).toBe('3');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('can read file and pipe to grep', async () => {
|
|
156
|
+
const bashTool = builtinTools.find((t) => t.name === 'bash')!;
|
|
157
|
+
const testDir = join(TEST_TMP_DIR, 'bash-cat-grep-test');
|
|
158
|
+
await mkdir(testDir, { recursive: true });
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
// Create a test file
|
|
162
|
+
await writeFile(
|
|
163
|
+
join(testDir, 'data.txt'),
|
|
164
|
+
'error: something failed\ninfo: all good\nerror: another problem\ninfo: success'
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Use cat and grep to find error lines
|
|
168
|
+
const result = (await bashTool.executor({
|
|
169
|
+
command: 'cat data.txt | grep error',
|
|
170
|
+
cwd: testDir,
|
|
171
|
+
})) as { stdout: string; exitCode: number };
|
|
172
|
+
|
|
173
|
+
expect(result.exitCode).toBe(0);
|
|
174
|
+
expect(result.stdout).toContain('error: something failed');
|
|
175
|
+
expect(result.stdout).toContain('error: another problem');
|
|
176
|
+
expect(result.stdout).not.toContain('info:');
|
|
177
|
+
} finally {
|
|
178
|
+
await rm(testDir, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('runCode tool', () => {
|
|
184
|
+
let testDir: string;
|
|
185
|
+
let cacheDir: string;
|
|
186
|
+
|
|
187
|
+
beforeAll(async () => {
|
|
188
|
+
testDir = join(TEST_TMP_DIR, 'runcode-test');
|
|
189
|
+
cacheDir = join(testDir, '.vibe-cache');
|
|
190
|
+
await mkdir(testDir, { recursive: true });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
afterAll(async () => {
|
|
194
|
+
await rm(testDir, { recursive: true });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('runCode tool is registered', () => {
|
|
198
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode');
|
|
199
|
+
expect(runCodeTool).toBeDefined();
|
|
200
|
+
expect(runCodeTool?.kind).toBe('builtin');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('runCode tool has correct schema', () => {
|
|
204
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
205
|
+
expect(runCodeTool.schema.name).toBe('runCode');
|
|
206
|
+
expect(runCodeTool.schema.parameters).toHaveLength(3);
|
|
207
|
+
expect(runCodeTool.schema.parameters[0].name).toBe('code');
|
|
208
|
+
expect(runCodeTool.schema.parameters[0].required).toBe(true);
|
|
209
|
+
expect(runCodeTool.schema.parameters[1].name).toBe('scope');
|
|
210
|
+
expect(runCodeTool.schema.parameters[1].required).toBe(false);
|
|
211
|
+
expect(runCodeTool.schema.parameters[2].name).toBe('timeout');
|
|
212
|
+
expect(runCodeTool.schema.parameters[2].required).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('executes simple code and returns result', async () => {
|
|
216
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
217
|
+
const result = (await runCodeTool.executor(
|
|
218
|
+
{ code: 'return 42;' },
|
|
219
|
+
{ rootDir: testDir }
|
|
220
|
+
)) as {
|
|
221
|
+
result?: unknown;
|
|
222
|
+
stdout: string;
|
|
223
|
+
stderr: string;
|
|
224
|
+
exitCode: number;
|
|
225
|
+
runFolder: string;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
expect(result.exitCode).toBe(0);
|
|
229
|
+
expect(result.result).toBe(42);
|
|
230
|
+
expect(result.runFolder).toMatch(/^\.vibe-cache\/r\d+$/);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('passes scope variables to code', async () => {
|
|
234
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
235
|
+
const result = (await runCodeTool.executor(
|
|
236
|
+
{
|
|
237
|
+
code: 'return x + y;',
|
|
238
|
+
scope: { x: 10, y: 20 },
|
|
239
|
+
},
|
|
240
|
+
{ rootDir: testDir }
|
|
241
|
+
)) as {
|
|
242
|
+
result?: unknown;
|
|
243
|
+
stdout: string;
|
|
244
|
+
stderr: string;
|
|
245
|
+
exitCode: number;
|
|
246
|
+
runFolder: string;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
expect(result.exitCode).toBe(0);
|
|
250
|
+
expect(result.result).toBe(30);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('captures console output', async () => {
|
|
254
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
255
|
+
const result = (await runCodeTool.executor(
|
|
256
|
+
{ code: 'console.log("hello from code"); return "done";' },
|
|
257
|
+
{ rootDir: testDir }
|
|
258
|
+
)) as {
|
|
259
|
+
result?: unknown;
|
|
260
|
+
stdout: string;
|
|
261
|
+
stderr: string;
|
|
262
|
+
exitCode: number;
|
|
263
|
+
runFolder: string;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
expect(result.exitCode).toBe(0);
|
|
267
|
+
expect(result.stdout).toContain('hello from code');
|
|
268
|
+
expect(result.result).toBe('done');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('creates unique run folders', async () => {
|
|
272
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
273
|
+
|
|
274
|
+
// Execute two times
|
|
275
|
+
const result1 = (await runCodeTool.executor(
|
|
276
|
+
{ code: 'return 1;' },
|
|
277
|
+
{ rootDir: testDir }
|
|
278
|
+
)) as { runFolder: string };
|
|
279
|
+
|
|
280
|
+
const result2 = (await runCodeTool.executor(
|
|
281
|
+
{ code: 'return 2;' },
|
|
282
|
+
{ rootDir: testDir }
|
|
283
|
+
)) as { runFolder: string };
|
|
284
|
+
|
|
285
|
+
expect(result1.runFolder).not.toBe(result2.runFolder);
|
|
286
|
+
|
|
287
|
+
// Both should be valid run folder paths
|
|
288
|
+
expect(result1.runFolder).toMatch(/^\.vibe-cache\/r\d+$/);
|
|
289
|
+
expect(result2.runFolder).toMatch(/^\.vibe-cache\/r\d+$/);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('concurrent executions get unique folders (mutex test)', async () => {
|
|
293
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
294
|
+
|
|
295
|
+
// Launch 5 concurrent executions
|
|
296
|
+
const promises = Array.from({ length: 5 }, (_, i) =>
|
|
297
|
+
runCodeTool.executor(
|
|
298
|
+
{ code: `return ${i};` },
|
|
299
|
+
{ rootDir: testDir }
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const results = (await Promise.all(promises)) as Array<{
|
|
304
|
+
result?: number;
|
|
305
|
+
runFolder: string;
|
|
306
|
+
exitCode: number;
|
|
307
|
+
}>;
|
|
308
|
+
|
|
309
|
+
// All should succeed
|
|
310
|
+
for (const result of results) {
|
|
311
|
+
expect(result.exitCode).toBe(0);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// All run folders should be unique
|
|
315
|
+
const folders = results.map((r) => r.runFolder);
|
|
316
|
+
const uniqueFolders = new Set(folders);
|
|
317
|
+
expect(uniqueFolders.size).toBe(5);
|
|
318
|
+
|
|
319
|
+
// All should match the pattern
|
|
320
|
+
for (const folder of folders) {
|
|
321
|
+
expect(folder).toMatch(/^\.vibe-cache\/r\d+$/);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('saves scope.json and script.ts in run folder', async () => {
|
|
326
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
327
|
+
const result = (await runCodeTool.executor(
|
|
328
|
+
{
|
|
329
|
+
code: 'return name.toUpperCase();',
|
|
330
|
+
scope: { name: 'test' },
|
|
331
|
+
},
|
|
332
|
+
{ rootDir: testDir }
|
|
333
|
+
)) as {
|
|
334
|
+
result?: unknown;
|
|
335
|
+
runFolder: string;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
expect(result.result).toBe('TEST');
|
|
339
|
+
|
|
340
|
+
// Check that files were created
|
|
341
|
+
const runDir = join(testDir, result.runFolder);
|
|
342
|
+
const files = await readdir(runDir);
|
|
343
|
+
expect(files).toContain('scope.json');
|
|
344
|
+
expect(files).toContain('script.ts');
|
|
345
|
+
|
|
346
|
+
// Check scope.json content
|
|
347
|
+
const scopeContent = await readFile(join(runDir, 'scope.json'), 'utf8');
|
|
348
|
+
expect(JSON.parse(scopeContent)).toEqual({ name: 'test' });
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('can return complex objects', async () => {
|
|
352
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
353
|
+
const result = (await runCodeTool.executor(
|
|
354
|
+
{ code: 'return { items: [1, 2, 3], nested: { value: "test" } };' },
|
|
355
|
+
{ rootDir: testDir }
|
|
356
|
+
)) as {
|
|
357
|
+
result?: unknown;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
expect(result.result).toEqual({
|
|
361
|
+
items: [1, 2, 3],
|
|
362
|
+
nested: { value: 'test' },
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('handles code with async/await', async () => {
|
|
367
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
368
|
+
const result = (await runCodeTool.executor(
|
|
369
|
+
{
|
|
370
|
+
code: `
|
|
371
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
372
|
+
await sleep(10);
|
|
373
|
+
return "completed";
|
|
374
|
+
`,
|
|
375
|
+
},
|
|
376
|
+
{ rootDir: testDir }
|
|
377
|
+
)) as {
|
|
378
|
+
result?: unknown;
|
|
379
|
+
exitCode: number;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
expect(result.exitCode).toBe(0);
|
|
383
|
+
expect(result.result).toBe('completed');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('handles code errors gracefully', async () => {
|
|
387
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
388
|
+
const result = (await runCodeTool.executor(
|
|
389
|
+
{ code: 'throw new Error("intentional error");' },
|
|
390
|
+
{ rootDir: testDir }
|
|
391
|
+
)) as {
|
|
392
|
+
stdout: string;
|
|
393
|
+
stderr: string;
|
|
394
|
+
exitCode: number;
|
|
395
|
+
runFolder: string;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Process should exit with non-zero code
|
|
399
|
+
expect(result.exitCode).not.toBe(0);
|
|
400
|
+
// Error should appear in stderr
|
|
401
|
+
expect(result.stderr).toContain('intentional error');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('works with no scope', async () => {
|
|
405
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
406
|
+
const result = (await runCodeTool.executor(
|
|
407
|
+
{ code: 'return "no scope needed";' },
|
|
408
|
+
{ rootDir: testDir }
|
|
409
|
+
)) as {
|
|
410
|
+
result?: unknown;
|
|
411
|
+
exitCode: number;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
expect(result.exitCode).toBe(0);
|
|
415
|
+
expect(result.result).toBe('no scope needed');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// =========================================================================
|
|
419
|
+
// Realistic AI code generation scenarios
|
|
420
|
+
// =========================================================================
|
|
421
|
+
|
|
422
|
+
test('processes array data from scope - aggregation', async () => {
|
|
423
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
424
|
+
|
|
425
|
+
// Simulate AI receiving sales data and calculating totals
|
|
426
|
+
const salesData = [
|
|
427
|
+
{ product: 'Widget', quantity: 10, price: 25 },
|
|
428
|
+
{ product: 'Gadget', quantity: 5, price: 50 },
|
|
429
|
+
{ product: 'Widget', quantity: 3, price: 25 },
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
const result = (await runCodeTool.executor(
|
|
433
|
+
{
|
|
434
|
+
code: `
|
|
435
|
+
// AI-generated code to aggregate sales by product
|
|
436
|
+
const totals = {};
|
|
437
|
+
for (const sale of sales) {
|
|
438
|
+
if (!totals[sale.product]) {
|
|
439
|
+
totals[sale.product] = { quantity: 0, revenue: 0 };
|
|
440
|
+
}
|
|
441
|
+
totals[sale.product].quantity += sale.quantity;
|
|
442
|
+
totals[sale.product].revenue += sale.quantity * sale.price;
|
|
443
|
+
}
|
|
444
|
+
return totals;
|
|
445
|
+
`,
|
|
446
|
+
scope: { sales: salesData },
|
|
447
|
+
},
|
|
448
|
+
{ rootDir: testDir }
|
|
449
|
+
)) as { result?: unknown; exitCode: number };
|
|
450
|
+
|
|
451
|
+
expect(result.exitCode).toBe(0);
|
|
452
|
+
expect(result.result).toEqual({
|
|
453
|
+
Widget: { quantity: 13, revenue: 325 },
|
|
454
|
+
Gadget: { quantity: 5, revenue: 250 },
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('transforms and filters data from scope', async () => {
|
|
459
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
460
|
+
|
|
461
|
+
const users = [
|
|
462
|
+
{ name: 'Alice', age: 30, active: true },
|
|
463
|
+
{ name: 'Bob', age: 25, active: false },
|
|
464
|
+
{ name: 'Charlie', age: 35, active: true },
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
const result = (await runCodeTool.executor(
|
|
468
|
+
{
|
|
469
|
+
code: `
|
|
470
|
+
// Filter active users and transform to summary
|
|
471
|
+
const activeUsers = users
|
|
472
|
+
.filter(u => u.active)
|
|
473
|
+
.map(u => ({ name: u.name, ageGroup: u.age >= 30 ? 'senior' : 'junior' }));
|
|
474
|
+
return { count: activeUsers.length, users: activeUsers };
|
|
475
|
+
`,
|
|
476
|
+
scope: { users },
|
|
477
|
+
},
|
|
478
|
+
{ rootDir: testDir }
|
|
479
|
+
)) as { result?: unknown; exitCode: number };
|
|
480
|
+
|
|
481
|
+
expect(result.exitCode).toBe(0);
|
|
482
|
+
expect(result.result).toEqual({
|
|
483
|
+
count: 2,
|
|
484
|
+
users: [
|
|
485
|
+
{ name: 'Alice', ageGroup: 'senior' },
|
|
486
|
+
{ name: 'Charlie', ageGroup: 'senior' },
|
|
487
|
+
],
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test('reads file from project directory using relative path', async () => {
|
|
492
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
493
|
+
|
|
494
|
+
// Create a data file in the project directory
|
|
495
|
+
const dataDir = join(testDir, 'data');
|
|
496
|
+
await mkdir(dataDir, { recursive: true });
|
|
497
|
+
await writeFile(
|
|
498
|
+
join(dataDir, 'config.json'),
|
|
499
|
+
JSON.stringify({ apiKey: 'test-key', maxRetries: 3 })
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
const result = (await runCodeTool.executor(
|
|
503
|
+
{
|
|
504
|
+
code: `
|
|
505
|
+
// AI reads config file using relative path (works because cwd = project root)
|
|
506
|
+
const configText = await Bun.file('data/config.json').text();
|
|
507
|
+
const config = JSON.parse(configText);
|
|
508
|
+
return { key: config.apiKey, retries: config.maxRetries };
|
|
509
|
+
`,
|
|
510
|
+
},
|
|
511
|
+
{ rootDir: testDir }
|
|
512
|
+
)) as { result?: unknown; exitCode: number };
|
|
513
|
+
|
|
514
|
+
expect(result.exitCode).toBe(0);
|
|
515
|
+
expect(result.result).toEqual({ key: 'test-key', retries: 3 });
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test('writes output file to cache folder', async () => {
|
|
519
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
520
|
+
|
|
521
|
+
const result = (await runCodeTool.executor(
|
|
522
|
+
{
|
|
523
|
+
code: `
|
|
524
|
+
// AI writes processed data to the cache folder
|
|
525
|
+
const data = { processed: true, items: [1, 2, 3] };
|
|
526
|
+
await Bun.write('.vibe-cache/my-output.json', JSON.stringify(data, null, 2));
|
|
527
|
+
return { outputPath: '.vibe-cache/my-output.json', success: true };
|
|
528
|
+
`,
|
|
529
|
+
},
|
|
530
|
+
{ rootDir: testDir }
|
|
531
|
+
)) as { result?: { outputPath: string; success: boolean }; exitCode: number };
|
|
532
|
+
|
|
533
|
+
expect(result.exitCode).toBe(0);
|
|
534
|
+
expect(result.result?.success).toBe(true);
|
|
535
|
+
|
|
536
|
+
// Verify the output file was created
|
|
537
|
+
const outputPath = join(testDir, '.vibe-cache', 'my-output.json');
|
|
538
|
+
const outputContent = await readFile(outputPath, 'utf8');
|
|
539
|
+
const parsed = JSON.parse(outputContent);
|
|
540
|
+
expect(parsed.processed).toBe(true);
|
|
541
|
+
expect(parsed.items).toEqual([1, 2, 3]);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test('multi-step: second run reads output from first run', async () => {
|
|
545
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
546
|
+
|
|
547
|
+
// Step 1: Process raw data and save intermediate result
|
|
548
|
+
const rawData = [1, 2, 3, 4, 5];
|
|
549
|
+
const step1 = (await runCodeTool.executor(
|
|
550
|
+
{
|
|
551
|
+
code: `
|
|
552
|
+
// Step 1: Square all numbers and save to run folder
|
|
553
|
+
const squared = numbers.map(n => n * n);
|
|
554
|
+
await Bun.write('.vibe-cache/step1-output.json', JSON.stringify(squared));
|
|
555
|
+
return { outputFile: '.vibe-cache/step1-output.json', count: squared.length };
|
|
556
|
+
`,
|
|
557
|
+
scope: { numbers: rawData },
|
|
558
|
+
},
|
|
559
|
+
{ rootDir: testDir }
|
|
560
|
+
)) as { result?: { outputFile: string }; runFolder: string; exitCode: number };
|
|
561
|
+
|
|
562
|
+
expect(step1.exitCode).toBe(0);
|
|
563
|
+
|
|
564
|
+
// Step 2: Read from step 1's output and continue processing
|
|
565
|
+
const step2 = (await runCodeTool.executor(
|
|
566
|
+
{
|
|
567
|
+
code: `
|
|
568
|
+
// Step 2: Read previous output and calculate sum
|
|
569
|
+
const previousOutput = JSON.parse(await Bun.file(previousFile).text());
|
|
570
|
+
const sum = previousOutput.reduce((a, b) => a + b, 0);
|
|
571
|
+
return { squared: previousOutput, sum };
|
|
572
|
+
`,
|
|
573
|
+
scope: { previousFile: '.vibe-cache/step1-output.json' },
|
|
574
|
+
},
|
|
575
|
+
{ rootDir: testDir }
|
|
576
|
+
)) as { result?: { squared: number[]; sum: number }; exitCode: number };
|
|
577
|
+
|
|
578
|
+
expect(step2.exitCode).toBe(0);
|
|
579
|
+
expect(step2.result).toEqual({
|
|
580
|
+
squared: [1, 4, 9, 16, 25],
|
|
581
|
+
sum: 55,
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test('handles complex nested scope data', async () => {
|
|
586
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
587
|
+
|
|
588
|
+
const complexScope = {
|
|
589
|
+
config: {
|
|
590
|
+
settings: { theme: 'dark', language: 'en' },
|
|
591
|
+
features: ['auth', 'analytics'],
|
|
592
|
+
},
|
|
593
|
+
users: [
|
|
594
|
+
{ id: 1, roles: ['admin', 'user'] },
|
|
595
|
+
{ id: 2, roles: ['user'] },
|
|
596
|
+
],
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const result = (await runCodeTool.executor(
|
|
600
|
+
{
|
|
601
|
+
code: `
|
|
602
|
+
// Access deeply nested scope data
|
|
603
|
+
const adminUsers = users.filter(u => u.roles.includes('admin'));
|
|
604
|
+
return {
|
|
605
|
+
theme: config.settings.theme,
|
|
606
|
+
featureCount: config.features.length,
|
|
607
|
+
adminCount: adminUsers.length,
|
|
608
|
+
adminIds: adminUsers.map(u => u.id),
|
|
609
|
+
};
|
|
610
|
+
`,
|
|
611
|
+
scope: complexScope,
|
|
612
|
+
},
|
|
613
|
+
{ rootDir: testDir }
|
|
614
|
+
)) as { result?: unknown; exitCode: number };
|
|
615
|
+
|
|
616
|
+
expect(result.exitCode).toBe(0);
|
|
617
|
+
expect(result.result).toEqual({
|
|
618
|
+
theme: 'dark',
|
|
619
|
+
featureCount: 2,
|
|
620
|
+
adminCount: 1,
|
|
621
|
+
adminIds: [1],
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test('can use npm packages available to Bun', async () => {
|
|
626
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
627
|
+
|
|
628
|
+
// Test using built-in Node.js modules
|
|
629
|
+
const result = (await runCodeTool.executor(
|
|
630
|
+
{
|
|
631
|
+
code: `
|
|
632
|
+
const path = require('path');
|
|
633
|
+
const joined = path.join('folder', 'subfolder', 'file.txt');
|
|
634
|
+
const parsed = path.parse(joined);
|
|
635
|
+
return { joined, base: parsed.base, ext: parsed.ext };
|
|
636
|
+
`,
|
|
637
|
+
},
|
|
638
|
+
{ rootDir: testDir }
|
|
639
|
+
)) as { result?: unknown; exitCode: number };
|
|
640
|
+
|
|
641
|
+
expect(result.exitCode).toBe(0);
|
|
642
|
+
expect(result.result).toEqual({
|
|
643
|
+
joined: expect.stringContaining('file.txt'),
|
|
644
|
+
base: 'file.txt',
|
|
645
|
+
ext: '.txt',
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test('string manipulation with scope data', async () => {
|
|
650
|
+
const runCodeTool = builtinTools.find((t) => t.name === 'runCode')!;
|
|
651
|
+
|
|
652
|
+
const result = (await runCodeTool.executor(
|
|
653
|
+
{
|
|
654
|
+
code: `
|
|
655
|
+
// Generate a formatted report from scope data
|
|
656
|
+
const lines = items.map((item, i) => \`\${i + 1}. \${item.name}: $\${item.price.toFixed(2)}\`);
|
|
657
|
+
const total = items.reduce((sum, item) => sum + item.price, 0);
|
|
658
|
+
lines.push('---');
|
|
659
|
+
lines.push(\`Total: $\${total.toFixed(2)}\`);
|
|
660
|
+
return lines.join('\\n');
|
|
661
|
+
`,
|
|
662
|
+
scope: {
|
|
663
|
+
items: [
|
|
664
|
+
{ name: 'Coffee', price: 4.5 },
|
|
665
|
+
{ name: 'Sandwich', price: 8.25 },
|
|
666
|
+
{ name: 'Cookie', price: 2.0 },
|
|
667
|
+
],
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
{ rootDir: testDir }
|
|
671
|
+
)) as { result?: string; exitCode: number };
|
|
672
|
+
|
|
673
|
+
expect(result.exitCode).toBe(0);
|
|
674
|
+
expect(result.result).toBe(
|
|
675
|
+
'1. Coffee: $4.50\n2. Sandwich: $8.25\n3. Cookie: $2.00\n---\nTotal: $14.75'
|
|
676
|
+
);
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
});
|