DeepSeek Harness plugin

dsh-llm-qodersdk

Qoder CLI (qoder-agent-sdk) adapter for the DeepSeek Harness LLM seam

Jump to install

Source facts

Repository
JiamingZang/dsh-llm-qodersdk
Latest update
Aug 19, 2026
Category
Workflow & Automation
GitHub stars
2
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/JiamingZang/dsh-llm-qodersdk
Plugin: dsh-llm-qodersdk
Author: JiamingZang

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer4 files
README.en.mdSource · read only
README language

dsh-llm-qodersdk

> 中文 | English

An adapter plugin (@jiamingzang/dsh-llm-qoder) that routes DeepSeek Harness's LLM seam (ctx.llm) to the local Qoder CLI, built on @qoder-ai/qoder-agent-sdk.

It registers the qoder / qoder-byok provider routes so the harness's model requests reuse the local qodercli login state — no credentials or settings required. Both built-in models and account-custom models are fetched live from qodercli.

Features

  • Zero-config: fully reuses the local qodercli login state; no API key or settings section needed.
  • Two routes: qoder advertises built-in account models; qoder-byok advertises only account-custom models (each with its own independent route).
  • Persistent sessions: one warm inner query() subprocess per host session id; conversation continuation and tool rounds all happen inside the session, LRU-evicted by insertion order within the maxSessions cap.
  • Tool bridging: host tools are exposed to the inner model through an in-process MCP server (dsh-host); qodercli executes one call at a time and the host returns the whole round of results, paired by callId (timed out and cancelled if not delivered within 120s).
  • Model catalog: fetches available models (including account-custom ones) live from the CLI with TTL caching + shared concurrency + timeout protection, falling back to a static catalog on failure; also provides deepseek-v4-flashdfmodel and deepseek-v4-prodmodel aliases.
  • Reasoning effort & context window: resolveModel reports the CLI's reasoning efforts, default level, and availableContextWindows / defaultContextWindow so the model selector can switch them; the selected values are sent per-request via the model-policy parameter.
  • Side-channel requests: titles, compaction summaries, and other side-channel requests use one-shot cold calls that never occupy a warm session.
  • Overflow recoverable: when the inner model fails on context overflow (e.g. maximum context length ... you requested N tokens), the error is classified as CONTEXT_WINDOW_EXCEEDED via dsh-llm's isContextWindowExceededError, so the harness's overflow auto-recovery (with compaction-basic) takes over instead of wasting the turn.

Adaptation Principles

This section explains how the plugin maps the harness's LLM seam to qodercli — what each layer adapts and why it is designed that way.

1. LLM seam adaptation (ctx.llm → qodercli)

The harness registers QoderAdapter via ctx.llm.registerAdapter(['qoder', 'qoder-byok'], adapter), implementing the harness's LlmAdapter contract:

Harness seamThis plugin's implementation
providerInfo(provider)Returns display names for qoder (Qoder CLI) / qoder-byok (Qoder custom)
listModels(provider)Fetches the qodercli model catalog live, filtered by provider (qoder → built-in; qoder-byok → account-custom source === 'user')
resolveModel(provider, model)Resolves model metadata (context window, reasoning effort, output limit) from the live catalog / static table
stream(options)Turns a GenerateOptions into a qodercli query() call and streams back StreamChunk

The core of the adaptation is stream() routing:

  • Warm session path (has sessionId and no purpose): reuse or spawn an inner query() subprocess, feed new messages incrementally, and round-trip tools over MCP.
  • Side-channel path (no sessionId, or carries purpose, e.g. title generation, compaction summaries): one-shot coldStream() call that does not occupy a warm session. This path reuses the main session's model (resolveQoderModelId(options.model)), so the call target dsh records matches the model actually executed.

2. Session model adaptation

  • One host session ↔ one warm qodercli session: QoderSessionManager keys query() subprocesses by host sessionId; beyond maxSessions they are evicted LRU by insertion order.
  • Incremental feed: the host sends the full message list on every request; the plugin uses planContinuation to diff against the previous one and renders only the new user turns and rewritten messages into a plain-text feed; tool results never enter the text feed (they go over MCP).
  • Rebuild detection: when the host surface is rewritten (e.g. compaction folds history) so messages shrink or restructure, planContinuation returns rebuild: true and the plugin disposes the old warm session and cold-starts from the new surface. This guarantees dsh-side compaction and qodercli's internal cache never hold duplicate state.
  • Model switching: setModel forwards reasoningEffort / contextWindow to the inner session as model-policy parameters.

3. Tool bridging adaptation (MCP)

Host tools are not sent directly to qodercli; they go through an in-process MCP server (dsh-host):

1. ensureTools() converts host ToolSchema to zod shapes and registers them on the MCP server; 2. qodercli's canUseTool allows tools with the mcp__dsh-host__* prefix and records tool-use ids; 3. the MCP handler parks on a promise, waiting for the host to deliver results via deliverToolResults() in the next request round; 4. results are paired by callId ↔ toolUseId; on timeout (TOOL_RESULT_TIMEOUT_MS) an error is returned so qodercli can recover.

