Skip to content

CH 19 · Tres Formas de Plugin: Función / Objeto / Clase

Recuento de palabras~2,870 palabrasTiempo~20 minRequisitosCH 18 (Tu Primer Plugin)NivelReproducible

Objetivo del capítulo

CH 18 fue la forma más común de escribir un plugin. Este capítulo cubre por completo las tres formas de plugin (función, objeto, clase) de una vez: qué aspecto tiene cada una, cuándo usar cuál, y por qué la forma clase es "abrir una ventana pública" — es la clave para la colaboración entre plugins. Tras leer este capítulo, sabrás qué forma debe usar un plugin, en lugar de solo poder usar una plantilla.

Las tres formas de un vistazo

Una tabla para la visión general, los detalles se desglosan uno por uno debajo:

FormaAspectoEntiende en una líneaCuándo usarla
Funciónexport function apply(ctx) {}Tú mismo vas a manejar una cosaOpción por defecto, el noventa por ciento de los casos es suficiente
Objetoexport default { name, inject, apply(ctx) }Empaqueta el nombre y el flujo juntosCuando quieres darle al plugin algunas declaraciones estáticas
Claseexport default class extends Service {}Abre una ventana pública, cualquiera puede venir a manejar cosasCuando quieres que otros plugins llamen a tus capacidades

Un juicio es suficiente: usa función para añadir capacidades al Agent; usa clase para que otros plugins dependan de ti. La forma objeto está en el medio, funcionalmente básicamente igual a la función, solo con más metadatos estáticos como name.

Forma función: ya la conoces

El hello-plugin de CH 18 es la forma función:

js
export function apply(ctx) {
  // Registra tus capacidades aquí
}

El framework llama a apply(ctx) al cargar el plugin, entregándote el contexto. La gran mayoría de plugins — registrar herramientas, escuchar eventos, montar timers — esta es suficiente. Hasta que estés seguro de que otros plugins dependerán de ti, escribe siempre primero la forma función.

Forma objeto: empaqueta el nombre y el flujo

La forma objeto consiste en poner name, inject, apply en un objeto y exportarlos juntos:

js
export default {
  name: 'my-plugin',
  inject: ['tools'],
  apply(ctx) {
    // Aquí ctx.tools está garantizado que estará disponible
  },
}

Es casi equivalente a la forma función, la única diferencia es organizar las declaraciones estáticas (name, dependencias) y apply en un "spec empaquetado". ¿Cuándo usarla? Cuando quieres que el "quién soy, qué servicios necesito, qué hago" de tu plugin quede claro de un vistazo, la forma objeto es más limpia. Funcionalmente no hay nada que la forma función no pueda hacer.

Forma clase: cuando el plugin quiere "proveer un servicio"

Esta sección es el foco de este capítulo. Primero establece un concepto:

Un Service es una capacidad con nombre montada en ctx. Usas servicios cada día — ctx.tools (herramientas), ctx.llm (modelo), ctx.agents (subagents) son todos servicios. Cualquier plugin puede proveer sus propios servicios para que otros plugins los llamen.

La forma función/objeto es "ve a manejar cosas tú mismo"; la forma clase es abrir una ventana pública, cualquiera puede venir a ti a manejar cosas. Una analogía: la forma función es como ir tú a una sala de servicios a manejar tus propios asuntos; la forma clase es como abrir una ventana en la sala, otros (otros plugins) vienen a tu ventana a entregar formularios y obtener resultados.

Provider: escribe una subclase de Service

js
import { Service } from '@deepseek-ai/cordis'

export default class MetricsService extends Service {
  constructor(ctx) {
    super(ctx, 'metrics')  // Registra un servicio llamado metrics
  }

  record(event, value) {
    console.log('[metrics]', event, value)
  }
}

Dos puntos clave:

  • extends Service + super(ctx, 'metrics'): monta el nombre metrics en ctx, otros plugins pueden acceder a él vía ctx.metrics.
  • record(event, value) es el método que este servicio provee externamente — otros llamando a ctx.metrics.record(...) llegarán aquí.

Consumer: declara dependencias con inject

js
export const name = 'consumer'
export const inject = ['metrics']

export function apply(ctx) {
  ctx.metrics.record('plugin_loaded', 1)
}

inject: ['metrics'] declara "quiero usar el servicio metrics". El framework garantiza: cuando apply se ejecute, los servicios declarados en inject están definitivamente listos. Si el servicio no está listo, tu plugin esperará y no se ejecutará antes de tiempo.

