mpinaev/dsh-context-governor ↗★ 1

dsh-context-governor

监控 DSH 会话上下文大小与 Token 费用 适合需要实时监控 DeepSeek 余额、提示词大小和单步成本的用户。

套件
dsh-context-governor
相容性
待驗證
Harness 依賴範圍
^0.1.7-rc.2 || ^0.2.0-rc.1
Cordis 依賴範圍
^4.0.1
版本
0.3.0
授權
MIT
最近更新
2026年10月1日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:mpinaev/dsh-context-governor

dsh-context-governor

English | Русский | 中文

npm CI license

A DeepSeek Harness plugin: a session context indicator — prompt size, step cost, cache-hit, bands and the compaction threshold, DeepSeek balance and the peak/off-peak tariff, plus a handoff button.

It never calls a model and spends no tokens at all — details below.

What it looks like

The chip in the session header opens the panel below. The same panel is available in English, Chinese and Russian:

English中文Русский
Session context panel in English会话上下文面板(中文)Панель контекста сессии (русский)

The handoff button sits next to the model selector:

Handoff button

No tokens, no model calls

Everything the plugin shows is measurement and arithmetic. It reads tokens and the window from harness projections, computes the cost with the configured rates and fetches the balance over HTTP.

  • No generation requests. Not for review, not for summarization, not in the background: the plugin never asks a model to compute anything.
  • Zero tokens. The plugin sends no provider requests and spends none of your budget. Its only touch of the LLM layer is reading the model catalog (resolveModelInfo) for the window and limits; that is not generation and not billable tokens.
  • Nothing is modified. It does not touch the history, the system prompt or the prefix cache, so it cannot affect prompt caching.
  • The network is used for the balance only. The harness account service (when signed in) and api.deepseek.com/user/balance are not a model. Plus git status on a handoff click, locally.

Install

As a package:

dsh plugin --profile web add dsh-context-governor

From source:

dsh plugin --profile web add github:mpinaev/dsh-context-governor

Locally, without installing: drop the directory into ~/.dsh/profiles/web/plugins/ and reference the file from the profile's cordis.patch.yml:

- insert:
    - id: context-governor
      name: /absolute/path/to/dsh-context-governor/index.js

Installed as a package, the host half is read at startup, so restart dsh web and reload the page — the client half is served as a revision snapshot.

Check it with npm test: the smoke test runs the host half against a stub harness, with no network and no model calls, and pins the compaction threshold, the cache buckets, the step cost and the balance provider gate.

Supported DSH versions

DSH versionState
0.1.7-rc.2 … `` overrides the default
freshRate / cacheReadRate / cacheWriteRate / outputRate, so a second
provider can carry its own numbers (and its own peakMultiplier);
  • the peak/off-peak tariff (⚡/🌙, the ×N multiplier, Chinese holidays) is DeepSeek's, so it is shown only for a seasonal provider: by default an id starting with deepseek. A foreign provider has a flat rate; declare seasonal: true in its providerRates entry if it really follows DeepSeek's schedule. A flat provider gets neither the time marker nor the tariff rows.

Signals

  • a server log entry when a session enters a band, on a cold prefill and on an expensive step;
  • a client chip in the session header, coloured by band, with a details panel;
  • on the chip: the balance and, for a provider that follows DeepSeek's schedule, the tariff marker (peak / off-peak with countdown).

Peak/off-peak tariff and balance

Tariff. The DeepSeek rule is fixed: peak is Beijing time (UTC+8), on weekdays, 09:00–12:00 and 14:00–18:00; everything else, weekends included, is off-peak and costs half. The local timezone plays no part in the decision, only in the display. The configured rates (freshRate, cacheReadRate, cacheWriteRate, outputRate) are off-peak; at peak they are multiplied by peakMultiplier (2 by default), so the step cost and the warnings follow the tariff of the moment. The countdown runs to the next real switch: boundaries inside a weekend are skipped, so after Friday 18:00 it counts to Monday 09:00, not to Saturday. On a provider with that schedule, at peak the tariff marker, the Beijing time and the balance are drawn in red, in both the chip and the panel, so the expensive hours are visible at a glance.

Chinese public holidays. The official rule has an easy-to-miss caveat: peak is weekdays excluding Chinese public holidays, and on those holidays DeepSeek stays off-peak around the clock. The plugin takes the year's calendar from a maintained source (holidayUrl, by default the data of the chinese-days package), caches it on disk once (holidayCacheDir, default /cache/context-governor) and refreshes itself — no code to edit every year. The network is used in the background only: if the calendar has not arrived, the tariff falls back to the weekday rule and the panel honestly says the holiday calendar is not loaded. Your own dates can be set with the holidays config — an array of 'YYYY-MM-DD' or 'YYYY-MM-DD..YYYY-MM-DD' ranges; they apply immediately, take priority over the source and work without a network. On a holiday the tariff row reads "off-peak (Chinese holiday)" and the countdown skips the whole holiday to the next working peak. The network can be turned off entirely: holidayFetch: false.

