Was macht dsh Plugin-Entwicklung?
Entwickeln Sie Plugins für DeepSeek Harness
(dsh), ein Cordis-basiertes Agenten-Framework. Plugins sind TypeScript-Module, die
apply(ctx: Context) exportieren und über cordis.yml komponiert werden.
Kurzreferenz
Plugin-Struktur
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export function apply(ctx: Context) { /* Fähigkeiten registrieren */ }
Drei Formen: Funktion (Standard), Objekt ({ name, inject, apply }), Klasse
(extends Service — nur verwenden, wenn ein Service für andere Plugins bereitgestellt wird).
Abhängigkeiten: export const inject = ['tools', 'llm'] — das Framework hält das
Plugin im Zustand PENDING, bis alle injizierten Services bereit sind.
Automatische Bereinigung: Alles, was über ctx registriert wird, ist ein Effekt —
beim Entladen wird automatisch aufgeräumt. Für benutzerdefinierte Ressourcen (Timer,
Verbindungen) in ctx.effect(() => { ... return () => cleanup }) kapseln.
Kind-Plugins: const fiber = ctx.plugin(childPlugin) — Kinder erben den
Elternkontext; await fiber.dispose() entlädt rekursiv.
Fiber-Zustände
PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED
↘ FAILED
PENDING → fehlende Service-Abhängigkeit. FAILED → apply hat einen Fehler
geworfen oder die Konfigurationsvalidierung ist fehlgeschlagen. Wenn ein Service zur
Laufzeit verschwindet, werden Abhängige automatisch entsorgt.
Tools
import { defineTool } from '@deepseek-ai/dsh-tools'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'greet', description: '...',
parameters: { name: { type: 'string', required: true } },
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
async execute(args) { return `Hello, ${args.name}!` },
}))
}
defineTool konvertiert Parameter in JSON Schema, validiert Argumente und
hebt die Registrierung beim Entladen automatisch auf. Siehe references/basic/tool.md
für Details.
Konfiguration
import Schema from '@deepseek-ai/schemastery'
export interface Config { greeting: string; maxRetries: number }
export const Config: Schema<Config> = Schema.object({
greeting: Schema.string().default('Hello'),
maxRetries: Schema.number().default(3),
})
export function apply(ctx: Context, config: Config) { /* validierte Konfiguration */ }
Es MÜSSEN sowohl interface Config als auch const Config: Schema<Config>
exportiert werden. Ungültige Konfiguration → Zustand FAILED mit klarem Fehler.
Verwenden Sie !!js in YAML für dynamische Werte:
config: { apiKey: !!js process.env.API_KEY }.
Siehe references/basic/config.md.
Services
Bereitstellen: class extends Service { constructor(ctx) { super(ctx, 'name') } }
declare module '@deepseek-ai/cordis' { interface Context { name: NameService } }
Konsumieren: export const inject = ['name'] → ctx.name.method().
Optional: ctx.get('name') → gibt undefined zurück, wenn nicht vorhanden.
Service-Isolation: group: true + isolate: { shell: true } gibt jeder Gruppe
eine eigene Anbieterinstanz. Siehe references/framework/service.md.
Events
| Modus | Aufruf | Verwendung |
|---|---|---|
emit | ctx.emit(n, ...args) | Synchrone Broadcast, Rückgabewerte ignorieren |
parallel | await ctx.parallel(n, ...args) | Parallele Listener |
serial | await ctx.serial(n, ...args) | Sequentiell, erster nicht-null gewinnt |
bail | ctx.bail(n, ...args) | Synchron seriell |
waterfall | ctx.waterfall(n, ...args, next) | Middleware-Kette |
Waterfall-Listener müssen next() aufrufen, es sei denn, sie beenden die Kette
absichtlich vorzeitig. Wichtige Harness-Events: agent/step, tools/result,
session/event. Siehe references/framework/events.md.
LLM-Adapter
class MyAdapter extends LlmAdapter {
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
// Konvertieren → Provider-API → yield StreamChunk
}
}
ctx.llm.registerAdapter(['my-provider'], adapter)
StreamChunk-Reihenfolge pro Block: block-start → text-delta/tool-call-delta →
block-end → usage → finish. Werfen Sie LlmError mit stabilen Codes.
Siehe references/practice/llm-adapter.md.
Drei-Schichten-Fähigkeitsdesign
Definition (Service-Abstract-Klasse) → Anbieter (implementiert) → Konsument (Werkzeug)
Alle drei teilen denselben Servicenamen über inject. Nur in separate Pakete
aufteilen, wenn die Rollen unabhängig voneinander weiterentwickelt werden müssen.
Siehe references/practice/index.md.
cordis.yml / Bundles
Lokale Entwicklung: --patch ./cordis.patch.yml
- insert:
- id: my-plugin
name: '/absolute/path/to/src/my-plugin.ts'
config: { greeting: 'Hi' }
Verteilung: npm-Paket mit dsh.bundle-Manifest + cordis.patch.yml.
Installation: dsh plugin --profile demo add ./hello-plugin.
Ebenen-Reihenfolge: Profil-Bundles → Profil-Patch → Home-Patch → --patch-Overlays.
Spätere Ebenen gewinnen zeilenweise (kein Deep Merge).
Siehe references/basic/publish.md.
HMR
Laden Sie @deepseek-ai/cordis-plugin-hmr + @deepseek-ai/cordis-plugin-logger-console
@deepseek-ai/cordis-plugin-timer, um bei Dateiänderungen automatisch neu zu laden.
Diagnose von 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} is PENDING`)
Referenzdokumente
Ausführliche Anleitungen im Verzeichnis references/:
| Pfad | Abgedeckt |
|---|---|
references/basic/ | Erstes Plugin, Werkzeuge, Konfiguration, Veröffentlichung |
references/framework/ | Lebenszyklus, Services, Events |
references/practice/ | Drei-Schichten-Design, LLM-Adapter |
references/cordis-tutorial/ | Schrittweises Cordis-Framework-Tutorial |