dsh-codex-connect 0.1.0-alpha.4.21 → 0.1.0-alpha.4.22

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.
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
  import { basename, dirname, join, parse, resolve } from "node:path";
3
3
  import z from "@deepseek-ai/schemastery";
4
- import { deepEqualJson, installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
4
+ import { deepEqualJson } from "@deepseek-ai/dsh-util-values";
5
5
  import { createModels, defaultProviderAuthContext } from "@earendil-works/pi-ai";
6
6
  import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
7
7
  import { createUserMessage, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
@@ -165,13 +165,43 @@ var OpenAICodexCredentialStore = class {
165
165
  }
166
166
  };
167
167
  //#endregion
168
+ //#region src/model-contract.ts
169
+ /** Node-free model catalog contract shared by the Host route and browser card. */
170
+ /** Same-origin endpoint exposing the complete Codex model catalog. */
171
+ const OPENAI_CODEX_MODEL_CATALOG_PATH = "/plugins/dsh-codex-connect/models";
172
+ const CONFIGURATION_LIMITS = Object.freeze({
173
+ "gpt-5.6-sol": 872e3,
174
+ "gpt-5.6-terra": 872e3,
175
+ "gpt-5.6-luna": 872e3,
176
+ "gpt-5.4": 1e6,
177
+ "gpt-5.5": 272e3,
178
+ "gpt-5.4-mini": 272e3
179
+ });
180
+ /** Keep unlisted or newer provider defaults usable without inventing a larger limit. */
181
+ function openAICodexContextLimit(id, contextWindow) {
182
+ const ceiling = Object.hasOwn(CONFIGURATION_LIMITS, id) ? CONFIGURATION_LIMITS[id] : void 0;
183
+ return ceiling === void 0 || ceiling < contextWindow ? {
184
+ maxContextWindow: contextWindow,
185
+ contextLimitSource: "catalog-default"
186
+ } : {
187
+ maxContextWindow: ceiling,
188
+ contextLimitSource: "codex-catalog"
189
+ };
190
+ }
191
+ /** Whether a proposed local token budget fits the model's configuration range. */
192
+ function isValidOpenAICodexContextBudget(value, maximum) {
193
+ return Number.isSafeInteger(value) && value > 0 && value <= maximum;
194
+ }
195
+ //#endregion
168
196
  //#region src/adapter.ts
169
197
  /** OpenAI Codex adapter assembled from public dsh-llm-pi-ai extension points. */
170
198
  /** Return a detached copy of the complete pi-ai Codex model catalog. */
171
199
  function openAICodexModelCatalog() {
172
200
  return openaiCodexProvider().getModels().map((model) => ({
173
201
  id: model.id,
174
- name: model.name
202
+ name: model.name,
203
+ contextWindow: model.contextWindow,
204
+ ...openAICodexContextLimit(model.id, model.contextWindow)
175
205
  }));
176
206
  }
177
207
  /** Provider idle ceiling used by the composite route. */
@@ -240,8 +270,9 @@ function requestProvider(provider, fastMode, proxyManager, resolveProxyUrl) {
240
270
  }
241
271
  };
242
272
  }
