From 1ffc8be2b6917073902c002fc40bf74dd27cb8de Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 18 Sep 2025 05:42:59 -0400 Subject: [PATCH] rework custom tools --- .opencode/tool/foo.ts | 10 +-- packages/plugin/src/example.ts | 8 +- packages/plugin/src/tool.ts | 15 ++-- .../web/src/content/docs/custom-tools.mdx | 78 ++++++++++++------- packages/web/src/content/docs/plugins.mdx | 8 +- 5 files changed, 70 insertions(+), 49 deletions(-) diff --git a/.opencode/tool/foo.ts b/.opencode/tool/foo.ts index 44ce990e..ac70798c 100644 --- a/.opencode/tool/foo.ts +++ b/.opencode/tool/foo.ts @@ -1,11 +1,11 @@ import { tool } from "@opencode-ai/plugin" -export default tool((z) => ({ - description: "foo tool for fooing", +export default tool({ + description: "call this tool when you want to give up", args: { - foo: z.string().describe("foo"), + message: tool.schema.string().describe("give up message"), }, - async execute() { + async execute(args) { return "Hey fuck you!" }, -})) +}) diff --git a/packages/plugin/src/example.ts b/packages/plugin/src/example.ts index fd6a404d..1e4557a6 100644 --- a/packages/plugin/src/example.ts +++ b/packages/plugin/src/example.ts @@ -5,15 +5,15 @@ export const ExamplePlugin: Plugin = async (ctx) => { return { permission: {}, tool: { - mytool: tool((zod) => ({ + mytool: tool({ description: "This is a custom tool tool", args: { - foo: zod.string(), + foo: tool.schema.string().describe("foo"), }, - async execute(args, ctx) { + async execute(args) { return `Hello ${args.foo}!` }, - })), + }), }, async "chat.params"(_input, output) { output.topP = 1 diff --git a/packages/plugin/src/tool.ts b/packages/plugin/src/tool.ts index 7c1d3d7c..2998a1e7 100644 --- a/packages/plugin/src/tool.ts +++ b/packages/plugin/src/tool.ts @@ -7,14 +7,13 @@ export type ToolContext = { abort: AbortSignal } -export function tool( - input: (zod: typeof z) => { - description: string - args: Args - execute: (args: z.infer>, ctx: ToolContext) => Promise - }, -) { - return input(z) +export function tool(input: { + description: string + args: Args + execute(args: z.infer>, context: ToolContext): Promise +}) { + return input } +tool.schema = z export type ToolDefinition = ReturnType diff --git a/packages/web/src/content/docs/custom-tools.mdx b/packages/web/src/content/docs/custom-tools.mdx index e67fe533..3ab8abda 100644 --- a/packages/web/src/content/docs/custom-tools.mdx +++ b/packages/web/src/content/docs/custom-tools.mdx @@ -9,27 +9,30 @@ Custom tools are functions you create that the LLM can call during conversations ## Tool structure -The easiest way to create tools is using the `tool()` helper which provides type safety and validation: +Tools are defined as `.ts/.js` files in the `.opencode/tool/` directory. They +can also be defined globally in `~/.config/opencode/tool/`. + +The easiest way to create tools is using the `tool()` helper which provides type safety and validation. Use `tool.schema` (which is just [Zod](https://zod.dev)) to define argument types: ```ts title=".opencode/tool/database.ts" import { tool } from "@opencode-ai/plugin" -export default tool((z) => ({ +export default tool({ description: "Query the project database", args: { - query: z.string().describe("SQL query to execute"), + query: tool.schema.string().describe("SQL query to execute"), }, async execute(args) { // Your database logic here return `Executed query: ${args.query}` }, -})) +}) ``` -You can also import Zod directly and return a plain object: +You can also import [Zod](https://zod.dev) directly and return a plain object: ```ts -import z from "zod/v4" +import { z } from "zod" export default { description: "Tool description", @@ -42,29 +45,62 @@ export default { }, } ``` - The filename becomes the tool name. This creates a `database` tool. --- +## Multiple tools per file + +You can export multiple tools from a single file. Each export becomes a separate tool with the name `_`: + +```ts title=".opencode/tool/math.ts" +import { tool } from "@opencode-ai/plugin" + +export const add = tool({ + description: "Add two numbers", + args: { + a: tool.schema.number().describe("First number"), + b: tool.schema.number().describe("Second number"), + }, + async execute(args) { + return args.a + args.b + }, +}) + +export const multiply = tool({ + description: "Multiply two numbers", + args: { + a: tool.schema.number().describe("First number"), + b: tool.schema.number().describe("Second number"), + }, + async execute(args) { + return args.a * args.b + }, +}) +``` + +This creates two tools: `math_add` and `math_multiply`. + +--- + ## Arguments -Use the `z` parameter to define tool arguments with validation and descriptions: +Use `tool.schema` (which is just [Zod](https://zod.dev)) to define tool arguments with validation and descriptions: ```ts title=".opencode/tool/calculator.ts" import { tool } from "@opencode-ai/plugin" -export default tool((z) => ({ +export default tool({ description: "Perform mathematical calculations", args: { - expression: z.string().describe("Mathematical expression to evaluate"), - precision: z.number().optional().describe("Decimal precision"), + expression: tool.schema.string().describe("Mathematical expression to evaluate"), + precision: tool.schema.number().optional().describe("Decimal precision"), }, async execute(args) { // Your calculation logic here return `Result: ${eval(args.expression).toFixed(args.precision || 2)}` }, -})) +}) ``` --- @@ -76,7 +112,7 @@ Tools receive context about the current session: ```ts title=".opencode/tool/project.ts" import { tool } from "@opencode-ai/plugin" -export default tool((z) => ({ +export default tool({ description: "Get project information", args: {}, async execute(args, context) { @@ -84,22 +120,8 @@ export default tool((z) => ({ const { project, directory, worktree } = context return `Project: ${project.name}, Directory: ${directory}` }, -})) +}) ``` ---- -## Tool locations -Custom tools are loaded from: - -- Project: `.opencode/tool/` -- Global: `~/.config/opencode/tool/` - -Files must use `.js` or `.ts` extensions. - ---- - -## Built-in tools - -opencode includes several built-in tools: `read`, `write`, `edit`, `bash`, `glob`, `grep`, `list`, `patch`, `todo`, and `task`. [Learn more](/docs/tui#tools). diff --git a/packages/web/src/content/docs/plugins.mdx b/packages/web/src/content/docs/plugins.mdx index 72758e8a..47f53fdd 100644 --- a/packages/web/src/content/docs/plugins.mdx +++ b/packages/web/src/content/docs/plugins.mdx @@ -108,7 +108,7 @@ export const EnvProtection = async ({ project, client, $, directory, worktree }) ### Custom tools -Create custom tools that opencode can use: +Plugins can also add custom tools to opencode: ```ts title=".opencode/plugin/custom-tools.ts" import type { Plugin, tool } from "@opencode-ai/plugin" @@ -116,15 +116,15 @@ import type { Plugin, tool } from "@opencode-ai/plugin" export const CustomToolsPlugin: Plugin = async (ctx) => { return { tool: { - mytool: tool((zod) => ({ + mytool: tool({ description: "This is a custom tool", args: { - foo: zod.string(), + foo: tool.schema.string(), }, async execute(args, ctx) { return `Hello ${args.foo}!` }, - })), + }), }, } }