@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,134 @@
|
|
|
1
|
+
import type { RegisteredTool, ToolContext } from './types';
|
|
2
|
+
import { validatePathInSandbox } from './security';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* File search tools: glob, grep
|
|
6
|
+
*/
|
|
7
|
+
export const searchTools = [
|
|
8
|
+
{
|
|
9
|
+
name: 'glob',
|
|
10
|
+
kind: 'builtin',
|
|
11
|
+
schema: {
|
|
12
|
+
name: 'glob',
|
|
13
|
+
description: 'Find files matching a glob pattern.',
|
|
14
|
+
parameters: [
|
|
15
|
+
{
|
|
16
|
+
name: 'pattern',
|
|
17
|
+
type: { type: 'string' },
|
|
18
|
+
description: 'The glob pattern (e.g., "**/*.ts", "src/*.js")',
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'cwd',
|
|
23
|
+
type: { type: 'string' },
|
|
24
|
+
description: 'Working directory for the search (default: root directory)',
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
returns: { type: 'array', items: { type: 'string' } },
|
|
29
|
+
},
|
|
30
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
31
|
+
const pattern = args.pattern as string;
|
|
32
|
+
const inputCwd = args.cwd as string | undefined;
|
|
33
|
+
const rootDir = context?.rootDir ?? process.cwd();
|
|
34
|
+
const cwd = inputCwd ? validatePathInSandbox(inputCwd, rootDir) : rootDir;
|
|
35
|
+
|
|
36
|
+
const glob = new Bun.Glob(pattern);
|
|
37
|
+
const matches: string[] = [];
|
|
38
|
+
|
|
39
|
+
for await (const file of glob.scan({ cwd })) {
|
|
40
|
+
matches.push(file);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return matches;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
name: 'grep',
|
|
49
|
+
kind: 'builtin',
|
|
50
|
+
schema: {
|
|
51
|
+
name: 'grep',
|
|
52
|
+
description: 'Search file contents for a pattern.',
|
|
53
|
+
parameters: [
|
|
54
|
+
{
|
|
55
|
+
name: 'pattern',
|
|
56
|
+
type: { type: 'string' },
|
|
57
|
+
description: 'The search pattern (regex)',
|
|
58
|
+
required: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'path',
|
|
62
|
+
type: { type: 'string' },
|
|
63
|
+
description: 'File or directory path to search',
|
|
64
|
+
required: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'ignoreCase',
|
|
68
|
+
type: { type: 'boolean' },
|
|
69
|
+
description: 'Ignore case in pattern matching',
|
|
70
|
+
required: false,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
returns: {
|
|
74
|
+
type: 'array',
|
|
75
|
+
items: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
file: { type: 'string' },
|
|
79
|
+
line: { type: 'number' },
|
|
80
|
+
match: { type: 'string' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
executor: async (args: Record<string, unknown>, context?: ToolContext) => {
|
|
86
|
+
const pattern = args.pattern as string;
|
|
87
|
+
const inputPath = args.path as string;
|
|
88
|
+
const ignoreCase = args.ignoreCase as boolean | undefined;
|
|
89
|
+
const rootDir = context?.rootDir ?? process.cwd();
|
|
90
|
+
const safePath = validatePathInSandbox(inputPath, rootDir);
|
|
91
|
+
|
|
92
|
+
const fs = await import('fs/promises');
|
|
93
|
+
const pathModule = await import('path');
|
|
94
|
+
const regex = new RegExp(pattern, ignoreCase ? 'gi' : 'g');
|
|
95
|
+
|
|
96
|
+
const results: Array<{ file: string; line: number; match: string }> = [];
|
|
97
|
+
|
|
98
|
+
async function searchFile(filePath: string) {
|
|
99
|
+
const content = await Bun.file(filePath).text();
|
|
100
|
+
const lines = content.split('\n');
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < lines.length; i++) {
|
|
103
|
+
const matches = lines[i].match(regex);
|
|
104
|
+
if (matches) {
|
|
105
|
+
for (const match of matches) {
|
|
106
|
+
results.push({
|
|
107
|
+
file: pathModule.relative(rootDir, filePath),
|
|
108
|
+
line: i + 1,
|
|
109
|
+
match,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const stats = await fs.stat(safePath);
|
|
117
|
+
if (stats.isDirectory()) {
|
|
118
|
+
// Search all files in directory recursively
|
|
119
|
+
const glob = new Bun.Glob('**/*');
|
|
120
|
+
for await (const file of glob.scan({ cwd: safePath })) {
|
|
121
|
+
const fullPath = pathModule.join(safePath, file);
|
|
122
|
+
const fileStats = await fs.stat(fullPath);
|
|
123
|
+
if (fileStats.isFile()) {
|
|
124
|
+
await searchFile(fullPath);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
await searchFile(safePath);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return results;
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
] satisfies RegisteredTool[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolve, normalize } from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates that a path is within the allowed root directory.
|
|
5
|
+
* Throws an error if the path escapes the sandbox.
|
|
6
|
+
*
|
|
7
|
+
* @param inputPath - The path to validate (can be relative or absolute)
|
|
8
|
+
* @param rootDir - The root directory that acts as the sandbox
|
|
9
|
+
* @returns The normalized absolute path within the sandbox
|
|
10
|
+
* @throws Error if the path is outside the allowed directory
|
|
11
|
+
*/
|
|
12
|
+
export function validatePathInSandbox(inputPath: string, rootDir: string): string {
|
|
13
|
+
// Resolve the path relative to the root directory
|
|
14
|
+
const resolved = resolve(rootDir, inputPath);
|
|
15
|
+
const normalized = normalize(resolved);
|
|
16
|
+
const normalizedRoot = normalize(rootDir);
|
|
17
|
+
|
|
18
|
+
// Ensure the resolved path starts with the root directory
|
|
19
|
+
// Add path separator to prevent partial matches (e.g., /foo vs /foobar)
|
|
20
|
+
const rootWithSep = normalizedRoot.endsWith('/') || normalizedRoot.endsWith('\\')
|
|
21
|
+
? normalizedRoot
|
|
22
|
+
: normalizedRoot + (process.platform === 'win32' ? '\\' : '/');
|
|
23
|
+
|
|
24
|
+
const pathWithSep = normalized.endsWith('/') || normalized.endsWith('\\')
|
|
25
|
+
? normalized
|
|
26
|
+
: normalized + (process.platform === 'win32' ? '\\' : '/');
|
|
27
|
+
|
|
28
|
+
// Check if the path is exactly the root or starts with root + separator
|
|
29
|
+
const isWithinRoot = normalized === normalizedRoot || pathWithSep.startsWith(rootWithSep);
|
|
30
|
+
|
|
31
|
+
if (!isWithinRoot) {
|
|
32
|
+
throw new Error(`Path '${inputPath}' is outside the allowed directory`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { mkdir, writeFile, readdir, rm } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import type { ToolContext, RegisteredTool } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* System tools: bash, runCode
|
|
8
|
+
*
|
|
9
|
+
* These tools provide shell command execution and sandboxed code execution.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Counter for unique bash script filenames
|
|
13
|
+
let bashScriptCounter = 0;
|
|
14
|
+
|
|
15
|
+
// Mutex for run folder creation to prevent race conditions
|
|
16
|
+
let runFolderLock: Promise<void> = Promise.resolve();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bash tool - Execute shell commands using Bun's cross-platform shell.
|
|
20
|
+
*
|
|
21
|
+
* Uses a temp script file executed with `bun run` to run commands through
|
|
22
|
+
* Bun's shell, which works cross-platform (Windows/Mac/Linux) without
|
|
23
|
+
* requiring Git Bash or WSL on Windows.
|
|
24
|
+
*/
|
|
25
|
+
const bashTool: RegisteredTool = {
|
|
26
|
+
name: 'bash',
|
|
27
|
+
kind: 'builtin',
|
|
28
|
+
schema: {
|
|
29
|
+
name: 'bash',
|
|
30
|
+
description:
|
|
31
|
+
'Execute a shell command and return stdout, stderr, and exit code. ' +
|
|
32
|
+
'Works cross-platform (Windows/Mac/Linux) without requiring Git Bash. ' +
|
|
33
|
+
'Supports pipes (cmd1 | cmd2), file redirection (> file, >> file), and standard shell features. ' +
|
|
34
|
+
'Commands run from the project root directory by default.',
|
|
35
|
+
parameters: [
|
|
36
|
+
{
|
|
37
|
+
name: 'command',
|
|
38
|
+
type: { type: 'string' },
|
|
39
|
+
description: 'The shell command to execute (supports pipes, redirection, etc.)',
|
|
40
|
+
required: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'cwd',
|
|
44
|
+
type: { type: 'string' },
|
|
45
|
+
description: 'Working directory for the command (defaults to project root)',
|
|
46
|
+
required: false,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'timeout',
|
|
50
|
+
type: { type: 'number' },
|
|
51
|
+
description: 'Timeout in milliseconds (default: 30000). Process is killed if exceeded.',
|
|
52
|
+
required: false,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
returns: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
stdout: { type: 'string' },
|
|
59
|
+
stderr: { type: 'string' },
|
|
60
|
+
exitCode: { type: 'number' },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
executor: async (
|
|
65
|
+
args: Record<string, unknown>,
|
|
66
|
+
context?: ToolContext
|
|
67
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }> => {
|
|
68
|
+
const command = args.command as string;
|
|
69
|
+
const cwd = (args.cwd as string) || context?.rootDir || process.cwd();
|
|
70
|
+
const timeout = (args.timeout as number) || 30000;
|
|
71
|
+
|
|
72
|
+
// Create a temp script file that uses Bun's shell
|
|
73
|
+
// This avoids escaping issues with inline code
|
|
74
|
+
const scriptId = ++bashScriptCounter;
|
|
75
|
+
const scriptPath = join(tmpdir(), `vibe-bash-${process.pid}-${scriptId}.ts`);
|
|
76
|
+
|
|
77
|
+
// Escape the command for embedding in a template literal
|
|
78
|
+
const escapedCommand = command
|
|
79
|
+
.replace(/\\/g, '\\\\')
|
|
80
|
+
.replace(/`/g, '\\`')
|
|
81
|
+
.replace(/\$\{/g, '\\${');
|
|
82
|
+
|
|
83
|
+
const scriptContent = `import { $ } from 'bun';
|
|
84
|
+
const result = await $\`${escapedCommand}\`.cwd(${JSON.stringify(cwd)}).nothrow().quiet();
|
|
85
|
+
process.stdout.write(result.stdout);
|
|
86
|
+
process.stderr.write(result.stderr);
|
|
87
|
+
process.exit(result.exitCode);
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await writeFile(scriptPath, scriptContent);
|
|
92
|
+
|
|
93
|
+
const proc = Bun.spawn(['bun', 'run', scriptPath], {
|
|
94
|
+
stdout: 'pipe',
|
|
95
|
+
stderr: 'pipe',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Set up timeout to kill runaway processes
|
|
99
|
+
const timeoutId = setTimeout(() => proc.kill(), timeout);
|
|
100
|
+
|
|
101
|
+
// Wait for completion
|
|
102
|
+
const exitCode = await proc.exited;
|
|
103
|
+
clearTimeout(timeoutId);
|
|
104
|
+
|
|
105
|
+
const stdout = await new Response(proc.stdout).text();
|
|
106
|
+
const stderr = await new Response(proc.stderr).text();
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
stdout,
|
|
110
|
+
stderr,
|
|
111
|
+
exitCode,
|
|
112
|
+
};
|
|
113
|
+
} finally {
|
|
114
|
+
// Clean up temp script
|
|
115
|
+
try {
|
|
116
|
+
await rm(scriptPath);
|
|
117
|
+
} catch {
|
|
118
|
+
// Ignore cleanup errors
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the next run folder name (r1, r2, r3, etc.) and create it atomically.
|
|
126
|
+
* Uses a mutex to prevent race conditions when multiple runCode calls happen simultaneously.
|
|
127
|
+
*/
|
|
128
|
+
async function getNextRunFolderAndCreate(cacheDir: string): Promise<string> {
|
|
129
|
+
// Use mutex to ensure only one folder creation at a time
|
|
130
|
+
let runName: string = '';
|
|
131
|
+
|
|
132
|
+
const operation = runFolderLock.then(async () => {
|
|
133
|
+
try {
|
|
134
|
+
await mkdir(cacheDir, { recursive: true });
|
|
135
|
+
const entries = await readdir(cacheDir);
|
|
136
|
+
const runNums = entries
|
|
137
|
+
.filter((e) => e.startsWith('r'))
|
|
138
|
+
.map((e) => parseInt(e.slice(1), 10))
|
|
139
|
+
.filter((n) => !isNaN(n));
|
|
140
|
+
const nextNum = runNums.length > 0 ? Math.max(...runNums) + 1 : 1;
|
|
141
|
+
runName = `r${nextNum}`;
|
|
142
|
+
|
|
143
|
+
// Create the directory while still holding the lock
|
|
144
|
+
await mkdir(join(cacheDir, runName), { recursive: true });
|
|
145
|
+
} catch {
|
|
146
|
+
// No cache dir yet, start at r1
|
|
147
|
+
runName = 'r1';
|
|
148
|
+
await mkdir(join(cacheDir, runName), { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Update the lock to include this operation
|
|
153
|
+
runFolderLock = operation.catch(() => {});
|
|
154
|
+
|
|
155
|
+
await operation;
|
|
156
|
+
return runName;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Code execution tool - Run AI-generated TypeScript in a sandboxed subprocess.
|
|
161
|
+
*
|
|
162
|
+
* Each execution gets a unique folder in .vibe-cache/ (r1, r2, r3...)
|
|
163
|
+
* so the AI can reference files from previous runs.
|
|
164
|
+
*
|
|
165
|
+
* Working directory is set to the project root so relative paths work naturally.
|
|
166
|
+
*/
|
|
167
|
+
const runCodeTool: RegisteredTool = {
|
|
168
|
+
name: 'runCode',
|
|
169
|
+
kind: 'builtin',
|
|
170
|
+
schema: {
|
|
171
|
+
name: 'runCode',
|
|
172
|
+
description:
|
|
173
|
+
'Execute TypeScript/JavaScript code in a sandboxed subprocess. ' +
|
|
174
|
+
'IMPORTANT: All scope variables are automatically available as local variables in your code - ' +
|
|
175
|
+
'just use them directly (e.g., if scope has {items: [...], name: "test"}, you can write ' +
|
|
176
|
+
'`items.map(...)` or `name.toUpperCase()` without any setup). ' +
|
|
177
|
+
'Working directory is the project root, so relative paths like "data/file.json" work naturally. ' +
|
|
178
|
+
'Use `return value` to pass results back. Bun APIs (Bun.file, Bun.write, etc.) are available. ' +
|
|
179
|
+
'Each execution creates a unique folder in .vibe-cache/ (r1, r2, r3...) for intermediate files.',
|
|
180
|
+
parameters: [
|
|
181
|
+
{
|
|
182
|
+
name: 'code',
|
|
183
|
+
type: { type: 'string' },
|
|
184
|
+
description:
|
|
185
|
+
'TypeScript/JavaScript code to execute. Scope variables are already available as local ' +
|
|
186
|
+
'variables - just use them directly. Use `return` to pass a result back.',
|
|
187
|
+
required: true,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'scope',
|
|
191
|
+
type: { type: 'object', additionalProperties: true },
|
|
192
|
+
description:
|
|
193
|
+
'Variables to make available in the code. Each key becomes a local variable. ' +
|
|
194
|
+
'Example: {items: [1,2,3], name: "test"} makes `items` and `name` directly usable in code.',
|
|
195
|
+
required: false,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'timeout',
|
|
199
|
+
type: { type: 'number' },
|
|
200
|
+
description: 'Timeout in milliseconds (default: 30000). Process is killed if exceeded.',
|
|
201
|
+
required: false,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
returns: {
|
|
205
|
+
type: 'object',
|
|
206
|
+
properties: {
|
|
207
|
+
result: { type: 'string' },
|
|
208
|
+
stdout: { type: 'string' },
|
|
209
|
+
stderr: { type: 'string' },
|
|
210
|
+
exitCode: { type: 'number' },
|
|
211
|
+
runFolder: { type: 'string' },
|
|
212
|
+
error: { type: 'string' },
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
executor: async (
|
|
217
|
+
args: Record<string, unknown>,
|
|
218
|
+
context?: ToolContext
|
|
219
|
+
): Promise<{
|
|
220
|
+
result?: unknown;
|
|
221
|
+
stdout: string;
|
|
222
|
+
stderr: string;
|
|
223
|
+
exitCode: number;
|
|
224
|
+
runFolder: string;
|
|
225
|
+
error?: string;
|
|
226
|
+
}> => {
|
|
227
|
+
const code = args.code as string;
|
|
228
|
+
const scope = (args.scope as Record<string, unknown>) || {};
|
|
229
|
+
const timeout = (args.timeout as number) || 30000;
|
|
230
|
+
|
|
231
|
+
const projectDir = context?.rootDir || process.cwd();
|
|
232
|
+
const cacheDir = join(projectDir, '.vibe-cache');
|
|
233
|
+
|
|
234
|
+
// Get unique run folder (r1, r2, r3...) - mutex ensures no race conditions
|
|
235
|
+
const runName = await getNextRunFolderAndCreate(cacheDir);
|
|
236
|
+
const runDir = join(cacheDir, runName);
|
|
237
|
+
const runPath = `.vibe-cache/${runName}`; // Relative path for AI to use
|
|
238
|
+
|
|
239
|
+
const scopePath = join(runDir, 'scope.json');
|
|
240
|
+
const scriptPath = join(runDir, 'script.ts');
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
// 1. Write scope to JSON file (directory already created by mutex)
|
|
244
|
+
await writeFile(scopePath, JSON.stringify(scope, null, 2));
|
|
245
|
+
|
|
246
|
+
// 3. Wrap code - AI reads scope from run folder
|
|
247
|
+
const scopeKeys = Object.keys(scope);
|
|
248
|
+
const destructure =
|
|
249
|
+
scopeKeys.length > 0 ? `const { ${scopeKeys.join(', ')} } = __scope;` : '';
|
|
250
|
+
|
|
251
|
+
const wrappedCode = `// Auto-generated by Vibe runtime - Run: ${runName}
|
|
252
|
+
// Scope: ${runPath}/scope.json
|
|
253
|
+
// Working directory: project root (relative paths work)
|
|
254
|
+
|
|
255
|
+
const __scope = JSON.parse(await Bun.file('${runPath}/scope.json').text());
|
|
256
|
+
${destructure}
|
|
257
|
+
|
|
258
|
+
// AI-generated code
|
|
259
|
+
const __result = await (async () => {
|
|
260
|
+
${code}
|
|
261
|
+
})();
|
|
262
|
+
|
|
263
|
+
console.log('__VIBE_RESULT__' + JSON.stringify(__result));
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
await writeFile(scriptPath, wrappedCode);
|
|
267
|
+
|
|
268
|
+
// 4. Execute in subprocess
|
|
269
|
+
const proc = Bun.spawn(['bun', 'run', `${runPath}/script.ts`], {
|
|
270
|
+
stdout: 'pipe',
|
|
271
|
+
stderr: 'pipe',
|
|
272
|
+
cwd: projectDir,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// 5. Set up timeout to kill runaway processes
|
|
276
|
+
const timeoutId = setTimeout(() => proc.kill(), timeout);
|
|
277
|
+
|
|
278
|
+
// 6. Wait for completion
|
|
279
|
+
const exitCode = await proc.exited;
|
|
280
|
+
clearTimeout(timeoutId);
|
|
281
|
+
|
|
282
|
+
const stdout = await new Response(proc.stdout).text();
|
|
283
|
+
const stderr = await new Response(proc.stderr).text();
|
|
284
|
+
|
|
285
|
+
// 7. Parse result from stdout
|
|
286
|
+
let result: unknown;
|
|
287
|
+
const resultMatch = stdout.match(/__VIBE_RESULT__(.+)/);
|
|
288
|
+
if (resultMatch) {
|
|
289
|
+
try {
|
|
290
|
+
result = JSON.parse(resultMatch[1]);
|
|
291
|
+
} catch {
|
|
292
|
+
result = resultMatch[1];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const cleanStdout = stdout.replace(/__VIBE_RESULT__.+\n?/, '');
|
|
297
|
+
return { result, stdout: cleanStdout, stderr, exitCode, runFolder: runPath };
|
|
298
|
+
} catch (err) {
|
|
299
|
+
return {
|
|
300
|
+
stdout: '',
|
|
301
|
+
stderr: '',
|
|
302
|
+
exitCode: 1,
|
|
303
|
+
runFolder: runPath,
|
|
304
|
+
error: err instanceof Error ? err.message : String(err),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// Note: We don't delete .vibe-cache - useful for debugging
|
|
308
|
+
// AI can reference previous runs via .vibe-cache/r1/, r2/, etc.
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export const systemTools: RegisteredTool[] = [bashTool, runCodeTool];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base types that get imported by test-types.ts
|
|
3
|
+
* Used to test cross-file type resolution.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A geographic address */
|
|
7
|
+
export interface Address {
|
|
8
|
+
street: string;
|
|
9
|
+
city: string;
|
|
10
|
+
zipCode: string;
|
|
11
|
+
country?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Contact information */
|
|
15
|
+
export interface ContactInfo {
|
|
16
|
+
email: string;
|
|
17
|
+
phone?: string;
|
|
18
|
+
address: Address;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Generic metadata that can be attached to entities */
|
|
22
|
+
export interface Metadata {
|
|
23
|
+
/** When the entity was created */
|
|
24
|
+
createdAt: string;
|
|
25
|
+
/** When the entity was last updated */
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
/** Optional tags for categorization */
|
|
28
|
+
tags?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Status values for orders */
|
|
32
|
+
export type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
|
|
33
|
+
|
|
34
|
+
/** A line item in an order */
|
|
35
|
+
export interface OrderItem {
|
|
36
|
+
productId: string;
|
|
37
|
+
productName: string;
|
|
38
|
+
quantity: number;
|
|
39
|
+
unitPrice: number;
|
|
40
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test types for ts-schema extraction tests.
|
|
3
|
+
* Imports types from base-types.ts to test cross-file resolution.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Address, ContactInfo, Metadata, OrderStatus, OrderItem } from './base-types';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Simple primitives
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/** A person with basic info */
|
|
13
|
+
export interface Person {
|
|
14
|
+
name: string;
|
|
15
|
+
age: number;
|
|
16
|
+
active: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Configuration with optional fields */
|
|
20
|
+
export interface Config {
|
|
21
|
+
required: string;
|
|
22
|
+
optional?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Container with array property */
|
|
26
|
+
export interface Container {
|
|
27
|
+
items: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Simple type alias */
|
|
31
|
+
export type Status = string;
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Documented types (JSDoc extraction)
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/** A documented entity with JSDoc on properties */
|
|
38
|
+
export interface Documented {
|
|
39
|
+
/** The user's unique identifier */
|
|
40
|
+
id: string;
|
|
41
|
+
/** The user's display name */
|
|
42
|
+
name: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Types using imports from base-types.ts
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/** A user with imported contact info */
|
|
50
|
+
export interface User {
|
|
51
|
+
id: string;
|
|
52
|
+
username: string;
|
|
53
|
+
contact: ContactInfo;
|
|
54
|
+
metadata: Metadata;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A customer with imported address */
|
|
58
|
+
export interface Customer {
|
|
59
|
+
customerId: string;
|
|
60
|
+
name: string;
|
|
61
|
+
email: string;
|
|
62
|
+
billingAddress: Address;
|
|
63
|
+
shippingAddress?: Address;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** An order using imported types */
|
|
67
|
+
export interface Order {
|
|
68
|
+
orderId: string;
|
|
69
|
+
customerId: string;
|
|
70
|
+
status: OrderStatus;
|
|
71
|
+
items: OrderItem[];
|
|
72
|
+
totalAmount: number;
|
|
73
|
+
shippingAddress: Address;
|
|
74
|
+
notes?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// Complex nested types
|
|
79
|
+
// ============================================================================
|
|
80
|
+
|
|
81
|
+
/** Deeply nested structure */
|
|
82
|
+
export interface Company {
|
|
83
|
+
name: string;
|
|
84
|
+
headquarters: Address;
|
|
85
|
+
employees: Employee[];
|
|
86
|
+
departments: Department[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Employee {
|
|
90
|
+
id: string;
|
|
91
|
+
name: string;
|
|
92
|
+
role: string;
|
|
93
|
+
contact: ContactInfo;
|
|
94
|
+
manager?: Employee;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface Department {
|
|
98
|
+
name: string;
|
|
99
|
+
head: Employee;
|
|
100
|
+
budget: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Array types
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/** Entity with various array types */
|
|
108
|
+
export interface ArrayTypes {
|
|
109
|
+
strings: string[];
|
|
110
|
+
numbers: number[];
|
|
111
|
+
booleans: boolean[];
|
|
112
|
+
nested: Address[];
|
|
113
|
+
matrix: number[][];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Union and optional types
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/** Type with unions */
|
|
121
|
+
export interface WithUnions {
|
|
122
|
+
id: string | number;
|
|
123
|
+
status: 'active' | 'inactive' | 'pending';
|
|
124
|
+
data: string | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Type with all optional fields */
|
|
128
|
+
export interface AllOptional {
|
|
129
|
+
field1?: string;
|
|
130
|
+
field2?: number;
|
|
131
|
+
field3?: boolean;
|
|
132
|
+
}
|