dsh-gpt-adapter 0.2.3 → 0.2.4

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
@@ -196,6 +196,84 @@ function createRequestBridge(provider) {
196
196
  };
197
197
  }
198
198
 
199
+ // src/context-window.ts
200
+ var officialModels = [
201
+ ["gpt-6-astra", 105e4],
202
+ ["gpt-6.1-sol", 105e4],
203
+ ["gpt-6.1-terra", 105e4],
204
+ ["gpt-6-luna", 105e4],
205
+ ["gpt-5.4", 105e4],
206
+ ["gpt-4.1", 1047576],
207
+ ["gpt-4o", 128e3],
208
+ ["gpt-5.4-2026-03-05", 105e4, "gpt-5.4"],
209
+ ["gpt-4.1-2025-04-14", 1047576, "gpt-4.1"],
210
+ ["gpt-4o-2024-08-06", 128e3, "gpt-4o"]
211
+ ];
212
+ var OFFICIAL_CONTEXT_WINDOWS = Object.fromEntries(
213
+ officialModels.map(([id, tokens, page]) => [
214
+ id,
215
+ {
216
+ tokens,
217
+ url: `https://developers.openai.com/api/docs/models/${page ?? id}`,
218
+ verifiedAt: "2026-11-13"
219
+ }
220
+ ])
221
+ );
222
+ function validContextWindow(value) {
223
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
224
+ }
225
+ function contextWindowMode(tokens) {
226
+ return tokens === 516e3 ? "516k" : tokens === 1e6 ? "1m" : "custom";
227
+ }
228
+ function contextWindowTokens(mode, customTokens) {
229
+ if (!["default", "516k", "1m", "custom"].includes(mode))
230
+ throw new Error("\u8BF7\u9009\u62E9\u6709\u6548\u7684\u4E0A\u4E0B\u6587\u9884\u8BBE");
231
+ if (mode !== "custom" && customTokens !== void 0)
232
+ throw new Error("\u53EA\u6709\u81EA\u5B9A\u4E49\u9884\u8BBE\u63A5\u53D7\u7A97\u53E3\u6570\u503C");
233
+ if (mode === "custom") {
234
+ if (!validContextWindow(customTokens))
235
+ throw new Error("\u4E0A\u4E0B\u6587\u7A97\u53E3\u5FC5\u987B\u662F\u6B63\u7684\u5B89\u5168\u6574\u6570");
236
+ return customTokens;
237
+ }
238
+ return mode === "516k" ? 516e3 : mode === "1m" ? 1e6 : void 0;
239
+ }
240
+ function resolveContextWindow(options) {
241
+ const fallback = [
242
+ [options.providerDefault, "provider-default"],
243
+ [options.upstream, "upstream"],
244
+ [options.catalog, "capability-catalog"],
245
+ [
246
+ Object.hasOwn(OFFICIAL_CONTEXT_WINDOWS, options.modelId) ? OFFICIAL_CONTEXT_WINDOWS[options.modelId]?.tokens : void 0,
247
+ "official-preset"
248
+ ],
249
+ [options.nativeDefault, "native-default"],
250
+ [1e6, "native-default"]
251
+ ];
252
+ const [defaultTokens, source] = fallback.find(
253
+ ([tokens]) => validContextWindow(tokens)
254
+ );
255
+ for (const [tokens, manualSource] of [
256
+ [options.legacy, "legacy-override"],
257
+ [options.native, "native-user"]
258
+ ]) {
259
+ if (tokens === void 0) continue;
260
+ if (!validContextWindow(tokens))
261
+ throw new Error("\u4E0A\u4E0B\u6587\u7A97\u53E3\u5FC5\u987B\u662F\u6B63\u7684\u5B89\u5168\u6574\u6570");
262
+ return {
263
+ mode: contextWindowMode(tokens),
264
+ effectiveTokens: tokens,
265
+ defaultTokens,
266
+ source: manualSource
267
+ };
268
+ }
269
+ return {
270
+ mode: "default",
271
+ effectiveTokens: defaultTokens,
272
+ defaultTokens,
273
+ source
274
+ };
275
+ }
276
+
199
277
  // src/sync.ts
