dsh-deepseek-web-login 0.2.2 → 0.3.0

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/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;
@@ -5160,10 +5204,19 @@ function modelInfoFor(provider, spec, requestedId) {
5160
5204
  inputModalities: ["text", "image"]
5161
5205
  };
5162
5206
  }
5163
- function resolvedModelInfo(provider, spec, requestedId) {
5207
+ /**
5208
+ * 组装模型信息。
5209
+ *
5210
+ * `contextWindowOverride` 来自设置页的「上下文范围」滑块(经 gate.json → index.ts → AdapterConfig)。
5211
+ * 传 undefined 就沿用 MODEL_SPECS 里的标称值(1Mi)—— 也就是不调这个开关时的原行为。
5212
+ *
5213
+ * ⚠️ 这个函数**每次请求都会被调用**(见下方 resolveModel / stream),所以滑块改完是即时生效的,
5214
+ * 不需要重启 DSH。
5215
+ */
5216
+ function resolvedModelInfo(provider, spec, requestedId, contextWindowOverride) {
5164
5217
  return {
5165
5218
  ...modelInfoFor(provider, spec, requestedId),
5166
- context: { contextWindow: spec.contextWindow },
5219
+ context: { contextWindow: contextWindowOverride ?? spec.contextWindow },
5167
5220
  defaultMaxTokens: spec.maxOutputTokens,
5168
5221
  reasoning: spec.configurableThinking ? {
5169
5222
  efforts: REASONING_EFFORTS,
@@ -5334,7 +5387,7 @@ function createAdapter(deps) {
5334
5387
  return Promise.resolve(MODEL_SPECS.map((spec) => modelInfoFor(provider, spec)));
5335
5388
  },
5336
5389
  resolveModel(provider, model) {
5337
- return Promise.resolve(resolvedModelInfo(provider, resolveSpec(model), String(model ?? "")));
5390
+ return Promise.resolve(resolvedModelInfo(provider, resolveSpec(model), String(model ?? ""), deps.config.contextWindow));
5338
5391
  },
5339
5392
  /**
5340
5393
  * 运行时契约(dsh-llm 0.1.2-rc.1):dispatch 前先取「精确模型元数据 + 该次调用的 stream」。
@@ -5343,7 +5396,7 @@ function createAdapter(deps) {
5343
5396
  prepareCall(provider, model, _signal) {
5344
5397
  const spec = resolveSpec(model);
5345
5398
  return Promise.resolve({
5346
- model: resolvedModelInfo(provider, spec, String(model ?? "")),
5399
+ model: resolvedModelInfo(provider, spec, String(model ?? ""), deps.config.contextWindow),
5347
5400
  stream: (options) => gatedStream(options)
5348
5401
  });
5349
5402
  },
@@ -8070,7 +8123,7 @@ async function checkForUpdate(current, fetchImpl) {
8070
8123
  * 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
8071
8124
  */
8072
8125
  /** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
8073
- const FALLBACK_VERSION = "0.2.2";
8126
+ const FALLBACK_VERSION = "0.3.0";
8074
8127
  let cached;
8075
8128
  /** 本插件版本(如 `0.1.26`)。 */
8076
8129
  function pluginVersion() {
@@ -8601,6 +8654,7 @@ function apply(ctx, config = {}) {
8601
8654
  longRunThreshold: savedGate?.longRunThreshold ?? 15,
8602
8655
  maxPromptChars: savedGate?.maxPromptChars ?? config.maxPromptChars ?? 4e5,
8603
8656
  maxRefImages: savedGate?.maxRefImages ?? config.maxRefImages ?? 24,
8657
+ contextWindow: savedGate?.contextWindow ?? 1048576,
8604
8658
  longRunBreakMs: savedGate?.longRunBreakMs,
8605
8659
  sessionCleanup: cleanupMode,
8606
8660
  cleanupBatch: cleanupBatchRange,
@@ -8621,6 +8675,7 @@ function apply(ctx, config = {}) {
8621
8675
  const adapterConfig = {
8622
8676
  maxPromptChars: savedGate?.maxPromptChars ?? config.maxPromptChars ?? 4e5,
8623
8677
  maxRefImages: savedGate?.maxRefImages ?? config.maxRefImages ?? 24,
8678
+ contextWindow: savedGate?.contextWindow ?? 1048576,
8624
8679
  idleTimeoutMs: config.idleTimeoutMs ?? 12e4,
8625
8680
  deleteWebSessions: config.deleteWebSessions !== false,
8626
8681
  sessionReuseTurns: config.sessionReuseTurns ?? 20,
@@ -8790,6 +8845,9 @@ function apply(ctx, config = {}) {
8790
8845
  maxPromptCharsDefault: DEFAULT_MAX_PROMPT_CHARS,
8791
8846
  maxRefImagesBounds: MAX_REF_IMAGES_BOUNDS,
8792
8847
  maxRefImagesDefault: 24,
8848
+ contextWindowBounds: CONTEXT_WINDOW_BOUNDS,
8849
+ contextWindowDefault: DEFAULT_CONTEXT_WINDOW,
8850
+ contextWindowOptions: CONTEXT_WINDOW_OPTIONS,
8793
8851
  cleanup: sessionCleaner.policy(),
8794
8852
  cleanupBounds: {
8795
8853
  batch: CLEANUP_BATCH_BOUNDS,
@@ -8881,6 +8939,17 @@ function apply(ctx, config = {}) {
8881
8939
  }
8882
8940
  patch.maxRefImages = clampMaxRefImages(count);
8883
8941
  }
8942
+ if (body.contextWindow !== void 0) {
8943
+ const window = Number(body.contextWindow);
8944
+ if (!Number.isFinite(window)) {
8945
+ sendJson(res, 400, {
8946
+ ok: false,
8947
+ error: "contextWindow 必须是数字"
8948
+ });
8949
+ return;
8950
+ }
8951
+ patch.contextWindow = clampContextWindow(window);
8952
+ }
8884
8953
  if (Object.keys(patch).length === 0) {
8885
8954
  sendJson(res, 400, {
8886
8955
  ok: false,
@@ -8891,6 +8960,7 @@ function apply(ctx, config = {}) {
8891
8960
  const applied = gate.configure(patch);
8892
8961
  if (applied.maxPromptChars !== void 0) adapterConfig.maxPromptChars = applied.maxPromptChars;
8893
8962
  if (applied.maxRefImages !== void 0) adapterConfig.maxRefImages = applied.maxRefImages;
8963
+ if (applied.contextWindow !== void 0) adapterConfig.contextWindow = applied.contextWindow;
8894
8964
  if (patch.sessionCleanup) sessionCleaner.configure({ mode: patch.sessionCleanup });
8895
8965
  const rangePatch = {};
8896
8966
  if (patch.cleanupBatch) rangePatch.batchRange = patch.cleanupBatch;