03. Criando plugins¶
🎯 Objetivo¶
Escrever plugins que adicionam ferramentas, hooks e transformações ao OpenCode, e expor métodos/eventos via RPC.
📋 Estrutura básica¶
import { Plugin } from "@opencode/plugin"
export default Plugin.define({
id: "example",
async setup(ctx) {
await ctx.storage.set("loaded", true)
},
})
Plugins em .opencode/plugins/ carregam automaticamente. setup roda ao carregar e pode retornar uma função de cleanup. O ctx é essencialmente um cliente do servidor OpenCode mais métodos exclusivos de plugin.
➕ Adicionar uma ferramenta¶
import { Plugin } from "@opencode/plugin"
export default Plugin.define({
id: "acme",
async setup(ctx) {
const registration = await ctx.tool.transform((editor) => {
editor.namespace({ name: "acme", description: "Ferramentas da conta do cliente" })
editor.add({
name: "greeting",
description: "Cria uma saudação",
input: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
additionalProperties: false,
},
options: { namespace: "acme", codemode: true },
execute: async (input, context) => {
await context.progress({ status: "greeting" })
return { content: `Olá ${(input as { name: string }).name}!` }
},
})
})
return () => registration.dispose()
},
})
- Passe
context.signalpara trabalho cancelável (ex.:fetch) - Dispor a registration remove a ferramenta e reaplica as demais
🔧 Transformações (agents, providers, models, commands, MCP…)¶
Cada domínio tem transform + reload. Exemplos:
// agentes
await ctx.agent.transform((editor) => {
editor.update("build", (agent) => {
agent.description = "Implementa features e corrige bugs"
})
})
// modelos: remove modelos caros
await ctx.model.transform((editor) => {
editor.list().filter((m) => m.cost.some((t) => t.output > 20))
.forEach((m) => editor.remove(m.providerID, m.id))
})
// MCP
await ctx.mcp.transform((editor) => {
editor.set("docs", { type: "remote", url: "https://mcp.example.com" })
})
Domínios disponíveis: agent, provider, model, command, integration, mcp, reference, skill, tool, vcs, worktree, websearch, credential.
🪝 Hooks¶
Interceptam operações ao vivo. Exemplos:
// redige segredos em prompts de entrada
await ctx.session.hook("prompt", (event) => {
event.prompt.text = event.prompt.text.replaceAll("company-secret", "[redacted]")
})
// ajusta a requisição ao modelo
await ctx.session.hook("context", (event) => {
event.system.push({ type: "text", text: "Foque em correção." })
event.options.temperature = 0.2
})
Hooks de sessão incluem prompt, context, compaction, generate, title, model.request, http.request, http.response.
🔌 RPC: métodos e eventos entre plugins/clientes¶
import { Rpc } from "@opencode/plugin/rpc"
export const Acme = Rpc.define({
id: "acme",
methods: {
search: {
input: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
output: { type: "object", properties: { text: { type: "string" } }, required: ["text"] },
},
},
events: {
updated: { schema: { type: "object", properties: { itemID: { type: "string" } } } },
},
})
Registrar dentro do setup e chamar de outro plugin ou via HTTP:
const registration = await ctx.rpc.register(Acme, {
search: async (input, context) => ({ text: await findText((input as any).query, { signal: context.signal }) }),
})
await registration.events.emit("updated", { itemID: "item-1" })
Cliente HTTP: client.rpc(Acme).search({ query: "hello" }) ou POST /api/rpc/acme/search com { "input": ... }.
📦 Publicar¶
Empacote como um pacote npm (@acme/opencode-plugin) com entrypoint exportando o Plugin.define(...). Habilite com opencode plugin add.
💡 Exemplos¶
exemplos/plugins/greeting/index.ts— plugin com uma ferramentaexemplos/plugins/acme-rpc.ts— plugin com RPC (métodos + eventos)
❓ Desafio prático¶
- Crie um plugin com uma ferramenta
git_branch_nameque retorna a branch atual - Adicione um hook
promptque acrescenta "responda em português" - Exponha um método RPC e chame-o de um cliente
- Publique localmente e carregue via
pluginsnoopencode.json
📚 Fonte oficial¶
Próximo passo: Aula 04 — Busca web · Voltar ao módulo