200
278
  import { createHash } from "node:crypto";
201
279
  var MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
@@ -373,7 +451,7 @@ function parseCapabilityHints(row, modelId) {
373
451
  ...typeof defaultReasoningValue === "string" ? { defaultReasoningId: defaultReasoningValue } : {},
374
452
  ...hasTierSignal ? { serviceTiers: tiers } : {},
375
453
  ...defaultTier ? { defaultServiceTier: defaultTier } : {},
376
- ...typeof contextWindow === "number" && contextWindow > 0 ? { contextWindow } : {},
454
+ ...validContextWindow(contextWindow) ? { contextWindow } : {},
377
455
  ...typeof maxOutputTokens === "number" && maxOutputTokens > 0 ? { maxOutputTokens } : {}
378
456
  };
379
457
  return Object.keys(hints).length === 0 ? void 0 : hints;
@@ -735,6 +813,7 @@ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
735
813
  import { createRequire } from "node:module";
736
814
  import { readFileSync } from "node:fs";
737
815
  import { createHash as createHash6 } from "node:crypto";
816
+ import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
738
817
 
739
818
  // src/native-adapter.ts
740
819
  function installNativeAdapterBridge(prototype, capture) {
@@ -759,6 +838,12 @@ function installNativeAdapterBridge(prototype, capture) {
759
838
  });
