Models & Providers

dsh-codex-model-effort-selector

ABING1111/dsh-codex-model-effort-selector · ★ 23

ABING1111

Codex desktop style model + reasoning-effort selector for DeepSeek Harness: one trigger, one popover, a model list pane and a pixel-replica effort slider.

License
MIT
Added
2026-10-30
Maintenance
active
Install kind
github
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-10-30T09:38:16.033Z
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

dsh plugin --profile web add 'github:ABING1111/dsh-codex-model-effort-selector#816b636fa3b6af95d3c229e7cb3d24040cf7fec0'

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. Table of contents
  2. What it is
  3. Features
  4. Reasoning-effort pane
  5. Model pane (two-level drill-down)
  6. Two details that are easy to miss
  7. Dark mode
  8. Accessibility & robustness
  9. Install
  10. Requirements
  11. Option 1 — DSH Desktop plugin panel (recommended)
  12. Option 2 — command line (non-desktop profiles only)
  13. Verify it took effect
  14. Updating & uninstalling
  15. Usage
  16. Compatibility & known limitations
  17. Development
  18. Project layout
  19. Docs
  20. FAQ
  21. Credits & license

A Codex-desktop-style "model + reasoning effort" selector for DeepSeek Harness (DSH): it replaces the compact model seat above the composer with a Codex-style popover — one trigger, two panes.

version license tests dsh platform

Rendered preview of the plugin

This image and every other screenshot below are rendered from the plugin's real CSS and class names, extracted verbatim from client.js by test/make-readme-shots.mjs (only the React runtime is replaced). They are not hand-drawn mockups.

中文说明 · English


Table of contents


What it is

DSH ships a tiny model button above the composer. This plugin takes over that seat (via priority: -1) and opens a Codex-style popover with two panes:

Pane Contents
Reasoning effort A pixel-replica Codex effort slider: evenly spaced stops, one colour per stop, a violet stardust rail at Ultra
Model list A provider-grouped two-level drill-down (provider → models + search), each row badged with its level count

One trigger drives both, exactly like the Codex desktop app.

The geometry is not eyeballed. Card height, rail position and size, knob diameter, tick dots and row pitch were all measured frame-by-frame from a 93-frame recording of the Codex desktop app (the recording is 1.385×, so CSS px = recorded px ÷ 1.385); colours were sampled the same way. Full measurements are in docs/SPEC.md.


Features

Reasoning-effort pane

Seven evenly spaced stops, one colour each

  • Evenly spaced stops: re-ordered to whatever levels the model actually declares — as many stops as the model has. The Codex ladder holds up to 7: off → minimal → low → medium → high → xhigh → Ultra
  • One colour per stop: fill, rail tint, heading ink and the collapsed trigger all change together — hue advances monotonically from blue 216.6° to violet 263.9°, instead of only Ultra being special
  • Drag tracks the pointer: while dragging the knob follows continuously (no snapping); snapping happens on release
  • Keyboard: ←/→ or ↑/↓ move one stop, Home/End jump to either end
  • Ultra: the rail switches to an animated violet gradient plus 22 stardust particles, and shows a "consumes quota faster" warning for 1.8 s
  • Fast toggle (⚡): visual only — it does not change real speed or usage
  • Reset button: restores the model's default level
Fast on / off

Fast toggle compared on and off

The same high level, side by side — the ⚡ turns from an outline into a solid bolt, the rail lights up with stardust particles, and the collapsed seat grows its own ⚡. None of it changes real speed or usage.

Model pane (two-level drill-down)

Model pane drill-down

