@vibe-lang/runtime 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
@@ -0,0 +1,707 @@
1
+ // Standard tools for AI models in Vibe
2
+ // Import with: import { allTools, readFile, writeFile, ... } from "system/tools"
3
+ //
4
+ // These are tools that AI models can use via the tools parameter.
5
+ // For direct script use, import functions from "system" instead.
6
+
7
+ import type { VibeToolValue, ToolContext } from '../../tools/types';
8
+ import { validatePathInSandbox } from '../../tools/security';
9
+
10
+ // Helper to escape regex special characters
11
+ function escapeRegex(s: string): string {
12
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
13
+ }
14
+
15
+ // =============================================================================
16
+ // File Tools
17
+ // =============================================================================
18
+
19
+ export const readFile: VibeToolValue = {
20
+ __vibeTool: true,
21
+ name: 'readFile',
22
+ schema: {
23
+ name: 'readFile',
24
+ description: 'Read the contents of a file as text. Optionally read a range of lines.',
25
+ parameters: [
26
+ { name: 'path', type: { type: 'string' }, description: 'The file path to read', required: true },
27
+ { name: 'startLine', type: { type: 'number' }, description: 'First line to read (1-based, inclusive)', required: false },
28
+ { name: 'endLine', type: { type: 'number' }, description: 'Last line to read (1-based, inclusive)', required: false },
29
+ ],
30
+ returns: { type: 'string' },
31
+ },
32
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
33
+ const inputPath = args.path as string;
34
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
35
+ const startLine = args.startLine as number | undefined;
36
+ const endLine = args.endLine as number | undefined;
37
+
38
+ const file = Bun.file(safePath);
39
+ const content = await file.text();
40
+
41
+ if (startLine === undefined && endLine === undefined) {
42
+ return content;
43
+ }
44
+
45
+ const lines = content.split('\n');
46
+ const start = startLine !== undefined ? Math.max(1, startLine) - 1 : 0;
47
+ const end = endLine !== undefined ? Math.min(lines.length, endLine) : lines.length;
48
+
49
+ return lines.slice(start, end).join('\n');
50
+ },
51
+ };
52
+
53
+ export const writeFile: VibeToolValue = {
54
+ __vibeTool: true,
55
+ name: 'writeFile',
56
+ schema: {
57
+ name: 'writeFile',
58
+ description: 'Write content to a file.',
59
+ parameters: [
60
+ { name: 'path', type: { type: 'string' }, description: 'The file path to write to', required: true },
61
+ { name: 'content', type: { type: 'string' }, description: 'The content to write', required: true },
62
+ ],
63
+ returns: { type: 'boolean' },
64
+ },
65
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
66
+ const inputPath = args.path as string;
67
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
68
+ const content = args.content as string;
69
+ await Bun.write(safePath, content);
70
+ return true;
71
+ },
72
+ };
73
+
74
+ export const appendFile: VibeToolValue = {
75
+ __vibeTool: true,
76
+ name: 'appendFile',
77
+ schema: {
78
+ name: 'appendFile',
79
+ description: 'Append content to a file.',
80
+ parameters: [
81
+ { name: 'path', type: { type: 'string' }, description: 'The file path to append to', required: true },
82
+ { name: 'content', type: { type: 'string' }, description: 'The content to append', required: true },
83
+ ],
84
+ returns: { type: 'boolean' },
85
+ },
86
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
87
+ const inputPath = args.path as string;
88
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
89
+ const content = args.content as string;
90
+ const file = Bun.file(safePath);
91
+ const existing = (await file.exists()) ? await file.text() : '';
92
+ await Bun.write(safePath, existing + content);
93
+ return true;
94
+ },
95
+ };
96
+
97
+ export const fileExists: VibeToolValue = {
98
+ __vibeTool: true,
99
+ name: 'fileExists',
100
+ schema: {
101
+ name: 'fileExists',
102
+ description: 'Check if a file exists.',
103
+ parameters: [
104
+ { name: 'path', type: { type: 'string' }, description: 'The file path to check', required: true },
105
+ ],
106
+ returns: { type: 'boolean' },
107
+ },
108
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
109
+ const inputPath = args.path as string;
110
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
111
+ const file = Bun.file(safePath);
112
+ return await file.exists();
113
+ },
114
+ };
115
+
116
+ export const listDir: VibeToolValue = {
117
+ __vibeTool: true,
118
+ name: 'listDir',
119
+ schema: {
120
+ name: 'listDir',
121
+ description: 'List files in a directory.',
122
+ parameters: [
123
+ { name: 'path', type: { type: 'string' }, description: 'The directory path to list', required: true },
124
+ ],
125
+ returns: { type: 'array', items: { type: 'string' } },
126
+ },
127
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
128
+ const inputPath = args.path as string;
129
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
130
+ const fs = await import('fs/promises');
131
+ return await fs.readdir(safePath);
132
+ },
133
+ };
134
+
135
+ export const edit: VibeToolValue = {
136
+ __vibeTool: true,
137
+ name: 'edit',
138
+ schema: {
139
+ name: 'edit',
140
+ description: 'Find and replace text in a file. The oldText must match exactly once in the file.',
141
+ parameters: [
142
+ { name: 'path', type: { type: 'string' }, description: 'The file path to edit', required: true },
143
+ { name: 'oldText', type: { type: 'string' }, description: 'The text to find (must match exactly once)', required: true },
144
+ { name: 'newText', type: { type: 'string' }, description: 'The text to replace with', required: true },
145
+ ],
146
+ returns: { type: 'boolean' },
147
+ },
148
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
149
+ const inputPath = args.path as string;
150
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
151
+ const oldText = args.oldText as string;
152
+ const newText = args.newText as string;
153
+
154
+ const file = Bun.file(safePath);
155
+ const content = await file.text();
156
+
157
+ const matches = content.split(oldText).length - 1;
158
+
159
+ if (matches === 0) {
160
+ throw new Error(`edit failed: oldText not found in file`);
161
+ }
162
+
163
+ if (matches > 1) {
164
+ throw new Error(`edit failed: oldText matches ${matches} times, must match exactly once`);
165
+ }
166
+
167
+ const newContent = content.replace(oldText, newText);
168
+ await Bun.write(safePath, newContent);
169
+ return true;
170
+ },
171
+ };
172
+
173
+ export const fastEdit: VibeToolValue = {
174
+ __vibeTool: true,
175
+ name: 'fastEdit',
176
+ schema: {
177
+ name: 'fastEdit',
178
+ description:
179
+ 'Replace a region identified by prefix and suffix anchors. Use this instead of edit when replacing large blocks where specifying prefix/suffix anchors saves significant tokens vs the full oldText. For small edits, prefer the simpler edit tool. If this tool fails, fall back to using the edit tool.',
180
+ parameters: [
181
+ { name: 'path', type: { type: 'string' }, description: 'The file path to edit', required: true },
182
+ {
183
+ name: 'prefix',
184
+ type: { type: 'string' },
185
+ description: 'Start anchor (beginning of region to replace)',
186
+ required: true,
187
+ },
188
+ {
189
+ name: 'suffix',
190
+ type: { type: 'string' },
191
+ description: 'End anchor (end of region to replace)',
192
+ required: true,
193
+ },
194
+ {
195
+ name: 'newText',
196
+ type: { type: 'string' },
197
+ description: 'Replacement text (replaces entire region including anchors)',
198
+ required: true,
199
+ },
200
+ ],
201
+ returns: { type: 'boolean' },
202
+ },
203
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
204
+ const inputPath = args.path as string;
205
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
206
+ const prefix = args.prefix as string;
207
+ const suffix = args.suffix as string;
208
+ const newText = args.newText as string;
209
+
210
+ const file = Bun.file(safePath);
211
+ const content = await file.text();
212
+
213
+ // Build regex with escaped anchors, non-greedy match
214
+ const pattern = escapeRegex(prefix) + '[\\s\\S]*?' + escapeRegex(suffix);
215
+ const regex = new RegExp(pattern, 'g');
216
+ const matches = content.match(regex);
217
+
218
+ if (!matches || matches.length === 0) {
219
+ throw new Error(`fastEdit failed: no region found matching prefix...suffix`);
220
+ }
221
+
222
+ if (matches.length > 1) {
223
+ throw new Error(`fastEdit failed: ${matches.length} regions match, must match exactly once`);
224
+ }
225
+
226
+ const newContent = content.replace(regex, newText);
227
+ await Bun.write(safePath, newContent);
228
+ return true;
229
+ },
230
+ };
231
+
232
+ // =============================================================================
233
+ // Search Tools
234
+ // =============================================================================
235
+
236
+ export const glob: VibeToolValue = {
237
+ __vibeTool: true,
238
+ name: 'glob',
239
+ schema: {
240
+ name: 'glob',
241
+ description: 'Find files matching a glob pattern.',
242
+ parameters: [
243
+ { name: 'pattern', type: { type: 'string' }, description: 'The glob pattern (e.g., "**/*.ts")', required: true },
244
+ { name: 'cwd', type: { type: 'string' }, description: 'Working directory for the search', required: false },
245
+ ],
246
+ returns: { type: 'array', items: { type: 'string' } },
247
+ },
248
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
249
+ const pattern = args.pattern as string;
250
+ const inputCwd = args.cwd as string | undefined;
251
+ const rootDir = context?.rootDir ?? process.cwd();
252
+ const cwd = inputCwd ? validatePathInSandbox(inputCwd, rootDir) : rootDir;
253
+
254
+ const globber = new Bun.Glob(pattern);
255
+ const matches: string[] = [];
256
+
257
+ for await (const file of globber.scan({ cwd })) {
258
+ matches.push(file);
259
+ }
260
+
261
+ return matches;
262
+ },
263
+ };
264
+
265
+ export const grep: VibeToolValue = {
266
+ __vibeTool: true,
267
+ name: 'grep',
268
+ schema: {
269
+ name: 'grep',
270
+ description: 'Search file contents for a pattern.',
271
+ parameters: [
272
+ { name: 'pattern', type: { type: 'string' }, description: 'The search pattern (regex)', required: true },
273
+ { name: 'path', type: { type: 'string' }, description: 'File or directory path to search', required: true },
274
+ { name: 'ignoreCase', type: { type: 'boolean' }, description: 'Ignore case in pattern matching', required: false },
275
+ ],
276
+ returns: {
277
+ type: 'array',
278
+ items: {
279
+ type: 'object',
280
+ properties: {
281
+ file: { type: 'string' },
282
+ line: { type: 'number' },
283
+ match: { type: 'string' },
284
+ },
285
+ },
286
+ },
287
+ },
288
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
289
+ const pattern = args.pattern as string;
290
+ const inputPath = args.path as string;
291
+ const ignoreCase = args.ignoreCase as boolean | undefined;
292
+ const rootDir = context?.rootDir ?? process.cwd();
293
+ const safePath = validatePathInSandbox(inputPath, rootDir);
294
+
295
+ const fs = await import('fs/promises');
296
+ const pathModule = await import('path');
297
+ const regex = new RegExp(pattern, ignoreCase ? 'gi' : 'g');
298
+
299
+ const results: Array<{ file: string; line: number; match: string }> = [];
300
+
301
+ async function searchFile(filePath: string) {
302
+ const content = await Bun.file(filePath).text();
303
+ const lines = content.split('\n');
304
+
305
+ for (let i = 0; i < lines.length; i++) {
306
+ const matches = lines[i].match(regex);
307
+ if (matches) {
308
+ for (const match of matches) {
309
+ results.push({
310
+ file: pathModule.relative(rootDir, filePath),
311
+ line: i + 1,
312
+ match,
313
+ });
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ const stats = await fs.stat(safePath);
320
+ if (stats.isDirectory()) {
321
+ const globber = new Bun.Glob('**/*');
322
+ for await (const file of globber.scan({ cwd: safePath })) {
323
+ const fullPath = pathModule.join(safePath, file);
324
+ const fileStats = await fs.stat(fullPath);
325
+ if (fileStats.isFile()) {
326
+ await searchFile(fullPath);
327
+ }
328
+ }
329
+ } else {
330
+ await searchFile(safePath);
331
+ }
332
+
333
+ return results;
334
+ },
335
+ };
336
+
337
+ // =============================================================================
338
+ // Directory Tools
339
+ // =============================================================================
340
+
341
+ export const mkdir: VibeToolValue = {
342
+ __vibeTool: true,
343
+ name: 'mkdir',
344
+ schema: {
345
+ name: 'mkdir',
346
+ description: 'Create a directory.',
347
+ parameters: [
348
+ { name: 'path', type: { type: 'string' }, description: 'The directory path to create', required: true },
349
+ { name: 'recursive', type: { type: 'boolean' }, description: 'Create parent directories as needed', required: false },
350
+ ],
351
+ returns: { type: 'boolean' },
352
+ },
353
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
354
+ const inputPath = args.path as string;
355
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
356
+ const recursive = args.recursive as boolean | undefined;
357
+
358
+ const fs = await import('fs/promises');
359
+ await fs.mkdir(safePath, { recursive: recursive ?? false });
360
+ return true;
361
+ },
362
+ };
363
+
364
+ export const dirExists: VibeToolValue = {
365
+ __vibeTool: true,
366
+ name: 'dirExists',
367
+ schema: {
368
+ name: 'dirExists',
369
+ description: 'Check if a directory exists.',
370
+ parameters: [
371
+ { name: 'path', type: { type: 'string' }, description: 'The directory path to check', required: true },
372
+ ],
373
+ returns: { type: 'boolean' },
374
+ },
375
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
376
+ const inputPath = args.path as string;
377
+ const safePath = context ? validatePathInSandbox(inputPath, context.rootDir) : inputPath;
378
+
379
+ const fs = await import('fs/promises');
380
+ try {
381
+ const stats = await fs.stat(safePath);
382
+ return stats.isDirectory();
383
+ } catch {
384
+ return false;
385
+ }
386
+ },
387
+ };
388
+
389
+ // =============================================================================
390
+ // Utility Tools
391
+ // =============================================================================
392
+
393
+ export const env: VibeToolValue = {
394
+ __vibeTool: true,
395
+ name: 'env',
396
+ schema: {
397
+ name: 'env',
398
+ description: 'Get an environment variable.',
399
+ parameters: [
400
+ { name: 'name', type: { type: 'string' }, description: 'The environment variable name', required: true },
401
+ { name: 'defaultValue', type: { type: 'string' }, description: 'Default value if not set', required: false },
402
+ ],
403
+ returns: { type: 'string' },
404
+ },
405
+ executor: async (args: Record<string, unknown>) => {
406
+ const name = args.name as string;
407
+ const defaultValue = args.defaultValue as string | undefined;
408
+ return process.env[name] ?? defaultValue ?? '';
409
+ },
410
+ };
411
+
412
+ export const now: VibeToolValue = {
413
+ __vibeTool: true,
414
+ name: 'now',
415
+ schema: {
416
+ name: 'now',
417
+ description: 'Get the current timestamp in milliseconds.',
418
+ parameters: [],
419
+ returns: { type: 'number' },
420
+ },
421
+ executor: async () => {
422
+ return Date.now();
423
+ },
424
+ };
425
+
426
+ export const jsonParse: VibeToolValue = {
427
+ __vibeTool: true,
428
+ name: 'jsonParse',
429
+ schema: {
430
+ name: 'jsonParse',
431
+ description: 'Parse a JSON string into an object.',
432
+ parameters: [
433
+ { name: 'text', type: { type: 'string' }, description: 'The JSON string to parse', required: true },
434
+ ],
435
+ returns: { type: 'object', additionalProperties: true },
436
+ },
437
+ executor: async (args: Record<string, unknown>) => {
438
+ const text = args.text as string;
439
+ return JSON.parse(text);
440
+ },
441
+ };
442
+
443
+ export const jsonStringify: VibeToolValue = {
444
+ __vibeTool: true,
445
+ name: 'jsonStringify',
446
+ schema: {
447
+ name: 'jsonStringify',
448
+ description: 'Convert an object to a JSON string.',
449
+ parameters: [
450
+ { name: 'value', type: { type: 'object', additionalProperties: true }, description: 'The value to stringify', required: true },
451
+ { name: 'pretty', type: { type: 'boolean' }, description: 'Whether to format with indentation', required: false },
452
+ ],
453
+ returns: { type: 'string' },
454
+ },
455
+ executor: async (args: Record<string, unknown>) => {
456
+ const value = args.value;
457
+ const pretty = args.pretty as boolean | undefined;
458
+ return pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value);
459
+ },
460
+ };
461
+
462
+ export const random: VibeToolValue = {
463
+ __vibeTool: true,
464
+ name: 'random',
465
+ schema: {
466
+ name: 'random',
467
+ description: 'Generate a random number. Without arguments, returns 0-1. With min/max, returns integer in range.',
468
+ parameters: [
469
+ { name: 'min', type: { type: 'number' }, description: 'Minimum value (inclusive)', required: false },
470
+ { name: 'max', type: { type: 'number' }, description: 'Maximum value (inclusive)', required: false },
471
+ ],
472
+ returns: { type: 'number' },
473
+ },
474
+ executor: async (args: Record<string, unknown>) => {
475
+ const min = args.min as number | undefined;
476
+ const max = args.max as number | undefined;
477
+
478
+ if (min !== undefined && max !== undefined) {
479
+ return Math.floor(Math.random() * (max - min + 1)) + min;
480
+ }
481
+
482
+ return Math.random();
483
+ },
484
+ };
485
+
486
+ export const uuid: VibeToolValue = {
487
+ __vibeTool: true,
488
+ name: 'uuid',
489
+ schema: {
490
+ name: 'uuid',
491
+ description: 'Generate a UUID v4.',
492
+ parameters: [],
493
+ returns: { type: 'string' },
494
+ },
495
+ executor: async () => {
496
+ return crypto.randomUUID();
497
+ },
498
+ };
499
+
500
+ // =============================================================================
501
+ // System Tools
502
+ // =============================================================================
503
+
504
+ export const bash: VibeToolValue = {
505
+ __vibeTool: true,
506
+ name: 'bash',
507
+ schema: {
508
+ name: 'bash',
509
+ description:
510
+ 'Execute a shell command and return stdout, stderr, and exit code. ' +
511
+ 'Works cross-platform (Windows/Mac/Linux). ' +
512
+ 'Supports pipes (cmd1 | cmd2), file redirection (> file, >> file), and standard shell features. ' +
513
+ 'Commands run from the project root directory by default.',
514
+ parameters: [
515
+ { name: 'command', type: { type: 'string' }, description: 'The shell command to execute', required: true },
516
+ { name: 'cwd', type: { type: 'string' }, description: 'Working directory for the command', required: false },
517
+ { name: 'timeout', type: { type: 'number' }, description: 'Timeout in milliseconds (default: 30000)', required: false },
518
+ ],
519
+ returns: {
520
+ type: 'object',
521
+ properties: {
522
+ stdout: { type: 'string' },
523
+ stderr: { type: 'string' },
524
+ exitCode: { type: 'number' },
525
+ },
526
+ },
527
+ },
528
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
529
+ const command = args.command as string;
530
+ const cwd = (args.cwd as string) || context?.rootDir || process.cwd();
531
+ const timeout = (args.timeout as number) || 30000;
532
+
533
+ const { writeFile: fsWriteFile, rm } = await import('fs/promises');
534
+ const { join } = await import('path');
535
+ const { tmpdir } = await import('os');
536
+
537
+ const scriptPath = join(tmpdir(), `vibe-bash-${process.pid}-${Date.now()}.ts`);
538
+ const escapedCommand = command.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
539
+
540
+ const scriptContent = `import { $ } from 'bun';
541
+ const result = await $\`${escapedCommand}\`.cwd(${JSON.stringify(cwd)}).nothrow().quiet();
542
+ process.stdout.write(result.stdout);
543
+ process.stderr.write(result.stderr);
544
+ process.exit(result.exitCode);
545
+ `;
546
+
547
+ try {
548
+ await fsWriteFile(scriptPath, scriptContent);
549
+ const proc = Bun.spawn(['bun', 'run', scriptPath], { stdout: 'pipe', stderr: 'pipe' });
550
+ const timeoutId = setTimeout(() => proc.kill(), timeout);
551
+ const exitCode = await proc.exited;
552
+ clearTimeout(timeoutId);
553
+
554
+ const stdout = await new Response(proc.stdout).text();
555
+ const stderr = await new Response(proc.stderr).text();
556
+
557
+ return { stdout, stderr, exitCode };
558
+ } finally {
559
+ try { await rm(scriptPath); } catch { /* ignore */ }
560
+ }
561
+ },
562
+ };
563
+
564
+ export const runCode: VibeToolValue = {
565
+ __vibeTool: true,
566
+ name: 'runCode',
567
+ schema: {
568
+ name: 'runCode',
569
+ description:
570
+ 'Execute TypeScript/JavaScript code in a sandboxed subprocess. ' +
571
+ 'All scope variables are automatically available as local variables. ' +
572
+ 'Use `return value` to pass results back. Bun APIs are available. ' +
573
+ 'Each execution creates a unique folder in .vibe-cache/ for intermediate files.',
574
+ parameters: [
575
+ { name: 'code', type: { type: 'string' }, description: 'TypeScript/JavaScript code to execute', required: true },
576
+ { name: 'scope', type: { type: 'object', additionalProperties: true }, description: 'Variables to make available in the code', required: false },
577
+ { name: 'timeout', type: { type: 'number' }, description: 'Timeout in milliseconds (default: 30000)', required: false },
578
+ ],
579
+ returns: {
580
+ type: 'object',
581
+ properties: {
582
+ result: { type: 'string' },
583
+ stdout: { type: 'string' },
584
+ stderr: { type: 'string' },
585
+ exitCode: { type: 'number' },
586
+ runFolder: { type: 'string' },
587
+ error: { type: 'string' },
588
+ },
589
+ },
590
+ },
591
+ executor: async (args: Record<string, unknown>, context?: ToolContext) => {
592
+ const code = args.code as string;
593
+ const scope = (args.scope as Record<string, unknown>) || {};
594
+ const timeout = (args.timeout as number) || 30000;
595
+
596
+ const { mkdir: fsMkdir, writeFile: fsWriteFile, readdir } = await import('fs/promises');
597
+ const { join } = await import('path');
598
+
599
+ const projectDir = context?.rootDir || process.cwd();
600
+ const cacheDir = join(projectDir, '.vibe-cache');
601
+
602
+ // Get unique run folder
603
+ let runName = 'r1';
604
+ try {
605
+ await fsMkdir(cacheDir, { recursive: true });
606
+ const entries = await readdir(cacheDir);
607
+ const runNums = entries.filter(e => e.startsWith('r')).map(e => parseInt(e.slice(1), 10)).filter(n => !isNaN(n));
608
+ runName = runNums.length > 0 ? `r${Math.max(...runNums) + 1}` : 'r1';
609
+ } catch { /* start at r1 */ }
610
+
611
+ const runDir = join(cacheDir, runName);
612
+ const runPath = `.vibe-cache/${runName}`;
613
+
614
+ try {
615
+ await fsMkdir(runDir, { recursive: true });
616
+ await fsWriteFile(join(runDir, 'scope.json'), JSON.stringify(scope, null, 2));
617
+
618
+ const scopeKeys = Object.keys(scope);
619
+ const destructure = scopeKeys.length > 0 ? `const { ${scopeKeys.join(', ')} } = __scope;` : '';
620
+
621
+ const wrappedCode = `const __scope = JSON.parse(await Bun.file('${runPath}/scope.json').text());
622
+ ${destructure}
623
+ const __result = await (async () => {
624
+ ${code}
625
+ })();
626
+ console.log('__VIBE_RESULT__' + JSON.stringify(__result));
627
+ `;
628
+
629
+ await fsWriteFile(join(runDir, 'script.ts'), wrappedCode);
630
+
631
+ const proc = Bun.spawn(['bun', 'run', `${runPath}/script.ts`], {
632
+ stdout: 'pipe',
633
+ stderr: 'pipe',
634
+ cwd: projectDir,
635
+ });
636
+
637
+ const timeoutId = setTimeout(() => proc.kill(), timeout);
638
+ const exitCode = await proc.exited;
639
+ clearTimeout(timeoutId);
640
+
641
+ const stdout = await new Response(proc.stdout).text();
642
+ const stderr = await new Response(proc.stderr).text();
643
+
644
+ let result: unknown;
645
+ const resultMatch = stdout.match(/__VIBE_RESULT__(.+)/);
646
+ if (resultMatch) {
647
+ try { result = JSON.parse(resultMatch[1]); } catch { result = resultMatch[1]; }
648
+ }
649
+
650
+ return { result, stdout: stdout.replace(/__VIBE_RESULT__.+\n?/, ''), stderr, exitCode, runFolder: runPath };
651
+ } catch (err) {
652
+ return { stdout: '', stderr: '', exitCode: 1, runFolder: runPath, error: err instanceof Error ? err.message : String(err) };
653
+ }
654
+ },
655
+ };
656
+
657
+ // =============================================================================
658
+ // Tool Bundles
659
+ // =============================================================================
660
+
661
+ /**
662
+ * All tools - the complete set of standard tools.
663
+ * Includes all file, search, directory, utility, and system tools.
664
+ */
665
+ export const allTools: VibeToolValue[] = [
666
+ // File tools
667
+ readFile, writeFile, appendFile, fileExists, listDir, edit, fastEdit,
668
+ // Search tools
669
+ glob, grep,
670
+ // Directory tools
671
+ mkdir, dirExists,
672
+ // Utility tools
673
+ env, now, jsonParse, jsonStringify, random, uuid,
674
+ // System tools
675
+ bash, runCode,
676
+ ];
677
+
678
+ /**
679
+ * Read-only tools - safe tools that cannot modify the filesystem or execute commands.
680
+ * Excludes: writeFile, appendFile, edit, fastEdit, mkdir, bash, runCode
681
+ */
682
+ export const readonlyTools: VibeToolValue[] = [
683
+ // File tools (read-only)
684
+ readFile, fileExists, listDir,
685
+ // Search tools
686
+ glob, grep,
687
+ // Directory tools (read-only)
688
+ dirExists,
689
+ // Utility tools
690
+ env, now, jsonParse, jsonStringify, random, uuid,
691
+ ];
692
+
693
+ /**
694
+ * Safe tools - all tools except code execution and shell commands.
695
+ * Excludes: bash, runCode
696
+ */
697
+ export const safeTools: VibeToolValue[] = [
698
+ // File tools
699
+ readFile, writeFile, appendFile, fileExists, listDir, edit, fastEdit,
700
+ // Search tools
701
+ glob, grep,
702
+ // Directory tools
703
+ mkdir, dirExists,
704
+ // Utility tools
705
+ env, now, jsonParse, jsonStringify, random, uuid,
706
+ ];
707
+