dsh-magpie-connector 0.3.1 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1
4
+
5
+ - **Check refreshes latency.** A completed line check on a **saved** line now also re-runs the settings-page latency measurement (one `GET /api/dsh-magpie-connector/latency` pass), so the signal reading next to that line reflects the same moment as the check instead of the last panel open or save. Unsaved drafts trigger nothing (they have no latency row), the host measurement stays single-flight with its 600 ms deadline, and there is still no polling.
6
+
7
+ ## 0.4.0
8
+
9
+ - **Line panel polish.** Settings now collapses every configured line by default (it no longer force-expands the active one); clicking a line header toggles it, and the add/remove/reorder actions are always-visible DSH product-icon buttons with accessible names and tooltips. A newly added line still opens for editing and stays open through its first save (including when it was moved before saving) and when a save fails.
10
+ - **Line latency.** Opening Settings measures each saved line with a parseable address once (parallel TCP connect time, ≤600ms per line, DNS included, no HTTP and no credentials) and shows a 4-bar signal reading plus the millisecond value; timeouts show 「超时」, failed or older-host responses degrade silently to 「—」. Readings are display-only: they are not persisted, never touch automatic line selection, and are re-measured only on the next panel open or after a successful line save (no polling). New GET-only host resource `GET /api/dsh-magpie-connector/latency`.
11
+ - Usage bar fill now has rounded ends matching the track.
12
+ - **Check a line before saving it.** Every line card has 「检查线路 / Check line」: it runs one real `GET /models` against the card's current draft (address, access mode, key; empty credentials fall back to that line's saved values) and renders a read-only snapshot — result kind, latency, model count and, when the host has a cached catalog with a sync time, the model ids extra to or missing from that cache. It never writes credentials or settings, never rotates the generation, never selects a line, never registers a route and never touches the probe cache, so it cannot change which line is in use; the snapshot is explicitly compared against the *cached* catalog, never called "same as the current line" (a switch keeps an inherited catalog).
13
+ - **Bounded, classified, redacted.** The `line-check` settings API resource admits no write: with a save in progress or a credential write pending it answers `pending`/`busy` without reading saved credentials, a host-memory connection-write version proves no write landed during the credential read, the whole check shares a 5 s budget, only one check runs at a time, and draft credentials are redacted from results, reasons and logs. Failure classification has a fixed precedence (response state before body, transport-blocked redirects included).
14
+ - **Quieter saves, honest labels.** Usage cards are cleared only when a save touches the active line's identity (line removed, address/access mode changed, credentials supplied) or the host auto-switches lines; editing or reordering spare lines keeps the cards and their read time. The save result states the resulting line (`已保存 · 当前线路 N` / no active line) with no live region, and a saved-but-incomplete line is labelled `未配置`.
15
+ - **Wire contract.** `POST /api/dsh-magpie-connector/line-check` accepts `{id?, baseUrl, accessMode?, key?, accessClientId?, accessClientSecret?}`; shape violations are 400, every other outcome is a 200 result with `kind`. See `openspec/changes/archive/2026-10-30-magpie-line-probe-and-save-clarity/` for the specification.
16
+
3
17
  ## 0.3.1
4
18
 
5
19
  - Replace both README demo GIFs with representative PNG screenshots (the model picker grouped by provider; per-provider visibility, default effort and usage cards in Settings), because the DSH plugin store does not show GIFs by default. No runtime behavior changes.
package/README.md CHANGED
@@ -19,8 +19,10 @@ English · [简体中文](README.zh.md)
19
19
  | Capability | What it does |
20
20
  |---|---|
21
21
  | **Gateway lines** (basic) | Each line has its own URL and gateway key. The model catalog comes from the gateway's `GET /models`; Responses-capable models use Responses, everything else uses Chat Completions. Text, tool round trips, images, usage, cancellation and DSH-managed retries go through. |
