⚠️ ALPHA TESTING
This is pre-release software under active testing. It works, it is fast, and it has been driven daily for weeks — but interfaces still move between versions, and the browser route depends on a website that changes without notice.
What that means in practice:
- Expect breakage in edge cases. When the page changes shape, the plugin has to change with it.
- Pin the version if you depend on it. Behaviour is versioned; not every push is a stable release.
- Report what you see. A screenshot and the version number is worth more than a description — several of the bugs fixed tonight were found by somebody watching the browser rather than reading a log.
- Not on npm yet. Install from this repository, which is why there is no
npm installline below.Not affiliated with DeepSeek. It drives their public web product exactly as a person would.
Uncle Sam — a harness model provider that drives chat.deepseek.com
Adds deepseek-web as a model provider. A real browser answers each call, so the model runs on the
web account already open in a tab — no API key, no per-token billing.
Replies from the web route are signed 🔴 Uncle Sam. That name is a joke, not a claim: it marks which route answered, and API replies carry no signature at all.
provider deepseek-web
models deepseek-chat · deepseek-reasoner
status ALPHA — pre-release, under active testing
measured first token ~1.9 s · overhead ~0.6 s · a full turn in ~2.5 s
Installing
Uncle Sam is a DeepSeek Harness plugin. It does nothing outside the harness, because it registers a model provider, a host route and two client slots. Installing it into anything else will simply do nothing visible.
Requirements
| DeepSeek Harness | any recent build with the plugin manager |
| Browser | Brave recommended, Edge used automatically when Brave is absent |
| Node | 22 or newer |
From a package registry, once published
pnpm add @operatingtag/dsh-unclesam
Then add the bundle row to your profile so the harness loads it:
# cordis.patch.yml
- insert:
- id: dsh-unclesam
name: '@operatingtag/dsh-unclesam'
config:
port: 9222
defaultModel: deepseek-chat
autoMinimise: true
From a folder
Settings → Plugins → install from a local path
pointed at the folder containing package.json.
What you should see afterwards
- Settings → Uncle Sam — a page with four status rows and a sign-in button.
- Under the chat box —
● DeepSeek Harnessor● Uncle Sam, click to switch. - The model list —
deepseek-chat (web)anddeepseek-reasoner (web)beside the API models.
First run
Click Sign in / open chat. The browser opens and stays visible until you have signed in at chat.deepseek.com. After that it minimises itself and you will not see it again. The session lives in your browser profile, so no password is stored by the plugin.
Removing it
Settings → Plugins → Uncle Sam → Uninstall
Nothing else is written outside the browser profile you signed in to.
Why it is built this way
The obvious route — call chat.deepseek.com's own HTTP API — was attempted and abandoned, because the
site makes every message clear two gates:
| Gate | What it means |
|---|---|
/api/v0/chat/create_pow_challenge |
a proof-of-work challenge per message |
challenges.cloudflare.com/turnstile |
a bot gate on the origin |
Re-implementing either is a losing game: they are obfuscated, they change, and when they change the failure looks exactly like a network fault. The page already solves both for its own requests.
So the adapter never speaks HTTP to the site. It drives the page, and the page does the rest. Auth, PoW and Turnstile stop being our problem and cannot break independently of the site itself.
Verified: a plain HEAD to the site returns 403; the browser path returns answers.
Architecture
index.js the adapter: harness StreamChunk ←→ engine
lib/engine.mjs browser + page: type, send, stream, settle
lib/cdp.mjs Chrome DevTools Protocol client, zero dependencies
lib/prompt.mjs flatten messages + tools into one message; recover tool calls from the reply
Nothing is installed as a dependency. Node 24 ships a global WebSocket and fetch, which is all CDP
needs, so the token stream never leaves the host process and there is no IPC hop per delta.
The four failures that shaped the code
Each of these was observed, not anticipated, and each is now guarded by a comment at the site.
The answer arrived with the previous answer glued to the front. The reader took the last "message-ish" block, and after a failed New chat click the previous transcript was still there. Fixed twice over: the reset is now verified (count of assistant blocks must reach zero), and the reader takes a baseline index so only blocks created after the prompt can ever be returned.
The answer was emitted twice. The DOM held it once; the adapter's fallback branch re-emitted the whole text for a non-prefix change and the consumer appended it. Deltas now emit only characters not yet sent, computed by longest common prefix. A streaming adapter may never resend text.
A
zoompan-style trap in miniature:waitForcalled its probe without a catch, so waiting for the browser's debug port died on the firstfetch failed— the normal state for several seconds. A throw inside the poll now means "not ready yet".The tool call JSON leaked into the transcript. Tool calls are JSON, so a reply beginning with
{or a fence is now buffered; prose streams immediately, and only a buffered JSON reply is turned into a tool call.committed text == concatenated deltasis asserted in the test.
Tool use is emulated, and that is a real limitation
A web chat has no structured tool channel. Tools are described in the prompt, and a call is recovered by parsing a JSON object out of the reply:
{"tool": "get_weather", "arguments": {"city": "Madrid"}}
This works — verified returning get_weather({"city":"Madrid"}) with finish: tool-calls.
But it is weaker than native tool calling. A model that ignores the instruction answers in prose, and the harness sees no tool use that turn. For heavy agentic work the API provider is more reliable; this provider is best for chat, subagents, compaction, and any turn where the model must be the web model.
Configuration
cordis.patch.yml sets:
| key | default | meaning |
|---|---|---|
port |
9222 |
the browser's debugging port |
defaultModel |
deepseek-chat |
used when a call names no model |
showBrowser |
true |
whether the window is visible while it works |
The browser must hold a signed-in chat.deepseek.com session. The first call launches Brave on the
Default profile if the port is not already open. If Brave is already running without a debugging
port, the adapter cannot attach and reports that clearly.
The API-vs-web switch
The model picker is the switch. There is no second control, because a second control that also chose the model would be two sources of truth for one decision.
| Pick this model | Provider | DeepThink | Search |
|---|---|---|---|
DeepSeek Chat (web) |
browser | off | off |
DeepSeek Chat + Search (web) |
browser | off | on |
DeepSeek Reasoner (web) |
browser | on | off |
DeepSeek Reasoner + Search (web) |
browser | on | on |
any model without (web) |
the API | — | — |
A WEB or API badge renders beside the model label so the active route is visible at a glance. When the label is not recognised the badge renders nothing rather than guessing — a wrong badge would be worse than none.
Search is visible in the transcript
With a search model, a turn produces two blocks:
Web search — "What is one technology news headline from today?"
Sources (4): yna.co.kr · fnnews.com · rthk.hk · sina.com.cn
Links: https://k.sina.com.cn/article_... http://www.21jingji.com/article_...
then the answer that used them. The page does not expose the query it typed internally, so the quoted
query is the prompt that triggered the search; the sources and links are read from the page after the
answer settles, from the cdn.deepseek.com/site-icons/<domain> favicons and the external citation
anchors. Because the sources only exist once the answer is finished, search turns buffer instead of
streaming; non-search turns still stream token by token.
The two switches
DeepThink is the reasoning pass; Search lets it read the live web. Both are per-conversation and are
cleared by New chat, which is why the engine sets them after the reset and before typing. They
are also re-read after clicking, because a click is a request rather than a result.
Testing
node test-adapter.mjs # prose path: contract checks
node test-adapter.mjs --tools # tool path: JSON must not reach the text channel
node test-search-adapter.mjs # search path: two blocks, query + sources + answer
node test-engine.mjs "prompt" # raw engine timings
DEBUG_WEB_CHAT=1 node test-engine.mjs "prompt" # per-poll trace
test-adapter.mjs enforces the three rules that matter: a block is opened before any delta, exactly one
terminal finish, and committed text equals the concatenated deltas.
Verification status
| Claim | Evidence |
|---|---|
| Engine drives the page end to end | measured across several prompts |
| Adapter emits a valid chunk sequence | test-adapter.mjs — passes |
| Search emits query + sources + answer | test-search-adapter.mjs — passes, 4 domains + links, 5.0 s |
| Tool emulation recovers a call | passes, get_weather({"city":"Madrid"}) |
| Bundle installs and activates | application: applied, v1.1.0 |
| Row composed into the profile | present in dsh.profile.bundles |
Client half present with dsh.client |
client.js + platform: web, inject conversation UI |
| Model selectable in the GUI | not visually verified — the Web UI answers a bare origin with dsh web authentication required; reopen the URL printed by dsh web, and that URL is not recoverable from this session |
| The WEB/API badge rendered | not visually verified — same reason |
| A live harness turn | not run — needs a model selected in the picker |
The last three need the harness restarted by the user and one model chosen; they cannot be closed from inside the running session.
Changelog
Every released version, newest first, in CHANGELOG.md.
Support
Uncle Sam exists to stop you paying per token. If it has saved you money, and you would like to see the next one built on hardware that does not run out of memory halfway through, you are welcome to buy me a coffee:
A star on the repo or a useful bug report is worth just as much.
Licence
MIT License — Copyright (c) 2026 Darren T. Weldon.
The whole deal, in one line: use it for anything, at any scale, for free. Keep the copyright notice, and don't blame me if it breaks.
No royalties. No copyleft — your own code stays yours. No attribution on your website or splash screen, only in the source and the distributed files. No patent grab. No telemetry. No paid tier holding a feature back. No licence key you are missing out on by reading this instead of buying something.
The LICENSE file is what actually governs, and it opens with the same summary in plain English before the legal wording.
The ask
This is written by one person, at night, funded by nothing. Fifty-one languages were written out by hand and each was tested until it behaved. There is no other revenue model.
If it saved you money or an afternoon:
That is a request, not a condition. MIT obliges you to nothing beyond the notice, the request carries no legal weight, and if you ignore it nothing changes. A free thing that quietly works is worth more than a nagging one.
Earlier versions
3.9.7 to 3.9.11 were published under the Business Source License 1.1. Those grants are irrevocable and remain in force for those versions — anyone who took a copy during that window keeps the terms they took it under. This licence applies from 3.9.12 onward, and 3.9.6 and earlier were MIT too.
Uncle Sam is an independent plugin. It is not affiliated with, endorsed by, or supported by DeepSeek.
@operatingtag/dsh-unclesam v3.9.12
Author Operating Tag
Licence MIT — free for everything, coffee if you feel like it
No comments yet. Be the first to write one.