@tnnevol/dsh-codex-auth 0.1.7-rc.2 → 0.1.7-rc.2.2

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
@@ -9,7 +9,7 @@ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
9
9
  /** Read-only Codex account quota information for the local settings card. */
10
10
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
11
11
  const CODEX_USAGE_TIMEOUT_MS = 1e4;
12
- function record$1(value) {
12
+ function record$2(value) {
13
13
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
14
14
  }
15
15
  function number$1(value) {
@@ -29,7 +29,7 @@ function percentage(value) {
29
29
  return Math.max(0, Math.min(100, value));
30
30
  }
31
31
  function window(value) {
32
- const source = record$1(value);
32
+ const source = record$2(value);
33
33
  if (source === void 0) return void 0;
34
34
  const result = {};
35
35
  const usedPercent = number$1(source["used_percent"]);
@@ -45,7 +45,7 @@ function window(value) {
45
45
  return Object.keys(result).length > 0 ? result : void 0;
46
46
  }
47
47
  function credits(value) {
48
- const source = record$1(value);
48
+ const source = record$2(value);
49
49
  if (source === void 0) return void 0;
50
50
  const result = {};
51
51
  const hasCredits = boolean(source["has_credits"]);
@@ -58,9 +58,9 @@ function credits(value) {
58
58
  }
59
59
  /** Normalize the evolving private WHAM response into a small UI-safe shape. */
60
60
  function normalizeCodexUsagePayload(value) {
61
- const source = record$1(value);
61
+ const source = record$2(value);
62
62
  if (source === void 0) throw new Error("Codex usage response was not an object");
63
- const rateLimit = record$1(source["rate_limit"]);
63
+ const rateLimit = record$2(source["rate_limit"]);
64
64
  const result = {};
65
65
  const planType = string$1(source["plan_type"]);
66
66
  const allowed = boolean(rateLimit?.["allowed"]);
@@ -171,14 +171,14 @@ const LLM_PI_AI_THINKING_LEVELS = /* @__PURE__ */ new Set([
171
171
  ]);
172
172
  /** Rows the account endpoint returns for machinery, not human selection. */
173
173
  const INTERNAL_CODEX_MODEL_IDS = /* @__PURE__ */ new Set(["codex-auto-review", "gpt-reserve"]);
174
- function record(value) {
174
+ function record$1(value) {
175
175
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
176
176
  }
177
177
  function accessToken(auth) {
178
178
  return typeof auth?.apiKey === "string" && auth.apiKey.length > 0 ? auth.apiKey : void 0;
179
179
  }
180
180
  function accountId(credential) {
181
- const value = record(credential)?.accountId;
181
+ const value = record$1(credential)?.accountId;
182
182
  return typeof value === "string" && value.length > 0 ? value : void 0;
183
183
  }
184
184
  function string(value) {
@@ -310,7 +310,7 @@ async function fetchCodexModelCatalog(store, fetchImpl = fetch) {
310
310
  signal: controller.signal
311
311
  });
312
312
  if (!response.ok) throw new Error(`Codex model catalog request failed with status ${String(response.status)}`);
313
- const parsed = record(await response.json());
313
+ const parsed = record$1(await response.json());
314
314
  if (parsed === void 0 || !Array.isArray(parsed.models)) throw new Error("Codex model catalog response was not an object with a models array");
315
315
  return { models: parsed.models };
316
316
  } finally {
@@ -820,6 +820,205 @@ var CodexCredentialMirror = class {
820
820
  }
821
821
  };
822
822
  //#endregion
823
+ //#region src/contracts/model-capabilities.ts
824
+ /** 深思等级的共同前缀:`minimal..max`,各模型的真实线路值。 */
825
+ const FULL_LEVELS = {
826
+ minimal: "low",
827
+ low: "low",
828
+ medium: "medium",
829
+ high: "high",
830
+ xhigh: "xhigh",
831
+ max: "max"
832
+ };
833
+ const CONTEXT_WINDOW = 272e3;
834
+ const MAX_TOKENS = 128e3;
835
+ /** 七个模型当前都是图文输入;单独抽出来避免每行重复书写造成漂移。 */
836
+ const TEXT_AND_IMAGE = ["text", "image"];
837
+ /**
838
+ * Codex Auth 声明并保证的模型集合,顺序即补丁中的书写顺序。
839
+ *
840
+ * `gpt-6-astra` 的目录条目把 `off` 映射为 `null`,即该模型确实没有 `off`;
841
+ * `gpt-5.5` 的目录不提供 `max`,因此它的等级到 `xhigh` 为止。
842
+ */
843
+ const CODEX_MODEL_CAPABILITIES = [
844
+ {
845
+ id: "gpt-6-astra",
846
+ name: "GPT-6-Astra",
847
+ contextWindow: CONTEXT_WINDOW,
848
+ maxTokens: MAX_TOKENS,
849
+ input: TEXT_AND_IMAGE,
850
+ reasoningEfforts: { ...FULL_LEVELS }
851
+ },
852
+ {
853
+ id: "gpt-6-sol",
854
+ name: "GPT-6-Sol",
855
+ contextWindow: CONTEXT_WINDOW,
856
+ maxTokens: MAX_TOKENS,
857
+ input: TEXT_AND_IMAGE,
858
+ reasoningEfforts: {
859
+ off: "none",
860
+ ...FULL_LEVELS
861
+ }
862
+ },
863
+ {
864
+ id: "gpt-6-luna",
865
+ name: "GPT-6-Luna",
866
+ contextWindow: CONTEXT_WINDOW,
867
+ maxTokens: MAX_TOKENS,
868
+ input: TEXT_AND_IMAGE,
869
+ reasoningEfforts: {
870
+ off: "none",
871
+ ...FULL_LEVELS
872
+ }
873
+ },
874
+ {
875
+ id: "gpt-5.6-sol",
876
+ name: "GPT-5.6 Sol",
877
+ contextWindow: CONTEXT_WINDOW,
878
+ maxTokens: MAX_TOKENS,
879
+ input: TEXT_AND_IMAGE,
880
+ reasoningEfforts: {
881
+ off: null,
882
+ ...FULL_LEVELS
883
+ }
884
+ },
885
+ {
886
+ id: "gpt-5.6-terra",
887
+ name: "GPT-5.6 Terra",
888
+ contextWindow: CONTEXT_WINDOW,
889
+ maxTokens: MAX_TOKENS,
890
+ input: TEXT_AND_IMAGE,
891
+ reasoningEfforts: {
892
+ off: null,
893
+ ...FULL_LEVELS
894
+ }
895
+ },
896
+ {
897
+ id: "gpt-5.6-luna",
898
+ name: "GPT-5.6 Luna",
899
+ contextWindow: CONTEXT_WINDOW,
900
+ maxTokens: MAX_TOKENS,
901
+ input: TEXT_AND_IMAGE,
902
+ reasoningEfforts: {
903
+ off: null,
904
+ ...FULL_LEVELS
905
+ }
906
+ },
907
+ {
908
+ id: "gpt-5.5",
909
+ name: "GPT-5.5",
910
+ contextWindow: CONTEXT_WINDOW,
911
+ maxTokens: MAX_TOKENS,
912
+ input: TEXT_AND_IMAGE,
913
+ reasoningEfforts: {
914
+ off: null,
915
+ minimal: "low",
916
+ low: "low",
917
+ medium: "medium",
918
+ high: "high",
919
+ xhigh: "xhigh"
920
+ }
921
+ }
922
+ ];
923
+ /** 按 id 取能力;未知 id 返回 `undefined`(调用方决定是否省略字段而不是猜)。 */
924
+ function codexModelCapability(id) {
925
+ return CODEX_MODEL_CAPABILITIES.find((model) => model.id === id);
926
+ }
927
+ //#endregion
928
+ //#region src/host/model-capability-heal.ts
929
+ /**
930
+ * Repair a Codex model list that lost its capability fields.
931
+ *
932
+ * The official Models page writes a provider's whole `models` array into the
933
+ * profile's patch document. That array *replaces* the shipped baseline
934
+ * wholesale (DSH merges objects field by field but replaces arrays), so any
935
+ * row that only carries `id`/`name` hides the route's real context window,
936
+ * output cap and thinking levels from every selector — and the model picker
937
+ * shows grey `256K`/`32K` placeholders while the composer offers no reasoning
938
+ * levels. Rows like that were produced by the official candidate picker while
939
+ * its Codex candidates carried no capabilities; the bridge that feeds it is
940
+ * fixed, but the rows it already wrote are still in user profiles.
941
+ *
942
+ * This repair is deliberately **additive**:
943
+ *
944
+ * - it only fills a field that is *absent* — a value the user set, even a
945
+ * custom one, is never overwritten, so a deliberate `input: [text]` survives;
946
+ * - it never adds, removes, renames or reorders models, so a user's own
947
+ * selection, order and display names are preserved;
948
+ * - it only touches models this plugin's contract describes, so an id from a
949
+ * newer account catalog is left exactly as the user has it;
950
+ * - it returns `undefined` when nothing needs filling, which makes the caller
951
+ * a no-op and the whole operation idempotent — the write it triggers cannot
952
+ * echo back into another write.
953
+ *
954
+ * @module dsh-codex-auth/model-capability-heal
955
+ */
956
+ /** The settings namespace owning the Codex route. */
957
+ const CODEX_SETTINGS_NAMESPACE = "llm-pi-ai";
958
+ /** Capability fields the repair may fill; all other fields are user-owned. */
959
+ const FILLABLE = [
960
+ "contextWindow",
961
+ "maxTokens",
962
+ "input",
963
+ "reasoningEfforts"
964
+ ];
965
+ function record(value) {
966
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
967
+ }
968
+ /**
969
+ * Plan the capability repair for one provider's `models` array.
970
+ *
971
+ * @param models - the row list as stored in the settings document.
972
+ * @returns the repaired rows plus the ids that were filled, or `undefined`
973
+ * when every described model already states its capabilities.
974
+ */
975
+ function planCodexCapabilityRepair(models) {
976
+ if (!Array.isArray(models)) return void 0;
977
+ const filled = [];
978
+ let changed = false;
979
+ const repaired = models.map((row) => {
980
+ const model = record(row);
981
+ const id = model?.id;
982
+ if (model === void 0 || typeof id !== "string") return row;
983
+ const capability = codexModelCapability(id);
984
+ if (capability === void 0) return row;
985
+ if (FILLABLE.filter((field) => model[field] === void 0).length === 0) return row;
986
+ const add = {};
987
+ if (model.contextWindow === void 0) add.contextWindow = capability.contextWindow;
988
+ if (model.maxTokens === void 0) add.maxTokens = capability.maxTokens;
989
+ if (model.input === void 0) add.input = [...capability.input];
990
+ if (model.reasoningEfforts === void 0) add.reasoningEfforts = { ...capability.reasoningEfforts };
991
+ changed = true;
992
+ filled.push(id);
993
+ return {
994
+ ...model,
995
+ ...add
996
+ };
997
+ });
998
+ return changed ? {
999
+ models: repaired,
1000
+ filled
1001
+ } : void 0;
1002
+ }
1003
+ /**
1004
+ * Fill in missing Codex capabilities in the stored settings document.
1005
+ *
1006
+ * A missing namespace entry, a route that states its capabilities already, or
1007
+ * any error is reported as "nothing to do": this runs at startup beside the
1008
+ * credential mirror, so it must never fail activation or block boot. The
1009
+ * failure mode it guards against is cosmetic-plus-silent (degraded selectors),
1010
+ * never data loss.
1011
+ *
1012
+ * @param settings - the Host settings service.
1013
+ * @returns the ids that were filled, empty when no write was needed.
1014
+ */
1015
+ async function healCodexModelCapabilities(settings) {
1016
+ const repair = planCodexCapabilityRepair(record(record(record(settings.describe().find((row) => row.ns === CODEX_SETTINGS_NAMESPACE)?.value)?.providers)?.[CODEX_PROVIDER])?.models);
1017
+ if (repair === void 0) return [];
1018
+ await settings.update(CODEX_SETTINGS_NAMESPACE, { providers: { [CODEX_PROVIDER]: { models: repair.models } } });
1019
+ return repair.filled;
1020
+ }
1021
+ //#endregion
823
1022
  //#region src/host/adapter.ts
824
1023
  /** OpenAI Codex adapter assembled from dsh's public pi-ai extension seam. */
825
1024
  /** Keep the Codex stream open while the provider is still producing output. */
@@ -892,6 +1091,39 @@ const inject = [
892
1091
  "agentDefaultModel",
893
1092
  "llm"
894
1093
  ];
1094
+ /**
1095
+ * One-shot capability repair, run once the settings entries are addressable.
1096
+ *
1097
+ * Activation order between this plugin and `llm-pi-ai` is not guaranteed, so
1098
+ * the descriptor may not exist yet on the first attempt. The retry listens for
1099
+ * the settings service's own update notification and disconnects immediately
1100
+ * after the first attempt that can see the namespace: this has to stay a
1101
+ * one-shot, because rewriting a document while the user is editing it would
1102
+ * make their save lose its revision check.
1103
+ */
1104
+ function scheduleCodexCapabilityHeal(ctx) {
1105
+ let settled = false;
1106
+ const attempt = () => {
1107
+ const settings = ctx.settings;
1108
+ if (!settings.describe().some((row) => row.ns === "llm-pi-ai")) return false;
1109
+ settled = true;
1110
+ healCodexModelCapabilities(settings).then((filled) => {
1111
+ if (filled.length > 0) ctx.logger.info(`dsh-codex-auth: filled missing Codex model capabilities for ${filled.join(", ")}`);
1112
+ }).catch((error) => {
1113
+ ctx.logger.warn("dsh-codex-auth: unable to fill Codex model capabilities", error);
1114
+ });
1115
+ return true;
1116
+ };
1117
+ ctx.effect(() => {
1118
+ if (attempt()) return () => {};
1119
+ const off = ctx.on("settings/document-updated", () => {
1120
+ if (!settled) attempt();
1121
+ });
1122
+ return () => {
1123
+ off();
1124
+ };
1125
+ }, "dsh-codex-auth: Codex model capability repair");
1126
+ }
895
1127
  function apply(ctx) {
896
1128
  const store = new CodexCredentialStore();
897
1129
  const mirror = new CodexCredentialMirror(ctx.credentials, store);
@@ -910,6 +1142,7 @@ function apply(ctx) {
910
1142
  registerCodexAuthRoutes(ctx, store, mirror);
911
1143
  registerCodexModelRefreshRoute(ctx, store, { update: (ns, patch) => ctx.settings.update(ns, patch) });
912
1144
  registerCodexGlobalModelRoute(ctx, ctx.agentDefaultModel, ctx.llm);
1145
+ scheduleCodexCapabilityHeal(ctx);
913
1146
  }
914
1147
  //#endregion
915
1148
  export { CODEX_API_KEY_ENV, CODEX_API_KEY_REF, CODEX_AUTH_LOGIN_PATH, CODEX_AUTH_LOGOUT_PATH, CODEX_AUTH_STATUS_PATH, CODEX_GLOBAL_MODEL_PATH, CODEX_STREAM_IDLE_TIMEOUT_MS, CodexCredentialMirror, CodexCredentialStore, CodexUsageService, CodexWebAuth, apply, codexAuthStatus, createCodexAdapter, inject, loginCodex, logoutCodex, name, normalizeCodexUsagePayload, registerCodexAuthRoutes, trustedRequest };