DeepSeek-Harness: Abhängigkeitsgesteuertes Laden, automatisches Neu Laden und geschachtelte Kontexte

Zuvor haben wir bereits die Fiber-Zustandsmaschine kennengelernt. Dieser Artikel betrachtet, wie die Zustandsmaschine durch Dienstabhängigkeiten gesteuert wird.

Warum wird ein Plugin automatisch entladen, wenn ein abhängiger Dienst verschwindet? Und warum kann es automatisch neu geladen werden, nachdem der Dienst wieder verfügbar ist?


Abhängigkeitsgesteuertes Laden

Ein Plugin, das inject deklariert, wartet darauf, dass alle erforderlichen Dienste bereit sind, bevor es in LOADING eintritt und apply ausführt.

Dieser Mechanismus drückt die Ladereihenfolge durch Dienstabhängigkeiten aus, wodurch die Startreihenfolge nicht mehr manuell orchestriert werden muss.

Beispiel

// Dateipfad: scratch-plugin/src/my-plugin.ts
// Deklariere, dass dieses Plugin von zwei Diensten abhängt: tools und llm
export const inject = ['tools', 'llm']

export function apply(ctx: Context) {
  // An dieser Stelle sind ctx.tools und ctx.llm bereit und sicher verwendbar.
}

Wenn der abhängige Dienst verschwindet (zum Beispiel, wenn der Anbieter ersetzt wird), wird das Plugin automatisch entladen und geht direkt von ACTIVE zu DISPOSED über.

Wenn der Dienst wieder verfügbar ist, wird das Plugin neu geladen und durchläuft erneut PENDING → LOADING → ACTIVE.

卸载与重载时序图

Der genaue Wortlaut aus der offiziellen Dokumentation: Wenn der abhängige Dienst verschwindet (zum Beispiel, wenn der Anbieter ersetzt wird), wird das Plugin automatisch entladen (ACTIVE → DISPOSED) und neu geladen, nachdem der Dienst wieder verfügbar ist.


Garantie für automatisches Neuladen

Das automatische Neuladen ist sicher, weil alle über ctx vorgenommenen Registrierungen beim Entladen widerrufen werden.

Die Listener, Tools und Adapter der alten Instanz bleiben nicht bestehen; die neue Instanz registriert sie erneut.

Dies verhindert, dass Plugins Dienste aufrufen, die nicht mehr existieren, und verhindert außerdem doppelte Listener oder doppelte Registrierungen.


Verschachtelter Kontext: ctx.plugin()

ctx.plugin() wird verwendet, um ein Sub-Plugin innerhalb des aktuellen Plugins zu registrieren.

Ein untergeordnetes Plugin erstellt eine untergeordnete Fiber, die den übergeordneten Kontext erbt, aber einen unabhängigen Lebenszyklus hat.

Die untergeordnete Fiber wird zusammen mit dem übergeordneten Plugin entladen.

Beispiel

// Dateipfad: scratch-plugin/src/parent-plugin.ts
export function apply(ctx: Context) {
  // Registriere ein untergeordnetes Plugin; childPlugin kann eine Funktion, ein Objekt oder eine Service-Unterklasse sein
  ctx.plugin(childPlugin)

  // Unter-Plugins haben ihre eigene Fiber und werden zusammen mit dem übergeordneten Plugin entladen.
}

Verschachtelte Kontexte sind bei der Plugin-Komposition sehr verbreitet: Ein "Aggregator"-Plugin organisiert mehrere kleine Plugins gemeinsam und verwaltet deren Lebenszyklen einheitlich.


Manuelle vorzeitige Beendigung: fiber.dispose()

In den meisten Fällen werden Plugins entladen, wenn der Kontext zerstört wird, aber manchmal müssen Sie eine Plugin-Instanz vorzeitig zur Laufzeit beenden.

Das von ctx.plugin() zurückgegebene Fiber-Objekt bietet diedispose()Methode.

Beispiel

// Dateipfad: scratch-plugin/src/manual-dispose.ts
import type { Context } from '@deepseek-ai/cordis'

// Typdeklarationen: Kontext- und Plugin-Funktionen (illustrativ)
declare const ctx: Context
declare function myPlugin(ctx: Context): void

// Eine Plugin-Instanz erstellen und ihren Fiber abrufen
const fiber = ctx.plugin(myPlugin)

// dispose später aufrufen, wenn eine manuelle Beendigung erforderlich ist
// Hinweis: dispose gibt ein Promise zurück, also den Abschluss der asynchronen Bereinigung abwarten
await fiber.dispose()

dispose garantiert drei Dinge:

GarantieBeschreibung
Alle Registrierungen entferntAlle Registrierungen, die dem Plugin gehören – Listener, Tools, Adapter usw. – werden widerrufen.
Rekursives Entladen von Sub-PluginsÜber ctx.plugin() erstellte untergeordnete Fibers werden ebenfalls zusammen bereinigt
Warten, bis die asynchrone Bereinigung abgeschlossen ist.Das zurückgegebene Promise wird erst aufgelöst, nachdem alle asynchronen Disposer abgeschlossen sind

Mit anderen Worten: Wenn await fiber.dispose() zurückkehrt, ist dieses Plugin zusammen mit seinem gesamten Teilbaum vollständig zum Stillstand gekommen.


Zusammenfassung Selbsttest

Abhängigkeitsgesteuertes Laden bewirkt, dass Plugins nur starten, wenn Dienste bereit sind, automatisch entladen werden, wenn Dienste verschwinden, und automatisch neu geladen werden, wenn Dienste wiederhergestellt werden; ctx.plugin() erstellt untergeordnete Fibers, die rekursiv entladen werden können.

Testen Sie sich selbst:

  1. Wenn ein durch inject deklarierter Dienst nicht bereit ist, in welchem Zustand verbleibt das Plugin?
  2. Welche Beziehung besteht zwischen dem von ctx.plugin() erstellten untergeordneten Fiber und dem übergeordneten Fiber?
  3. Wenn await fiber.dispose() zurückkehrt, welche Dinge sind garantiert abgeschlossen worden?
Weitere Erweiterungen