Level Contents
First Provider list; the provider holding the current model carries a blue dot on the right. Each row is just provider name + dot + trailing chevron
Second Slides in after tapping a provider: back button + provider title + search box + that provider's models
  • Each model row carries an "N levels" badge; models without reasoning support are greyed out as "unsupported"; the current model shows a check mark
  • Search: case-insensitive, with non-contiguous in-order character matching (the same rule the official menu uses)
  • Switching model lands on the middle level: floor(n/2) — 4 levels → the 3rd; models with no levels omit the reasoningEffort key entirely (absent means "no opinion", which differs from an explicit null)
  • Provider load failures are surfaced, not swallowed: once this plugin shadows the official seat, the official per-provider failure messages become its responsibility — a failing row reads {name} failed to load: {message} instead of a blank panel
  • Escape steps back one level at a time: second → first → effort pane → closed

Two details that are easy to miss

Fast tooltip and the Ultra warning

  • The Fast tooltip is a position:fixed layer: as a child of the popover it would be clipped by overflow:hidden, so it is rendered as a fixed layer and positioned dynamically
  • The Ultra warning's timing: the moment you cross into Ultra from a non-Ultra level, the middle column and both tool buttons fade out while the warning fades in with a single sweep (1.1 s, 0.2 s delay); it then stays for 1.8 s before fading back to the level heading. So the warning appears immediately rather than after 1.8 s. Staying on Ultra does not re-trigger it, and leaving Ultra restores the heading instantly (no need to wait out the 1.8 s)

Dark mode

Dark mode: one colour per stop

Same layout, same real CSS as the other shots — rendered on the shipped dark surfaces: the seven levels live, the Ultra warning state and the collapsed seats. In the dark theme the entire palette is re-solved against the shipped dark surfaces:

  • Theme detection reads the official signal: the data-ds-dark-theme attribute on <body> (set by the official theme boot script; every shipped dark token keys off that same selector). It deliberately does not use prefers-color-scheme — when the user pins "dark" while the OS is light, the media query disagrees with the palette actually in force. A MutationObserver subscription recolours live when the theme is switched in settings, no reload needed
  • All seven stops re-solved: v1.0.0's light inks all fall below AA on the dark card (high reached only 1.83:1 — practically invisible). The dark table keeps the same hue axis (blue → violet) but luminance now rises with effort — on a dark card, "darker" means "less visible"
  • The rail becomes an inset groove #232324: the pale #e8e7e8 was an 11.3:1 glare bar on the dark card
  • The knob becomes a bright puck: the token it inherited resolved to #2c2c2e in dark, nearly identical to the track — the knob simply vanished
  • The Ultra warning sweep inverts: the light theme sweeps a dark band over violet text, which drops below contrast on a dark card, so dark sweeps a light band; all 15 gradient stops were lifted above the track floor too
  • The light theme is bit-for-bit unchanged: both palettes are validated against every WCAG floor by test/palette.mjs (24 assertions), with a regression guard against light values leaking back into the dark table

Accessibility & robustness

  • role="slider" with aria-valuemin/max/now/valuetext; the popover carries aria-expanded/aria-haspopup/aria-busy and is inert while closed
  • Every fill and ink clears its WCAG floor in both themes (asserted per-item by test/palette.mjs, once per palette)
  • Dark mode: see the section above — official signal detection, shipped dark surfaces, live recolouring on theme switch
  • Fail-open: it registers no host hooks, emits no events and touches no official classes or text. If any anchor is unrecognisable (directory not ready, not its own session, subagent session) it does nothing, leaving the official UI intact

Install

Requirements

  • DSH 0.2.1-rc.2 (also runs on 0.2.0-rc.2; the label child slot degrades gracefully)
  • No build step: client.js is the release artifact — hand-written and statically auditable

The desktop profile is managed exclusively by Electron, so it cannot be installed from the CLI. Use the panel:

  1. Copy this directory anywhere on your machine (do not move or delete it afterwards)
  2. Open DSH Desktop → Settings → Plugins → Add plugin
  3. Enter the full path of this directory (not a zip, not package.json)
  4. Tick the trust confirmation → Install → Enable now
  5. If prompted to restart, fully quit and reopen DSH Desktop

Option 2 — command line (non-desktop profiles only)

dsh plugin --profile web add link:"<absolute path to this directory>"
dsh --profile web --dump-config    # confirm dsh-codex-model-effort-selector appears

