dsh-codex-subscription 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -11,9 +11,13 @@ import { promisify } from "node:util";
11
11
  import { HttpsProxyAgent } from "https-proxy-agent";
12
12
  import { openaiCodexProvider as createOpenAICodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
13
13
  import { createModels } from "@earendil-works/pi-ai";
14
- import { randomUUID } from "node:crypto";
14
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
15
15
  import { WebError } from "@deepseek-ai/dsh-web";
16
16
  import { defineTool } from "@deepseek-ai/dsh-tools";
17
+ import { constants } from "node:fs";
18
+ import { lstat, mkdir, open, readFile, rename, rm } from "node:fs/promises";
19
+ import { dirname, join, resolve } from "node:path";
20
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
17
21
  //#region src/credential-store.js
18
22
  const PROVIDER$1 = "openai-codex";
19
23
  const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
@@ -725,12 +729,20 @@ const SETTINGS_NAMESPACE = "codex-subscription";
725
729
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
726
730
  const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
727
731
  const QUICK_QUOTA_MODE_PERCENT = "percent";
732
+ const QUICK_QUOTA_MODE_FORECAST = "forecast";
728
733
  const SEARCH_PROVIDER_FIELD = "searchProvider";
734
+ const SEARCH_PROVIDER_AUTO = "auto";
729
735
  const SEARCH_PROVIDER_CODEX = "codex";
736
+ const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
730
737
  const SPEED_MODE_FIELD = "speedMode";
731
738
  const SPEED_MODE_STANDARD = "standard";
732
739
  const SPEED_MODE_FAST = "fast";
733
740
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
741
+ const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
742
+ const OUTPUT_VERBOSITY_DEFAULT = "default";
743
+ const OUTPUT_VERBOSITY_MEDIUM = "medium";
744
+ const OUTPUT_VERBOSITY_HIGH = "high";
745
+ const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
734
746
  const CONTEXT_MODE_FIELD = "contextMode";
735
747
  const CONTEXT_MODE_STANDARD = "standard";
736
748
  const CONTEXT_MODE_EXTENDED = "extended";
@@ -758,6 +770,12 @@ const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
758
770
  "gpt-5.5": 272e3,
759
771
  "gpt-5.6": 272e3
760
772
  });
773
+ const normalizeOutputVerbosity = (value) => [
774
+ "default",
775
+ "low",
776
+ "medium",
777
+ "high"
778
+ ].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
761
779
  const normalizeContextMode = (value) => [
762
780
  "standard",
763
781
  "extended",
@@ -803,7 +821,8 @@ function contextModelGroups(models) {
803
821
  const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
804
822
  "off",
805
823
  "percent",
806
- "bar"
824
+ "bar",
825
+ "forecast"
807
826
  ].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
808
827
  const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
809
828
  //#endregion
@@ -829,7 +848,7 @@ const EXTENDED_CONTEXT_WINDOWS = Object.freeze({
829
848
  "gpt-5.6-sol": 1e6,
830
849
  "gpt-5.6-terra": 1e6
831
850
  });
832
- function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, runNetwork = (_area, operation) => operation() } = {}) {
851
+ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, runNetwork = (_area, operation) => operation() } = {}) {
833
852
  const provider = createOpenAICodexProvider();
834
853
  const requestToken = Object.freeze({
835
854
  name: "DSH-managed Codex OAuth request token",
@@ -842,25 +861,40 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
842
861
  };
843
862
  }
844
863
  });
845
- const withSpeed = (model, options = {}) => {
846
- if (resolveSpeedMode() !== "fast" || !supportsCodexFastMode(model?.id)) return options;
864
+ const modelMetadata = (model) => catalog?.metadata(model?.id);
865
+ const supportsVerbosity = (model) => modelMetadata(model)?.supportVerbosity ?? model?.id !== "gpt-5.3-codex-spark";
866
+ const withPreferences = (model, options = {}) => {
867
+ const metadata = modelMetadata(model);
868
+ const requestedVerbosity = resolveOutputVerbosity();
869
+ const textVerbosity = supportsVerbosity(model) ? requestedVerbosity === "default" ? metadata?.defaultVerbosity ?? "medium" : requestedVerbosity : void 0;
870
+ const fast = resolveSpeedMode() === "fast" && (metadata?.supportsFast ?? supportsCodexFastMode(model?.id));
847
871
  const onPayload = options.onPayload;
848
872
  return {
849
873
  ...options,
850
- serviceTier: FAST_SERVICE_TIER,
874
+ ...textVerbosity === void 0 ? {} : { textVerbosity },
875
+ ...fast ? { serviceTier: FAST_SERVICE_TIER } : {},
851
876
  async onPayload(payload, requestModel) {
852
- const fastPayload = {
877
+ const preferred = {
853
878
  ...payload,
854
- service_tier: FAST_SERVICE_TIER
879
+ ...textVerbosity === void 0 ? {} : { text: {
880
+ ...payload.text ?? {},
881
+ verbosity: textVerbosity
882
+ } },
883
+ ...fast ? { service_tier: FAST_SERVICE_TIER } : {}
855
884
  };
885
+ const next = await onPayload?.(preferred, requestModel);
856
886
  return {
857
- ...await onPayload?.(fastPayload, requestModel) ?? fastPayload,
858
- service_tier: FAST_SERVICE_TIER
887
+ ...next ?? preferred,
888
+ ...textVerbosity === void 0 ? {} : { text: {
889
+ ...(next ?? preferred).text ?? {},
890
+ verbosity: textVerbosity
891
+ } },
892
+ ...fast ? { service_tier: FAST_SERVICE_TIER } : {}
859
893
  };
860
894
  }
861
895
  };
862
896
  };
863
- const getModels = () => provider.getModels().map((model) => {
897
+ const getModels = () => (catalog?.getModels(provider.getModels()) ?? provider.getModels()).map((model) => {
864
898
  const maximum = EXTENDED_CONTEXT_WINDOWS[model.id];
865
899
  const mode = resolveContextMode();
866
900
  if (maximum === void 0 || !["extended", "custom"].includes(mode)) return model;
@@ -896,17 +930,147 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
896
930
  apiKey: requestToken
897
931
  }),
898
932
  getModels,
899
- stream: (model, context, options) => networkIterable(() => provider.stream(model, context, withSpeed(model, options))),
900
- streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context, withSpeed(model, options)))
933
+ stream: (model, context, options) => networkIterable(() => provider.stream(model, context, withPreferences(model, options))),
934
+ streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context, withPreferences(model, options)))
901
935
  });
902
936
  }
903
937
  //#endregion
904
938
  //#region src/version.js
905
- const PACKAGE_VERSION = "1.9.0";
939
+ const PACKAGE_VERSION = "1.11.0";
906
940
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
907
941
  //#endregion