Balance. Sources in order:

  1. The official platform account — the harness service ctx.deepseekAccount (getBalance). It works when the account is signed in and the client has sent its build version; wallets arrive as strings, the currency may be CNY or USD, and bonus wallets come as a separate list.
  2. The API key — GET https://api.deepseek.com/user/balance, with the key from the credentials seam (DEEPSEEK_API_KEY) or from the environment.

The source field says where the numbers came from: account or api-key. The fallback to the second source is deliberate: another build may have no platform sign-in, and the balance must not disappear because of that.

The balance is shown only for DeepSeek providers. On cline, clinebot and any other provider a foreign total on the chip is plain misinformation: no balance is displayed there, and the panel shows a DeepSeek-only note instead of a number. The provider list is configurable through balanceProviders (default ['deepseek-official']), and any id starting with deepseek is accepted. If a session has no route of its own yet, the provider is not guessed and the balance stays hidden.

The key never leaves the host: only numbers reach the client (total, bonus, top-up, currency, availability). Concurrent reads collapse, and failures degrade into a state (no-credential, error, disabled) rather than an exception. The refresh button clears the cache and re-reads the balance and the tariff.

The currency is not converted. The step cost and the configured rates (freshRate, cacheReadRate, cacheWriteRate, outputRate — all $/1M) are in US dollars, while the balance is printed in whatever currency the source reports (USD or CNY for DeepSeek), to the cent. The plugin never converts between them, so a ¥ balance and a $ step cost in the same chip are different units — compare them only after converting yourself.

Route access. /context-governor/api/* are closed by a guard header and an Origin check: a request must carry x-dsh-context-governor: 1 (only the plugin client sets it) and must not be cross-origin. Without the header — 403, with a foreign Origin — 403. A plain loopback GET without Origin is allowed, which keeps curl diagnostics convenient.

The practical point: at 99% cache-hit an expensive step is not a large context but fresh input or a cache miss, and the tariff doubles that difference.

Handoff button

One click: the host assembles a session summary (task, state, cwd, touched files, child sessions), the client opens a new session in the same workspace through ctx.uiWorkspace.startSession() and puts the summary into its draft. There are no branches or fallbacks: no Alt+click, no insertion into the current session, no clipboard.

Two declared services keep it reliable:

  • the client declares inject = ['slots', 'uiWorkspace']: without the declaration Cordis does not hand over the service, and the button used to slip into the fallback paths instead of opening a session;
  • the host declares inject = [... 'sessionQuery']: without it the summary came out with no task, state or paths (placeholders only).

The summary reaches the new session lazily: the click remembers the text, startSession() opens the session, its input slot renders, and the same component puts the text into the draft through inputActions.setDraft.

UI languages

Languages: en (default), zh, ru. The switch is a small button next to the refresh control in the chip header: it shows the current code (EN / Chinese / RU) and cycles the language. The choice is remembered in the browser (localStorage: dsh-context-governor.lang) and immediately re-renders both the chip and the handoff button.

The language covers all interface text (panel labels, warnings, tooltips) and the language of the document the handoff button inserts into the new session: the client passes lang to /api/handoff. The host returns data without text (band, tariff and warnings are codes), so switching the language needs no data refetch.

Thresholds

The plugin never hardcodes the model window — it reads it from the harness:

  1. The main source is the contextPressure projection (contextWindow, pressureTokens) of ctx.sessionProjections: the authoritative window of the live session.
  2. The output reserve is the request maxTokens from the request/header event; until then request/context (contextWindow) serves as a hint.
  3. The model catalog (ctx.llm.resolveModelInfo for agentDefaultModel.currentSelection()) remains a hint until the first request.

Window and reserve are kept per session.

The compaction threshold and the bands are derived from the window:

reserve               = request maxTokens
compaction threshold  = min(thresholdRatio * window, window - reserve - headroomTokens)
band i                = bandRatios[i] * compaction threshold

With a 1,000,000 window and a 256,000 reserve the threshold is 678,464 and the bands are 237k / 407k / 577k. With an 800,000 window the threshold is 478,464 and the bands 167k / 287k / 407k. The formula matches dsh-compaction-basic (window - reserve - headroom); with constant bands, critical would