Verify it took effect

After restarting DSH, open the model name below the composer — it should expand into the Codex-style popover (effort slider + model list).

dsh plugin --profile desktop list | Select-String codex-model-effort

Updating & uninstalling

  • Updating: reinstall from the panel / point it at the new directory, then fully quit and reopen DSH Desktop.
  • Uninstalling: from the panel, or dsh plugin --profile <profile> remove dsh-codex-model-effort-selector.
  • ⚠️ Do not delete a junction with Remove-Item -Recurse — it can follow the link and delete the plugin's own files.

Usage

Collapsed (one button on the right of the composer, still carrying the level colour):

[ ⚡ 6 Astra  Ultra  ⌄ ]

Once open:

Action Result
Drag the rail / tap it Change level; dragging tracks continuously, releasing snaps to the nearest stop
←/→ ↑/↓ / Home End Change level from the keyboard
Tap the middle heading Flip to the model list's first level
Tap a provider row Slide in that provider's model list (with a search box)
Tap a model Switch model, return to the effort pane, landing on the middle level
Esc Step back: second level → first level → effort pane → closed
⚡ Fast visual toggle (visual only)
↻ Reset to the model's default level

Compatibility & known limitations

Item Notes
DSH Verified on DSH Desktop 0.2.1-rc.2 (Windows); 0.2.0-rc.2 still works
Runtime dependencies None (client.js is hand-written, zero-build, zero third-party code)
Theme Follows DSH theme tokens (--dsw-*), falling back to neutral grey + #4d93f8
Platform Verified on Windows; other platforms untested
  • A restart is required (the client bundle is injected at startup)
  • Depends on the official slot contract: if the official conversation.input.model shape changes, this plugin silently falls back to doing nothing rather than breaking the official UI
  • Child-slot declarations are exclusive, and this plugin does not compete for them: conversation.input.model.label exists only while the entry that declares it is mounted — and the official seat entry declares it too. The registry throws slot "..." is already declared for the second declarer. This plugin therefore declares and renders neither that child slot nor anything else: claiming it throws the official registration away, and then switching this plugin off empties the seat, so the model selector vanishes from the composer and the official one cannot come back (it had never registered in the first place). The cost is that the official "1017" badge, which decorates the model name, is not drawn while this plugin holds the seat — the badge is decoration, the selector is the feature. See docs/INCIDENTS.md, incident 20
  • The official model menu loses its entry point: that seat was the only entry to the official menu; switching models still works through this plugin's pane and the official /model command
  • Levels come from the model: the slider only shows the reasoning levels the current model actually declares; with no reasoning metadata the pane says "this model provides no reasoning effort levels"
  • The name "Ultra": the max level is called Ultra in Codex and this plugin keeps that naming; if a model names its top level differently, that model's own name is shown
  • Model ordering follows the host catalog: models the catalog does not list are not shown in the pane

Development

The package itself is dependency-free — no npm install needed:

# syntax check
node --check client.js

# offline assertion suite (47: loading / registration contract / shadowing / geometry /
# palette / theme wiring / fail-open / accessibility / two-level drill-down /
# child-slot ownership)
node test/verify.mjs

# palette assertions (24: the three WCAG floors for BOTH themes + monotonic hue +
# chroma + uniqueness + theme-signal checks)
node test/palette.mjs

# collapse-bug regression (6: runs BOTH the buggy and the fixed state machine)
node test/collapse-regression.mjs

# seat-lifecycle regression (8: replays load -> shadow -> "switch the plugin off"
# against the REAL shipped SlotCore)
node test/seat-lifecycle.mjs
# point it at a custom install with the DSH_ASAR or DSH_RESOURCES env var

# integration against the REAL SlotCore (9; auto-skips when DSH is not installed)
node test/slots-integration.mjs

# encoding guard (26: every text file must be clean, BOM-free UTF-8)
node test/encoding.mjs