760
839
  const models = new Proxy(snapshot.models, {
761
840
  get(target, key) {
841
+ if (key === "getModel" && typeof target.getModel === "function")
842
+ return (provider, modelId) => {
843
+ const model = target.getModel(provider, modelId);
844
+ const capacity = extension.model.context;
845
+ return model && provider === options.provider && modelId === options.model && capacity?.contextWindow ? { ...model, contextWindow: capacity.contextWindow } : model;
846
+ };
762
847
  if (key === "streamSimple")
763
848
  return (model, context, settings) => {
764
849
  const call = extension.prepare(options);
@@ -853,11 +938,12 @@ function installNativeAdapterBridge(prototype, capture) {
853
938
  // src/settings-overlay.ts
854
939
  import z from "@deepseek-ai/schemastery";
855
940
  import { symbols } from "@deepseek-ai/cordis";
856
- function projectModel(provider, model, resolve4, profile) {
941
+ function projectModel(provider, model, resolve4, profile, includeContext = true) {
857
942
  const capability = resolve4(provider, model, profile);
858
943
  if (!capability) return model;
859
944
  return {
860
945
  ...model,
946
+ ...includeContext && validContextWindow(capability.contextWindow) ? { contextWindow: capability.contextWindow } : {},
861
947
  reasoningEfforts: capability.reasoning.status === "unsupported" ? false : Object.fromEntries(
862
948
  capability.reasoning.choices.map((choice) => [
863
949
  choice.id,
@@ -903,7 +989,7 @@ function projectSchema(json, preserveNativeLevels) {
903
989
  keys.list = offered.map((level) => z.const(level));
904
990
  return schema.toJSON();
905
991
  }
906
- function nativeWrite(next, current, resolve4) {
992
+ function nativeWrite(next, current, resolve4, contextEdits) {
907
993
  if (!next.providers) return next;
908
994
  return {
909
995
  ...next,
@@ -932,6 +1018,21 @@ function nativeWrite(next, current, resolve4) {
932
1018
  "GPT \u601D\u8003\u7B49\u7EA7\u7531\u63D2\u4EF6\u9884\u8BBE\u7BA1\u7406\uFF1B\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5\uFF0C\u6216\u5728\u6A21\u578B\u80FD\u529B\u8986\u76D6\u4E2D\u4FEE\u6539\uFF0C\u505C\u7528\u81EA\u52A8\u9002\u914D\u53EF\u6062\u590D\u539F\u751F\u7F16\u8F91\u3002"
933
1019
  );
934
1020
  const restored = { ...model };
1021
+ const projectedContext = original ? projectModel(provider, original, resolve4, ownershipProfile).contextWindow : void 0;
1022
+ const editedContext = original ? model.contextWindow !== projectedContext : Object.hasOwn(model, "contextWindow");
1023
+ if (!editedContext) {
1024
+ if (original && Object.hasOwn(original, "contextWindow"))
1025
+ restored.contextWindow = original.contextWindow;
1026
+ else delete restored.contextWindow;
1027
+ } else {
1028
+ if (model.contextWindow !== void 0 && !validContextWindow(model.contextWindow))
1029
+ throw new Error("\u4E0A\u4E0B\u6587\u7A97\u53E3\u5FC5\u987B\u662F\u6B63\u7684\u5B89\u5168\u6574\u6570");
1030
+ contextEdits.push({
1031
+ provider,
1032
+ modelId: model.id,
1033
+ previousTokens: original?.contextWindow
1034
+ });
1035
+ }
935
1036
  if (original && Object.hasOwn(original, "reasoningEfforts"))
936
1037
  restored.reasoningEfforts = structuredClone(
937
1038
  original.reasoningEfforts
@@ -945,10 +1046,11 @@ function nativeWrite(next, current, resolve4) {
945
1046
  )
946
1047
  };
947
1048
  }
948
- function installSettingsOverlay(settings, resolve4) {
1049
+ function installSettingsOverlay(settings, resolve4, contextWrites) {
949
1050
  settings = settings[symbols.original] ?? settings;
950
1051
  const originalDescribe = settings.describe;
951
1052
  const originalWrite = settings.write;
1053
+ let viewRevision;
952
1054
  function describe(options) {
953
1055
  return originalDescribe.call(this, options).map((row) => {
954
1056
  if (row.ns !== "llm-pi-ai") return row;
@@ -960,18 +1062,46 @@ function installSettingsOverlay(settings, resolve4) {
960
1062
  for (const model of profile.models ?? [])
961
1063
  if (resolve4(provider, model, profile)) managed = true;
962
1064
  else other = true;
963
- if (!managed) return row;
1065
+ const value = managed ? projectConfiguration(row.value, resolve4) : row.value;
1066
+ const contexts = JSON.stringify(
1067
+ Object.entries(value?.providers ?? {}).map(([provider, profile]) => [
1068
+ provider,
1069
+ profile.models?.map((model) => [
1070
+ model.id,
1071
+ model.contextWindow
1072
+ ])
1073
+ ])
1074
+ );
1075
+ if (typeof row.revision === "number") {
1076
+ if (!viewRevision)
1077
+ viewRevision = {
1078
+ native: row.revision,
1079
+ contexts,
1080
+ revision: row.revision
1081
+ };
1082
+ else if (viewRevision.native !== row.revision || viewRevision.contexts !== contexts)
1083
+ viewRevision = {
1084
+ native: row.revision,
1085
+ contexts,
1086
+ revision: Math.max(row.revision, viewRevision.revision + 1)
1087
+ };
1088
+ }
964
1089
  return {
965
1090
  ...row,
966
- schema: projectSchema(row.schema, other),
967
- value: projectConfiguration(row.value, resolve4),
968
- base: projectConfiguration(row.base, resolve4),
969
- user: projectConfiguration(row.user, resolve4)
1091
+ ...managed ? {
1092
+ schema: projectSchema(row.schema, other),
1093
+ value,
1094
+ base: projectConfiguration(row.base, resolve4),
1095
+ user: projectConfiguration(row.user, resolve4)
1096
+ } : {},
1097
+ ...typeof row.revision === "number" ? { revision: viewRevision.revision } : {}
970
1098
  };
971
1099
  });
972
1100
  }
973
1101
  async function write(ns, change, expected, paths) {
974
- return originalWrite.call(
1102
+ const edits = [];
1103
+ const legacyRevision = contextWrites?.revision();
1104
+ await originalWrite.call(
975
1105
  this,
976
1106
  ns,
977
1107
  ns !== "llm-pi-ai" ? change : (current, base, schema) => nativeWrite(
@@ -981,19 +1111,88 @@ function installSettingsOverlay(settings, resolve4) {
981
1111
  schema
982
1112
  ),
983
1113
  current,
984
- resolve4
1114
+ resolve4,
1115
+ edits
985
1116
  ),
986
1117
  expected,
987
1118
  paths
988
1119
  );
1120
+ await finishContextWrite.call(this, ns, edits, legacyRevision, paths);
1121
+ }
1122
+ async function finishContextWrite(ns, edits, legacyRevision, paths) {
1123
+ if (ns !== "llm-pi-ai" || !edits.length || !contextWrites) return;
1124
+ const committedRevision = describe.call(this).find((row) => row.ns === ns)?.revision;
1125
+ try {
1126
+ await contextWrites.saved(edits, legacyRevision);
1127
+ } catch (error) {
1128
+ try {
1129
+ await originalWrite.call(
1130
+ this,
1131
+ ns,
1132
+ (current) => {
1133
+ const restored = structuredClone(current);
1134
+ for (const edit of edits) {
1135
+ const row = restored.providers?.[edit.provider]?.models?.find(
1136
+ (model) => model.id === edit.modelId
1137
+ );
1138
+ if (!row) continue;
1139
+ if (edit.previousTokens === void 0) delete row.contextWindow;
1140
+ else row.contextWindow = edit.previousTokens;
1141
+ }
1142
+ return restored;
1143
+ },
1144
+ committedRevision,
1145
+ paths
1146
+ );
1147
+ } catch (rollbackError) {
1148
+ throw new AggregateError(
1149
+ [error, rollbackError],
1150
+ "\u4E0A\u4E0B\u6587\u4FDD\u5B58\u672A\u5B8C\u6210\uFF0C\u6062\u590D\u5931\u8D25\uFF1B\u8BF7\u5237\u65B0\u67E5\u770B\u5F53\u524D\u8BBE\u7F6E"
1151
+ );
1152
+ }
1153
+ throw error;
1154
+ }
989
1155
  }
990
1156
  settings.describe = describe;
991
1157
  if (originalWrite) settings.write = write;
992
- return () => {
1158
+ const dispose = () => {
993
1159
  if (settings.describe === describe) settings.describe = originalDescribe;
994
1160
  if (originalWrite && settings.write === write)
995
1161
  settings.write = originalWrite;
996
1162
  };
1163
+ const setContextWindow = async (provider, modelId, tokens, expected) => {
1164
+ if (!originalWrite) throw new Error("\u4E0A\u4E0B\u6587\u8BBE\u7F6E\u6682\u4E0D\u53EF\u7528");
1165
+ if (tokens !== void 0 && !validContextWindow(tokens))
1166
+ throw new Error("\u4E0A\u4E0B\u6587\u7A97\u53E3\u5FC5\u987B\u662F\u6B63\u7684\u5B89\u5168\u6574\u6570");
1167
+ const edits = [];
1168
+ const legacyRevision = contextWrites?.revision();
1169
+ const paths = [["providers", provider, "models"]];
1170
+ await originalWrite.call(
1171
+ settings,
1172
+ "llm-pi-ai",
1173
+ (current) => {
1174
+ const next = structuredClone(current);
1175
+ const model = next.providers?.[provider]?.models?.find(
1176
+ (row) => row.id === modelId
1177
+ );
1178
+ if (!model) throw new Error("\u6A21\u578B\u5DF2\u53D8\u66F4\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5");
1179
+ edits.push({ provider, modelId, previousTokens: model.contextWindow });
1180
+ if (tokens === void 0) delete model.contextWindow;
1181
+ else model.contextWindow = tokens;
1182
+ return next;
1183
+ },
1184
+ expected,
1185
+ paths
1186
+ );
1187
+ await finishContextWrite.call(
1188
+ settings,
1189
+ "llm-pi-ai",
1190
+ edits,
1191
+ legacyRevision,
1192
+ paths
1193
+ );
1194
+ };
1195
+ return Object.assign(dispose, { setContextWindow });
997
1196
  }
998
1197
 
999
1198
  // src/codex.ts
@@ -2299,14 +2498,15 @@ var Config2 = z2.object({
2299
2498
  mcpAllowlist: z2.array(String).default([])
2300
2499
  }).default({})
2301
2500
  });
2302
- var _speed_dec, _setSpeed_dec, _removeStale_dec, _refresh_dec, _updateOptions_dec, _configure_dec, _status_dec, _refreshCodex_dec, _codexStatus_dec, _a, _init;
2303
- var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refreshCodex_dec = [Remote], _status_dec = [Remote], _configure_dec = [Remote], _updateOptions_dec = [Remote], _refresh_dec = [Remote], _removeStale_dec = [Remote], _setSpeed_dec = [Remote({ resources: { session: (sessionId) => sessionId } })], _speed_dec = [Remote({ resources: { session: (sessionId) => sessionId } })], _a) {
2501
+ var _speed_dec, _setSpeed_dec, _setContextWindow_dec, _removeStale_dec, _refresh_dec, _updateOptions_dec, _configure_dec, _status_dec, _refreshCodex_dec, _codexStatus_dec, _a, _init;
2502
+ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refreshCodex_dec = [Remote], _status_dec = [Remote], _configure_dec = [Remote], _updateOptions_dec = [Remote], _refresh_dec = [Remote], _removeStale_dec = [Remote], _setContextWindow_dec = [Remote], _setSpeed_dec = [Remote({ resources: { session: (sessionId) => sessionId } })], _speed_dec = [Remote({ resources: { session: (sessionId) => sessionId } })], _a) {
2304
2503
  constructor(ctx, options, settingsNs) {
2305
2504
  super(ctx, "gptModels");
2306
2505
  this.options = options;
2307
2506
  this.settingsNs = settingsNs;
2308
2507
  __runInitializers(_init, 5, this);
2309
2508
  __publicField(this, "codex");
2509
+ __publicField(this, "contextWriter");
2310
2510
  __publicField(this, "typertRemote");
2311
2511
  __publicField(this, "runners", /* @__PURE__ */ new Map());
2312
2512
  __publicField(this, "snapshots", /* @__PURE__ */ new Map());
@@ -2434,11 +2634,11 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2434
2634
  }
2435
2635
  async publish(provider, snapshot) {
2436
2636
  if (this.closed) return;
2437
- const { descriptor, profile } = this.profile(provider);
2637
+ const { profile } = this.profile(provider);
2438
2638
  const existing = profile.models ?? [];
2439
2639
  const ids = new Set(existing.map((model) => model.id));
2440
2640
  const added = snapshot.models.filter((model) => !model.stale && !ids.has(model.id)).map((model) => ({ id: model.id, name: model.displayName ?? model.id }));
2441
- if (added.length) {
2641
+ if (added.length && !Object.keys(profile.modelOverrides ?? {}).length) {
2442
2642
  await this.ctx.settings.mutate(
2443
2643
  "llm-pi-ai",
2444
2644
  [
@@ -2449,12 +2649,14 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2449
2649
  (model) => projectModel(
2450
2650
  provider,
2451
2651
  model,
2452
- (route, row) => this.capability(route, row.id, row.name)
2652
+ (route, row) => this.capability(route, row.id, row.name),
2653
+ void 0,
2654
+ ids.has(model.id)
2453
2655
  )
2454
2656
  )
2455
2657
  }
2456
2658
  ],
2457
- descriptor.revision
2659
+ this.modelsRevision()
2458
2660
  );
2459
2661
  }
2460
2662
  if (this.closed) return;
@@ -2490,6 +2692,15 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2490
2692
  stale: model.stale ?? false
2491
2693
  })) ?? [],
2492
2694
  capabilities,
2695
+ contextWindows: Object.fromEntries(
2696
+ this.routeOptions(provider)?.enabled ? (this.profile(provider).profile.models ?? []).filter(
2697
+ (model) => this.capability(provider, model.id) !== void 0
2698
+ ).map((model) => [
2699
+ model.id,
2700
+ this.contextWindow(provider, model.id)
2701
+ ]) : []
2702
+ ),
2703
+ contextRevision: this.modelsRevision(),
2493
2704
  options: this.routeOptions(provider) ?? {
2494
2705
  enabled: false,
2495
2706
  refreshIntervalMs: 18e5,
@@ -2530,6 +2741,10 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2530
2741
  const overrides = JSON.parse(overridesJSON);
2531
2742
  if (!overrides || typeof overrides !== "object" || Array.isArray(overrides))
2532
2743
  throw new Error("\u6A21\u578B\u8986\u76D6\u9700\u8981\u662F JSON \u5BF9\u8C61");
2744
+ for (const entry of Object.values(overrides)) {
2745
+ if (entry && typeof entry === "object" && "contextWindow" in entry && !validContextWindow(entry.contextWindow))
2746
+ throw new Error("\u4E0A\u4E0B\u6587\u7A97\u53E3\u5FC5\u987B\u662F\u6B63\u7684\u5B89\u5168\u6574\u6570");
2747
+ }
2533
2748
  const descriptor = this.ctx.settings.describe().find((entry) => entry.ns === this.settingsNs);
2534
2749
  if (!descriptor) throw new Error("GPT plugin settings unavailable");
2535
2750
  await this.ctx.settings.mutate(
@@ -2559,7 +2774,7 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2559
2774
  const runner = this.runners.get(provider);
2560
2775
  if (!runner) throw new Error("Enable GPT adaptation first");
2561
2776
  const staleIds = runner.current()?.models.filter((model) => model.stale).map((model) => model.id) ?? [];
2562
- const { descriptor, profile } = this.profile(provider);
2777
+ const { profile } = this.profile(provider);
2563
2778
  const nextModels = profile.models?.filter(
2564
2779
  (model) => !staleIds.includes(model.id)
2565
2780
  );
@@ -2574,15 +2789,101 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2574
2789
  (model) => projectModel(
2575
2790
  provider,
2576
2791
  model,
2577
- (route, row) => this.capability(route, row.id, row.name)
2792
+ (route, row) => this.capability(route, row.id, row.name),
2793
+ void 0
2578
2794
  )
2579
2795
  )
2580
2796
  }
2581
2797
  ],
2582
- descriptor.revision
2798
+ this.modelsRevision()
2583
2799
  );
2584
2800
  await runner.removeStale(staleIds);
2585
2801
  }
2802
+ /** Resolve raw metadata separately from merged capabilities and cached overrides. */
2803
+ contextWindow(provider, modelId, nativeDefault) {
2804
+ const { descriptor, profile } = this.profile(provider);
2805
+ const overrides = JSON.parse(
2806
+ this.routeOptions(provider, profile)?.overridesJSON ?? "{}"
2807
+ );
2808
+ const row = profile.models?.length ? profile.models.find((model) => model.id === modelId) : profile.modelOverrides?.[modelId];
2809
+ const snapshot = this.snapshots.get(provider);
2810
+ const raw = snapshot?.models.find((model) => model.id === modelId);
2811
+ const userProfile = descriptor.user?.providers?.[provider];
2812
+ const providerDefault = userProfile?.defaultContextWindow ?? (profile.defaultContextWindow !== 1e6 || descriptor.base === void 0 && descriptor.user === void 0 ? profile.defaultContextWindow : void 0);
2813
+ const catalogModel = getBuiltinModels(provider).find(
2814
+ (model) => model.id === modelId
2815
+ );
2816
+ return resolveContextWindow({
2817
+ modelId,
2818
+ legacy: overrides[modelId]?.contextWindow,
2819
+ native: row?.contextWindow,
2820
+ providerDefault,
2821
+ upstream: raw?.capabilities?.contextWindow,
2822
+ catalog: snapshot?.catalog?.models[modelId]?.contextWindow,
2823
+ nativeDefault: nativeDefault ?? catalogModel?.contextWindow ?? profile.defaultContextWindow
2824
+ });
2825
+ }
2826
+ contextWriteRevision() {
2827
+ return this.nativeDescribe().find((row) => row.ns === this.settingsNs)?.revision;
2828
+ }
2829
+ /** Public form revisions also cover changes to projected model capacities. */
2830
+ modelsRevision() {
2831
+ const descriptor = this.ctx.settings.describe().find((row) => row.ns === "llm-pi-ai");
2832
+ if (!descriptor) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u6682\u4E0D\u53EF\u7528");
2833
+ return descriptor.revision;
2834
+ }
2835
+ /** Remove only legacy context fields after the matching native save commits. */
2836
+ async clearLegacyContext(edits, expected) {
2837
+ const operations = [];
2838
+ for (const provider of new Set(edits.map((edit) => edit.provider))) {
2839
+ const text = this.options.routes[provider]?.overridesJSON;
2840
+ if (!text) continue;
2841
+ const overrides = JSON.parse(text);
2842
+ let changed = false;
2843
+ for (const edit of edits.filter((entry) => entry.provider === provider)) {
2844
+ if (overrides[edit.modelId] && Object.hasOwn(overrides[edit.modelId], "contextWindow")) {
2845
+ delete overrides[edit.modelId].contextWindow;
2846
+ changed = true;
2847
+ }
2848
+ }
2849
+ if (changed)
2850
+ operations.push({
2851
+ op: "set",
2852
+ path: ["routes", provider, "overridesJSON"],
2853
+ value: JSON.stringify(overrides)
2854
+ });
2855
+ }
2856
+ if (operations.length)
2857
+ await this.ctx.settings.mutate(this.settingsNs, operations, expected);
2858
+ }
2859
+ async setContextWindow(provider, modelId, mode, customTokens, expectedRevision) {
2860
+ const tokens = contextWindowTokens(mode, customTokens);
2861
+ const { profile } = this.profile(provider);
2862
+ if (!this.capability(provider, modelId))
2863
+ throw new Error("\u8BF7\u5148\u542F\u7528\u8BE5 GPT \u6A21\u578B\u7684\u81EA\u52A8\u9002\u914D");
2864
+ const index = profile.models?.findIndex((model) => model.id === modelId) ?? -1;
2865
+ if (index < 0) throw new Error("\u6A21\u578B\u5DF2\u53D8\u66F4\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5");
2866
+ const effective = tokens ?? this.contextWindow(provider, modelId).defaultTokens;
2867
+ const row = profile.models[index];
2868
+ const compaction = this.nativeDescribe().find(
2869
+ (entry) => entry.ns === "compaction-basic"
2870
+ )?.value;
2871
+ const policy = compaction?.modelPolicies?.find(
2872
+ (entry) => entry.provider === provider && entry.model === modelId
2873
+ );
2874
+ const headroom = policy?.headroomTokens ?? compaction?.headroomTokens ?? 0;
2875
+ const output = validContextWindow(row.maxTokens) ? row.maxTokens : 0;
2876
+ if (effective <= output + headroom)
2877
+ throw new Error("\u4E0A\u4E0B\u6587\u7A97\u53E3\u5FC5\u987B\u5927\u4E8E\u5F53\u524D\u6700\u5927\u8F93\u51FA\u548C\u538B\u7F29\u9884\u7559\u7684\u603B\u548C");
2878
+ if (!this.contextWriter) throw new Error("\u4E0A\u4E0B\u6587\u8BBE\u7F6E\u6682\u4E0D\u53EF\u7528");
2879
+ await this.contextWriter(
2880
+ provider,
2881
+ modelId,
2882
+ tokens,
2883
+ expectedRevision ?? this.modelsRevision()
2884
+ );
2885
+ this.ctx.emit("llm/models-updated", provider);
2886
+ }
2586
2887
  async setSpeed(sessionId, provider, modelId, tier) {
2587
2888
  const capability = this.capability(provider, modelId);
2588
2889
  if (!capability?.serviceTiers.some((choice) => choice.id === tier))
@@ -2619,11 +2920,15 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2619
2920
  ...overrides[modelId]
2620
2921
  };
2621
2922
  const current = this.snapshots.get(provider)?.capabilities[modelId];
2622
- return current ? mergeCapabilities(current, override, current.capabilityRevision) : resolveCapabilities(
2923
+ const result = current ? mergeCapabilities(current, override, current.capabilityRevision) : resolveCapabilities(
2623
2924
  { id: modelId, ...displayName ? { displayName } : {} },
2624
2925
  "gpt-preset",
2625
2926
  override
2626
2927
  );
2928
+ return {
2929
+ ...result,
2930
+ contextWindow: this.contextWindow(provider, modelId).effectiveTokens
2931
+ };
2627
2932
  }
2628
2933
  /** Capture request controls for the version-checked native runtime extension. */
2629
2934
  capture(provider, modelId, base) {
@@ -2636,6 +2941,14 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2636
2941
  const protocol = profile.api ?? "openai-responses";
2637
2942
  const model = {
2638
2943
  ...base,
2944
+ context: {
2945
+ ...base.context ?? {},
2946
+ contextWindow: this.contextWindow(
2947
+ provider,
2948
+ modelId,
2949
+ base.context?.contextWindow
2950
+ ).effectiveTokens
2951
+ },
2639
2952
  name: capabilities.displayName === modelId ? base.name : capabilities.displayName
2640
2953
  };
2641
2954
  if (!preserveReasoning) delete model.reasoning;
@@ -2686,6 +2999,7 @@ __decorateElement(_init, 1, "configure", _configure_dec, GptModels);
2686
2999
  __decorateElement(_init, 1, "updateOptions", _updateOptions_dec, GptModels);
2687
3000
  __decorateElement(_init, 1, "refresh", _refresh_dec, GptModels);
2688
3001
  __decorateElement(_init, 1, "removeStale", _removeStale_dec, GptModels);
3002
+ __decorateElement(_init, 1, "setContextWindow", _setContextWindow_dec, GptModels);
2689
3003
  __decorateElement(_init, 1, "setSpeed", _setSpeed_dec, GptModels);
2690
3004
  __decorateElement(_init, 1, "speed", _speed_dec, GptModels);
2691
3005
  __decoratorMetadata(_init, GptModels);
@@ -2725,9 +3039,15 @@ async function apply2(ctx, config) {
2725
3039
  const providers = service.enabledProviders();
2726
3040
  const dispose = installSettingsOverlay(
2727
3041
  ctx.settings,
2728
- (provider, model, profile) => typeof model.id === "string" ? service.capability(provider, model.id, model.name, profile) : void 0
3042
+ (provider, model, profile) => typeof model.id === "string" ? service.capability(provider, model.id, model.name, profile) : void 0,
3043
+ {
3044
+ revision: () => service.contextWriteRevision(),
3045
+ saved: (edits, revision) => service.clearLegacyContext(edits, revision)
3046
+ }
2729
3047
  );
3048
+ service.contextWriter = dispose.setContextWindow;
2730
3049
  return () => {
3050
+ service.contextWriter = void 0;
2731
3051
  dispose();
2732
3052
  for (const provider of providers) ctx.emit("llm/models-updated", provider);
2733
3053
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gpt-adapter",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"