CH 19 · Três Formas de Plugin: Função / Objeto / Classe
Objetivo do Capítulo
O CH 18 foi a forma mais comum de escrever um plugin. Este capítulo cobre totalmente as três formas de plugin (função, objeto, classe) de uma vez: como cada uma se parece, quando usar qual, e por que a forma de classe é "abrindo uma janela pública" — é a chave para colaboração de plugins. Depois de ler este capítulo, você vai saber qual forma um plugin deve usar, em vez de apenas conseguir usar um modelo.
As Três Formas em um Relance
Uma tabela para a visão geral, detalhes detalhados um por um abaixo:
| Forma | Como se parece | Compreensão em uma frase | Quando usar |
|---|---|---|---|
| Função | export function apply(ctx) {} | Você mesmo vai resolver uma coisa | Escolha padrão, noventa por cento dos casos são suficientes |
| Objeto | export default { name, inject, apply(ctx) } | Empacote o nome e o fluxo juntos | Quando você quer dar ao plugin algumas declarações estáticas |
| Classe | export default class extends Service {} | Abra uma janela pública, qualquer um pode vir resolver coisas | Quando você quer que outros plugins chamem suas capacidades |
Um julgamento basta: use função para adicionar capacidades ao Agent; use classe para deixar outros plugins dependerem de você. A forma de objeto está no meio, funcionalmente basicamente igual à função, apenas com mais metadados estáticos como name.
Forma de Função: Você Já Conhece
O hello-plugin do CH 18 é a forma de função:
export function apply(ctx) {
// Registre suas capacidades aqui
}O framework chama apply(ctx) ao carregar o plugin, entregando a você o contexto. A grande maioria dos plugins — registrar ferramentas, escutar eventos, montar timers — este basta. Até que você tenha certeza de que será dependido por outros plugins, sempre escreva a forma de função primeiro.
Forma de Objeto: Empacote o Nome e o Fluxo
A forma de objeto é colocar name, inject, apply em um objeto e exportar juntos:
export default {
name: 'my-plugin',
inject: ['tools'],
apply(ctx) {
// Aqui ctx.tools está garantido para estar disponível
},
}É quase equivalente à forma de função, a única diferença é organizar as declarações estáticas (nome, dependências) e apply em uma "especificação empacotada". Quando usar? Quando você quer que o "quem sou eu, quais serviços preciso, o que faço" do seu plugin esteja claro de relance, a forma de objeto é mais limpa. Funcionalmente não há nada que a forma de função não possa fazer.
Forma de Classe: Quando o Plugin Quer "Fornecer um Serviço"
Esta seção é o foco deste capítulo. Primeiro estabeleça um conceito:
Um Service é uma capacidade nomeada montada em
ctx. Você usa serviços todos os dias —ctx.tools(ferramentas),ctx.llm(modelo),ctx.agents(subagentes) são todos serviços. Qualquer plugin pode fornecer seus próprios serviços para outros plugins chamarem.
A forma de função/objeto é "vá resolver coisas sozinho"; a forma de classe é abrir uma janela pública, qualquer um pode vir até você resolver coisas. Uma analogia: a forma de função é como você ir a uma sala de serviços resolver seu próprio negócio; a forma de classe é como você abrir uma janela na sala, outros (outros plugins) vêm à sua janela submeter formulários e obter resultados.
Provider: Escreva uma Subclasse de Service
import { Service } from '@deepseek-ai/cordis'
export default class MetricsService extends Service {
constructor(ctx) {
super(ctx, 'metrics') // Registre um serviço chamado metrics
}
record(event, value) {
console.log('[metrics]', event, value)
}
}Dois pontos-chave:
extends Service+super(ctx, 'metrics'): monte o nomemetricsemctx, outros plugins podem acessá-lo viactx.metrics.record(event, value)é o método que este serviço fornece externamente — outros chamandoctx.metrics.record(...)chegarão aqui.
Consumer: Declare Dependências com inject
export const name = 'consumer'
export const inject = ['metrics']
export function apply(ctx) {
ctx.metrics.record('plugin_loaded', 1)
}inject: ['metrics'] declara "eu quero usar o serviço metrics". O framework garante: quando apply executa, os serviços declarados em inject estão definitivamente prontos. Se o serviço não estiver pronto, seu plugin vai esperar e não vai executar cedo.
Um Diagrama para Entender a Colaboração
O provider monta o serviço em ctx, o consumer declara dependências e chama diretamente. Capacidades integradas como tools, llm, agents são essencialmente o mesmo mecanismo — quando você as usa, você é um "consumer".
Prática: Faça Dois Plugins Realmente Conversarem
Acima foi tudo conceito. Agora vamos escrever na prática um provider e um consumer e deixá-los realmente conversar. Em um workspace onde você quer colocar plugins (primeiro cd para lá) crie um diretório:
New-Item -ItemType Directory -Path "service-demo\src" -ForcePasso 1: Instale Dependências no Diretório do Plugin
A forma de classe precisa fazer import do pacote @deepseek-ai/cordis do framework. Seu workspace não tem ele, carregar diretamente vai reportar Cannot find package '@deepseek-ai/cordis'. Então vá para o diretório do plugin e instale primeiro:
cd "E:\software-workspace\DeepSeek harness demo\service-demo" # substitua pelo seu diretório
npm init -y
npm install @deepseek-ai/cordisApós a instalação, modifique package.json e adicione uma linha "type": "module" ao objeto raiz — caso contrário o Node vai adivinhar o formato do módulo toda vez que carregar o plugin e spammar avisos:
{
"name": "service-demo",
"version": "1.0.0",
"type": "module"
}Passo 2: Escreva o Provider
Crie service-demo\src\provider.js:
import { Service } from '@deepseek-ai/cordis'
export default class MetricsService extends Service {
constructor(ctx) {
super(ctx, 'metrics')
}
record(event, value) {
console.log('[metrics]', event, value)
}
}Passo 3: Escreva o Consumer
Crie service-demo\src\consumer.js:
export const name = 'consumer'
export const inject = ['metrics']
export function apply(ctx) {
ctx.metrics.record('plugin_loaded', 1)
}Passo 4: Declare e Verifique
Crie service-demo\cordis.yml (substitua o caminho pelo seu, note que espaços devem ser escritos como %20):
- insert:
- id: provider
name: 'file:///E:/seu-workspace/service-demo/src/provider.js'
- id: consumer
name: 'file:///E:/seu-workspace/service-demo/src/consumer.js'Use o headless para verificação rápida:
cd seu-diretório-de-workspace
dsh --profile headless --patch "./service-demo/cordis.yml" "Just reply: hi"Na saída você verá:
[metrics] plugin_loaded 1
hi[metrics] plugin_loaded 1 é o consumer chamando o método do provider — seus dois plugins realmente conversaram. Neste ponto, você verificou pessoalmente todas as três formas: função, objeto, classe.

