Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
3.9 KiB
Cordis Primer
English | 中文
Cordis is the vendored plugin framework underneath the DeepSeek Harness SDK. This primer teaches the Cordis ideas a harness plugin author needs before reading the generated service/event reference on the subsystem pages; the Cordis tutorial walks the same ideas hands-on. The vendored source and sync procedure live in vendor/README.md.
Cordis In Five Ideas
- A plugin is a object that implements Service. It can be a function with optional
injectandapply(ctx)fields, or aServicesubclass whose lifecycle Cordis mounts into the current context. - A context is a repository of services. A service claims a stable
ctx.<key>such asctx.tools,ctx.llm, orctx.sessionsfrom a context; other plugins find services via key instead of importing a concrete implementation. - Declare service dependency via
inject. A plugin that names required services waits until those services exist, so load order is expressed through service requirements rather than manual boot sequencing. - Typed Events for communication. Services declare event names through TypeScript declaration merging, then dispatch them as
emit,waterfall,parallel, orserialdepending on whether listeners observe, wrap, fan out, or run in order. - Registrations are reversible effects. Prompt sections, tool schemas, adapters, providers, and listeners are installed through
ctx.effect()orctx.on()so reload and teardown unwind them predictably.
Dispatch Modes
Every event can have one of the following dispatch mode and can only be dispatched by these methods accordingly.
| Mode | Awaited? | Dispatch Order | Has Return Value? |
|---|---|---|---|
emit |
No | listeners observe in registration order | No |
waterfall |
No | listeners observe in registration order | Yes |
parallel |
Yes | all listeners observe the event in parallel | No |
serial |
Yes | listeners observe in registration order | Yes |
The dispatch mode is part of the event's public contract. New harness events document it with an @mode tag so the generated catalog can check declarations against dispatch sites.
Cordis Waterfall Semantics
ctx.waterfall is around-middleware. A listener receives (...args, next). Call next() to delegate the possibly wrapped result to the next service; return without next() to short-circuit. Values propagate through next()'s return value.
Cooperative listeners usually mutate a shared request or decision object and then delegate. A listener can also choose to replace the result entirely and downstream listeners will only see the result after replacement. Use prepend: true only when the listener must run before ordinary registrations.
For single-decision events, short-circuiting is the design. A policy listener can return without next() when it owns the decision, while a listener that only annotates or observes must delegate.
Loader Configuration
@deepseek-ai/cordis-plugin-include parses !!js into expression nodes, but the Loader interpolates only an entry's config before mounting the plugin. Entry metadata (id, name, group, disabled, inject, intercept, and isolate) remains literal; disabled: !!js ... is therefore a truthy object that always disables the entry. Use explicit config overlays when environment selection changes which plugins are mounted.
Practical Rules
Encapsulate behavior into plugins: a tool pipeline event belongs to ctx.tools, model streaming belongs to ctx.llm, and live agent coordination belongs to ctx.agents. Prefer events for interception and policy; prefer service methods for direct capability calls.
Every registration should have a disposer, either by returning one from ctx.effect() or using a Cordis helper that does it for you. If teardown order matters, keep the related work in one effect so disposal unwinds in the intended sequence.