22
+ | **Check a line before saving** (0.4.0) | Every line card has “Check line”: one real `GET /models` with that card's address, access mode and key (empty credentials fall back to the line's saved values), rendered as a read-only snapshot — result kind, latency, model count and, when the host has a cached catalog with a sync time, the model ids extra to or missing from that cache. It never changes the active line, writes credentials/settings, syncs the catalog or registers a route; the diff is against the *cached* catalog, so it never claims to match the current line. |
22
23
  | **Automatic multi-line failover** (signature) | Empty on first install; normally one line; add backup lines and let the order decide priority. Before every gateway operation the plugin picks the first reachable complete line with a ≤600ms TCP probe (10s positive / 15s negative in-memory cache, no background polling), so home uses the LAN while outside falls back to the public endpoint. Switching lines carries the catalog over (no flash), clears usage cards and re-syncs the new line; single-line users keep 0.1.x behavior. |
23
24
  | **Grouped by upstream provider** (signature) | Works on stock DSH, no Host extension needed: the plugin registers one real provider route per gateway `owned_by` (`dsh-magpie-connector-claude`, `-codex`, `-opencode-go`, …; missing or invalid ownership goes to `-other`), so the official model picker shows `Magpie (Claude)`, `Magpie (Codex)`, `Magpie (Opencode-go)`, `Magpie (Other)` groups. Settings and usage cards use the same grouping, and a provider that only appears in usage is never dropped. |
25
+ | **Line panel and latency** | Configured lines start collapsed; click a row header to edit it, and reorder/delete are always-visible DSH product-icon buttons with tooltips. Opening Settings measures every saved line with a parseable address once (parallel TCP connect time, ≤600ms per line, no HTTP, no credentials) and shows a 4-bar signal plus milliseconds; timeouts show a timeout reading and failures or older hosts degrade to a dash. Display-only: it never affects automatic line selection, is not persisted, and never polls. |
24
26
  | **Per-line Cloudflare Access** | Each line independently chooses Direct or Cloudflare Access. Access-enabled line requests carry the service token pair plus the bearer key; the token is never sent to a plain-HTTP line. |
25
27
  | **Read-only usage cards** | Each upstream account shows plan, expiry, window readings and Codex reset credits when the gateway provides them. Cards are read once when settings open and only re-read when you press **Refresh** on that card; bars turn orange above 70% and red at 90%. |
26
28
  | **Model visibility and default effort** | Per provider, leave visibility untouched to follow the gateway catalog, or save an explicit allowlist so new models stay hidden. Per model, save a default reasoning effort; DSH applies it on the next request. |
@@ -56,7 +58,7 @@ Need another method — custom headers, mTLS, another zero-trust gateway? **Issu
56
58
  | **Model picker** | Search across group headings, model names and full ids. Each group is a real route `dsh-magpie-connector-<owner>`; model ids stay the gateway's full ids, and visibility keys and default effort are unchanged. |
57
59
  | **Model menu groups** | `owned_by` (lower-cased; 1–48 chars of `a-z0-9._-`) picks the route and the label `Magpie (<Owner>)` with the first letter capitalised. Missing or invalid ownership goes to `Magpie (Other)` instead of guessing from the model name. Owners seen once are remembered in `settings.json` `knownOwners` (max 32) so their routes exist right after a restart; the models appear once the first catalog sync finishes. An owner that disappears keeps its (empty, hidden) route. Clearing `knownOwners` by hand invalidates selections saved on the removed routes. If route registration fails at start-up (another plugin owns the id), Settings shows the error and DSH must be restarted after resolving it. |
58
60
  | **Composer speed control** | Shows only for the current model when the gateway advertises the `priority` tier (on current Magpie: the Codex/GPT models), and updates as soon as the selection changes. Hover the model menu to see effort; the official picker shows one effort value. |
