dsh-model 0.22.1 → 0.22.3

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 CHANGED
@@ -26,7 +26,7 @@ You may have several at once: Codex from ChatGPT Plus, a Kimi coding plan, free
26
26
  dsh-model turns them into **one address and one kind of key**:
27
27
 
28
28
  - 🔌 **Connect once, use everywhere**: every model shows up in dsh, grouped by source; any other tool uses the same address and key.
29
- - 🆓 **Get the most from free tiers**: WorkBuddy credits, free Gemini / Groq / OpenRouter keys and local models; every source is tagged Free / Free · limited / Paid.
29
+ - 🆓 **Get the most from free tiers**: free Gemini / Groq / OpenRouter keys, local models and WorkBuddy credits; every source is tagged Free / Free · limited / Paid.
30
30
  - 📅 **Daily check-in, credits claimed for you**: WorkBuddy (China), Qoder and Trae are checked in automatically every day.
31
31
  - 🔁 **Rate limits are not the end**: build a combo (say Codex → Kimi → WorkBuddy) and requests move to the next model on a rate limit or used-up quota; a model offered by several sources also switches automatically.
32
32
  - 📊 **Know where you stand**: plan usage with reset countdowns, remaining credits and when they expire, success rate and speed per model, recent failures with advice, and a notice when quota runs low.
