OpenCode Custom Tools

Custom Tools allow you to extend OpenCode's capabilities, enabling the LLM to call functions you define during conversations, thereby implementing complex operations such as database queries, script execution, and API calls.

These tools work with built-in tools (such asread、write、bash)


Custom tools are OpenCode's core extension capability, upgrading the LLM from only generating code to being able to execute tasks.

By designing tools properly, you can build:

  • Automated development pipelines
  • Intelligent code assistants
  • AI Ops systems
  • Enterprise internal AI tool platforms

1. What are Custom Tools?

A custom tool is essentially a function interface that the LLM can proactively call during execution and obtain the result.

Typical use cases:

  • Query databases (SQL / NoSQL)
  • Call internal APIs
  • Execute scripts (Python / Shell)
  • Read system status or environment information
  • Encapsulate complex business logic

You can think of it as:A plugin mechanism that gives AI execution capabilities。


2. Tool Storage Location

OpenCode automatically loads tools from the following directories:

  • Project level:.opencode/tools/
  • Global level:~/.config/opencode/tools/

The filename is the tool name (this is critical).


3. Basic Tool Definition (Recommended)

Usetool()Helper functions provide type safety and parameter validation.

import { tool } from "@opencode-ai/plugin"

export default tool({
  description: "Query the project database",
  args: {
    query: tool.schema.string().describe("SQL query to execute"),
  },
  async execute(args) {
    return `Executed query: ${args.query}`
  },
})

Explanation:

  • description: Tool description, used by the LLM to determine whether to call
  • args: Parameter definition (based on Zod)
  • execute: Actual execution logic

4. Multiple Tools in a Single File

A single file can export multiple tools, and each tool is automatically named as:

<文件名>_<导出名>

import { tool } from "@opencode-ai/plugin"

export const add = tool({
  description: "Add two numbers",
  args: {
    a: tool.schema.number(),
    b: tool.schema.number(),
  },
  async execute(args) {
    return args.a + args.b
  },
})

export const multiply = tool({
  description: "Multiply two numbers",
  args: {
    a: tool.schema.number(),
    b: tool.schema.number(),
  },
  async execute(args) {
    return args.a * args.b
  },
})

The final generated tools:

  • math_add
  • math_multiply

5. Conflicts with Built-in Tools

If your tool name is the same as a built-in tool, it willOverride the built-in tool。

// 覆盖内置 bash 工具
export default tool({
  description: "Restricted bash wrapper",
  args: {
    command: tool.schema.string(),
  },
  async execute(args) {
    return `blocked: ${args.command}`
  },
})

Recommendation:

  • Avoid naming conflicts
  • If you only need to restrict capabilities, prefer usingpermissionconfiguration

6. Parameter Definition (Zod)

Parameters are defined using Zod Schema, supporting type validation and descriptions:

args: {
  query: tool.schema.string().describe("SQL query"),
  limit: tool.schema.number().optional(),
}

You can also directly use Zod:

import { z } from "zod"

export default {
  description: "Example tool",
  args: {
    name: z.string(),
  },
  async execute(args) {
    return `Hello ${args.name}`
  },
}

7. Context

When a tool executes, context information is automatically injected:

export default tool({
  description: "Get project info",
  args: {},
  async execute(args, context) {
    const { agent, sessionID, directory, worktree } = context
    return `Agent: ${agent}, Dir: ${directory}`
  },
})<

Available fields:

  • agent: Current agent
  • sessionID: Session ID
  • messageID: Message ID
  • directory: Current working directory
  • worktree: Git root directory

8. Calling External Languages (Python Example)

You can implement tool logic in any language, such as Python.

1. Python Script

# .opencode/tools/add.py
import sys

a = int(sys.argv[1])
b = int(sys.argv[2])

print(a + b)

2. Tool Encapsulation

import { tool } from "@opencode-ai/plugin"
import path from "path"

export default tool({
  description: "Add two numbers using Python",
  args: {
    a: tool.schema.number(),
    b: tool.schema.number(),
  },
  async execute(args, context) {
    const script = path.join(context.worktree, ".opencode/tools/add.py")
    const result = await Bun.$`python3 ${script} ${args.a} ${args.b}`.text()
    return result.trim()
  },
})

This approach is suitable for:

  • Existing Python toolchains
  • Calling data analysis / AI models
  • Executing complex computation tasks

9. Best Practices

  • Tool descriptions should be clear to help the LLM choose correctly
  • Design parameters to be as simple and clear as possible
  • Avoid side effects (unless clearly needed)
  • Prefer controlling risky operations through permission
  • Tools should focus on a single responsibility
Other extensions