dsh-deepseek-web-login 0.2.2 → 0.3.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 +42 -0
- package/lib/client.js +56 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +88 -12
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/tools/panel-preview.mjs +5 -0
package/lib/index.js
CHANGED
|
@@ -874,6 +874,46 @@ function clampMaxRefImages(value) {
|
|
|
874
874
|
if (!Number.isFinite(value)) return 24;
|
|
875
875
|
return Math.max(MAX_REF_IMAGES_BOUNDS.min, Math.min(MAX_REF_IMAGES_BOUNDS.max, Math.round(value)));
|
|
876
876
|
}
|
|
877
|
+
/**
|
|
878
|
+
* 对外声明的模型上下文窗口(token),即模型信息里的 `context.contextWindow`。
|
|
879
|
+
*
|
|
880
|
+
* 它决定的是**DSH 什么时候开始压缩/截断历史**,而不是插件发多少 —— 声明 1M,DSH 就认为
|
|
881
|
+
* "还装得下",于是一直不压,每轮都把整段转写重发。对只跑短任务的人来说,那部分体量是白烧的。
|
|
882
|
+
*
|
|
883
|
+
* ⚠️ 它和 `maxPromptChars` 是**两道独立阀门**,别混:
|
|
884
|
+
* - `maxPromptChars` 管"单次请求最多发多少字符"(插件自己切,见上);
|
|
885
|
+
* - `contextWindow` 管"DSH 认为模型能装多少"(DSH 据此决定压不压历史)。
|
|
886
|
+
* 两个都调小,单次体量才会真的降下来;只调一个,另一个仍会把请求撑大。
|
|
887
|
+
*
|
|
888
|
+
* 上限取 DeepSeek 标称的 1Mi(服务端自己的数字也都是 1024 的整数倍),也就是本插件
|
|
889
|
+
* 一直以来的硬编码值;下限 32K —— 再小的话工具目录(约 5.6 万字符)就先装不下了。
|
|
890
|
+
*/
|
|
891
|
+
const CONTEXT_WINDOW_BOUNDS = {
|
|
892
|
+
min: 32768,
|
|
893
|
+
max: 1048576
|
|
894
|
+
};
|
|
895
|
+
/** 默认=原来的硬编码值(1Mi)—— 不动这个开关的人行为完全不变。 */
|
|
896
|
+
const DEFAULT_CONTEXT_WINDOW = 1048576;
|
|
897
|
+
/**
|
|
898
|
+
* 面板滑块的档位。
|
|
899
|
+
*
|
|
900
|
+
* **用档位而不是线性连续值**:32K→1M 是 32 倍跨度,线性拖动时前四分之三的行程都挤在低档位,
|
|
901
|
+
* 手感很差;档位化之后每一格都是"翻倍"这个直觉,也正好落在 1024 的整数倍上。
|
|
902
|
+
* 直接调接口传任意值仍被接受(只做边界夹取,不强制吸附)—— 档位只约束面板。
|
|
903
|
+
*/
|
|
904
|
+
const CONTEXT_WINDOW_OPTIONS = [
|
|
905
|
+
32768,
|
|
906
|
+
65536,
|
|
907
|
+
131072,
|
|
908
|
+
262144,
|
|
909
|
+
524288,
|
|
910
|
+
1048576
|
|
911
|
+
];
|
|
912
|
+
/** 规整上下文窗口:非数 → 默认;越界 → 夹到边界。 */
|
|
913
|
+
function clampContextWindow(value) {
|
|
914
|
+
if (!Number.isFinite(value)) return DEFAULT_CONTEXT_WINDOW;
|
|
915
|
+
return Math.max(CONTEXT_WINDOW_BOUNDS.min, Math.min(CONTEXT_WINDOW_BOUNDS.max, Math.round(value)));
|
|
916
|
+
}
|
|
877
917
|
/** 距上次请求超过这么久就算"歇过了",连续计数归零。 */
|
|
878
918
|
const LONG_RUN_IDLE_RESET_MS = 12e4;
|
|
879
919
|
/** 间隔可选的推荐档位(设置页的快捷按钮用):[下限, 上限]。 */
|
|
@@ -965,6 +1005,7 @@ function readGateSettings() {
|
|
|
965
1005
|
if (lrb) out.longRunBreakMs = lrb;
|
|
966
1006
|
if (Number.isFinite(parsed?.maxPromptChars)) out.maxPromptChars = clampMaxPromptChars(Number(parsed.maxPromptChars));
|
|
967
1007
|
if (Number.isFinite(parsed?.maxRefImages)) out.maxRefImages = clampMaxRefImages(Number(parsed.maxRefImages));
|
|
1008
|
+
if (Number.isFinite(parsed?.contextWindow)) out.contextWindow = clampContextWindow(Number(parsed.contextWindow));
|
|
968
1009
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
969
1010
|
} catch {
|
|
970
1011
|
return;
|
|
@@ -1116,6 +1157,7 @@ function createRequestGate(options = {}) {
|
|
|
1116
1157
|
/** prompt 字符上限(同 cleanupMode:只是存着,执行在 adapter)。 */
|
|
1117
1158
|
let maxPromptChars = clampMaxPromptChars(options.maxPromptChars ?? 4e5);
|
|
1118
1159
|
let maxRefImages = clampMaxRefImages(options.maxRefImages ?? 24);
|
|
1160
|
+
let contextWindow = clampContextWindow(options.contextWindow ?? 1048576);
|
|
1119
1161
|
let cleanupMode = options.sessionCleanup;
|
|
1120
1162
|
let cleanupBatch = normalizeCleanupRange(options.cleanupBatch, CLEANUP_BATCH_BOUNDS);
|
|
1121
1163
|
let cleanupDelayMs = normalizeCleanupRange(options.cleanupDelayMs, CLEANUP_DELAY_BOUNDS_MS);
|
|
@@ -1132,7 +1174,8 @@ function createRequestGate(options = {}) {
|
|
|
1132
1174
|
longRunThreshold,
|
|
1133
1175
|
...longRunBreakMs ? { longRunBreakMs } : {},
|
|
1134
1176
|
maxPromptChars,
|
|
1135
|
-
maxRefImages
|
|
1177
|
+
maxRefImages,
|
|
1178
|
+
contextWindow
|
|
1136
1179
|
};
|
|
1137
1180
|
}
|
|
1138
1181
|
function configure(next) {
|
|
@@ -1142,6 +1185,7 @@ function createRequestGate(options = {}) {
|
|
|
1142
1185
|
if (next.sessionCleanup !== void 0) cleanupMode = next.sessionCleanup;
|
|
1143
1186
|
if (next.maxPromptChars !== void 0) maxPromptChars = clampMaxPromptChars(Number(next.maxPromptChars));
|
|
1144
1187
|
if (next.maxRefImages !== void 0) maxRefImages = clampMaxRefImages(Number(next.maxRefImages));
|
|
1188
|
+
if (next.contextWindow !== void 0) contextWindow = clampContextWindow(Number(next.contextWindow));
|
|
1145
1189
|
if (next.cleanupBatch !== void 0) {
|
|
1146
1190
|
const value = normalizeCleanupRange(next.cleanupBatch, CLEANUP_BATCH_BOUNDS);
|
|
1147
1191
|
if (value) cleanupBatch = value;
|
|
@@ -1395,10 +1439,16 @@ function muteUntilMs(json) {
|
|
|
1395
1439
|
if (!Number.isFinite(seconds) || seconds <= 0) return void 0;
|
|
1396
1440
|
return Math.round(seconds * 1e3);
|
|
1397
1441
|
}
|
|
1398
|
-
/**
|
|
1442
|
+
/**
|
|
1443
|
+
* 被限制时的用户可读文案。
|
|
1444
|
+
*
|
|
1445
|
+
* 只说结论与解除时间 —— **不解释原因**。原因(免费网页端对高频自动化的静默限流、
|
|
1446
|
+
* 登录态其实有效、只有 completion 被拒……)属于开发/排查信息,写在这里就够了:
|
|
1447
|
+
* 用户看到报错时只需要知道「是什么 + 到几点结束」,长篇解释只会淹没这两件事。
|
|
1448
|
+
*/
|
|
1399
1449
|
function mutedMessage(untilMs) {
|
|
1400
|
-
if (untilMs === void 0) return "DeepSeek
|
|
1401
|
-
return `DeepSeek
|
|
1450
|
+
if (untilMs === void 0) return "DeepSeek 网页端已封禁本账号(未给出解除时间)";
|
|
1451
|
+
return `DeepSeek 网页端已封禁本账号,${new Date(untilMs).toLocaleString("zh-CN", { hour12: false })} 解除(约 ${Math.max(1, Math.round((untilMs - Date.now()) / 6e4))} 分钟)`;
|
|
1402
1452
|
}
|
|
1403
1453
|
/**
|
|
1404
1454
|
* 「同一账号同时只能生成一条」的并发拒绝(实测 2026-09-11:两个 DSH 窗口共用同一网页账号,
|
|
@@ -2867,7 +2917,7 @@ async function openCompletion(auth, params, signal, transport) {
|
|
|
2867
2917
|
const text = await resp.text().catch(() => "");
|
|
2868
2918
|
const code = httpErrorCode(resp.status);
|
|
2869
2919
|
const retryAfter = parseRetryAfterMs(resp.headers.get("retry-after"));
|
|
2870
|
-
const hint = code === "AUTH" ? " —— 网页登录态可能已过期,请到「设置 → DeepSeek 网页登录」重新登录" : code === "RATE_LIMIT" ? " ——
|
|
2920
|
+
const hint = code === "AUTH" ? " —— 网页登录态可能已过期,请到「设置 → DeepSeek 网页登录」重新登录" : code === "RATE_LIMIT" ? " —— 网页版限流" : "";
|
|
2871
2921
|
retireSession(sessionId);
|
|
2872
2922
|
params.onDeleteSession?.(sessionId);
|
|
2873
2923
|
throw new AdapterLlmError(`DeepSeek web completion failed (HTTP ${resp.status})${text ? `: ${text.slice(0, 200)}` : ""}${hint}`, code, {
|
|
@@ -2899,7 +2949,7 @@ async function openCompletion(auth, params, signal, transport) {
|
|
|
2899
2949
|
const muted = isMutedError(biz);
|
|
2900
2950
|
const busy = !muted && !!biz && isBusyGenerating(biz.msg);
|
|
2901
2951
|
const untilMs = muteUntilMs(parsed);
|
|
2902
|
-
const failure = biz ? new AdapterLlmError(muted ? mutedMessage(untilMs) : busy ? "
|
|
2952
|
+
const failure = biz ? new AdapterLlmError(muted ? mutedMessage(untilMs) : busy ? "网页版限流:同一账号同时只能生成一条消息,稍后自动重试" : bizErrorMessage(biz.code, biz.msg), muted || busy ? "RATE_LIMIT" : isInvalidSessionError(biz) ? "TRANSPORT" : isInvalidRefFileError(biz) ? "INVALID_REF_FILE" : bizErrorCode(biz.code), {
|
|
2903
2953
|
status: resp.status,
|
|
2904
2954
|
...muted && untilMs !== void 0 ? { providerRetryAfterMs: Math.max(0, untilMs - Date.now()) } : {},
|
|
2905
2955
|
...muted && untilMs !== void 0 ? { mutedUntilMs: untilMs } : {},
|
|
@@ -5160,10 +5210,19 @@ function modelInfoFor(provider, spec, requestedId) {
|
|
|
5160
5210
|
inputModalities: ["text", "image"]
|
|
5161
5211
|
};
|
|
5162
5212
|
}
|
|
5163
|
-
|
|
5213
|
+
/**
|
|
5214
|
+
* 组装模型信息。
|
|
5215
|
+
*
|
|
5216
|
+
* `contextWindowOverride` 来自设置页的「上下文范围」滑块(经 gate.json → index.ts → AdapterConfig)。
|
|
5217
|
+
* 传 undefined 就沿用 MODEL_SPECS 里的标称值(1Mi)—— 也就是不调这个开关时的原行为。
|
|
5218
|
+
*
|
|
5219
|
+
* ⚠️ 这个函数**每次请求都会被调用**(见下方 resolveModel / stream),所以滑块改完是即时生效的,
|
|
5220
|
+
* 不需要重启 DSH。
|
|
5221
|
+
*/
|
|
5222
|
+
function resolvedModelInfo(provider, spec, requestedId, contextWindowOverride) {
|
|
5164
5223
|
return {
|
|
5165
5224
|
...modelInfoFor(provider, spec, requestedId),
|
|
5166
|
-
context: { contextWindow: spec.contextWindow },
|
|
5225
|
+
context: { contextWindow: contextWindowOverride ?? spec.contextWindow },
|
|
5167
5226
|
defaultMaxTokens: spec.maxOutputTokens,
|
|
5168
5227
|
reasoning: spec.configurableThinking ? {
|
|
5169
5228
|
efforts: REASONING_EFFORTS,
|
|
@@ -5334,7 +5393,7 @@ function createAdapter(deps) {
|
|
|
5334
5393
|
return Promise.resolve(MODEL_SPECS.map((spec) => modelInfoFor(provider, spec)));
|
|
5335
5394
|
},
|
|
5336
5395
|
resolveModel(provider, model) {
|
|
5337
|
-
return Promise.resolve(resolvedModelInfo(provider, resolveSpec(model), String(model ?? "")));
|
|
5396
|
+
return Promise.resolve(resolvedModelInfo(provider, resolveSpec(model), String(model ?? ""), deps.config.contextWindow));
|
|
5338
5397
|
},
|
|
5339
5398
|
/**
|
|
5340
5399
|
* 运行时契约(dsh-llm 0.1.2-rc.1):dispatch 前先取「精确模型元数据 + 该次调用的 stream」。
|
|
@@ -5343,7 +5402,7 @@ function createAdapter(deps) {
|
|
|
5343
5402
|
prepareCall(provider, model, _signal) {
|
|
5344
5403
|
const spec = resolveSpec(model);
|
|
5345
5404
|
return Promise.resolve({
|
|
5346
|
-
model: resolvedModelInfo(provider, spec, String(model ?? "")),
|
|
5405
|
+
model: resolvedModelInfo(provider, spec, String(model ?? ""), deps.config.contextWindow),
|
|
5347
5406
|
stream: (options) => gatedStream(options)
|
|
5348
5407
|
});
|
|
5349
5408
|
},
|
|
@@ -5792,7 +5851,7 @@ function createAdapter(deps) {
|
|
|
5792
5851
|
if (isContextTooLong(event.message)) throw new AdapterLlmError(`DeepSeek 网页端上下文超限:${event.message}`, "CONTEXT_WINDOW_EXCEEDED");
|
|
5793
5852
|
if (event.code === "RATE_LIMIT") {
|
|
5794
5853
|
const throttled = event.rateLimitKind === "throttled";
|
|
5795
|
-
throw new AdapterLlmError(throttled ?
|
|
5854
|
+
throw new AdapterLlmError(throttled ? "网页版限流:发得太频繁,稍后自动重试" : "网页版限流:同一账号同时只能生成一条消息,稍后自动重试", "RATE_LIMIT", {
|
|
5796
5855
|
...event.retryAfterMs !== void 0 ? { providerRetryAfterMs: event.retryAfterMs } : {},
|
|
5797
5856
|
...throttled ? { rateLimitKind: "throttled" } : {}
|
|
5798
5857
|
});
|
|
@@ -8070,7 +8129,7 @@ async function checkForUpdate(current, fetchImpl) {
|
|
|
8070
8129
|
* 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
|
|
8071
8130
|
*/
|
|
8072
8131
|
/** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
|
|
8073
|
-
const FALLBACK_VERSION = "0.
|
|
8132
|
+
const FALLBACK_VERSION = "0.3.1";
|
|
8074
8133
|
let cached;
|
|
8075
8134
|
/** 本插件版本(如 `0.1.26`)。 */
|
|
8076
8135
|
function pluginVersion() {
|
|
@@ -8601,6 +8660,7 @@ function apply(ctx, config = {}) {
|
|
|
8601
8660
|
longRunThreshold: savedGate?.longRunThreshold ?? 15,
|
|
8602
8661
|
maxPromptChars: savedGate?.maxPromptChars ?? config.maxPromptChars ?? 4e5,
|
|
8603
8662
|
maxRefImages: savedGate?.maxRefImages ?? config.maxRefImages ?? 24,
|
|
8663
|
+
contextWindow: savedGate?.contextWindow ?? 1048576,
|
|
8604
8664
|
longRunBreakMs: savedGate?.longRunBreakMs,
|
|
8605
8665
|
sessionCleanup: cleanupMode,
|
|
8606
8666
|
cleanupBatch: cleanupBatchRange,
|
|
@@ -8621,6 +8681,7 @@ function apply(ctx, config = {}) {
|
|
|
8621
8681
|
const adapterConfig = {
|
|
8622
8682
|
maxPromptChars: savedGate?.maxPromptChars ?? config.maxPromptChars ?? 4e5,
|
|
8623
8683
|
maxRefImages: savedGate?.maxRefImages ?? config.maxRefImages ?? 24,
|
|
8684
|
+
contextWindow: savedGate?.contextWindow ?? 1048576,
|
|
8624
8685
|
idleTimeoutMs: config.idleTimeoutMs ?? 12e4,
|
|
8625
8686
|
deleteWebSessions: config.deleteWebSessions !== false,
|
|
8626
8687
|
sessionReuseTurns: config.sessionReuseTurns ?? 20,
|
|
@@ -8790,6 +8851,9 @@ function apply(ctx, config = {}) {
|
|
|
8790
8851
|
maxPromptCharsDefault: DEFAULT_MAX_PROMPT_CHARS,
|
|
8791
8852
|
maxRefImagesBounds: MAX_REF_IMAGES_BOUNDS,
|
|
8792
8853
|
maxRefImagesDefault: 24,
|
|
8854
|
+
contextWindowBounds: CONTEXT_WINDOW_BOUNDS,
|
|
8855
|
+
contextWindowDefault: DEFAULT_CONTEXT_WINDOW,
|
|
8856
|
+
contextWindowOptions: CONTEXT_WINDOW_OPTIONS,
|
|
8793
8857
|
cleanup: sessionCleaner.policy(),
|
|
8794
8858
|
cleanupBounds: {
|
|
8795
8859
|
batch: CLEANUP_BATCH_BOUNDS,
|
|
@@ -8881,6 +8945,17 @@ function apply(ctx, config = {}) {
|
|
|
8881
8945
|
}
|
|
8882
8946
|
patch.maxRefImages = clampMaxRefImages(count);
|
|
8883
8947
|
}
|
|
8948
|
+
if (body.contextWindow !== void 0) {
|
|
8949
|
+
const window = Number(body.contextWindow);
|
|
8950
|
+
if (!Number.isFinite(window)) {
|
|
8951
|
+
sendJson(res, 400, {
|
|
8952
|
+
ok: false,
|
|
8953
|
+
error: "contextWindow 必须是数字"
|
|
8954
|
+
});
|
|
8955
|
+
return;
|
|
8956
|
+
}
|
|
8957
|
+
patch.contextWindow = clampContextWindow(window);
|
|
8958
|
+
}
|
|
8884
8959
|
if (Object.keys(patch).length === 0) {
|
|
8885
8960
|
sendJson(res, 400, {
|
|
8886
8961
|
ok: false,
|
|
@@ -8891,6 +8966,7 @@ function apply(ctx, config = {}) {
|
|
|
8891
8966
|
const applied = gate.configure(patch);
|
|
8892
8967
|
if (applied.maxPromptChars !== void 0) adapterConfig.maxPromptChars = applied.maxPromptChars;
|
|
8893
8968
|
if (applied.maxRefImages !== void 0) adapterConfig.maxRefImages = applied.maxRefImages;
|
|
8969
|
+
if (applied.contextWindow !== void 0) adapterConfig.contextWindow = applied.contextWindow;
|
|
8894
8970
|
if (patch.sessionCleanup) sessionCleaner.configure({ mode: patch.sessionCleanup });
|
|
8895
8971
|
const rangePatch = {};
|
|
8896
8972
|
if (patch.cleanupBatch) rangePatch.batchRange = patch.cleanupBatch;
|