dsh-gpt-adapter 0.2.2 → 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
@@ -1082,7 +1281,7 @@ function serverName(name2) {
1082
1281
  const hash = createHash3("sha256").update(name2).digest("hex").slice(0, 8);
1083
1282
  return `${name2.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 23)}-${hash}`;
1084
1283
  }
1085
- function normalizeCodexMcp(name2, value, source, base, environment = process.env) {
1284
+ function normalizeCodexMcp(name2, value, source, base, environment = process.env, defaultCwd = base) {
1086
1285
  const raw = object(value);
1087
1286
  if (raw.enabled === false) return void 0;
1088
1287
  const headers = record2(raw.http_headers ?? raw.headers);
@@ -1131,8 +1330,8 @@ function normalizeCodexMcp(name2, value, source, base, environment = process.env
1131
1330
  if (environment[variable] !== void 0 && env[variable] === void 0)
1132
1331
  env[variable] = environment[variable];
1133
1332
  }
1134
- const cwd = typeof raw.cwd === "string" ? resolve(base, raw.cwd) : base;
1135
- const command = raw.command.includes("/") && !isAbsolute(raw.command) ? resolve(cwd, raw.command) : raw.command;
1333
+ const cwd = typeof raw.cwd === "string" ? resolve(base, raw.cwd) : defaultCwd;
1334
+ const command = raw.command.includes("/") && !isAbsolute(raw.command) ? resolve(typeof raw.cwd === "string" ? cwd : base, raw.command) : raw.command;
1136
1335
  return {
1137
1336
  ...common,
1138
1337
  transport: "stdio",
@@ -1151,9 +1350,29 @@ async function directories(path) {
1151
1350
  }
1152
1351
  }
1153
1352
  async function manifestAt(root) {
1353
+ const text = await optionalText(join2(root, "plugin.json"));
1354
+ if (text !== void 0) {
1355
+ const manifest = object(JSON.parse(text));
1356
+ if (manifest.$schema === "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json") {
1357
+ const inline = manifest.extensions?.["com.openai"];
1358
+ const overlay = inline !== null && typeof inline === "object" && !Array.isArray(inline) ? inline : object(
1359
+ JSON.parse(
1360
+ await optionalText(
1361
+ join2(root, ".codex-plugin", "plugin.json")
1362
+ ) ?? "{}"
1363
+ )
1364
+ );
1365
+ return {
1366
+ root,
1367
+ manifest: { ...manifest, ...overlay, name: manifest.name },
1368
+ portable: true
1369
+ };
1370
+ }
1371
+ }
1154
1372
  for (const name2 of [".codex-plugin", ".claude-plugin"]) {
1155
- const text = await optionalText(join2(root, name2, "plugin.json"));
1156
- if (text !== void 0) return { root, manifest: object(JSON.parse(text)) };
1373
+ const text2 = await optionalText(join2(root, name2, "plugin.json"));
1374
+ if (text2 !== void 0)
1375
+ return { root, manifest: object(JSON.parse(text2)), portable: false };
1157
1376
  }
1158
1377
  return void 0;
1159
1378
  }
@@ -1182,7 +1401,7 @@ async function resolvePlugin(home, id, config) {
1182
1401
  }
1183
1402
  function expandPlugin(value, root, home) {
1184
1403
  if (typeof value === "string")
1185
- return value.replaceAll("${CLAUDE_PLUGIN_ROOT}", root).replaceAll("${CODEX_PLUGIN_ROOT}", root).replaceAll("${CODEX_HOME}", home);
1404
+ return value.replaceAll("${PLUGIN_ROOT}", root).replaceAll("${CLAUDE_PLUGIN_ROOT}", root).replaceAll("${CODEX_PLUGIN_ROOT}", root).replaceAll("${CODEX_HOME}", home);
1186
1405
  if (Array.isArray(value)) return value.map((v) => expandPlugin(v, root, home));
1187
1406
  if (value && typeof value === "object")
1188
1407
  return Object.fromEntries(
@@ -1190,6 +1409,22 @@ function expandPlugin(value, root, home) {
1190
1409
  );
1191
1410
  return value;
1192
1411
  }
1412
+ function restrictMcpFallback(entry, policy) {
1413
+ if (policy.enabled === false) return void 0;
1414
+ const requested = Array.isArray(policy.enabled_tools) ? strings(policy.enabled_tools) : void 0;
1415
+ const enabledTools = requested && entry.enabledTools ? entry.enabledTools.filter((name2) => requested.includes(name2)) : requested ?? entry.enabledTools;
1416
+ return {
1417
+ ...entry,
1418
+ ...enabledTools ? { enabledTools } : {},
1419
+ disabledTools: [
1420
+ .../* @__PURE__ */ new Set([
1421
+ ...entry.disabledTools,
1422
+ ...strings(policy.disabled_tools),
1423
+ ...Object.entries(object(policy.tools)).filter(([, tool]) => object(tool).enabled === false).map(([name2]) => name2)
1424
+ ])
1425
+ ]
1426
+ };
1427
+ }
1193
1428
  async function discoverCodexSnapshot(home, options, context = {}) {
1194
1429
  const config = await readCodexConfig(
1195
1430
  home,
@@ -1207,24 +1442,38 @@ async function discoverCodexSnapshot(home, options, context = {}) {
1207
1442
  items: []
1208
1443
  };
1209
1444
  const names = /* @__PURE__ */ new Set();
1210
- const addServers = (servers, source, base, plugin = "") => {
1211
- for (const [name2, raw] of Object.entries(servers)) {
1445
+ const addServers = (servers, source, base, plugin = "", policies = {}) => {
1446
+ for (const [name2, declaration] of Object.entries(servers)) {
1447
+ const policy = Object.fromEntries(
1448
+ Object.entries(object(policies[name2])).filter(
1449
+ ([key]) => ["enabled", "enabled_tools", "disabled_tools", "tools"].includes(key)
1450
+ )
1451
+ );
1452
+ const raw = mergeConfig(object(declaration), policy);
1212
1453
  const publicName = plugin ? `${plugin}-${name2}` : name2;
1213
1454
  if (object(raw).enabled === false || options.mcpAllowlist.length && !options.mcpAllowlist.includes(name2)) {
1214
1455
  result.items.push({ name: publicName, kind: "mcp", state: "disabled" });
1215
1456
  continue;
1216
1457
  }
1217
1458
  try {
1218
- const value = normalizeCodexMcp(publicName, raw, source, base);
1459
+ const value = normalizeCodexMcp(
1460
+ publicName,
1461
+ raw,
1462
+ source,
1463
+ base,
1464
+ process.env,
1465
+ context.cwd ?? base
1466
+ );
1219
1467
  if (!value) continue;
1220
1468
  if (names.has(value.serverName)) throw new Error("namespace-conflict");
1221
1469
  names.add(value.serverName);
1222
- result.mcp.push(value);
1470
+ result.mcp.push({ ...value, originalName: name2 });
1223
1471
  } catch (error) {
1224
1472
  const previous = context.previous?.mcp.find(
1225
1473
  (entry) => entry.serverName === serverName(publicName)
1226
1474
  );
1227
- if (previous) result.mcp.push(previous);
1475
+ const retained = previous && restrictMcpFallback(previous, raw);
1476
+ if (retained) result.mcp.push(retained);
1228
1477
  const reason = [
1229
1478
  "missing-environment",
1230
1479
  "unsupported-transport",
@@ -1258,9 +1507,9 @@ async function discoverCodexSnapshot(home, options, context = {}) {
1258
1507
  });
1259
1508
  continue;
1260
1509
  }
1261
- const { root, manifest } = plugin;
1510
+ const { root, manifest, portable } = plugin;
1262
1511
  const name2 = String(manifest.name ?? id.split("@")[0]);
1263
- const declared = manifest.skills ?? "./skills";
1512
+ const declared = portable ? "./skills" : manifest.skills ?? "./skills";
1264
1513
  const skillRoots = [];
1265
1514
  for (const entry of Array.isArray(declared) ? declared : [declared]) {
1266
1515
  if (typeof entry === "string")
@@ -1271,7 +1520,10 @@ async function discoverCodexSnapshot(home, options, context = {}) {
1271
1520
  });
1272
1521
  }
1273
1522
  let servers = manifest.mcpServers;
1274
- if (typeof servers === "string") {
1523
+ if (portable) {
1524
+ const text = await optionalText(join2(root, "mcp.json"));
1525
+ servers = text === void 0 ? void 0 : JSON.parse(text);
1526
+ } else if (typeof servers === "string") {
1275
1527
  const text = await optionalText(resolve(root, servers));
1276
1528
  if (text === void 0) throw new Error("missing-plugin-mcp-file");
1277
1529
  servers = JSON.parse(text);
@@ -1285,7 +1537,13 @@ async function discoverCodexSnapshot(home, options, context = {}) {
1285
1537
  home
1286
1538
  );
1287
1539
  result.skillRoots.push(...skillRoots);
1288
- addServers(object(expanded), `plugin:${id}`, root, name2);
1540
+ addServers(
1541
+ object(expanded),
1542
+ `plugin:${id}`,
1543
+ root,
1544
+ name2,
1545
+ object(object(setting).mcp_servers)
1546
+ );
1289
1547
  result.items.push({ name: id, kind: "plugin", state: "loaded" });
1290
1548
  for (const field of ["hooks", "apps", "commands"]) {
1291
1549
  if (manifest[field])
@@ -1297,11 +1555,16 @@ async function discoverCodexSnapshot(home, options, context = {}) {
1297
1555
  });
1298
1556
  }
1299
1557
  } catch {
1300
- result.mcp.push(
1301
- ...context.previous?.mcp.filter(
1302
- (entry) => entry.source === `plugin:${id}`
1303
- ) ?? []
1304
- );
1558
+ const policies = object(object(setting).mcp_servers);
1559
+ for (const entry of context.previous?.mcp ?? []) {
1560
+ if (entry.source !== `plugin:${id}`) continue;
1561
+ if (!entry.originalName && Object.keys(policies).length) continue;
1562
+ const retained = restrictMcpFallback(
1563
+ entry,
1564
+ object(policies[entry.originalName ?? ""])
1565
+ );
1566
+ if (retained) result.mcp.push(retained);
1567
+ }
1305
1568
  result.skillRoots.push(
1306
1569
  ...context.previous?.skillRoots.filter(
1307
1570
  (entry) => entry.pluginId === id
@@ -1548,6 +1811,49 @@ function installCodexInstructions(ctx, home) {
1548
1811
  );
1549
1812
  }
1550
1813
 
1814
+ // src/codex-mcp-metadata.ts
1815
+ import { AsyncLocalStorage } from "node:async_hooks";
1816
+ import { Client } from "@modelcontextprotocol/client";
1817
+ var scope = new AsyncLocalStorage();
1818
+ var users = 0;
1819
+ var restore;
1820
+ function installCodexMcpMetadata() {
1821
+ if (users++ === 0) {
1822
+ const original = Client.prototype.callTool;
1823
+ const callTool = function(params, options) {
1824
+ const call = scope.getStore();
1825
+ const metadata = call?.active() ? call.metadata : void 0;
1826
+ return original.call(
1827
+ this,
1828
+ metadata ? {
1829
+ ...params,
1830
+ _meta: { ...params._meta, "x-codex-turn-metadata": metadata }
1831
+ } : params,
1832
+ options
1833
+ );
1834
+ };
1835
+ Client.prototype.callTool = callTool;
1836
+ restore = () => {
1837
+ if (Client.prototype.callTool === callTool)
1838
+ Client.prototype.callTool = original;
1839
+ };
1840
+ }
1841
+ let active = true;
1842
+ return {
1843
+ run(metadata, operation) {
1844
+ return scope.run({ active: () => active, metadata }, operation);
1845
+ },
1846
+ dispose() {
1847
+ if (!active) return;
1848
+ active = false;
1849
+ if (--users === 0) {
1850
+ restore?.();
1851
+ restore = void 0;
1852
+ }
1853
+ }
1854
+ };
1855
+ }
1856
+
1551
1857
  // src/codex.ts
1552
1858
  var fingerprint = (v) => createHash5("sha256").update(JSON.stringify(v)).digest("hex");
1553
1859
  function publicToolName(server, name2) {
@@ -1629,6 +1935,7 @@ function installCodexCompatibility(ctx, config) {
1629
1935
  dispose: async () => {
1630
1936
  }
1631
1937
  };
1938
+ const metadata = config.importMcpServers ? installCodexMcpMetadata() : void 0;
1632
1939
  const scopedSnapshot = async (cwd, signal, fallback) => {
1633
1940
  const key = resolve3(cwd);
1634
1941
  const previous = scopedSnapshots.get(key) ?? fallback ?? latest;
@@ -1841,7 +2148,22 @@ function installCodexCompatibility(ctx, config) {
1841
2148
  if (enabledTools && !enabledTools.includes(definition.name) || disabledTools.includes(definition.name))
1842
2149
  return () => {
1843
2150
  };
1844
- return target.register(definition);
2151
+ return target.register({
2152
+ ...definition,
2153
+ execute(args, execution) {
2154
+ const agent = execution.agent ?? owner.agent;
2155
+ const boundary = agent.session ? child.agents.turnBoundaryOf?.(agent.session) : void 0;
2156
+ const turn = boundary?.openTurnStartSeq;
2157
+ const turnMetadata = typeof agent.id === "string" && agent.id && turn != null ? {
2158
+ session_id: agent.id,
2159
+ turn_id: `${agent.id}:${turn}`
2160
+ } : void 0;
2161
+ return metadata.run(
2162
+ turnMetadata,
2163
+ () => definition.execute(args, execution)
2164
+ );
2165
+ }
2166
+ });
1845
2167
  };
1846
2168
  const value = Reflect.get(target, key);
1847
2169
  return typeof value === "function" ? value.bind(target) : value;
@@ -1984,9 +2306,15 @@ function installCodexCompatibility(ctx, config) {
1984
2306
  agents.delete(agent);
1985
2307
  await owner.tail.catch(() => {
1986
2308
  });
1987
- await Promise.all(
1988
- [...owner.clients.values()].map((client) => client.fiber.dispose())
2309
+ const results = await Promise.allSettled(
2310
+ [...owner.clients.values()].map(async (client) => client.fiber.dispose())
2311
+ );
2312
+ const errors = results.flatMap(
2313
+ (result) => result.status === "rejected" ? [result.reason] : []
1989
2314
  );
2315
+ if (errors.length === 1) throw errors[0];
2316
+ if (errors.length > 1)
2317
+ throw new AggregateError(errors, "Codex MCP client cleanup failed");
1990
2318
  };
1991
2319
  const reconcile = async () => {
1992
2320
  if (closed) return;
@@ -2082,11 +2410,25 @@ function installCodexCompatibility(ctx, config) {
2082
2410
  const dispose = () => stopping ??= (async () => {
2083
2411
  closed = true;
2084
2412
  if (timer) clearInterval(timer);
2085
- await Promise.all(injections.map((fiber) => fiber.dispose()));
2086
- await tail;
2087
- await Promise.all([...agents.keys()].map(disposeAgent));
2088
- skillDispose?.();
2089
- await Promise.all(skills.map((s) => s.dispose()));
2413
+ const errors = [];
2414
+ const settle = async (actions) => {
2415
+ const results = await Promise.allSettled(
2416
+ actions.map(async (action) => action())
2417
+ );
2418
+ for (const result of results)
2419
+ if (result.status === "rejected") errors.push(result.reason);
2420
+ };
2421
+ await settle(injections.map((fiber) => () => fiber.dispose()));
2422
+ await settle([() => tail]);
2423
+ await settle([...agents.keys()].map((agent) => () => disposeAgent(agent)));
2424
+ await settle([
2425
+ () => metadata?.dispose(),
2426
+ () => skillDispose?.(),
2427
+ ...skills.map((provider) => () => provider.dispose())
2428
+ ]);
2429
+ if (errors.length === 1) throw errors[0];
2430
+ if (errors.length > 1)
2431
+ throw new AggregateError(errors, "Codex compatibility cleanup failed");
2090
2432
  })();
2091
2433
  ctx.effect(() => dispose, "codex-compatibility");
2092
2434
  return {
@@ -2156,14 +2498,15 @@ var Config2 = z2.object({
2156
2498
  mcpAllowlist: z2.array(String).default([])
2157
2499
  }).default({})
2158
2500
  });
2159
- var _speed_dec, _setSpeed_dec, _removeStale_dec, _refresh_dec, _updateOptions_dec, _configure_dec, _status_dec, _refreshCodex_dec, _codexStatus_dec, _a, _init;
2160
- 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) {
2161
2503
  constructor(ctx, options, settingsNs) {
2162
2504
  super(ctx, "gptModels");
2163
2505
  this.options = options;
2164
2506
  this.settingsNs = settingsNs;
2165
2507
  __runInitializers(_init, 5, this);
2166
2508
  __publicField(this, "codex");
2509
+ __publicField(this, "contextWriter");
2167
2510
  __publicField(this, "typertRemote");
2168
2511
  __publicField(this, "runners", /* @__PURE__ */ new Map());
2169
2512
  __publicField(this, "snapshots", /* @__PURE__ */ new Map());
@@ -2291,11 +2634,11 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2291
2634
  }
2292
2635
  async publish(provider, snapshot) {
2293
2636
  if (this.closed) return;
2294
- const { descriptor, profile } = this.profile(provider);
2637
+ const { profile } = this.profile(provider);
2295
2638
  const existing = profile.models ?? [];
2296
2639
  const ids = new Set(existing.map((model) => model.id));
2297
2640
  const added = snapshot.models.filter((model) => !model.stale && !ids.has(model.id)).map((model) => ({ id: model.id, name: model.displayName ?? model.id }));
2298
- if (added.length) {
2641
+ if (added.length && !Object.keys(profile.modelOverrides ?? {}).length) {
2299
2642
  await this.ctx.settings.mutate(
2300
2643
  "llm-pi-ai",
2301
2644
  [
@@ -2306,12 +2649,14 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2306
2649
  (model) => projectModel(
2307
2650
  provider,
2308
2651
  model,
2309
- (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)
2310
2655
  )
2311
2656
  )
2312
2657
  }
2313
2658
  ],
2314
- descriptor.revision
2659
+ this.modelsRevision()
2315
2660
  );
2316
2661
  }
2317
2662
  if (this.closed) return;
@@ -2347,6 +2692,15 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2347
2692
  stale: model.stale ?? false
2348
2693
  })) ?? [],
2349
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(),
2350
2704
  options: this.routeOptions(provider) ?? {
2351
2705
  enabled: false,
2352
2706
  refreshIntervalMs: 18e5,
@@ -2387,6 +2741,10 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2387
2741
  const overrides = JSON.parse(overridesJSON);
2388
2742
  if (!overrides || typeof overrides !== "object" || Array.isArray(overrides))
2389
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
+ }
2390
2748
  const descriptor = this.ctx.settings.describe().find((entry) => entry.ns === this.settingsNs);
2391
2749
  if (!descriptor) throw new Error("GPT plugin settings unavailable");
2392
2750
  await this.ctx.settings.mutate(
@@ -2416,7 +2774,7 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2416
2774
  const runner = this.runners.get(provider);
2417
2775
  if (!runner) throw new Error("Enable GPT adaptation first");
2418
2776
  const staleIds = runner.current()?.models.filter((model) => model.stale).map((model) => model.id) ?? [];
2419
- const { descriptor, profile } = this.profile(provider);
2777
+ const { profile } = this.profile(provider);
2420
2778
  const nextModels = profile.models?.filter(
2421
2779
  (model) => !staleIds.includes(model.id)
2422
2780
  );
@@ -2431,15 +2789,101 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2431
2789
  (model) => projectModel(
2432
2790
  provider,
2433
2791
  model,
2434
- (route, row) => this.capability(route, row.id, row.name)
2792
+ (route, row) => this.capability(route, row.id, row.name),
2793
+ void 0
2435
2794
  )
2436
2795
  )
2437
2796
  }
2438
2797
  ],
2439
- descriptor.revision
2798
+ this.modelsRevision()
2440
2799
  );
2441
2800
  await runner.removeStale(staleIds);
2442
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
+ }
2443
2887
  async setSpeed(sessionId, provider, modelId, tier) {
2444
2888
  const capability = this.capability(provider, modelId);
2445
2889
  if (!capability?.serviceTiers.some((choice) => choice.id === tier))
@@ -2476,11 +2920,15 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2476
2920
  ...overrides[modelId]
2477
2921
  };
2478
2922
  const current = this.snapshots.get(provider)?.capabilities[modelId];
2479
- return current ? mergeCapabilities(current, override, current.capabilityRevision) : resolveCapabilities(
2923
+ const result = current ? mergeCapabilities(current, override, current.capabilityRevision) : resolveCapabilities(
2480
2924
  { id: modelId, ...displayName ? { displayName } : {} },
2481
2925
  "gpt-preset",
2482
2926
  override
2483
2927
  );
2928
+ return {
2929
+ ...result,
2930
+ contextWindow: this.contextWindow(provider, modelId).effectiveTokens
2931
+ };
2484
2932
  }
2485
2933
  /** Capture request controls for the version-checked native runtime extension. */
2486
2934
  capture(provider, modelId, base) {
@@ -2493,6 +2941,14 @@ var GptModels = class extends (_a = Service, _codexStatus_dec = [Remote], _refre
2493
2941
  const protocol = profile.api ?? "openai-responses";
2494
2942
  const model = {
2495
2943
  ...base,
2944
+ context: {
2945
+ ...base.context ?? {},
2946
+ contextWindow: this.contextWindow(
2947
+ provider,
2948
+ modelId,
2949
+ base.context?.contextWindow
2950
+ ).effectiveTokens
2951
+ },
2496
2952
  name: capabilities.displayName === modelId ? base.name : capabilities.displayName
2497
2953
  };
2498
2954
  if (!preserveReasoning) delete model.reasoning;
@@ -2543,6 +2999,7 @@ __decorateElement(_init, 1, "configure", _configure_dec, GptModels);
2543
2999
  __decorateElement(_init, 1, "updateOptions", _updateOptions_dec, GptModels);
2544
3000
  __decorateElement(_init, 1, "refresh", _refresh_dec, GptModels);
2545
3001
  __decorateElement(_init, 1, "removeStale", _removeStale_dec, GptModels);
3002
+ __decorateElement(_init, 1, "setContextWindow", _setContextWindow_dec, GptModels);
2546
3003
  __decorateElement(_init, 1, "setSpeed", _setSpeed_dec, GptModels);
2547
3004
  __decorateElement(_init, 1, "speed", _speed_dec, GptModels);
2548
3005
  __decoratorMetadata(_init, GptModels);
@@ -2582,9 +3039,15 @@ async function apply2(ctx, config) {
2582
3039
  const providers = service.enabledProviders();
2583
3040
  const dispose = installSettingsOverlay(
2584
3041
  ctx.settings,
2585
- (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
+ }
2586
3047
  );
3048
+ service.contextWriter = dispose.setContextWindow;
2587
3049
  return () => {
3050
+ service.contextWriter = void 0;
2588
3051
  dispose();
2589
3052
  for (const provider of providers) ctx.emit("llm/models-updated", provider);
2590
3053
  };