This skill should be used when the user asks to "create an OpenCode tool", "build an OpenCode plugin", "write a custom tool for OpenCode", "use @opencode-ai/sdk", "use @opencode-ai/plugin",...
Guide for creating custom tools and plugins using the OpenCode SDK.
OpenCode provides two main packages for SDK development:
| Package | Purpose |
|---|---|
@opencode-ai/sdk |
Client SDK for interacting with OpenCode server (sessions, messages, files) |
@opencode-ai/plugin |
Plugin system for creating custom tools with schema validation |
Custom tools extend OpenCode's capabilities. Tools are TypeScript/JavaScript files auto-discovered from:
.opencode/tool/ in project directory~/.config/opencode/tool/The filename becomes the tool name.
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Brief description of what the tool does",
args: {
paramName: tool.schema.string().describe("Parameter description")
},
async execute(args, context) {
// context provides: sessionID, messageID, agent, abort
return "Result string returned to the AI"
}
})
Use tool.schema (which is Zod) for argument validation:
args: {
// String with description
query: tool.schema.string().describe("Search query"),
// Optional string
path: tool.schema.string().optional().describe("File path"),
// Number with constraints
limit: tool.schema.number().min(1).max(100).default(10).describe("Max results"),
// Enum/literal union
format: tool.schema.enum(["json", "text"]).describe("Output format"),
// Boolean
recursive: tool.schema.boolean().default(false).describe("Search recursively")
}
The execute function receives a context object:
type ToolContext = {
sessionID: string // Current session ID
messageID: string // Current message ID
agent: string // Current agent identifier
abort: AbortSignal // Signal for cancellation
}
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export default tool({
description: "Search for files matching a pattern",
args: {
pattern: tool.schema.string().describe("Glob pattern to match"),
directory: tool.schema.string().default(".").describe("Directory to search")
},
async execute({ pattern, directory }) {
const result = await $`find ${directory} -name "${pattern}"`.text()
return result || "No files found"
}
})
Plugins provide more comprehensive integrations with hooks for events, authentication, and tool modification.
import type { Plugin } from "@opencode-ai/plugin"
const plugin: Plugin = async (input) => {
const { client, project, directory, worktree, $ } = input
return {
// Custom tools
tool: {
myTool: tool({ /* definition */ })
},
// Event hooks
event: async ({ event }) => { /* handle events */ },
// Configuration hooks
config: async (config) => { /* modify config */ },
// Message hooks
"chat.message": async (input, output) => { /* modify messages */ },
// Tool execution hooks
"tool.execute.before": async (input, output) => { /* pre-processing */ },
"tool.execute.after": async (input, output) => { /* post-processing */ }
}
}
export default plugin
| Hook | Purpose |
|---|---|
event |
Handle real-time events from server |
config |
Modify configuration on load |
tool |
Register custom tools |
auth |
Custom authentication providers |
chat.message |
Modify messages before sending |
chat.params |
Modify LLM parameters (temperature, topP) |
permission.ask |
Handle permission requests |
tool.execute.before |
Pre-process tool arguments |
tool.execute.after |
Post-process tool output |
The SDK client provides programmatic access to OpenCode functionality.
import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk"
// Create both client and server
const { client, server } = await createOpencode({
hostname: "127.0.0.1",
port: 4096,
timeout: 5000
})
// Or just the client
const client = createOpencodeClient({
baseUrl: "http://127.0.0.1:4096"
})
| Category | Methods |
|---|---|
client.session |
list, create, get, delete, prompt, messages, fork, share |
client.project |
list, current |
client.file |
list, read, status |
client.find |
text, files, symbols |
client.tool |
ids, list |
client.event |
subscribe (SSE streaming) |
client.mcp |
status, add |
client.tui |
appendPrompt, submitPrompt, showToast |
// List sessions
const { data: sessions } = await client.session.list()
// Create session
const { data: session } = await client.session.create()
// Send prompt
const { data: response } = await client.session.prompt({
path: { id: sessionId },
body: {
parts: [{ type: "text", text: "Your message here" }]
}
})
// Get messages
const { data: messages } = await client.session.messages({
path: { id: sessionId }
})
const result = await client.event.subscribe()
for await (const event of result.events) {
console.log("Event:", event.type, event.data)
}
# Install SDK
npm install @opencode-ai/sdk
# Install plugin package (for tools)
npm install @opencode-ai/plugin
Requires TypeScript >= 4.9.
| Location | Scope |
|---|---|
.opencode/tool/*.ts |
Project-specific tools |
~/.config/opencode/tool/*.ts |
Global tools for all projects |
Multiple exports create multiple tools: filename_exportname.
context.abort for long-running operationsimport { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export default tool({
description: "Run Python analysis script",
args: {
file: tool.schema.string().describe("File to analyze")
},
async execute({ file }) {
return await $`python3 analyze.py ${file}`.text()
}
})
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Get current session info",
args: {},
async execute(args, context) {
return JSON.stringify({
session: context.sessionID,
message: context.messageID,
agent: context.agent
}, null, 2)
}
})
Tool not appearing:
.opencode/tool/ or ~/.config/opencode/tool/Schema errors:
.optional()Execution errors:
execute returns a stringFor detailed API documentation:
references/sdk-api.md - Complete SDK client API referencereferences/plugin-api.md - Full plugin hooks and typesWorking examples in examples/:
examples/basic-tool.ts - Simple tool implementationexamples/full-plugin.ts - Complete plugin with hooks