Dax Raad 9 months ago
parent
commit
91a9e455e2

+ 1 - 1
js/src/tool/bash.ts

@@ -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(),

+ 1 - 1
js/src/tool/edit.ts

@@ -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"),

+ 1 - 1
js/src/tool/fetch.ts

@@ -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"),

+ 1 - 1
js/src/tool/glob.ts

@@ -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"),

+ 1 - 1
js/src/tool/grep.ts

@@ -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

+ 64 - 258
js/src/tool/ls.ts

@@ -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.
-
-WHEN TO USE THIS TOOL:
-- Use when you need to explore the structure of a directory
-- Helpful for understanding the organization of a project
-- Good first step when getting familiar with a new codebase
-
-HOW TO USE:
-- Provide a path to list (defaults to current working directory)
-- Optionally specify glob patterns to ignore
-- Results are displayed in a tree structure
-
-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[];
-}
+const IGNORE_PATTERNS = [
+  "node_modules/",
+  "__pycache__/",
+  ".git/",
+  "dist/",
+  "build/",
+  "target/",
+  "vendor/",
+  "bin/",
+  "obj/",
+  ".idea/",
+  ".vscode/",
+];
 
 
 export const ls = Tool.define({
 export const ls = Tool.define({
-  name: "ls",
-  description: DESCRIPTION,
+  name: "opencode.ls",
+  description: "List directory contents",
   parameters: z.object({
   parameters: z.object({
-    path: z
-      .string()
-      .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(),
+    path: z.string().optional(),
+    ignore: z.array(z.string()).optional(),
   }),
   }),
   async execute(params) {
   async execute(params) {
     const app = await App.use();
     const app = await App.use();
-    let searchPath = params.path || app.root;
-
-    if (!path.isAbsolute(searchPath)) {
-      searchPath = path.join(app.root, searchPath);
-    }
-
-    const stat = await fs.promises.stat(searchPath).catch(() => null);
-    if (!stat) {
-      return {
-        metadata: {},
-        output: `Path does not exist: ${searchPath}`,
-      };
-    }
-
-    const { files, truncated } = await listDirectory(
-      searchPath,
-      params.ignore || [],
-      MAX_LS_FILES,
-    );
-    const tree = createFileTree(files);
-    let output = printTree(tree, searchPath);
-
-    if (truncated) {
-      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}`;
-    }
-
-    return {
-      metadata: {
-        count: files.length,
-        truncated,
-      },
-      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 searchPath = path.resolve(app.root, params.path || ".");
 
 
-    const entries = await fs.promises
-      .readdir(dir, { withFileTypes: true })
-      .catch(() => []);
+    const glob = new Bun.Glob("**/*");
+    const files = [];
 
 
-    for (const entry of entries) {
-      const fullPath = path.join(dir, entry.name);
-
-      if (shouldSkip(fullPath, ignorePatterns)) {
+    for await (const file of glob.scan({ cwd: searchPath })) {
+      if (file.startsWith(".") || IGNORE_PATTERNS.some((p) => file.includes(p)))
         continue;
         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;
-        }
-      }
+      if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file)))
+        continue;
+      files.push(file);
+      if (files.length >= 1000) break;
     }
     }
-  }
 
 
-  await walk(initialPath);
-  return { files: results, truncated };
-}
+    // Build directory structure
+    const dirs = new Set<string>();
+    const filesByDir = new Map<string, string[]>();
 
 
-function shouldSkip(filePath: string, ignorePatterns: string[]): boolean {
-  const base = path.basename(filePath);
+    for (const file of files) {
+      const dir = path.dirname(file);
+      const parts = dir === "." ? [] : dir.split("/");
 
 
-  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;
+      // Add all parent directories
+      for (let i = 0; i <= parts.length; i++) {
+        const dirPath = i === 0 ? "." : parts.slice(0, i).join("/");
+        dirs.add(dirPath);
       }
       }
-    } else {
-      if (base === ignored) {
-        return true;
-      }
-    }
-  }
 
 
-  for (const pattern of ignorePatterns) {
-    const glob = new Bun.Glob(pattern);
-    if (glob.match(base)) {
-      return true;
+      // Add file to its directory
+      if (!filesByDir.has(dir)) filesByDir.set(dir, []);
+      filesByDir.get(dir)!.push(path.basename(file));
     }
     }
-  }
-
-  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 = "";
+    function renderDir(dirPath: string, depth: number): string {
+      const indent = "  ".repeat(depth);
+      let output = "";
 
 
-    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 (depth > 0) {
+        output += `${indent}${path.basename(dirPath)}/\n`;
       }
       }
 
 
-      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: [],
-      };
+      const childIndent = "  ".repeat(depth + 1);
+      const children = Array.from(dirs)
+        .filter((d) => path.dirname(d) === dirPath && d !== dirPath)
+        .sort();
 
 
-      pathMap[currentPath] = newNode;
+      // Render subdirectories first
+      for (const child of children) {
+        output += renderDir(child, depth + 1);
+      }
 
 
-      if (i > 0 && parentPath !== "") {
-        if (pathMap[parentPath]) {
-          pathMap[parentPath].children?.push(newNode);
-        }
-      } else {
-        root.push(newNode);
+      // Render files
+      const files = filesByDir.get(dirPath) || [];
+      for (const file of files.sort()) {
+        output += `${childIndent}${file}\n`;
       }
       }
 
 
-      parentPath = currentPath;
+      return output;
     }
     }
-  }
-
-  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`;
+    const output = `${searchPath}/\n` + renderDir(".", 0);
 
 
-  if (node.type === "directory" && node.children && node.children.length > 0) {
-    for (const child of node.children) {
-      result = printNode(child, level + 1, result);
-    }
-  }
-
-  return result;
-}
+    return {
+      metadata: { count: files.length, truncated: files.length >= 1000 },
+      output,
+    };
+  },
+});

+ 1 - 1
js/src/tool/lsp-diagnostics.ts

@@ -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:

+ 1 - 1
js/src/tool/lsp-hover.ts

@@ -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. 

+ 1 - 1
js/src/tool/patch.ts

@@ -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) => {

+ 1 - 1
js/src/tool/view.ts

@@ -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"),

+ 2 - 1
js/src/util/log.ts

@@ -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>) {