59
- | **Settings → Magpie** | Connection, advanced connection mode, per-provider model sections with visibility checkboxes and default effort selects, plus the usage cards for that provider. |
61
+ | **Settings → Magpie** | Connection, advanced connection mode, per-provider model sections with visibility checkboxes and default effort selects, plus the usage cards for that provider; each line card also carries the read-only “Check line” snapshot and an “unconfigured” label, and the save result states the resulting active line. |
60
62
  | **Usage card refresh** | Each card has its own **Refresh**, which asks Magpie to re-read that one account. A cold gateway may return no cards at all; the connection panel then offers **Fetch models and usage**, which first re-synchronizes the catalog and then asks Magpie to read every account once. A restricted held key cannot initialize an empty cache — ask the Magpie administrator to read usage first. |
61
63
 
62
64
  ### Behavior worth knowing
package/README.zh.md CHANGED
@@ -19,8 +19,10 @@
19
19
  | 能力 | 具体行为 |
20
20
  |---|---|
21
21
  | **Magpie 网关接入**(基础) | 每条线路只需地址和该线路的 key。模型目录来自网关 `GET /models`;支持 Responses 的模型走 Responses,其余走 Chat Completions。文本、工具往返、图片、usage、取消以及 DSH 统一重试都能正常工作。 |
22
+ | **保存前检查线路**(0.4.0) | 每条线路卡片有「检查线路」:用当前填写的地址、接入方式与 key 发一次真实 `GET /models`(凭据留空则用该线路已保存的值),显示结果分类、耗时、模型数量,以及在宿主有缓存目录时与缓存相比多出/缺少的模型。它不改当前线路、不写凭据或设置、不触发目录同步,只是只读快照;差异基准是**缓存目录**,所以不会宣称「与当前线路一致」。 |
22
23
  | **多线路自动切换**(特色) | 通常一条线路(首次安装为空,最多 5 条),可添加备用线路并按序号自动选择:每次操作前对候选地址做 ≤600ms 的 TCP 可达探测(正缓存 10s / 负缓存 15s,不后台轮询),在家走局域网、在外自动落到公网。切换线路时模型目录继承不闪断、用量卡片清空并按新线路重读;单线路(通常一条)行为与旧版一致。 |
23
24
  | **按上游 provider 分组**(特色) | 原版 DSH 即可使用,无需 Host 扩展:插件按网关 `owned_by` 为每个归属注册一个真实 provider route(`dsh-magpie-connector-claude`、`-codex`、`-opencode-go` 等;缺失或不合法归入 `-other`),官方模型选择器因此显示 `Magpie (Claude)`、`Magpie (Codex)`、`Magpie (Opencode-go)`、`Magpie (Other)` 分组。设置页和用量卡片使用同一套分组,只出现在用量里的 provider 也不会被丢弃。 |
25
+ | **线路面板与延迟** | 已配置的线路默认全部收起,点行头部展开编辑;上移 / 下移 / 删除是常驻的 DSH 产品图标按钮(带 tooltip)。打开设置页对每条已保存且地址可解析的线路做一次 TCP 建连测速(并行、单条 ≤600ms、不发 HTTP、不带凭据),用 4 格信号条 + 毫秒显示;超时显示「超时」,旧版宿主或失败显示「—」。只展示当时的网络质量,不参与自动选线、不持久化、不轮询。 |
24
26
  | **按线路的 Cloudflare Access** | 每条线路可单独选择「直连」或「Cloudflare Access」。启用后该线路的请求带上服务令牌两项并保留 bearer key;服务令牌永远不会发往明文 HTTP 线路。 |
25
27
  | **只读用量卡片** | 每个上游账号展示计划、到期时间、各窗口读数和 Codex 重置次数(网关提供时)。打开设置页只读一次,之后只有点单张卡片的「刷新」才会重新读取;用量条超过 70% 变橙、达到 90% 变红。 |
26
28
  | **模型可见性与默认 effort** | 每个 provider 不设置可见性时跟随网关目录;保存过选择后只显示勾选项,新上架模型默认隐藏。每个模型可保存一个默认推理 effort,DSH 在下一次请求应用。 |
@@ -56,7 +58,7 @@ dsh plugin --profile web add dsh-magpie-connector
56
58
  | **模型选择器** | 统一搜索覆盖分组标题、型号名和完整 id。每个分组是一个真实 route `dsh-magpie-connector-<owner>`;model id 保持网关完整 id,可见性键和默认 effort 不变。 |
