summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-31 17:12:16 -0400
committerDax Raad <[email protected]>2025-05-31 17:12:16 -0400
commitb4f809559eb2eb5634e9e2ee2d35a4ded9d67578 (patch)
tree1a1eedcae0bfbbc1776dca7580e08b5c67c52fe1
parent33109bac4dfb05d8c85749db63af343edf6e5795 (diff)
downloadopencode-b4f809559eb2eb5634e9e2ee2d35a4ded9d67578.tar.gz
opencode-b4f809559eb2eb5634e9e2ee2d35a4ded9d67578.zip
tool rework
-rw-r--r--bun.lock1
-rw-r--r--packages/opencode/package.json1
-rw-r--r--packages/opencode/src/config/config.ts2
-rw-r--r--packages/opencode/src/provider/provider.ts38
-rw-r--r--packages/opencode/src/session/session.ts45
-rw-r--r--packages/opencode/src/tool/bash.ts5
-rw-r--r--packages/opencode/src/tool/edit.ts4
-rw-r--r--packages/opencode/src/tool/example.ts19
-rw-r--r--packages/opencode/src/tool/fetch.ts19
-rw-r--r--packages/opencode/src/tool/glob.ts4
-rw-r--r--packages/opencode/src/tool/grep.ts4
-rw-r--r--packages/opencode/src/tool/index.ts9
-rw-r--r--packages/opencode/src/tool/ls.ts4
-rw-r--r--packages/opencode/src/tool/lsp-diagnostics.ts2
-rw-r--r--packages/opencode/src/tool/lsp-hover.ts2
-rw-r--r--packages/opencode/src/tool/patch.ts4
-rw-r--r--packages/opencode/src/tool/tool.ts79
-rw-r--r--packages/opencode/src/tool/view.ts4
-rw-r--r--packages/opencode/test/tool/tool.test.ts6
19 files changed, 142 insertions, 110 deletions
diff --git a/bun.lock b/bun.lock
index babf877d9..e33d04cc4 100644
--- a/bun.lock
+++ b/bun.lock
@@ -24,6 +24,7 @@
"@flystorage/file-storage": "1.1.0",
"@flystorage/local-fs": "1.1.0",
"@hono/zod-validator": "0.5.0",
+ "@standard-schema/spec": "1.0.0",
"ai": "catalog:",
"cac": "6.7.14",
"decimal.js": "10.5.0",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 51c537e90..61cccb89a 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -21,6 +21,7 @@
"@flystorage/file-storage": "1.1.0",
"@flystorage/local-fs": "1.1.0",
"@hono/zod-validator": "0.5.0",
+ "@standard-schema/spec": "1.0.0",
"ai": "catalog:",
"cac": "6.7.14",
"decimal.js": "10.5.0",
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 062f6c166..88952d57b 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -14,7 +14,7 @@ export namespace Config {
export const Info = z
.object({
- provider: Provider.Info.array().optional(),
+ provider: z.lazy(() => Provider.Info.array().optional()),
tool: z
.object({
provider: z.record(z.string(), z.string().array()).optional(),
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 54b9ff287..68cdda0fe 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -7,6 +7,17 @@ import { Log } from "../util/log"
import path from "path"
import { Global } from "../global"
import { BunProc } from "../bun"
+import { BashTool } from "../tool/bash"
+import { EditTool } from "../tool/edit"
+import { FetchTool } from "../tool/fetch"
+import { GlobTool } from "../tool/glob"
+import { GrepTool } from "../tool/grep"
+import { ListTool } from "../tool/ls"
+import { LspDiagnosticTool } from "../tool/lsp-diagnostics"
+import { LspHoverTool } from "../tool/lsp-hover"
+import { PatchTool } from "../tool/patch"
+import { ViewTool } from "../tool/view"
+import type { Tool } from "../tool/tool"
export namespace Provider {
const log = Log.create({ service: "provider" })
@@ -130,6 +141,33 @@ export namespace Provider {
}
}
+ const TOOLS = [
+ BashTool,
+ EditTool,
+ FetchTool,
+ GlobTool,
+ GrepTool,
+ ListTool,
+ LspDiagnosticTool,
+ LspHoverTool,
+ PatchTool,
+ ViewTool,
+ EditTool,
+ ]
+ const TOOL_MAPPING: Record<string, Tool.Info[]> = {
+ anthropic: TOOLS,
+ openai: TOOLS,
+ google: TOOLS,
+ }
+ export async function tools(providerID: string) {
+ const cfg = await Config.get()
+ if (cfg.tool?.provider?.[providerID])
+ return cfg.tool.provider[providerID].map(
+ (id) => TOOLS.find((t) => t.id === id)!,
+ )
+ return TOOL_MAPPING[providerID] ?? TOOLS
+ }
+
class ModelNotFoundError extends Error {
constructor(public readonly model: string) {
super()
diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts
index dac05164a..008c84248 100644
--- a/packages/opencode/src/session/session.ts
+++ b/packages/opencode/src/session/session.ts
@@ -8,10 +8,11 @@ import {
generateText,
stepCountIs,
streamText,
+ tool,
+ type Tool as AITool,
type LanguageModelUsage,
} from "ai"
-import { z } from "zod"
-import * as tools from "../tool"
+import { z, ZodSchema } from "zod"
import { Decimal } from "decimal.js"
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
@@ -290,6 +291,38 @@ export namespace Session {
},
}
await updateMessage(next)
+ const tools: Record<string, AITool> = {}
+ for (const item of await Provider.tools(input.providerID)) {
+ tools[item.id.replaceAll(".", "_")] = tool({
+ id: item.id as any,
+ description: item.description,
+ parameters: item.parameters as ZodSchema,
+ async execute(args, opts) {
+ const start = Date.now()
+ try {
+ const result = await item.execute(args)
+ next.metadata!.tool![opts.toolCallId] = {
+ ...result.metadata,
+ time: {
+ start,
+ end: Date.now(),
+ },
+ }
+ return result.output
+ } catch (e: any) {
+ next.metadata!.tool![opts.toolCallId] = {
+ error: true,
+ message: e.toString(),
+ time: {
+ start,
+ end: Date.now(),
+ },
+ }
+ return e.toString()
+ }
+ },
+ })
+ }
const result = streamText({
onStepFinish: async (step) => {
const assistant = next.metadata!.assistant!
@@ -358,12 +391,12 @@ export namespace Session {
p.toolInvocation.toolCallId === value.toolCallId,
)
if (match && match.type === "tool-invocation") {
- const { output, metadata } = value.result as any
- next.metadata!.tool[value.toolCallId] = metadata
match.toolInvocation = {
- ...match.toolInvocation,
+ args: match.toolInvocation.args,
+ toolCallId: match.toolInvocation.toolCallId,
+ toolName: match.toolInvocation.toolName,
state: "result",
- result: output,
+ result: value.result as string,
}
}
break
diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts
index 6e6bcd669..14af360d2 100644
--- a/packages/opencode/src/tool/bash.ts
+++ b/packages/opencode/src/tool/bash.ts
@@ -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"),
}
},
diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts
index 5e765beee..c8e31f522 100644
--- a/packages/opencode/src/tool/edit.ts
+++ b/packages/opencode/src/tool/edit.ts
@@ -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"),
diff --git a/packages/opencode/src/tool/example.ts b/packages/opencode/src/tool/example.ts
new file mode 100644
index 000000000..c7c16d49d
--- /dev/null
+++ b/packages/opencode/src/tool/example.ts
@@ -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!",
+ }
+ },
+})
diff --git a/packages/opencode/src/tool/fetch.ts b/packages/opencode/src/tool/fetch.ts
index 6d586964e..a4abc2293 100644
--- a/packages/opencode/src/tool/fetch.ts
+++ b/packages/opencode/src/tool/fetch.ts
@@ -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: {} }
}
},
})
diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts
index 89abb9e7d..f505a6389 100644
--- a/packages/opencode/src/tool/glob.ts
+++ b/packages/opencode/src/tool/glob.ts
@@ -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"),
diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts
index 2e7639eb5..d1712e147 100644
--- a/packages/opencode/src/tool/grep.ts
+++ b/packages/opencode/src/tool/grep.ts
@@ -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
diff --git a/packages/opencode/src/tool/index.ts b/packages/opencode/src/tool/index.ts
deleted file mode 100644
index f9bc9cb97..000000000
--- a/packages/opencode/src/tool/index.ts
+++ /dev/null
@@ -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"
diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts
index c9cd1b3ff..7e97eb655 100644
--- a/packages/opencode/src/tool/ls.ts
+++ b/packages/opencode/src/tool/ls.ts
@@ -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(),
diff --git a/packages/opencode/src/tool/lsp-diagnostics.ts b/packages/opencode/src/tool/lsp-diagnostics.ts
index f944f269b..f9bde717a 100644
--- a/packages/opencode/src/tool/lsp-diagnostics.ts
+++ b/packages/opencode/src/tool/lsp-diagnostics.ts
@@ -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:
diff --git a/packages/opencode/src/tool/lsp-hover.ts b/packages/opencode/src/tool/lsp-hover.ts
index dd698f1c8..e5bf02305 100644
--- a/packages/opencode/src/tool/lsp-hover.ts
+++ b/packages/opencode/src/tool/lsp-hover.ts
@@ -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.
diff --git a/packages/opencode/src/tool/patch.ts b/packages/opencode/src/tool/patch.ts
index dda185f37..68d2cfeed 100644
--- a/packages/opencode/src/tool/patch.ts
+++ b/packages/opencode/src/tool/patch.ts
@@ -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) => {
diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts
index 1a38ce529..75ff1cfb8 100644
--- a/packages/opencode/src/tool/tool.ts
+++ b/packages/opencode/src/tool/tool.ts
@@ -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
}
}
diff --git a/packages/opencode/src/tool/view.ts b/packages/opencode/src/tool/view.ts
index f63224089..c17aa1d22 100644
--- a/packages/opencode/src/tool/view.ts
+++ b/packages/opencode/src/tool/view.ts
@@ -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"),
diff --git a/packages/opencode/test/tool/tool.test.ts b/packages/opencode/test/tool/tool.test.ts
index a7185ada1..eeb681bb3 100644
--- a/packages/opencode/test/tool/tool.test.ts
+++ b/packages/opencode/test/tool/tool.test.ts
@@ -1,12 +1,12 @@
import { describe, expect, test } from "bun:test"
import { App } from "../../src/app/app"
-import { glob } from "../../src/tool/glob"
+import { GlobTool } from "../../src/tool/glob"
import { ls } from "../../src/tool/ls"
describe("tool.glob", () => {
test("truncate", async () => {
await App.provide({ directory: process.cwd() }, async () => {
- let result = await glob.execute(
+ let result = await GlobTool.execute(
{
pattern: "./node_modules/**/*",
},
@@ -20,7 +20,7 @@ describe("tool.glob", () => {
})
test("basic", async () => {
await App.provide({ directory: process.cwd() }, async () => {
- let result = await glob.execute(
+ let result = await GlobTool.execute(
{
pattern: "*.json",
},