Saudações.
Esse é o primeiro tutorial de uma série de plugins para OpenCode.
Nesse vou compartilhar um plugin que me ajuda muito na configuração do OpenCode em conjunto com provedores de modelos baseados no SDK OpenAI.
A versão nova, OpenCode 2, implementou novas técnicas, incluindo plugins que podem se intrometer nos processos internos do OpenCode e resolver problemas de engessamento dos recursos oficiais.
1 – Problema
O OpenCode tem uma limitação chata: ele não aprende automaticamente quais modelos um provedor oferece.
A lista de modelos é “hard-coded” em cada módulo que lida com um provedor suportado oficialmente, e somente 3 módulos tem atualização de modelos automática:
- Ollama;
- LM Studio;
- vLLM.
Exemplo: Via conta oficial da Anthropic, ele dispõe dos modelos “Fable”, “Opus”, “Sonnet” e “Haiku” nas versões correntes. Se uma nova versão de modelo e/ou novo nome de modelo é lançada, o OpenCode também lança uma nova versão contendo essa lista oficial pinada.
Ao meu ver, o problema mais grave pois uso o Manifest e o LiteLLM Proxy para rotear modelos entre vários provedores.
Manipular a configuração JSON do OC com os modelos copiados da lista atualizada no roteador para colocar no agente é MUITO CHATO, extremamente irritante. Criar um software paralelo é mais coisa pra tomar conta.
2 – Objetivo
A missão é deixar o OpenCode funcionando com configuração mínima e com aprendizado de modelos disponíveis no roteador.
Colocar um OpenCode em produção e esquecer que ele existe, sem a preocupação que um dia ele vai parar porque o nome do modelo sumiu.
3 – Solução A – Usando profiles de harness
A primeira solução que encontrei é resolver no Manifest e criar uma conta harness para cada modelo que o OpenCode irá enxergar.
- Harness “OpenCode-Ultron” onde o routing aponta para:
- Principal: “Claude Fable 5.1“;
- Fallback: “GPT Astra 6“;
- Fallback: “GPT Astra 6“;
- Harness “OpenCode-Thanos” onde o routing aponta para:
- Principal: “Opus 5.5“;
- Fallback: “GPT Sol 6.1“;
- Fallback: “Gemini 2.5 Pro“;
- Harness “OpenCode-Loki“, onde o routing aponta para:
- Principal: “Claude Sonnet 5.5“;
- Fallback: “GPT Terra 5.6“;
- Fallback: “Gemini 2.5 Flash-Lite“;
- Harness “OpenCode-Caveira“, onde o routing aponta para:
- Principal: “Claude Haiku 5.5“;
- Fallback: “GPT Luna 6“;
- Fallback: “Gemini 2.5 Flash-Lite“;
Mas nisso, eu tenho que cadastrar os 4 provedores no OpenCode, perdendo a capacidade de dar a ele comandos remotos do tipo:
- “Crie um sub-agente usando o modelo deepseek 4.1 flash para investigar falhas de segurança no software XYZ“;
- “Faça uma pesquisa de todos os modelos disponíveis localmente e me diga qual tem melhor custo benefício para resolver problemas em lote baseado no arquivo chamados.csv“.
Ou seja, é uma solução, mas carece de elegância:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"manifest-ultron": {
"npm": "@ai-sdk/openai-compatible",
"name": "Manifest-Ultron",
"options": {
"baseURL": "https://manifest.dominio.com/v1",
"apiKey": "mnfst_<chave-especifica-do-harness>"
},
"models": {
"auto": {
"name": "Manifest Ultron"
}
}
},
"manifest-thanos": {
"npm": "@ai-sdk/openai-compatible",
"name": "Manifest-Thanos",
"options": {
"baseURL": "https://manifest.dominio.com/v1",
"apiKey": "mnfst_<chave-especifica-do-harness>"
},
"models": {
"auto": {
"name": "Manifest Thanos"
}
}
},
"manifest-loki": {
"npm": "@ai-sdk/openai-compatible",
"name": "Manifest-Loki",
"options": {
"baseURL": "https://manifest.dominio.com/v1",
"apiKey": "mnfst_<chave-especifica-do-harness>"
},
"models": {
"auto": {
"name": "Manifest Loki"
}
}
},
"manifest-caveira": {
"npm": "@ai-sdk/openai-compatible",
"name": "Manifest-Caveira",
"options": {
"baseURL": "https://manifest.dominio.com/v1",
"apiKey": "mnfst_<chave-especifica-do-harness>"
},
"models": {
"auto": {
"name": "Manifest Caveira"
}
}
}
},
"model": "manifest-thanos/auto"
}4 – Solução B – Usando plugin
Esse plugin irá manter a base de modelos e fornecê-los a partir de uma lista obtida por fora, anexada ao provedor, aqui chamo de “manifest-models“.
O link que proverá a lista de modelos é “https://manifest.dominio.com/“.
Personalize para seu caso, o melhor caminho é definir a env “MANIFEST_BASE_URL“.
Vamos adicionar um plugin para esse recurso.
# Diretorio do plugin
mkdir -p ~/.config/opencode/manifest-models;
# Codigo do plugin
touch ~/.config/opencode/manifest-models/index.ts
Código do plugin em ~/.config/opencode/manifest-models/index.ts
import { Plugin, Model } from "@opencode/plugin"
const PROVIDER = "manifest"
const INTERVAL_MS = 5 * 60_000
type Entry = {
id: string
owned_by: string
capabilities?: { features?: string[]; input_modalities?: string[] }
cost?: { input?: number; output?: number }
}
export default Plugin.define({
id: "manifest-models",
setup: async (ctx) => {
const cfg = (ctx.options ?? {}) as { baseURL?: string; apiKey?: string }
const baseURL = (cfg.baseURL ?? process.env.MANIFEST_BASE_URL ?? "https://manifest.dominio.com/v1").replace(/\/+$/, "")
const apiKey = cfg.apiKey ?? process.env.MANIFEST_API_KEY ?? ""
let models: Entry[] = []
let hash = ""
const fetchModels = async (): Promise<Entry[]> => {
const res = await fetch(`${baseURL}/models?capabilities=true&cost=true`, {
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
})
if (!res.ok) throw new Error(`manifest /v1/models -> HTTP ${res.status}`)
const body = (await res.json()) as { data: Entry[] }
return body.data.filter((m) => typeof m.id === "string" && m.id.length > 0)
}
await ctx.provider.transform((editor) => {
for (const m of models) {
editor.models.update(PROVIDER, m.id, (model) => {
model.name = m.id
const feats = m.capabilities?.features ?? []
const inputs = m.capabilities?.input_modalities ?? ["text"]
model.capabilities = {
tools: feats.length ? feats.includes("tools") : true,
input: inputs.includes("image") ? ["text", "image"] : ["text"],
output: ["text"],
}
if (m.cost?.input !== undefined && m.cost?.output !== undefined) {
// Os campos de custo são números "branded" (Money.USDPerMillionTokens); o cast satisfaz o tsc.
model.cost = [{ input: m.cost.input, output: m.cost.output, cache: { read: 0, write: 0 } } as Model.Cost]
}
})
}
})
const refresh = async () => {
try {
const next = await fetchModels()
const h = JSON.stringify(next.map((m) => m.id))
if (h === hash) return
models = next
hash = h
await ctx.provider.reload()
} catch (e) {
console.error("[manifest-models]", e)
}
}
await refresh()
const timer = setInterval(refresh, INTERVAL_MS)
return () => clearInterval(timer)
},
})Agora declare em ~/.config/opencode/opencode.json:
- O provedor “manifest“;
- O plugin “manifest-models“;
- Note que a chave de API é declarada duas vezes, no provedor e no plugin.
{
"providers": {
"manifest": {
"package": "aisdk:@ai-sdk/openai-compatible",
"name": "Manifest",
"settings": {
"baseURL": "https://manifest.dominio.com/v1",
"apiKey": "mnfst_sua_chave_aqui_jamais_compartilhe"
},
"models": {
"auto": { "name": "Manifest Auto" }
}
}
},
"model": "manifest/auto",
"plugins": [
{
"package": "./manifest-models",
"options": {
"baseURL": "https://manifest.dominio.com/v1",
"apiKey": "mnfst_sua_chave_aqui_jamais_compartilhe"
}
}
]
}Reinicie o OpenCode 2 e consulte os modelos aprendidos dinamicamente:
# Listar modelos disponiveis para uso:
opencode models;
# manifest/anthropic/claude-sonnet-5-5-subscription
# manifest/anthropic/claude-opus-5-5-subscription
# manifest/anthropic/claude-haiku-5-5-subscription
# ...
# manifest/auto
# manifest/openrouter/free
# ...
Terminamos por hoje!
Patrick Brandão, patrickbrandao@gmail.com
“Não podemos resolver problemas com
o mesmo nível de pensamento
que usamos para criá-los“
Albert Einstein
