mirror of
https://github.com/aljazceru/opencode.git
synced 2026-01-05 00:44:54 +01:00
sync
This commit is contained in:
@@ -171,7 +171,7 @@ Important:
|
|||||||
- Never update git config`;
|
- Never update git config`;
|
||||||
|
|
||||||
export const bash = Tool.define({
|
export const bash = Tool.define({
|
||||||
name: "bash",
|
name: "opencode.bash",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ When making edits:
|
|||||||
Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`;
|
Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`;
|
||||||
|
|
||||||
export const edit = Tool.define({
|
export const edit = Tool.define({
|
||||||
name: "edit",
|
name: "opencode.edit",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ TIPS:
|
|||||||
- Set appropriate timeouts for potentially slow websites`;
|
- Set appropriate timeouts for potentially slow websites`;
|
||||||
|
|
||||||
export const Fetch = Tool.define({
|
export const Fetch = Tool.define({
|
||||||
name: "fetch",
|
name: "opencode.fetch",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
url: z.string().describe("The URL to fetch content from"),
|
url: z.string().describe("The URL to fetch content from"),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ TIPS:
|
|||||||
- Always check if results are truncated and refine your search pattern if needed`;
|
- Always check if results are truncated and refine your search pattern if needed`;
|
||||||
|
|
||||||
export const glob = Tool.define({
|
export const glob = Tool.define({
|
||||||
name: "glob",
|
name: "opencode.glob",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
pattern: z.string().describe("The glob pattern to match files against"),
|
pattern: z.string().describe("The glob pattern to match files against"),
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ async function searchFiles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const grep = Tool.define({
|
export const grep = Tool.define({
|
||||||
name: "grep",
|
name: "opencode.grep",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
pattern: z
|
pattern: z
|
||||||
|
|||||||
@@ -2,289 +2,95 @@ import { z } from "zod";
|
|||||||
import { Tool } from "./tool";
|
import { Tool } from "./tool";
|
||||||
import { App } from "../app/app";
|
import { App } from "../app/app";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as fs from "fs";
|
|
||||||
|
|
||||||
const DESCRIPTION = `Directory listing tool that shows files and subdirectories in a tree structure, helping you explore and understand the project organization.
|
const IGNORE_PATTERNS = [
|
||||||
|
"node_modules/",
|
||||||
WHEN TO USE THIS TOOL:
|
"__pycache__/",
|
||||||
- Use when you need to explore the structure of a directory
|
".git/",
|
||||||
- Helpful for understanding the organization of a project
|
"dist/",
|
||||||
- Good first step when getting familiar with a new codebase
|
"build/",
|
||||||
|
"target/",
|
||||||
HOW TO USE:
|
"vendor/",
|
||||||
- Provide a path to list (defaults to current working directory)
|
"bin/",
|
||||||
- Optionally specify glob patterns to ignore
|
"obj/",
|
||||||
- Results are displayed in a tree structure
|
".idea/",
|
||||||
|
".vscode/",
|
||||||
FEATURES:
|
];
|
||||||
- Displays a hierarchical view of files and directories
|
|
||||||
- Automatically skips hidden files/directories (starting with '.')
|
|
||||||
- Skips common system directories like __pycache__
|
|
||||||
- Can filter out files matching specific patterns
|
|
||||||
|
|
||||||
LIMITATIONS:
|
|
||||||
- Results are limited to 1000 files
|
|
||||||
- Very large directories will be truncated
|
|
||||||
- Does not show file sizes or permissions
|
|
||||||
- Cannot recursively list all directories in a large project
|
|
||||||
|
|
||||||
TIPS:
|
|
||||||
- Use Glob tool for finding files by name patterns instead of browsing
|
|
||||||
- Use Grep tool for searching file contents
|
|
||||||
- Combine with other tools for more effective exploration`;
|
|
||||||
|
|
||||||
const MAX_LS_FILES = 1000;
|
|
||||||
|
|
||||||
interface TreeNode {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
type: "file" | "directory";
|
|
||||||
children?: TreeNode[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ls = Tool.define({
|
export const ls = Tool.define({
|
||||||
name: "ls",
|
name: "opencode.ls",
|
||||||
description: DESCRIPTION,
|
description: "List directory contents",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
path: z
|
path: z.string().optional(),
|
||||||
.string()
|
ignore: z.array(z.string()).optional(),
|
||||||
.describe(
|
|
||||||
"The path to the directory to list (defaults to current working directory)",
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
ignore: z
|
|
||||||
.array(z.string())
|
|
||||||
.describe("List of glob patterns to ignore")
|
|
||||||
.optional(),
|
|
||||||
}),
|
}),
|
||||||
async execute(params) {
|
async execute(params) {
|
||||||
const app = await App.use();
|
const app = await App.use();
|
||||||
let searchPath = params.path || app.root;
|
const searchPath = path.resolve(app.root, params.path || ".");
|
||||||
|
|
||||||
if (!path.isAbsolute(searchPath)) {
|
const glob = new Bun.Glob("**/*");
|
||||||
searchPath = path.join(app.root, searchPath);
|
const files = [];
|
||||||
|
|
||||||
|
for await (const file of glob.scan({ cwd: searchPath })) {
|
||||||
|
if (file.startsWith(".") || IGNORE_PATTERNS.some((p) => file.includes(p)))
|
||||||
|
continue;
|
||||||
|
if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file)))
|
||||||
|
continue;
|
||||||
|
files.push(file);
|
||||||
|
if (files.length >= 1000) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stat = await fs.promises.stat(searchPath).catch(() => null);
|
// Build directory structure
|
||||||
if (!stat) {
|
const dirs = new Set<string>();
|
||||||
return {
|
const filesByDir = new Map<string, string[]>();
|
||||||
metadata: {},
|
|
||||||
output: `Path does not exist: ${searchPath}`,
|
for (const file of files) {
|
||||||
};
|
const dir = path.dirname(file);
|
||||||
|
const parts = dir === "." ? [] : dir.split("/");
|
||||||
|
|
||||||
|
// Add all parent directories
|
||||||
|
for (let i = 0; i <= parts.length; i++) {
|
||||||
|
const dirPath = i === 0 ? "." : parts.slice(0, i).join("/");
|
||||||
|
dirs.add(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add file to its directory
|
||||||
|
if (!filesByDir.has(dir)) filesByDir.set(dir, []);
|
||||||
|
filesByDir.get(dir)!.push(path.basename(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { files, truncated } = await listDirectory(
|
function renderDir(dirPath: string, depth: number): string {
|
||||||
searchPath,
|
const indent = " ".repeat(depth);
|
||||||
params.ignore || [],
|
let output = "";
|
||||||
MAX_LS_FILES,
|
|
||||||
);
|
|
||||||
const tree = createFileTree(files);
|
|
||||||
let output = printTree(tree, searchPath);
|
|
||||||
|
|
||||||
if (truncated) {
|
if (depth > 0) {
|
||||||
output = `There are more than ${MAX_LS_FILES} files in the directory. Use a more specific path or use the Glob tool to find specific files. The first ${MAX_LS_FILES} files and directories are included below:\n\n${output}`;
|
output += `${indent}${path.basename(dirPath)}/\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childIndent = " ".repeat(depth + 1);
|
||||||
|
const children = Array.from(dirs)
|
||||||
|
.filter((d) => path.dirname(d) === dirPath && d !== dirPath)
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
// Render subdirectories first
|
||||||
|
for (const child of children) {
|
||||||
|
output += renderDir(child, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render files
|
||||||
|
const files = filesByDir.get(dirPath) || [];
|
||||||
|
for (const file of files.sort()) {
|
||||||
|
output += `${childIndent}${file}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const output = `${searchPath}/\n` + renderDir(".", 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metadata: {
|
metadata: { count: files.length, truncated: files.length >= 1000 },
|
||||||
count: files.length,
|
|
||||||
truncated,
|
|
||||||
},
|
|
||||||
output,
|
output,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function listDirectory(
|
|
||||||
initialPath: string,
|
|
||||||
ignorePatterns: string[],
|
|
||||||
limit: number,
|
|
||||||
): Promise<{ files: string[]; truncated: boolean }> {
|
|
||||||
const results: string[] = [];
|
|
||||||
let truncated = false;
|
|
||||||
|
|
||||||
async function walk(dir: string): Promise<void> {
|
|
||||||
if (results.length >= limit) {
|
|
||||||
truncated = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entries = await fs.promises
|
|
||||||
.readdir(dir, { withFileTypes: true })
|
|
||||||
.catch(() => []);
|
|
||||||
|
|
||||||
for (const entry of entries) {
|
|
||||||
const fullPath = path.join(dir, entry.name);
|
|
||||||
|
|
||||||
if (shouldSkip(fullPath, ignorePatterns)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
if (fullPath !== initialPath) {
|
|
||||||
results.push(fullPath + path.sep);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.length >= limit) {
|
|
||||||
truncated = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await walk(fullPath);
|
|
||||||
} else if (entry.isFile()) {
|
|
||||||
if (fullPath !== initialPath) {
|
|
||||||
results.push(fullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.length >= limit) {
|
|
||||||
truncated = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await walk(initialPath);
|
|
||||||
return { files: results, truncated };
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldSkip(filePath: string, ignorePatterns: string[]): boolean {
|
|
||||||
const base = path.basename(filePath);
|
|
||||||
|
|
||||||
if (base !== "." && base.startsWith(".")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const commonIgnored = [
|
|
||||||
"__pycache__",
|
|
||||||
"node_modules",
|
|
||||||
"dist",
|
|
||||||
"build",
|
|
||||||
"target",
|
|
||||||
"vendor",
|
|
||||||
"bin",
|
|
||||||
"obj",
|
|
||||||
".git",
|
|
||||||
".idea",
|
|
||||||
".vscode",
|
|
||||||
".DS_Store",
|
|
||||||
"*.pyc",
|
|
||||||
"*.pyo",
|
|
||||||
"*.pyd",
|
|
||||||
"*.so",
|
|
||||||
"*.dll",
|
|
||||||
"*.exe",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (filePath.includes(path.join("__pycache__", ""))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ignored of commonIgnored) {
|
|
||||||
if (ignored.endsWith("/")) {
|
|
||||||
if (filePath.includes(path.join(ignored.slice(0, -1), ""))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (ignored.startsWith("*.")) {
|
|
||||||
if (base.endsWith(ignored.slice(1))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (base === ignored) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const pattern of ignorePatterns) {
|
|
||||||
const glob = new Bun.Glob(pattern);
|
|
||||||
if (glob.match(base)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createFileTree(sortedPaths: string[]): TreeNode[] {
|
|
||||||
const root: TreeNode[] = [];
|
|
||||||
const pathMap: Record<string, TreeNode> = {};
|
|
||||||
|
|
||||||
for (const filePath of sortedPaths) {
|
|
||||||
const parts = filePath.split(path.sep).filter((part) => part !== "");
|
|
||||||
let currentPath = "";
|
|
||||||
let parentPath = "";
|
|
||||||
|
|
||||||
if (parts.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
|
||||||
const part = parts[i];
|
|
||||||
|
|
||||||
if (currentPath === "") {
|
|
||||||
currentPath = part;
|
|
||||||
} else {
|
|
||||||
currentPath = path.join(currentPath, part);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pathMap[currentPath]) {
|
|
||||||
parentPath = currentPath;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLastPart = i === parts.length - 1;
|
|
||||||
const isDir = !isLastPart || filePath.endsWith(path.sep);
|
|
||||||
const nodeType = isDir ? "directory" : "file";
|
|
||||||
|
|
||||||
const newNode: TreeNode = {
|
|
||||||
name: part,
|
|
||||||
path: currentPath,
|
|
||||||
type: nodeType,
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
pathMap[currentPath] = newNode;
|
|
||||||
|
|
||||||
if (i > 0 && parentPath !== "") {
|
|
||||||
if (pathMap[parentPath]) {
|
|
||||||
pathMap[parentPath].children?.push(newNode);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
root.push(newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
parentPath = currentPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
function printTree(tree: TreeNode[], rootPath: string): string {
|
|
||||||
let result = `- ${rootPath}${path.sep}\n`;
|
|
||||||
|
|
||||||
for (const node of tree) {
|
|
||||||
result = printNode(node, 1, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function printNode(node: TreeNode, level: number, result: string): string {
|
|
||||||
const indent = " ".repeat(level);
|
|
||||||
|
|
||||||
let nodeName = node.name;
|
|
||||||
if (node.type === "directory") {
|
|
||||||
nodeName += path.sep;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += `${indent}- ${nodeName}\n`;
|
|
||||||
|
|
||||||
if (node.type === "directory" && node.children && node.children.length > 0) {
|
|
||||||
for (const child of node.children) {
|
|
||||||
result = printNode(child, level + 1, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { LSP } from "../lsp";
|
|||||||
import { App } from "../app/app";
|
import { App } from "../app/app";
|
||||||
|
|
||||||
export const LspDiagnosticTool = Tool.define({
|
export const LspDiagnosticTool = Tool.define({
|
||||||
name: "diagnostics",
|
name: "opencode.lsp_diagnostic",
|
||||||
description: `Get diagnostics for a file and/or project.
|
description: `Get diagnostics for a file and/or project.
|
||||||
|
|
||||||
WHEN TO USE THIS TOOL:
|
WHEN TO USE THIS TOOL:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { LSP } from "../lsp";
|
|||||||
import { App } from "../app/app";
|
import { App } from "../app/app";
|
||||||
|
|
||||||
export const LspHoverTool = Tool.define({
|
export const LspHoverTool = Tool.define({
|
||||||
name: "lsp.hover",
|
name: "opencode.lsp_hover",
|
||||||
description: `
|
description: `
|
||||||
Looks up hover information for a given position in a source file using the Language Server Protocol (LSP).
|
Looks up hover information for a given position in a source file using the Language Server Protocol (LSP).
|
||||||
This includes type information, documentation, or symbol details at the specified line and character.
|
This includes type information, documentation, or symbol details at the specified line and character.
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ async function applyCommit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const patch = Tool.define({
|
export const patch = Tool.define({
|
||||||
name: "patch",
|
name: "opencode.patch",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: PatchParams,
|
parameters: PatchParams,
|
||||||
execute: async (params) => {
|
execute: async (params) => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ TIPS:
|
|||||||
- When viewing large files, use the offset parameter to read specific sections`;
|
- When viewing large files, use the offset parameter to read specific sections`;
|
||||||
|
|
||||||
export const view = Tool.define({
|
export const view = Tool.define({
|
||||||
name: "view",
|
name: "opencode.view",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z.string().describe("The path to the file to read"),
|
filePath: z.string().describe("The path to the file to read"),
|
||||||
|
|||||||
@@ -38,9 +38,10 @@ export namespace Log {
|
|||||||
...tags,
|
...tags,
|
||||||
...extra,
|
...extra,
|
||||||
})
|
})
|
||||||
|
.filter(([_, value]) => value !== undefined && value !== null)
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
.join(" ");
|
.join(" ");
|
||||||
return [new Date().toISOString(), prefix, message].join(" ") + "\n";
|
return [new Date().toISOString(), prefix, message].filter(Boolean).join(" ") + "\n";
|
||||||
}
|
}
|
||||||
const result = {
|
const result = {
|
||||||
info(message?: any, extra?: Record<string, any>) {
|
info(message?: any, extra?: Record<string, any>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user