# regenerate the README screenshots (extracts the real CSS from client.js; needs Edge/Chrome)
node test/make-readme-shots.mjs

# build the browser preview page (same real-CSS extraction, light and dark side by side)
node test/make-preview.mjs   # then open test/preview.html

# regenerate the dark-mode comparison shot (light vs dark, headless real-CSS render)
node test/make-dark-shot.mjs   # writes docs/images/dark-mode.png

A few deliberate choices in the test bench:

  • test/verify.mjs actually executes client.js, captures the slot it registers, and asserts the registration shape matches the official docs
  • test/palette.mjs reads the real colour values out of the source and computes contrast, so changing a colour re-validates it
  • test/collapse-regression.mjs implements the buggy state machine too: it first proves that version really does expand everything on one click, then proves the fixed one does not — a regression test should not only test the good version
  • test/seat-lifecycle.mjs unpacks the real shipped SlotCore out of app.asar and replays the whole seat lifecycle: the official entry must register successfully, and switching this plugin off must hand the seat back to it. A control case replays the old (buggy) shape to prove that failure path is really being caught
  • test/slots-integration.mjs unpacks the real SlotCore out of app.asar, drives it with this plugin's real registration, and end-to-end verifies shadowing and uninstall recovery

Project layout

client.js                  release artifact: the browser bundle, hand-written, zero-build, auditable
index.js                   host-side half, deliberately empty — it only makes this package a node on the cordis tree
cordis.patch.yml           inserts this package into the profile's composition layer
package.json               declares dsh.bundle.patch and dsh.client (with its inject dependencies)
locale/{zh,en}.json        plugin title & description shown in the plugin manager
icon.svg                   plugin icon
docs/                      architecture, incident postmortems, pixel spec and the images used here
test/                      offline assertions + palette + collapse regression + real-SlotCore integration + shot generator

Docs

Document Contents
docs/ARCHITECTURE.md How it works: seat takeover, state machine, geometry, colour, particle maths, accessibility, test-bench strategy
docs/INCIDENTS.md Incident postmortems: symptom → root cause → fix → assertion. Start here when something looks wrong
docs/SPEC.md Frame-by-frame measurement spec and replica fidelity comparison (original vs this plugin)
CHANGELOG.md Release-by-release changes

FAQ

Nothing happens after installing. First fully quit and reopen DSH Desktop (not just the window). If it still does not work, check the plugin's detail page for an error line — the host JSON.parses package.json directly, so a malformed manifest (a BOM, for instance) gets the whole package discarded and the UI shows nothing at all.

The official model menu still opens instead. That means the plugin was not loaded. Make sure it is actually enabled in the profile and that you fully restarted DSH Desktop. This plugin shadows the official seat with priority: -1, and test/slots-integration.mjs asserts that against the real SlotCore.

The pane says "the host listed no providers". That means the host's model catalog is empty — it is not the plugin's fault: the official /model command is empty at that moment too. Check DSH's Settings → Models / Account: usually there is no available provider, or the account cannot get a token.

The pane says "no session yet — send a message to pick a model". You are sitting on a fresh, empty session shell, where the composer has no session id yet. Send a message, or open an existing session.

The level the slider shows is not what I expected. The slider only shows the levels the current model actually declares. With no reasoning metadata, the pane says "this model provides no reasoning effort levels".


Credits & license

  • The model pane's interaction follows the two-level drill-down of Bai-2008/a-better-dsh-codex-effort-slider (this plugin adopted it to replace an earlier, self-contradictory collapsible-set scheme).
  • The original geometry and palette take a Codex desktop recording as their primary evidence and were cross-checked against other community Codex slider ports; see the evidence section of docs/SPEC.md.
  • The target host is deepseek-ai/deepseek-harness; this plugin only uses its public surface (slots, the modelDirectories service and semantic DOM anchors).
  • Licensed MIT (see LICENSE). No optional third-party code: the bolt and chevron icons are hand-drawn paths, and the particle layer and all CSS are hand-written.