@dmh2002/dsh-connect-qoder 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md ADDED
@@ -0,0 +1,266 @@
1
+ <p align="center">
2
+ <img src="docs/assets/dsh-connect-qoder-usage-card.png" width="640" alt="dsh-connect-qoder settings card: CN / Global tabs with the account pool, credits, daily check-in and automatic failover" />
3
+ </p>
4
+
5
+ <h1 align="center">dsh-connect-qoder</h1>
6
+
7
+ <p align="center"><b>Connect Qoder (Lingma CN / international) models to DeepSeek Harness, with an account pool, daily check-in, credit readouts and model management.</b></p>
8
+
9
+ <p align="center">
10
+ <a href="README.md">中文</a> ·
11
+ <a href="#installation">Installation</a> ·
12
+ <a href="#how-it-works">How it works</a> ·
13
+ <a href="#configuration">Configuration</a> ·
14
+ <a href="#account-pool">Account pool</a> ·
15
+ <a href="CHANGELOG.md">Changelog</a> ·
16
+ <a href="https://github.com/dingminhua/dsh-connect-qoder/issues">Issues</a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://www.npmjs.com/package/%40dmh2002%2Fdsh-connect-qoder"><img src="https://img.shields.io/npm/v/%40dmh2002%2Fdsh-connect-qoder?style=flat-square&label=npm&color=cb3837" alt="npm version"></a>
21
+ <a href="https://www.npmjs.com/package/%40dmh2002%2Fdsh-connect-qoder"><img src="https://img.shields.io/npm/d18m/%40dmh2002%2Fdsh-connect-qoder?style=flat-square&label=downloads&color=cb3837" alt="npm downloads"></a>
22
+ <a href="https://github.com/dingminhua/dsh-connect-qoder/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/dingminhua/dsh-connect-qoder/ci.yml?branch=main&style=flat-square&label=tests" alt="test status"></a>
23
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/dingminhua/dsh-connect-qoder?style=flat-square" alt="MIT license"></a>
24
+ <a href="https://github.com/dingminhua/dsh-connect-qoder/stargazers"><img src="https://img.shields.io/github/stars/dingminhua/dsh-connect-qoder?style=flat-square" alt="GitHub stars"></a>
25
+ </p>
26
+
27
+ A standalone [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle plugin. It connects your Qoder account's models to the DSH model picker, with an account pool, daily check-in, credit readouts and model management. **The CN and international editions are two parallel providers (`qoder` / `qoder-global`) that can be used at the same time**; the plugin's settings card separates them with tabs.
28
+
29
+ > Qoder does not expose an OpenAI-compatible endpoint (its official SDK is only an agent launcher), so this plugin talks to Qoder's cosy gateway directly, implementing a minimal client for the protocol. No Qoder desktop sign-in is needed — just a PAT (Personal Access Token).
30
+
31
+ ## Features
32
+
33
+ - **Two parallel providers (CN + international)** — the CN edition registers as the `qoder` provider (`gateway.qoder.com.cn`); the international edition registers as `qoder-global` (`api1.qoder.sh`). **Regions with a PAT configured show their models in the DSH model picker**: with a PAT for both regions you get two groups that can be used simultaneously and independently per session. **A region without a PAT lists no models** (no request could succeed, so listing them would only invite failures); configure a PAT and hit "Refresh models" to make them appear — the provider itself stays registered, because its settings card is the configuration entry point. Model names embed the upstream credit multiplier (e.g. `Qwen3.8-Flash · x0.00`), matching Qoder's own model menu.
34
+
35
+ <img src="docs/assets/dsh-connect-qoder-model-picker.png" width="360" alt="The DSH model picker with the Qoder and Qoder Global groups; each model name carries its credit multiplier" />
36
+ - **Settings card tabs** — the card top has "CN / Global" tabs, each with its own PAT, account pool, model management and credits; catalogs, selections and state stay fully isolated per tab.
37
+ - **Per-region kill switch** — each tab has a checkbox on the right (both checked by default). **Unchecking removes that provider from the DSH model picker entirely** (not just hiding the tab): both its adapter route and its "Settings → Models" directory entry are removed. PAT, credits, and selections are **all kept**; re-checking restores everything. Note: a session that had already selected a model of the disabled provider will get `NO_ADAPTER` on its next call.
38
+ - **Model management** — refresh the full model catalog from upstream and toggle each model on or off; each row shows the multiplier (`(0.50x)`), context/output limits, thinking-effort tiers and a free marker, plus "Image" and "Off-able thinking" switches, a context-tier selector and a "Test" button on the right. Selections are written to local config and sync to the model picker immediately.
39
+
40
+ <img src="docs/assets/dsh-connect-qoder-model-management.png" width="640" alt="The model management area: per-model multiplier, max context/output, thinking-effort tiers, and the image / off-able-thinking / context-tier controls with a test button" />
41
+ - **Per-model availability probe** — every model row has a "Test" button that goes straight to upstream: rate-limited (429) → "Rate limited", invalid credential → "Invalid credential", content-policy rejection → "Blocked by content policy", quota exhausted → "Quota exhausted", and other upstream transient failures are classified honestly. Probes run in the background, **credentials never reach the page**; results are not written into settings and do not change your saved selections. **Probes are real requests** (they consume a small amount of upstream credit), so they are per-model and manual only — there is no batch "test all checked" button.
42
+ - **Daily check-in** — Qoder runs an official daily check-in campaign (individual users claim 100 credits a day, valid for 30 days). The account pool has a "Check in all accounts" action: it claims per account, treats an already-claimed day as idempotent success (no double claim, not a failure), and refreshes credits afterwards. Both regions are wired up (the international edition needs the device-risk identity that the local Qoder client ships; when it cannot be obtained the plugin reports that honestly instead of pretending "no campaign today").
43
+ - **Credits (read-only)** — the pool table shows each account's remaining credits and its nearest credit-package expiry (from Qoder's `sash/api/v2/me/usage`, queried with the `jt-` jobToken); packages expiring within three days are highlighted. The query is **read-only** and costs no credits; when it fails (credential rejected / upstream silent) the card says why and offers a way out instead of showing a blank.
44
+ - **Secure loopback shim** — one random port + in-process random secret per region, so the real Qoder PAT **never reaches pi-ai**: pi-ai only talks to the local shim, which performs upstream requests with cosy sessions (jobTokens).
45
+ - **Multi-account pool (PAT)** — a per-region local vault `$DSH_HOME/.qoder-accounts.<region>.json` (0600) merges the settings PAT, environment variable, and vault accounts into one de-duplicated list; the card can add (paste a `pt-…`, validated via a `JobToken` exchange that returns the display name; you may also set your own label), rename accounts, and can pin a specific account to serve (`regions.<region>.accountId`, empty = automatic by settings → env → vault). **Taking an account out of rotation and deleting it are different actions**: untick it in the pool table and save (the PAT stays put, and you can tick it back at any time), or press the row's **Delete** button — a second confirmation erases the stored PAT for good and drops the account from the pool and the region selection. A PAT coming from the app's settings or the environment is not the plugin's to delete, so those rows get no delete button.
46
+ - **Probe-ranked selection and automatic failover** — "Test all accounts" sends a real request per pool member (the target model defaults to an automatically picked multiplier-0 free model in that region) and persists results to `$DSH_HOME/.qoder-pool.<region>.json`; with the pool enabled, the chat stream picks the top-ranked usable account before the first byte (`rankPool`: usability → highest credits → soonest expiry → id) and retries the next usable accounts on failure, writing failures back as `live-request` probes. With the switch off, the plugin neither picks nor swaps for you — it just reports the error.
47
+ - **Account transfer (import / export)** — export a region's vault accounts to a transfer file, preview it, and import by index; cross-region domains (`qoder.com.cn` / `qoder.sh`) are positively matched and non-matching records are counted as `skipped`.
48
+ - **Thinking effort and context budgets** — upstream declares `thinking_config.efforts` per model, and the card lists **only the tiers that really exist** (Qwen-family `low/medium/xhigh`, DeepSeek/GLM-family `low/high/max`); your choice is saved and sent with every request using the same fields the Qoder client uses (dual-written at the top level and in `parameters`). Context tiers likewise come only from the model's declared `context_config`; nothing is invented when a model declares none.
49
+ - **Honest model capabilities** — upstream's `is_vl` is a **platform**-level image claim, not a per-model capability (the built-in catalog says `true` for almost everything, including text-only models). Image input is pre-checked only for models confirmed multimodal in vendor docs, keyed by exact model id; unverified models stay unchecked and the switch tooltip explains why. "Off-able thinking" is offered only where upstream explicitly declares it and the model is not in the known-rejection set. Fewer options beats an option that fails when pressed.
50
+ - **Composer credits readout** — the session input (`conversation.input.left`) shows `QD CN` / `QD AI` with the serving account's balance (e.g. `QD CN · 92`); clicking it opens a per-account balance table where a row click switches accounts. Gated by the per-region "show credits in the composer" switch (default on, opt-out).
51
+
52
+ <img src="docs/assets/dsh-connect-qoder-composer-credits.png" width="360" alt="The composer credit panel: per-account QD CN balances, a refresh button and the last refresh time" />
53
+ - **CLI diagnostics** — `dsh-connect-qoder doctor|status|logout [--json] [--region cn|global]`: `doctor` reports PAT source, cosy fingerprint derivation, pi-ai generation, host heartbeat liveness, and vault account counts; `status` reports credential source, upstream reachability, and remaining credits; `logout` clears a region's vault accounts and pool measurements. No browser needed.
54
+
55
+ ## How it works
56
+
57
+ ```text
58
+ DSH PiAiAdapter (one set per provider)
59
+ -> secure loopback shim (random port + in-process secret per region)
60
+ -> QoderUpstreamClient (minimal cosy-protocol client)
61
+ -> CN https://gateway.qoder.com.cn/algo/api/v2/...
62
+ -> Global https://api1.qoder.sh/algo/api/v2/...
63
+ -> Qoder SSE (OpenAI-shaped deltas)
64
+ -> DSH executes local tools and returns results
65
+ ```
66
+
67
+ The CN and international editions each own a full runtime stack — upstream client, model catalog, loopback shim, adapter — isolated by region configuration (gateway / openapi / catalog paths), so **models from both regions can be available at the same time and used by different sessions**.
68
+
69
+ Credentials are PATs (settings card `regions.<region>.pat`, or environment variables — see [Configuration](#configuration)); the settings value wins. Sessions lazily exchange a **jobToken** on demand: the PAT is used once in that exchange, and every subsequent request carries a cosy bearer (custom Base64 payload + MD5 signature) plus "machine fingerprint" session headers. **The PAT never reaches pi-ai and never leaves the process except in that one local exchange; this plugin neither reads nor writes Qoder desktop sign-in files.**
70
+
71
+ Encoding and signing details (custom alphabet, `n/3` rearrangement, `cosy&<SECRET>&<date>` signature, RSA/AES session wrapping) are documented at the top of `src/cosy.ts`; the error taxonomy (content policy / rate limit / quota exhausted / transient) and how it maps onto DSH retry semantics live in `src/upstream.ts`.
72
+
73
+ ## Installation
74
+
75
+ ### From a published package (recommended)
76
+
77
+ ```bash
78
+ dsh plugin --profile desktop add @dmh2002/dsh-connect-qoder # Desktop
79
+ dsh plugin --profile web add @dmh2002/dsh-connect-qoder # Web
80
+ ```
81
+
82
+ Then **restart that profile's DSH process** (the host half of a bundle loads at startup). You can also search for `dsh-connect-qoder` in DSH's "Plugin market / Bundles" and install it in one click.
83
+
84
+ > The npm package name carries the `@dmh2002/` scope. The plugin's **configuration identity inside DSH does not** — the settings namespace, the `/plugins/dsh-connect-qoder/…` routes and the CLI program name all stay `dsh-connect-qoder`, so anyone upgrading from 0.2.0 keeps their saved configuration with no need to re-enter PATs. But the **browser bundle's module id and the settings card's slot keys are both keyed by the scoped package name** (`plugins.bundle.config` takes the bare package name, `plugins.row.config` takes `<package name>#<row id>`, and the row id stays unscoped); these are injected from `package.json` at build time, so a rename cannot leave them behind.
85
+
86
+ ### From source (development)
87
+
88
+ ```bash
89
+ git clone https://github.com/dingminhua/dsh-connect-qoder.git
90
+ cd dsh-connect-qoder
91
+ pnpm install
92
+ pnpm run check # typecheck + test + build must all pass first
93
+ ```
94
+
95
+ Install the repository directory with `link:` so edits take effect after a DSH restart (without reinstalling the dependency tree):
96
+
97
+ ```bash
98
+ dsh plugin --profile desktop add /Users/dmh2002/DshProject/dsh-connect-qoder
99
+ ```
100
+
101
+ ### Verify the installation
102
+
103
+ - "Settings → Plugins" should show the dsh-connect-qoder card with its CN / Global tabs.
104
+ - `pnpm exec dsh-connect-qoder doctor`, run inside that profile directory, should print both regions' PAT status and fingerprint derivation.
105
+ - After configuring a PAT, "Refresh from Qoder" should fetch the region's real catalog; the model picker shows `Qoder` / `Qoder Global` groups.
106
+
107
+ ## Configuration
108
+
109
+ ### PAT (Personal Access Token)
110
+
111
+ Create a PAT (`pt-…`) in your Qoder account settings. Two ways to provide it — **the settings card wins over the environment variable**:
112
+
113
+ | Region | Where to issue it | Setting | Environment variable |
114
+ | --- | --- | --- | --- |
115
+ | CN (qoder.com.cn) | Console → Settings → API Tokens | Card "CN" tab → Add PAT | `QODERCN_PERSONAL_ACCESS_TOKEN` |
116
+ | Global (qoder.sh) | Console → Settings → Personal Access Tokens | Card "Global" tab → Add PAT | `QODER_PERSONAL_ACCESS_TOKEN` |
117
+
118
+ - The PAT is stored only in the local plugin configuration (`regions.<region>.pat`); it is never uploaded and never enters any network request.
119
+ - A region without a PAT lists no models; the `doctor` command points out which region is missing one.
120
+ - **Step-by-step walkthrough in [`docs/PAT.md`](docs/PAT.md)** (issuing, verification, precedence, troubleshooting).
121
+
122
+ ### Region enable/disable
123
+
124
+ The checkbox on each tab controls whether that region participates in the model picker (opt-out: **only an explicit uncheck disables it**). Disabling only removes the route and the directory entries; configuration is kept.
125
+
126
+ ### Multi-account pool and credential sources
127
+
128
+ Besides the settings PAT and the environment variable, each region has a local vault (`$DSH_HOME/.qoder-accounts.<region>.json`, mode 0600) that stores multiple PAT accounts. The three sources are merged de-duplicated by **settings → env → vault** (a PAT appearing in two sources is listed once, under the higher-priority source).
129
+
130
+ - The card can add (paste a `pt-…`, validated via a `JobToken` exchange that returns the display name; you may also set your own label) and rename vault accounts, and can pin a specific account to serve (`regions.<region>.accountId`, empty = automatic). Deleting a vault account is the pool table's per-row **Delete** button (which asks once before it acts); unticking and saving only takes it out of rotation.
131
+ - The selected credential is used only for the session's jobToken exchange and subsequent cosy requests — it never reaches pi-ai or the page.
132
+ - `logout [--region cn|global]` clears that region's vault accounts and pool measurements (`.qoder-accounts.<region>.json` and `.qoder-pool.<region>.json`).
133
+ - Membership, the batch actions, and the failover rules are covered in the [Account pool](#account-pool) section.
134
+
135
+ ## Account pool
136
+
137
+ One machine often holds several Qoder accounts (several PATs). The **account pool** collapses that into single clicks: check accounts into the pool, then "Check in all accounts" or "Test all accounts"; switch on **Enable account pool** and the plugin automatically fails over to another account when a request fails upstream.
138
+
139
+ **Each region (CN / international) has its own pool**: members, target model, credits and check-in state never cross over — the accounts belong to different providers anyway. **Both regions have daily check-in**: the international campaign is only served to requests carrying device-risk headers, which the plugin obtains from the device-identity component shipped by the local Qoder client; if it cannot be read, the plugin reports that honestly instead of silently degrading to "no campaign today".
140
+
141
+ ### Automatic failover (enabled together with the pool)
142
+
143
+ - **Ranking decides who serves**: with the pool on, **every request** goes to the top-ranked usable account, so "currently serving" may not be the account you pinned.
144
+ - **Failures walk down the list**: when a request fails upstream, the plugin retries the next usable accounts **in order**, until one takes over or all are exhausted — only then is the error surfaced.
145
+ - **What triggers a switch**: 429 rate limits, exhausted quota, rejected credentials (401/403), 5xx gateway/network failures — those are per-account states.
146
+ - **What does not**: an invalid request (HTTP 400) — the same body fails identically for everyone, so retrying only makes you wait; and a stream that has already started is never replayed (bytes are on the wire).
147
+ - **Known-bad accounts are skipped**: members inside a rate-limit cooldown or with a credential the upstream rejected do not participate. **Never-tested accounts count as usable by default** — a freshly added account should not be excluded because nobody pressed "Test" yet.
148
+ - **Your manual choice is never rewritten**: the pool overrides routing at runtime and never writes back your saved selection; turning the switch off restores your pinned account immediately.
149
+ - **Failures are remembered**: a failed request is stored as that account's measurement (including the reset time the upstream states), so the next request starts from a usable account instead of hitting the same wall again.
150
+
151
+ ### What the switch actually controls
152
+
153
+ **It controls "should the plugin decide", not "are you allowed to act".**
154
+
155
+ | | Pool **on** | Pool **off** |
156
+ | --- | --- | --- |
157
+ | Who serves | chosen **automatically** by ranking (usable → most credits → soonest-expiring package → id) | the account **you** pin in the account rows |
158
+ | Request fails | retries the next usable account **automatically** | no switch; the error is reported as-is |
159
+ | Check in all / Test all | available | **still available** |
160
+ | Target test model | editable | **still editable** |
161
+
162
+ The last two rows are deliberate: checking in and testing are **manual actions** on the members you selected — a different question from whether the plugin routes automatically.
163
+
164
+ **The one hard rule**: with zero members checked, batch actions refuse outright. An empty pool never degrades into "then run it on all accounts" — that would be the one reading that spends credits you did not authorize.
165
+
166
+ ### Ranking rules (who serves when the pool is on)
167
+
168
+ 1. **Usable first** — accounts measured unusable (rate-limit cooldown, rejected credential) go to the back and do not participate;
169
+ 2. **Most credits first** — but only with a **fresh reading** (from card polling, a batch test, or a save; valid for 10 minutes). The request path never calls upstream just to rank, otherwise one page of chat becomes N extra requests;
170
+ 3. **Soonest-expiring credit package** — packages expiring within 3 days get spent before they lapse;
171
+ 4. Finally account id, so the order is a total, reproducible sequence.
172
+
173
+ ### The two batch actions
174
+
175
+ - **Check in all accounts** — claims the daily campaign per account. **Status is read before claiming**, so an already-claimed day is marked "checked in" without a write request (idempotent, no double claim); a failure on one account marks that row and **never aborts the batch**. Claiming changes balances, so the region's usage cache is invalidated when the batch ends.
176
+ - **Test all accounts** — one real request per member, answering "can this account use this model right now". The target model defaults to "automatic": a multiplier-0 free model in that region (largest context wins ties). You may pin one — but **testing stays disabled until a free model appears**, so credits are never spent without you knowing. Tests share the per-model probe, so they **have a real cost**.
177
+
178
+ There is **no automatic** check-in: claiming collects a real reward, so it only happens when you press the button.
179
+
180
+ ## CLI diagnostics
181
+
182
+ Run **inside the profile directory that has the plugin installed** (recommended, no global install):
183
+
184
+ ```bash
185
+ cd ~/.dsh/profiles/desktop
186
+ pnpm exec dsh-connect-qoder doctor # PAT source + cosy fingerprint + pi-ai generation + heartbeat + vault counts (offline)
187
+ pnpm exec dsh-connect-qoder status # per region: credential source + upstream model-list reachability + remaining credits (needs PAT)
188
+ pnpm exec dsh-connect-qoder logout --region cn # clear that region's vault accounts and pool measurements
189
+ pnpm exec dsh-connect-qoder doctor --json # machine-readable output
190
+ ```
191
+
192
+ `status` really exchanges a jobToken and fetches the model catalog (then reads usage once, read-only), so it needs that region's environment or vault PAT; `doctor` is fully offline.
193
+
194
+ > **Two things measured on a real machine**:
195
+ > 1. **Do not use a bare path like `node node_modules/dsh-connect-qoder/lib/bin.js`** — as of 0.3.0 the package name is scoped, so the symlink lives at `node_modules/@dmh2002/dsh-connect-qoder`; the old path just gives `MODULE_NOT_FOUND` (a missing **entry file**, not a missing peer).
196
+ > 2. The CLI depends on the host-provided peers (`@deepseek-ai/dsh-atomic-write` / `dsh-home-paths`). Locally, pnpm installs them into the profile's dependency tree, and both `pnpm exec` and an npm-installed tarball work. But that **depends on your pnpm version and `auto-install-peers` setting** — if you see `Cannot find package '@deepseek-ai/dsh-atomic-write'`, that layer did not get installed. Provider registration, the settings card and chat are **unaffected** (they run inside the host, which resolves those names). The sibling workbuddy's issue #47 is the same shape; its fix (host-first, fall back to a built-in copy on demand) applies here too and has not been ported yet.
197
+
198
+ ## Platform support
199
+
200
+ | Platform | Status |
201
+ | --- | --- |
202
+ | macOS | ✅ Verified (development and CI) |
203
+ | Windows | ✅ CI tests pass (upstream uses standard fetch; no platform-specific paths) |
204
+ | Linux | ✅ CI tests pass |
205
+
206
+ ## Supported host line
207
+
208
+ | Host | Peer range |
209
+ | --- | --- |
210
+ | DSH (`@deepseek-ai/*`) | `>=0.1.7-rc.1 <0.2.0-0 \|\| >=0.2.0-rc.1 <0.3.0-0` |
211
+ | Node.js | `^22.19.0 \|\| >=24.0.0` |
212
+
213
+ > Enforced by `tests/dsh-line.spec.ts`: the upper bound must carry `-0` (DSH only ships prereleases; `<0.3.0` would block nothing).
214
+
215
+ ## Security notes
216
+
217
+ - The PAT is stored only in the local plugin configuration (or an environment variable) and **never enters any network request**; external calls always use cosy session tokens (jobToken) plus machine-fingerprint headers.
218
+ - The local shim is a random-port loopback service with an in-process secret, verifying Host / Origin / Content-Type, with a 64 MB request-body cap.
219
+ - The plugin only reads PATs you explicitly provide; it **never reads or modifies** Qoder desktop sign-in/credential files.
220
+ - Diagnostic output (`doctor` / `status` and card probes) is redacted and never prints token-like strings.
221
+
222
+ ## Market listing and presentation
223
+
224
+ The plugin declares an installable `dsh.bundle` manifest (`dsh.bundle.patch: ./cordis.patch.yml`), so `dsh plugin add` works. Community markets usually sync entries from the [Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) registry; the submission draft lives in [`awesome-dsh-plugin-submission/dingminhua__dsh-connect-qoder.yml`](awesome-dsh-plugin-submission/dingminhua__dsh-connect-qoder.yml).
225
+
226
+ **Registry rules** (per [contributing.md](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md)):
227
+
228
+ - One YAML per plugin: `data/plugins/<owner>__<repo>.yml`, with `url` (exactly the repository URL), `name` (`owner/repo`), `category` (a valid value; `model` here), `description.en` (required, ends with a period) / `description.zh` (optional); quote any description containing `: `.
229
+ - The repository must declare the `dsh.bundle` manifest, carry the `dsh-plugin` topic, be at least a day old and actively maintained; descriptions must be factual and free of marketing language — they are checked against the code.
230
+ - The registry's two READMEs are generated, never hand-edited; after changing a YAML, run `npm ci` + `node scripts/generate-readme.mjs` in that repository.
231
+ - A PR carries at most 3 entries; only touch your own.
232
+
233
+ Market screenshots and GitHub README badges are two different mechanisms:
234
+
235
+ - **Market screenshots** are declared, under the current convention, by **`screenshots.json` in this plugin's own repository root** (see [screenshots.json](screenshots.json)); the registry's `data/screenshots.json` is a legacy fallback and takes no new keys.
236
+ - **GitHub badges** come from the Shields image links at the top of this README. **Market icons / placeholders** follow each storefront's own display rules — they are neither an npm field nor a README badge.
237
+
238
+ ## Known limitations
239
+
240
+ - This depends on Qoder's cosy gateway and `sash` endpoints (**not an official public API**); upstream changes may require plugin changes. The protocol behavior is referenced from [Zhengyuuuui/qoder2api](https://github.com/Zhengyuuuui/qoder2api) and independently implemented here — no source is copied.
241
+ - **Daily check-in** is Qoder's operational campaign for **individual** accounts (organization accounts do not participate); the rules and amounts are the vendor's to change. The international campaign requires the device identity that the local Qoder client provides, so **on a machine without the Qoder desktop client, international check-in is unavailable** (the plugin says so explicitly rather than degrading silently).
242
+ - Image input and "off-able thinking" are offered per a **verified** list: capabilities the upstream does not state are never pre-checked, so a model that actually works may need one manual check from you.
243
+ - Per-model and batch tests **send real requests** and consume a little upstream credit. There is no "test all checked" button, and batch actions only touch accounts you explicitly checked into the pool.
244
+ - **The standalone CLI depends on host-provided peers** (`@deepseek-ai/dsh-atomic-write` / `dsh-home-paths`). Locally pnpm installs them alongside the package and `pnpm exec dsh-connect-qoder …` works; if your pnpm configuration does not, the CLI reports `Cannot find package …`. Provider registration, the settings card and chat are unaffected — they run inside the host, which resolves those names. The workbuddy fix (host-first, fall back to a built-in copy) applies here and has not been ported yet.
245
+
246
+ ## Disclaimer
247
+
248
+ - This project is **for personal learning and research only**; it drives only your own Qoder account on your own machine. Do not use it commercially or beyond reasonable personal use.
249
+ - Users must comply with the Qoder / Lingma terms of service; any consequence of using this project (including account restrictions, quota wipe or service interruption) is borne by the user.
250
+ - The author accepts no liability for any direct or indirect loss caused by using or misusing this project.
251
+ - This project is unaffiliated with Alibaba, Lingma, Qoder, or DeepSeek and is not endorsed or authorized by them; names appear only to describe compatibility, and their trademarks belong to their respective owners.
252
+
253
+ ## Acknowledgements
254
+
255
+ This project builds on other people's public work — see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md):
256
+
257
+ - [**Zhengyuuuui/qoder2api**](https://github.com/Zhengyuuuui/qoder2api): reference implementation of the cosy protocol wire behavior (encoding, signing, sessions, fingerprints, jobToken, SSE shapes, error classification). This project **independently re-implements** that behavior in TypeScript and copies no source code; the repository ships no standalone LICENSE and its README points at `wangtufly/QCCG` (GPL-3.0).
258
+ - [**L0NE-6/Qoder-Daily**](https://github.com/L0NE-6/Qoder-Daily) (MIT): reference for the daily check-in protocol (the `campaigns` / `claim` shapes, idempotency, and the international device-risk idea). This project independently implements it from that documentation plus local measurement, copying no source.
259
+ - [**dingminhua/dsh-connect-workbuddy**](https://github.com/dingminhua/dsh-connect-workbuddy) (MIT): baseline for the plugin engineering layout, card interaction model, loopback shim pattern, and npm publishing workflow.
260
+ - [**dingminhua/dsh-connect-trae**](https://github.com/dingminhua/dsh-connect-trae) (MIT): card look & publishing baseline, referenced via workbuddy.
261
+ - [**franksong2702/dsh-codex-connect**](https://github.com/franksong2702/dsh-codex-connect) (Apache-2.0): DSH plugin structure and pi-ai provider registration, referenced via workbuddy.
262
+ - Qoder official SDK type definitions: used only to cross-check usage/header shapes; not distributed.
263
+
264
+ ## License
265
+
266
+ MIT © LaoDing — see [LICENSE](LICENSE).
package/README.md CHANGED
@@ -1,3 +1,266 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <img src="docs/assets/dsh-connect-qoder-usage-card.png" width="640" alt="dsh-connect-qoder 设置卡片:国内版 / 国际版两个 tab,账号池、积分、一键签到与自动换号" />
3
+ </p>
2
4
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
5
+ <h1 align="center">dsh-connect-qoder</h1>
6
+
7
+ <p align="center"><b>把 Qoder(通义灵码国内版 / 国际版)的模型接入 DeepSeek Harness,并提供账号池、每日签到、积分读数与模型管理。</b></p>
8
+
9
+ <p align="center">
10
+ <a href="README.en.md">English</a> ·
11
+ <a href="#安装">安装</a> ·
12
+ <a href="#工作原理">工作原理</a> ·
13
+ <a href="#配置">配置</a> ·
14
+ <a href="#账号池">账号池</a> ·
15
+ <a href="CHANGELOG.md">更新日志</a> ·
16
+ <a href="https://github.com/dingminhua/dsh-connect-qoder/issues">问题反馈</a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://www.npmjs.com/package/%40dmh2002%2Fdsh-connect-qoder"><img src="https://img.shields.io/npm/v/%40dmh2002%2Fdsh-connect-qoder?style=flat-square&label=npm&color=cb3837" alt="npm version"></a>
21
+ <a href="https://www.npmjs.com/package/%40dmh2002%2Fdsh-connect-qoder"><img src="https://img.shields.io/npm/d18m/%40dmh2002%2Fdsh-connect-qoder?style=flat-square&label=downloads&color=cb3837" alt="npm downloads"></a>
22
+ <a href="https://github.com/dingminhua/dsh-connect-qoder/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/dingminhua/dsh-connect-qoder/ci.yml?branch=main&style=flat-square&label=tests" alt="test status"></a>
23
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/dingminhua/dsh-connect-qoder?style=flat-square" alt="MIT license"></a>
24
+ <a href="https://github.com/dingminhua/dsh-connect-qoder/stargazers"><img src="https://img.shields.io/github/stars/dingminhua/dsh-connect-qoder?style=flat-square" alt="GitHub stars"></a>
25
+ </p>
26
+
27
+ 一个独立的 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle 插件。它把 Qoder 账号的模型接到 DSH 的模型选择器,同时提供模型管理与只读的用量概览。**国内版与国际版是两个并行的供应商(`qoder` / `qoder-global`),可同时使用**;插件设置卡片以 tab 区分两者,方便统一管理。
28
+
29
+ > Qoder 官方不提供 OpenAI 兼容端点(其官方 SDK 只是 agent 启动器),因此本插件直连 Qoder 的 cosy 网关,实现了协议的最小客户端,无需在 Qoder 桌面端登录——只需一个 PAT(Personal Access Token)。
30
+
31
+ ## 功能特性
32
+
33
+ - **双供应商并行接入(国内版 + 国际版)** —— 国内版注册为 `qoder` provider(`gateway.qoder.com.cn`),国际版注册为 `qoder-global` provider(`api1.qoder.sh`)。**配置了 PAT 的区域其模型会出现在 DSH 模型选择器里**:两个区域都有 PAT 时两组同时可选,不同会话可以各选一边,互不干扰。**没有 PAT 的区域不列出任何模型**(一个请求也发不出去,列出来只会点到就失败);配置 PAT 后「刷新模型」即可让模型自动出现——provider 本身始终注册着,因为它的设置卡片正是配置入口。模型名内嵌上游积分倍率(如 `Qwen3.8-Flash · x0.00`),与 Qoder 自身模型菜单一致。
34
+
35
+ <img src="docs/assets/dsh-connect-qoder-model-picker.png" width="360" alt="DSH 模型选择器:Qoder 与 Qoder Global 两个分组,模型名带积分倍率" />
36
+ - **插件卡片 tab 切换** —— 设置卡片顶部为「国内版 / 国际版」两个 tab,各含独立的 PAT、模型管理与用量概览;每个 tab 的目录、勾选与状态完全隔离。
37
+ - **可单独关闭任一版本供应商** —— 每个 tab 右侧有一个勾选框(默认都勾选)。**取消勾选即把该供应商从 DSH 模型选择器里彻底撤掉**(不是只藏起 tab):它的路由与「设置 → 模型」页的目录条目一并摘除。PAT、用量与勾选**全部保留**,重新勾选即完整恢复。注意:若某会话此前选中的正是被关闭供应商的模型,该会话再调用会报 `NO_ADAPTER`。
38
+ - **模型管理** —— 从上游刷新完整模型目录,逐项勾选启用或禁用;模型行显示倍率(`(0.50x)`)、上下文/输出上限、思考强度档位与免费标记,行右有「图片」「可关闭思考」开关、上下文档位选择器与「测试」按钮。勾选状态写入本地配置,立即同步到模型选择器。
39
+
40
+ <img src="docs/assets/dsh-connect-qoder-model-management.png" width="640" alt="模型管理区:逐模型显示倍率、最大上下文/输出、思考强度档位,以及图片/可关闭思考/上下文档位与测试按钮" />
41
+ - **模型可用性测试** —— 每个模型行有一个「测试」按钮,直达上游确认该模型此刻能否使用:限流(429)报「被限流」、凭据无效报「凭据无效」、内容审核拒绝报「内容被审核拒绝」、额度耗尽报「额度已耗尽」,其余上游瞬时故障如实分类。探测在后台发起,**凭据不会传到页面**;结果不写入设置,也不改变你已保存的勾选。**它发的是真实请求**(会消耗少量上游额度),因此只能按单个模型手工点,没有「测试已勾选」这类批量按钮。
42
+ - **每日签到** —— Qoder 有官方每日签到活动(个人用户每天可领 100 Credits,30 天有效)。账号池提供「一键签到所有账号」:逐账号领取、已领过的按幂等处理(不重复领取、不算失败),签到后积分自动刷新。国内版与国际版都已接通(国际版需要本机 Qoder 客户端的设备风控组件,取不到时如实报错而不是假装「今天没有活动」)。
43
+ - **积分(只读)** —— 账号池逐账号显示剩余积分与最近积分包的到期时间(来自 Qoder 的 `sash/api/v2/me/usage`,凭 `jt-` jobToken 查询);临期的积分包会高亮。查询是**只读**的、不消耗积分;读不到时(凭据被拒 / 上游未响应)如实说明原因并给出路,不留一片空白。
44
+ - **安全 loopback shim** —— 每区域一个随机端口 + 进程内随机 secret,真实 Qoder PAT **不交给 pi-ai**:pi-ai 只与本地 shim 对话,shim 用 cosy 会话(jobToken)做上游请求。
45
+ - **多账号池(PAT)** —— 每区域一个本地 vault `$DSH_HOME/.qoder-accounts.<region>.json`(0600),把设置 PAT、环境变量与 vault 账号三源去重合并;卡片可新增(粘贴 `pt-…`,经 `JobToken` 交换校验并取显示名,也可自填备注名)/ 改名账号,也可显式选择某一账号服务(`regions.<region>.accountId`,留空自动按 settings → env → vault 选取)。**移出池**与**删除**是两件事:移出池 = 在池表格里取消勾选并保存(凭据留着,随时勾回);删除 = 池表格行内的「删除」按钮,二次确认后抹掉 vault 里的 PAT 并自动退出池与区域选择(不可恢复;设置/环境变量里的 PAT 不归本插件管,不提供删除按钮)。
46
+ - **按实测排序与自动换号** —— 「一键测试所有账号」对池内每个成员发真实请求(目标模型默认自动挑本区域倍率为 0 的免费模型),结果落 `$DSH_HOME/.qoder-pool.<region>.json`;开启账号池后,聊天流在首字节前按 `rankPool`(可用性 → 额度从高到低 → 到期最近 → id)选出服务账号,失败时依次换下一个可用账号重试,失败回写 `live-request` 探测。关掉开关则插件不替你选、也不替你换,失败如实报错。
47
+ - **账号转移(导入 / 导出)** —— 按区域把 vault 账号导出为传输文件,预览后按索引导入;跨区域域名(`qoder.com.cn` / `qoder.sh`)正向匹配,不匹配的记录计入 `skipped`。
48
+ - **思考强度与上下文预算** —— 上游按模型声明 `thinking_config.efforts`,卡片只列出**真实存在**的档位(Qwen 系 `low/medium/xhigh`、DeepSeek/GLM 系 `low/high/max`),选定即保存、每个请求自动带上(与 Qoder 客户端同一发送字段,双写顶层与 `parameters`);上下文档位同样只来自上游 `context_config` 声明的档位,未声明时不渲染控件、不编造选项。
49
+ - **诚实的模型能力** —— 上游的 `is_vl` 是**平台**的图片输入声明而非模型原生能力(内置目录里几乎全是 `true`,连纯文本模型也是)。图片输入按**精确 model id** 的人工核对表只对文档确认多模态的模型预勾选,未核实的一律不勾,并在开关 tooltip 里说明「为什么没勾」;「关闭思考」同样只对上游显式声明、且不在已知拒绝集里的模型提供。宁可少给一个选项,也不要让用户按下去就失败。
50
+ - **composer 积分读数** —— 会话输入区(`conversation.input.left`)按区域显示 `QD CN` / `QD AI` 与当前账号余额(如 `QD CN · 92`),点开是逐账号的余额表,点某一行即切换到该账号;受每区域「在对话框显示积分」开关控制(默认开,opt-out)。
51
+
52
+ <img src="docs/assets/dsh-connect-qoder-composer-credits.png" width="360" alt="对话输入框的积分面板:QD CN 逐账号余额表、刷新积分按钮与最近刷新时间" />
53
+ - **命令行诊断与现场验证** —— `dsh-connect-qoder doctor|status|logout [--json] [--region cn|global]`:`doctor` 报 PAT 来源、cosy 指纹派生、pi-ai 代际、宿主心跳存活与 vault 账号计数;`status` 报凭据来源、上游可达性与当前剩余积分;`logout` 清除指定区域的 vault 账号与账号池测量副本。另有 `pnpm run probe:cosy`(不依赖宿主直连 cosy 链路验证)与 `pnpm run probe:vault`(只读 dump vault / 账号池测量,PAT 只显示掩码)。
54
+
55
+ ## 工作原理
56
+
57
+ ```text
58
+ DSH PiAiAdapter(每个 provider 一套)
59
+ -> 安全 loopback shim(每区域一个随机端口 + 进程内随机 secret)
60
+ -> QoderUpstreamClient(cosy 协议最小客户端)
61
+ -> 国内版 https://gateway.qoder.com.cn/algo/api/v2/...
62
+ -> 国际版 https://api1.qoder.sh/algo/api/v2/...
63
+ -> Qoder SSE(OpenAI 形状增量)
64
+ -> DSH 本地执行工具并回传结果
65
+ ```
66
+
67
+ 国内版与国际版各持一套完整的运行时栈——上游客户端、模型 catalog、回环 shim、adapter——按区域配置(网关 / openapi / 目录路径)隔离,所以**两个区域的模型可以同时可选、被不同会话使用**。
68
+
69
+ 凭据是 PAT(设置卡片 `regions.<region>.pat` 或环境变量,见[配置](#配置)),设置项优先。每次会话按需惰性交换 **jobToken**:PAT 只在这个交换请求里使用一次,之后的对外请求全部携带 cosy bearer(自定义 Base64 载荷 + MD5 签名)与「机器指纹」会话头。**PAT 从不进入 pi-ai,也从不外发到网关之外;本插件不读不写 Qoder 桌面端的本地登录文件。**
70
+
71
+ 模型的 cosy 编码与签名细节(自定义字母表、`n/3` 重排、`cosy&<SECRET>&<date>` 签名、RSA/AES 会话封装)见 `src/cosy.ts` 的头部注释;错误分类(内容审核 / 限流 / 额度耗尽 / 瞬时故障)与 DSH 重试语义的对应见 `src/upstream.ts`。
72
+
73
+ ## 安装
74
+
75
+ ### 从发布包安装(推荐)
76
+
77
+ ```bash
78
+ dsh plugin --profile desktop add @dmh2002/dsh-connect-qoder # Desktop
79
+ dsh plugin --profile web add @dmh2002/dsh-connect-qoder # Web
80
+ ```
81
+
82
+ 装完**重启该 profile 的 DSH 进程**(bundle 的 host 半边在启动时加载)。也可以在 DSH 的「插件市场 / Bundles」里搜 `dsh-connect-qoder` 一键安装。
83
+
84
+ > npm 包名带 `@dmh2002/` 作用域。插件在 DSH 内的**配置标识不带作用域**——设置命名空间、`/plugins/dsh-connect-qoder/…` 路由与命令行程序名都仍是 `dsh-connect-qoder`,所以 0.2.0 之前装过的用户升级后配置照旧生效,不用重填 PAT。但**浏览器 bundle 的模块 id、以及设置卡片两个槽位的 key 都按带作用域的包名建**(`plugins.bundle.config` 用裸包名,`plugins.row.config` 用 `<包名>#<行 id>`,行 id 仍不带作用域),这几处由构建期从 `package.json` 注入,改包名不会漏下它们。
85
+
86
+ ### 从源码构建(开发)
87
+
88
+ ```bash
89
+ git clone https://github.com/dingminhua/dsh-connect-qoder.git
90
+ cd dsh-connect-qoder
91
+ pnpm install
92
+ pnpm run check # typecheck + test + build 全绿后再安装
93
+ ```
94
+
95
+ 以 `link:` 安装进当前 profile,改码后重启 DSH 即生效(不会重装依赖树):
96
+
97
+ ```bash
98
+ dsh plugin --profile desktop add /Users/dmh2002/DshProject/dsh-connect-qoder
99
+ ```
100
+
101
+ ### 验证安装
102
+
103
+ - 打开「设置 → 插件」应能看到 dsh-connect-qoder 的配置卡片(国内版 / 国际版两个 tab)。
104
+ - 在该 profile 目录里 `pnpm exec dsh-connect-qoder doctor` 应输出两个区域的 PAT 状态与指纹派生。
105
+ - 配置 PAT 后,「从 Qoder 刷新」应拉出该区域的真实模型清单;模型选择器出现 `Qoder` / `Qoder Global` 两个分组。
106
+
107
+ ## 配置
108
+
109
+ ### PAT(Personal Access Token)
110
+
111
+ 在 Qoder 账户设置里创建 PAT(`pt-…` 开头)。两种提供方式,**设置卡片优先于环境变量**:
112
+
113
+ | 区域 | 发证入口 | 设置项 | 环境变量 |
114
+ | --- | --- | --- | --- |
115
+ | 国内版(qoder.com.cn) | 控制台 → 设置 → API 令牌 | 卡片「国内版」tab → 添加 PAT | `QODERCN_PERSONAL_ACCESS_TOKEN` |
116
+ | 国际版(qoder.sh) | 控制台 → Settings → Personal Access Tokens | 卡片「国际版」tab → 添加 PAT | `QODER_PERSONAL_ACCESS_TOKEN` |
117
+
118
+ - PAT 只保存到本地插件配置(`regions.<region>.pat`),从不上传、不进入任何网络请求。
119
+ - 未配置 PAT 的区域不列出任何模型;`doctor` 命令会指出哪个区域缺 PAT。
120
+ - **逐步操作说明见 [`docs/PAT.md`](docs/PAT.md)**(含创建流程、验证机制、优先级与常见问题)。
121
+
122
+ ### 区域启停
123
+
124
+ 卡片每个 tab 右侧的勾选框控制该区域是否参与模型选择(opt-out:**显式取消勾选才停用**)。停用只摘除路由与目录条目,配置全部保留。
125
+
126
+ ### 多账号池与凭据来源
127
+
128
+ 除设置 PAT 与环境变量外,每区域还有一个本地 vault(`$DSH_HOME/.qoder-accounts.<region>.json`,权限 0600)保存多个 PAT 账号。三源按 **settings → env → vault** 的优先级去重合并(同一个 PAT 只在最高优先级的来源里列出一次)。
129
+
130
+ - 卡片可新增(粘贴 `pt-…`,经 `JobToken` 交换校验并取回显示名,也可自填备注名)/ 改名 vault 账号,也可显式选择某一账号服务(`regions.<region>.accountId`,留空自动)。删除 vault 账号在池表格的行内「删除」按钮(需二次确认);取消勾选并保存只是把它移出轮换,PAT 仍留在本地。
131
+ - 选中的凭据只用于会话的 jobToken 交换与后续 cosy 请求,从不进入 pi-ai 或页面。
132
+ - `logout [--region cn|global]` 会清除指定区域的 vault 账号与账号池测量副本(`$DSH_HOME/.qoder-accounts.<region>.json` 与 `.qoder-pool.<region>.json`)。
133
+ - 勾选成员、一键签到、一键测试与自动换号的规则见[账号池](#账号池)。
134
+
135
+ ## 账号池
136
+
137
+ 一台机器上往往有多个 Qoder 账号(多个 PAT)。**账号池**把这些账号收成一次点击:勾进池子,然后「一键签到所有账号」或「一键测试所有账号」;再打开**启用账号池**,插件就会在请求失败时自动换号接着跑。
138
+
139
+ **每个区域(国内版 / 国际版)各有独立的一池**:成员、目标模型、积分与签到状态互不影响——两边的账号本来就属于不同的供应商。**两个区域都有每日签到**:国际版的签到活动只下发给带设备风控头的请求,插件按区域读取本机 Qoder 客户端自带的设备身份组件;取不到时如实报错,不会把「拿不到设备身份」伪装成「今天没有活动」。
140
+
141
+ ### 自动换号(开启账号池即生效)
142
+
143
+ - **谁来服务由排序决定**:开启后**每一次请求**都发给池内排序最靠前的可用账号,所以「当前使用」显示的是排序的结果,不一定是你手动选的那个。
144
+ - **失败时继续往下试**:某个请求在上游失败时,插件**依次**用池内其他可用账号重试,直到有一个接手、或全部不可用为止才把错误报出来。
145
+ - **哪些失败会换号**:429 限流、额度耗尽、401/403 凭据被拒、5xx 网关与网络故障——这些都是**某个账号**的状态,换一个很可能就过了。
146
+ - **哪些不会**:请求本身不合法(HTTP 400)——同一个请求体换谁都是同样的 400,重试只是让你多等几轮;**流已开始之后**也不会重放(字节已经发出去了)。
147
+ - **已知不可用的账号会被跳过**:处于限流冷却期内、或凭据已被上游拒绝的成员不参与选择。**从没测过的账号按「默认可用」参与**——刚添加的账号不该因为「还没测」就被排除。
148
+ - **手动选择从不被改写**:池子通过运行时覆盖决定「谁服务」,**不写回你保存的选择**;关掉开关,你选定的账号立刻完全生效。
149
+ - **失败会被记下来**:一次失败会写成该账号的测量(含上游报文中给出的恢复时间),下一个请求直接从可用账号开始,不必每次先撞一遍同一个限流账号。
150
+
151
+ ### 这个开关到底控制什么
152
+
153
+ **它只控制「插件要不要自己决定」,不控制「你能不能操作」。**
154
+
155
+ | | 账号池 **开启** | 账号池 **关闭** |
156
+ | --- | --- | --- |
157
+ | 谁服务 | 由排序**自动**选出(可用优先 → 积分高 → 积分包快过期 → id) | **你**在账号行里手动指定 |
158
+ | 请求失败 | **自动**换下一个可用账号重试 | 不换号,如实报错 |
159
+ | 一键签到 / 一键测试 | 可用 | **仍然可用** |
160
+ | 目标测试模型 | 可改 | **仍然可改** |
161
+
162
+ 最后两行是刻意的:签到与测试是**手动动作**,作用于你勾选的成员,与「插件是否自动路由」是两件事。
163
+
164
+ **唯一的硬约束**:池里一个成员都没勾选时,批量动作一律拒绝。空的池**绝不**退化成「那就对全部账号执行」——那是唯一会花掉你没授权过的积分的解读。
165
+
166
+ ### 排序规则(开启账号池后决定谁服务)
167
+
168
+ 1. **可用优先** —— 被测量判定为不可用(限流未到恢复时间、凭据被拒等)的排到最后;
169
+ 2. **积分高者优先** —— 只在有**新鲜读数**时参与(卡片轮询、批量测试或保存时读到的那份,10 分钟内有效):请求路径**不会**为了排序去打上游,否则一页对话就变成 N 个额外请求;
170
+ 3. **快过期的积分包优先** —— 3 天内到期的先花,过期即作废;
171
+ 4. 最后按账号 id 收尾,保证顺序是**全序**、可复现。
172
+
173
+ ### 两个批量动作
174
+
175
+ - **一键签到所有账号** —— 逐个账号领取当日的官方签到活动。**先查状态再领取**,已领过的直接标「已签到」而**不发写入请求**(幂等,不重复领取);任何单个账号失败只标记该行,**不中断整批**。签到会改变余额,因此批次结束后作废该区域的积分缓存,卡片不会继续显示领取前的数字。
176
+ - **一键测试所有账号** —— 对每个成员发一次真实请求,得出「这个账号此刻能不能用这个模型」。目标模型默认由「自动」挑本区域**倍率为 0 的免费模型**(多个候选取上下文窗口最大的),你也可以显式指定——但**在免费模型出现之前测试保持关闭**,避免在你不知情的情况下花掉积分。测试走的是与模型行同一套探测,因此**有真实成本**。
177
+
178
+ 不提供**自动**签到:签到会真实领取奖励,只在你点按钮时执行。
179
+
180
+ ## 命令行诊断
181
+
182
+ 在**已装本插件的 profile 目录里**跑(推荐,无需全局安装):
183
+
184
+ ```bash
185
+ cd ~/.dsh/profiles/desktop
186
+ pnpm exec dsh-connect-qoder doctor # PAT 来源 + cosy 指纹派生 + pi-ai 代际 + 心跳 + vault 计数(纯本地,不发请求)
187
+ pnpm exec dsh-connect-qoder status # 每区域凭据来源 + 上游模型目录可达性 + 剩余积分(需 PAT)
188
+ pnpm exec dsh-connect-qoder logout --region cn # 清除指定区域的 vault 账号与账号池测量副本
189
+ pnpm exec dsh-connect-qoder doctor --json # 机器可读输出
190
+ ```
191
+
192
+ `status` 会真实交换一次 jobToken 并拉取模型目录(随后只读查询一次用量),因此需要对应区域的环境变量或 vault PAT;`doctor` 完全离线。
193
+
194
+ > **两条实测过的注意事项**:
195
+ > 1. **别用 `node node_modules/dsh-connect-qoder/lib/bin.js` 这种裸路径**——0.3.0 起包名带作用域,软链在 `node_modules/@dmh2002/dsh-connect-qoder`,写旧路径只会得到 `MODULE_NOT_FOUND`(缺的是**入口文件**,不是 peer)。
196
+ > 2. CLI 依赖宿主提供的 peer 包(`@deepseek-ai/dsh-atomic-write` / `dsh-home-paths`)。本机实测:pnpm 会随包把它们装进 profile 的依赖树,`pnpm exec` 与 npm 装的 tarball 都能跑通。但这**取决于 pnpm 版本与 `auto-install-peers` 配置**——如果在你机器上报 `Cannot find package '@deepseek-ai/dsh-atomic-write'`,那是这一层没装上;设置卡片与 provider **不受影响**(它们在宿主进程内,宿主自己解析这些名字)。同族 workbuddy 的 issue #47 是同一个形状,其修复方案(宿主优先、按需回落自带实现)适用于本项目,尚未移植。
197
+
198
+ ## 平台支持
199
+
200
+ | 平台 | 状态 |
201
+ | --- | --- |
202
+ | macOS | ✅ 已验证(开发与 CI) |
203
+ | Windows | ✅ CI 测试通过(上游请求走标准 fetch,无平台专属路径) |
204
+ | Linux | ✅ CI 测试通过 |
205
+
206
+ ## 受支持的宿主范围
207
+
208
+ | 宿主 | peer 范围 |
209
+ | --- | --- |
210
+ | DSH(`@deepseek-ai/*`) | `>=0.1.7-rc.1 <0.2.0-0 \|\| >=0.2.0-rc.1 <0.3.0-0` |
211
+ | Node.js | `^22.19.0 \|\| >=24.0.0` |
212
+
213
+ > 范围由 `tests/dsh-line.spec.ts` 强制,且**必须写成两条分支**:DSH 至今只发预发布,而 node-semver 只有当范围里*某个*比较符与该版本的 `major.minor.patch` 元组完全一致、且自身也带预发布标签时,才会放行预发布版本。只写 `>=0.1.7-rc.1 <0.3.0-0` 会**静默排除整条 `0.2.0-rc.*` 线**——而 `0.2.0-rc.2` 正是 `@deepseek-ai/dsh` 当前的 `latest`,用户装到就会 `ERESOLVE`。上界仍须带 `-0`(`<0.3.0` 不挡任何东西)。
214
+
215
+ ## 安全说明
216
+
217
+ - PAT 只写入本地插件配置(或环境变量),**从不进入任何网络请求**;对外请求一律使用 cosy 会话令牌(jobToken)+ 机器指纹头。
218
+ - 本地 shim 用随机端口的 loopback 服务 + 进程内 secret 鉴权,并校验 Host / Origin / Content-Type,请求体有 64MB 上限。
219
+ - 本插件只读你显式提供的 PAT;**不读取、不修改** Qoder 桌面端的本地登录/凭据文件。
220
+ - 诊断输出(`doctor` / `status` 与卡片探测)均脱敏,不打印任何令牌样式的字符串。
221
+
222
+ ## 市场收录与展示
223
+
224
+ 本插件声明了可安装的 `dsh.bundle` manifest(`dsh.bundle.patch: ./cordis.patch.yml`),因此可以用 `dsh plugin add` 安装。社区市场通常从 [Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) 注册表同步条目;仓库内的提交草稿位于 [`awesome-dsh-plugin-submission/dingminhua__dsh-connect-qoder.yml`](awesome-dsh-plugin-submission/dingminhua__dsh-connect-qoder.yml)。
225
+
226
+ **收录目录规范**(依据 [contributing.md](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md)):
227
+
228
+ - 每个插件一个 YAML:`data/plugins/<owner>__<repo>.yml`,字段为 `url`(与仓库地址完全一致)、`name`(`owner/repo`)、`category`(有效值之一,本项目为 `model`)、`description.en`(必填,以句号结尾)/ `description.zh`(可选);描述中含 `: ` 必须加引号。
229
+ - 仓库需声明 `dsh.bundle` manifest、被 `dsh-plugin` topic 标记、创建满 1 天且处于活跃维护;描述必须属实、不带营销词——它会被逐条对照代码核对。
230
+ - 注册表的两个 README 由脚本生成,不得手工编辑;修改 YAML 后在其仓库执行 `npm ci` + `node scripts/generate-readme.mjs` 重新生成。
231
+ - 一个 PR 最多收录 3 条;只改自己的条目。
232
+
233
+ 市场卡片中的截图与 GitHub README 徽章是两套机制:
234
+
235
+ - **市场截图**按当前约定由**插件自己仓库根目录的 `screenshots.json`** 声明(本项目声明了 [screenshots.json](screenshots.json));注册表的 `data/screenshots.json` 是旧约定下的回退,**不再添加新键**。
236
+ - **GitHub 徽章**由本 README 顶部的 Shields 图片链接生成;**市场图标/占位图**由具体市场的展示规则决定,不是 npm 字段,也不是 README 徽章。
237
+
238
+ ## 已知限制
239
+
240
+ - 依赖 Qoder 的 cosy 网关与 `sash` 接口(**非官方开放 API**),上游变动后插件可能需要随之调整。协议行为参照 [Zhengyuuuui/qoder2api](https://github.com/Zhengyuuuui/qoder2api),本项目独立实现,不复制其源码。
241
+ - **每日签到**是 Qoder 面向**个人用户**的运营活动(企业版账号不参与),规则与额度以官方为准;国际版的签到活动要求请求带本机 Qoder 客户端的设备身份,因此**未安装 Qoder 客户端的机器上国际版签到不可用**(插件如实报错,不静默降级)。
242
+ - 图片输入与「可关闭思考」按**已核实**的清单给出:上游没有明确公布的能力一律不预勾选,因此某些其实可用的模型可能需要你手动勾一次。
243
+ - 模型可用性测试与批量测试**都会发出真实请求**,会消耗少量上游额度;没有「测试已勾选」这类批量按钮,批量动作只作用于你显式勾选进池的账号。
244
+ - **独立 CLI 依赖宿主提供的 peer 包**(`@deepseek-ai/dsh-atomic-write` / `dsh-home-paths`)。本机实测:pnpm 会随包把它们装进 profile 的依赖树,`pnpm exec dsh-connect-qoder …` 与 npm 装的 tarball 都跑得通;但这取决于 pnpm 版本与 `auto-install-peers` 配置,若报 `Cannot find package '@deepseek-ai/dsh-atomic-write'` 就是这一层没装上。provider 注册、设置卡片与对话**都不受影响**(它们在宿主进程内,宿主负责解析这些名字)。同族 workbuddy 的修复方案(宿主优先、按需回落自带实现)适用于本项目,尚未移植。
245
+
246
+ ## 免责声明
247
+
248
+ - 本项目**仅供个人学习和研究使用**,仅驱动使用者自己的 Qoder 账号在本机调用,请勿用于商业用途或超出个人合理使用的场景。
249
+ - 使用者需遵守 Qoder / 通义灵码的服务条款;因使用本项目产生的任何后果(包括但不限于账号被限制、额度被清空、服务中断),由使用者自行承担。
250
+ - 本项目作者不对任何因使用或滥用本项目产生的直接或间接损失负责。
251
+ - 本项目与阿里巴巴、通义灵码、Qoder、DeepSeek 均无关联,未获其授权或认可;文中出现的名称仅用于描述兼容关系,其商标权利归各自所有。
252
+
253
+ ## 致谢
254
+
255
+ 本项目建立在他人已公开的工作之上,详见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md):
256
+
257
+ - [**Zhengyuuuui/qoder2api**](https://github.com/Zhengyuuuui/qoder2api):cosy 协议 wire behavior 的参照实现(编码、签名、会话、指纹、jobToken、SSE 形状、错误分类)。本项目按其行为**独立实现**为 TypeScript,不复制其源码;该仓库未附独立 LICENSE,其 README 声明源自 `wangtufly/QCCG`(GPL-3.0)。
258
+ - [**L0NE-6/Qoder-Daily**](https://github.com/L0NE-6/Qoder-Daily)(MIT):每日签到的协议参照(`campaigns` / `claim` 形状、幂等判定、国际版设备风控思路)。本项目按其文档与本机实测行为独立实现,不复制其源码。
259
+ - [**dingminhua/dsh-connect-workbuddy**](https://github.com/dingminhua/dsh-connect-workbuddy)(MIT):插件工程结构、卡片交互模型、loopback shim 模式与 npm 发布工程的基准。
260
+ - [**dingminhua/dsh-connect-trae**](https://github.com/dingminhua/dsh-connect-trae)(MIT):经 workbuddy 转引的卡片外观与发布工程基准。
261
+ - [**franksong2702/dsh-codex-connect**](https://github.com/franksong2702/dsh-codex-connect)(Apache-2.0):经 workbuddy 转引的 DSH 插件结构与 pi-ai provider 注册参照。
262
+ - Qoder 官方 SDK 的类型定义:仅用于 usage/头的形状核对,不随本项目分发。
263
+
264
+ ## License
265
+
266
+ MIT © LaoDing —— 详见 [LICENSE](LICENSE)。