Host tool calls thus remain ordinary tool rounds on the host side, while qodercli only sees an MCP tool "executed once".

4. Model catalog adaptation

  • Live catalog: QoderModelCatalog sends a get_models control request to qodercli, with TTL caching (default 300s), shared concurrency, and a timeout fallback to the static table.
  • Static fallback: QODER_MODELS is a captured built-in model table (including deepseek-v4-flash / deepseek-v4-pro aliases), used when the CLI is unreachable.
  • Provider grouping: listModels splits by the source field — built-in models go to qoder, account-custom models (source === 'user') go to qoder-byok. No manual configuration; everything is fetched live from qodercli.

5. Context window & compaction threshold adaptation

The qodercli live catalog reports both the ceiling and the actual window for each model:

maxInputTokens: 1000000          ← model ceiling (1M)
availableContextWindows: [200000, 400000, 1000000]
defaultContextWindow: 200000     ← the window requests actually use

The harness compaction engine derives the auto-compaction threshold from resolveModel().context.contextWindow (thresholdTokens = 0.8 × contextWindow; compaction-basic default thresholdRatio: 0.8), and the UI context ring uses it as the denominator. contextWindow must therefore reflect the window requests actually use, not the model ceiling:

contextWindow: live.defaultContextWindow ?? live.maxInputTokens ?? DEFAULT_CONTEXT_WINDOW
  • Using defaultContextWindow (the actual window, e.g. 200K) → threshold = 160K, aligned with the provider's real capacity;
  • maxInputTokens (the ceiling, e.g. 1M) stays only in availableContextWindows for the selector to switch;
  • If the ceiling were used, the threshold would be inflated (e.g. 800K) and compaction would never fire before the provider rejects the request.

6. Context usage metering adaptation

The qodercli stream usage frames (input_tokens / output_tokens) are zeroed by default (no metering data), so real usage cannot be reported directly. The harness contextPressure projection uses the most recent request's inputTokens as the numerator (pressureTokens) to drive the UI ring and compaction checks.

Adaptation: estimate each request's input on the plugin side with the same measure the harness front-end token meter uses.

adapter.stream() calls session.recordRequestInput(system, messages) on every request, rendering the full conversation (system prompt + all messages) via renderInitialFeed(system, messages) and estimating tokens as rendered.length / 4 — the same CHARS_PER_TOKEN = 4 convention as harness estimate.ts. usage() prefers this estimate:

if (this.estimatedInputTokens !== undefined && this.estimatedInputTokens > 0) {
  return { inputTokens: this.estimatedInputTokens, ... }
}

The UI context ring, auto-compaction threshold, and the plugin-reported values thus share one estimation convention: occupancy display and compaction behavior agree, with no "UI shows 2% while actually near the limit" split.

7. Error classification adaptation

qodercli reports context overflow, quota exhaustion, and other rejections uniformly as a generic per-turn error (error_during_execution). The harness overflow recovery only triggers on the CONTEXT_WINDOW_EXCEEDED code (prune + compact + retry). The plugin therefore maps qodercli error text to harness-routable codes using dsh-llm's shared classifier:

function classifyTurnError(detail: string): string {
  if (isContextWindowExceededError(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE
  if (isQuotaExceededError(detail)) return QUOTA_EXCEEDED_CODE
  return 'BACKEND_TURN_ERROR'
}

Provider context overflow thus triggers harness auto-recovery and quota exhaustion surfaces correctly, instead of dying as an ordinary backend error.

8. Compaction responsibility split (dsh vs qoder)

  • Compaction is executed by dsh (harness compaction-basic): it decides the compaction scope and retention ratio (default retainRatio: 0.16 keeps the most recent 16%), calls the LLM to produce a checkpoint, and rewrites the session surface.
  • The qoder plugin only acts as the LLM backend: it feeds dsh's messages to qodercli and returns the replies. Compaction summary requests go through the side-channel coldStream() and reuse the main session's model (see §1), so the summary target dsh records matches what actually runs.
  • No-conflict guarantee: after dsh compaction rewrites the surface, the plugin's planContinuation detects the message structure change (rebuild: true) and rebuilds the warm session. qodercli's internal cache is invalidated along with the dsh compaction — there is never "both sides compacting".
  • Summaries default to the main session route; to bypass a specific provider's quota, point compaction-basic's summarizationProvider / summarizationModel at another model with quota in cordis.patch.yml.

Installation

Prerequisites: a local qodercli binary with an active login (qodercli --version runs). The plugin fully reuses the qodercli login state — no API key or settings section needed.

From the release tarball

1. Get the package: download the latest jiamingzang-dsh-llm-qoder-<version>.tgz from this repository's Releases (or produce it yourself with pnpm pack in the repo root); 2. Add it to the target profile:

``sh dsh plugin --profile <profile> add jiamingzang-dsh-llm-qoder-<version>.tgz ``

3. First install requires approving a build script: @qoder-ai/qoder-agent-sdk ships a postinstall (downloads the worker runtime), which pnpm 11+ blocks by default with ERR_PNPM_IGNORED_BUILDS. dsh writes the pending key into the profile's pnpm-workspace.yaml placeholder ('@qoder-ai/qoder-agent-sdk': set this to true or false under allowBuilds); set it to true and rerun the add command to finish — this is dsh's standard fail-loud flow for any dependency with postinstall scripts;

4. Verify: dsh --profile <profile> --dump-config | grep llm-qoder shows the plugin entry; after restarting the service, the model selector offers qoder / qoder-byok.

Manual mount (optional)

Without the plugin command, declare it directly in cordis.yml or a patch layer (the plugin's package.json also declares dsh.bundle, so plugin add joins it to the profile's bundles automatically):

- id: llm-qoder
  name: '@jiamingzang/dsh-llm-qoder'

Usage & troubleshooting

  • Pick a model under qoder (account built-ins) or qoder-byok (account custom models) in the dialog model selector or the Models settings page; context window and reasoning effort are switchable in the model panel.
  • Custom models or the context-window switch gone: usually the live catalog fetch failed during a qodercli auto-upgrade window or because the account quota ran out (the backend marks models isEnabled: false), so the plugin fell back to the static catalog. Failed fetches are not cached; once the CLI recovers, the live catalog returns automatically — no service restart needed.

Configuration

FieldTypeDefaultDescription
maxSessionsnumber8Max warm inner qodercli sessions kept (beyond this, LRU eviction by insertion order)
modelCacheTtlSecondsnumber300Freshness TTL for the CLI model catalog cache

Context Management & Compaction

Warm inner sessions accumulate the whole host history: the first turn feeds the full history (renderInitialFeed), and each later turn feeds only the increment (new user messages, in-place refreshes). The inner context thus grows with the conversation, up to the model's hard ceiling (e.g. 1048576 tokens).

  • Overflow auto-recovery: when the inner model reports context overflow, the plugin reports CONTEXT_WINDOW_EXCEEDED. The harness overflow recovery (dsh-compaction-basic's agent/request-error handler) compacts the host history and retries; once the history shrinks, the next request detects the rollback and rebuilds the inner session, cold-feeding the compacted history.
  • Prerequisite: the deployment must load dsh-compaction-basic (auto defaults to true) and dsh-token-meter. Without a compaction plugin, the overflowing turn still fails — only a new session or manual compaction helps.
  • Consider lowering the pressure threshold: the default thresholdRatio of 0.8 × model contextWindow (1048576 → ~838k) may fire late, especially since host-side token estimation can drift from the inner real usage. Lowering it to 0.6 makes pressure compaction fire well before overflow:

``yaml - id: compaction-basic config: auto: true thresholdRatio: 0.6 retainRatio: 0.16 ``

You can also configure the qoder route separately via modelPolicies.

  • Manual compaction: with dsh-command-compact loaded, type /compact in the conversation to compact immediately.
  • Already-overflowed sessions: when the history already exceeds the model ceiling, "continue" only retries with longer history and fails again; run /compact first (or wait for pressure compaction after lowering the threshold), otherwise start a new session.

Source Layout

FileResponsibility
src/index.tsPlugin entry: ctx.llm.registerAdapter(['qoder', 'qoder-byok'], adapter)
src/adapter.tsQoderAdapter: model listing/resolution/streaming, warm session management, continuation planning, side-channel model pass-through, request input estimation
src/session.tsQoderSession: inner query() subprocess, MCP tool bridge, SDK stream events → harness StreamChunk, usage reporting (input estimation + error classification)
src/models.tsLive model catalog fetch (TTL cache, shared concurrency, timeout, static fallback)
src/catalog.tsStatic model table and deepseek-v4-* aliases
src/render.tsHost messages → inner plain-text feed; identity override
src/jsonschema.tsdsh ToolSchema.parameters → zod shape (for MCP tool registration)

Development & Build

This repo stores only source (src/) and tests (tests/); build artifacts lib/ (lib/index.js + lib/types/*.d.ts) are gitignored and generated on demand by the build script.

pnpm install
pnpm test        # vitest unit tests
pnpm run build   # tsc emits lib/types/*.d.ts, tsdown bundles lib/index.js
pnpm pack        # produces jiamingzang-dsh-llm-qoder-<version>.tgz
pnpm publish     # prepublishOnly builds first

The build config is in place (tsconfig.json + tsdown.config.ts); peer dependencies @deepseek-ai/dsh-llm and @deepseek-ai/cordis stay external.

> Version note: the peer dependency @deepseek-ai/dsh-llm@^0.1.0-rc.5 is now resolvable from the public npm registry (currently 0.1.0-rc.6), so this repo can pnpm install && pnpm run build directly. To align exactly with the local version inside the DeepSeek Harness repo (0.1.0-rc.5), build inside plugins/llm-qoder/ there instead (the repo-root tsc + tsdown produce lib/).

License

[MIT](LICENSE) © 2026 dsh-llm-qodersdk contributors