Skip to content

CH 19 · Três Formas de Plugin: Função / Objeto / Classe

Número de palavras~2.870 palavrasTempo~20 minPré-requisitosCH 18 (Seu Primeiro Plugin)NívelReproduzível

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:

FormaComo se pareceCompreensão em uma fraseQuando usar
Funçãoexport function apply(ctx) {}Você mesmo vai resolver uma coisaEscolha padrão, noventa por cento dos casos são suficientes
Objetoexport default { name, inject, apply(ctx) }Empacote o nome e o fluxo juntosQuando você quer dar ao plugin algumas declarações estáticas
Classeexport default class extends Service {}Abra uma janela pública, qualquer um pode vir resolver coisasQuando 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:

js
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:

js
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

js
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 nome metrics em ctx, outros plugins podem acessá-lo via ctx.metrics.
  • record(event, value) é o método que este serviço fornece externamente — outros chamando ctx.metrics.record(...) chegarão aqui.

Consumer: Declare Dependências com inject

js
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:

powershell
New-Item -ItemType Directory -Path "service-demo\src" -Force

Passo 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:

powershell
cd "E:\software-workspace\DeepSeek harness demo\service-demo"   # substitua pelo seu diretório
npm init -y
npm install @deepseek-ai/cordis

Apó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:

json
{
  "name": "service-demo",
  "version": "1.0.0",
  "type": "module"
}

Passo 2: Escreva o Provider

Crie service-demo\src\provider.js:

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:

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):

yaml
- 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:

powershell
cd seu-diretório-de-workspace
dsh --profile headless --patch "./service-demo/cordis.yml" "Just reply: hi"

Na saída você verá:

text
[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:

text
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

ProblemaO que está acontecendoComo lidar
Reporta Cannot find package '@deepseek-ai/cordis'Diretório do plugin não instalou dependênciasnpm install @deepseek-ai/cordis no diretório do plugin
Um monte de avisos MODULE_TYPELESS_PACKAGE_JSONpackage.json não declarou tipo de móduloAdicione "type": "module"
Consumer não consegue obter o serviçoIncompatibilidade de nome de serviçoVerifique se o nome em super(ctx, 'xxx') e inject: ['xxx'] são exatamente iguais
Plugin de classe falha ao carregarSem extends Service ou não chamou superA forma de classe deve herdar Service e super(ctx, 'service-name')
ctx.metrics é undefinedConsumer não declarou injectNo 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/agents são todos serviços
  • [ ] Escreva uma subclasse Service como provider, super(ctx, 'name') para registrar o serviço
  • [ ] Escreva um consumer inject, chame diretamente via ctx.nome-do-serviço.método() em apply
  • [ ] Saiba que o plugin de forma de classe deve instalar a dependência @deepseek-ai/cordis no diretório do plugin primeiro

Open Source · MIT · Community Driven