Connects locally signed-in Trae CN models to DSH, supports DSH-executed tool calls, account switching, and Work/general credit visibility.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-connect-trae
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:dingminhua/dsh-connect-trae
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
A DeepSeek Harness (DSH) bundle plugin that connects locally signed-in Trae models (both CN and international installs) to the DSH model picker. Trae generates structured tool calls while DSH executes its own local tools, with a usage overview (Work/general credits on CN, subscription status on international), daily check-in claiming, and a model-management panel. The CN and international sides are two parallel providers (trae / trae-global) that can be used at the same time; the settings card separates them with tabs for convenient management.
Runs on macOS, Windows, and Linux: CI runs the full typecheck / test / build matrix on ubuntu-latest and windows-latest, and Windows gets purpose-built handling for directory probing, app version headers, and lock behavior — see Windows notes.
Apart from the daily check-in claim, every query this plugin makes is read-only and consumes nothing; the claim only happens when you click the button.
Features
Dual parallel providers — the CN side registers as the
traeprovider (e.g.DeepSeek-V4-Flash,DeepSeek-V4-Pro), the international side astrae-global(the Gemini/GPT/MiniMax roster), and both rosters appear in the DSH model picker simultaneously: different sessions can each pick a side without interfering.Either side can be switched off — each tab carries its own checkbox (both checked by default): unchecking withdraws that provider from the DSH model picker entirely (not merely hiding the tab) and stops all of its requests; checking it again restores it with the account, directory, picks, and context budgets intact. If you never use the international side, just uncheck it.
Tabbed settings card — the card's top carries a "Domestic / Global" tab bar; each tab holds its own account picker, usage overview, and model management. Account, directory, selection, and unsaved drafts are fully isolated per tab — switching accounts or refreshing models on one tab never touches the other side's runtime catalog or sessions.
Multiplier in the model name — model names show the credit multiplier in Trae's own menu format (e.g.
GLM-5.2 · x0.79), updated with each directory refresh.DSH local tool loop — gets pending structured
tool_callsfrom Traellm_utils_chat, lets DSH execute its own local tools, then returns tool results to the model.Dual-region detection (CN / international) — detects local sign-ins across Trae CN, TRAE SOLO CN, Trae, and TRAE SOLO installs. The region is derived from the credential's own
userRegionclaim (host suffix and edition label as fallbacks), with no manual switch.Region-isolated directories and selections — CN and international each keep their own model directory, enabled picks, image opt-ins, and context budgets, and each provider reads only its own slot.
Vision follows Trae's own declaration — when Trae marks a model
multimodal, the plugin advertises it as such with no manual tick per model (issue #16). The rule is "models you tick ∪ models Trae marks multimodal": models Trae marksfalse, or does not mark at all, stay text-only and can still be turned on by hand. A row enabled automatically is shown ticked and read-only, with the reason. See "Image input" below.Account switching — refreshes the token list and lets users select an account without storing tokens in DSH settings. Your choice is remembered and stays in effect: the plugin will never switch accounts on its own just because the bound one ran out of credit or another usable one was found (switching bills a different person). So when the bound account is exhausted the card says so explicitly and lists the other accounts on this machine that still have credit, with their balances, for you to switch to in one click — instead of showing an empty panel or quietly rebinding you. "Refresh accounts & tokens" now always reports what happened (how many accounts were found, who is still bound) instead of appearing to do nothing. The dropdown names the product (
Trae/TRAE SOLO/ international) so a credential left behind by an uninstalled app is recognisable.Read-only usage and model management — Work/general credits on CN accounts, subscription/trial status on international ones; enable Trae models freely. Read-only queries consume nothing.
Credit-near-expiry notices — the remaining balance only says "how much is left", not "which pack lapses first". The notice escalates by urgency (7 days → 24 hours → already lapsed) and names the amount, the pack count and the soonest expiry, so a user watching only the total never silently loses the pack that expires first. Shown in both the usage card and the composer credits panel on the CN side (the international side is subscription-based and has no per-pack expiry).
One-click test of the enabled models — sends each enabled model one minimal message (a single
ping, no tools) and reports whether it actually answers: works / needs a higher plan (Trae1005; your plan does not cover it) / not served (4001; no SOLO function serves it) / throttled (4011; Trae rate-limited that route) / sign-in rejected / not determined. The last one is a first-class outcome: a timeout, a dropped connection, a proxy or a 5xx is never reported as unusable — only an explicit upstream refusal is a negative verdict. This is the plugin's only operation that spends quota (check-in changes account state but spends nothing), it runs only when you click, and its results live in the current session rather than in your settings.4011is a rate limit, not "this model is unavailable" (measured for issue #33): an exhausted balance reports a different code,4008, so "I still have plenty of credits" and "I got a 4011" are both true at once. A throttled model answers again after a short wait — do not delete it.One-click test of all models — the same probe applied to every visible model of the current region, not just the enabled ones. Use it when you want to know whether the models you have switched off could still answer — after a plan change, or after Trae reshuffles its lineup.
Daily check-in claiming (per account) — on the CN card every account row carries its own check-in button, with the day's reward written on it (
Check in +150). A row reads Claimed and disables itself once that account has been paid, and reads Device used and disables itself when this machine has already spent today's slot through some other account — the two are reported honestly, and the second is never dressed up as "claimed". This is the plugin's only operation that changes account state, and it runs only when you click; one click targets that one row's account and sends exactly one request. There is deliberately no "claim for every account": Trae allows one check-in per device per day (switching accounts does not reset the slot), so a sweep could only manufacture a row of false failures. The international side has no check-in activity, so no button is shown there.Composer readout — the composer tool row (between "Expert" and the model selector) shows a clickable readout, refreshing every 5 minutes. Both regions show it, but they do not show the same quantity, and that is the data's doing: the CN side is credit-based, so it reads "Trae CN · "; the international side is subscription-based and its usage document carries no credit field at all, so it reads "Trae Global · " (e.g.
Pro/Free). The whole readout is clickable and opens a panel showing a TABLE: one row per account in the region — general credits on CN, plan on international — where clicking a row SWITCHES accounts (through the same mergedaccountswrite the card uses, so the other region's selection survives), with the manual refresh button in the panel. It appears only while the session's selected model belongs to this plugin, and renders nothing for any other provider — which is what keeps two connector plugins from competing for one shared row (the sidebar foot is a shared root row; the composer row is per-session and provider-scoped).Secure loopback shim — one random port + in-process random secret per region; the real Trae token is never handed to pi-ai.
How it works
DSH PiAiAdapter (one stack per provider)
-> secure loopback shim (one random port + in-process random secret per region)
-> TraeSoloBridge
-> CN: https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat
-> Global: https://coresg-normal.trae.ai/api/agent/v3/llm_utils_chat
-> Trae SSE / pending function_call
-> OpenAI SSE tool_calls
-> DSH executes local tools and returns their results
The CN and international sides each own a complete runtime stack — credential store, model catalog, wire map, upstream clients, loopback shim, adapter — with visibility filtered by the credential's own region claim, so both regions' accounts can be signed in and used by different sessions at the same time.
Usage overview hits the read-only https://api.trae.cn/trae/api/v2/pay/* and /trae/api/v2/ug/* endpoints (international accounts read their own gateway's subscription status); the daily check-in claim is the one write among them, going to POST /trae/api/v2/ug/checkin_credits/claim with this installation's device id (x-device-id, the same identity the chat path sends). That device id is also what Trae keys the "one check-in per device per day" rule on, so switching accounts does not reset the day's slot. Refreshed tokens are kept per region in $DSH_HOME/.trae-auth.cn.json and $DSH_HOME/.trae-auth.ai.json (two simultaneously signed-in accounts never overwrite each other; the legacy single file .trae-auth.json is still read as a migration source).
See
docs/IMPLEMENTATION_PLAN.md,docs/SOLO_ROUTE_DECISION.md,docs/USAGE_API_RESEARCH.md.
Image input
The plugin declares whether a model accepts images, and DSH uses that to decide whether an attachment is sent as-is or degraded into a text description. That makes the declaration a hard switch, not a cosmetic label.
The rule is the union of two sources (issue #16):
| Source | Meaning |
|---|---|
Trae's own multimodal field |
Trae's own answer to "does this model read images". The Trae IDE's attach-image button is driven purely by this field, with no user switch |
Your manual imageModelIds ticks |
Explicit choice. Even if Trae later stops reporting the flag for that model, your tick is never revoked |
Notes:
- Only
multimodal === trueenables it automatically. A missing field (a directory saved by an older release, a fallback row) or an explicitfalsestays text-only — better to not send than to advertise a capability we cannot confirm. - Automatic does not mean automatic sending. It only lets DSH attach an image to that model; you still choose to attach one per message. The plugin never sends an image on your behalf.
- An automatically enabled row is shown ticked and read-only. Under the union, unticking it would be re-applied immediately, so offering a clickable box would be a lie. There is currently no per-model "force off" escape hatch; if you want one, say so on issue #16.
- Manual ticks still work for models Trae marks
false(or does not mark), which is the escape hatch for "upstream does not say so, but it actually works".
Model coverage
This plugin serves models from Trae's SOLO channel (DeepSeek-V4-Flash-Official, DeepSeek-V4-Pro-Official, GLM-5.3, GLM-5.2, Kimi-K3, MiniMax-M3, Qwen3.8-Max, Doubao-Seed-*, …), for both the domestic and international sides.
The following three models currently come from the TraeCode (Trae IDE) channel and have not been opened up to the SOLO channel:
| Model |
|---|
glm-5.3-flash |
kimi-k2.8-preview |
qwen3.8-flash |
They do appear in the model list (Trae's directory advertises them), but the plugin has no channel that can call them yet. Sending a message with one selected produces an explicit error, for example:
Trae does not serve this model under the SOLO function the request used
(config_name rejected) · Trae code 4001 · upstream: We're sorry, the param is invalid…
That is the upstream refusal stated plainly, rather than a silent failure or something that reads like a network fault. Once Trae opens them up to the SOLO channel the plugin picks them up automatically (the test is simply whether a callable id can be resolved) — no plugin upgrade needed.
Why they are listed now (policy from 2026-10-02): such rows — present in the directory but with no resolvable callable id — used to be hidden outright, on the grounds that showing one only produces an error. But the callable rosters are issued per account tier: not seeing a model on a free account does not prove a paying account cannot call it (the reporter in issue #19 is on Pro, and their account calls models this machine's free account gets gated on). Hiding models the user's own Trae IDE lists is worse than showing an explicit error, so the behaviour is now: list everything, report refusals honestly.
deepseek-v4.1-flashused to be listed here —docs/DS41_CALLABILITY.md(evidence gathered 2026-09-15) measured4001 param is invalidfrom all eight SOLO functions. Trae has since opened it up to the SOLO channel: it now ships in the live directory withwireFunction: solo_work_remoteand calls succeed (verified against a live refresh). This page had not caught up, so it is removed from the table.
Note the distinction:
GLM-5.3is supported (it goes through the SOLO channel), butglm-5.3-flashis not — they are different models.
To use these three models today, use the Trae IDE itself.
Install
dsh plugin --profile desktop add dsh-connect-trae
Or directly via npm:
npm install dsh-connect-trae
Restart the DSH process after install/update/uninstall.
DSH version compatibility
Requires DSH 0.1.7-rc.1 and up (since 2.3.0 the plugin no longer targets hosts older than 0.1.7; the peer dependency ranges are narrowed accordingly).
The peer upper bound is <0.3.0-0, so both the 0.1.7 and the 0.2 line are supported.
Both pi-ai context generations are supported (since 3.4.2)
DSH itself spans two generations of @earendil-works/pi-ai, and which one this plugin resolves to is decided by your install, not by the host — declaring it a peer is correct, but the two generations disagree on what a context looks like:
| legacy (≤0.85) | modern (0.86+) | |
|---|---|---|
| shape | { systemPrompt, tools, messages } |
{ messages: [ {role:'system', content, toolsAdded}, … ] } |
| system prompt | top-level systemPrompt |
only on that system message |
| tool declarations | top-level tools |
only on that system message's toolsAdded |
A mismatch breaks in both directions, so since 3.4.2 the plugin adapts bidirectionally according to the generation it resolved (issue #27):
- Modern host + legacy library: a guaranteed crash — the legacy token estimator has no
role === 'system'branch, so it iterates a string and throwsCannot read properties of undefined (reading 'length'). This happens before the request is built, which is why it looks like "every model crashes, within milliseconds, with all-zero usage". - Legacy host + modern library: no crash, but the modern api reads only that system message and silently ignores the top-level fields — the system prompt and every tool declaration are lost, so the model can never emit a tool call. Quieter, and also fixed.
The generation gate is the copy this plugin resolved (its manifest version; package layout when that is unreadable), not the host version — a nested older copy shadows a newer host copy and vice versa. Startup logs one line, dsh-connect-trae: pi-ai <version> (<generation>); legacy means your install resolved to the ≤0.85 line.
The boundary is 0.86: dist/utils/transcript.js, normalizeContext, the estimator's system branch, and the switch away from the top-level fields all land in 0.86.0 together. Gating on 0.87 misclassifies 0.86.x as legacy and folds a context the modern api consumes natively, destroying the prompt and the tools.
The 0.2.0 line breaks no contract this plugin consumes — a symbol-by-symbol check confirmed every API it imports is unchanged (full 0.2.0-rc.1 impact check). But since 0.1.7 DSH enforces a bundle-level peer gate: a bundle whose peers are unsatisfied is skipped whole, with one line on stderr and nothing in the UI — the plugin simply disappears.
Why the upper bound is not <0.2.0
2.3.1 wrote <0.2.0-0, meaning "not the 0.2.0 release yet". That spelling is wrong: in SemVer 0.2.0-rc.1 < 0.2.0-0, so it excluded the entire 0.2.0 prerelease line, and the plugin went dead the moment a user installed the first 0.2.0 prerelease.
| Spelling | 0.1.7 line | 0.2.0 alpha/rc | 0.2.0 release | 0.3.0 |
|---|---|---|---|---|
>=0.1.7-rc.1 <0.2.0-0 ❌ |
✅ | ❌ | ❌ | ❌ |
>=0.1.7-rc.1 <0.2.0 |
✅ | ✅ | ❌ | ❌ |
>=0.1.7-rc.1 <0.3.0-0 ✅ |
✅ | ✅ | ✅ | ❌ |
tests/dsh-peer-range.spec.ts pins this invariant: every peer range must admit the 0.1.7 and 0.2 lines, must reject the 0.3 line, and <0.2.0-0 may not come back.
Why this needs a dedicated test: a wrong range passes
tsc, every unit test,pnpm install, and CI — only a real host refuses to load it. That is exactly how 2.3.1 shipped green.
0.1.7 rebuilt the settings machinery, and the plugin follows the 0.1.7 contract:
- Host registration:
SettingsForms.configure({auto}, owner)(installSectionwas removed in 0.1.7), and the namespace is the Loader entry id the host actually serves (ctx.fiber.entry?.options.id, falling back totrae) — the harness looks a provider's namespace up by exact match, so hard-codingtraemade the provider read as "not configured". - Client settings surface:
configForms(settingsScopewas removed in 0.1.7).injectdeclares onlyslots/locale; the settings surface is probed withctx.get(). Cordis' dependency gate is hard — anyinjectentry the running line does not provide keepsapply()from ever running (this is why 2.2.0 and earlier sat atpending (waiting for service: settingsScope)on 0.1.7). - Config card slots:
plugins.bundle.config/plugins.row.config(settings.plugin.itemwas removed). - Schema writable marker: writable fields must be marked
volatile()(asVolatile), or 0.1.7's write gate rejects every write; 0.1.7 delivers config values as{get(): T}live references, so every read and merge path unwraps them first (unwrapVolatile/unwrapVolatileDeep). - The collapse caret is pure CSS, with no static primitives icon import — icon naming changes across releases, so a static import is not a stable contract.
Platform support
| Platform | Status | Notes |
|---|---|---|
| macOS | ✅ Fully supported | Development and verification environment; directory names confirmed on a real host |
| Windows | ✅ Supported | CI runs windows-latest; account directory, app version headers, and lock behavior all handled. Verified on a real host (Windows 10.0.22621 + TRAE SOLO CN) — see below |
| Linux | ✅ Supported | Includes the WSL2 + Trae CLI case (issue #5); directory names probed as multiple candidates |
All three platforms share one implementation: account discovery, decryption, region routing, check-in, and usage queries are platform-independent. Platform differences are confined to path resolution (src/paths.ts) and the device fingerprint (src/identity.ts).
Windows notes
- Account data directory: the plugin reads
%APPDATA%\<directory name>\User\globalStorage\storage.json(unrelated to the install directory — this Electron family keeps user data under%APPDATA%even when installed on another drive). It probes several directory spellings in parallel (Trae CNandtrae-cn,TRAE SOLO CNandtrae-solo-cn), and any hit works. Windows file systems are case-insensitive, so the same directory is never probed twice. - App version headers: read from
<LOCALAPPDATA>\Programs\<install directory name>\resources\app\product.json(falling back to<home>\AppData\LocalwhenLOCALAPPDATAis unset) and sent asx-app-version/x-ide-version; if unreadable those headers are omitted and nothing else is affected. - Device fingerprint:
x-device-typeis sent aswindowsandx-os-versionasWindows <release>. The machine id comes from the data directory'stelemetry.machineId(or a siblingmachineidfile), which holds on Windows. - File locking: the host's
dsh-atomic-writehandles Windows exclusive-create semantics (EPERM/EBUSYretries) natively; no plugin-side adaptation is needed. - Raw Chat probing (known limitation):
model-cachedepends on thesqlite3command-line tool, which is not installed by default on Windows; that probe fails and falls back safely. Raw Chat is off by default and does not affect the main flow. - Permission bits: credential copies are written with
mode: 0o600/dirMode: 0o700, which Windows ignores — no error, documented as harmless.
Real-host verification result (2026-09-26, Windows 10.0.22621 + TRAE SOLO CN)
node scripts/verify-windows.mjs passed everything (exit code 0):
| Check | Observed |
|---|---|
| Directory probe | Hit %APPDATA%\TRAE SOLO CN\User\globalStorage\storage.json (all other candidates missing) |
| Account resolution | 1 account resolved, solo / cn region |
x-device-type |
windows ✅ |
x-os-version |
Windows 10.0.22621 ✅ |
x-device-id |
16 digits, all numeric (read from the data directory, not the fallback hash) ✅ |
x-app-version |
0.1.56, read from %LOCALAPPDATA%\Programs\TRAE SOLO CN\resources\app\product.json ✅ |
What this confirms: using win32DirName as the authority is correct — the TRAE SOLO CN spelling really is the directory name on a real Windows host, and both the product.json install-path derivation and every device-fingerprint field hold. In other words "the plugin can read Trae's sign-in on Windows" is now backed by real evidence rather than inference.
Runtime paths beyond the script (same host, 2026-09-26)
The six checks above cover path probing and the device fingerprint only — that is not the same as "the whole plugin works on Windows". So the plugin's main flows were exercised separately, calling the lib/ exports directly (i.e. the product code itself):
| Runtime path | Observed |
|---|---|
TraeCredentialStore.resolve() |
✅ returned a credential, host https://api.trae.cn, token expiry parsed |
identityHeaders() (all headers) |
✅ all 10 headers produced (x-device-id / x-machine-id / x-device-cpu / x-ide-version-code, …) |
| Usage query (read-only, consumes no credits) | ✅ 9 credit packs, total 2050 / consumed 1522.85 |
| Model directory fetch (read-only) | ✅ 42 models returned |
writeFileAtomic |
✅ write + read-back identical |
Concurrent withFileLock |
✅ 8 concurrent writers serialized correctly with no lost updates (Windows exclusive-create semantics are handled by the host — now verified rather than assumed) |
SSE decoding |
✅ splits on \r?\n, so CRLF and chunk splits are both handled |
In short: sign-in, identity, usage, model directory, and concurrent writes are all verified working on Windows.
Known limitation (its behaviour confirmed on this host):
model-cachedepends on thesqlite3command-line tool, which is not installed here; it threwENOENT(spawn sqlite3 ENOENT) and the caller's.catch(() => undefined)fell back correctly — the main flow is unaffected, and Raw Chat is off by default.That module's directory-spelling gap has been fixed (found and fixed in the same round): it used to hardcode the single spelling
Trae CNfor thestate.vscdbpath whilepaths.tsprobes multiple candidates, and it re-derived the directory instead of reusingtraeStorageCandidates. On this host — whose real directory name isTRAE SOLO CN— the old code pointed at...\Trae CN\...\state.vscdb, which does not exist at all; only becausesqlite3is absent did that call necessarily fail and fall back, hiding a wrong path behind a different, plausible-looking error (a missing dependency).The fix derives the cache path from the credential path:
state.vscdbandstorage.jsonshare oneglobalStoragedirectory, so the candidate that yields the sign-in also owns the cache.raw-resolvernow passes the resolved candidate down, so a machine with several editions installed cannot read another installation's model map. Re-measured on the real host: the new code selects%APPDATA%\TRAE SOLO CN\User\globalStorage\state.vscdb(which exists), where the old code only ever looked atTrae CN(which does not).
What remains unverified: this host only had TRAE SOLO CN installed, so the
Trae CN/trae-cnspellings are still unconfirmed on a real host — their absence here only means this build is not installed, not that the spelling is wrong. Windows users running Trae China Edition are still welcome to run the command above and report.
Running Trae China Edition, or a different setup? One command reports it:
node scripts/verify-windows.mjs— it uses the plugin's own build output to list every path it probes on this machine, whether an account can be resolved, and the device fingerprint. Its output is already redacted (user name becomes<user>; account and device ids are reported as shape only) and can be pasted into an issue as-is. Full steps and how to read the result are indocs/WINDOWS_VERIFY_GUIDE.md; step-by-step manual troubleshooting is indocs/WINDOWS_TOKEN_PROBE.md(it only asks for directory and key names, never for tokens). If the directory name still does not match,authFile+editioncan point at the exact path as a workaround.
Development
pnpm install
pnpm run check # typecheck + test + build
Local dev via a link: install to the desktop profile (restart DSH Desktop after editing):
dsh plugin --profile desktop add /path/to/dsh-connect-trae
Acknowledgements
- Wang-JQ77/dsh-trae-api (MIT) — Reference implementation for research into Trae authentication, sessions, and model protocols.
Third-party open-source dependencies
The open-source projects referenced for Trae integration (architecture/protocol research), together with their licenses and compliance notes, are recorded in THIRD_PARTY_NOTICES.md. When introducing new Trae-related external dependencies or reusing code from other projects, update that file accordingly and honor the upstream licenses.
License
This project is licensed under the MIT License. Copyright: Copyright (c) 2026 LaoDing.
Links
More in this category
corrinehu/dsh-workbuddy-connect★ 776
Brings the models in the WorkBuddy desktop app straight into DeepSeek Harness — zero configuration in the DSH chat.
V1ki/dsh-plugin-subscriptions★ 458
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★ 388
Unofficial Command Code LLM provider: registers a `commandcode` route with a live model catalog and reasoning-effort support.
cv-superding/dsh-deepseek-web-login★ 265
Adds a deepseek-web provider that uses chat.deepseek.com web models in DSH, with browser login capture, PoW request signing, SSE streaming, and prompting-based tool calls.
FishBottle7/opencode2dsh★ 191
Exposes OpenCode Zen free models to DeepSeek Harness, with no API key.
WSL043/dsh-codex-subscription★ 172
ChatGPT OAuth provider for Codex models with subscription web search, quota and safe reset, image tools, Fast mode, and model-aware context; no API key or Codex CLI.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.