@@ -82,7 +82,7 @@ If a source is signed in but has no usable plan (say, a Kimi account without Kim
82
82
 
83
83
  | Source | Cost | Notes |
84
84
  |---|---|---|
85
- | WorkBuddy / WorkBuddy AI | Free | Credits come with the account |
85
+ | WorkBuddy / WorkBuddy AI | Free | Credits come with the account. **Since 2026-10 WorkBuddy recognises conversations sent by dsh and blocks them**, so it barely works inside dsh; check-in still works |
86
86
  | Ollama / LM Studio | Free | Models running on this machine; they appear once installed and started, no sign-in |
87
87
  | Gemini (Google AI Studio), Groq | Free · limited | Paste a free API key; daily / per-minute limits apply |
88
88
  | OpenRouter | Free · limited | `:free` models are free (about 50 requests a day) |
package/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
  dsh-model 把它们合成**一个地址、一种 key**:
27
27
 
28
28
  - 🔌 **一处接入,处处可用**:dsh 里自动出现全部模型,按来源分组;其他软件填上同一个地址和 key 就行。
29
- - 🆓 **把免费的用足**:WorkBuddy 积分、Gemini / Groq / OpenRouter 免费 key、本机模型都能接;每个来源标出「免费 / 免费·额度少 / 需付费」。
29
+ - 🆓 **把免费的用足**:Gemini / Groq / OpenRouter 免费 key、本机模型、WorkBuddy 积分都能接;每个来源标出「免费 / 免费·额度少 / 需付费」。
30
30
  - 📅 **每日签到,自动领积分**:WorkBuddy 国内版、Qoder、Trae 每天自动签到,不用记。
31
31
  - 🔁 **不怕限流**:自己排一个组合(如 Codex → Kimi → WorkBuddy),一个限流或额度用完就自动换下一个;同一个模型在几个来源都有时也会自动切换。
32
32
  - 📊 **心里有数**:套餐用量和重置倒计时、剩余积分和到期时间、每个模型的成功率和速度、最近失败的原因和建议;额度快用完时提醒。
@@ -82,7 +82,7 @@ dsh-model setup
82
82
 
83
83
  | 来源 | 费用 | 说明 |
84
84
  |---|---|---|
85
- | WorkBuddy / WorkBuddy AI | 免费 | 账号自带积分 |
85
+ | WorkBuddy / WorkBuddy AI | 免费 | 账号自带积分。**2026-10 起 WorkBuddy 会认出 dsh 发出的对话并拦下**,在 dsh 里基本用不了;签到照常 |
86
86
  | Ollama / LM Studio | 免费 | 本机运行的模型。装好并启动后自动出现,不用登录 |
87
87
  | Gemini(Google AI Studio)、Groq | 免费·额度少 | 填免费 API key 即可,每天 / 每分钟有上限 |
88
88
  | OpenRouter | 免费·额度少 | 名字带 `:free` 的模型免费(每天约 50 次) |
package/dist/combo.js CHANGED
@@ -4,6 +4,7 @@
4
4
  // 请求先发给第一个;限流、额度用完、出错就换下一个,直到有一个成功。已经开始输出之后不再切换。
5
5
  // 引擎把 combo/<id> 转给守护进程(openai-compatibility 上游),守护进程再逐个调引擎里的真实模型。
6
6
  import { Readable } from 'node:stream';
7
+ import { BLOCKED_RE } from './daemon/failures.js';
7
8
  import { DshModelError } from './errors.js';
8
9
  import { L } from './i18n.js';
9
10
  export const COMBO_PREFIX = 'combo';
@@ -45,6 +46,8 @@ export function normalizeCombo(input, available) {
45
46
  export function shouldFallBack(status, text) {
46
47
  if (status !== 400 && status !== 422)
47
48
  return true;
49
+ if (BLOCKED_RE.test(text))
50
+ return true;
48
51
  return /quota|rate|limit|credit|context|too long|too many tokens|maximum|exceed|overload|unavailable|not supported|unsupported/i.test(text);
49
52
  }
50
53
  /** 按顺序试组合里的模型,把第一个成功的响应原样转回客户端 */
@@ -866,7 +866,9 @@ export class Daemon {
866
866
  catch {
867
867
  // 不是 JSON 也算通了
868
868
  }
869
- return { ok: true, ms, reply };
869
+ // 测试只发一句短话;WorkBuddy 会拦带 dsh 内置说明的真实对话(2026-10),通过不代表在 dsh 里能用
870
+ const note = /^workbuddy(-ai)?\//.test(model) ? L('这只是一句短测试;WorkBuddy 会拦下 dsh 里的真实对话', 'Only a short test; WorkBuddy blocks real conversations from dsh') : undefined;
871
+ return { ok: true, ms, reply, ...(note ? { note } : {}) };
870
872
  }
871
873
  let message = text.slice(0, 300);
872
874
  let code;
@@ -38,9 +38,16 @@ export function parseErrorLog(text) {
38
38
  }
39
39
  return { at, model, source: model.includes('/') ? model.split('/')[0] : '', status, ...(code ? { code } : {}), message: message.replace(/\s+/g, ' ').slice(0, 300) };
40
40
  }
41
+ /** 服务商拦截第三方调用的报错 */
42
+ export const BLOCKED_RE = /请求被拦截|11-128|unapproved channel|illegal api invocation/i;
41
43
  /** 按常见原因归类,给出该怎么办 */
42
44
  export function classify(f) {
43
45
  const m = `${f.code ?? ''} ${f.message}`.toLowerCase();
46
+ // 服务商在挡官方客户端之外的调用(2026-10 起 WorkBuddy:国际版"请求被拦截",国内版 11-128 unapproved channel)。
47
+ // 不去绕:改用别的来源
48
+ if (BLOCKED_RE.test(m)) {
49
+ return { kind: 'blocked', advice: L('被服务商拦截:它在限制官方客户端之外的使用(WorkBuddy 会认出 dsh 发出的请求并拦下,国内版、国际版都是),换模型、重试都没用。请改用其他来源,或建一个组合自动换到下一个;继续调用有封号风险,建议在「来源」里先把它关掉', 'Blocked by the provider: it is restricting use outside its official app (WorkBuddy recognises requests sent by dsh and blocks them, China and international alike), so retrying or switching its models will not help. Use another source, or build a combo that moves on automatically; continuing may get the account banned, so consider turning this source off under Sources') };
50
+ }
44
51
  if (f.status === 429 || /cooldown|rate|频繁|too many/.test(m)) {
45
52
  return { kind: 'rate', advice: L('被限流:请求太频繁。等一会儿再试,或换一个来源的同款模型', 'Rate limited: too many requests. Wait a bit, or use the same model from another source') };
46
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-model",
3
- "version": "0.22.1",
3
+ "version": "0.22.3",
4
4
  "description": "One OpenAI-compatible address for all your models: subscriptions (Codex, Kimi…), free tiers (WorkBuddy, Gemini, Groq, OpenRouter), local models (Ollama, LM Studio), daily check-in credits, auto failover. Works with DeepSeek Harness (dsh) and any OpenAI-compatible tool. 一个地址用上你所有的模型。",
5
5
  "keywords": [
6
6
  "dsh-plugin",
package/plugin/client.js CHANGED
@@ -412,6 +412,7 @@ window.__ModuleLoader__.load({
412
412
  m.recommended ? h('span', { style: { fontSize: 11, color: C.accent } }, L('推荐', 'Suggested')) : null,
413
413
  tests[m.id] ? h('span', { style: { fontSize: 11, color: tests[m.id].running ? C.sub : tests[m.id].ok ? C.ok : C.err, maxWidth: 260, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: tests[m.id].message || tests[m.id].reply || '' },
414
414
  tests[m.id].running ? L('测试中…', 'Testing…') : tests[m.id].ok ? L(`可用 · ${(tests[m.id].ms / 1000).toFixed(1)} 秒${tests[m.id].reply ? ` · ${tests[m.id].reply}` : ''}`, `OK · ${(tests[m.id].ms / 1000).toFixed(1)}s${tests[m.id].reply ? ` · ${tests[m.id].reply}` : ''}`) : (tests[m.id].advice || L('不可用', 'Not working'))) : null,
415
+ tests[m.id]?.ok && tests[m.id].note ? h('span', { style: { fontSize: 11, color: C.warn } }, tests[m.id].note) : null,
415
416
  h('button', { type: 'button', style: { ...S.btn, padding: '2px 8px' }, disabled: tests[m.id]?.running, onClick: (e) => { e.preventDefault(); void test(m.id) } }, L('测一下', 'Test'))))),
416
417
  msg ? h('div', { style: { fontSize: 12, color: C.err, marginTop: 6 } }, msg) : null,
417
418
  h('div', { style: { ...S.line, marginTop: 8, flexWrap: 'wrap' } },
@@ -490,6 +491,7 @@ window.__ModuleLoader__.load({
490
491
  s.loggedIn && s.kind !== 'opencode' && s.kind !== 'local' ? h('button', { type: 'button', style: S.btn, onClick: () => logout(s) }, s.kind === 'apikey' ? L('删除 key', 'Remove key') : L('退出登录', 'Sign out')) : null,
491
492
  (s.kind === 'opencode' || s.kind === 'apikey') && s.loggedIn ? h('button', { type: 'button', style: S.btn, onClick: () => onKey(s) }, L('换 key', 'Change key')) : null),
492
493
  picking === s.id ? h(ModelPicker, { source: s, onClose: (saved) => { setPicking(''); if (saved) void reload() } }) : null,
494
+ s.kind === 'workbuddy' && s.loggedIn ? h('div', { style: { fontSize: 12, color: C.warn, paddingLeft: 46, marginTop: 4, lineHeight: 1.6 } }, L('注意:WorkBuddy 现在会认出 dsh 发出的对话并拦下(「请求被拦截」),「测一下」能通也不代表能用。积分建议在官方 WorkBuddy 软件里用;签到照常。', 'Note: WorkBuddy now recognises conversations sent by dsh and blocks them ("请求被拦截"); a passing Test does not mean it works. Use the credits in the official WorkBuddy app; check-in still works.')) : null,
493
495
  s.enabled ? h(Usage, { usage: s.usage, subscribeUrl: s.subscribeUrl }) : null,
494
496
  !s.loggedIn && s.pricing ? h('div', { style: { ...S.meta, paddingLeft: 46, marginTop: 4 } },
495
497
  L(s.pricing.zh, s.pricing.en),
@@ -575,6 +577,7 @@ window.__ModuleLoader__.load({
575
577
  try { return Number(window.localStorage.getItem(FAIL_ACK)) || 0 } catch { return 0 }
576
578
  }
577
579
  const KIND_LABEL = {
580
+ blocked: () => L('被服务商拦截', 'blocked by the provider'),
578
581
  rate: () => L('限流', 'rate limits'),
579
582
  quota: () => L('额度或套餐不足', 'quota or plan'),
580
583
  auth: () => L('登录或 key 失效', 'sign-in or key'),