Un diagrama para entender la colaboración

El provider monta el servicio en ctx, el consumer declara dependencias y llama directamente. Capacidades integradas como tools, llm, agents son esencialmente el mismo mecanismo — cuando las usas, eres un "consumer".

Práctica: haz que dos plugins hablen realmente

Lo de arriba fue todo concepto. Ahora vamos a escribir un provider y un consumer y hacer que hablen realmente. En un workspace donde quieras poner plugins (primero cd allí) crea un directorio:

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

Paso 1: instala dependencias en el directorio del plugin

La forma clase necesita import el paquete @deepseek-ai/cordis del framework. Tu workspace no lo tiene, cargarlo directamente reportará Cannot find package '@deepseek-ai/cordis'. Así que ve al directorio del plugin e instálalo primero:

powershell
cd "E:\software-workspace\DeepSeek harness demo\service-demo"   # sustituye por tu directorio
npm init -y
npm install @deepseek-ai/cordis

Tras la instalación, modifica package.json y añade una línea "type": "module" al objeto raíz — si no, Node adivinará el formato del módulo cada vez que cargue el plugin y spammeará avisos:

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

Paso 2: escribe el Provider

Crea 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)
  }
}

Paso 3: escribe el Consumer

Crea service-demo\src\consumer.js:

js
export const name = 'consumer'
export const inject = ['metrics']

export function apply(ctx) {
  ctx.metrics.record('plugin_loaded', 1)
}

Paso 4: declara y verifica

Crea service-demo\cordis.yml (sustituye la ruta por la tuya, fíjate en que los espacios deben escribirse como %20):

yaml
- insert:
    - id: provider
      name: 'file:///E:/tu-workspace/service-demo/src/provider.js'
    - id: consumer
      name: 'file:///E:/tu-workspace/service-demo/src/consumer.js'

Usa headless para verificación rápida:

powershell
cd tu-directorio-de-workspace
dsh --profile headless --patch "./service-demo/cordis.yml" "Just reply: hi"

En la salida verás:

text
[metrics] plugin_loaded 1
hi

[metrics] plugin_loaded 1 es el consumer llamando al método del provider — tus dos plugins hablaron realmente. En este punto, has verificado personalmente las tres formas: función, objeto, clase.

La línea amarilla [metrics] plugin_loaded 1 en la salida de la terminal es el consumer llamando al método del provider vía ctx.metrics.record(...) — los dos plugins hablaron realmente.

Deja que dsh lo haga: un prompt lo resuelve

Los directorios, dependencias y dos archivos de arriba los creaste manualmente tú. Como siempre, escribir plugins es algo que dsh puede hacer por sí mismo — y conoce "cómo deben definirse los servicios, cómo deben inyectarse las dependencias" mejor que tú.

Envía este prompt directamente en el cuadro de entrada de la 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.

Irá a consultar los docs oficiales por sí mismo, instalará dependencias por sí mismo, escribirá dos plugins por sí mismo, verificará si pueden hablarse entre sí por sí mismo. Tú solo verificas qué escribió.

Errores comunes

ProblemaQué está pasandoCómo manejarlo
Reporta Cannot find package '@deepseek-ai/cordis'El directorio del plugin no instaló dependenciasnpm install @deepseek-ai/cordis en el directorio del plugin
Un montón de avisos MODULE_TYPELESS_PACKAGE_JSONEl package.json no declaró el tipo de móduloAñade "type": "module"
El consumer no puede obtener el servicioEl nombre del servicio no coincideComprueba que el nombre en super(ctx, 'xxx') y inject: ['xxx'] son exactamente iguales
El plugin clase falla al cargarSin extends Service o no llamó a superLa forma clase debe heredar Service y super(ctx, 'service-name')
ctx.metrics es undefinedEl consumer no declaró injectEn el consumer escribe export const inject = ['metrics']

Lo que aprendiste en este capítulo

Apruebas si puedes completar los puntos de abajo:

  • [ ] Enuncias qué aspecto tiene cada una de las tres formas, y cuándo usar cuál
  • [ ] Sabes que service = una capacidad con nombre montada en ctx; tools/llm/agents son todos servicios
  • [ ] Escribes una subclase Service como provider, super(ctx, 'name') para registrar el servicio
  • [ ] Escribes un consumer con inject, llamas directamente vía ctx.service-name.method() en apply
  • [ ] Sabes que el plugin en forma clase debe instalar la dependencia @deepseek-ai/cordis en el directorio del plugin primero

Open Source · MIT · Community Driven