Documentation Index
Fetch the complete documentation index at: https://mintlify.com/anomalyco/opencode/llms.txt
Use this file to discover all available pages before exploring further.
Overview
OpenCode’s plugin system allows you to extend functionality by:
- Creating custom tools for the AI agent
- Hooking into the chat lifecycle
- Modifying prompts and parameters
- Handling authentication for custom providers
- Responding to events
Plugins are TypeScript/JavaScript modules that export a plugin function returning hooks.
Installation
Install the plugin SDK:
npm install @opencode-ai/plugin
Basic Plugin
Create a plugin file (e.g., my-plugin.ts):
import { Plugin } from '@opencode-ai/plugin'
export const MyPlugin: Plugin = async (ctx) => {
// ctx provides access to client, project, directory, etc.
return {
// Return hooks and tools
tool: {
// Custom tools
},
event: async (input) => {
// Handle events
},
}
}
The plugin function receives a context object:
type PluginInput = {
client: OpencodeClient // SDK client instance
project: Project // Current project
directory: string // Project directory
worktree: string // Project worktree root
serverUrl: URL // Server URL
$: BunShell // Shell for running commands
}
Using the Context
export const MyPlugin: Plugin = async (ctx) => {
// Access the SDK client
const config = await ctx.client.config.get()
console.log('Current model:', config.data.model)
// Get project info
console.log('Project:', ctx.project.id)
console.log('Directory:', ctx.directory)
// Run shell commands
const result = await ctx.$`git status`
console.log(result.stdout.toString())
return {
// ... hooks
}
}
Registering Plugins
Add your plugin to opencode.json:
{
"plugin": [
"./my-plugin.ts",
"@company/opencode-plugin"
]
}
Or programmatically:
import { createOpencode } from '@opencode-ai/sdk'
const { client, server } = await createOpencode({
config: {
plugin: ['./my-plugin.ts'],
},
})
Tools are functions that the AI agent can call. Use the tool() helper to define them:
import { Plugin, tool } from '@opencode-ai/plugin'
export const MyPlugin: Plugin = async (ctx) => {
return {
tool: {
search_database: tool({
description: 'Search the database for records',
args: {
query: tool.schema.string().describe('Search query'),
limit: tool.schema.number().optional().describe('Max results'),
},
async execute(args, context) {
// args.query and args.limit are typed
const results = await searchDB(args.query, args.limit)
return JSON.stringify(results)
},
}),
},
}
}
function tool<Args extends z.ZodRawShape>(input: {
description: string
args: Args
execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<string>
})
Tool Context
The execute function receives a context object:
type ToolContext = {
sessionID: string // Current session
messageID: string // Current message
agent: string // Current agent name
directory: string // Project directory
worktree: string // Project worktree root
abort: AbortSignal // Cancellation signal
// Update tool metadata
metadata(input: {
title?: string
metadata?: Record<string, any>
}): void
// Request permission
ask(input: {
permission: string
patterns: string[]
always: string[]
metadata: Record<string, any>
}): Promise<void>
}
Use Zod for argument validation:
import { tool } from '@opencode-ai/plugin'
tool({
description: 'Example tool with various argument types',
args: {
// String
name: tool.schema.string().describe('User name'),
// Number
age: tool.schema.number().min(0).max(150).describe('User age'),
// Boolean
active: tool.schema.boolean().describe('Is active'),
// Optional
email: tool.schema.string().email().optional().describe('Email address'),
// Enum
role: tool.schema.enum(['admin', 'user', 'guest']).describe('User role'),
// Array
tags: tool.schema.array(tool.schema.string()).describe('Tags'),
// Object
metadata: tool.schema.object({
key: tool.schema.string(),
value: tool.schema.string(),
}).describe('Metadata'),
},
async execute(args, context) {
// args is fully typed
return 'Result'
},
})
See Plugin Tools for detailed tool documentation.
Hooks
Plugins can implement various hooks to customize behavior:
Event Hook
Listen to all server events:
export const MyPlugin: Plugin = async (ctx) => {
return {
event: async (input) => {
const { event } = input
if (event.type === 'session.created') {
console.log('New session:', event.properties.info.id)
}
if (event.type === 'message.updated') {
console.log('Message updated:', event.properties.info.id)
}
},
}
}
Config Hook
Modify configuration:
export const MyPlugin: Plugin = async (ctx) => {
return {
config: async (config) => {
console.log('Config loaded:', config.model)
// Can modify config here
},
}
}
Chat Hooks
chat.message
Called when a new message is received:
export const MyPlugin: Plugin = async (ctx) => {
return {
'chat.message': async (input, output) => {
const { sessionID, agent, model } = input
const { message, parts } = output
console.log(`Message in session ${sessionID}:`)
console.log(`Agent: ${agent}`)
console.log(`Model: ${model?.providerID}/${model?.modelID}`)
console.log(`Parts: ${parts.length}`)
},
}
}
chat.params
Modify LLM parameters:
export const MyPlugin: Plugin = async (ctx) => {
return {
'chat.params': async (input, output) => {
const { agent, model } = input
// Adjust temperature based on agent
if (agent === 'build') {
output.temperature = 0.7
} else if (agent === 'plan') {
output.temperature = 0.3
}
// Add custom options
output.options.customParam = 'value'
},
}
}
Add custom headers to LLM requests:
export const MyPlugin: Plugin = async (ctx) => {
return {
'chat.headers': async (input, output) => {
output.headers['X-Custom-Header'] = 'value'
output.headers['X-Session-ID'] = input.sessionID
},
}
}
Permission Hook
Control permission requests:
export const MyPlugin: Plugin = async (ctx) => {
return {
'permission.ask': async (permission, output) => {
// Auto-approve certain patterns
if (permission.type === 'bash' && permission.pattern?.includes('npm')) {
output.status = 'allow'
}
// Deny dangerous operations
if (permission.pattern?.includes('rm -rf')) {
output.status = 'deny'
}
},
}
}
Command Hook
Run code before command execution:
export const MyPlugin: Plugin = async (ctx) => {
return {
'command.execute.before': async (input, output) => {
const { command, sessionID, arguments: args } = input
console.log(`Executing command: ${command} ${args}`)
// Add context parts
output.parts.push({
type: 'text',
text: `Additional context for ${command}`,
})
},
}
}
Called before tool execution:
export const MyPlugin: Plugin = async (ctx) => {
return {
'tool.execute.before': async (input, output) => {
const { tool, sessionID, callID } = input
console.log(`Tool ${tool} called`)
// Modify arguments
output.args.modified = true
},
}
}
Called after tool execution:
export const MyPlugin: Plugin = async (ctx) => {
return {
'tool.execute.after': async (input, output) => {
const { tool, args } = input
// Modify output
output.output += '\n\nProcessed by plugin'
output.metadata.processed = true
},
}
}
Modify tool definitions sent to the LLM:
export const MyPlugin: Plugin = async (ctx) => {
return {
'tool.definition': async (input, output) => {
if (input.toolID === 'bash') {
// Make bash tool description more specific
output.description += ' Use this for running shell commands.'
}
},
}
}
Shell Environment Hook
Customize shell environment:
export const MyPlugin: Plugin = async (ctx) => {
return {
'shell.env': async (input, output) => {
// Add custom environment variables
output.env.CUSTOM_VAR = 'value'
output.env.PATH = `/custom/path:${output.env.PATH}`
},
}
}
Auth Hook
Handle authentication for custom providers:
export const MyPlugin: Plugin = async (ctx) => {
return {
auth: {
provider: 'my-provider',
methods: [
{
type: 'api',
label: 'API Key',
prompts: [
{
type: 'text',
key: 'apiKey',
message: 'Enter your API key',
validate: (value) => {
if (!value.startsWith('sk-')) {
return 'Invalid API key format'
}
},
},
],
async authorize(inputs) {
// Validate the API key
const isValid = await validateKey(inputs.apiKey)
if (isValid) {
return { type: 'success', key: inputs.apiKey }
}
return { type: 'failed' }
},
},
{
type: 'oauth',
label: 'OAuth',
async authorize() {
const authUrl = 'https://auth.example.com'
return {
url: authUrl,
instructions: 'Open this URL to authenticate',
method: 'auto',
async callback() {
// Handle OAuth callback
const tokens = await waitForCallback()
return {
type: 'success',
refresh: tokens.refresh,
access: tokens.access,
expires: tokens.expires,
}
},
}
},
},
],
},
}
}
Experimental Hooks
These hooks may change in future versions:
Transform messages before sending to LLM:
export const MyPlugin: Plugin = async (ctx) => {
return {
'experimental.chat.messages.transform': async (input, output) => {
// Modify messages array
output.messages = output.messages.filter(
(msg) => msg.info.role === 'user'
)
},
}
}
Transform system prompt:
export const MyPlugin: Plugin = async (ctx) => {
return {
'experimental.chat.system.transform': async (input, output) => {
// Add to system prompt
output.system.push('Additional system instructions')
},
}
}
experimental.session.compacting
Customize session compaction:
export const MyPlugin: Plugin = async (ctx) => {
return {
'experimental.session.compacting': async (input, output) => {
// Add context for compaction
output.context.push('Important context to preserve')
// Or replace the entire prompt
output.prompt = 'Custom compaction prompt'
},
}
}
Complete Example
Here’s a complete plugin with multiple features:
import { Plugin, tool } from '@opencode-ai/plugin'
export const DatabasePlugin: Plugin = async (ctx) => {
// Initialize database connection
const db = await connectDB(ctx.directory)
return {
// Custom tools
tool: {
query_db: tool({
description: 'Query the database',
args: {
sql: tool.schema.string().describe('SQL query'),
},
async execute(args, context) {
const results = await db.query(args.sql)
return JSON.stringify(results, null, 2)
},
}),
},
// Listen to events
event: async (input) => {
if (input.event.type === 'session.created') {
// Log new sessions to database
await db.logSession(input.event.properties.info)
}
},
// Customize chat parameters
'chat.params': async (input, output) => {
// Use lower temperature for database queries
if (input.agent === 'database') {
output.temperature = 0.1
}
},
// Add custom context
'command.execute.before': async (input, output) => {
if (input.command === 'query') {
// Add database schema context
const schema = await db.getSchema()
output.parts.push({
type: 'text',
text: `Database schema:\n${schema}`,
})
}
},
}
}
Next Steps
Plugin Tools
Detailed tool creation guide
Plugin Examples
Real-world plugin examples