57
59
  | **模型菜单分组** | `owned_by`(转小写,1–48 个 `a-z0-9._-` 字符)决定 route 和标题 `Magpie (<Owner>)`(首字母大写)。缺失或不合法时归入 `Magpie (Other)`,不按模型名猜测。见过的归属记在 `settings.json` 的 `knownOwners`(最多 32 个),重启后 route 立即存在,首次目录同步完成后模型才出现。归属下架后其 route 保留(空组自动隐藏)。手动清空 `knownOwners` 会让保存在被移除 route 上的选择失效。启动时若 route 注册失败(例如被别的插件占用),设置页会显示错误,排除后需重启 DSH。 |
58
60
  | **输入框速度控件** | 仅当前模型被网关声明为 `priority` 档时显示(当前 Magpie 上即 Codex/GPT 模型),切换模型后立即更新。悬停模型菜单可看 effort;官方选择器只显示一个 effort 值。 |
59
- | **设置 → Magpie** | 连接、接入方式、按 provider 分组的模型区(可见性复选框与默认 effort 选择器),以及该 provider 的用量卡片。 |
61
+ | **设置 → Magpie** | 连接、接入方式、按 provider 分组的模型区(可见性复选框与默认 effort 选择器),以及该 provider 的用量卡片;每条线路卡片还有「检查线路」只读快照与「未配置」标记,保存结果只陈述当时的生效线路。 |
60
62
  | **用量卡片刷新** | 每张卡片有独立的「刷新」,请求 Magpie 重新读取这一个账号。网关冷启动可能没有任何卡片;此时连接面板提供「获取模型与用量」,先同步模型目录,再请 Magpie 读取全部账号一次。受限 held key 无法初始化空缓存,需要管理员先在 Magpie 中读取一次。 |
61
63
 
62
64
  ### 需要知道的行为
@@ -3,4 +3,4 @@
3
3
  | file | kind | source | date | reviewer | verdict | note |
4
4
  |---|---|---|---|---|---|---|
5
5
  | model-picker.png | screenshot | frame 11 of the 2026-10-28 recording (isolated DSH_HOME instance, temp HOME/XDG/DSH_HOME under /tmp, port 3199; local mock Magpie gateway on loopback with demo models; lossless deviceScaleFactor-2 screenshots of the real Web UI, 1500x880 at 1:1), exported losslessly from the global-palette GIF | 2026-10-29 | lead | pass (reviewed frame: demo workspace and mock models only; no hostname, user, path, token or real session) | 106789 bytes; picker open with Magpie (Claude), Magpie (GPT) and Magpie (deepseek) groups; replaces model-picker.gif because the plugin store does not show GIFs by default |
6
- | settings.png | screenshot | isolated DSH_HOME under /tmp (temp HOME/XDG, port 3199) running the deployed 0.3.0 plugin (code identical to 0.3.1) against a local loopback mock Magpie gateway serving demo models and usage cards; Playwright/CDP deviceScaleFactor-2 screenshot of the settings dialog, 1600x1600 | 2026-10-29 | lead | pass (reviewed: loopback mock address, key from a fake startup env value, demo@example.com account, demo models; no real hostname, user, path, token or session) | 202707 bytes; connection section with line 1 collapsed, then the claude group with per-model default effort and its usage card, codex group below; replaces settings.gif |
6
+ | settings.png | screenshot | isolated preview home (`git-common-dir/ws/dsh-home/<op>`, port 3202) running the 0.4.0 worktree build against a local loopback mock Magpie gateway (demo models and usage cards); headless Chrome 156 via agent-browser at 1600x1600 CSS pixels | 2026-10-30 | agent (privacy-reviewed; pending lead acceptance) | pass (reviewed: loopback/mock-only addresses 127.0.0.1 and 10.255.255.1, demo@example.com account, demo models and readings; no real hostname, user, key, path, token or session) | 137797 bytes; 0.4.0 connection section with two lines collapsed by default (fast line with 4-bar latency reading, backup line showing timeout), then the claude group with per-model default effort and its usage card with rounded bars; replaces the 0.3.1 screenshot |
@@ -28,6 +28,20 @@ BREAKING: the host settings surface keeps only the `lines[]` schema. Legacy top-
28
28
  - Credential writes are not transactional. Attempted `(lineId, field)` pairs are persisted as `pending` before any secret write; on successful whole-table commit, pending entries for lines absent from the commit are cleaned automatically (unset writable refs, keep env-owned ones in `retainedCredentials`). Deletes/access-mode changes commit only after every writable credential was cleared.
