dsh-llm-capabilities
DSH plugin: auto-detect and configure model capabilities (reasoningEfforts + input modalities) for llm-pi-ai. Successor to dsh-reasoning-efforts.
- Stars
- 1
- Language
- TypeScript
- Created
- Aug 26, 2026
- Updated
- Sep 30, 2026
Introduction
dsh-llm-capabilities
DSH plugin: auto-detect and configure model capabilities (reasoningEfforts + capacities) for
llm-pi-ai. Successor todsh-reasoning-efforts— one panel patches the fields the officialllm-pi-aiUI leaves unconfigurable for self-hosted gateways.
English | 简体中文
Why
The official llm-pi-ai settings page for a custom provider only lets you edit contextWindow. What actually matters for routing stays hidden:
- Thinking levels (
reasoningEfforts) — whichoff/minimal/low/medium/high/xhigh/maxthe model accepts, and the wire spelling (max: ultra) each level maps to. Without this the intensity slider says “this model provides no selectable thinking levels”.
Vision (input) used to be the second gap, but the official UI declares input modalities itself now — so since 0.2.0 this plugin leaves input strictly alone (existing values are preserved untouched on every save).
If you came from dsh-reasoning-efforts: keep it, it still works. dsh-llm-capabilities serves both endpoints (/model-capabilities/raw-models and legacy /thinking-levels/raw-models) so old clients keep working. New installs should use this package.
What it does
- One settings page
Settings → Model Capabilities - Auto-detect on selection: picking a provider fetches
GET {baseURL}/modelsserver-side (credential resolved host-side, no key leak) and parses:supported_features: ["reasoning"],supported_parameters,supports_reasoning,reasoning_effort… → reasoningcontext_length,max_output_tokens… → capacities
- Catalog-aware: merges
llm.models()(pi-ai's own knowledge) as a complementary source — the endpoint wins for yes/no, the catalog refines the level set and stands in when the endpoint says nothing. - Configured state echoes back: re-detection never erases saved
reasoningEfforts— models already configured show their own levels (custom wire spellings included) whether or not the endpoint lists signals. - New models: endpoint lists a model you haven't configured yet → it shows up with a
not configuredbadge, saving will add it. - Vision preserved:
inputis never written or removed; whatever the official UI declared stays as-is. - Sticky save bar: a single “Save changes” button pinned to the bottom of the list while you scroll — no scrolling back to the top; frosted-glass surface so list content never bleeds through.
- Safe writes:
settings.mutatewithexpectedRevision, deep-clonedmodelsarray, no other provider fields are touched.
Install
# published
dsh plugin --profile web add dsh-llm-capabilities
# local dev
dsh plugin --profile web add link:D:\AllCode\dsh\dsh-llm-capabilities
Requires DHS >= 0.1.1-rc.2, node >= 22.13.
Usage
- Add your provider in
Settings → Modelsas usual (route, baseURL, apiKeyEnv). - Open
Settings → Model Capabilitiesand pick the provider — detection runs automatically. - Toggle
Offer thinking levelsper model: pickoff/low/medium/high/xhigh/max; customize wire spellings via the pencil icon when needed. - Save changes → writes to
llm-pi-ai(providers.<route>.models). The intensity slider updates immediately;input/vision stays however the official UI set it.
Go session header (x-opencode-session)
OpenCode Go requires third-party agents to send a stable x-opencode-session
header for prompt-cache affinity (docs).
The host half wraps globalThis.fetch and sets it on requests whose URL contains
opencode.ai/zen/go (covers ocg-c / ocg-r / ocg-a). All other requests
pass through untouched.
- Conversation keying (default):
ses_+ 32 hex derived from the dsh conversation id (sha256), so the same chat keeps the same OpenCode session across restarts. Title calls use an isolated namespace; compaction rides the conversation's namespace because it replays history (reasoningencrypted_contentis bound to the account that issued it). - Encrypted-reasoning self-heal: if the gateway still answers
400 … reasoning 'encrypted_content' was not issued to this caller(session→account affinity lost on the gateway side), the wrapper retries once with the replayed encrypted reasoning items stripped instead of failing the turn. Opt out withsessionHeaders.encryptedContentRetry: false. - Never touched:
user-agent(staysdeepseek-harness/...— Go requires honest identification) andauthorization. - Disable without uninstalling (profile
cordis.patch.yml):
- insert:
- id: model-capabilities
name: dsh-llm-capabilities
config:
sessionHeaders:
enabled: false
Configuration shape written
llm-pi-ai:
providers:
my-gateway:
baseURL: https://gateway.example/v1
api: openai-completions
models:
- id: gpt-4o
input: [text, image] # vision — written by the official UI, preserved by this plugin
reasoningEfforts: # thinking — written by this plugin
off: null
low: low
medium: medium
high: high
- id: text-only-model
input: [text]
reasoningEfforts: false # explicitly no reasoning
How detection works
llm.discoverModels is intentionally narrowed host-side to id/name/contextWindow/maxTokens. This plugin adds a host route that returns the raw listing:
GET /model-capabilities/raw-models?route=<route>
It reads baseURL/apiKeyEnv from your own llm-pi-ai settings, resolves the credential via ctx.credentials, fetches {baseURL}/models, and returns {ok, data} without ever echoing the secret. Only routes you already configured are reachable (browser trust fence: rejects cross-site, checks Origin/Host).
Development
pnpm install
pnpm run typecheck
pnpm run build
pnpm run dev # watch
The web bundle is a window.__ModuleLoader__.load({id, factory}) closure — not ESM — so keep client imports to platform modules (react, @deepseek-ai/dsh-client-*) only.
Relation to dsh-reasoning-efforts
| dsh-reasoning-efforts | dsh-llm-capabilities (this) | |
|---|---|---|
| reasoning auto-detect | ✅ | ✅ (ported, strict TS) |
| vision | ❌ | handled by the official UI; input preserved on save |
| host route | /thinking-levels/raw-models | /model-capabilities/raw-models + legacy compat |
Migrate when ready — both can coexist during the transition.
License
MIT © hank reed (bamboostrip)