Skip to content

Commit b4f8095

Browse files
committed
tool rework
1 parent 33109ba commit b4f8095

19 files changed

Lines changed: 142 additions & 110 deletions

File tree

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/opencode/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@flystorage/file-storage": "1.1.0",
2222
"@flystorage/local-fs": "1.1.0",
2323
"@hono/zod-validator": "0.5.0",
24+
"@standard-schema/spec": "1.0.0",
2425
"ai": "catalog:",
2526
"cac": "6.7.14",
2627
"decimal.js": "10.5.0",

packages/opencode/src/config/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export namespace Config {
1414

1515
export const Info = z
1616
.object({
17-
provider: Provider.Info.array().optional(),
17+
provider: z.lazy(() => Provider.Info.array().optional()),
1818
tool: z
1919
.object({
2020
provider: z.record(z.string(), z.string().array()).optional(),

packages/opencode/src/provider/provider.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ import { Log } from "../util/log"
77
import path from "path"
88
import { Global } from "../global"
99
import { BunProc } from "../bun"
10+
import { BashTool } from "../tool/bash"
11+
import { EditTool } from "../tool/edit"
12+
import { FetchTool } from "../tool/fetch"
13+
import { GlobTool } from "../tool/glob"
14+
import { GrepTool } from "../tool/grep"
15+
import { ListTool } from "../tool/ls"
16+
import { LspDiagnosticTool } from "../tool/lsp-diagnostics"
17+
import { LspHoverTool } from "../tool/lsp-hover"
18+
import { PatchTool } from "../tool/patch"
19+
import { ViewTool } from "../tool/view"
20+
import type { Tool } from "../tool/tool"
1021

1122
export namespace Provider {
1223
const log = Log.create({ service: "provider" })
@@ -130,6 +141,33 @@ export namespace Provider {
130141
}
131142
}
132143

144+
const TOOLS = [
145+
BashTool,
146+
EditTool,
147+
FetchTool,
148+
GlobTool,
149+
GrepTool,
150+
ListTool,
151+
LspDiagnosticTool,
152+
LspHoverTool,
153+
PatchTool,
154+
ViewTool,
155+
EditTool,
156+
]
157+
const TOOL_MAPPING: Record<string, Tool.Info[]> = {
158+
anthropic: TOOLS,
159+
openai: TOOLS,
160+
google: TOOLS,
161+
}
162+
export async function tools(providerID: string) {
163+
const cfg = await Config.get()
164+
if (cfg.tool?.provider?.[providerID])
165+
return cfg.tool.provider[providerID].map(
166+
(id) => TOOLS.find((t) => t.id === id)!,
167+
)
168+
return TOOL_MAPPING[providerID] ?? TOOLS
169+
}
170+
133171
class ModelNotFoundError extends Error {
134172
constructor(public readonly model: string) {
135173
super()

packages/opencode/src/session/session.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import {
88
generateText,
99
stepCountIs,
1010
streamText,
11+
tool,
12+
type Tool as AITool,
1113
type LanguageModelUsage,
1214
} from "ai"
13-
import { z } from "zod"
14-
import * as tools from "../tool"
15+
import { z, ZodSchema } from "zod"
1516
import { Decimal } from "decimal.js"
1617

1718
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
@@ -290,6 +291,38 @@ export namespace Session {
290291
},
291292
}
292293
await updateMessage(next)
294+
const tools: Record<string, AITool> = {}
295+
for (const item of await Provider.tools(input.providerID)) {
296+
tools[item.id.replaceAll(".", "_")] = tool({
297+
id: item.id as any,
298+
description: item.description,
299+
parameters: item.parameters as ZodSchema,
300+
async execute(args, opts) {
301+
const start = Date.now()
302+
try {
303+
const result = await item.execute(args)
304+
next.metadata!.tool![opts.toolCallId] = {
305+
...result.metadata,
306+
time: {
307+
start,
308+
end: Date.now(),
309+
},
310+
}
311+
return result.output
312+
} catch (e: any) {
313+
next.metadata!.tool![opts.toolCallId] = {
314+
error: true,
315+
message: e.toString(),
316+
time: {
317+
start,
318+
end: Date.now(),
319+
},
320+
}
321+
return e.toString()
322+
}
323+
},
324+
})
325+
}
293326
const result = streamText({
294327
onStepFinish: async (step) => {
295328
const assistant = next.metadata!.assistant!
@@ -358,12 +391,12 @@ export namespace Session {
358391
p.toolInvocation.toolCallId === value.toolCallId,
359392
)
360393
if (match && match.type === "tool-invocation") {
361-
const { output, metadata } = value.result as any
362-
next.metadata!.tool[value.toolCallId] = metadata
363394
match.toolInvocation = {
364-
...match.toolInvocation,
395+
args: match.toolInvocation.args,
396+
toolCallId: match.toolInvocation.toolCallId,
397+
toolName: match.toolInvocation.toolName,
365398
state: "result",
366-
result: output,
399+
result: value.result as string,
367400
}
368401
}
369402
break

packages/opencode/src/tool/bash.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ Important:
170170
- Return an empty response - the user will see the gh output directly
171171
- Never update git config`
172172

173-
export const bash = Tool.define({
174-
name: "opencode.bash",
173+
export const BashTool = Tool.define({
174+
id: "opencode.bash",
175175
description: DESCRIPTION,
176176
parameters: z.object({
177177
command: z.string(),
@@ -193,6 +193,7 @@ export const bash = Tool.define({
193193
timeout: timeout,
194194
})
195195
return {
196+
metadata: {},
196197
output: process.stdout.toString("utf-8"),
197198
}
198199
},

packages/opencode/src/tool/edit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ When making edits:
5252
5353
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.`
5454

55-
export const edit = Tool.define({
56-
name: "opencode.edit",
55+
export const EditTool = Tool.define({
56+
id: "opencode.edit",
5757
description: DESCRIPTION,
5858
parameters: z.object({
5959
filePath: z.string().describe("The absolute path to the file to modify"),
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { z } from "zod"
2+
import { Tool } from "./tool"
3+
4+
export const ExampleTool = Tool.define({
5+
id: "opencode.example",
6+
description: "Example tool",
7+
parameters: z.object({
8+
foo: z.string().describe("The foo parameter"),
9+
bar: z.number().describe("The bar parameter"),
10+
}),
11+
async execute(params) {
12+
return {
13+
metadata: {
14+
lol: "hey",
15+
},
16+
output: "Hello, world!",
17+
}
18+
},
19+
})

packages/opencode/src/tool/fetch.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ TIPS:
3636
- Use html format when you need the raw HTML structure
3737
- Set appropriate timeouts for potentially slow websites`
3838

39-
export const Fetch = Tool.define({
40-
name: "opencode.fetch",
39+
export const FetchTool = Tool.define({
40+
id: "opencode.fetch",
4141
description: DESCRIPTION,
4242
parameters: z.object({
4343
url: z.string().describe("The URL to fetch content from"),
@@ -53,7 +53,7 @@ export const Fetch = Tool.define({
5353
.describe("Optional timeout in seconds (max 120)")
5454
.optional(),
5555
}),
56-
async execute(params, opts) {
56+
async execute(param) {
5757
// Validate URL
5858
if (
5959
!params.url.startsWith("http://") &&
@@ -69,9 +69,6 @@ export const Fetch = Tool.define({
6969

7070
const controller = new AbortController()
7171
const timeoutId = setTimeout(() => controller.abort(), timeout)
72-
if (opts?.abortSignal) {
73-
opts.abortSignal.addEventListener("abort", () => controller.abort())
74-
}
7572

7673
const response = await fetch(params.url, {
7774
signal: controller.signal,
@@ -104,22 +101,22 @@ export const Fetch = Tool.define({
104101
case "text":
105102
if (contentType.includes("text/html")) {
106103
const text = extractTextFromHTML(content)
107-
return { output: text }
104+
return { output: text, metadata: {} }
108105
}
109-
return { output: content }
106+
return { output: content, metadata: {} }
110107

111108
case "markdown":
112109
if (contentType.includes("text/html")) {
113110
const markdown = convertHTMLToMarkdown(content)
114-
return { output: markdown }
111+
return { output: markdown, metadata: {} }
115112
}
116113
return { output: "```\n" + content + "\n```" }
117114

118115
case "html":
119-
return { output: content }
116+
return { output: content, metadata: {} }
120117

121118
default:
122-
return { output: content }
119+
return { output: content, metadata: {} }
123120
}
124121
},
125122
})

packages/opencode/src/tool/glob.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ TIPS:
3737
- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
3838
- Always check if results are truncated and refine your search pattern if needed`
3939

40-
export const glob = Tool.define({
41-
name: "opencode.glob",
40+
export const GlobTool = Tool.define({
41+
id: "opencode.glob",
4242
description: DESCRIPTION,
4343
parameters: z.object({
4444
pattern: z.string().describe("The glob pattern to match files against"),

0 commit comments

Comments
 (0)