29
29
  - `Operation` captures `lineId`, `accessMode`, `baseUrl` and the credentials; use `op` (not `gen.baseUrl`) for requests. `Generation` still owns catalog/cards; a line switch replaces the generation but carries the catalog, so `isCurrent(gen)` remains the commit guard.
30
30
 
31
+ ## 0.4.0 line latency resource (current)
32
+
33
+ ```ts
34
+ // GET-only; the route is registered with methods:['GET'].
35
+ GET /api/dsh-magpie-connector/latency → { lines: [{ id, ms: number | null }] }
36
+ ```
37
+
38
+ - One entry per **saved line with a parseable address** (empty addresses are omitted, not reported as failures). Unconfigured lines still get a reading: measurement is a network property, not a completeness one.
39
+ - `ms` is a non-negative integer TCP connect time in milliseconds, DNS lookup included, capped by the same 600ms deadline the selector uses; `null` means no connect completed in time.
40
+ - Measurements run in parallel, single-flight (concurrent GETs share one pass), are never cached or persisted, and send no HTTP request and no credentials. They do not write settings, do not change `activeLineId`, and do not read or populate the selector's probe cache.
41
+ - Non-GET methods are not handled by the plugin: they fall through the connection layer to `404` and never open a socket.
42
+ - `dispose` aborts in-flight probes; a later read returns `{lines: []}` without connecting.
43
+ - The web half renders the reading per line (`data-latency-state/level/ms`); any non-2xx (including `404` from an older host) degrades silently to 「—」 with no alert/status/aria-live content.
44
+
31
45
  ## Runtime and connection identity
32
46
 
