Docs
DeepSeek Harness
DeepSeek Harness keeps its own DeepSeek card and adds yours beside it. Five fields under “Add model provider” → “Custom model API” put Claude and GPT in the same model picker, on one key.
Settings → Models → “Add model provider” → “Custom model API” takes five fields — Provider ID, display name, base URL, API protocol and API key — and puts Claude and GPT in the same picker as the built-in DeepSeek card.
# Settings → Models → Add model provider → Custom model API
#
# Provider ID kunavo (lowercase, and permanent)
# display name Kunavo
# base URL https://api.kunavo.com/v1
# API protocol OpenAI Chat Completions (openai-completions)
# API key sk-kn-...
#
# Then Model catalog → Fetch available models → Add selected,
# or type the ids by hand. The page writes the active profile's
# $DSH_HOME/profiles/<profile>/cordis.patch.yml — profile "web" under
# `dsh web`. The same provider there, plus an optional second one
# that sends Claude ids over Anthropic Messages, whose base URL has
# NO /v1. This entry replaces the whole llm-pi-ai config: keep any
# provider already in it.
- id: llm-pi-ai
config:
providers:
kunavo:
apiKeyEnv: KUNAVO_API_KEY
api: openai-completions
baseURL: https://api.kunavo.com/v1 # → /v1/chat/completions
models:
- id: claude-sonnet-5
- id: claude-opus-5
- id: claude-haiku-4-5
- id: gpt-5-6-sol
kunavo-claude:
apiKeyEnv: KUNAVO_API_KEY
api: anthropic-messages
baseURL: https://api.kunavo.com # → /v1/messages
models:
- id: claude-sonnet-5
- id: claude-haiku-4-5openai-completions takes https://api.kunavo.com/v1; anthropic-messages takes https://api.kunavo.com, no /v1, because dsh appends /v1/messages itself. A run of dsh 0.2.0-rc.2 against a recording stand-in settled both: the first posted to /v1/chat/completions, the second to /v1/messages?beta=true from the bare root — and to /v1/v1/messages when its base URL kept /v1, which a real gateway answers with a 404, not an auth error.reasoningEfforts changes the system prompt's role, not whether it arrives. The harness documents that a model declaring reasoning has its system prompt sent as role: "developer". Kunavo reads that role as the system turn on every family, Claude included, so no compat switch is needed for it. Until 2026-09-30 the Claude path dropped the role, and this card told you to set compat.supportsDeveloperRole: false; if you did, it is harmless and can stay.deepseek- ids, and use this one for the Claude and GPT ids in the table below. It also means the compat.thinkingFormat: deepseek switch the harness documents for “DeepSeek V4 behind an OpenAI-compatible gateway” has nothing to act on here.dsh_session_log, the session's events including your working-directory path, and dsh_plugin_packages. In the run, neither custom provider sent either one, so a Kunavo provider does not receive them. What they weigh and the switch that turns the upload off are on DeepSeek Harness pricing.curl below is the Kunavo half, and you can check it in ten seconds; dsh is a developer preview and still moving.sk-kn-) and add credit from $10 — calls are paid from that balance, and failed calls are not billed. The dashboard then opens on the DeepSeek Harness setup.Step by step
- Create a key at
/app/keysand copy it — it is shown once. - Start the Web UI (
dsh web) and go to Settings → Models. Choose Add model provider. The card opens on Third-party model provider, which only lists the providersdshships with — switch it to Custom model API. - Fill in Provider ID (lowercase, and permanent — the docs say requests, saved sessions, model defaults and credential references all use it, and renaming means adding a new provider and deleting the old one), display name, base URL
https://api.kunavo.com/v1, API protocol OpenAI Chat Completions and API key. The key is write-only; dsh keeps it in$DSH_HOME/.credentials.yamland stores only a reference to it in the profile. - Under Model catalog, choose Fetch available models — Kunavo answers
GET /v1/models, so the picker fills itself. Tick what you want and choose Add selected. Ids typed by hand work just the same, and the docs say to fall back to that whenever discovery lists nothing. - Optional: for Claude ids over Anthropic's own protocol, add a second custom model API with its own Provider ID, base URL
https://api.kunavo.com— no/v1— API protocol Anthropic Messages and the same key. Fetch lists the whole catalog here too; add only theclaude-ids (why only those). - Pick a model in the composer and send a turn that touches a file, not a greeting — the harness leans on tool calling for most of what it does, so a first run that reads and edits something tells you more. Model changes take effect on the next request; the docs are explicit that nothing needs a restart.
Checked against the DeepSeek Harness “Configure models” page (the same text as docs/user/guide/providers.md at tag dsh-v0.2.0-rc.2) on October 1, 2026. Third-party settings move; if a field name here no longer matches what you see, that page is the authority, not this one.
Verify before you debug the client
One request settles whether a failure is the endpoint, the key, or the configuration file. If this returns JSON, the same base URL and key work in DeepSeek Harness.
# Settles whether a failure is the endpoint, the key, or the client.
curl -sS https://api.kunavo.com/v1/models \
-H "Authorization: Bearer sk-kn-..."Which model id to put in the field
Every text model is reachable as a model id — the live list is GET /v1/models, and the catalog with prices is on the models page. Rates are USD per 1M tokens, input / output.
| Model id | Kunavo in / out | Where it fits in DeepSeek Harness |
|---|---|---|
claude-sonnet-5 | $1.40 / $7.00 | the default working model for sessions that edit files |
claude-opus-5 | $3.50 / $17.50 | planning a change that would be expensive to get wrong |
claude-haiku-4-5 | $0.70 / $3.50 | cheap turns — triage, summaries, the loop that runs all day |
gpt-5-6-sol | $2.00 / $12.00 | a second opinion from another family, same key and same provider |
gpt-5-6-terra | $0.70 / $4.20 | long inputs, where the per-token rate is what decides the bill |
Claude over Anthropic Messages
Kunavo answers the Anthropic Messages API as well, and anthropic-messages is one of the three protocols the form offers. The harness's docs are plain that “A provider speaks one protocol, so a gateway that serves two needs two providers” — so this is a second provider beside the first, not a setting on it.
- Base URL
https://api.kunavo.com, the bare root. In the run, that root posted to/v1/messages?beta=true— the path Claude Code uses too, and one Kunavo answers. With/v1on the end it posted to/v1/v1/messages. DeepSeek Harness vs Claude Code puts the two clients' requests side by side. - Claude ids only. Kunavo's
/v1/messagesservesclaude-ids and nothing else; agpt-id there gets a404that names/v1/chat/completions. Keep GPT on theopenai-completionsprovider. - Fetch lists every model; add only the Claude ones. dsh's
llm-pi-aiREADME says discovery on this protocol asksGET /v1/modelswith Anthropic'sx-api-keyheader, and Kunavo's model list takes the key in that header as it does inAuthorization: Bearer— that comes from dsh's source and Kunavo's own tests, not from the run. What Fetch available models gets back is the whole catalog, GPT and image models included, so tick only theclaude-ids; typing them by hand works the same. A full list proves less than it looks: the same README says the listing URL is accepted with or without/v1, while model requests take the base URL unchanged — so Fetch fills the list fromhttps://api.kunavo.com/v1as well, and the first turn from that base URL goes to/v1/v1/messages. - What it buys you. The request arrives in Anthropic's shape — in the run, the system prompt travelled as the top-level
systemfield, so thedeveloperrole never comes up — and Kunavo hands it on as it is rather than translating it from OpenAI's. Theopenai-completionsprovider reaches the same Claude ids through translation, which is why it is the one in the steps.
The file behind the form
The Models page writes $DSH_HOME/profiles/<profile>/cordis.patch.yml — $DSH_HOME/profiles/web/cordis.patch.yml when you start with dsh web. Older dsh docs pointed at $DSH_HOME/settings.yaml; 0.2.0-rc.2's do not. When the browser is on the same machine as the server, Open configuration file in the Settings header opens it, and the adapters re-read it on the next request. Five things matter for this endpoint:
- Context window and max output tokens — in the form, under Customized settings → Model options. An id typed in by hand carries neither, so the route's fallbacks apply: 262,144 tokens of context and 32,768 of output, per the
llm-pi-aiREADME — and in the run both custom providers asked for exactlymax_tokens: 32768. Every id in the table above takes more; check the row either way, and raise them from the model's catalog entry if you want more. Kunavo bills the tokens the model writes, not the cap. compat.supportsDeveloperRole— not needed. The harness recommends it for gateways that reject thedeveloperrole; Kunavo reads that role as the system turn on every family, Claude included. (Until 2026-09-30 the Claude path dropped it, and this item told you to set the switch — leaving it set is harmless.)compat.maxTokensField— leave it alone. The harness pairs it with the switch above as the usual first fix, but Kunavo's own handler readsmax_completion_tokensand falls back tomax_tokens, so the default already works.reasoningEfforts— no field in the form. A model you enter by hand declares no levels, so the Effort menu does not appear and the endpoint's own default decides whether the model thinks. Declare the levels yourself if you want the menu; onopenai-completionseach key is a level and its value is the spelling sent asreasoning_effort. That reaches agpt-id; on aclaude-id it has no effect, because Kunavo's chat surface does not forwardreasoning_effortto Anthropic (/docs/chat#reasoning).- Input types (
input: [text, image]in the file) — the docs are clear that this “states a claim about your endpoint rather than checking it”. Ticking Image on an id that does not take images is not caught in the harness; the request is refused further along. Check the id on/modelsbefore you tick the box.
The rest of what a session sends — 24 tool definitions per turn, a short title request for every new session, and the extra fields on DeepSeek's own route — is measured on DeepSeek Harness pricing.
Frequently asked questions
How do I add a custom API provider to DeepSeek Harness?
Start the Web UI with dsh web, go to Settings → Models and choose "Add model provider". The card opens on "Third-party model provider", which only lists providers dsh ships with; switch it to "Custom model API". The form asks for a lowercase Provider ID, a display name, a base URL, an API protocol and an API key, then at least one model under Model catalog. The Provider ID is permanent, because requests, saved sessions, model defaults and credential references all use it; renaming means adding a new provider and deleting the old one. In 0.2.0-rc.2 the page saves to the active profile's cordis.patch.yml — $DSH_HOME/profiles/web/cordis.patch.yml under dsh web.
Does the DeepSeek Harness base URL need /v1 at the end?
It depends on the API protocol. For openai-completions, yes: https://api.kunavo.com/v1, which in a run of dsh 0.2.0-rc.2 posted to /v1/chat/completions. For anthropic-messages, no: https://api.kunavo.com, because dsh appends /v1/messages itself — the bare root posted to /v1/messages?beta=true, while a base URL ending in /v1 posted to /v1/v1/messages, which a real gateway answers with 404 rather than an authentication error. The run was against a local stand-in that records requests, not against Kunavo.
Can DeepSeek Harness use Claude or GPT models instead of DeepSeek?
Yes. The API protocol field names the wire format, not the vendor: openai-completions is OpenAI Chat Completions, openai-responses is the Responses API, and anthropic-messages is the Anthropic Messages API. A custom provider passes your model id straight to the base URL you configured, so a Claude or GPT id resolves at that endpoint rather than inside the harness. On Kunavo, an openai-completions provider at https://api.kunavo.com/v1 reaches Claude and GPT ids; a second provider on anthropic-messages at https://api.kunavo.com reaches the Claude ids only, in Anthropic's own request shape. Either sits beside the built-in DeepSeek card rather than replacing it, so DeepSeek ids keep using your DeepSeek key.
What does DeepSeek Harness send to a custom provider?
In a recorded run of dsh 0.2.0-rc.2, both custom providers — openai-completions and anthropic-messages — sent 24 tool definitions with each agent turn, asked for max_tokens 32,768, which is the harness's fallback for a model you typed in without a size, and made one short title request per new session with max_tokens 64. Neither sent dsh_session_log or dsh_plugin_packages: those two fields, the session's event log and the installed plugin list, went only with the built-in DeepSeek route. The run used a stand-in that records requests, not Kunavo, so it shows what dsh sends rather than what any provider does with it.
Why does DeepSeek Harness behave as if it ignored my system prompt?
Check whether the model declares reasoning levels. On openai-completions the harness sends a reasoning model's system prompt as role "developer" rather than "system", because it infers request shape from the endpoint URL and treats an address it does not recognise as OpenAI itself. Kunavo reads that role as the system turn on every model family, Claude included, so on Kunavo the prompt arrives either way. Until 2026-09-30 the Claude path dropped the role silently; if a prompt went missing before then, that was the cause, and compat.supportsDeveloperRole: false on the route or the model in the profile's cordis.patch.yml was the workaround. It is no longer needed, and harmless if it stays. An anthropic-messages provider never sends the role: its system prompt goes as Anthropic's top-level system field.
Why does "Fetch available models" return nothing or a 401 in DeepSeek Harness?
Discovery uses the base URL, protocol and key currently in the form, so a 401 usually points at the key and an empty list at the base URL or at a listing format discovery does not read. The harness documents both outcomes and says to enter the ids by hand, which works identically. The two protocols send the key differently — openai-completions as Authorization: Bearer, anthropic-messages as Anthropic's x-api-key header — and Kunavo's model list accepts either. Settle which half is at fault with a plain curl against https://api.kunavo.com/v1/models using the same key in the same header: JSON back means the fault is in the form, a 401 means the key, a 404 means the URL. On anthropic-messages a full list leaves two things open: whether the base URL is right, because discovery drops one trailing /v1 from the listing URL and model requests do not, and which ids the provider can call, because the list is the whole catalog and only the claude- ids work there. A built-in provider is always answered from the installed catalog even when its base URL points elsewhere, so fetch through a custom provider to see what the endpoint really serves.