Expose every model already configured in your local DeepSeek Harness to the LAN over OpenAI- and Anthropic-compatible endpoints, with API keys, per-key rate limits, a circuit breaker, model filters and usage reports.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-lantern
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Saretheya/dsh-lantern
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
LANtern 🏮
Expose every model in your local DSH to the LAN.
English · 中文
What it is
Your local DeepSeek Harness already has a set of AI models configured — various providers, each with its own login state and quota. Your phone, tablet, or second computer can't use them, unless you copy every key onto every device.
LANtern solves exactly that: it runs a small OpenAI / Anthropic-compatible gateway inside your local DSH and exposes the models DSH already knows about to your LAN.
┌──────────────┐ ┌───────────────────┐ ┌────────────────┐
│ phone/tablet │ ───► │ LANtern gateway │ ───► │ provider APIs │
│ 2nd computer │ ◄─── │ (inside local DSH)│ ◄─── │ (existing auth)│
└──────────────┘ └───────────────────┘ └────────────────┘
your LAN your machine upstream
It adds no accounts, changes none of your DSH configuration, and touches no other plugin: it reads the model registry DSH already exposes, then forwards requests.
| You might wonder | Answer |
|---|---|
| Do I re-enter every provider key? | No. It reuses the providers and login state DSH already has |
| Will it modify my DSH settings? | No. All data lives in the plugin's own directory |
| Who on the LAN can use it? | Only devices holding an API key you issued |
| How do I name a model? | <model>@<provider>, e.g. deepseek-v4-pro@buddy (see below) |
Quick start
1. Install
You need Node.js and dsh:
npm install -g @deepseek-ai/dsh
Install the plugin (replace web with your profile name — usually web):
dsh plugin --profile web add dsh-lantern
2. Restart dsh and refresh the page
dsh --profile web
A newly added bundle is not hot-applied to a running process — a restart is required. After restarting, refresh the browser page.
3. Open the settings panel
Gear icon at the bottom of the sidebar → a new LANtern entry appears on the left.
4. Four steps to a working setup
| Step | Where | What |
|---|---|---|
| ① | Status & Security → Service toggle | Turn the master switch on (off by default) |
| ② | Status & Security → Listeners & ports | Pick standalone, bind 0.0.0.0, any port (e.g. 3081) |
| ③ | Status & Security → API Keys | Click "Generate key" and copy the plaintext immediately (shown once) |
| ④ | Phone / another computer | Call it with the address and key below |
5. Call it from another device
Replace <YOUR_PC_IP> with your LAN IP (ipconfig on Windows) and <PORT> with the port from step ②:
# OpenAI-compatible
curl http://<YOUR_PC_IP>:<PORT>/v1/chat/completions \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json" \
-d '{"model":"<model>@<provider>","messages":[{"role":"user","content":"hello"}]}'
# First, see which models are available
curl http://<YOUR_PC_IP>:<PORT>/v1/models \
-H "Authorization: Bearer <YOUR_KEY>"
How to name a model (the most commonly missed step)
LANtern enforces a fully-qualified name: <model>@<provider>.
deepseek-v4-pro@buddy ✅
deepseek-v4-pro ❌ a name without @provider always returns 404
Why: the same model name can come from several providers (different accounts, quotas, or regional variants). Without the suffix there's no way to know which one you mean, so it's mandatory. The 404 response includes the list of available models — just copy one.
You can see the model list and its suffixes in two places:
- Browser:
GET /v1/models - Settings panel: each provider page on the left lists its models' public names
Exposed endpoints
| Endpoint | Method | Description |
|---|---|---|
/v1/models |
GET | Model catalog (supports ?provider=<slug>) |
/v1/models/{id} |
GET | Single model detail (capabilities and provenance) |
/v1/chat/completions |
POST | OpenAI-compatible inference endpoint (streaming / non-streaming) |
/v1/messages |
POST | Anthropic-compatible endpoint (off by default, enable in settings) |
/v1/lantern/health |
GET | Health probe (no auth required) |
The Anthropic-compatible endpoint is off by default. Enable it under Settings → LANtern → Status & Security → Protocol channels when you need a client such as Claude Code that only speaks the Anthropic protocol.
Client configuration
| Client | Base URL | Key |
|---|---|---|
| Tools speaking OpenAI protocol | http://<IP>:<PORT>/v1 |
your generated key |
| Tools speaking Anthropic protocol | http://<IP>:<PORT> |
your generated key (x-api-key header) |
The default key format is sk- followed by 43 base64url characters (same shape as major
vendors). The plaintext is shown only once at generation; the server stores only a sha256
and it can never be displayed again — save it on the spot.
What the settings panel does
| Section | Purpose |
|---|---|
| Service toggle | Master switch (off by default); when off, all LAN requests are rejected |
| Listeners & ports | Add/remove/edit listeners. standalone = the plugin opens its own port; reuse = share DSH's port |
| API Keys | Generate / rename / enable / disable / delete / rotate (new key active, old key kept for a grace period) |
| Limits & concurrency | Max concurrency, per-key requests/tokens per minute, max_tokens cap, and slots reserved for the local machine |
| Model & source filters | Denylist (matches are hidden) + allowlist (when non-empty, only these are exposed) |
| Protocol channels | Enable the Anthropic-compatible endpoint |
| Image input | Whether to accept images (off by default; see below) |
| Usage stats | Token usage per key and per call type; exportable as an HTML report |
| Diagnostics | Export redacted diagnostics; inspect in-flight requests and breaker state |
| Danger zone | Reset breaker, factory reset, stop all listeners (each requires confirmation) |
About reuse mode (important)
reuse shares the port DSH itself is already listening on. Its bind address is determined by
DSH's --host start-up argument, and this plugin cannot change it.
DSH listens only on 127.0.0.1 by default, so the LAN cannot reach it in that case. To expose
reuse to the LAN you must start DSH with --host 0.0.0.0.
If you want the simple path, use standalone — the bind address is fully under the plugin's
control and does not depend on how DSH was started.
The "Bind" column in settings shows the address actually in effect (not your configured value) and marks whether it was inherited from DSH.
Isolation and safety
The design principle is read-only reuse, write only to its own directory.
Write footprint
| Location | Written? | Notes |
|---|---|---|
<plugin dir>/data/ |
✅ the only place | config, keys, usage ledger, reports |
~/.dsh/settings.yaml |
❌ never | registers no settings namespace |
| Other plugin directories | ❌ never touched | |
| Your sessions and history | ❌ never modified | |
The llm registry |
👁 read-only | reads the model list, modifies nothing |
Uninstall leaves nothing behind
dsh plugin --profile web remove dsh-lantern
The plugin ships its own assembly layer (cordis.patch.yml). Uninstalling removes the package
from the profile's dsh.profile.bundles, so the assembly layer is no longer loaded —
no dangling insert line is left behind. Deleting the plugin directory takes all of data/
with it: zero residue.
Uninstalling also requires a dsh restart + page refresh.
The one effect that cannot be removed
Shared upstream quota. LAN requests and your local conversations draw on the same upstream account quota. That is the premise of the value it provides (reusing existing login state), and it cannot be avoided.
Protections are built in for this (next section), aiming to be "rather let the LAN be slower than slow down the local machine".
Protections: breaker, rate limits, filters
| Mechanism | Behavior |
|---|---|
| Concurrency gate | Caps total LAN concurrency; slots reserved for local guarantees the local machine always has room |
| Per-key rate limit | Requests per minute (rpm) and output tokens per minute (tpm); exceeding returns 429 |
| Circuit breaker | After consecutive upstream rate limits the LAN channel pauses (during cooldown: immediate 503, no upstream call, no quota burned); half-opens automatically afterwards |
| Model filters | Denylist hides matches; a non-empty allowlist exposes only those. Filters apply both to /v1/models and to direct calls (hidden does not mean bypassable) |
| Capability gate | Unsupported requests (e.g. an image sent to a non-vision model) are rejected before reaching upstream, wasting no quota |
The breaker triggers only on rate limits. Insufficient balance (402) or network timeouts do not trip it — those are not signs of being flooded.
Image input and its impact (off by default)
Off by default. While off, image requests return 400 immediately with zero disk writes.
When enabled, image bytes are briefly written to local disk (the plugin's own attachment directory) and reclaimed by the ledger once the request finishes.
Why writing to disk is unavoidable: DSH's attachment service requires an image to be stored first so a reference can be handed to the model. This plugin cannot bypass that layer.
Deletion risk and three safeguards: the ledger only cleans up objects that this plugin created and that are no longer referenced; objects reused from the local machine are never deleted. Two further checks (reference counting + session reference scanning) apply.
dry-run is the default: it only logs, never actually deletes. After observing for a while and confirming correctness, turn dry-run off in the settings panel.
If you don't need LAN image input, leave it off — that is the safer default.
Usage statistics
Counts only calls that went through this plugin (your local DSH conversations are excluded).
- By time: today / this week / this month / this year
- By API key: calls, token breakdown, hit rate per key
- By call type: inference / capability test / performance test
- Exportable as a self-contained single-file HTML report (no external resources; easy to archive or share)
Accounting notes (spelled out because they're easy to misread):
- Input is split into miss and hit parts, which do not overlap
total = miss + hit + cache write + output(reasoning tokens are already inside output; not double-counted)- Cache hit rate is
Σhit / Σ(miss + hit)— weighted, not a per-row average - With no cache data it displays
—(not 0%)
Naming rule: why model names carry @provider
In DSH's model registry, model and provider are two independent dimensions. LANtern joins them
into <model>@<provider> as the public name because:
- Uniqueness: the same model name may come from several providers
- Readability: you can see at a glance which account a request will use
- Explicitness: it prevents the silent case of "thinking you used A while actually using B"
Even a sole provider does not omit the suffix — a uniform rule never becomes ambiguous when a new provider appears.
Troubleshooting
The LAN can't connect
- Check the bind address first: Settings → LANtern → Listeners & ports, look at "Bind"
- Shows
127.0.0.1→ bound to localhost only; the LAN cannot connect by definition - Shows
0.0.0.0→ binding is correct; the problem is the firewall or the network
- Shows
- Check the firewall: Windows prompts on first listen — allow "Private networks"
- Confirm the port:
standaloneuses the port you entered;reuseuses DSH's own port - Are both devices on the same subnet? (is the phone on the same Wi-Fi?)
401
Wrong or missing key. Check the header: OpenAI style uses Authorization: Bearer <KEY>,
Anthropic style uses x-api-key: <KEY>.
404 with an unknown-model message
The model name lacks the @provider suffix. The response body includes the available list —
copy one from it.
429
A rate limit was hit (per-key rpm/tpm) or upstream throttled. For the former, adjust limits in settings; for the latter, wait out the cooldown.
503
Likely the circuit breaker (consecutive upstream rate limits). Settings → Diagnostics shows the breaker state and remaining cooldown.
An error event appears mid-stream
Upstream failed. LANtern surfaces it as an in-protocol error frame rather than disguising it as a "normally finished, empty answer".
Responsibility boundary: it only surfaces errors DSH itself perceived. A provider's silent behavior (for example ignoring a parameter) is forwarded as-is and never fabricated.
Known limitations
- Shared upstream quota — LAN requests and local conversations share quota; a design premise that cannot be removed.
- Depends on DSH's model registry — providers DSH hasn't registered are invisible to LANtern too.
- A capability declaration is not a gateway allowance — a model declaring support for a capability doesn't guarantee upstream actually works. Use the model page's "Capability test" to verify (real calls, consumes quota).
- Capability and performance tests consume upstream quota — and test results never auto-rewrite capability declarations; they are for your reference only. To change a declaration, tick the checkbox on the capability row yourself.
Compatibility
| Item | Requirement |
|---|---|
| DSH | verified on 0.1.5-rc.2 |
| Platform | web |
| Node.js | ^22.19.0 || >=24.0.0 |
| Dependencies | @deepseek-ai/dsh-llm (provided by the DSH environment) |
License
Links
More in this category
V1ki/dsh-plugin-subscriptions★ 373
Use ChatGPT (Codex), Claude, and Grok subscriptions as DeepSeek Harness LLM providers, with Settings login, model catalogs, usage, plus image_generate, video_generate, and x_search tools.
Mars-Sea/dsh-commandcode-provider★ 289
Unofficial Command Code LLM provider: registers a `commandcode` route with a live model catalog and reasoning-effort support.
corrinehu/dsh-workbuddy-connect★ 140
Brings the models in the WorkBuddy desktop app straight into DeepSeek Harness — zero configuration in the DSH chat.
volcengine/ark-cli#ark-plan-api★ 133
Registers Ark Agent Plan, Coding Plan and postpaid model routes in the native DSH model picker.
franksong2702/dsh-codex-connect★ 114
Connect ChatGPT OAuth and OpenAI Codex models to DeepSeek Harness, with opt-in search and image tools.
Stormycry-cryp/dsh-AuthInOne★ 104
Adds account login, API and custom Provider setup, model switching, image fallback for text-only models, and token/cost attribution to DeepSeek Harness 47f.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.