dsh-model 0.22.0 → 0.22.2
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/dist/combo.js +3 -0
- package/dist/daemon/failures.js +7 -0
- package/dist/dsh/connect.js +17 -2
- package/package.json +1 -1
- package/plugin/client.js +1 -0
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
|
/** 按顺序试组合里的模型,把第一个成功的响应原样转回客户端 */
|
package/dist/daemon/failures.js
CHANGED
|
@@ -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 国际版不行可以用国内版),或建一个组合自动换到下一个;继续调用有封号风险,建议在「来源」里先把它关掉', 'Blocked by the provider: it is restricting use outside its official app, so retrying or switching its models will not help. Use another source (for WorkBuddy AI, try WorkBuddy China), 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/dist/dsh/connect.js
CHANGED
|
@@ -52,6 +52,12 @@ async function checkLocation(state, location, force) {
|
|
|
52
52
|
return warnings;
|
|
53
53
|
}
|
|
54
54
|
/** 写入 / 移除一组项。所有权记在 state.dsh.ownedProviders / ownedRefs */
|
|
55
|
+
/**
|
|
56
|
+
* 上次安装留下的东西(台账丢了,例如卸载没走完、或手动删过 ~/.dsh-model):
|
|
57
|
+
* dsh-model 格式的 key(dshm_ + 43 位),或指向 DSH_MODEL_API_KEY 的 dsh-model* provider。直接接管,不让用户去手动删。
|
|
58
|
+
*/
|
|
59
|
+
export const isLeftoverKey = (value) => /^dshm_[A-Za-z0-9_-]{43}$/.test(value);
|
|
60
|
+
const isLeftoverProvider = (id, spec) => id.startsWith('dsh-model') && spec?.apiKeyEnv === KEY_REF;
|
|
55
61
|
export async function applyDsh(ctx, state, items, opts) {
|
|
56
62
|
const location = await locateDsh(ctx, opts.profile);
|
|
57
63
|
const warnings = await checkLocation(state, location, opts.force);
|
|
@@ -72,7 +78,12 @@ export async function applyDsh(ctx, state, items, opts) {
|
|
|
72
78
|
continue;
|
|
73
79
|
}
|
|
74
80
|
if (existing !== undefined && !ownedRefs.has(name) && !opts.isOwnRef?.(name, existing)) {
|
|
75
|
-
|
|
81
|
+
if (name === KEY_REF && isLeftoverKey(existing)) {
|
|
82
|
+
warnings.push(L(`接管了上次安装留下的 ${name}(换成这次的 key)`, `Took over ${name} left by an earlier install (replaced with this install's key)`));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw new DshModelError('credential_ref_conflict', L(`dsh 凭据里已有 ${name},且不是 dsh-model 写的`, `dsh credentials already contain ${name}, not written by dsh-model`), L('不会覆盖它。要让 dsh-model 接管,请先在 dsh 设置里删掉它再重试', 'It will not be overwritten. To let dsh-model manage it, remove it in dsh settings and retry'));
|
|
86
|
+
}
|
|
76
87
|
}
|
|
77
88
|
credAfter = upsertRef(credAfter === '' ? null : credAfter, value, name);
|
|
78
89
|
ownedRefs.add(name);
|
|
@@ -88,7 +99,11 @@ export async function applyDsh(ctx, state, items, opts) {
|
|
|
88
99
|
}
|
|
89
100
|
continue;
|
|
90
101
|
}
|
|
91
|
-
|
|
102
|
+
const existingProvider = ownedProviders.has(id) ? null : readProvider(patchAfter === '' ? null : patchAfter, id);
|
|
103
|
+
if (existingProvider && isLeftoverProvider(id, existingProvider)) {
|
|
104
|
+
warnings.push(L(`接管了上次安装留下的 provider ${id}`, `Took over provider ${id} left by an earlier install`));
|
|
105
|
+
}
|
|
106
|
+
else if (existingProvider) {
|
|
92
107
|
throw new DshModelError('provider_conflict', L(`dsh 里已有 provider ${id},且不是 dsh-model 写的`, `dsh already has a provider ${id}, not written by dsh-model`), L('不会覆盖它', 'It will not be overwritten'));
|
|
93
108
|
}
|
|
94
109
|
const r = upsertProvider(patchAfter === '' ? null : patchAfter, id, spec);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
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
|
@@ -575,6 +575,7 @@ window.__ModuleLoader__.load({
|
|
|
575
575
|
try { return Number(window.localStorage.getItem(FAIL_ACK)) || 0 } catch { return 0 }
|
|
576
576
|
}
|
|
577
577
|
const KIND_LABEL = {
|
|
578
|
+
blocked: () => L('被服务商拦截', 'blocked by the provider'),
|
|
578
579
|
rate: () => L('限流', 'rate limits'),
|
|
579
580
|
quota: () => L('额度或套餐不足', 'quota or plan'),
|
|
580
581
|
auth: () => L('登录或 key 失效', 'sign-in or key'),
|