¿Qué hace Desarrollo de plugins para dsh?
Desarrolla plugins para DeepSeek Harness
(dsh), un framework de agentes basado en Cordis. Los plugins son módulos TypeScript que exportan
apply(ctx: Context), compuestos mediante cordis.yml.
Referencia rápida
Estructura del plugin
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export function apply(ctx: Context) { /* registrar capacidades */ }
Tres formas: función (por defecto), objeto ({ name, inject, apply }), clase
(extends Service — úsala solo cuando quieras proveer un servicio a otros plugins).
Dependencias: export const inject = ['tools', 'llm'] — el framework mantiene
el plugin en estado PENDING hasta que todos los servicios inyectados estén listos.
Limpieza automática: Todo lo registrado a través de ctx es un efecto — se
limpia automáticamente al descargar. Para recursos personalizados (temporizadores, conexiones), envuélvelos en
ctx.effect(() => { ... return () => cleanup }).
Plugins hijos: const fiber = ctx.plugin(childPlugin) — los hijos heredan
el contexto padre; await fiber.dispose() descarga recursivamente.
Estados de Fiber
PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED
↘ FAILED
PENDING → falta una dependencia de servicio. FAILED → apply lanzó una excepción o la validación de configuración
falló. La desaparición de un servicio en tiempo de ejecución descarga automáticamente a los dependientes.
Herramientas (Tools)
import { defineTool } from '@deepseek-ai/dsh-tools'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'saludar', description: '...',
parameters: { nombre: { type: 'string', required: true } },
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
async execute(args) { return `¡Hola, ${args.nombre}!` },
}))
}
defineTool convierte los parámetros a JSON Schema, valida argumentos y se da de baja automáticamente al descargar.
Consulta references/basic/tool.md para más detalles.
Configuración
import Schema from '@deepseek-ai/schemastery'
export interface Config { saludo: string; maxReintentos: number }
export const Config: Schema<Config> = Schema.object({
saludo: Schema.string().default('Hola'),
maxReintentos: Schema.number().default(3),
})
export function apply(ctx: Context, config: Config) { /* configuración validada */ }
Debes exportar TANTO interface Config COMO const Config: Schema<Config>.
Configuración inválida → estado FAILED con error claro. Usa !!js en YAML para valores
dinámicos: config: { apiKey: !!js process.env.API_KEY }.
Consulta references/basic/config.md.
Servicios
Proveer: class extends Service { constructor(ctx) { super(ctx, 'nombre') } }
declare module '@deepseek-ai/cordis' { interface Context { nombre: NombreService } }
Consumir: export const inject = ['nombre'] → ctx.nombre.metodo().
Opcional: ctx.get('nombre') → devuelve undefined cuando está ausente.
Aislamiento de servicios: group: true + isolate: { shell: true } le da a cada grupo
su propia instancia del proveedor. Consulta references/framework/service.md.
Eventos
| Modo | Llamada | Uso |
|---|---|---|
emit | ctx.emit(n, ...args) | Difusión síncrona, ignora valores de retorno |
parallel | await ctx.parallel(n, ...args) | Listeners concurrentes |
serial | await ctx.serial(n, ...args) | Secuencial, el primer no nulo gana |
bail | ctx.bail(n, ...args) | Serie síncrona |
waterfall | ctx.waterfall(n, ...args, next) | Cadena de middleware |
Los listeners waterfall deben llamar a next() a menos que intencionalmente cortocircuiten.
Eventos clave del harness: agent/step, tools/result, session/event.
Consulta references/framework/events.md.
Adaptadores LLM
class MiAdaptador extends LlmAdapter {
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
// Convertir → API del proveedor → yield StreamChunk
}
}
ctx.llm.registerAdapter(['mi-proveedor'], adaptador)
Orden de StreamChunk por bloque: block-start → text-delta/tool-call-delta →
block-end → usage → finish. Lanza LlmError con códigos estables.
Consulta references/practice/llm-adapter.md.
Diseño de capacidades en tres capas
Definición (clase abstracta Service) → Proveedor (implementa) → Consumidor (herramienta)
Los tres comparten el mismo nombre de servicio mediante inject. Solo se dividen en paquetes
separados cuando los roles necesitan evolución independiente.
Consulta references/practice/index.md.
cordis.yml / Bundles
Desarrollo local: --patch ./cordis.patch.yml
- insert:
- id: my-plugin
name: '/ruta/absoluta/hacia/src/my-plugin.ts'
config: { saludo: 'Hola' }
Distribución: paquete npm con manifiesto dsh.bundle + cordis.patch.yml.
Instalación: dsh plugin --profile demo add ./hello-plugin.
Orden de capas: bundles de perfil → parche de perfil → parche de home → superposiciones --patch.
Las capas posteriores ganan por fila (sin fusión profunda).
Consulta references/basic/publish.md.
HMR (Recarga en caliente)
Carga @deepseek-ai/cordis-plugin-hmr + @deepseek-ai/cordis-plugin-logger-console
@deepseek-ai/cordis-plugin-timerpara recargar automáticamente al guardar archivos.
Diagnosticando PENDING
import { FiberState, type Context } from '@deepseek-ai/cordis'
for (const fiber of ctx.registry.values().flatMap(r => r.fibers))
if (fiber.state === FiberState.PENDING)
console.log(`${fiber.name} está PENDING`)
Documentación de referencia
Guías detalladas en el directorio references/:
| Ruta | Cubre |
|---|---|
references/basic/ | Primer plugin, herramientas, configuración, publicación |
references/framework/ | Ciclo de vida, servicios, eventos |
references/practice/ | Diseño de tres capas, adaptadores LLM |
references/cordis-tutorial/ | Tutorial paso a paso del framework Cordis |