243
- /** Build the immutable profile consumed by the rc.2 pi-ai adapter. */
244
- function createOpenAICodexProfile(provider, fastMode, proxyManager, resolveProxyUrl) {
273
+ /** Build the immutable profile consumed by the DSH pi-ai adapter. */
274
+ function createOpenAICodexProfile(provider, fastMode, proxyManager, resolveProxyUrl, contextWindowOverrides) {
275
+ const effectiveProvider = contextWindowOverrides === void 0 ? provider : withOpenAICodexContextWindowOverrides(provider, contextWindowOverrides);
245
276
  return {
246
277
  provider: OPENAI_CODEX_PROVIDER,
247
278
  displayName: "OpenAI Codex",
@@ -252,18 +283,59 @@ function createOpenAICodexProfile(provider, fastMode, proxyManager, resolveProxy
252
283
  requestImageMaxBytes: OPENAI_CODEX_REQUEST_IMAGE_MAX_BYTES,
253
284
  retryPolicy: resolveRetryPolicy(void 0, "dsh-codex-connect retryPolicy"),
254
285
  configuredMaxTokens: /* @__PURE__ */ new Map(),
255
- piProvider: requestProvider(provider, fastMode, proxyManager, resolveProxyUrl)
286
+ piProvider: requestProvider(effectiveProvider, fastMode, proxyManager, resolveProxyUrl)
256
287
  };
257
288
  }
258
289
  /**
290
+ * Detach one provider and replace the advertised context window for the
291
+ * configured model ids. Request streaming itself is unaffected: pi-ai streams
292
+ * the caller-supplied model, so only the metadata Harness reads for context
293
+ * budgeting and compaction changes.
294
+ */
295
+ function withOpenAICodexContextWindowOverrides(provider, overrides) {
296
+ const baselineModels = provider.getModels();
297
+ assertOpenAICodexContextWindowOverrides(overrides, baselineModels);
298
+ const replaced = baselineModels.map((model) => {
299
+ const contextWindow = overrides[model.id];
300
+ return contextWindow === void 0 ? model : {
301
+ ...model,
302
+ contextWindow
303
+ };
304
+ });
305
+ return {
306
+ ...provider,
307
+ getModels: () => replaced
308
+ };
309
+ }
310
+ /** Reject unknown ids and out-of-range budgets before accepting settings or requests. */
311
+ function assertOpenAICodexContextWindowOverrides(overrides, catalog) {
312
+ const models = new Map(catalog.map((model) => [model.id, model]));
313
+ for (const [id, budget] of Object.entries(overrides ?? {})) {
314
+ const model = models.get(id);
315
+ if (model === void 0) throw new TypeError(`OpenAI Codex contextWindowOverrides contains unknown model id "${id}"`);
316
+ const { maxContextWindow } = openAICodexContextLimit(id, model.contextWindow);
317
+ if (budget !== null && !isValidOpenAICodexContextBudget(budget, maxContextWindow)) throw new TypeError(`OpenAI Codex contextWindowOverrides for "${id}" must be an integer from 1 to ${maxContextWindow} tokens; use null to restore the catalog default`);
318
+ }
319
+ }
320
+ /**
259
321
  * Create the Codex subscription adapter without requiring a dsh fork. The
260
322
  * public pi-ai adapter owns Harness message conversion, image attachment
261
323
  * resolution, streaming, reasoning metadata, and compaction behavior; this
262
324
  * plugin supplies its provider-native OAuth token for each request.
263
325
  */
264
- function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, visibleModelIds, proxyManager, resolveProxyUrl) {
326
+ function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, visibleModelIds, proxyManager, resolveProxyUrl, contextWindowOverrides) {
265
327
  const provider = openaiCodexProvider();
266
- const profiles = /* @__PURE__ */ new Map([[OPENAI_CODEX_PROVIDER, createOpenAICodexProfile(provider, fastMode, proxyManager, resolveProxyUrl)]]);
328
+ let profiles;
329
+ let previousOverrides;
330
+ const currentProfiles = () => {
331
+ const overrides = contextWindowOverrides?.();
332
+ if (profiles === void 0 || !deepEqualJson(previousOverrides, overrides)) {
333
+ const profile = createOpenAICodexProfile(provider, fastMode, proxyManager, resolveProxyUrl, overrides);
334
+ previousOverrides = overrides === void 0 ? void 0 : { ...overrides };
335
+ profiles = /* @__PURE__ */ new Map([[OPENAI_CODEX_PROVIDER, profile]]);
336
+ }
337
+ return profiles;
338
+ };
267
339
  const models = createModels({ credentials });
268
340
  models.setProvider(provider);
269
341
  class OpenAICodexAdapter extends PiAiAdapter {
@@ -276,7 +348,7 @@ function createOpenAICodexAdapter(credentials, resolveAttachments, fastMode, vis
276
348
  }
277
349
  }
278
350
  return new OpenAICodexAdapter({
279
- profiles: () => profiles,
351
+ profiles: currentProfiles,
280
352
  resolveApiKey: async () => {
281
353
  const operation = async () => (await models.getAuth(OPENAI_CODEX_PROVIDER))?.auth.apiKey;
282
354
  return proxyManager?.run(resolveProxyUrl?.(), operation) ?? operation();
@@ -494,6 +566,8 @@ const OPENAI_CODEX_AUTH_STATUS_PATH = "/plugins/dsh-openai-codex/auth/status";
494
566
  const OPENAI_CODEX_AUTH_LOGIN_PATH = "/plugins/dsh-openai-codex/auth/login";
495
567
  /** Plugin-owned logout endpoint consumed by its browser half. */
496
568
  const OPENAI_CODEX_AUTH_LOGOUT_PATH = "/plugins/dsh-openai-codex/auth/logout";
569
+ /** Cancel only the pending authorization; never delete a stored credential. */
570
+ const OPENAI_CODEX_AUTH_CANCEL_PATH = "/plugins/dsh-openai-codex/auth/cancel";
497
571
  //#endregion
498
572
  //#region src/trusted-origins.ts
499
573
  /** Owner-only allowlist for browser origins that may reach the Web OAuth routes. */
@@ -737,6 +811,8 @@ var FastModeRegistry = class {
737
811
  /** Node-free Fast Mode route constants shared by Host and browser halves. */
738
812
  /** GET/POST endpoint for one conversation's process-local Fast Mode state. */
739
813
  const OPENAI_CODEX_FAST_MODE_PATH = "/plugins/dsh-openai-codex/fast-mode";
814
+ /** Default deadline for the complete interactive authorization, including the callback. */
815
+ const OPENAI_CODEX_AUTHORIZATION_TIMEOUT_MS = 6e5;
740
816
  /** Stable, non-sensitive error returned when a browser origin needs CLI trust. */
741
817
  const REMOTE_WEB_ORIGIN_NOT_TRUSTED = "remote-web-origin-not-trusted";
742
818
  /** Redact provider diagnostics before they cross to the browser. */
@@ -744,9 +820,8 @@ function safeMessage(error) {
744
820
  return (error instanceof Error ? error.message : String(error)).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted token]").replace(/(\b(?:code|token|refresh_token|access_token)=)[^&\s]+/giu, "$1[redacted]").slice(0, 1e3);
745
821
  }
746
822
  /** Reject with the prompt's abort reason while browser callback owns completion. */
747
- function waitForPromptAbort(prompt) {
748
- const signal = prompt.signal;
749
- if (signal === void 0) return new Promise(() => {});
823
+ function waitForPromptAbort(prompt, operationSignal) {
824
+ const signal = prompt.signal === void 0 ? operationSignal : AbortSignal.any([prompt.signal, operationSignal]);
750
825
  if (signal.aborted) return Promise.reject(signal.reason);
751
826
  return new Promise((_resolve, reject) => {
752
827
  signal.addEventListener("abort", () => {
@@ -763,15 +838,21 @@ var OpenAICodexWebAuth = class {
763
838
  challenge;
764
839
  challengeWaiters = [];
765
840
  challengeTimer;
841
+ authorizationTimer;
842
+ transition;
843
+ disposed = false;
766
844
  challengeTimeoutMs;
845
+ authorizationTimeoutMs;
767
846
  proxyManager;
768
847
  resolveProxyUrl;
769
848
  constructor(store, options = {}) {
770
849
  this.store = store;
771
850
  this.challengeTimeoutMs = options.challengeTimeoutMs ?? 3e4;
851
+ this.authorizationTimeoutMs = options.authorizationTimeoutMs ?? 6e5;
772
852
  this.proxyManager = options.proxyManager;
773
853
  this.resolveProxyUrl = options.resolveProxyUrl ?? (() => void 0);
774
854
  if (!Number.isFinite(this.challengeTimeoutMs) || this.challengeTimeoutMs <= 0) throw new TypeError("OpenAI Codex auth URL timeout must be a positive finite number");
855
+ if (!Number.isSafeInteger(this.authorizationTimeoutMs) || this.authorizationTimeoutMs <= 0 || this.authorizationTimeoutMs > 2147483647) throw new TypeError("OpenAI Codex authorization timeout must be a positive timer-safe integer");
775
856
  }
776
857
  /** Read current public state, consulting durable storage while idle. */
777
858
  async status() {
@@ -781,6 +862,8 @@ var OpenAICodexWebAuth = class {
781
862
  }
782
863
  /** Start or join the current browser-login operation. */
783
864
  async signIn() {
865
+ while (this.transition !== void 0) await this.transition;
866
+ if (this.disposed) throw new Error("OpenAI Codex plugin disposed");
784
867
  if (this.operation === void 0) this.start();
785
868
  if (this.challenge !== void 0) return this.challenge;
786
869
  return new Promise((resolve, reject) => {
@@ -792,16 +875,35 @@ var OpenAICodexWebAuth = class {
792
875
  }
793
876
  /** Cancel any callback listener, wait for quiescence, then delete the credential. */
794
877
  async signOut() {
795
- this.cancelSignIn(/* @__PURE__ */ new Error("OpenAI Codex sign-in cancelled"));
796
- await this.operation?.catch(() => void 0);
797
- await logoutOpenAICodex(this.store);
798
- this.challenge = void 0;
799
- this.state = { status: "signed-out" };
878
+ await this.finishLogin("logout");
879
+ }
880
+ /** Cancel the pending authorization and retain any already stored account. */
881
+ async cancel() {
882
+ await this.finishLogin("cancel");
800
883
  }
801
884
  /** Stop the owned callback listener during plugin disposal. */
802
885
  async dispose() {
803
- this.cancelSignIn(/* @__PURE__ */ new Error("OpenAI Codex plugin disposed"));
804
- await this.operation?.catch(() => void 0);
886
+ this.disposed = true;
887
+ await this.finishLogin("dispose");
888
+ }
889
+ async finishLogin(action) {
890
+ while (this.transition !== void 0) await this.transition;
891
+ const operation = this.operation;
892
+ this.cancelSignIn(/* @__PURE__ */ new Error(action === "dispose" ? "OpenAI Codex plugin disposed" : "OpenAI Codex sign-in cancelled"));
893
+ const transition = (async () => {
894
+ await operation?.catch(() => void 0);
895
+ this.challenge = void 0;
896
+ if (action === "logout") {
897
+ await logoutOpenAICodex(this.store);
898
+ this.state = { status: "signed-out" };
899
+ } else if (action === "cancel") this.state = await this.readStoredStatus();
900
+ })();
901
+ this.transition = transition;
902
+ try {
903
+ await transition;
904
+ } finally {
905
+ if (this.transition === transition) this.transition = void 0;
906
+ }
805
907
  }
806
908
  start() {
807
909
  const cancellation = new AbortController();
@@ -812,9 +914,13 @@ var OpenAICodexWebAuth = class {
812
914
  this.cancelSignIn(/* @__PURE__ */ new Error(`OpenAI Codex did not provide an authorization URL within ${String(this.challengeTimeoutMs)}ms`));
813
915
  }, this.challengeTimeoutMs);
814
916
  this.challengeTimer.unref();
917
+ this.authorizationTimer = setTimeout(() => {
918
+ this.cancelSignIn(/* @__PURE__ */ new Error("ChatGPT authorization expired. Please sign in again."));
919
+ }, this.authorizationTimeoutMs);
920
+ this.authorizationTimer.unref();
815
921
  const login = () => loginOpenAICodex({
816
922
  signal: cancellation.signal,
817
- prompt: (prompt) => prompt.type === "select" ? Promise.resolve("browser") : waitForPromptAbort(prompt),
923
+ prompt: (prompt) => prompt.type === "select" ? Promise.resolve("browser") : waitForPromptAbort(prompt, cancellation.signal),
818
924
  notify: (event) => {
819
925
  this.onEvent(event);
820
926
  }
@@ -838,6 +944,9 @@ var OpenAICodexWebAuth = class {
838
944
  };
839
945
  }).finally(() => {
840
946
  this.clearChallengeTimer();
947
+ clearTimeout(this.authorizationTimer);
948
+ this.authorizationTimer = void 0;
949
+ this.challenge = void 0;
841
950
  this.operation = void 0;
842
951
  this.cancellation = void 0;
843
952
  });
@@ -1045,10 +1154,11 @@ function fastModeBody(value) {
1045
1154
  } : void 0;
1046
1155
  }
1047
1156
  /** Register the plugin-owned OAuth routes when the Web server is composed. */
1048
- function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastModeOverride, proxyManager, resolveProxyUrl) {
1157
+ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastModeOverride, proxyManager, resolveProxyUrl, authorizationTimeoutMs) {
1049
1158
  const auth = new OpenAICodexWebAuth(store, {
1050
1159
  proxyManager,
1051
- resolveProxyUrl
1160
+ resolveProxyUrl,
1161
+ authorizationTimeoutMs
1052
1162
  });
1053
1163
  const storedFilename = store.filename;
1054
1164
  const fastMode = fastModeOverride ?? new FastModeRegistry();
@@ -1083,6 +1193,20 @@ function registerOpenAICodexAuthRoutes(ctx, store, trustedOriginsOverride, fastM
1083
1193
  }
1084
1194
  }
1085
1195
  }),
1196
+ ctx.webServer.register({
1197
+ kind: "exact",
1198
+ path: OPENAI_CODEX_AUTH_CANCEL_PATH,
1199
+ handler: async (req, res) => {
1200
+ if (req.method !== "POST") return json$4(res, 405, { error: "method not allowed" });
1201
+ if (!await authorize(req, res)) return;
1202
+ try {
1203
+ await auth.cancel();
1204
+ json$4(res, 200, await auth.status());
1205
+ } catch (error) {
1206
+ json$4(res, 500, { error: safeMessage(error) });
1207
+ }
1208
+ }
1209
+ }),
1086
1210
  ctx.webServer.register({
1087
1211
  kind: "exact",
1088
1212
  path: OPENAI_CODEX_AUTH_LOGOUT_PATH,
@@ -1157,6 +1281,30 @@ function normalizeOpenAICodexProxyUrl(value) {
1157
1281
  function isValidOpenAICodexProxyUrl(value) {
1158
1282
  return normalizeOpenAICodexProxyUrl(value) !== void 0;
1159
1283
  }
1284
+ /**
1285
+ * Whether a value is a bounded per-model context-window override map. Keys
1286
+ * are nonempty, unpadded model ids; values are positive safe integers or null
1287
+ * to restore that model's catalog default. The Host also checks catalog
1288
+ * membership and the model-specific configuration ceiling.
1289
+ */
1290
+ function isValidOpenAICodexContextWindowOverrides(value) {
1291
+ if (!isRecord$1(value)) return false;
1292
+ const entries = Object.entries(value);
1293
+ if (entries.length > 256) return false;
1294
+ return entries.every(([modelId, window]) => modelId.length > 0 && modelId.trim() === modelId && (window === null || typeof window === "number" && Number.isSafeInteger(window) && window > 0));
1295
+ }
1296
+ /** Preserve per-model null masks until the Host has merged its settings layers. */
1297
+ function parseOpenAICodexContextWindowOverrides(value) {
1298
+ if (value === void 0 || value === null) return void 0;
1299
+ if (!isValidOpenAICodexContextWindowOverrides(value)) throw new TypeError("OpenAI Codex contextWindowOverrides must contain at most 256 nonempty model ids with positive safe-integer token budgets or null resets");
1300
+ return { ...value };
1301
+ }
1302
+ /** Resolve merged settings; whole-map or per-model null masks use catalog defaults. */
1303
+ function resolveOpenAICodexContextWindowOverrides(value) {
1304
+ const overrides = parseOpenAICodexContextWindowOverrides(value);
1305
+ if (overrides === void 0) return void 0;
1306
+ return Object.fromEntries(Object.entries(overrides).filter((entry) => entry[1] !== null));
1307
+ }
1160
1308
  /** Default model used by the standalone search endpoint. */
1161
1309
  const DEFAULT_OPENAI_CODEX_SEARCH_MODEL = "gpt-5.6-sol";
1162
1310
  /** Default search mode, matching the official local Codex client. */
@@ -1169,6 +1317,7 @@ const DEFAULT_OPENAI_CODEX_SETTINGS = Object.freeze({
1169
1317
  models: void 0,
1170
1318
  enableProxy: false,
1171
1319
  proxyUrl: DEFAULT_OPENAI_CODEX_PROXY_URL,
1320
+ contextWindowOverrides: void 0,
1172
1321
  enableSearch: false,
1173
1322
  enableImageTool: false,
1174
1323
  enableImageGeneration: false,
@@ -1184,7 +1333,10 @@ function resolveOpenAICodexSettings(value) {
1184
1333
  ...value
1185
1334
  };
1186
1335
  if (!isValidOpenAICodexProxyUrl(resolved.proxyUrl)) throw new TypeError("OpenAI Codex proxyUrl must be an HTTP(S) origin without credentials or a path");
1187
- return resolved;
1336
+ return {
1337
+ ...resolved,
1338
+ contextWindowOverrides: resolveOpenAICodexContextWindowOverrides(resolved.contextWindowOverrides)
1339
+ };
1188
1340
  }
1189
1341
  /** Resolve the active proxy without treating a disabled value as a route. */
1190
1342
  function resolveOpenAICodexProxyUrl(value) {
@@ -1200,6 +1352,7 @@ function decodeOpenAICodexSettings(value) {
1200
1352
  const models = value["models"];
1201
1353
  const enableProxy = value["enableProxy"];
1202
1354
  const proxyUrl = value["proxyUrl"];
1355
+ const contextWindowOverrides = value["contextWindowOverrides"];
1203
1356
  const enableSearch = value["enableSearch"];
1204
1357
  const enableImageTool = value["enableImageTool"];
1205
1358
  const enableImageGeneration = value["enableImageGeneration"];
@@ -1210,16 +1363,19 @@ function decodeOpenAICodexSettings(value) {
1210
1363
  if (models !== void 0 && (!Array.isArray(models) || models.some((model) => typeof model !== "string"))) return void 0;
1211
1364
  if (enableProxy !== void 0 && typeof enableProxy !== "boolean") return void 0;
1212
1365
  if (proxyUrl !== void 0 && (typeof proxyUrl !== "string" || !isValidOpenAICodexProxyUrl(proxyUrl))) return void 0;
1366
+ if (contextWindowOverrides !== void 0 && contextWindowOverrides !== null && !isValidOpenAICodexContextWindowOverrides(contextWindowOverrides)) return void 0;
1213
1367
  if (typeof enableSearch !== "boolean" || typeof enableImageTool !== "boolean") return void 0;
1214
1368
  if (enableImageGeneration !== void 0 && typeof enableImageGeneration !== "boolean") return void 0;
1215
1369
  if (typeof searchModel !== "string" || searchModel.trim().length === 0) return void 0;
1216
1370
  if (searchMode !== "cached" && searchMode !== "indexed" && searchMode !== "live") return void 0;
1217
1371
  if (searchContextSize !== "low" && searchContextSize !== "medium" && searchContextSize !== "high") return void 0;
1218
1372
  if (typeof searchMaxOutputTokens !== "number" || !Number.isInteger(searchMaxOutputTokens) || searchMaxOutputTokens < 1) return void 0;
1373
+ const overrides = resolveOpenAICodexContextWindowOverrides(contextWindowOverrides);
1219
1374
  return {
1220
1375
  models: models === void 0 ? void 0 : [...new Set(models)],
1221
1376
  enableProxy: enableProxy ?? false,
1222
1377
  proxyUrl: proxyUrl === void 0 ? DEFAULT_OPENAI_CODEX_PROXY_URL : normalizeOpenAICodexProxyUrl(proxyUrl),
1378
+ contextWindowOverrides: overrides === void 0 ? void 0 : Object.freeze(overrides),
1223
1379
  enableSearch,
1224
1380
  enableImageTool,
1225
1381
  enableImageGeneration: enableImageGeneration ?? false,
@@ -1514,8 +1670,8 @@ function registerOpenAICodexProxyRoutes(ctx, trustedOrigins, manager) {
1514
1670
  //#region src/compatibility.ts
1515
1671
  const COMPATIBILITY_SCHEMA_VERSION = 1;
1516
1672
  const SUPPORTED_NODE_RANGE = "^22.19.0 || >=24.0.0";
1517
- const SUPPORTED_DSH_PLUGIN_API_VERSION = "0.1.1-rc.2";
1518
- const SUPPORTED_PI_AI_VERSION = "0.82.1";
1673
+ const SUPPORTED_DSH_PLUGIN_API_VERSION = "0.1.2-alpha.2";
1674
+ const SUPPORTED_PI_AI_RANGE = "^0.84.2";
1519
1675
  const PI_AI_PACKAGE = "@earendil-works/pi-ai";
1520
1676
  const DSH_PLUGIN_API_PACKAGES = [
1521
1677
  "@deepseek-ai/dsh-agent",
@@ -1530,6 +1686,7 @@ const DSH_PLUGIN_API_PACKAGES = [
1530
1686
  "@deepseek-ai/dsh-session",
1531
1687
  "@deepseek-ai/dsh-settings",
1532
1688
  "@deepseek-ai/dsh-tools",
1689
+ "@deepseek-ai/dsh-util-values",
1533
1690
  "@deepseek-ai/dsh-web"
1534
1691
  ];
1535
1692
  const COMPATIBILITY_PACKAGES = [
@@ -1547,13 +1704,21 @@ const COMPATIBILITY_CONTRACT = {
1547
1704
  },
1548
1705
  piAi: {
1549
1706
  package: PI_AI_PACKAGE,
1550
- version: SUPPORTED_PI_AI_VERSION
1707
+ version: SUPPORTED_PI_AI_RANGE
1551
1708
  }
1552
1709
  };
1553
1710
  const PACKAGE_JSON_SEARCH_DEPTH = 8;
1554
1711
  function compareVersion(left, right) {
1555
1712
  return left === right ? "compatible" : "incompatible";
1556
1713
  }
1714
+ function piAiVersionStatus(value) {
1715
+ const match = /^(\d+)\.(\d+)\.(\d+)$/u.exec(value.trim());
1716
+ if (match === null) return "incompatible";
1717
+ const major = Number(match[1]);
1718
+ const minor = Number(match[2]);
1719
+ const patch = Number(match[3]);
1720
+ return major === 0 && minor === 84 && patch >= 2 ? "compatible" : "incompatible";
1721
+ }
1557
1722
  function parseNodeVersion(value) {
1558
1723
  const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?$/u.exec(value.trim());
1559
1724
  if (match === null) return void 0;
@@ -1579,11 +1744,11 @@ function nodeStatus(value) {
1579
1744
  if (major === 22) return minor > 19 || minor === 19 && patch >= 0 ? "compatible" : "incompatible";
1580
1745
  return major >= 24 ? "compatible" : "incompatible";
1581
1746
  }
1582
- function packageEntry(supported, installed) {
1747
+ function packageEntry(supported, installed, status = (value) => compareVersion(value, supported)) {
1583
1748
  return {
1584
1749
  supported,
1585
1750
  installed: installed ?? null,
1586
- status: installed === void 0 || installed === null || installed === "" ? "unknown" : compareVersion(installed, supported)
1751
+ status: installed === void 0 || installed === null || installed === "" ? "unknown" : status(installed)
1587
1752
  };
1588
1753
  }
1589
1754
  function nodeEntry(installed) {
@@ -1605,7 +1770,7 @@ function evaluateCompatibility(input = {}) {
1605
1770
  const packages = {
1606
1771
  "@deepseek-ai/dsh-llm": packageEntry(SUPPORTED_DSH_PLUGIN_API_VERSION, suppliedPackages["@deepseek-ai/dsh-llm"]),
1607
1772
  "@deepseek-ai/dsh-llm-pi-ai": packageEntry(SUPPORTED_DSH_PLUGIN_API_VERSION, suppliedPackages["@deepseek-ai/dsh-llm-pi-ai"]),
1608
- [PI_AI_PACKAGE]: packageEntry(SUPPORTED_PI_AI_VERSION, suppliedPackages[PI_AI_PACKAGE])
1773
+ [PI_AI_PACKAGE]: packageEntry(SUPPORTED_PI_AI_RANGE, suppliedPackages[PI_AI_PACKAGE], piAiVersionStatus)
1609
1774
  };
1610
1775
  const node = nodeEntry(installedNode);
1611
1776
  return {
@@ -1671,7 +1836,9 @@ const HIGHLIGHT_KINDS = [
1671
1836
  "image-generation",
1672
1837
  "oauth-history",
1673
1838
  "model-visibility",
1674
- "proxy-connection"
1839
+ "proxy-connection",
1840
+ "models-account",
1841
+ "context-budget"
1675
1842
  ];
1676
1843
  function isHighlightKind(value) {
1677
1844
  return typeof value === "string" && HIGHLIGHT_KINDS.includes(value);
@@ -2165,11 +2332,6 @@ function registerOpenAICodexUpdateRoutes(ctx, options, trustedOrigins) {
2165
2332
  }, "dsh-codex-connect: update route");
2166
2333
  }
2167
2334
  //#endregion
2168
- //#region src/model-contract.ts
2169
- /** Node-free model catalog contract shared by the Host route and browser card. */
2170
- /** Same-origin endpoint exposing the complete Codex model catalog. */
2171
- const OPENAI_CODEX_MODEL_CATALOG_PATH = "/plugins/dsh-codex-connect/models";
2172
- //#endregion
2173
2335
  //#region src/model-routes.ts
2174
2336
  function json$1(res, status, value) {
2175
2337
  res.writeHead(status, {
@@ -2339,7 +2501,7 @@ function registerOpenAICodexOriginalImageRoute(ctx, trustedOrigins, assets) {
2339
2501
  }
2340
2502
  //#endregion
2341
2503
  //#region src/version.ts
2342
- const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.21";
2504
+ const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.22";
2343
2505
  //#endregion
2344
2506
  //#region src/doctor.ts
2345
2507
  /** Secret-free diagnostics and duplicate-provider guidance. */
@@ -2379,7 +2541,7 @@ async function diagnoseOpenAICodex(options = {}) {
2379
2541
  if (state === "unreadable-metadata") hints.push("Harness could not inspect the OAuth file metadata; check the parent directory and file ownership.");
2380
2542
  if (providerConflict) hints.push(openAICodexConflictMessage());
2381
2543
  if (!providerConflict) hints.push("If Harness reports a duplicate openai-codex adapter, remove the legacy bundle or manual provider row.");
2382
- if (compatibility.status === "incompatible") hints.push("Compatibility mismatch: install the declared DSH plugin API versions and pin @earendil-works/pi-ai to 0.82.1, then run doctor again; no files are changed automatically.");
2544
+ if (compatibility.status === "incompatible") hints.push("Compatibility mismatch: install the declared DSH plugin API versions with @earendil-works/pi-ai ^0.84.2, then run doctor again; no files are changed automatically.");
2383
2545
  else if (compatibility.status === "unknown") hints.push("Compatibility is unknown: verify the declared DSH plugin API and @earendil-works/pi-ai versions, then run doctor again.");
2384
2546
  return {
2385
2547
  package: "dsh-codex-connect",
@@ -4063,11 +4225,13 @@ const name = "llm-openai-codex";
4063
4225
  /** The model registry required before the provider can register. */
4064
4226
  const inject = ["llm"];
4065
4227
  /** Branded Host settings namespace for Codex Connect capability configuration. */
4066
- const OPENAI_CODEX_SETTINGS_NS = settingsNamespace(OPENAI_CODEX_SETTINGS_NAMESPACE);
4228
+ const OPENAI_CODEX_SETTINGS_NS = OPENAI_CODEX_SETTINGS_NAMESPACE;
4067
4229
  const Config = z.object({
4230
+ oauthTimeoutMs: z.number().step(1).min(1e3).max(18e5).default(OPENAI_CODEX_AUTHORIZATION_TIMEOUT_MS),
4068
4231
  models: z.union([z.const(void 0), z.array(z.string())]),
4069
4232
  enableProxy: z.boolean().default(false),
4070
4233
  proxyUrl: z.string().default(DEFAULT_OPENAI_CODEX_PROXY_URL),
4234
+ contextWindowOverrides: z.transform(z.union([z.const(void 0), z.dict(z.union([z.const(null), z.number()]))]), parseOpenAICodexContextWindowOverrides),
4071
4235
  enableSearch: z.boolean().default(false),
4072
4236
  enableImageTool: z.boolean().default(false),
4073
4237
  enableImageGeneration: z.boolean().default(false),
@@ -4092,6 +4256,12 @@ const Config = z.object({
4092
4256
  * @param config - capability gates and standalone-search tuning.
4093
4257
  */
4094
4258
  function apply(ctx, config) {
4259
+ const catalog = openAICodexModelCatalog();
4260
+ const validateSettings = (value) => {
4261
+ resolveOpenAICodexSettings(value);
4262
+ assertOpenAICodexContextWindowOverrides(value.contextWindowOverrides ?? void 0, catalog);
4263
+ };
4264
+ validateSettings(config);
4095
4265
  let current = () => config;
4096
4266
  const proxyManager = new OpenAICodexProxyManager();
4097
4267
  const resolveProviderProxyUrl = () => resolveOpenAICodexProxyUrl(resolveOpenAICodexSettings(current()));
@@ -4102,9 +4272,9 @@ function apply(ctx, config) {
4102
4272
  const fastMode = new FastModeRegistry();
4103
4273
  assertNoOpenAICodexProviderConflict(ctx.llm.listProviders().map((provider) => provider.id));
4104
4274
  new OpenAICodexTransport(ctx, credentials, proxyManager, resolveProviderProxyUrl);
4105
- ctx.llm.registerAdapter([OPENAI_CODEX_PROVIDER], createOpenAICodexAdapter(credentials, () => ctx.get("attachments"), fastMode, () => resolveOpenAICodexSettings(current()).models, proxyManager, resolveProviderProxyUrl));
4275
+ ctx.llm.registerAdapter([OPENAI_CODEX_PROVIDER], createOpenAICodexAdapter(credentials, () => ctx.get("attachments"), fastMode, () => resolveOpenAICodexSettings(current()).models, proxyManager, resolveProviderProxyUrl, () => resolveOpenAICodexSettings(current()).contextWindowOverrides));
4106
4276
  ctx.inject(["webServer"], (webCtx) => {
4107
- registerOpenAICodexAuthRoutes(webCtx, credentials, trustedOrigins, fastMode, proxyManager, resolveProviderProxyUrl);
4277
+ registerOpenAICodexAuthRoutes(webCtx, credentials, trustedOrigins, fastMode, proxyManager, resolveProviderProxyUrl, config.oauthTimeoutMs);
4108
4278
  registerOpenAICodexProxyRoutes(webCtx, trustedOrigins, proxyManager);
4109
4279
  registerOpenAICodexUpdateRoutes(webCtx, { currentVersion: CODEX_CONNECT_VERSION }, trustedOrigins);
4110
4280
  registerOpenAICodexModelCatalogRoute(webCtx, openAICodexModelCatalog, trustedOrigins);
@@ -4224,25 +4394,27 @@ function apply(ctx, config) {
4224
4394
  ]);
4225
4395
  await proxyManager.dispose();
4226
4396
  }, "dsh-codex-connect: optional capability lifecycle");
4227
- installSettingsSection(ctx, OPENAI_CODEX_SETTINGS_NS, Config, config, {
4228
- validate(value) {
4229
- resolveOpenAICodexSettings(value);
4230
- if (value.enableProxy === true && !isValidOpenAICodexProxyUrl(value.proxyUrl)) throw new TypeError("OpenAI Codex proxyUrl must be an HTTP(S) origin without credentials or a path");
4231
- },
4232
- setSource(source) {
4233
- current = source;
4234
- },
4235
- onChange() {
4236
- const proxyIsActive = resolveProviderProxyUrl() !== void 0;
4237
- if (proxyWasActive && !proxyIsActive) proxyManager.deactivate().catch((error) => {
4238
- ctx.logger.error("dsh-codex-connect: could not deactivate the provider proxy");
4239
- ctx.logger.error(error);
4240
- });
4241
- proxyWasActive = proxyIsActive;
4242
- scheduleCapabilities();
4243
- }
4397
+ ctx.inject(["settings"], (settingsCtx) => {
4398
+ settingsCtx.settings.installSection(ctx, OPENAI_CODEX_SETTINGS_NS, Config, config, {
4399
+ validate(value) {
4400
+ validateSettings(value);
4401
+ if (value.enableProxy === true && !isValidOpenAICodexProxyUrl(value.proxyUrl)) throw new TypeError("OpenAI Codex proxyUrl must be an HTTP(S) origin without credentials or a path");
4402
+ },
4403
+ setSource(source) {
4404
+ current = source;
4405
+ },
4406
+ onChange() {
4407
+ const proxyIsActive = resolveProviderProxyUrl() !== void 0;
4408
+ if (proxyWasActive && !proxyIsActive) proxyManager.deactivate().catch((error) => {
4409
+ ctx.logger.error("dsh-codex-connect: could not deactivate the provider proxy");
4410
+ ctx.logger.error(error);
4411
+ });
4412
+ proxyWasActive = proxyIsActive;
4413
+ scheduleCapabilities();
4414
+ }
4415
+ });
4244
4416
  });
4245
4417
  scheduleCapabilities();
4246
4418
  }
4247
4419
  //#endregion
4248
- export { OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS as $, openAICodexConflictMessage as A, OPENAI_CODEX_AUTH_FILENAME as At, DSH_PLUGIN_API_PACKAGES as B, OpenAICodexTransport as C, OPENAI_CODEX_USAGE_URL as Ct, IMAGE_GENERATE_TOOL_NAME as D, logoutOpenAICodex as Dt, VIEW_IMAGE_TOOL_NAME as E, loginOpenAICodex as Et, parseOpenAICodexUpdateResult as F, assessCompatibility as G, SUPPORTED_DSH_PLUGIN_API_VERSION as H, parseOpenAICodexVersion as I, readInstalledPackageVersion as J, detectCompatibility as K, COMPATIBILITY_CONTRACT as L, OPENAI_CODEX_UPDATE_PATH as M, OpenAICodexCredentialStore as Mt, checkForOpenAICodexUpdate as N, openAICodexAuthPath as Nt, assertNoOpenAICodexProviderConflict as O, openAICodexAuthStatus as Ot, compareOpenAICodexVersions as P, OPENAI_CODEX_PROXY_CANDIDATE_LIMIT as Q, COMPATIBILITY_PACKAGES as R, OPENAI_CODEX_TRANSPORT_SERVICE as S, normalizeTrustedOrigin as St, isOpenAICodexTransportError as T, readOpenAICodexRateLimits as Tt, SUPPORTED_NODE_RANGE as U, PI_AI_PACKAGE as V, SUPPORTED_PI_AI_VERSION as W, OPENAI_CODEX_PROXY_TEST_PATH as X, OPENAI_CODEX_PROXY_DETECT_PATH as Y, OPENAI_CODEX_LOCAL_PROXY_CANDIDATES as Z, OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES as _, FastModeRegistry as _t, name as a, DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE as at, OPENAI_CODEX_TRANSPORT_API_VERSION as b, isFastModeSessionId as bt, migrateOpenAICodexSearchHistory as c, DEFAULT_OPENAI_CODEX_SEARCH_MODEL as ct, OPENAI_CODEX_SEARCH_URL as d, decodeOpenAICodexSettings as dt, OPENAI_CODEX_PROXY_PROBE_URL as et, OpenAICodexSearchProvider as f, isValidOpenAICodexProxyUrl as ft, OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES as g, OPENAI_CODEX_FAST_MODE_PATH as gt, OPENAI_CODEX_IMAGE_MAX_COUNT as h, resolveOpenAICodexSettings as ht, inject as i, DEFAULT_OPENAI_CODEX_PROXY_URL as it, CODEX_CONNECT_VERSION as j, OPENAI_CODEX_PROVIDER as jt, diagnoseOpenAICodex as k, openAICodexModelCatalog as kt, OPENAI_CODEX_BASE_URL as l, DEFAULT_OPENAI_CODEX_SETTINGS as lt, OPENAI_CODEX_IMAGE_GENERATION_URL as m, resolveOpenAICodexProxyUrl as mt, OPENAI_CODEX_SETTINGS_NS as n, detectOpenAICodexProxies as nt, OPENAI_CODEX_HISTORY_BACKUP_SUFFIX as o, DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS as ot, mapOpenAICodexSearchResponse as p, normalizeOpenAICodexProxyUrl as pt, evaluateCompatibility as q, apply as r, listOpenAICodexProxyCandidates as rt, OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT as s, DEFAULT_OPENAI_CODEX_SEARCH_MODE as st, Config as t, OpenAICodexProxyManager as tt, OPENAI_CODEX_SEARCH_PROVIDER as u, OPENAI_CODEX_SETTINGS_NAMESPACE as ut, OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH as v, OPENAI_CODEX_FAST_MODE_MAX_SESSIONS as vt, OpenAICodexTransportError as w, parseOpenAICodexUsage as wt, OPENAI_CODEX_TRANSPORT_ERROR_CODES as x, OpenAICodexTrustedOriginsStore as xt, OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS as y, OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH as yt, COMPATIBILITY_SCHEMA_VERSION as z };
4420
+ export { OPENAI_CODEX_PROXY_PROBE_TIMEOUT_MS as $, openAICodexConflictMessage as A, openAICodexModelCatalog as At, DSH_PLUGIN_API_PACKAGES as B, OpenAICodexTransport as C, normalizeTrustedOrigin as Ct, IMAGE_GENERATE_TOOL_NAME as D, loginOpenAICodex as Dt, VIEW_IMAGE_TOOL_NAME as E, readOpenAICodexRateLimits as Et, parseOpenAICodexUpdateResult as F, assessCompatibility as G, SUPPORTED_DSH_PLUGIN_API_VERSION as H, parseOpenAICodexVersion as I, readInstalledPackageVersion as J, detectCompatibility as K, COMPATIBILITY_CONTRACT as L, OPENAI_CODEX_UPDATE_PATH as M, OPENAI_CODEX_PROVIDER as Mt, checkForOpenAICodexUpdate as N, OpenAICodexCredentialStore as Nt, assertNoOpenAICodexProviderConflict as O, logoutOpenAICodex as Ot, compareOpenAICodexVersions as P, openAICodexAuthPath as Pt, OPENAI_CODEX_PROXY_CANDIDATE_LIMIT as Q, COMPATIBILITY_PACKAGES as R, OPENAI_CODEX_TRANSPORT_SERVICE as S, OpenAICodexTrustedOriginsStore as St, isOpenAICodexTransportError as T, parseOpenAICodexUsage as Tt, SUPPORTED_NODE_RANGE as U, PI_AI_PACKAGE as V, SUPPORTED_PI_AI_RANGE as W, OPENAI_CODEX_PROXY_TEST_PATH as X, OPENAI_CODEX_PROXY_DETECT_PATH as Y, OPENAI_CODEX_LOCAL_PROXY_CANDIDATES as Z, OPENAI_CODEX_IMAGE_MAX_RESPONSE_BYTES as _, OPENAI_CODEX_FAST_MODE_PATH as _t, name as a, DEFAULT_OPENAI_CODEX_SEARCH_CONTEXT_SIZE as at, OPENAI_CODEX_TRANSPORT_API_VERSION as b, OPENAI_CODEX_FAST_MODE_MAX_SESSION_ID_LENGTH as bt, migrateOpenAICodexSearchHistory as c, DEFAULT_OPENAI_CODEX_SEARCH_MODEL as ct, OPENAI_CODEX_SEARCH_URL as d, decodeOpenAICodexSettings as dt, OPENAI_CODEX_PROXY_PROBE_URL as et, OpenAICodexSearchProvider as f, isValidOpenAICodexContextWindowOverrides as ft, OPENAI_CODEX_IMAGE_MAX_ERROR_BYTES as g, resolveOpenAICodexSettings as gt, OPENAI_CODEX_IMAGE_MAX_COUNT as h, resolveOpenAICodexProxyUrl as ht, inject as i, DEFAULT_OPENAI_CODEX_PROXY_URL as it, CODEX_CONNECT_VERSION as j, OPENAI_CODEX_AUTH_FILENAME as jt, diagnoseOpenAICodex as k, openAICodexAuthStatus as kt, OPENAI_CODEX_BASE_URL as l, DEFAULT_OPENAI_CODEX_SETTINGS as lt, OPENAI_CODEX_IMAGE_GENERATION_URL as m, normalizeOpenAICodexProxyUrl as mt, OPENAI_CODEX_SETTINGS_NS as n, detectOpenAICodexProxies as nt, OPENAI_CODEX_HISTORY_BACKUP_SUFFIX as o, DEFAULT_OPENAI_CODEX_SEARCH_MAX_OUTPUT_TOKENS as ot, mapOpenAICodexSearchResponse as p, isValidOpenAICodexProxyUrl as pt, evaluateCompatibility as q, apply as r, listOpenAICodexProxyCandidates as rt, OPENAI_CODEX_SEARCH_MODEL_REQUEST_EVENT as s, DEFAULT_OPENAI_CODEX_SEARCH_MODE as st, Config as t, OpenAICodexProxyManager as tt, OPENAI_CODEX_SEARCH_PROVIDER as u, OPENAI_CODEX_SETTINGS_NAMESPACE as ut, OPENAI_CODEX_IMAGE_PROMPT_MAX_LENGTH as v, FastModeRegistry as vt, OpenAICodexTransportError as w, OPENAI_CODEX_USAGE_URL as wt, OPENAI_CODEX_TRANSPORT_ERROR_CODES as x, isFastModeSessionId as xt, OPENAI_CODEX_IMAGE_REQUEST_TIMEOUT_MS as y, OPENAI_CODEX_FAST_MODE_MAX_SESSIONS as yt, COMPATIBILITY_SCHEMA_VERSION as z };