@apinggithub/mydsh-auto-model-router 0.2.0 → 0.2.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/README.md +147 -82
- package/README.zh.md +122 -72
- package/auto-adapter.mjs +132 -104
- package/compat.mjs +199 -0
- package/config.mjs +4 -1
- package/index.mjs +429 -307
- package/package.json +74 -72
package/README.md
CHANGED
|
@@ -116,6 +116,48 @@ npm install @apinggithub/mydsh-auto-model-router
|
|
|
116
116
|
dsh plugin --profile web add @apinggithub/mydsh-auto-model-router
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
## Host compatibility
|
|
120
|
+
|
|
121
|
+
DSH treats a profile's plugin tree as one composition: a bundle whose patch
|
|
122
|
+
fails can stop the whole Harness from starting, and a plugin entry that throws
|
|
123
|
+
is reported as *did not activate* — the router simply disappears from the
|
|
124
|
+
running session. This plugin is therefore written to **mount, degrade and
|
|
125
|
+
log** rather than fail on a host whose surface moved.
|
|
126
|
+
|
|
127
|
+
| Host line | Status |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| 0.1.x – 0.3.x | Tested (`0.2.1-rc.2` is the reference host) |
|
|
130
|
+
| 0.4.x – 1.x | Allowed to mount, reported as *untested* in the log and in `GET /api/mydsh-auto-model-router/status` |
|
|
131
|
+
|
|
132
|
+
At mount, `compat.mjs` probes every API the plugin touches
|
|
133
|
+
(`llm.registerAdapter`, `llm.listModels`, `llm.stream`, scoped `ctx.inject`,
|
|
134
|
+
`webServer.register`, the optional `tokenMeter`) and records the result in the
|
|
135
|
+
status payload under `host.compat`. What that buys you on an unknown host:
|
|
136
|
+
|
|
137
|
+
- **No hard dependency on `tokenMeter`** — it is looked up softly and token
|
|
138
|
+
spend falls back to the plugin's own estimator.
|
|
139
|
+
- **No virtual-provider requirement** — without `llm.registerAdapter` the
|
|
140
|
+
plugin still mounts and logs one warning; the `Auto` model simply does not
|
|
141
|
+
appear in the picker.
|
|
142
|
+
- **HTTP route aliases** — `webServer.register()`, `route()` or `addRoute()`
|
|
143
|
+
are all accepted; without any of them the settings page shows its "no report
|
|
144
|
+
yet" state instead of breaking.
|
|
145
|
+
- **Payload tolerance** — event handlers accept the current `{ agent }` /
|
|
146
|
+
`(session, event)` shapes *and* a bare-agent / `{ session, event }` payload,
|
|
147
|
+
and a handler that throws hands the host's own proposal back untouched. A
|
|
148
|
+
router bug can no longer fail a user request.
|
|
149
|
+
- **No process globals** — the adapter is created per mount and closes over
|
|
150
|
+
that mount's router, so several profiles in one process cannot overwrite
|
|
151
|
+
each other.
|
|
152
|
+
- **Version report** — when the host exposes its version (environment, app
|
|
153
|
+
service, or a resolvable manifest), a host outside `>=0.1.0-0 <0.4.0` logs
|
|
154
|
+
one warning naming both versions. Capability probes run regardless, so a
|
|
155
|
+
host that hides its version still reports what it actually provides.
|
|
156
|
+
|
|
157
|
+
The peer ranges (`>=0.1.0-0 <1.0.0`) and `engines.dsh` are deliberately wider
|
|
158
|
+
than the tested range so an upgrade does not hard-block installation; the
|
|
159
|
+
runtime warning is what tells you to file an issue.
|
|
160
|
+
|
|
119
161
|
## Configuration
|
|
120
162
|
|
|
121
163
|
Add to your profile's `cordis.patch.yml`:
|
|
@@ -370,88 +412,111 @@ See [tier-cost/README.md](tier-cost/README.md) for details.
|
|
|
370
412
|
## License
|
|
371
413
|
|
|
372
414
|
MIT
|
|
373
|
-
|
|
374
|
-
### Context-window guard (contextGuard)
|
|
375
|
-
|
|
376
|
-
A level may declare its model's `contextWindow`. When the session's live context
|
|
377
|
-
(the input side of the last request, including cache reads/writes) exceeds
|
|
378
|
-
`window × headroom` (default 0.85) the level is **skipped** and the router
|
|
379
|
-
drifts up to the nearest level that fits; if no declared window fits, the
|
|
380
|
-
original level stands (fail open) so the provider answers with a real error the
|
|
381
|
-
fallback chain can handle. Each skip is recorded once per (level, session) and
|
|
382
|
-
visible in `/status` under `contextGuard`. With no window declared anywhere the
|
|
383
|
-
layer is inert.
|
|
384
|
-
|
|
385
|
-
```yaml
|
|
386
|
-
levels:
|
|
387
|
-
L0: { provider: bailian, model: qwen-max, contextWindow: 32768 } # guard small windows
|
|
388
|
-
contextGuard: { enabled: true, headroom: 0.85 }
|
|
415
|
+
|
|
416
|
+
### Context-window guard (contextGuard)
|
|
417
|
+
|
|
418
|
+
A level may declare its model's `contextWindow`. When the session's live context
|
|
419
|
+
(the input side of the last request, including cache reads/writes) exceeds
|
|
420
|
+
`window × headroom` (default 0.85) the level is **skipped** and the router
|
|
421
|
+
drifts up to the nearest level that fits; if no declared window fits, the
|
|
422
|
+
original level stands (fail open) so the provider answers with a real error the
|
|
423
|
+
fallback chain can handle. Each skip is recorded once per (level, session) and
|
|
424
|
+
visible in `/status` under `contextGuard`. With no window declared anywhere the
|
|
425
|
+
layer is inert.
|
|
426
|
+
|
|
427
|
+
```yaml
|
|
428
|
+
levels:
|
|
429
|
+
L0: { provider: bailian, model: qwen-max, contextWindow: 32768 } # guard small windows
|
|
430
|
+
contextGuard: { enabled: true, headroom: 0.85 }
|
|
389
431
|
```
|
|
390
|
-
|
|
391
|
-
## Deploying to another machine
|
|
392
|
-
|
|
393
|
-
One machine = two commands (this repository ships both plugins):
|
|
394
|
-
|
|
395
|
-
```powershell
|
|
396
|
-
git clone https://github.com/apinggithub/mydsh-auto-model-router
|
|
397
|
-
cd mydsh-auto-model-router; cd ..
|
|
398
|
-
# On 0.2.x hosts the plugin's stale pinned peers make the installer refuse; grant the
|
|
399
|
-
# exact-version exemption first (per new plugin version):
|
|
400
|
-
dsh plugin --profile desktop allow-version '@apinggithub/mydsh-auto-model-router@0.1.1' --dsh-version 0.2.0-rc.2 --accept-risk
|
|
401
|
-
dsh plugin --profile desktop add ./mydsh-auto-model-router
|
|
402
|
-
dsh plugin --profile desktop add ./mydsh-auto-model-router/tier-cost # optional: per-tier cost panel
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
Then write a full levels block into that machine's `profiles/<profile>/cordis.patch.yml` —
|
|
406
|
-
provider/model must be routes **that machine** can actually use (do not copy ids across machines):
|
|
407
|
-
|
|
408
|
-
```yaml
|
|
409
|
-
- id: mydsh-auto-model-router-runtime
|
|
410
|
-
name: '@apinggithub/mydsh-auto-model-router'
|
|
411
|
-
config:
|
|
412
|
-
levels:
|
|
413
|
-
L0: { provider: ..., model: ..., reasoningEffort: off, contextWindow: ..., freeQuota: true }
|
|
414
|
-
L1: { provider: ..., model: ..., reasoningEffort: low, contextWindow: ..., freeQuota: true }
|
|
415
|
-
L2: { provider: ..., model: ..., reasoningEffort: high, contextWindow: ..., freeQuota: true }
|
|
416
|
-
L3: { provider: ..., model: ..., reasoningEffort: max, contextWindow: ... }
|
|
417
|
-
contextGuard: { enabled: true, headroom: 0.85 }
|
|
418
|
-
freeQuota: { enabled: true, demoteOnError: true, notify: true }
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
⚠️ The built-in default levels carry legacy values (`deepseek-v4-flash` + `medium`) that are
|
|
422
|
-
both invalid on 0.2.x hosts — a fresh machine must ship its own levels block or Auto fails
|
|
423
|
-
outright. `tier-cost` has zero hard dependencies beyond the host: without the router every
|
|
424
|
-
turn folds into manual/unknown, without the billing plugin tokens are counted and cost
|
|
432
|
+
|
|
433
|
+
## Deploying to another machine
|
|
434
|
+
|
|
435
|
+
One machine = two commands (this repository ships both plugins):
|
|
436
|
+
|
|
437
|
+
```powershell
|
|
438
|
+
git clone https://github.com/apinggithub/mydsh-auto-model-router
|
|
439
|
+
cd mydsh-auto-model-router; cd ..
|
|
440
|
+
# On 0.2.x hosts the plugin's stale pinned peers make the installer refuse; grant the
|
|
441
|
+
# exact-version exemption first (per new plugin version):
|
|
442
|
+
dsh plugin --profile desktop allow-version '@apinggithub/mydsh-auto-model-router@0.1.1' --dsh-version 0.2.0-rc.2 --accept-risk
|
|
443
|
+
dsh plugin --profile desktop add ./mydsh-auto-model-router
|
|
444
|
+
dsh plugin --profile desktop add ./mydsh-auto-model-router/tier-cost # optional: per-tier cost panel
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Then write a full levels block into that machine's `profiles/<profile>/cordis.patch.yml` —
|
|
448
|
+
provider/model must be routes **that machine** can actually use (do not copy ids across machines):
|
|
449
|
+
|
|
450
|
+
```yaml
|
|
451
|
+
- id: mydsh-auto-model-router-runtime
|
|
452
|
+
name: '@apinggithub/mydsh-auto-model-router'
|
|
453
|
+
config:
|
|
454
|
+
levels:
|
|
455
|
+
L0: { provider: ..., model: ..., reasoningEffort: off, contextWindow: ..., freeQuota: true }
|
|
456
|
+
L1: { provider: ..., model: ..., reasoningEffort: low, contextWindow: ..., freeQuota: true }
|
|
457
|
+
L2: { provider: ..., model: ..., reasoningEffort: high, contextWindow: ..., freeQuota: true }
|
|
458
|
+
L3: { provider: ..., model: ..., reasoningEffort: max, contextWindow: ... }
|
|
459
|
+
contextGuard: { enabled: true, headroom: 0.85 }
|
|
460
|
+
freeQuota: { enabled: true, demoteOnError: true, notify: true }
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
⚠️ The built-in default levels carry legacy values (`deepseek-v4-flash` + `medium`) that are
|
|
464
|
+
both invalid on 0.2.x hosts — a fresh machine must ship its own levels block or Auto fails
|
|
465
|
+
outright. `tier-cost` has zero hard dependencies beyond the host: without the router every
|
|
466
|
+
turn folds into manual/unknown, without the billing plugin tokens are counted and cost
|
|
425
467
|
stays 0 — both cases self-report in the panel notes.
|
|
426
|
-
|
|
427
|
-
### Catalog freshness guard (modelsGuard)
|
|
428
|
-
|
|
429
|
-
When a provider retires a model id, the level pointing at it goes stale. The
|
|
430
|
-
guard reads what each provider actually advertises (`ctx.llm.listModels`) at
|
|
431
|
-
activation and every `refreshMs` (default 10 min): a stale level's requests
|
|
432
|
-
**drift to the nearest fresh level** instead of failing, and the status report
|
|
433
|
-
names same-family candidate ids so the fix stays a one-line edit. Unreadable or
|
|
434
|
-
empty catalogs fail open (never marked stale); environments without a catalog
|
|
435
|
-
reader leave the layer inert.
|
|
436
|
-
|
|
437
|
-
```yaml
|
|
438
|
-
modelsGuard: { enabled: true, refreshMs: 600000 }
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
> The static list remains configuration (`cordis.patch.yml`) on purpose: the
|
|
442
|
-
> plugin never decides *which* new model to adopt (windows, prices and
|
|
443
|
-
> capabilities all change) — it guarantees a dead tier is not selected and
|
|
444
|
-
> tells you what to switch to.
|
|
445
|
-
|
|
446
|
-
## Publishing to npm
|
|
447
|
-
|
|
448
|
-
Both plugins live in this repository and publish independently:
|
|
449
|
-
|
|
450
|
-
```bash
|
|
451
|
-
npm publish # @apinggithub/mydsh-auto-model-router (root)
|
|
452
|
-
cd tier-cost && npm publish # @apinggithub/dsh-tier-cost
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
From 0.2.0 the peerDependencies are host-adaptive ranges, so fresh machines no
|
|
456
|
-
longer need the allow-version exemption (only 0.1.1 and earlier did). Run
|
|
468
|
+
|
|
469
|
+
### Catalog freshness guard (modelsGuard)
|
|
470
|
+
|
|
471
|
+
When a provider retires a model id, the level pointing at it goes stale. The
|
|
472
|
+
guard reads what each provider actually advertises (`ctx.llm.listModels`) at
|
|
473
|
+
activation and every `refreshMs` (default 10 min): a stale level's requests
|
|
474
|
+
**drift to the nearest fresh level** instead of failing, and the status report
|
|
475
|
+
names same-family candidate ids so the fix stays a one-line edit. Unreadable or
|
|
476
|
+
empty catalogs fail open (never marked stale); environments without a catalog
|
|
477
|
+
reader leave the layer inert.
|
|
478
|
+
|
|
479
|
+
```yaml
|
|
480
|
+
modelsGuard: { enabled: true, refreshMs: 600000 }
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
> The static list remains configuration (`cordis.patch.yml`) on purpose: the
|
|
484
|
+
> plugin never decides *which* new model to adopt (windows, prices and
|
|
485
|
+
> capabilities all change) — it guarantees a dead tier is not selected and
|
|
486
|
+
> tells you what to switch to.
|
|
487
|
+
|
|
488
|
+
## Publishing to npm
|
|
489
|
+
|
|
490
|
+
Both plugins live in this repository and publish independently:
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
npm publish # @apinggithub/mydsh-auto-model-router (root)
|
|
494
|
+
cd tier-cost && npm publish # @apinggithub/dsh-tier-cost
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
From 0.2.0 the peerDependencies are host-adaptive ranges, so fresh machines no
|
|
498
|
+
longer need the allow-version exemption (only 0.1.1 and earlier did). Run
|
|
457
499
|
`npm login --scope=apinggithub` once per package account setup.
|
|
500
|
+
|
|
501
|
+
## The silent-billing trap of free quota (real incident · rules)
|
|
502
|
+
|
|
503
|
+
A true story from the author's machine: L1 pointed at the Bailian free model `glm-5.3`
|
|
504
|
+
(`freeQuota: true`). When the quota ran out the provider did **not** return the promised
|
|
505
|
+
403 (`stop-when-exhausted` had not actually taken effect for that model) — calls kept
|
|
506
|
+
succeeding, now silently at pay-as-you-go rates, for a full day until a billing alert
|
|
507
|
+
arrived. `demoteOnError` had no error to catch.
|
|
508
|
+
|
|
509
|
+
Three rules, all required:
|
|
510
|
+
|
|
511
|
+
1. **Verify "stop when exhausted" per model on the provider side** (make one over-quota
|
|
512
|
+
call and confirm a 403). The router can only switch lines on errors it can see;
|
|
513
|
+
a provider that silently converts a free route into paid billing is invisible to it.
|
|
514
|
+
2. **Keep `costControl.defaultLevel` on a stable paid tier.** In this file's examples a
|
|
515
|
+
`defaultLevel: L1` where L1 is a free tier is exactly the incident shape. Prefer free
|
|
516
|
+
only at L0, or keep an explicit paid replacement for every free tier.
|
|
517
|
+
3. **Watch the billing panel per tier.** A "free" tier whose cost line keeps growing and
|
|
518
|
+
never zeroes is burning money — drop it from the free routes immediately.
|
|
519
|
+
|
|
520
|
+
> Another sharp edge: quota pools are counted per **provider+model**. The same id on two
|
|
521
|
+
> providers (e.g. `qwen3.8-flash` on both `bailian` and `qianwen`) are **separate pools**;
|
|
522
|
+
> quota and billing never cross over.
|
package/README.zh.md
CHANGED
|
@@ -99,6 +99,38 @@ npm install @apinggithub/mydsh-auto-model-router
|
|
|
99
99
|
dsh plugin --profile web add @apinggithub/mydsh-auto-model-router
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
## 宿主兼容性
|
|
103
|
+
|
|
104
|
+
DSH 把 profile 的插件树当成一次组合:补丁失败的 bundle 可能让整个 Harness 起不来,
|
|
105
|
+
而抛错的插件条目会被标记为「did not activate」——路由器从会话里直接消失。
|
|
106
|
+
所以本插件的行为约定是**能挂载、能降级、能打日志**,而不是在接口变动时直接失败。
|
|
107
|
+
|
|
108
|
+
| 宿主版本线 | 状态 |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| 0.1.x – 0.3.x | 已实测(`0.2.1-rc.2` 为基准宿主) |
|
|
111
|
+
| 0.4.x – 1.x | 允许挂载,但在日志与 `GET /api/mydsh-auto-model-router/status` 中标记为「未实测」 |
|
|
112
|
+
|
|
113
|
+
挂载时 `compat.mjs` 会探测插件用到的每个接口(`llm.registerAdapter`、
|
|
114
|
+
`llm.listModels`、`llm.stream`、scoped `ctx.inject`、`webServer.register`、
|
|
115
|
+
可选的 `tokenMeter`),结果记录在状态接口的 `host.compat` 字段里。在未知宿主上:
|
|
116
|
+
|
|
117
|
+
- **`tokenMeter` 不再是硬依赖**——软查找,缺失时改用插件自带的估算器。
|
|
118
|
+
- **没有 `registerAdapter` 也能挂载**——只记一条警告,模型选择框里不出现
|
|
119
|
+
`Auto`,路由逻辑本身不受影响。
|
|
120
|
+
- **HTTP 路由方法适配**——`webServer.register()`、`route()`、`addRoute()`
|
|
121
|
+
都可接受;全都没有时设置页显示「暂无报告」,而不是报错。
|
|
122
|
+
- **事件负载容错**——既接受当前的 `{ agent }` / `(session, event)` 形状,也接受
|
|
123
|
+
裸 agent / `{ session, event }` 形状;处理函数抛错时原样返回宿主自己的提案,
|
|
124
|
+
路由器的 bug 不会再让用户请求失败。
|
|
125
|
+
- **不再使用进程级全局变量**——适配器按每次挂载创建并闭包引用该次挂载的
|
|
126
|
+
router,同进程多 profile 不会互相覆盖。
|
|
127
|
+
- **版本报告**——当宿主愿意暴露版本(环境变量、app 服务或可解析的 manifest)时,
|
|
128
|
+
超出 `>=0.1.0-0 <0.4.0` 会打一条同时含宿主版本与实测范围的警告。能力探测始终
|
|
129
|
+
执行,因此即使宿主隐藏版本,也能从状态接口看到它实际提供了哪些接口。
|
|
130
|
+
|
|
131
|
+
peer 范围(`>=0.1.0-0 <1.0.0`)和 `engines.dsh` 故意比实测范围宽,避免宿主升级后
|
|
132
|
+
直接拒装;真正的提示是运行时那条警告——看到它就请提 issue。
|
|
133
|
+
|
|
102
134
|
## 配置
|
|
103
135
|
|
|
104
136
|
在 profile 的 `cordis.patch.yml` 中添加:
|
|
@@ -336,78 +368,96 @@ dsh plugin --profile desktop add ./tier-cost
|
|
|
336
368
|
## License
|
|
337
369
|
|
|
338
370
|
MIT
|
|
339
|
-
|
|
340
|
-
### 上下文窗口守卫(contextGuard)
|
|
341
|
-
|
|
342
|
-
档位可声明其模型的 `contextWindow`。会话实际上下文(最近一次请求的输入侧
|
|
343
|
-
token,含缓存读写)超过 `window × headroom`(默认 0.85)时,**该档位被跳过**,
|
|
344
|
-
路由向上漂到最近的装得下的档位;没有任何声明窗口装得下时保持原档(fail-open),
|
|
345
|
-
让 provider 用真实错误说话、交给降级链处理。跳过每 (档位, 会话) 记录一次,
|
|
346
|
-
`/status` 的 `contextGuard` 字段可见。未声明任何窗口时整层不生效。
|
|
347
|
-
|
|
348
|
-
```yaml
|
|
349
|
-
levels:
|
|
350
|
-
L0: { provider: bailian, model: qwen-max, contextWindow: 32768 } # 小窗口模型的保护
|
|
351
|
-
contextGuard: { enabled: true, headroom: 0.85 }
|
|
371
|
+
|
|
372
|
+
### 上下文窗口守卫(contextGuard)
|
|
373
|
+
|
|
374
|
+
档位可声明其模型的 `contextWindow`。会话实际上下文(最近一次请求的输入侧
|
|
375
|
+
token,含缓存读写)超过 `window × headroom`(默认 0.85)时,**该档位被跳过**,
|
|
376
|
+
路由向上漂到最近的装得下的档位;没有任何声明窗口装得下时保持原档(fail-open),
|
|
377
|
+
让 provider 用真实错误说话、交给降级链处理。跳过每 (档位, 会话) 记录一次,
|
|
378
|
+
`/status` 的 `contextGuard` 字段可见。未声明任何窗口时整层不生效。
|
|
379
|
+
|
|
380
|
+
```yaml
|
|
381
|
+
levels:
|
|
382
|
+
L0: { provider: bailian, model: qwen-max, contextWindow: 32768 } # 小窗口模型的保护
|
|
383
|
+
contextGuard: { enabled: true, headroom: 0.85 }
|
|
352
384
|
```
|
|
353
|
-
|
|
354
|
-
## 部署到其它电脑
|
|
355
|
-
|
|
356
|
-
一台机器 = 两条命令(本仓库含两个插件):
|
|
357
|
-
|
|
358
|
-
```powershell
|
|
359
|
-
git clone https://github.com/apinggithub/mydsh-auto-model-router
|
|
360
|
-
cd mydsh-auto-model-router; cd ..
|
|
361
|
-
# 宿主 0.2.x 会因插件 peer 钉死旧版本而拒装,先授予精确版本豁免(每个新版本各授一次):
|
|
362
|
-
dsh plugin --profile desktop allow-version '@apinggithub/mydsh-auto-model-router@0.1.1' --dsh-version 0.2.0-rc.2 --accept-risk
|
|
363
|
-
dsh plugin --profile desktop add ./mydsh-auto-model-router
|
|
364
|
-
dsh plugin --profile desktop add ./mydsh-auto-model-router/tier-cost # 可选:按档位拆账
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
然后在该机器的 `profiles/<profile>/cordis.patch.yml` 里写完整档位块,**provider/model 必须
|
|
368
|
-
是那条机器自己能用的路由,别照抄 id**:
|
|
369
|
-
|
|
370
|
-
```yaml
|
|
371
|
-
- id: mydsh-auto-model-router-runtime
|
|
372
|
-
name: '@apinggithub/mydsh-auto-model-router'
|
|
373
|
-
config:
|
|
374
|
-
levels:
|
|
375
|
-
L0: { provider: ..., model: ..., reasoningEffort: off, contextWindow: ..., freeQuota: true }
|
|
376
|
-
L1: { provider: ..., model: ..., reasoningEffort: low, contextWindow: ..., freeQuota: true }
|
|
377
|
-
L2: { provider: ..., model: ..., reasoningEffort: high, contextWindow: ..., freeQuota: true }
|
|
378
|
-
L3: { provider: ..., model: ..., reasoningEffort: max, contextWindow: ... }
|
|
379
|
-
contextGuard: { enabled: true, headroom: 0.85 }
|
|
380
|
-
freeQuota: { enabled: true, demoteOnError: true, notify: true }
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
⚠️ 内置默认 levels 是历史值(`deepseek-v4-flash` + `medium`),在 0.2.x 宿主上**两者都不是
|
|
384
|
-
合法值**——新机器必须自带档位块,否则选 Auto 会直接失败。
|
|
385
|
-
`tier-cost` 对宿主其余部分零依赖:缺路由插件时全部归 manual/unknown,缺计费插件时只统计
|
|
385
|
+
|
|
386
|
+
## 部署到其它电脑
|
|
387
|
+
|
|
388
|
+
一台机器 = 两条命令(本仓库含两个插件):
|
|
389
|
+
|
|
390
|
+
```powershell
|
|
391
|
+
git clone https://github.com/apinggithub/mydsh-auto-model-router
|
|
392
|
+
cd mydsh-auto-model-router; cd ..
|
|
393
|
+
# 宿主 0.2.x 会因插件 peer 钉死旧版本而拒装,先授予精确版本豁免(每个新版本各授一次):
|
|
394
|
+
dsh plugin --profile desktop allow-version '@apinggithub/mydsh-auto-model-router@0.1.1' --dsh-version 0.2.0-rc.2 --accept-risk
|
|
395
|
+
dsh plugin --profile desktop add ./mydsh-auto-model-router
|
|
396
|
+
dsh plugin --profile desktop add ./mydsh-auto-model-router/tier-cost # 可选:按档位拆账
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
然后在该机器的 `profiles/<profile>/cordis.patch.yml` 里写完整档位块,**provider/model 必须
|
|
400
|
+
是那条机器自己能用的路由,别照抄 id**:
|
|
401
|
+
|
|
402
|
+
```yaml
|
|
403
|
+
- id: mydsh-auto-model-router-runtime
|
|
404
|
+
name: '@apinggithub/mydsh-auto-model-router'
|
|
405
|
+
config:
|
|
406
|
+
levels:
|
|
407
|
+
L0: { provider: ..., model: ..., reasoningEffort: off, contextWindow: ..., freeQuota: true }
|
|
408
|
+
L1: { provider: ..., model: ..., reasoningEffort: low, contextWindow: ..., freeQuota: true }
|
|
409
|
+
L2: { provider: ..., model: ..., reasoningEffort: high, contextWindow: ..., freeQuota: true }
|
|
410
|
+
L3: { provider: ..., model: ..., reasoningEffort: max, contextWindow: ... }
|
|
411
|
+
contextGuard: { enabled: true, headroom: 0.85 }
|
|
412
|
+
freeQuota: { enabled: true, demoteOnError: true, notify: true }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
⚠️ 内置默认 levels 是历史值(`deepseek-v4-flash` + `medium`),在 0.2.x 宿主上**两者都不是
|
|
416
|
+
合法值**——新机器必须自带档位块,否则选 Auto 会直接失败。
|
|
417
|
+
`tier-cost` 对宿主其余部分零依赖:缺路由插件时全部归 manual/unknown,缺计费插件时只统计
|
|
386
418
|
token、费用记 0,均会在面板 notes 里说明。
|
|
387
|
-
|
|
388
|
-
### 目录新鲜度守卫(modelsGuard)
|
|
389
|
-
|
|
390
|
-
模型版本升级后,provider 不再公布的 id 会变成「过期路由」。守卫在激活时与每
|
|
391
|
-
`refreshMs`(默认 10 分钟)用 `ctx.llm.listModels(provider)` 读取各 provider
|
|
392
|
-
实际公布的模型:过期档位的请求**自动漂移到最近的鲜活档位**(绝不静默打死),
|
|
393
|
-
状态报告列出过期档位与**同族候选 id**,改档位仍是一行编辑的事。目录读不到或
|
|
394
|
-
为空时一律 fail-open(不判过期)。未注册 listModels 的环境整层惰性。
|
|
395
|
-
|
|
396
|
-
```yaml
|
|
397
|
-
modelsGuard: { enabled: true, refreshMs: 600000 }
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
> 静态列表本身仍是配置(`cordis.patch.yml`)——这是刻意的:插件不会替你决定
|
|
401
|
-
> 换成哪个模型(窗口/单价/能力都变了),它只保证**坏档位不再被选中**并告诉你该换什么。
|
|
402
|
-
|
|
403
|
-
## 发布到 npm
|
|
404
|
-
|
|
405
|
-
两个插件同一仓库、各自独立发布(root 与本目录的 package.json 都可直接 publish):
|
|
406
|
-
|
|
407
|
-
```bash
|
|
408
|
-
npm publish # @apinggithub/mydsh-auto-model-router(root)
|
|
409
|
-
cd tier-cost && npm publish # @apinggithub/dsh-tier-cost
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
0.2.0 起 peerDependencies 已放宽为宿主自适应范围,新机器安装**不再需要**
|
|
419
|
+
|
|
420
|
+
### 目录新鲜度守卫(modelsGuard)
|
|
421
|
+
|
|
422
|
+
模型版本升级后,provider 不再公布的 id 会变成「过期路由」。守卫在激活时与每
|
|
423
|
+
`refreshMs`(默认 10 分钟)用 `ctx.llm.listModels(provider)` 读取各 provider
|
|
424
|
+
实际公布的模型:过期档位的请求**自动漂移到最近的鲜活档位**(绝不静默打死),
|
|
425
|
+
状态报告列出过期档位与**同族候选 id**,改档位仍是一行编辑的事。目录读不到或
|
|
426
|
+
为空时一律 fail-open(不判过期)。未注册 listModels 的环境整层惰性。
|
|
427
|
+
|
|
428
|
+
```yaml
|
|
429
|
+
modelsGuard: { enabled: true, refreshMs: 600000 }
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
> 静态列表本身仍是配置(`cordis.patch.yml`)——这是刻意的:插件不会替你决定
|
|
433
|
+
> 换成哪个模型(窗口/单价/能力都变了),它只保证**坏档位不再被选中**并告诉你该换什么。
|
|
434
|
+
|
|
435
|
+
## 发布到 npm
|
|
436
|
+
|
|
437
|
+
两个插件同一仓库、各自独立发布(root 与本目录的 package.json 都可直接 publish):
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
npm publish # @apinggithub/mydsh-auto-model-router(root)
|
|
441
|
+
cd tier-cost && npm publish # @apinggithub/dsh-tier-cost
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
0.2.0 起 peerDependencies 已放宽为宿主自适应范围,新机器安装**不再需要**
|
|
413
445
|
allow-version 豁免(豁免仅 0.1.1 及更早需要)。发布前各自 `npm login --scope=apinggithub`。
|
|
446
|
+
|
|
447
|
+
## 免费额度的静默计费陷阱(真实事故 · 守则)
|
|
448
|
+
|
|
449
|
+
本仓库作者机器上的实录事故:L1 指向百炼免费模型 `glm-5.3`(`freeQuota: true`),额度耗尽后
|
|
450
|
+
服务商**没有按「用完即停」返回 403**(批量开关未覆盖到该模型/生效延迟),调用照常成功——
|
|
451
|
+
于是 `demoteOnError` 无错可捕,**glm-5.3 静默转按量计费跑了一整天**,直到账单通知才被发现。
|
|
452
|
+
|
|
453
|
+
三条守则,缺一不可:
|
|
454
|
+
|
|
455
|
+
1. **先在服务商侧逐模型确认「用完即停」真的生效**(发一条超额调用看是否返回 403,别信批量开关)。
|
|
456
|
+
路由器只能在**看得见错误**时换线;额度耗尽若被服务商静默转按量,插件无从得知。
|
|
457
|
+
2. **`costControl.defaultLevel` 指向稳定付费档**(本文各处示例的 `defaultLevel: L1` 若 L1 是免费档,
|
|
458
|
+
正是本次事故形态)。免费档只放最低层(L0),或至少每个免费档都有明确的付费替代。
|
|
459
|
+
3. **用计费面板按档核账**:任何"免费"档的费用行持续大于 0 且不归零,立刻怀疑额度已尽、
|
|
460
|
+
在按量扣费——把该档从免费路由摘下来。
|
|
461
|
+
|
|
462
|
+
> 另一个易错点:免费额度池按 **provider+model 独立**计数。同一个模型 id 出现在多个 provider
|
|
463
|
+
> (如 `qwen3.8-flash` 同时在 bailian 与 qianwen)是**不同的池子**,额度与计费互不相干。
|