tool rework

This commit is contained in:
Dax Raad
2025-05-31 17:12:16 -04:00
parent 33109bac4d
commit b4f809559e
19 changed files with 142 additions and 110 deletions

View File

@@ -170,8 +170,8 @@ Important:
- Return an empty response - the user will see the gh output directly
- Never update git config`
export const bash = Tool.define({
name: "opencode.bash",
export const BashTool = Tool.define({
id: "opencode.bash",
description: DESCRIPTION,
parameters: z.object({
command: z.string(),
@@ -193,6 +193,7 @@ export const bash = Tool.define({
timeout: timeout,
})
return {
metadata: {},
output: process.stdout.toString("utf-8"),
}
},

View File

@@ -52,8 +52,8 @@ 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.`
export const edit = Tool.define({
name: "opencode.edit",
export const EditTool = Tool.define({
id: "opencode.edit",
description: DESCRIPTION,
parameters: z.object({
filePath: z.string().describe("The absolute path to the file to modify"),

View File

@@ -0,0 +1,19 @@
import { z } from "zod"
import { Tool } from "./tool"
export const ExampleTool = Tool.define({
id: "opencode.example",
description: "Example tool",
parameters: z.object({
foo: z.string().describe("The foo parameter"),
bar: z.number().describe("The bar parameter"),
}),
async execute(params) {
return {
metadata: {
lol: "hey",
},
output: "Hello, world!",
}
},
})

View File

@@ -36,8 +36,8 @@ TIPS:
- Use html format when you need the raw HTML structure
- Set appropriate timeouts for potentially slow websites`
export const Fetch = Tool.define({
name: "opencode.fetch",
export const FetchTool = Tool.define({
id: "opencode.fetch",
description: DESCRIPTION,
parameters: z.object({
url: z.string().describe("The URL to fetch content from"),
@@ -53,7 +53,7 @@ export const Fetch = Tool.define({
.describe("Optional timeout in seconds (max 120)")
.optional(),
}),
async execute(params, opts) {
async execute(param) {
// Validate URL
if (
!params.url.startsWith("http://") &&
@@ -69,9 +69,6 @@ export const Fetch = Tool.define({
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
if (opts?.abortSignal) {
opts.abortSignal.addEventListener("abort", () => controller.abort())
}
const response = await fetch(params.url, {
signal: controller.signal,
@@ -104,22 +101,22 @@ export const Fetch = Tool.define({
case "text":
if (contentType.includes("text/html")) {
const text = extractTextFromHTML(content)
return { output: text }
return { output: text, metadata: {} }
}
return { output: content }
return { output: content, metadata: {} }
case "markdown":
if (contentType.includes("text/html")) {
const markdown = convertHTMLToMarkdown(content)
return { output: markdown }
return { output: markdown, metadata: {} }
}
return { output: "```\n" + content + "\n```" }
case "html":
return { output: content }
return { output: content, metadata: {} }
default:
return { output: content }
return { output: content, metadata: {} }
}
},
})

View File

@@ -37,8 +37,8 @@ TIPS:
- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
- Always check if results are truncated and refine your search pattern if needed`
export const glob = Tool.define({
name: "opencode.glob",
export const GlobTool = Tool.define({
id: "opencode.glob",
description: DESCRIPTION,
parameters: z.object({
pattern: z.string().describe("The glob pattern to match files against"),

View File

@@ -255,8 +255,8 @@ async function searchFiles(
return { matches, truncated }
}
export const grep = Tool.define({
name: "opencode.grep",
export const GrepTool = Tool.define({
id: "opencode.grep",
description: DESCRIPTION,
parameters: z.object({
pattern: z

View File

@@ -1,9 +0,0 @@
export * from "./bash"
export * from "./edit"
export * from "./fetch"
export * from "./glob"
export * from "./grep"
export * from "./view"
export * from "./ls"
export * from "./lsp-diagnostics"
export * from "./lsp-hover"

View File

@@ -17,8 +17,8 @@ const IGNORE_PATTERNS = [
".vscode/",
]
export const ls = Tool.define({
name: "opencode.ls",
export const ListTool = Tool.define({
id: "opencode.list",
description: "List directory contents",
parameters: z.object({
path: z.string().optional(),

View File

@@ -5,7 +5,7 @@ import { LSP } from "../lsp"
import { App } from "../app/app"
export const LspDiagnosticTool = Tool.define({
name: "opencode.lsp_diagnostic",
id: "opencode.lsp_diagnostics",
description: `Get diagnostics for a file and/or project.
WHEN TO USE THIS TOOL:

View File

@@ -5,7 +5,7 @@ import { LSP } from "../lsp"
import { App } from "../app/app"
export const LspHoverTool = Tool.define({
name: "opencode.lsp_hover",
id: "opencode.lsp_hover",
description: `
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.

View File

@@ -265,8 +265,8 @@ async function applyCommit(
}
}
export const patch = Tool.define({
name: "opencode.patch",
export const PatchTool = Tool.define({
id: "opencode.patch",
description: DESCRIPTION,
parameters: PatchParams,
execute: async (params) => {

View File

@@ -1,72 +1,23 @@
import { tool, type Tool as AITool } from "ai"
import { Log } from "../util/log"
import { Config } from "../config/config"
import type { StandardSchemaV1 } from "@standard-schema/spec"
export namespace Tool {
const log = Log.create({ service: "tool" })
export interface Metadata<
Properties extends Record<string, any> = Record<string, any>,
export interface Info<
Parameters extends StandardSchemaV1 = StandardSchemaV1,
Metadata extends Record<string, any> = Record<string, any>,
> {
properties: Properties
time: {
start: number
end: number
}
}
const TOOL_MAPPING: Record<string, string[]> = {
anthropic: [],
}
export async function forProvider(providerID: string) {
const config = await Config.get()
const match = config.tool?.provider?.[providerID] ?? []
id: string
description: string
parameters: Parameters
execute(args: StandardSchemaV1.InferOutput<Parameters>): Promise<{
metadata: Metadata
output: string
}>
}
export function define<
Params,
Output extends { metadata?: any; output: any },
Name extends string,
>(
input: AITool<Params, Output> & {
name: Name
},
) {
return tool({
...input,
execute: async (params, opts) => {
log.info("invoking", {
id: opts.toolCallId,
name: input.name,
...params,
})
try {
const start = Date.now()
const result = await input.execute!(params, opts)
const metadata: Metadata<Output["metadata"]> = {
...result.metadata,
time: {
start,
end: Date.now(),
},
}
return {
metadata,
output: result.output,
}
} catch (e: any) {
log.error("error", {
msg: e.toString(),
})
return {
metadata: {
error: true,
message: e.toString(),
},
output: "An error occurred: " + e.toString(),
}
}
},
})
Parameters extends StandardSchemaV1,
Result extends Record<string, any>,
>(input: Info<Parameters, Result>): Info<Parameters, Result> {
return input
}
}

View File

@@ -40,8 +40,8 @@ TIPS:
- For code exploration, first use Grep to find relevant files, then View to examine them
- When viewing large files, use the offset parameter to read specific sections`
export const view = Tool.define({
name: "opencode.view",
export const ViewTool = Tool.define({
id: "opencode.view",
description: DESCRIPTION,
parameters: z.object({
filePath: z.string().describe("The path to the file to read"),