Overview
OpenCode plugins extend the functionality of the OpenCode AI assistant by allowing you to add custom tools, authentication providers, event handlers, and hooks into the core system. This skill guides you through creating, configuring, and distributing plugins.
When to Use This Skill
Use this skill when:
- •Creating a new OpenCode plugin
- •Adding custom tools for the AI to use
- •Implementing authentication providers
- •Responding to system events
- •Modifying chat parameters or permissions
- •Testing or debugging plugins
Plugin Structure
A plugin is a TypeScript module that exports a function conforming to the Plugin type:
import { Plugin, tool } from '@opencode-ai/plugin'
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
myTool: tool({
description: 'Custom tool',
args: { input: tool.schema.string() },
execute: async (args) => `Result: ${args.input}`,
}),
},
auth: {
provider: 'myservice',
methods: [{ type: 'api', label: 'API Key' }],
},
event: async ({ event }) => console.log(event.type),
config: async (config) => (config.myPlugin = { enabled: true }),
'chat.message': async ({}, { message }) => console.log(message.content),
'chat.params': async (
{ model, provider, message },
{ temperature, topP, options },
) => {
temperature = 0.7
options.custom = 'value'
},
'permission.ask': async (perm, out) => (out.status = 'allow'),
'tool.execute.before': async ({ tool }, { args }) => (args.modified = true),
'tool.execute.after': async ({ tool }, { title, output, metadata }) => {
console.log(`Tool ${tool} completed:`, output)
},
}
}
Plugin Context (ctx) API
// Plugin Context (ctx) API Overview
ctx.client // Opencode SDK client (localhost:4096)
ctx.project.id // Project identifier (git hash or "global")
ctx.project.worktree // Git worktree root directory
ctx.project.vcs // Version control system ("git" or undefined)
ctx.directory // Current working directory
ctx.worktree // Git worktree root (alias for ctx.project.worktree)
ctx.$`command` // Bun shell for executing commands
ctx.$`git status`.text() // Shell command with output methods
Project Setup
1. Create Plugin Package
mkdir my-opencode-plugin cd my-opencode-plugin bun init
2. Install Dependencies
{
"dependencies": {
"@opencode-ai/plugin": "latest",
"@opencode-ai/sdk": "latest",
"zod": "latest"
},
"devDependencies": {
"@types/node": "latest",
"typescript": "latest"
}
}
3. Configure TypeScript
{
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"module": "preserve",
"declaration": true,
"moduleResolution": "bundler"
},
"include": ["src"]
}
4. Create Plugin Source
// src/index.ts
import { Plugin, tool } from '@opencode-ai/plugin'
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
hello: tool({
description: 'Say hello',
args: {
name: tool.schema.string().describe('Name to greet'),
},
async execute({ name }) {
return `Hello, ${name}!`
},
}),
},
}
}
Plugin Hooks Reference
Tool Definition
Add custom tools that the AI can use:
import { tool } from '@opencode-ai/plugin'
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
mytool: tool({
description: 'This is a custom tool',
args: {
foo: tool.schema.string().describe('foo parameter'),
count: tool.schema.number().optional().describe('optional count'),
},
async execute(args, context) {
// context includes: sessionID, messageID, agent, abort
return `Hello ${args.foo}! Count: ${args.count || 1}`
},
}),
},
}
}
Authentication Providers
Add custom authentication methods:
export const MyPlugin: Plugin = async (ctx) => {
return {
auth: {
provider: 'myservice',
loader: async (auth, provider) => {
// Load authentication configuration
return { apiKey: 'loaded-key' }
},
methods: [
{
type: 'oauth',
label: 'Connect MyService',
async authorize() {
return {
url: 'https://myservice.com/oauth/authorize',
instructions: 'Authorize Opencode to access MyService',
method: 'code',
async callback(code) {
// Handle OAuth callback
return {
type: 'success',
access: 'access-token',
refresh: 'refresh-token',
expires: Date.now() + 3600000,
}
},
}
},
},
],
},
}
}
Event Handlers
Respond to system events:
export const MyPlugin: Plugin = async (ctx) => {
return {
event: async ({ event }) => {
console.log('Event received:', event.type)
},
}
}
Chat Message Hooks
Intercept and modify chat messages:
export const MyPlugin: Plugin = async (ctx) => {
return {
'chat.message': async ({}, { message, parts }) => {
// Modify message before sending to LLM
console.log('Message:', message.content)
},
}
}
Chat Parameter Modification
Modify LLM parameters:
export const MyPlugin: Plugin = async (ctx) => {
return {
'chat.params': async (
{ model, provider, message },
{ temperature, topP, options },
) => {
// Adjust parameters based on context
temperature = 0.7
options.customParam = 'value'
},
}
}
Permission Control
Control permission requests:
export const MyPlugin: Plugin = async (ctx) => {
return {
'permission.ask': async (permission, output) => {
// Auto-allow certain permission types
if (permission.type === 'read_file') {
output.status = 'allow'
}
},
}
}
Tool Execution Hooks
Intercept tool execution:
export const MyPlugin: Plugin = async (ctx) => {
return {
'tool.execute.before': async ({ tool, sessionID, callID }, { args }) => {
// Modify arguments before execution
if (tool === 'mytool') {
args.modified = true
}
},
'tool.execute.after': async (
{ tool, sessionID, callID },
{ title, output, metadata },
) => {
// Process tool results
console.log(`Tool ${tool} executed:`, output)
},
}
}
Configuration Hook
Modify Opencode configuration:
export const MyPlugin: Plugin = async (ctx) => {
return {
config: async (config) => {
// Add configuration options
config.myPlugin = { enabled: true }
},
}
}
Shell Integration
Plugins have access to a Bun shell for executing commands:
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
gitStatus: tool({
description: 'Get git status',
args: {},
async execute() {
const result = await ctx.$`git status --porcelain`
return result.text()
},
}),
},
}
}
Events Reference
Session Events:
- •
session.created- New session created - •
session.updated- Session updated - •
session.deleted- Session deleted - •
session.error- Session error occurred - •
session.idle- Session became idle
Message Events:
- •
message.updated- Message updated - •
message.removed- Message removed - •
message.part.updated- Message part updated - •
message.part.removed- Message part removed
File Events:
- •
file.edited- File was edited - •
file.watcher.updated- File watcher detected changes (add/change/unlink)
Permission Events:
- •
permission.updated- Permission updated - •
permission.replied- Permission response received
Server Events:
- •
server.connected- Server connected
LSP Events:
- •
lsp.updated- Language Server Protocol updated - •
lsp.diagnostics- LSP diagnostics available
Command Events:
- •
command.executed- Command executed
TUI Events:
- •
tui.prompt.append- Text appended to TUI prompt - •
tui.command.execute- Command executed in TUI - •
tui.toast.show- Toast shown in TUI
Other Events:
- •
installation.updated- Installation updated - •
ide.installed- IDE extension installed
Plugin Configuration
Local Development
Add your plugin to opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["file:///path/to/your/plugin/dist/index.js"]
}
Published Plugins
For published npm packages:
{
"plugin": ["my-opencode-plugin@1.0.0"]
}
Multiple Plugins
{
"plugin": [
"plugin-one@latest",
"plugin-two@2.0.0",
"file:///path/to/local/plugin"
]
}
Building and Publishing
Use the prefix opencode- for plugin names:
- •
opencode-my-service - •
opencode-custom-tools
Best Practices
1. Error Handling
Always handle errors gracefully:
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
riskyTool: tool({
description: 'Tool that might fail',
args: {},
async execute() {
try {
const result = await ctx.$`some-command`
return result.text()
} catch (error) {
return `Error: ${error.message}`
}
},
}),
},
}
}
2. Async Operations
All plugin hooks are async - use proper async/await:
export const MyPlugin: Plugin = async (ctx) => {
return {
event: async ({ event }) => {
await processEvent(event)
},
}
}
3. Type Safety
Leverage TypeScript for type safety:
import { z } from 'zod'
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
typedTool: tool({
description: 'Tool with typed arguments',
args: {
url: tool.schema.string().url().describe('Valid URL'),
count: tool.schema.number().min(1).max(100).describe('Count 1-100'),
},
async execute(args) {
// args are fully typed
return `Processing ${args.url} ${args.count} times`
},
}),
},
}
}
4. Resource Management
Clean up resources when needed:
export const MyPlugin: Plugin = async (ctx) => {
const cleanup = setupResource()
return {
event: async ({ event }) => {
if (event.type === 'shutdown') {
await cleanup()
}
},
}
}
Testing Plugins
Unit Testing
// src/index.test.ts
import { describe, it, expect } from 'bun:test'
import { MyPlugin } from './index'
describe('MyPlugin', () => {
it('should register tools', async () => {
const mockCtx = createMockContext()
const hooks = await MyPlugin(mockCtx)
expect(hooks.tool).toBeDefined()
expect(hooks.tool.hello).toBeDefined()
})
})
Integration Testing
Test with actual Opencode instance:
# Link local plugin for testing bun link cd /path/to/opencode/project bun link my-opencode-plugin # Add to opencode.json and test
Debugging
Logging
Use console.log for debugging:
export const MyPlugin: Plugin = async (ctx) => {
console.log('Plugin loading with context:', ctx.project.name)
return {
tool: {
debugTool: tool({
description: 'Debug tool',
args: {},
async execute() {
console.log('Debug tool executed')
return 'Debug complete'
},
}),
},
}
}
Plugin Loading Issues
Check plugin loading with:
opencode --verbose
Recipe: Sending Session Prompt
// Send a message from a tool to the session
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
sendPrompt: tool({
description: 'Send a prompt to the current session',
args: {
text: tool.schema.string().describe('Text to send'),
},
async execute(args, toolCtx) {
ctx.client.session.prompt({
path: { id: toolCtx.sessionID },
body: {
noReply: true,
parts: [{ type: 'text', text: args.text }],
},
})
return 'Prompt sent'
},
}),
},
}
}
Examples
File System Plugin
export const FileSystemPlugin: Plugin = async (ctx) => {
return {
tool: {
listFiles: tool({
description: 'List files in directory',
args: {
path: tool.schema.string().describe('Directory path'),
},
async execute({ path }) {
const result = await ctx.$`ls -la ${path}`
return result.text()
},
}),
readFile: tool({
description: 'Read file contents',
args: {
path: tool.schema.string().describe('File path'),
},
async execute({ path }) {
const file = Bun.file(path)
return await file.text()
},
}),
},
}
}
API Integration Plugin
export const APIPlugin: Plugin = async (ctx) => {
return {
tool: {
fetchAPI: tool({
description: 'Fetch data from API',
args: {
url: tool.schema.string().url().describe('API URL'),
method: tool.schema.enum(['GET', 'POST']).default('GET'),
},
async execute({ url, method }) {
const response = await fetch(url, { method })
return await response.text()
},
}),
},
}
}