A linha amarela [metrics] plugin_loaded 1 na saída do terminal é o consumer chamando o método do provider via ctx.metrics.record(...) — os dois plugins realmente conversaram.
Deixe o dsh Fazer: Um Prompt Resolve
Os diretórios, dependências, dois arquivos acima foram todos criados manualmente por você. Como de costume, escrever plugins é algo que o dsh pode fazer sozinho — e ele sabe "como serviços devem ser definidos, como dependências devem ser injetadas" melhor que você.
Envie este prompt diretamente na caixa de entrada da Web UI:
In my current workspace, help me write a "service form" plugin example: a service-providing plugin (Service subclass) and a plugin that uses it (inject depending on it), make them actually talk. First read the official dsh plugin dev docs to understand how to define services, how to inject dependencies, implement per the official spec, verify it can load and call normally, and finally tell me the result and where the files are.Ele vai procurar a documentação oficial sozinho, instalar dependências sozinho, escrever dois plugins sozinho, verificar se eles podem conversar um com o outro sozinho. Você apenas verifica o que ele escreveu.

Armadilhas Comuns
| Problema | O que está acontecendo | Como lidar |
|---|---|---|
Reporta Cannot find package '@deepseek-ai/cordis' | Diretório do plugin não instalou dependências | npm install @deepseek-ai/cordis no diretório do plugin |
Um monte de avisos MODULE_TYPELESS_PACKAGE_JSON | package.json não declarou tipo de módulo | Adicione "type": "module" |
| Consumer não consegue obter o serviço | Incompatibilidade de nome de serviço | Verifique se o nome em super(ctx, 'xxx') e inject: ['xxx'] são exatamente iguais |
| Plugin de classe falha ao carregar | Sem extends Service ou não chamou super | A forma de classe deve herdar Service e super(ctx, 'service-name') |
ctx.metrics é undefined | Consumer não declarou inject | No consumer escreva export const inject = ['metrics'] |
O que você aprendeu neste capítulo
Você passa se conseguir completar os itens abaixo:
- [ ] Indique como cada uma das três formas se parece, e quando usar qual
- [ ] Saiba que serviço = uma capacidade nomeada montada em
ctx;tools/llm/agentssão todos serviços - [ ] Escreva uma subclasse
Servicecomo provider,super(ctx, 'name')para registrar o serviço - [ ] Escreva um consumer
inject, chame diretamente viactx.nome-do-serviço.método()emapply - [ ] Saiba que o plugin de forma de classe deve instalar a dependência
@deepseek-ai/cordisno diretório do plugin primeiro