942
+ //#region src/model-catalog.js
943
+ const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${encodeURIComponent(PACKAGE_VERSION)}`;
944
+ const LEVELS = [
945
+ "off",
946
+ "minimal",
947
+ "low",
948
+ "medium",
949
+ "high",
950
+ "xhigh",
951
+ "max"
952
+ ];
953
+ const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
954
+ const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
955
+ const positiveInteger$1 = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
956
+ function reasoningMap(levels) {
957
+ const supported = new Set((Array.isArray(levels) ? levels : []).map((level) => nonEmpty$2(record$4(level) ? level.effort : void 0)).filter(Boolean));
958
+ const map = Object.fromEntries(LEVELS.map((level) => [level, null]));
959
+ if (supported.has("none")) map.off = "none";
960
+ for (const level of LEVELS.slice(1)) if (supported.has(level)) map[level] = level;
961
+ return map;
962
+ }
963
+ function visibleModel(value) {
964
+ if (!record$4(value)) return void 0;
965
+ const id = nonEmpty$2(value.slug);
966
+ if (id === void 0 || value.supported_in_api !== true || value.visibility !== "list") return void 0;
967
+ const supported = Array.isArray(value.supported_reasoning_levels) ? value.supported_reasoning_levels : [];
968
+ const input = Array.isArray(value.input_modalities) ? value.input_modalities.filter((item) => ["text", "image"].includes(item)) : ["text", "image"];
969
+ return {
970
+ id,
971
+ name: nonEmpty$2(value.display_name) ?? id,
972
+ description: nonEmpty$2(value.description),
973
+ priority: Number.isFinite(value.priority) ? value.priority : 0,
974
+ input: input.length > 0 ? input : ["text"],
975
+ contextWindow: positiveInteger$1(value.context_window) ?? positiveInteger$1(value.max_context_window),
976
+ reasoning: supported.length > 0,
977
+ thinkingLevelMap: reasoningMap(supported),
978
+ supportVerbosity: value.support_verbosity === true,
979
+ defaultVerbosity: [
980
+ "low",
981
+ "medium",
982
+ "high"
983
+ ].includes(value.default_verbosity) ? value.default_verbosity : void 0,
984
+ supportsFast: [...Array.isArray(value.additional_speed_tiers) ? value.additional_speed_tiers : [], ...Array.isArray(value.service_tiers) ? value.service_tiers.map((tier) => tier?.id) : []].some((tier) => tier === "fast" || tier === "priority")
985
+ };
986
+ }
987
+ function parseOfficialModelCatalog(value) {
988
+ if (!record$4(value) || !Array.isArray(value.models)) throw new Error("Codex returned a malformed model catalog");
989
+ const seen = /* @__PURE__ */ new Set();
990
+ return value.models.map(visibleModel).filter((model) => model !== void 0 && !seen.has(model.id) && seen.add(model.id)).sort((left, right) => right.priority - left.priority);
991
+ }
992
+ function mergeModel(baseModels, remote) {
993
+ const base = baseModels.find((model) => model.id === remote.id) ?? baseModels.find((model) => model.id !== "gpt-5.3-codex-spark") ?? baseModels[0];
994
+ if (base === void 0) return void 0;
995
+ return {
996
+ ...base,
997
+ id: remote.id,
998
+ name: remote.name,
999
+ input: remote.input,
1000
+ reasoning: remote.reasoning,
1001
+ thinkingLevelMap: remote.thinkingLevelMap,
1002
+ ...remote.contextWindow === void 0 ? {} : { contextWindow: remote.contextWindow },
1003
+ ...base.id === remote.id ? {} : { cost: {
1004
+ input: 0,
1005
+ output: 0,
1006
+ cacheRead: 0,
1007
+ cacheWrite: 0
1008
+ } }
1009
+ };
1010
+ }
1011
+ function createOfficialModelCatalog(options = {}) {
1012
+ const fetchCatalog = options.fetch ?? fetch;
1013
+ let models;
1014
+ let metadata = /* @__PURE__ */ new Map();
1015
+ let etag;
1016
+ let revision = 0;
1017
+ let refreshing;
1018
+ const refresh = async ({ signal } = {}) => {
1019
+ if (refreshing !== void 0) return refreshing;
1020
+ refreshing = (async () => {
1021
+ const auth = await options.getAuth({ signal });
1022
+ const credential = await options.readCredential({ signal });
1023
+ const access = auth?.auth?.apiKey;
1024
+ const accountId = credential?.type === "oauth" ? credential.accountId : void 0;
1025
+ if (typeof access !== "string" || access.length === 0 || typeof accountId !== "string" || accountId.length === 0) return false;
1026
+ const headers = {
1027
+ authorization: `Bearer ${access}`,
1028
+ "chatgpt-account-id": accountId,
1029
+ accept: "application/json",
1030
+ originator: "pi",
1031
+ "user-agent": USER_AGENT,
1032
+ ...etag === void 0 ? {} : { "if-none-match": etag }
1033
+ };
1034
+ const response = await fetchCatalog(CODEX_MODELS_URL, {
1035
+ method: "GET",
1036
+ redirect: "error",
1037
+ headers,
1038
+ signal
1039
+ });
1040
+ if (response.status === 304) return false;
1041
+ if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
1042
+ const remote = parseOfficialModelCatalog(await response.json());
1043
+ if (remote.length === 0) throw new Error("Codex returned an empty model catalog");
1044
+ const baseModels = options.baseModels();
1045
+ const next = remote.map((model) => mergeModel(baseModels, model)).filter(Boolean);
1046
+ if (next.length === 0) throw new Error("Codex model catalog has no compatible models");
1047
+ models = next;
1048
+ metadata = new Map(remote.map((model) => [model.id, model]));
1049
+ etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
1050
+ revision += 1;
1051
+ return true;
1052
+ })().finally(() => {
1053
+ refreshing = void 0;
1054
+ });
1055
+ return refreshing;
1056
+ };
1057
+ return Object.freeze({
1058
+ refresh,
1059
+ getModels: (fallback) => models ?? fallback,
1060
+ metadata: (modelId) => metadata.get(modelId),
1061
+ revision: () => revision,
1062
+ clear() {
1063
+ models = void 0;
1064
+ metadata = /* @__PURE__ */ new Map();
1065
+ etag = void 0;
1066
+ revision += 1;
1067
+ }
1068
+ });
1069
+ }
1070
+ //#endregion
908
1071
  //#region src/codex-search.js
909
1072
  const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
1073
+ const CODEX_AUTO_SEARCH_PROVIDER_ID = "codex-subscription-auto";
910
1074
  const CODEX_SEARCH_URL = "https://chatgpt.com/backend-api/codex/alpha/search";
911
1075
  const DEFAULT_MODEL = "gpt-5.6-luna";
912
1076
  const MAX_OUTPUT_TOKENS = 4096;
@@ -1021,6 +1185,55 @@ function createCodexSearchProvider(options) {
1021
1185
  }
1022
1186
  });
1023
1187
  }
1188
+ /** Route each request by its initiating model without changing the user's explicit overrides. */
1189
+ function createCodexAutoSearchProvider(options) {
1190
+ return Object.freeze({
1191
+ id: CODEX_AUTO_SEARCH_PROVIDER_ID,
1192
+ available: () => true,
1193
+ async search(request, signal) {
1194
+ if (options.resolveModelProvider?.() === "openai-codex") return options.codex.search(request, signal);
1195
+ const provider = options.resolveDshProvider?.();
1196
+ if (provider === void 0 || provider.id === "codex-subscription-auto" || provider.id === "codex-subscription" || provider.available() !== true) throw new WebError("DSH default search is unavailable", "WEB_PROVIDER_UNAVAILABLE");
1197
+ return provider.search(request, signal);
1198
+ }
1199
+ });
1200
+ }
1201
+ const ORIGINAL_IMAGE_CHUNK_BYTES = 4 * 1024 * 1024;
1202
+ const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
1203
+ const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
1204
+ function decodeOriginalImageRef(value) {
1205
+ if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(value.assetId) || value.mediaType !== "image/png" || !positiveInteger(value.bytes) || value.bytes > 48 * 1024 * 1024 || !positiveInteger(value.width) || !positiveInteger(value.height) || typeof value.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.name) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.sha256)) return void 0;
1206
+ return {
1207
+ assetId: value.assetId,
1208
+ mediaType: value.mediaType,
1209
+ bytes: value.bytes,
1210
+ width: value.width,
1211
+ height: value.height,
1212
+ name: value.name,
1213
+ sha256: value.sha256
1214
+ };
1215
+ }
1216
+ function decodeImagePresentation(value) {
1217
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
1218
+ const original = decodeOriginalImageRef(value.original);
1219
+ return original === void 0 ? void 0 : { original };
1220
+ }
1221
+ function originalImageRefsEqual(left, right) {
1222
+ const a = decodeOriginalImageRef(left);
1223
+ const b = decodeOriginalImageRef(right);
1224
+ return a !== void 0 && b !== void 0 && a.assetId === b.assetId && a.mediaType === b.mediaType && a.bytes === b.bytes && a.width === b.width && a.height === b.height && a.name === b.name && a.sha256 === b.sha256;
1225
+ }
1226
+ /** Resolve only an exact original reference copied into a DSH fork prefix. */
1227
+ function inheritedOriginalImageRef(session, assetId) {
1228
+ const parentSession = session?.header?.parentSession;
1229
+ const seedLength = session?.header?.seedLength;
1230
+ if (typeof parentSession !== "string" || parentSession.length === 0 || !Number.isSafeInteger(seedLength) || seedLength < 0 || !Array.isArray(session?.events) || !ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) return void 0;
1231
+ for (const event of session.events) {
1232
+ if (!Number.isSafeInteger(event?.seq) || event.seq < 0 || event.seq >= seedLength || event.type !== "tool/result") continue;
1233
+ const original = decodeImagePresentation(event.data?.meta)?.original;
1234
+ if (original?.assetId === assetId) return original;
1235
+ }
1236
+ }
1024
1237
  //#endregion
1025
1238
  //#region src/codex-images.js
1026
1239
  const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
@@ -1029,6 +1242,17 @@ const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits
1029
1242
  const IMAGE_MODEL = "gpt-image-2";
1030
1243
  const MAX_REFERENCE_IMAGES = 5;
1031
1244
  const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
1245
+ const IMAGE_QUALITIES = /* @__PURE__ */ new Set([
1246
+ "auto",
1247
+ "low",
1248
+ "medium",
1249
+ "high"
1250
+ ]);
1251
+ const IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
1252
+ "auto",
1253
+ "transparent",
1254
+ "opaque"
1255
+ ]);
1032
1256
  const PNG_SIGNATURE = Buffer.from([
1033
1257
  137,
1034
1258
  80,
@@ -1041,6 +1265,27 @@ const PNG_SIGNATURE = Buffer.from([
1041
1265
  ]);
1042
1266
  const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1043
1267
  const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1268
+ function normalizeImageOptions(args) {
1269
+ const quality = nonEmpty(args?.quality) ?? "auto";
1270
+ const background = nonEmpty(args?.background) ?? "auto";
1271
+ const size = nonEmpty(args?.size) ?? "auto";
1272
+ if (!IMAGE_QUALITIES.has(quality)) throw new Error("quality must be auto, low, medium, or high");
1273
+ if (!IMAGE_BACKGROUNDS.has(background)) throw new Error("background must be auto, transparent, or opaque");
1274
+ if (size !== "auto") {
1275
+ const match = /^(\d+)x(\d+)$/u.exec(size);
1276
+ const width = Number(match?.[1]);
1277
+ const height = Number(match?.[2]);
1278
+ const short = Math.min(width, height);
1279
+ const long = Math.max(width, height);
1280
+ const pixels = width * height;
1281
+ if (match === null || width % 16 !== 0 || height % 16 !== 0 || long > 3840 || long > short * 3 || pixels < 655360 || pixels > 8294400) throw new Error("size must be auto or a valid GPT Image 2 widthxheight resolution");
1282
+ }
1283
+ return {
1284
+ quality,
1285
+ background,
1286
+ size
1287
+ };
1288
+ }
1044
1289
  function encodedLimit(decodedBytes) {
1045
1290
  return Math.ceil(decodedBytes / 3) * 4;
1046
1291
  }
@@ -1157,6 +1402,42 @@ function imageOutputSchema() {
1157
1402
  name: { type: "string" }
1158
1403
  }
1159
1404
  },
1405
+ original: {
1406
+ type: "object",
1407
+ required: true,
1408
+ additionalProperties: false,
1409
+ properties: {
1410
+ assetId: {
1411
+ type: "string",
1412
+ required: true
1413
+ },
1414
+ mediaType: {
1415
+ type: "string",
1416
+ enum: ["image/png"],
1417
+ required: true
1418
+ },
1419
+ bytes: {
1420
+ type: "integer",
1421
+ required: true
1422
+ },
1423
+ width: {
1424
+ type: "integer",
1425
+ required: true
1426
+ },
1427
+ height: {
1428
+ type: "integer",
1429
+ required: true
1430
+ },
1431
+ name: {
1432
+ type: "string",
1433
+ required: true
1434
+ },
1435
+ sha256: {
1436
+ type: "string",
1437
+ required: true
1438
+ }
1439
+ }
1440
+ },
1160
1441
  background: { type: "string" },
1161
1442
  quality: { type: "string" },
1162
1443
  size: { type: "string" }
@@ -1187,6 +1468,29 @@ function createCodexImageTool(options) {
1187
1468
  required: true,
1188
1469
  description: "A complete, production-ready description of the image to generate."
1189
1470
  },
1471
+ size: {
1472
+ type: "string",
1473
+ description: "Optional GPT Image 2 output size. Use auto unless the user requests an exact valid widthxheight resolution."
1474
+ },
1475
+ quality: {
1476
+ type: "string",
1477
+ enum: [
1478
+ "auto",
1479
+ "low",
1480
+ "medium",
1481
+ "high"
1482
+ ],
1483
+ description: "Optional rendering quality. Use auto unless the user requests draft speed or final quality."
1484
+ },
1485
+ background: {
1486
+ type: "string",
1487
+ enum: [
1488
+ "auto",
1489
+ "transparent",
1490
+ "opaque"
1491
+ ],
1492
+ description: "Optional background mode. Request transparent only when the user needs transparency."
1493
+ },
1190
1494
  referenceImages: {
1191
1495
  type: "array",
1192
1496
  description: "Optional explicit references to 1-5 prior images to edit. Omit for a new image.",
@@ -1221,13 +1525,19 @@ function createCodexImageTool(options) {
1221
1525
  },
1222
1526
  output: {
1223
1527
  schema: imageOutputSchema(),
1224
- render: (_args, value) => imageContent(value)
1528
+ render: (_args, value) => imageContent(value),
1529
+ presentationMeta: (_args, value) => ({
1530
+ kind: "codex-subscription-image",
1531
+ schemaVersion: 1,
1532
+ original: value.original
1533
+ })
1225
1534
  },
1226
1535
  timeoutMs: 300 * 1e3,
1227
1536
  isConcurrencySafe: () => false,
1228
1537
  async execute(args, exec) {
1229
1538
  const prompt = nonEmpty(args.prompt);
1230
1539
  if (prompt === void 0) throw new Error("prompt must be a non-empty string");
1540
+ const imageOptions = normalizeImageOptions(args);
1231
1541
  const auth = await options.getAuth({ signal: exec.signal });
1232
1542
  const credential = await options.readCredential({ signal: exec.signal });
1233
1543
  const access = auth?.auth?.apiKey;
@@ -1254,10 +1564,10 @@ function createCodexImageTool(options) {
1254
1564
  body: JSON.stringify({
1255
1565
  ...images === void 0 ? {} : { images },
1256
1566
  prompt,
1257
- background: "auto",
1567
+ background: imageOptions.background,
1258
1568
  model: IMAGE_MODEL,
1259
- quality: "auto",
1260
- size: "auto"
1569
+ quality: imageOptions.quality,
1570
+ size: imageOptions.size
1261
1571
  }),
1262
1572
  signal: exec.signal
1263
1573
  });
@@ -1272,12 +1582,23 @@ function createCodexImageTool(options) {
1272
1582
  }
1273
1583
  const metadata = responseMetadata(await readJsonWithin(response, encodedLimit(maximumBytes) + RESPONSE_ENVELOPE_BYTES));
1274
1584
  const data = decodeCodexPng(metadata.encoded, maximumBytes);
1275
- const result = {
1276
- image: imageReference(await attachments.saveImage({
1585
+ const sessionId = exec.agent?.id;
1586
+ if (sessionId === void 0) throw new Error("Codex image generation requires a session-owned tool call");
1587
+ const original = await options.originalImages.save(String(sessionId), data);
1588
+ let ref;
1589
+ try {
1590
+ ref = await attachments.saveImage({
1277
1591
  data,
1278
1592
  mediaType: "image/png",
1279
1593
  name: "codex-generated.png"
1280
- })),
1594
+ });
1595
+ } catch (error) {
1596
+ await options.originalImages.remove(original);
1597
+ throw error;
1598
+ }
1599
+ const result = {
1600
+ image: imageReference(ref),
1601
+ original,
1281
1602
  ...metadata.background === void 0 ? {} : { background: metadata.background },
1282
1603
  ...metadata.quality === void 0 ? {} : { quality: metadata.quality },
1283
1604
  ...metadata.size === void 0 ? {} : { size: metadata.size }
@@ -1294,6 +1615,142 @@ function createCodexImageTool(options) {
1294
1615
  });
1295
1616
  }
1296
1617
  //#endregion
1618
+ //#region src/image-original-store.js
1619
+ const ORIGINAL_IMAGE_DIRECTORY = "dsh-codex-subscription/images/v1";
1620
+ const METADATA_VERSION = 1;
1621
+ const digest = (data) => createHash("sha256").update(data).digest("hex");
1622
+ const validSessionId = (value) => typeof value === "string" && value.length > 0 && value.length <= 512;
1623
+ function pngDimensions(data) {
1624
+ if (!(data instanceof Uint8Array) || data.byteLength < 24 || Buffer.from(data.subarray(0, 8)).toString("hex") !== "89504e470d0a1a0a" || Buffer.from(data.subarray(12, 16)).toString("ascii") !== "IHDR") throw new TypeError("invalid PNG dimensions");
1625
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1626
+ const width = view.getUint32(16, false);
1627
+ const height = view.getUint32(20, false);
1628
+ if (width === 0 || height === 0) throw new TypeError("invalid PNG dimensions");
1629
+ return {
1630
+ width,
1631
+ height
1632
+ };
1633
+ }
1634
+ async function writeExclusive(filename, data) {
1635
+ await mkdir(dirname(filename), {
1636
+ recursive: true,
1637
+ mode: 448
1638
+ });
1639
+ const handle = await open(filename, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
1640
+ try {
1641
+ await handle.writeFile(data);
1642
+ await handle.sync();
1643
+ } finally {
1644
+ await handle.close();
1645
+ }
1646
+ }
1647
+ async function assertPrivateFile(filename) {
1648
+ const stat = await lstat(filename);
1649
+ if (!stat.isFile()) throw new Error("not a regular file");
1650
+ if (process.platform !== "win32" && (stat.mode & 63) !== 0) throw new Error("file is not owner-only");
1651
+ }
1652
+ function parseMetadata(text) {
1653
+ let value;
1654
+ try {
1655
+ value = JSON.parse(text);
1656
+ } catch {
1657
+ return;
1658
+ }
1659
+ if (value?.version !== METADATA_VERSION || !validSessionId(value.sessionId)) return void 0;
1660
+ const image = decodeOriginalImageRef(value.image);
1661
+ return image === void 0 ? void 0 : {
1662
+ sessionId: value.sessionId,
1663
+ image
1664
+ };
1665
+ }
1666
+ var OriginalImageStore = class {
1667
+ constructor(dshHome) {
1668
+ this.root = resolve(join(resolveDshHome(dshHome), ORIGINAL_IMAGE_DIRECTORY));
1669
+ }
1670
+ directory(assetId) {
1671
+ if (!ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) throw new TypeError("invalid original image asset id");
1672
+ return join(this.root, assetId.slice(4, 6), assetId);
1673
+ }
1674
+ async save(sessionId, data, name = "codex-generated-original.png") {
1675
+ if (!validSessionId(sessionId) || !(data instanceof Uint8Array) || data.byteLength === 0 || data.byteLength > 48 * 1024 * 1024) throw new TypeError("invalid original image input");
1676
+ const { width, height } = pngDimensions(data);
1677
+ const assetId = `img_${randomBytes(16).toString("hex")}`;
1678
+ const directory = this.directory(assetId);
1679
+ const ref = {
1680
+ assetId,
1681
+ mediaType: "image/png",
1682
+ bytes: data.byteLength,
1683
+ width,
1684
+ height,
1685
+ name,
1686
+ sha256: digest(data)
1687
+ };
1688
+ try {
1689
+ await mkdir(dirname(directory), {
1690
+ recursive: true,
1691
+ mode: 448
1692
+ });
1693
+ await mkdir(directory, {
1694
+ recursive: false,
1695
+ mode: 448
1696
+ });
1697
+ await writeExclusive(join(directory, "original"), data);
1698
+ const temporary = join(directory, `metadata.${randomBytes(8).toString("hex")}.tmp`);
1699
+ await writeExclusive(temporary, Buffer.from(`${JSON.stringify({
1700
+ version: METADATA_VERSION,
1701
+ sessionId,
1702
+ image: ref
1703
+ }, null, 2)}\n`));
1704
+ await rename(temporary, join(directory, "metadata.json"));
1705
+ return ref;
1706
+ } catch (error) {
1707
+ await rm(directory, {
1708
+ recursive: true,
1709
+ force: true
1710
+ }).catch(() => void 0);
1711
+ throw error;
1712
+ }
1713
+ }
1714
+ async remove(ref) {
1715
+ if (ref !== void 0 && ORIGINAL_IMAGE_ID_PATTERN.test(ref.assetId)) await rm(this.directory(ref.assetId), {
1716
+ recursive: true,
1717
+ force: true
1718
+ }).catch(() => void 0);
1719
+ }
1720
+ async read(sessionId, assetId, inherited) {
1721
+ if (!validSessionId(sessionId) || !ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) return void 0;
1722
+ try {
1723
+ const directory = this.directory(assetId);
1724
+ const metadataFile = join(directory, "metadata.json");
1725
+ const originalFile = join(directory, "original");
1726
+ await Promise.all([assertPrivateFile(metadataFile), assertPrivateFile(originalFile)]);
1727
+ const metadata = parseMetadata(await readFile(metadataFile, "utf8"));
1728
+ if (metadata === void 0 || metadata.image.assetId !== assetId || metadata.sessionId !== sessionId && !originalImageRefsEqual(metadata.image, inherited)) return void 0;
1729
+ const data = new Uint8Array(await readFile(originalFile));
1730
+ const dimensions = pngDimensions(data);
1731
+ if (data.byteLength !== metadata.image.bytes || digest(data) !== metadata.image.sha256 || dimensions.width !== metadata.image.width || dimensions.height !== metadata.image.height) return void 0;
1732
+ return {
1733
+ ref: metadata.image,
1734
+ data
1735
+ };
1736
+ } catch {
1737
+ return;
1738
+ }
1739
+ }
1740
+ async chunk(sessionId, assetId, offset, inherited) {
1741
+ if (!Number.isSafeInteger(offset) || offset < 0) return void 0;
1742
+ const stored = await this.read(sessionId, assetId, inherited);
1743
+ if (stored === void 0 || offset >= stored.data.byteLength || offset % 4194304 !== 0) return void 0;
1744
+ const end = Math.min(stored.data.byteLength, offset + ORIGINAL_IMAGE_CHUNK_BYTES);
1745
+ return {
1746
+ ref: stored.ref,
1747
+ offset,
1748
+ encoded: Buffer.from(stored.data.subarray(offset, end)).toString("base64"),
1749
+ done: end === stored.data.byteLength
1750
+ };
1751
+ }
1752
+ };
1753
+ //#endregion
1297
1754
  //#region src/diagnostics.js
1298
1755
  const requestAreas = /* @__PURE__ */ new Set([
1299
1756
  "login",
@@ -1367,6 +1824,7 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
1367
1824
  configuration: {
1368
1825
  contextMode: preference.contextMode,
1369
1826
  quickQuotaMode: preference.quickQuotaMode,
1827
+ ...typeof preference.outputVerbosity === "string" ? { outputVerbosity: preference.outputVerbosity } : {},
1370
1828
  searchProvider: preference.searchProvider,
1371
1829
  speedMode: preference.speedMode,
1372
1830
  writable: preference.writable === true
@@ -1561,6 +2019,136 @@ function createCodexUsageReader(options) {
1561
2019
  });
1562
2020
  }
1563
2021
  //#endregion
2022
+ //#region src/quota-forecast.js
2023
+ const HOUR_MS = 3600 * 1e3;
2024
+ const HISTORY_MS = 24 * HOUR_MS;
2025
+ const MIN_SPAN_MS = 1800 * 1e3;
2026
+ const MIN_CONSUMED_PERCENT = 1;
2027
+ const PLATEAU_SAMPLE_MS = 900 * 1e3;
2028
+ const finite = (value) => Number.isFinite(Number(value));
2029
+ const clampPercent = (value) => Math.max(0, Math.min(100, Number(value)));
2030
+ const keyFor = (window) => `codex:${Number(window.windowSeconds) || "limit"}`;
2031
+ function observeQuotaForecast(state, windows, now = Date.now()) {
2032
+ const next = { windows: { ...state?.windows ?? {} } };
2033
+ let changed = false;
2034
+ for (const window of windows ?? []) {
2035
+ if (!finite(window?.remainingPercent)) continue;
2036
+ const key = keyFor(window);
2037
+ const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
2038
+ const remainingPercent = Math.round(clampPercent(window.remainingPercent) * 1e4) / 1e4;
2039
+ const previous = next.windows[key];
2040
+ const resetChanged = previous !== void 0 && (previous.resetsAt === null !== (resetsAt === null) || previous.resetsAt !== null && Math.abs(previous.resetsAt - resetsAt) > 300);
2041
+ const last = previous?.samples?.at(-1);
2042
+ const quotaIncreased = last !== void 0 && remainingPercent > last.remainingPercent + .5;
2043
+ const record = resetChanged || quotaIncreased ? {
2044
+ resetsAt,
2045
+ samples: []
2046
+ } : {
2047
+ resetsAt,
2048
+ samples: [...previous?.samples ?? []]
2049
+ };
2050
+ const latest = record.samples.at(-1);
2051
+ if (latest === void 0 || now > latest.at && (Math.abs(remainingPercent - latest.remainingPercent) >= .001 || now - latest.at >= PLATEAU_SAMPLE_MS)) {
2052
+ record.samples.push({
2053
+ at: now,
2054
+ remainingPercent
2055
+ });
2056
+ record.samples = record.samples.filter((sample) => sample.at >= now - HISTORY_MS).slice(-192);
2057
+ changed = true;
2058
+ }
2059
+ next.windows[key] = record;
2060
+ }
2061
+ return {
2062
+ state: next,
2063
+ changed
2064
+ };
2065
+ }
2066
+ function estimateQuotaForecast(state, window, now = Date.now()) {
2067
+ if (!finite(window?.remainingPercent)) return { status: "calibrating" };
2068
+ const record = state?.windows?.[keyFor(window)];
2069
+ if (record === void 0) return { status: "calibrating" };
2070
+ const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
2071
+ if (record.resetsAt === null !== (resetsAt === null) || resetsAt !== null && Math.abs(record.resetsAt - resetsAt) > 300) return { status: "calibrating" };
2072
+ const samples = record.samples.filter((sample) => sample.at >= now - HISTORY_MS && sample.at <= now + 6e4);
2073
+ if (samples.length < 3) return {
2074
+ status: "calibrating",
2075
+ sampleCount: samples.length
2076
+ };
2077
+ const first = samples[0];
2078
+ const last = samples.at(-1);
2079
+ const spanMs = last.at - first.at;
2080
+ const consumedPercent = Math.max(0, first.remainingPercent - last.remainingPercent);
2081
+ if (spanMs < MIN_SPAN_MS || consumedPercent < MIN_CONSUMED_PERCENT) return {
2082
+ status: "calibrating",
2083
+ sampleCount: samples.length,
2084
+ observedSpanMs: spanMs,
2085
+ consumedPercent
2086
+ };
2087
+ const firstAt = first.at;
2088
+ const weighted = samples.map((sample) => ({
2089
+ x: (sample.at - firstAt) / HOUR_MS,
2090
+ y: first.remainingPercent - sample.remainingPercent,
2091
+ weight: Math.exp((sample.at - last.at) / (6 * HOUR_MS))
2092
+ }));
2093
+ const totalWeight = weighted.reduce((sum, point) => sum + point.weight, 0);
2094
+ const meanX = weighted.reduce((sum, point) => sum + point.x * point.weight, 0) / totalWeight;
2095
+ const meanY = weighted.reduce((sum, point) => sum + point.y * point.weight, 0) / totalWeight;
2096
+ const numerator = weighted.reduce((sum, point) => sum + point.weight * (point.x - meanX) * (point.y - meanY), 0);
2097
+ const denominator = weighted.reduce((sum, point) => sum + point.weight * (point.x - meanX) ** 2, 0);
2098
+ const pacePerHour = denominator > 0 ? numerator / denominator : 0;
2099
+ if (!Number.isFinite(pacePerHour) || pacePerHour < .02) return {
2100
+ status: "idle",
2101
+ pacePerHour: 0
2102
+ };
2103
+ const runwaySeconds = clampPercent(window.remainingPercent) / pacePerHour * 3600;
2104
+ const resetSeconds = resetsAt === null ? null : Math.max(0, resetsAt - now / 1e3);
2105
+ return {
2106
+ status: "ready",
2107
+ pacePerHour,
2108
+ runwaySeconds,
2109
+ survivesReset: resetSeconds !== null && runwaySeconds >= resetSeconds,
2110
+ sampleCount: samples.length,
2111
+ observedSpanMs: spanMs,
2112
+ consumedPercent
2113
+ };
2114
+ }
2115
+ function forecastUsage(usage, state = { windows: {} }, now = Date.now()) {
2116
+ const observed = observeQuotaForecast(state, usage?.rateLimits?.find((limit) => limit.id === "codex")?.windows ?? [], now);
2117
+ return {
2118
+ state: observed.state,
2119
+ changed: observed.changed,
2120
+ usage: {
2121
+ ...usage,
2122
+ rateLimits: (usage?.rateLimits ?? []).map((limit) => limit.id !== "codex" ? limit : {
2123
+ ...limit,
2124
+ windows: limit.windows.map((window) => ({
2125
+ ...window,
2126
+ forecast: estimateQuotaForecast(observed.state, window, now)
2127
+ }))
2128
+ })
2129
+ }
2130
+ };
2131
+ }
2132
+ function createQuotaForecastReader({ reader, enabled, now = Date.now }) {
2133
+ let state = { windows: {} };
2134
+ return Object.freeze({
2135
+ async read(options) {
2136
+ const usage = await reader.read(options);
2137
+ if (!enabled()) {
2138
+ state = { windows: {} };
2139
+ return usage;
2140
+ }
2141
+ const forecast = forecastUsage(usage, state, now());
2142
+ state = forecast.state;
2143
+ return forecast.usage;
2144
+ },
2145
+ clear() {
2146
+ state = { windows: {} };
2147
+ reader.clear();
2148
+ }
2149
+ });
2150
+ }
2151
+ //#endregion
1564
2152
  //#region src/reset-credits.js
1565
2153
  const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
1566
2154
  const CODEX_RESET_CONSUME_URL = `${CODEX_RESET_CREDITS_URL}/consume`;
@@ -1783,8 +2371,22 @@ const publicError = (code, message) => ({
1783
2371
  details: { issues: [] }
1784
2372
  }
1785
2373
  });
1786
- function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, diagnosticsReader }) {
2374
+ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal }) {
1787
2375
  return async (endpoint, payload, signal) => {
2376
+ if (endpoint === "image/original/chunk") try {
2377
+ signal.throwIfAborted();
2378
+ if (typeof payload?.sessionId !== "string" || payload.sessionId.length === 0 || payload.sessionId.length > 512 || typeof payload?.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(payload.assetId) || !Number.isSafeInteger(payload?.offset) || payload.offset < 0 || payload.offset % 4194304 !== 0) return publicError("invalid-input", "Invalid original image request");
2379
+ const inherited = resolveInheritedOriginal?.(payload.sessionId, payload.assetId);
2380
+ const chunk = await originalImages?.chunk(payload.sessionId, payload.assetId, payload.offset, inherited);
2381
+ if (chunk === void 0) return publicError("not-found", "Original image is unavailable");
2382
+ return {
2383
+ ok: true,
2384
+ value: chunk
2385
+ };
2386
+ } catch (error) {
2387
+ if (signal.aborted) throw error;
2388
+ return publicError("internal", "Could not read the original image");
2389
+ }
1788
2390
  if (endpoint === "diagnostics") try {
1789
2391
  signal.throwIfAborted();
1790
2392
  return {
@@ -1803,18 +2405,32 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1803
2405
  if (![
1804
2406
  "off",
1805
2407
  "percent",
1806
- "bar"
2408
+ "bar",
2409
+ "forecast"
1807
2410
  ].includes(payload["quickQuotaMode"])) return publicError("internal", "Invalid quick quota preference");
1808
2411
  patch[QUICK_QUOTA_MODE_FIELD] = payload[QUICK_QUOTA_MODE_FIELD];
1809
2412
  }
1810
2413
  if (Object.hasOwn(payload ?? {}, "searchProvider")) {
1811
- if (!["dsh", "codex"].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
2414
+ if (![
2415
+ "auto",
2416
+ "dsh",
2417
+ "codex"
2418
+ ].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
1812
2419
  patch[SEARCH_PROVIDER_FIELD] = payload[SEARCH_PROVIDER_FIELD];
1813
2420
  }
1814
2421
  if (Object.hasOwn(payload ?? {}, "speedMode")) {
1815
2422
  if (!["standard", "fast"].includes(payload["speedMode"])) return publicError("internal", "Invalid speed mode preference");
1816
2423
  patch[SPEED_MODE_FIELD] = payload[SPEED_MODE_FIELD];
1817
2424
  }
2425
+ if (Object.hasOwn(payload ?? {}, "outputVerbosity")) {
2426
+ if (![
2427
+ "default",
2428
+ "low",
2429
+ "medium",
2430
+ "high"
2431
+ ].includes(payload["outputVerbosity"])) return publicError("internal", "Invalid output verbosity preference");
2432
+ patch[OUTPUT_VERBOSITY_FIELD] = payload[OUTPUT_VERBOSITY_FIELD];
2433
+ }
1818
2434
  if (Object.hasOwn(payload ?? {}, "contextMode")) {
1819
2435
  if (![
1820
2436
  "standard",
@@ -1890,37 +2506,56 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1890
2506
  if (endpoint === "logout" && result.ok === true) {
1891
2507
  usageReader.clear();
1892
2508
  resetCreditService.clear();
1893
- }
2509
+ modelCatalog?.clear();
2510
+ } else if (result.ok === true && (endpoint === "status" || result.value?.authenticated === true)) modelCatalog?.refresh({ signal: void 0 }).catch(() => {});
1894
2511
  return result;
1895
2512
  };
1896
2513
  }
1897
2514
  function createSearchProviderSwitcher(loader) {
1898
2515
  const webEntry = () => [...loader.entries()].find((entry) => entry.options?.id === WEB_ENTRY_ID);
1899
- return Object.freeze({ async select(selection) {
1900
- const entry = webEntry();
1901
- const fiber = entry?.fiber;
1902
- if (entry === void 0 || fiber === void 0 || typeof fiber.update !== "function") throw new Error("DSH web runtime is unavailable");
1903
- const baseConfig = entry.options?.config ?? {};
1904
- const currentConfig = fiber.config ?? baseConfig;
1905
- const dshProvider = typeof baseConfig.searchProvider === "string" && baseConfig.searchProvider.length > 0 ? baseConfig.searchProvider : DSH_SEARCH_PROVIDER_FALLBACK;
1906
- const provider = selection === "codex" ? CODEX_SEARCH_PROVIDER_ID : dshProvider;
1907
- if (currentConfig.searchProvider === provider) return;
1908
- await fiber.update({
1909
- ...currentConfig,
1910
- searchProvider: provider
1911
- }, true);
1912
- } });
2516
+ const dshProviderId = () => {
2517
+ const baseConfig = webEntry()?.options?.config ?? {};
2518
+ return typeof baseConfig.searchProvider === "string" && baseConfig.searchProvider.length > 0 ? baseConfig.searchProvider : DSH_SEARCH_PROVIDER_FALLBACK;
2519
+ };
2520
+ return Object.freeze({
2521
+ dshProviderId,
2522
+ async select(selection) {
2523
+ const entry = webEntry();
2524
+ const fiber = entry?.fiber;
2525
+ if (entry === void 0 || fiber === void 0 || typeof fiber.update !== "function") throw new Error("DSH web runtime is unavailable");
2526
+ const baseConfig = entry.options?.config ?? {};
2527
+ const currentConfig = fiber.config ?? baseConfig;
2528
+ const dshProvider = dshProviderId();
2529
+ const provider = selection === "codex" ? CODEX_SEARCH_PROVIDER_ID : selection === "auto" ? CODEX_AUTO_SEARCH_PROVIDER_ID : dshProvider;
2530
+ if (currentConfig.searchProvider === provider) return;
2531
+ await fiber.update({
2532
+ ...currentConfig,
2533
+ searchProvider: provider
2534
+ }, true);
2535
+ }
2536
+ });
1913
2537
  }
1914
2538
  function apply(ctx) {
1915
2539
  const settings = ctx.settings.register(settingsNamespace(SETTINGS_NAMESPACE), z.object({
1916
2540
  [QUICK_QUOTA_MODE_FIELD]: z.union([
1917
2541
  "off",
1918
2542
  QUICK_QUOTA_MODE_PERCENT,
1919
- "bar"
2543
+ "bar",
2544
+ QUICK_QUOTA_MODE_FORECAST
1920
2545
  ]),
1921
2546
  [LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
1922
- [SEARCH_PROVIDER_FIELD]: z.union(["dsh", SEARCH_PROVIDER_CODEX]).default("dsh"),
2547
+ [SEARCH_PROVIDER_FIELD]: z.union([
2548
+ SEARCH_PROVIDER_AUTO,
2549
+ "dsh",
2550
+ SEARCH_PROVIDER_CODEX
2551
+ ]).default(DEFAULT_SEARCH_PROVIDER),
1923
2552
  [SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE),
2553
+ [OUTPUT_VERBOSITY_FIELD]: z.union([
2554
+ OUTPUT_VERBOSITY_DEFAULT,
2555
+ "low",
2556
+ OUTPUT_VERBOSITY_MEDIUM,
2557
+ OUTPUT_VERBOSITY_HIGH
2558
+ ]).default(DEFAULT_OUTPUT_VERBOSITY),
1924
2559
  [CONTEXT_MODE_FIELD]: z.union([
1925
2560
  CONTEXT_MODE_STANDARD,
1926
2561
  CONTEXT_MODE_EXTENDED,
@@ -1931,13 +2566,25 @@ function apply(ctx) {
1931
2566
  }));
1932
2567
  const searchProvider = createSearchProviderSwitcher(ctx.loader);
1933
2568
  const network = createCodexNetworkTransport();
2569
+ const originalImages = new OriginalImageStore();
2570
+ const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF, [LEGACY_CREDENTIAL_REF]);
2571
+ const baseProvider = createOpenAICodexProvider();
2572
+ let resolveAuth = async () => void 0;
2573
+ const modelCatalog = createOfficialModelCatalog({
2574
+ getAuth: (options) => resolveAuth(options),
2575
+ readCredential: (options) => store.read(PROVIDER, options),
2576
+ baseModels: () => baseProvider.getModels(),
2577
+ fetch: (input, init) => network.fetch("catalog", input, init)
2578
+ });
1934
2579
  const provider = openaiCodexSubscriptionProvider({
1935
2580
  resolveSpeedMode: () => settings.get()[SPEED_MODE_FIELD],
2581
+ resolveOutputVerbosity: () => normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
1936
2582
  resolveContextMode: () => normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
1937
2583
  resolveCustomContextWindow: (modelKey) => {
1938
2584
  const field = CUSTOM_CONTEXT_MODEL_FIELDS[modelKey];
1939
2585
  return normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey]);
1940
2586
  },
2587
+ catalog: modelCatalog,
1941
2588
  runNetwork: network.run
1942
2589
  });
1943
2590
  const preferences = {
@@ -1945,15 +2592,16 @@ function apply(ctx) {
1945
2592
  [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
1946
2593
  [SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
1947
2594
  [SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
2595
+ [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
1948
2596
  [CONTEXT_MODE_FIELD]: normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
1949
2597
  [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(settings.get()[CUSTOM_CONTEXT_WINDOW_FIELD]),
1950
2598
  ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
1951
2599
  contextModels: contextModelGroups(provider.getModels()),
2600
+ verbosityModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportVerbosity ?? model.id !== "gpt-5.3-codex-spark").map((model) => model.id),
1952
2601
  writable: ctx.settings.writable
1953
2602
  }),
1954
2603
  update: (patch) => settings.update(patch)
1955
2604
  };
1956
- const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF, [LEGACY_CREDENTIAL_REF]);
1957
2605
  const authModels = createModels({ credentials: store });
1958
2606
  authModels.setProvider(provider);
1959
2607
  const profile = Object.freeze({
@@ -1971,14 +2619,18 @@ function apply(ctx) {
1971
2619
  let profileKey;
1972
2620
  let profileSnapshot;
1973
2621
  const profiles = () => {
1974
- const key = [normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]), ...Object.values(CUSTOM_CONTEXT_MODEL_FIELDS).map((field) => settings.get()[field])].join(":");
2622
+ const key = [
2623
+ modelCatalog.revision(),
2624
+ normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
2625
+ ...Object.values(CUSTOM_CONTEXT_MODEL_FIELDS).map((field) => settings.get()[field])
2626
+ ].join(":");
1975
2627
  if (key !== profileKey) {
1976
2628
  profileKey = key;
1977
2629
  profileSnapshot = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
1978
2630
  }
1979
2631
  return profileSnapshot;
1980
2632
  };
1981
- const resolveAuth = () => authModels.getAuth(PROVIDER);
2633
+ resolveAuth = () => authModels.getAuth(PROVIDER);
1982
2634
  const adapterAuth = Object.freeze({
1983
2635
  credentials: store,
1984
2636
  authContext: Object.freeze({
@@ -1990,6 +2642,7 @@ function apply(ctx) {
1990
2642
  getAuth: resolveAuth,
1991
2643
  readCredential: (options) => store.read(PROVIDER, options),
1992
2644
  attachments: ctx.attachments,
2645
+ originalImages,
1993
2646
  fetch: (input, init) => network.fetch("image", input, init)
1994
2647
  }));
1995
2648
  const adapter = new PiAiAdapter({
@@ -2009,7 +2662,7 @@ function apply(ctx) {
2009
2662
  });
2010
2663
  ctx.llm.registerAdapter([PROVIDER], adapter);
2011
2664
  const currentAgent = () => ctx.get?.("agents")?.currentInitiator?.();
2012
- ctx.web.registerSearchProvider(createCodexSearchProvider({
2665
+ const codexSearch = createCodexSearchProvider({
2013
2666
  getAuth: resolveAuth,
2014
2667
  readCredential: (options) => store.read(PROVIDER, options),
2015
2668
  resolveModel: () => {
@@ -2018,6 +2671,12 @@ function apply(ctx) {
2018
2671
  },
2019
2672
  resolveSessionId: () => currentAgent()?.session.id,
2020
2673
  fetch: (input, init) => network.fetch("search", input, init)
2674
+ });
2675
+ ctx.web.registerSearchProvider(codexSearch);
2676
+ ctx.web.registerSearchProvider(createCodexAutoSearchProvider({
2677
+ codex: codexSearch,
2678
+ resolveModelProvider: () => currentAgent()?.session.requestContext?.()?.provider,
2679
+ resolveDshProvider: () => ctx.web.searchProviders?.get(searchProvider.dshProviderId())
2021
2680
  }));
2022
2681
  ctx.effect(() => {
2023
2682
  const select = async (value) => {
@@ -2032,10 +2691,13 @@ function apply(ctx) {
2032
2691
  }, "codex-subscription: search provider selection");
2033
2692
  const auth = createCodexAuthService(authModels, store, { runLogin: (operation) => network.run("login", operation) });
2034
2693
  const coordinator = new CodexLoginCoordinator(auth);
2035
- const usageReader = createCodexUsageReader({
2036
- getAuth: resolveAuth,
2037
- readCredential: (options) => store.read(PROVIDER, options),
2038
- fetch: (input, init) => network.fetch("quota", input, init)
2694
+ const usageReader = createQuotaForecastReader({
2695
+ reader: createCodexUsageReader({
2696
+ getAuth: resolveAuth,
2697
+ readCredential: (options) => store.read(PROVIDER, options),
2698
+ fetch: (input, init) => network.fetch("quota", input, init)
2699
+ }),
2700
+ enabled: () => normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]) === QUICK_QUOTA_MODE_FORECAST
2039
2701
  });
2040
2702
  const resetCreditService = createCodexResetCreditService({
2041
2703
  getAuth: resolveAuth,
@@ -2053,9 +2715,15 @@ function apply(ctx) {
2053
2715
  preferences,
2054
2716
  login: coordinator.supportState(),
2055
2717
  network
2056
- })
2718
+ }),
2719
+ modelCatalog,
2720
+ originalImages,
2721
+ resolveInheritedOriginal: (sessionId, assetId) => inheritedOriginalImageRef(ctx.get?.("sessions")?.get?.(sessionId), assetId)
2057
2722
  });
2058
- ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }), "codex-subscription: loopback account RPC");
2723
+ ctx.effect(() => {
2724
+ modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
2725
+ }, "codex-subscription: official model catalog");
2726
+ ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "trusted-host" }), "codex-subscription: DSH-trusted account RPC");
2059
2727
  }
2060
2728
  //#endregion
2061
2729
  export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexResetCreditService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionDiagnostics, createSubscriptionRpcHandler, decodeCodexPng, inject, name, normalizeContextMode, normalizeCustomContextWindow, openCodexAuthUrl, parseCodexUsage };