33
47
  ```ts
@@ -98,7 +112,7 @@ Every request validates same origin and exact API-root-relative path, forces aut
98
112
 
99
113
  ## Host wiring
100
114
 
101
- `apply(ctx,{home?})` registers one adapter on the owner routes derived from `knownOwners` (via `createRouteRegistrar`, see `src/host/registrar.ts`), credentials event, `/api/dsh-magpie-connector/settings` through injected `connection.fetch`, starts initial sync, and installs an async effect disposer. It returns `{runtime,adapter,routes}` for focused harness tests. The feature assembly adds cards/speed resources and their controller lifecycle here; do not add another adapter registration or another settings route.
115
+ `apply(ctx,{home?})` registers one adapter on the owner routes derived from `knownOwners` (via `createRouteRegistrar`, see `src/host/registrar.ts`), credentials event, `/api/dsh-magpie-connector/settings` through injected `connection.fetch`, starts initial sync, and installs an async effect disposer. It returns `{runtime,adapter,routes}` for focused harness tests. The feature assembly adds cards/speed/latency resources and their controller lifecycle here; do not add another adapter registration or another settings route.
102
116
 
103
117
  ## Evidence and remaining assembly
104
118
 
@@ -149,10 +163,10 @@ GREEN checkpoints:
149
163
  - Generation: all seven exact names exist. The two real dispatch cases `已准备的调用按捕获值跑完` and `进行中的生成不被换代中止` live in `generate.test.ts`, not the planned `generation.test.ts`; the names match verbatim, but the test-plan file location needs lead correction/acceptance. Executor did not modify OpenSpec.
150
164
  - Catalog sync: all five names match; mapping: both names match; generation: all fifteen names match; transport: all three names match.
151
165
  - `未配置时不提供模型且不发请求` was already negative-only green against the initial empty seam. A later real adapter test asserts `MAGPIE_NOT_CONFIGURED` plus empty advertised catalog and zero network, but there is **no legitimate first-red record for that full scenario**. Do not misreport the vacuous initial pass as RED. All generation negative tests initially vacuous were strengthened and confirmed RED before bridge implementation.
152
- - Transport `完整流程只触达白名单` directly exercises all allowed operations through the one transport and rejects forbidden paths, but the final assembled cards/speed feature workflow still requires its own end-to-end run. This test alone is not evidence that all feature endpoints were wired.
166
+ - Transport `完整流程只触达白名单` directly exercises all allowed operations through the one transport and rejects forbidden paths, but the final assembled cards/speed/latency feature workflow still requires its own end-to-end run. This test alone is not evidence that all feature endpoints were wired.
153
167
 
154
168
  ## Interfaces and assembly boundary
155
169
 
156
170
  See `internal-api.md`. Exported core classes: `MagpieRuntime`, `MagpieAdapter`, `GatewayTransport`, `SettingsStore`; operation/capability interfaces come from their host modules. Real DSH `LlmAdapter`/registration/credentials/attachment types are used, not an alternative adapter framework.
157
171
 
158
- The lead/host-features owner takes final composition of preferences, adapter projection and cards/speed routes. Do not mark tasks1–7 or shared task1 complete merely from this evidence: interface assembly and final whole-suite snapshot must be accepted by lead. No dependency installation was needed or performed in this takeover; pre-existing root lockfile changes were preserved.
172
+ The lead/host-features owner takes final composition of preferences, adapter projection and cards/speed/latency routes. Do not mark tasks1–7 or shared task1 complete merely from this evidence: interface assembly and final whole-suite snapshot must be accepted by lead. No dependency installation was needed or performed in this takeover; pre-existing root lockfile changes were preserved.
package/docs/settings.png CHANGED
Binary file
@@ -1,4 +1,5 @@
1
1
  import { type Api } from './api.js';
2
+ import { type CheckSnapshot, type SettingsView } from './view-model.js';
2
3
  /**
3
4
  * Magpie settings section: connection (with an advanced access mode), then
4
5
  * per-provider groups (model visibility, DSH-side default effort, read-only
@@ -9,6 +10,17 @@ import { type Api } from './api.js';
9
10
  export declare const EMPTY_TEXT = "Magpie \u672A\u8FD4\u56DE\u53EF\u89C1\u7528\u91CF";
10
11
  export declare const RESTRICTED_HINT = "\u53D7\u9650\u7684 gateway key \u65E0\u6CD5\u8BA9 Magpie \u9996\u6B21\u8BFB\u53D6\u7528\u91CF\uFF1A\u9700\u8981 Magpie \u7BA1\u7406\u5458\u5148\u5728 Magpie \u4E2D\u8BFB\u53D6\u4E00\u6B21\uFF0C\u6216\u8005\u6539\u7528\u4E0D\u53D7\u9650\u7684 key\u3002";
11
12
  export declare const NOT_REREAD = "Magpie \u672A\u91CD\u65B0\u8BFB\u53D6\uFF08\u53EF\u80FD\u5904\u4E8E\u8282\u6D41\u95F4\u9694\u5185\uFF09";
13
+ /**
14
+ * Does this submitted list touch the active line's identity? Only then the old usage cards must
15
+ * disappear before the request is even sent (the host writes credentials before awaiting its
16
+ * catalog sync, which can take up to 15s).
17
+ */
18
+ export declare function touchesActiveIdentity(lines: unknown[], view: SettingsView): boolean;
19
+ /** One line of the read-only snapshot: kind, model count, diff against the cache and latency. */
20
+ export declare function describeCheck(snapshot: CheckSnapshot, diff: {
21
+ added: string[];
22
+ removed: string[];
23
+ } | undefined, lastSyncAt: string | undefined): string;
12
24
  export declare function MagpieSettings({ api }: {
13
25
  api: Api;
14
26
  }): import("react").DetailedReactHTMLElement<{