lion231226/dsh-freellmapi

dsh-freellmapi

FreeLLMAPI (Auto) provider route for DeepSeek Harness: pick the auto model and a local FreeLLMAPI gateway chooses the best free model, fails over automatically, and streams back as native DSH chunks.

미분류auto-routingdeepseek-harnessfailover

dsh-freellmapi

Free-tier multi-provider routing for DeepSeek Harness. Eighteen provider entries behind one DSH provider route — five of them enabled by default, and all five of those with a free tier — plus a per-request complexity classifier, capability/speed ranking, and automatic 429/5xx failover.

install the plugin  →  start DSH  →  pick provider "freellmapi" + model "auto"  →  it answers

No external process. No gateway to run. No API key required to get a first answer — the built-in pool contains keyless providers that the maintainer verified with real, credential-free requests.

Unofficial plugin — please read

This is a community project. It is not affiliated with, endorsed by, sponsored by or supported by any of the upstream providers it talks to, nor by DeepSeek. It is not an official DSH plugin. Provider names and "DeepSeek Harness" / @deepseek-ai/* belong to their respective owners. Licensed under the MIT License. You are responsible for complying with each provider's terms of service, and for what you send to a free tier that may log and train on it.


Table of contents

Quick start

  1. Install — open the DSH web GUI, go to the Plugins manager in the sidebar, install the bundle and paste one of these specs:

    github:lion231226/dsh-freellmapi          # from GitHub (no npm publish needed)
    github:lion231226/dsh-freellmapi#v0.7.18   # pinned to a tag
    dsh-freellmapi                            # from npm, once published
    

    CLI equivalent, and the manual path, are in Install details.

  2. (Optional) paste a few keys — see Adding keys. Skipping this step is a supported configuration: the two keyless providers still answer.

  3. Pick the model — in the DSH model picker choose provider FreeLLMAPI (Auto) and model auto.

  4. Watch it work — with logRouting on (the default) every request logs one line:

    dsh-freellmapi: tier=complex -> openrouter/nvidia/nemotron-3-ultra-550b-a55b:free (capability=5, speed=3, 2 candidates tried)
    

That is the whole setup. Everything below is detail.

Install details

MethodHow
Plugins manager (recommended)Sidebar → Plugins → install → paste github:lion231226/dsh-freellmapi (or the npm name once published), confirm, restart if DSH asks. The manager accepts registry names, absolute paths, git addresses and tarball URLs, and refuses any spec whose manifest declares no dsh.bundle — this package declares one.
dsh plugin CLIdsh plugin --profile desktop add github:lion231226/dsh-freellmapi — the subcommands come from DSH's own runtime error text; ⚠️ not verified end-to-end here, so confirm against your DSH build.
Manual placementCopy the package to $DSH_HOME/profiles/<profile>/node_modules/dsh-freellmapi/, add it to both dependencies and dsh.profile.bundles in the profile's package.json, then run dsh plugin --profile <profile> install and restart. Listing it in bundles is what makes DSH load it at all.

The package ships its own cordis.patch.yml (- insert: [{ id: freellmapi, name: 'dsh-freellmapi' }]), so a correctly installed bundle needs no hand-written patch row. Never add a second row with the same id in the profile's own cordis.patch.yml: two entries with one id abort bundle composition with duplicate loader entry id "freellmapi".

What you get

Self-contained routingThe plugin owns the routing intelligence. direct mode (the default) needs no gateway, no daemon, no Docker — nothing but DSH itself.
A built-in pool of 17 providers / 46 modelsGroq, Cerebras, OpenRouter, Mistral, Z.ai (Zhipu), Volcengine Ark, LongCat, LLM7, Kilo, NVIDIA NIM and OVH AI Endpoints, plus the six 0.7.0 additions — ModelScope (魔搭), iFlytek Spark (讯飞星火), SiliconFlow (硅基流动), SambaNova, HuggingFace Router and Routeway — with per-model capability, speed, context window, vision and tool-calling facts. Only five of the seventeen are enabled out of the box.
Per-request complexity routingEvery request is scored and classified simple / medium / complex; the tier decides whether capability or speed dominates the ranking. A one-line "list the files here" and a 12-tool refactoring task do not go to the same model.
Automatic failoverA rate-limited (429), broken (5xx), timed-out or model_unavailable candidate is parked and the next-ranked one is tried — only before the first chunk has been sent, so a single answer is never spliced from two models.
Health tracking and cooldownsThe first retryable failure already parks the candidate (2 minutes), and each further consecutive failure escalates one step: 2 m → 10 m → 1 h → 24 h. Retry-After from the provider always wins; a rejected credential (401/403) parks that provider for 24 h. The scoring penalty decays with a 60-second half-life, so a single 429 is never a life sentence.
Zero-config startKilo and LLM7 answer without any credential; a fresh install has a non-empty candidate pool before you configure anything.
Honest diagnosticsOne log line per request names the tier, the winner and how many candidates were tried; every failure is an in-band finish chunk with a stable code, never an exception.
No silent substitutionsPicking a concrete model id is a strict pin: if it has no credential, does not exist, or is cooling down, you get an explicit error with instructions instead of somebody else's answer. The model list shows only what can actually serve, plus one setup:<provider> signpost per unkeyed provider.
gateway mode kept intactThe v0.1.0 behaviour — forwarding to an external freellmapi gateway — is still there behind mode: gateway, unchanged, for people who want the gateway's own bandit scoring, caching and fusion.

Zero config: what works with no key at all

The pool marks three providers keyless (no Authorization header at all). The maintainer verified them with real requests on 2026-10-13 (_lead/live-probe-2026-10-13.md), sending no credential:

ProviderModelResult
kilo (https://api.kilo.ai/api/gateway/v1)nvidia/nemotron-3-super-120b-a12b:freeHTTP 200, real completion
kilonvidia/nemotron-3-ultra-550b-a55b:freefree route, 550B class
ovh (https://oai.endpoints.kepler.ai.cloud.ovh.net/v1)gpt-oss-120bHTTP 200
llm7 (https://api.llm7.io/v1)DeepSeek-V4-Flash-0731HTTP 200 (anonymous)
llm7codestral-latestHTTP 200 (anonymous)

kilo and llm7 are enabled by default, so those two carry the "install and it works" promise. ovh is keyless too but disabled by default (2 requests/minute — far too tight to be in the default candidate set); enable it explicitly if you want it.

Three things you should know before you rely on a keyless pool:

  • Keyless is not unlimited. Kilo is documented at ~200 requests/hour per IP, LLM7 at ~100/hour, OVH at 2 RPM. Quota numbers throughout this document come from the upstream registry baseline (2026-04) and may be stale; the plugin reacts to them rather than trusting them — a 429 parks that candidate and moves on.

  • Free traffic may be recorded and used for training. Kilo states this in its own provider registry (free prompts and completions are logged for training). If that is not acceptable to you, disable it:

    providers:
      kilo:
        enabled: false
    

    With kilo off, zero-config still works through llm7 alone.

  • Free model catalogs drift. During the 2026-10-13 probe, llm7 answered 400 model_unavailable for gpt-oss-20b and GLM-4.6V-Flash — the same kind of id that worked the day before. The router treats model_unavailable as a per-model condition: it parks that (provider, model) pair and tries the next candidate instead of failing your request. Seeing one of these is normal operation, not a bug.

Adding keys

Keys are the single biggest quality upgrade: the default candidate pool is groq, cerebras, openrouter, kilo, llm7, and adding the first three gives you the strong-and-fast models.

Set a key by exporting the environment variable named in apiKeyEnv — or store it through DSH's credentials service, which is the first thing resolution consults. Resolution order is: credentials service → config.providers[id].apiKey → environment variable named by apiKeyEnv → the convention <PROVIDER_ID>_API_KEY. Empty values count as "not configured".

Where to edit in the GUI. Settings → Models lists this plugin's provider route (FreeLLMAPI) as an ordinary card, and the key rows render inside that card — one per upstream that is enabled but has no usable credential, each with a link to its key page, the environment-variable name it will be stored under, a password field and Save. Saving writes through DSH's credentials service, which is read without caching, so the key takes effect on the next request with no restart.

Two notes on the host's own editor, so its behaviour is not mistaken for a bug:

  • The 编辑 button next to that card opens DSH's provider editor, which can only render fields for its own two provider families (llm-deepseek, llm-pi-ai); for any other namespace it shows "other fields live in cordis.patch.yml" and disables Save. That is a current host limitation and the key rows above are the answer to it.
  • The card shows a missing-credential dot while the root apiKeyEnv (FREELLMAPI_API_KEY) is unset. Direct mode needs no such key — the dot refers to the optional gateway credential and can be ignored.

The full field set (including the per-upstream providers map and the routing knobs) is on the plugin's own settings page: Settings → Plugins → dsh-freellmapi, generated from the schema; it is also editable in cordis.patch.yml.

The card offers a key field for the pool's default-enabled providers (groq, cerebras, openrouter, llm7, kilo). The browser table is generated at build time from those defaults, so a provider you enable yourself through providers.<id>.enabled will not grow a key row — set its apiKeyEnv and store the credential directly in that case. That applies to all six providers added in 0.7.0, which ship disabled on purpose: enabling one of them does not make a key row appear in the card (this is the known gap recorded in the 0.6.1 changelog, not a bug).

Which providers are actually reachable — and how to ask

Free-tier access is network-sensitive, so this section tells you how to measure, not what to believe. It previously carried a table claiming groq and cerebras were region-blocked "and a key changes nothing". That table was wrong, and the reason is worth keeping: the probe sent no Authorization header. Measured 2026-10-15:

ProviderNo credentialSyntactically valid but bogus key
groqHTTP 403HTTP 401 → reachable, it just wants a real key
cerebrasHTTP 403HTTP 401 → reachable
ovh (keyless)HTTP 200HTTP 403 → sending a key breaks a keyless provider

So a 403 is never self-explanatory. It means one of three different things depending on what you sent: anonymous request rejected, credential rejected, or region rejected. Reporting one without the other is how the wrong table got written.

Probe correctly, per provider:

  • keyless providers (llm7, kilo, ovh) — send no Authorization header; 200 is healthy.
  • key-required providers — send a syntactically valid but bogus Authorization: Bearer … header; 401 is healthy (the endpoint exists and refused politely), 200 is fine too.
  • Treat 403 as ambiguous: it needs a second probe with the other method before it means anything.
  • 404/400/422 usually mean the path differs, not that the host is down.

The request body is part of the method too. Probing a chat endpoint with a model id that does not exist yields the opposite conclusion: modelscope answers 400 Invalid model id and sambanova answers 404 model_not_found, and neither can be told apart from "this provider's chat needs no credential" — the request never reached the credential check. Replaying the same request with a real id from the catalog returned 401 from both. Same URL, same headers, different body, opposite verdict.

The gate automates the header split: node tools/gate.mjs --online → G7 probes every pool provider with the method that suits it and prints the method beside each result, so the verdict can be audited rather than trusted. G7 asks GET {baseURL}/models only, so read its green as "the host answers", not as "this provider will serve you a completion once you add a key".

G10 answers the question G7 cannot. A host that answers says nothing about whether the specific model ids we ship are still served — and that gap let the same defect ship three times: four OpenRouter ids and two Groq ids that the provider had retired, each found by a person rather than by the gate. G10 compares every declared id against what the catalogue actually lists. For a default-enabled provider a dead id blocks; for a default-disabled one it is reported as latent and blocks the moment that provider is enabled. Catalogues that need a credential are recorded as not checked — a 401 is not evidence that an id is missing.

Measured this way on 2026-10-15:

  • All 12 pre-0.7 pool providers answer (G7, 2026-10-15) — including the two the old table called blocked.
  • The six 0.7.0 additions, probed anonymously and with a bogus header: modelscope, xfyun, siliconflow, sambanova and huggingface are reachable. routeway's anonymous chat path is the one open AMBIGUOUS: without a credential its POST /chat/completions gets a Cloudflare interstitial (403, HTML, "Attention Required"), while the same request with a bogus Authorization header reaches the origin and gets a plain-JSON 401 Invalid API key. A single response cannot separate anonymous rejected from region rejected; a real key, or a second egress IP, would settle it. Nothing in this README depends on that path being open.

Which egress each probe used is part of the result. The probe host runs a VPN in split-tunnel mode: traffic to mainland-Chinese destinations goes out direct, everything else goes through the proxy, whose exit is US-Los Angeles. The LA exit is the overseas path's location — it is not the host's location, and this README does not claim otherwise. So the results above split by path, and the split is stated rather than left to inference:

  • Direct (within mainland China): modelscope, xfyun and siliconflow — the three mainland-Chinese hosts in the 0.7.0 additions. Each answered its probe over the direct path (xfyun and siliconflow returned 401 to the bogus-credential probe; modelscope was also reached, see the doc-site note below).
  • Through the proxy (overseas exit, US-LA): the other three additions and all 12 pre-0.7 providers. routeway's Cloudflare behaviour is measured on this path, which is why its 403 still cannot be separated from a region rejection — see the AMBIGUOUS note above.
  • The application-layer verdicts (401, RFC 6750 credential challenges, origin error JSON) do not depend on the egress at all; "reachable from your network" does, which is why the path is recorded per provider.

A tool limit is not a property of the site. An earlier draft of this section reasoned from a failed web_fetch of a Chinese vendor documentation site — "hostname resolves to a non-public IP address" — to "Chinese vendor and documentation sites are unreachable from this machine." That inference was wrong. The message is this project's own DNS guard: a split-tunnel VPN answers DNS with fake-IP addresses from the reserved benchmarking range 198.18.0.0/15, and web_fetch refuses to fetch any hostname that resolves there. It rejected www.xfyun.cn and github.com in the same session with the same sentence, which is what exposes it as a guard rather than a measurement. Re-measured (2026-10-15) over the direct path with an ordinary HTTP client:

HostResult over the direct path
www.xfyun.cn (vendor doc page)HTTP 200, 128 382 B, 618 ms, Server: Tengine
modelscope.cnHTTP 200, 4 639 B, 290 ms
spark-api-open.xf-yun.com (POST /v1/chat/completions)HTTP 401, {"message":"HMAC signature cannot be verified: apikey not found"}
siliconflow's api.siliconflow.com (GET /v1/models)HTTP 401, {"code":30014,…"message":"Token is invalid."}

So the Chinese documentation sites were always reachable from this machine; only web_fetch could not read them. Any verdict about "sites in country X cannot be reached" needs a reachability measurement (an HTTP client speaking to the host), never a fetch-tool error, because the latter reports the tool's policy and the former reports the network.

And when a provider genuinely is unreachable from your network, turning it off is the whole change, in $DSH_HOME/profiles/<profile>/cordis.patch.yml (or the plugin's own providers field):

- id: freellmapi
  config:
    providers:
      someprovider: { enabled: false }

Key request pages (the first seven were verified reachable on 2026-10-14): groq https://console.groq.com/keys, cerebras https://cloud.cerebras.ai/, openrouter https://openrouter.ai/keys, mistral https://console.mistral.ai/api-keys, zhipu https://open.bigmodel.cn/usercenter/apikeys, volcengine https://console.volcengine.com/ark, longcat https://longcat.chat/platform/api_keys. The six 0.7.0 additions' key pages below are the pool's own keyURL values and were not independently verified — only modelscope's carries an upstream citation. (An earlier draft justified that gap with the claim that Chinese vendor/documentation sites "resolve to non-public addresses" from this machine. That claim is withdrawn: it was a web_fetch DNS-guard artefact, and the vendor doc page was in fact fetched over the direct path — see the reachability table above. The key pages themselves remain unverified; only the reason previously given for that was wrong.)

ProviderapiKeyEnvKey pageFree tier (upstream baseline)DefaultNotes
groqGROQ_API_KEYhttps://console.groq.com/keys30 RPM / 1000 RPD / ~8K TPM / 200K TPD✅ onFast; openai/gpt-oss-120b, qwen/qwen3.8-27b, openai/gpt-oss-20b (ids re-measured 2026-10-15: two older ids had been retired upstream)
cerebrasCEREBRAS_API_KEYhttps://cloud.cerebras.ai/30 RPM / no RPD / 60K TPM / 1M TPD✅ onTwo models rated capability 5 and speed 5; upstream notes a second, tighter quota revision (5 RPM / 30K TPM)
openrouterOPENROUTER_API_KEYhttps://openrouter.ai/keys20 RPM / 200 RPD✅ onThe complex tier's main supplier (nvidia/nemotron-3-ultra-550b-a55b:free, nemotron-3-super-120b-a12b:free, cohere/north-mini-code:free)
llm7LLM7_API_KEYhttps://token.llm7.io/~100 req/hour✅ onKeyless works; a key raises the ceiling. Key page is reported, not independently verified
kiloKILO_API_KEY—~200 req/hour per IP✅ onKeyless works. Free traffic is logged for training
mistralMISTRAL_API_KEYhttps://console.mistral.ai/api-keys500K TPM⬜ offmistral-large-latest, magistral-medium-latest, codestral-latest
zhipuZHIPU_API_KEY—~1M TPD⬜ off · ⚠️ speculativeglm-4.7-flash, glm-4.6v-flash. baseURL is the single riskiest row in the pool: the mainland and overseas sites do not share keys, and picking the wrong hostname gives a guaranteed 401. Enable it only if you know which site your key belongs to.
volcengineARK_API_KEY—~2M TPD⬜ off · ⚠️ speculativeChinese provider, real-name registration required; the model id is inferred, not verified
longcatLONGCAT_API_KEY——⬜ off · ⚠️ speculativeNo real-name requirement; model id inferred, not verified
nvidiaNVIDIA_API_KEY—40 RPM⬜ offHigh capability ceiling (qwen/qwen3-coder-480b-a35b-instruct), but "evaluation only" trial ToS, first token can take minutes, and see the known gap in Limitations
ovhOVH_AI_ENDPOINTS_KEY—2 RPM⬜ offKeyless works, but 2 RPM only
modelscopeMODELSCOPE_API_KEYhttps://modelscope.cn/my/myaccesstokenUpstream wrote 2 000 req/day, then marked that figure historical (billing moved to magic grains 魔粒)⬜ offAlibaba's ModelScope (魔搭). A token only serves traffic once the account is bound to an Alibaba Cloud China-site account with Chinese real-name verification — unbound tokens answer 401 please bind your alibaba cloud account before use on every call. Its GET /models answers 200 for any credential, so it cannot validate a key; use a 1-token chat instead
xfyunXFYUN_API_KEYhttps://console.xfyun.cn/No published token/QPS ceiling — upstream sets null rather than guessing⬜ off · ⚠️ speculativeiFlytek Spark (讯飞星火). Chinese real-name verification required. Auth is the console APIPassword as a bearer token. Its model id lite is now vendor-documented, not a placeholder: the official HTTP API page (spark-api-open.xf-yun.com/v1, fetched 2026-10-15 over the direct path) lists 4.0Ultra / generalv3.5 / max-32k / generalv3 / pro-128k / lite as the accepted model values, and its version table describes Lite as 轻量级 and 支持免费使用 (supports free use). Still not exercised with a valid credential here; confidence stays speculative
siliconflowSILICONFLOW_API_KEYhttps://cloud.siliconflow.com/account/akNo recurring free chat tier — the .com endpoint is a one-time $1 trial credit⬜ off · ⚠️ speculativeSiliconFlow (硅基流动). Chinese real-name verification required. Upstream keeps the platform for its free generative-media models (image/TTS), which this plugin's LLM seam cannot consume; the chat model it once listed is paid ($0.06/M) and upstream deleted that row
sambanovaSAMBANOVA_API_KEYhttps://cloud.sambanova.ai/apisNone — the free tier was permanently retired. With a vali