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/AGENTS.md +5 -5
- package/README.md +36 -44
- package/README.zh-CN.md +35 -43
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/lib/client.js +458 -210
- package/lib/index.js +719 -51
- package/package.json +5 -1
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
|
|
846
|
-
|
|
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
|
-
|
|
874
|
+
...textVerbosity === void 0 ? {} : { textVerbosity },
|
|
875
|
+
...fast ? { serviceTier: FAST_SERVICE_TIER } : {},
|
|
851
876
|
async onPayload(payload, requestModel) {
|
|
852
|
-
const
|
|
877
|
+
const preferred = {
|
|
853
878
|
...payload,
|
|
854
|
-
|
|
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
|
-
...
|
|
858
|
-
|
|
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,
|
|
900
|
-
streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context,
|
|
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.
|
|
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:
|
|
1567
|
+
background: imageOptions.background,
|
|
1258
1568
|
model: IMAGE_MODEL,
|
|
1259
|
-
quality:
|
|
1260
|
-
size:
|
|
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
|
|
1276
|
-
|
|
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 (![
|
|
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
|
-
|
|
1900
|
-
const
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
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([
|
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
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(() =>
|
|
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 };
|