Technical reference for OpenCode's tool system.
OpenCode combines built-in tools, file-based custom tools, and plugins.
- bash - Command execution with security restrictions
- edit - File modification with surgical precision
- webfetch - Web content fetching and analysis
- glob - File pattern matching
- grep - Content search using regex
- list - Directory listing
- read - File reading with smart formatting
- write - File creation
- todo (read/write) - Task management
- task - Complex task delegation
- patch - Advanced file patching
- invalid - Error handling tool
Each tool has .ts implementation and .txt description files.
- Global:
~/.config/opencode/tool/*.{js,ts} - Project:
.opencode/tool/*.{js,ts}(searches up directory tree)
- Default export: Tool named after file
- Named exports:
{filename}_{exportName}
Tools from @opencode-ai/plugin interface implementations.
export namespace Tool {
export type Context<M extends Metadata = Metadata> = {
sessionID: string;
messageID: string;
agent: string;
abort: AbortSignal;
callID?: string;
extra?: { [key: string]: any };
metadata(input: { title?: string; metadata?: M }): void;
};
export interface Info<
Parameters extends z.ZodType = z.ZodType,
M extends Metadata = Metadata,
> {
id: string;
init: () => Promise<{
description: string;
parameters: Parameters;
execute(args: z.infer<Parameters>, ctx: Context): Promise<{
title: string;
metadata: M;
output: string;
}>;
}>;
}
}export type ToolContext = {
sessionID: string;
messageID: string;
agent: string;
abort: AbortSignal;
};
export function tool<Args extends z.ZodRawShape>(input: {
description: string;
args: Args;
execute(
args: z.infer<z.ZodObject<Args>>,
context: ToolContext,
): Promise<string>;
}) {
return input;
}export type PluginInput = {
client: ReturnType<typeof createOpencodeClient>;
project: Project;
directory: string;
worktree: string;
$: BunShell; // Command execution interface
};
export type Plugin = (input: PluginInput) => Promise<Hooks>;
export interface Hooks {
event?: (input: { event: Event }) => Promise<void>;
config?: (input: Config) => Promise<void>;
tool?: { [key: string]: ToolDefinition };
auth?: {/* OAuth and API key authentication */};
"chat.message"?: (
input: {},
output: { message: UserMessage; parts: Part[] },
) => Promise<void>;
"tool.execute.before"?: (
input: { tool: string; sessionID: string; callID: string },
output: { args: any },
) => Promise<void>;
"tool.execute.after"?: (
input: { tool: string; sessionID: string; callID: string },
output: { title: string; output: string; metadata: any },
) => Promise<void>;
// ... other hooks
}export interface BunShell {
(
strings: TemplateStringsArray,
...expressions: ShellExpression[]
): BunShellPromise;
braces(pattern: string): string[];
escape(input: string): string;
env(newEnv?: Record<string, string | undefined>): BunShell;
cwd(newCwd?: string): BunShell;
nothrow(): BunShell;
}
export interface BunShellPromise extends Promise<BunShellOutput> {
readonly stdin: WritableStream;
quiet(): this;
lines(): AsyncIterable<string>;
text(encoding?: BufferEncoding): Promise<string>;
json(): Promise<any>;
// ... other methods
}export namespace ToolRegistry {
// Built-in tools array
const BUILTIN = [InvalidTool, BashTool, EditTool, WebFetchTool /* ... */];
// Dynamic loading
export const state = Instance.state(async () => {
const custom = [] as Tool.Info[];
// Load file-based tools
for (const dir of await Config.directories()) {
for await (
const match of new Bun.Glob("tool/*.{js,ts}").scan({
cwd: dir,
absolute: true,
})
) {
const namespace = path.basename(match, path.extname(match));
const mod = await import(match);
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
custom.push(
fromPlugin(
id === "default" ? namespace : `${namespace}_${id}`,
def,
),
);
}
}
}
// Load plugin tools
const plugins = await Plugin.list();
for (const plugin of plugins) {
for (const [id, def] of Object.entries(plugin.tool ?? {})) {
custom.push(fromPlugin(id, def));
}
}
return { custom };
});
}- Tool registration - Discovery and loading into registry
- Call initiation - LLM requests execution with validated parameters
- Context creation - Session/message IDs, abort signal, metadata function
- Hook execution -
tool.execute.beforehooks called - Tool execution - Execute function called with args and context
- Result processing - Output processed and formatted
- Hook completion -
tool.execute.afterhooks called - State update - Tool state updated to completed/error
- Descriptive names - Tool IDs should clearly indicate purpose
- Input validation - Use Zod schemas for parameter validation
- Error handling - Provide meaningful error messages
- Cancellation support - Check abort signal for long operations
- Progress reporting - Use metadata function for status updates
- Lifecycle hooks - Use before/after hooks for logging/monitoring
- Shell security - Validate commands to prevent security issues
- Resource management - Clean up resources on abort
- Testing - Test tools in isolation and integrated
- Permission awareness - Design tools for OpenCode's permission system
- Context usage - Use session/message context appropriately
- Performance - Consider impact on chat response times
- Compatibility - Ensure tools work across OpenCode versions
This architecture provides extensible toolsets while maintaining security, performance, and user experience.