dsh-codex-subscription 1.14.3 → 2.0.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/compatibility.json +2 -14
- package/lib/client.js +5704 -2281
- package/lib/index.js +753 -302
- package/package.json +18 -18
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
1
2
|
import * as dshCredentials from "@deepseek-ai/dsh-credentials";
|
|
2
3
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
3
4
|
import { LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
@@ -17,6 +18,389 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
17
18
|
import { constants } from "node:fs";
|
|
18
19
|
import { lstat, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
19
20
|
import { dirname, join, resolve } from "node:path";
|
|
21
|
+
//#region src/image-models.js
|
|
22
|
+
const DEFAULT_IMAGE_MODEL = "gpt-image-2";
|
|
23
|
+
const IMAGE_MODELS = Object.freeze({
|
|
24
|
+
"gpt-image-2": Object.freeze([
|
|
25
|
+
"auto",
|
|
26
|
+
"low",
|
|
27
|
+
"medium",
|
|
28
|
+
"high"
|
|
29
|
+
]),
|
|
30
|
+
"gpt-image-2.5-flare": Object.freeze([
|
|
31
|
+
"auto",
|
|
32
|
+
"low",
|
|
33
|
+
"medium",
|
|
34
|
+
"high",
|
|
35
|
+
"xhigh",
|
|
36
|
+
"max"
|
|
37
|
+
]),
|
|
38
|
+
"gpt-image-2.5-sunburst": Object.freeze([
|
|
39
|
+
"auto",
|
|
40
|
+
"low",
|
|
41
|
+
"medium",
|
|
42
|
+
"high",
|
|
43
|
+
"xhigh",
|
|
44
|
+
"max"
|
|
45
|
+
])
|
|
46
|
+
});
|
|
47
|
+
function resolveImageModel(model = DEFAULT_IMAGE_MODEL) {
|
|
48
|
+
if (typeof model !== "string" || !Object.hasOwn(IMAGE_MODELS, model)) throw new Error("Unknown image model");
|
|
49
|
+
return model;
|
|
50
|
+
}
|
|
51
|
+
function validateImageQuality(model, quality) {
|
|
52
|
+
if (!IMAGE_MODELS[resolveImageModel(model)].includes(quality)) throw new Error(`Unsupported quality for ${model}`);
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/image-features.js
|
|
56
|
+
const IMAGE_FEATURE_DEFAULTS = Object.freeze({
|
|
57
|
+
imageGeneration: true,
|
|
58
|
+
imageShortcut: true,
|
|
59
|
+
imageEditing: true,
|
|
60
|
+
imageViewer: true,
|
|
61
|
+
imageAnnotations: true,
|
|
62
|
+
imageSketch: true
|
|
63
|
+
});
|
|
64
|
+
function readImageFeatures(value = {}) {
|
|
65
|
+
return Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, fallback]) => [key, typeof value?.[key] === "boolean" ? value[key] : fallback]));
|
|
66
|
+
}
|
|
67
|
+
function readImageDefaults(value = {}) {
|
|
68
|
+
const imageModel = Object.hasOwn(IMAGE_MODELS, value?.imageModel) ? value.imageModel : DEFAULT_IMAGE_MODEL;
|
|
69
|
+
return {
|
|
70
|
+
imageModel,
|
|
71
|
+
imageQuality: IMAGE_MODELS[imageModel].includes(value?.imageQuality) ? value.imageQuality : "auto"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function imageFeaturePatch(value = {}) {
|
|
75
|
+
const patch = {};
|
|
76
|
+
if (Object.hasOwn(value, "imageModel")) patch.imageModel = resolveImageModel(value.imageModel);
|
|
77
|
+
if (Object.hasOwn(value, "imageQuality")) {
|
|
78
|
+
if (![
|
|
79
|
+
"auto",
|
|
80
|
+
"low",
|
|
81
|
+
"medium",
|
|
82
|
+
"high",
|
|
83
|
+
"xhigh",
|
|
84
|
+
"max"
|
|
85
|
+
].includes(value.imageQuality)) throw new Error("Invalid image quality");
|
|
86
|
+
patch.imageQuality = value.imageQuality;
|
|
87
|
+
}
|
|
88
|
+
for (const key of Object.keys(IMAGE_FEATURE_DEFAULTS)) {
|
|
89
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
90
|
+
if (typeof value[key] !== "boolean") throw new Error("Invalid image feature preference");
|
|
91
|
+
patch[key] = value[key];
|
|
92
|
+
}
|
|
93
|
+
return patch;
|
|
94
|
+
}
|
|
95
|
+
function assertImageOperation(features, editing) {
|
|
96
|
+
const current = readImageFeatures(features);
|
|
97
|
+
if (!(editing ? current.imageEditing : current.imageGeneration)) throw new Error(editing ? "Image editing is disabled in subscription settings" : "Image generation is disabled in subscription settings");
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/capability-settings.js
|
|
101
|
+
const CUSTOM_CONTEXT_OVERRIDES_FIELD = "customContextModels";
|
|
102
|
+
const SEARCH_MODE_FIELD = "searchMode";
|
|
103
|
+
const SEARCH_DOMAINS_FIELD = "searchDomains";
|
|
104
|
+
const QUOTA_ALERTS_FIELD = "quotaAlerts";
|
|
105
|
+
const SEARCH_MODES = [
|
|
106
|
+
"live",
|
|
107
|
+
"cached",
|
|
108
|
+
"disabled"
|
|
109
|
+
];
|
|
110
|
+
const QUOTA_ALERT_MODES = [
|
|
111
|
+
"off",
|
|
112
|
+
"important",
|
|
113
|
+
"early",
|
|
114
|
+
"custom"
|
|
115
|
+
];
|
|
116
|
+
const QUOTA_THRESHOLD_FIELDS = ["quotaShortThreshold", "quotaLongThreshold"];
|
|
117
|
+
const validQuotaThreshold = (value) => Number.isInteger(value) && value >= 1 && value <= 100;
|
|
118
|
+
const MAX_CONTEXT_BUDGET = 16e6;
|
|
119
|
+
const validModelKey = (key) => typeof key === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,95}$/u.test(key) && ![
|
|
120
|
+
"constructor",
|
|
121
|
+
"prototype",
|
|
122
|
+
"__proto__"
|
|
123
|
+
].includes(key);
|
|
124
|
+
function normalizeContextOverrides(value) {
|
|
125
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
126
|
+
return Object.fromEntries(Object.entries(value).filter(([key, size]) => validModelKey(key) && Number.isSafeInteger(size) && size > 0 && size <= 16e6).slice(0, 64));
|
|
127
|
+
}
|
|
128
|
+
function normalizeSearchDomains(value) {
|
|
129
|
+
if (!Array.isArray(value) || value.length > 20) throw new Error("Invalid search domains");
|
|
130
|
+
return [...new Set(value.map((item) => {
|
|
131
|
+
if (typeof item !== "string" || item.length > 253 || !/^[\p{L}\p{N}.-]+$/u.test(item)) throw new Error("Invalid search domain");
|
|
132
|
+
const hostname = new URL(`https://${item}`).hostname.toLowerCase();
|
|
133
|
+
if (!hostname.includes(".") || hostname.split(".").some((part) => !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u.test(part))) throw new Error("Invalid search domain");
|
|
134
|
+
return hostname;
|
|
135
|
+
}))];
|
|
136
|
+
}
|
|
137
|
+
function readCapabilitySettings(value = {}) {
|
|
138
|
+
return {
|
|
139
|
+
...Object.fromEntries(QUOTA_THRESHOLD_FIELDS.map((key) => [key, validQuotaThreshold(value[key]) ? value[key] : 20])),
|
|
140
|
+
...readImageFeatures(value),
|
|
141
|
+
...readImageDefaults(value),
|
|
142
|
+
[CUSTOM_CONTEXT_OVERRIDES_FIELD]: normalizeContextOverrides(value[CUSTOM_CONTEXT_OVERRIDES_FIELD]),
|
|
143
|
+
[SEARCH_MODE_FIELD]: SEARCH_MODES.includes(value["searchMode"]) ? value[SEARCH_MODE_FIELD] : "live",
|
|
144
|
+
[SEARCH_DOMAINS_FIELD]: normalizeSearchDomains(value["searchDomains"] ?? []),
|
|
145
|
+
[QUOTA_ALERTS_FIELD]: QUOTA_ALERT_MODES.includes(value["quotaAlerts"]) ? value[QUOTA_ALERTS_FIELD] : "important"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function capabilityPatch(payload) {
|
|
149
|
+
const patch = imageFeaturePatch(payload ?? {});
|
|
150
|
+
for (const field of QUOTA_THRESHOLD_FIELDS) {
|
|
151
|
+
if (!Object.hasOwn(payload ?? {}, field)) continue;
|
|
152
|
+
if (!validQuotaThreshold(payload[field])) throw new Error("Threshold must be an integer from 1 to 100");
|
|
153
|
+
patch[field] = payload[field];
|
|
154
|
+
}
|
|
155
|
+
for (const [key, choices] of [[SEARCH_MODE_FIELD, SEARCH_MODES], [QUOTA_ALERTS_FIELD, QUOTA_ALERT_MODES]]) {
|
|
156
|
+
if (!Object.hasOwn(payload ?? {}, key)) continue;
|
|
157
|
+
if (!choices.includes(payload[key])) throw new Error("Invalid capability preference");
|
|
158
|
+
patch[key] = payload[key];
|
|
159
|
+
}
|
|
160
|
+
if (Object.hasOwn(payload ?? {}, "searchDomains")) patch[SEARCH_DOMAINS_FIELD] = normalizeSearchDomains(payload[SEARCH_DOMAINS_FIELD]);
|
|
161
|
+
if (Object.hasOwn(payload ?? {}, "customContextModels")) {
|
|
162
|
+
const original = payload[CUSTOM_CONTEXT_OVERRIDES_FIELD];
|
|
163
|
+
const normalized = normalizeContextOverrides(original);
|
|
164
|
+
if (JSON.stringify(normalized) !== JSON.stringify(original)) throw new Error("Invalid model context preferences");
|
|
165
|
+
patch[CUSTOM_CONTEXT_OVERRIDES_FIELD] = normalized;
|
|
166
|
+
}
|
|
167
|
+
return patch;
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/settings-contract.js
|
|
171
|
+
const SETTINGS_NAMESPACE = "codex-subscription";
|
|
172
|
+
const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
|
|
173
|
+
const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
|
|
174
|
+
const QUICK_QUOTA_MODE_PERCENT = "percent";
|
|
175
|
+
const QUICK_QUOTA_MODE_FORECAST = "forecast";
|
|
176
|
+
const SEARCH_PROVIDER_FIELD = "searchProvider";
|
|
177
|
+
const SEARCH_PROVIDER_AUTO = "auto";
|
|
178
|
+
const SEARCH_PROVIDER_CODEX = "codex";
|
|
179
|
+
const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
|
|
180
|
+
const SPEED_MODE_FIELD = "speedMode";
|
|
181
|
+
const SPEED_MODE_STANDARD = "standard";
|
|
182
|
+
const SPEED_MODE_FAST = "fast";
|
|
183
|
+
const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
|
|
184
|
+
const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
|
|
185
|
+
const OUTPUT_VERBOSITY_DEFAULT = "default";
|
|
186
|
+
const OUTPUT_VERBOSITY_MEDIUM = "medium";
|
|
187
|
+
const OUTPUT_VERBOSITY_HIGH = "high";
|
|
188
|
+
const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
|
|
189
|
+
const CONTEXT_MODE_FIELD = "contextMode";
|
|
190
|
+
const CONTEXT_MODE_STANDARD = "standard";
|
|
191
|
+
const CONTEXT_MODE_EXTENDED = "extended";
|
|
192
|
+
const CONTEXT_MODE_CUSTOM = "custom";
|
|
193
|
+
const DEFAULT_CONTEXT_MODE = CONTEXT_MODE_STANDARD;
|
|
194
|
+
const CUSTOM_CONTEXT_WINDOW_FIELD = "customContextWindow";
|
|
195
|
+
const DEFAULT_CUSTOM_CONTEXT_WINDOW = 272e3;
|
|
196
|
+
const MIN_CUSTOM_CONTEXT_WINDOW = 128e3;
|
|
197
|
+
const MAX_CUSTOM_CONTEXT_WINDOW = 1e6;
|
|
198
|
+
const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
|
|
199
|
+
"gpt-5.4": "customContextGpt54",
|
|
200
|
+
"gpt-5.4-mini": "customContextGpt54Mini",
|
|
201
|
+
"gpt-5.5": "customContextGpt55",
|
|
202
|
+
"gpt-5.6": "customContextGpt56",
|
|
203
|
+
"gpt-6-astra": "customContextGpt6Astra"
|
|
204
|
+
});
|
|
205
|
+
const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
|
|
206
|
+
"gpt-5.4": 1e6,
|
|
207
|
+
"gpt-5.4-mini": 4e5,
|
|
208
|
+
"gpt-5.5": 1e6,
|
|
209
|
+
"gpt-5.6": 1e6,
|
|
210
|
+
"gpt-6-astra": 872e3
|
|
211
|
+
});
|
|
212
|
+
const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
|
|
213
|
+
"gpt-5.4": 272e3,
|
|
214
|
+
"gpt-5.4-mini": 272e3,
|
|
215
|
+
"gpt-5.5": 272e3,
|
|
216
|
+
"gpt-5.6": 272e3,
|
|
217
|
+
"gpt-6-astra": 272e3
|
|
218
|
+
});
|
|
219
|
+
const normalizeOutputVerbosity = (value) => [
|
|
220
|
+
"default",
|
|
221
|
+
"low",
|
|
222
|
+
"medium",
|
|
223
|
+
"high"
|
|
224
|
+
].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
|
|
225
|
+
const normalizeContextMode = (value) => [
|
|
226
|
+
"standard",
|
|
227
|
+
"extended",
|
|
228
|
+
"custom"
|
|
229
|
+
].includes(value) ? value : DEFAULT_CONTEXT_MODE;
|
|
230
|
+
const normalizeCustomContextWindow = (value, maximum = MAX_CUSTOM_CONTEXT_WINDOW) => {
|
|
231
|
+
if (!Number.isInteger(value)) return DEFAULT_CUSTOM_CONTEXT_WINDOW;
|
|
232
|
+
return Math.min(Math.max(value, MIN_CUSTOM_CONTEXT_WINDOW), maximum);
|
|
233
|
+
};
|
|
234
|
+
const customContextModelKey = (modelId) => modelId?.startsWith("gpt-5.6-") ? "gpt-5.6" : modelId;
|
|
235
|
+
function modelContextMaximum(model) {
|
|
236
|
+
const explicit = Number.isSafeInteger(model?.maxContextWindow) && model.maxContextWindow > 0 ? model.maxContextWindow : void 0;
|
|
237
|
+
const fallback = CUSTOM_CONTEXT_MODEL_CAPS[customContextModelKey(model?.id)] ?? model?.contextWindow;
|
|
238
|
+
return Math.min(MAX_CONTEXT_BUDGET, explicit ?? fallback ?? 272e3);
|
|
239
|
+
}
|
|
240
|
+
function clampModelContext(value, maximum, fallback = DEFAULT_CUSTOM_CONTEXT_WINDOW) {
|
|
241
|
+
return Math.max(Math.min(MIN_CUSTOM_CONTEXT_WINDOW, maximum), Math.min(Number.isSafeInteger(value) ? value : fallback, maximum));
|
|
242
|
+
}
|
|
243
|
+
function contextModelGroups(models) {
|
|
244
|
+
const groups = /* @__PURE__ */ new Map();
|
|
245
|
+
for (const model of models ?? []) {
|
|
246
|
+
if (model?.id === "gpt-5.3-codex-spark") {
|
|
247
|
+
groups.set(model.id, {
|
|
248
|
+
key: model.id,
|
|
249
|
+
label: model.name ?? model.id,
|
|
250
|
+
maximum: 128e3,
|
|
251
|
+
fixed: true
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const key = customContextModelKey(model?.id);
|
|
256
|
+
if (!validModelKey(key)) continue;
|
|
257
|
+
const maximum = modelContextMaximum(model);
|
|
258
|
+
if (key !== "gpt-5.6") {
|
|
259
|
+
groups.set(key, {
|
|
260
|
+
key,
|
|
261
|
+
label: model.name ?? model.id,
|
|
262
|
+
maximum,
|
|
263
|
+
...Object.hasOwn(CUSTOM_CONTEXT_MODEL_FIELDS, key) ? {} : { default: clampModelContext(model.contextWindow, maximum) }
|
|
264
|
+
});
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const variant = String(model.name ?? model.id).replace(/^GPT-5\.6[ -]/iu, "");
|
|
268
|
+
const current = groups.get(key);
|
|
269
|
+
groups.set(key, {
|
|
270
|
+
key,
|
|
271
|
+
label: `GPT-5.6 ${current === void 0 ? variant : `${current.label.replace(/^GPT-5\.6 /u, "")} / ${variant}`}`,
|
|
272
|
+
maximum: Math.min(current?.maximum ?? maximum, maximum)
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return [...groups.values()];
|
|
276
|
+
}
|
|
277
|
+
const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
|
|
278
|
+
"off",
|
|
279
|
+
"percent",
|
|
280
|
+
"bar",
|
|
281
|
+
"forecast"
|
|
282
|
+
].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
|
|
283
|
+
const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4" || modelId === "gpt-6-astra");
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/preference-fields.js
|
|
286
|
+
const PREFERENCE_FIELDS = Object.freeze({
|
|
287
|
+
[QUICK_QUOTA_MODE_FIELD]: {
|
|
288
|
+
choices: [
|
|
289
|
+
"off",
|
|
290
|
+
QUICK_QUOTA_MODE_PERCENT,
|
|
291
|
+
"bar",
|
|
292
|
+
QUICK_QUOTA_MODE_FORECAST
|
|
293
|
+
],
|
|
294
|
+
error: "Invalid quick quota preference"
|
|
295
|
+
},
|
|
296
|
+
[SEARCH_PROVIDER_FIELD]: {
|
|
297
|
+
choices: [
|
|
298
|
+
SEARCH_PROVIDER_AUTO,
|
|
299
|
+
"dsh",
|
|
300
|
+
SEARCH_PROVIDER_CODEX
|
|
301
|
+
],
|
|
302
|
+
default: DEFAULT_SEARCH_PROVIDER,
|
|
303
|
+
error: "Invalid search provider preference"
|
|
304
|
+
},
|
|
305
|
+
[SPEED_MODE_FIELD]: {
|
|
306
|
+
choices: [SPEED_MODE_STANDARD, SPEED_MODE_FAST],
|
|
307
|
+
default: DEFAULT_SPEED_MODE,
|
|
308
|
+
error: "Invalid speed mode preference"
|
|
309
|
+
},
|
|
310
|
+
[OUTPUT_VERBOSITY_FIELD]: {
|
|
311
|
+
choices: [
|
|
312
|
+
OUTPUT_VERBOSITY_DEFAULT,
|
|
313
|
+
"low",
|
|
314
|
+
OUTPUT_VERBOSITY_MEDIUM,
|
|
315
|
+
OUTPUT_VERBOSITY_HIGH
|
|
316
|
+
],
|
|
317
|
+
default: DEFAULT_OUTPUT_VERBOSITY,
|
|
318
|
+
error: "Invalid output verbosity preference"
|
|
319
|
+
},
|
|
320
|
+
[CONTEXT_MODE_FIELD]: {
|
|
321
|
+
choices: [
|
|
322
|
+
CONTEXT_MODE_STANDARD,
|
|
323
|
+
CONTEXT_MODE_EXTENDED,
|
|
324
|
+
CONTEXT_MODE_CUSTOM
|
|
325
|
+
],
|
|
326
|
+
default: DEFAULT_CONTEXT_MODE,
|
|
327
|
+
error: "Invalid context mode preference"
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/rpc-contract.js
|
|
332
|
+
const RPC_ENDPOINTS = Object.freeze([
|
|
333
|
+
"status",
|
|
334
|
+
"login/start",
|
|
335
|
+
"login/status",
|
|
336
|
+
"login/submit",
|
|
337
|
+
"login/cancel",
|
|
338
|
+
"logout",
|
|
339
|
+
"account/select",
|
|
340
|
+
"account/remove",
|
|
341
|
+
"usage",
|
|
342
|
+
"diagnostics",
|
|
343
|
+
"preferences/status",
|
|
344
|
+
"preferences/models",
|
|
345
|
+
"preferences/update",
|
|
346
|
+
"reset-credit/inspect",
|
|
347
|
+
"reset-credit/prepare",
|
|
348
|
+
"reset-credit/consume",
|
|
349
|
+
"image/original/chunk"
|
|
350
|
+
]);
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/subscription-transport.js
|
|
353
|
+
/** Exact routes stay inside DSH's authenticated /api bridge and body limit. */
|
|
354
|
+
function registerSubscriptionTransport(connection, handler) {
|
|
355
|
+
const disposers = [];
|
|
356
|
+
try {
|
|
357
|
+
for (const endpoint of RPC_ENDPOINTS) {
|
|
358
|
+
const method = `codex-subscription/${endpoint}`;
|
|
359
|
+
disposers.push(connection.fetch.register({
|
|
360
|
+
path: `/api/${method}`,
|
|
361
|
+
methods: ["POST"],
|
|
362
|
+
requestBody: "buffered",
|
|
363
|
+
async fetch(request) {
|
|
364
|
+
if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return new Response("content type must be application/json", { status: 415 });
|
|
365
|
+
let body;
|
|
366
|
+
try {
|
|
367
|
+
body = await request.json();
|
|
368
|
+
} catch {
|
|
369
|
+
return new Response("invalid JSON", { status: 400 });
|
|
370
|
+
}
|
|
371
|
+
const envelope = clientRequestSchema.safeParse(body);
|
|
372
|
+
if (!envelope.success || envelope.data.method !== method) return new Response("invalid RPC envelope", { status: 400 });
|
|
373
|
+
let result;
|
|
374
|
+
try {
|
|
375
|
+
request.signal.throwIfAborted();
|
|
376
|
+
result = await handler(endpoint, envelope.data.payload, request.signal);
|
|
377
|
+
} catch {
|
|
378
|
+
result = {
|
|
379
|
+
ok: false,
|
|
380
|
+
error: {
|
|
381
|
+
code: "internal",
|
|
382
|
+
message: "Subscription request failed",
|
|
383
|
+
details: { issues: [] }
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return Response.json({
|
|
388
|
+
type: "server-response",
|
|
389
|
+
rpcId: envelope.data.rpcId,
|
|
390
|
+
result
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
for (const dispose of disposers.reverse()) dispose();
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
return () => {
|
|
400
|
+
for (const dispose of disposers.reverse()) dispose();
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
20
404
|
//#region src/account-vault.js
|
|
21
405
|
const VERSION = 1;
|
|
22
406
|
const DEFAULT_LABEL = "Account 1";
|
|
@@ -1126,111 +1510,6 @@ function createCodexNetworkTransport(options = {}) {
|
|
|
1126
1510
|
});
|
|
1127
1511
|
}
|
|
1128
1512
|
//#endregion
|
|
1129
|
-
//#region src/settings-contract.js
|
|
1130
|
-
const SETTINGS_NAMESPACE = "codex-subscription";
|
|
1131
|
-
const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
|
|
1132
|
-
const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
|
|
1133
|
-
const QUICK_QUOTA_MODE_PERCENT = "percent";
|
|
1134
|
-
const QUICK_QUOTA_MODE_FORECAST = "forecast";
|
|
1135
|
-
const SEARCH_PROVIDER_FIELD = "searchProvider";
|
|
1136
|
-
const SEARCH_PROVIDER_AUTO = "auto";
|
|
1137
|
-
const SEARCH_PROVIDER_CODEX = "codex";
|
|
1138
|
-
const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
|
|
1139
|
-
const SPEED_MODE_FIELD = "speedMode";
|
|
1140
|
-
const SPEED_MODE_STANDARD = "standard";
|
|
1141
|
-
const SPEED_MODE_FAST = "fast";
|
|
1142
|
-
const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
|
|
1143
|
-
const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
|
|
1144
|
-
const OUTPUT_VERBOSITY_DEFAULT = "default";
|
|
1145
|
-
const OUTPUT_VERBOSITY_MEDIUM = "medium";
|
|
1146
|
-
const OUTPUT_VERBOSITY_HIGH = "high";
|
|
1147
|
-
const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
|
|
1148
|
-
const CONTEXT_MODE_FIELD = "contextMode";
|
|
1149
|
-
const CONTEXT_MODE_STANDARD = "standard";
|
|
1150
|
-
const CONTEXT_MODE_EXTENDED = "extended";
|
|
1151
|
-
const CONTEXT_MODE_CUSTOM = "custom";
|
|
1152
|
-
const DEFAULT_CONTEXT_MODE = CONTEXT_MODE_STANDARD;
|
|
1153
|
-
const CUSTOM_CONTEXT_WINDOW_FIELD = "customContextWindow";
|
|
1154
|
-
const DEFAULT_CUSTOM_CONTEXT_WINDOW = 272e3;
|
|
1155
|
-
const MIN_CUSTOM_CONTEXT_WINDOW = 128e3;
|
|
1156
|
-
const MAX_CUSTOM_CONTEXT_WINDOW = 1e6;
|
|
1157
|
-
const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
|
|
1158
|
-
"gpt-5.4": "customContextGpt54",
|
|
1159
|
-
"gpt-5.4-mini": "customContextGpt54Mini",
|
|
1160
|
-
"gpt-5.5": "customContextGpt55",
|
|
1161
|
-
"gpt-5.6": "customContextGpt56",
|
|
1162
|
-
"gpt-6-astra": "customContextGpt6Astra"
|
|
1163
|
-
});
|
|
1164
|
-
const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
|
|
1165
|
-
"gpt-5.4": 1e6,
|
|
1166
|
-
"gpt-5.4-mini": 4e5,
|
|
1167
|
-
"gpt-5.5": 1e6,
|
|
1168
|
-
"gpt-5.6": 1e6,
|
|
1169
|
-
"gpt-6-astra": 872e3
|
|
1170
|
-
});
|
|
1171
|
-
const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
|
|
1172
|
-
"gpt-5.4": 272e3,
|
|
1173
|
-
"gpt-5.4-mini": 272e3,
|
|
1174
|
-
"gpt-5.5": 272e3,
|
|
1175
|
-
"gpt-5.6": 272e3,
|
|
1176
|
-
"gpt-6-astra": 272e3
|
|
1177
|
-
});
|
|
1178
|
-
const normalizeOutputVerbosity = (value) => [
|
|
1179
|
-
"default",
|
|
1180
|
-
"low",
|
|
1181
|
-
"medium",
|
|
1182
|
-
"high"
|
|
1183
|
-
].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
|
|
1184
|
-
const normalizeContextMode = (value) => [
|
|
1185
|
-
"standard",
|
|
1186
|
-
"extended",
|
|
1187
|
-
"custom"
|
|
1188
|
-
].includes(value) ? value : DEFAULT_CONTEXT_MODE;
|
|
1189
|
-
const normalizeCustomContextWindow = (value, maximum = MAX_CUSTOM_CONTEXT_WINDOW) => {
|
|
1190
|
-
if (!Number.isInteger(value)) return DEFAULT_CUSTOM_CONTEXT_WINDOW;
|
|
1191
|
-
return Math.min(Math.max(value, MIN_CUSTOM_CONTEXT_WINDOW), maximum);
|
|
1192
|
-
};
|
|
1193
|
-
const customContextModelKey = (modelId) => modelId?.startsWith("gpt-5.6-") ? "gpt-5.6" : modelId;
|
|
1194
|
-
function contextModelGroups(models) {
|
|
1195
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1196
|
-
for (const model of models ?? []) {
|
|
1197
|
-
if (model?.id === "gpt-5.3-codex-spark") {
|
|
1198
|
-
groups.set(model.id, {
|
|
1199
|
-
key: model.id,
|
|
1200
|
-
label: model.name ?? model.id,
|
|
1201
|
-
maximum: 128e3,
|
|
1202
|
-
fixed: true
|
|
1203
|
-
});
|
|
1204
|
-
continue;
|
|
1205
|
-
}
|
|
1206
|
-
const key = customContextModelKey(model?.id);
|
|
1207
|
-
if (!Object.hasOwn(CUSTOM_CONTEXT_MODEL_FIELDS, key)) continue;
|
|
1208
|
-
if (key !== "gpt-5.6") {
|
|
1209
|
-
groups.set(key, {
|
|
1210
|
-
key,
|
|
1211
|
-
label: model.name ?? model.id,
|
|
1212
|
-
maximum: CUSTOM_CONTEXT_MODEL_CAPS[key]
|
|
1213
|
-
});
|
|
1214
|
-
continue;
|
|
1215
|
-
}
|
|
1216
|
-
const variant = String(model.name ?? model.id).replace(/^GPT-5\.6[ -]/iu, "");
|
|
1217
|
-
const current = groups.get(key);
|
|
1218
|
-
groups.set(key, {
|
|
1219
|
-
key,
|
|
1220
|
-
label: `GPT-5.6 ${current === void 0 ? variant : `${current.label.replace(/^GPT-5\.6 /u, "")} / ${variant}`}`,
|
|
1221
|
-
maximum: CUSTOM_CONTEXT_MODEL_CAPS[key]
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1224
|
-
return [...groups.values()];
|
|
1225
|
-
}
|
|
1226
|
-
const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
|
|
1227
|
-
"off",
|
|
1228
|
-
"percent",
|
|
1229
|
-
"bar",
|
|
1230
|
-
"forecast"
|
|
1231
|
-
].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
|
|
1232
|
-
const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
|
|
1233
|
-
//#endregion
|
|
1234
1513
|
//#region src/pi-ai-runtime.js
|
|
1235
1514
|
const FAST_SERVICE_TIER = "priority";
|
|
1236
1515
|
/**
|
|
@@ -1245,15 +1524,6 @@ const FAST_SERVICE_TIER = "priority";
|
|
|
1245
1524
|
* persistence, headers, transport, and model behavior remain owned by the
|
|
1246
1525
|
* original provider.
|
|
1247
1526
|
*/
|
|
1248
|
-
const EXTENDED_CONTEXT_WINDOWS = Object.freeze({
|
|
1249
|
-
"gpt-5.4": 1e6,
|
|
1250
|
-
"gpt-5.4-mini": 4e5,
|
|
1251
|
-
"gpt-5.5": 1e6,
|
|
1252
|
-
"gpt-5.6-luna": 1e6,
|
|
1253
|
-
"gpt-5.6-sol": 1e6,
|
|
1254
|
-
"gpt-5.6-terra": 1e6,
|
|
1255
|
-
"gpt-6-astra": CUSTOM_CONTEXT_MODEL_CAPS["gpt-6-astra"]
|
|
1256
|
-
});
|
|
1257
1527
|
function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, runNetwork = (_area, operation) => operation() } = {}) {
|
|
1258
1528
|
const provider = createOpenAICodexProvider();
|
|
1259
1529
|
const requestToken = Object.freeze({
|
|
@@ -1301,17 +1571,17 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1301
1571
|
};
|
|
1302
1572
|
};
|
|
1303
1573
|
const getModels = () => (catalog?.getModels(provider.getModels()) ?? provider.getModels()).map((model) => {
|
|
1304
|
-
const maximum =
|
|
1574
|
+
const maximum = modelContextMaximum(model);
|
|
1305
1575
|
const mode = resolveContextMode();
|
|
1306
|
-
if (
|
|
1576
|
+
if (model.id === "gpt-5.3-codex-spark" || !["extended", "custom"].includes(mode)) return model;
|
|
1307
1577
|
if (mode === "extended") {
|
|
1308
|
-
const contextWindow =
|
|
1578
|
+
const contextWindow = maximum;
|
|
1309
1579
|
return {
|
|
1310
1580
|
...model,
|
|
1311
1581
|
contextWindow
|
|
1312
1582
|
};
|
|
1313
1583
|
}
|
|
1314
|
-
const requested =
|
|
1584
|
+
const requested = clampModelContext(resolveCustomContextWindow(customContextModelKey(model.id)), maximum, model.contextWindow);
|
|
1315
1585
|
return {
|
|
1316
1586
|
...model,
|
|
1317
1587
|
contextWindow: requested
|
|
@@ -1345,7 +1615,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1345
1615
|
}
|
|
1346
1616
|
//#endregion
|
|
1347
1617
|
//#region src/version.js
|
|
1348
|
-
const PACKAGE_VERSION = "
|
|
1618
|
+
const PACKAGE_VERSION = "2.0.0";
|
|
1349
1619
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
1350
1620
|
//#endregion
|
|
1351
1621
|
//#region src/model-catalog.js
|
|
@@ -1383,6 +1653,7 @@ function visibleModel(value) {
|
|
|
1383
1653
|
priority: Number.isFinite(value.priority) ? value.priority : 0,
|
|
1384
1654
|
input: input.length > 0 ? input : ["text"],
|
|
1385
1655
|
contextWindow: positiveInteger$1(value.context_window) ?? positiveInteger$1(value.max_context_window),
|
|
1656
|
+
...positiveInteger$1(value.max_context_window) === void 0 ? {} : { maxContextWindow: value.max_context_window },
|
|
1386
1657
|
reasoning: supported.length > 0,
|
|
1387
1658
|
thinkingLevelMap: reasoningMap(supported),
|
|
1388
1659
|
supportVerbosity: value.support_verbosity === true,
|
|
@@ -1410,6 +1681,7 @@ function mergeModel(baseModels, remote) {
|
|
|
1410
1681
|
reasoning: remote.reasoning,
|
|
1411
1682
|
thinkingLevelMap: remote.thinkingLevelMap,
|
|
1412
1683
|
...remote.contextWindow === void 0 ? {} : { contextWindow: remote.contextWindow },
|
|
1684
|
+
...remote.maxContextWindow === void 0 ? {} : { maxContextWindow: remote.maxContextWindow },
|
|
1413
1685
|
...base.id === remote.id ? {} : { cost: {
|
|
1414
1686
|
input: 0,
|
|
1415
1687
|
output: 0,
|
|
@@ -1429,10 +1701,13 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1429
1701
|
let revision = 0;
|
|
1430
1702
|
let refreshing;
|
|
1431
1703
|
let generation = 0;
|
|
1704
|
+
let refreshStatus = "idle";
|
|
1432
1705
|
const refresh = ({ signal } = {}) => {
|
|
1433
1706
|
if (signal?.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
|
|
1434
1707
|
if (refreshing?.generation === generation) return refreshing.promise;
|
|
1435
1708
|
const currentGeneration = generation;
|
|
1709
|
+
refreshStatus = "refreshing";
|
|
1710
|
+
let outcome = "idle";
|
|
1436
1711
|
const controller = new AbortController();
|
|
1437
1712
|
const abort = () => {
|
|
1438
1713
|
if (!controller.signal.aborted) controller.abort(signal?.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
|
|
@@ -1464,7 +1739,10 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1464
1739
|
signal: requestSignal
|
|
1465
1740
|
});
|
|
1466
1741
|
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
1467
|
-
if (response.status === 304)
|
|
1742
|
+
if (response.status === 304) {
|
|
1743
|
+
outcome = "ok";
|
|
1744
|
+
return false;
|
|
1745
|
+
}
|
|
1468
1746
|
if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
|
|
1469
1747
|
const remote = parseOfficialModelCatalog(await response.json());
|
|
1470
1748
|
if (currentGeneration !== generation || requestSignal.aborted) return false;
|
|
@@ -1477,6 +1755,7 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1477
1755
|
metadata = new Map(remote.map((model) => [model.id, model]));
|
|
1478
1756
|
etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
|
|
1479
1757
|
revision += 1;
|
|
1758
|
+
outcome = "ok";
|
|
1480
1759
|
return true;
|
|
1481
1760
|
})();
|
|
1482
1761
|
let rejectAborted;
|
|
@@ -1487,11 +1766,17 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1487
1766
|
});
|
|
1488
1767
|
timer = scheduleTimeout(() => controller.abort(timeoutError), timeoutMs);
|
|
1489
1768
|
timer.unref?.();
|
|
1490
|
-
const promise = Promise.race([work, abortPromise]).
|
|
1769
|
+
const promise = Promise.race([work, abortPromise]).catch((error) => {
|
|
1770
|
+
outcome = "failed";
|
|
1771
|
+
throw error;
|
|
1772
|
+
}).finally(() => {
|
|
1491
1773
|
cancelTimeout(timer);
|
|
1492
1774
|
signal?.removeEventListener("abort", abort);
|
|
1493
1775
|
requestSignal.removeEventListener("abort", rejectAborted);
|
|
1494
|
-
if (refreshing?.promise === promise)
|
|
1776
|
+
if (refreshing?.promise === promise) {
|
|
1777
|
+
refreshing = void 0;
|
|
1778
|
+
refreshStatus = outcome;
|
|
1779
|
+
}
|
|
1495
1780
|
});
|
|
1496
1781
|
refreshing = {
|
|
1497
1782
|
generation: currentGeneration,
|
|
@@ -1505,6 +1790,10 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1505
1790
|
getModels: (fallback) => models ?? fallback,
|
|
1506
1791
|
metadata: (modelId) => metadata.get(modelId),
|
|
1507
1792
|
revision: () => revision,
|
|
1793
|
+
status: () => ({
|
|
1794
|
+
source: models === void 0 ? "fallback" : "online",
|
|
1795
|
+
refresh: refreshStatus
|
|
1796
|
+
}),
|
|
1508
1797
|
clear() {
|
|
1509
1798
|
generation += 1;
|
|
1510
1799
|
const flight = refreshing;
|
|
@@ -1513,6 +1802,7 @@ function createOfficialModelCatalog(options = {}) {
|
|
|
1513
1802
|
models = void 0;
|
|
1514
1803
|
metadata = /* @__PURE__ */ new Map();
|
|
1515
1804
|
etag = void 0;
|
|
1805
|
+
refreshStatus = "idle";
|
|
1516
1806
|
revision += 1;
|
|
1517
1807
|
}
|
|
1518
1808
|
});
|
|
@@ -1580,6 +1870,9 @@ function createCodexSearchProvider(options) {
|
|
|
1580
1870
|
id: CODEX_SEARCH_PROVIDER_ID,
|
|
1581
1871
|
available: () => true,
|
|
1582
1872
|
async search(request, signal) {
|
|
1873
|
+
signal?.throwIfAborted();
|
|
1874
|
+
const preferences = readCapabilitySettings(options.resolvePreferences?.());
|
|
1875
|
+
if (preferences.searchMode === "disabled") throw new WebError("Codex search is disabled in subscription settings", "WEB_PROVIDER_UNAVAILABLE");
|
|
1583
1876
|
const auth = await options.getAuth({ signal });
|
|
1584
1877
|
const credential = await options.readCredential({ signal });
|
|
1585
1878
|
const access = auth?.auth?.apiKey;
|
|
@@ -1610,7 +1903,7 @@ function createCodexSearchProvider(options) {
|
|
|
1610
1903
|
},
|
|
1611
1904
|
settings: {
|
|
1612
1905
|
allowed_callers: ["direct"],
|
|
1613
|
-
external_web_access:
|
|
1906
|
+
external_web_access: preferences.searchMode === "live"
|
|
1614
1907
|
},
|
|
1615
1908
|
max_output_tokens: MAX_OUTPUT_TOKENS
|
|
1616
1909
|
}),
|
|
@@ -1628,7 +1921,12 @@ function createCodexSearchProvider(options) {
|
|
|
1628
1921
|
throw new WebError("Codex returned an unreadable search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
1629
1922
|
}
|
|
1630
1923
|
try {
|
|
1631
|
-
|
|
1924
|
+
const result = parseSearchResponse(value);
|
|
1925
|
+
if (preferences.searchDomains.length > 0) result.sources = result.sources.filter((source) => {
|
|
1926
|
+
const hostname = new URL(source.url).hostname.toLowerCase();
|
|
1927
|
+
return preferences.searchDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
1928
|
+
});
|
|
1929
|
+
return result;
|
|
1632
1930
|
} catch (error) {
|
|
1633
1931
|
throw new WebError("Codex returned a malformed search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
1634
1932
|
}
|
|
@@ -1696,15 +1994,8 @@ function inheritedOriginalImageRef(session, assetId) {
|
|
|
1696
1994
|
const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
|
|
1697
1995
|
const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
|
|
1698
1996
|
const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits";
|
|
1699
|
-
const IMAGE_MODEL = "gpt-image-2";
|
|
1700
1997
|
const MAX_REFERENCE_IMAGES = 5;
|
|
1701
1998
|
const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
|
|
1702
|
-
const IMAGE_QUALITIES = /* @__PURE__ */ new Set([
|
|
1703
|
-
"auto",
|
|
1704
|
-
"low",
|
|
1705
|
-
"medium",
|
|
1706
|
-
"high"
|
|
1707
|
-
]);
|
|
1708
1999
|
const IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
|
|
1709
2000
|
"auto",
|
|
1710
2001
|
"transparent",
|
|
@@ -1730,7 +2021,7 @@ function normalizeImageOptions(args) {
|
|
|
1730
2021
|
const quality = nonEmpty(args?.quality) ?? "auto";
|
|
1731
2022
|
const background = nonEmpty(args?.background) ?? "auto";
|
|
1732
2023
|
const size = nonEmpty(args?.size) ?? "auto";
|
|
1733
|
-
|
|
2024
|
+
validateImageQuality(resolveImageModel(args?.model), quality);
|
|
1734
2025
|
if (!IMAGE_BACKGROUNDS.has(background)) throw new Error("background must be auto, transparent, or opaque");
|
|
1735
2026
|
if (size !== "auto") {
|
|
1736
2027
|
const match = /^(\d+)x(\d+)$/u.exec(size);
|
|
@@ -1956,6 +2247,9 @@ function imageOutputSchema() {
|
|
|
1956
2247
|
}
|
|
1957
2248
|
},
|
|
1958
2249
|
background: { type: "string" },
|
|
2250
|
+
requestedModel: { type: "string" },
|
|
2251
|
+
reportedModel: { type: "string" },
|
|
2252
|
+
requestedSize: { type: "string" },
|
|
1959
2253
|
localPath: {
|
|
1960
2254
|
type: "string",
|
|
1961
2255
|
required: true,
|
|
@@ -1974,7 +2268,8 @@ function responseMetadata(value) {
|
|
|
1974
2268
|
encoded,
|
|
1975
2269
|
background: nonEmpty(value.background),
|
|
1976
2270
|
quality: nonEmpty(value.quality),
|
|
1977
|
-
size: nonEmpty(value.size)
|
|
2271
|
+
size: nonEmpty(value.size),
|
|
2272
|
+
reportedModel: nonEmpty(value.model)
|
|
1978
2273
|
};
|
|
1979
2274
|
}
|
|
1980
2275
|
/** Create the DSH-native image-generation tool backed only by the ChatGPT subscription. */
|
|
@@ -1983,8 +2278,13 @@ function createCodexImageTool(options) {
|
|
|
1983
2278
|
const attachments = options.attachments;
|
|
1984
2279
|
return defineTool({
|
|
1985
2280
|
name: CODEX_IMAGE_TOOL_NAME,
|
|
1986
|
-
description: "
|
|
2281
|
+
description: "Generate or edit images only when the user asks for image output, not when merely discussing images. Uses the signed-in Codex subscription. For a new image, omit referenceImages. For edits, copy complete references only for the images the user selected; obtain missing references using read_image. Never substitute paths, unrelated images, or text-only generation for an edit. For numbered annotations, include the clean source and location-reference image, preserve the requested changes and coordinates in the prompt, and remove guidance markers from the result. If the intended references cannot be identified, ask rather than guessing.",
|
|
1987
2282
|
parameters: {
|
|
2283
|
+
model: {
|
|
2284
|
+
type: "string",
|
|
2285
|
+
enum: Object.keys(IMAGE_MODELS),
|
|
2286
|
+
description: "Optional image engine, independent of the conversation model. When omitted, uses the user image setting (initially gpt-image-2). The 2.5 identifiers are experimental subscription candidates; override only when explicitly requested. Never silently retry with another model."
|
|
2287
|
+
},
|
|
1988
2288
|
prompt: {
|
|
1989
2289
|
type: "string",
|
|
1990
2290
|
required: true,
|
|
@@ -2000,7 +2300,9 @@ function createCodexImageTool(options) {
|
|
|
2000
2300
|
"auto",
|
|
2001
2301
|
"low",
|
|
2002
2302
|
"medium",
|
|
2003
|
-
"high"
|
|
2303
|
+
"high",
|
|
2304
|
+
"xhigh",
|
|
2305
|
+
"max"
|
|
2004
2306
|
],
|
|
2005
2307
|
description: "Optional rendering quality. Use auto unless the user requests draft speed or final quality."
|
|
2006
2308
|
},
|
|
@@ -2066,12 +2368,22 @@ function createCodexImageTool(options) {
|
|
|
2066
2368
|
presentationMeta: (_args, value) => ({
|
|
2067
2369
|
kind: "codex-subscription-image",
|
|
2068
2370
|
schemaVersion: 1,
|
|
2069
|
-
original: value.original
|
|
2371
|
+
original: value.original,
|
|
2372
|
+
...value.requestedModel === void 0 ? {} : { requestedModel: value.requestedModel },
|
|
2373
|
+
...value.reportedModel === void 0 ? {} : { reportedModel: value.reportedModel },
|
|
2374
|
+
...value.requestedSize === void 0 ? {} : { requestedSize: value.requestedSize }
|
|
2070
2375
|
})
|
|
2071
2376
|
},
|
|
2072
2377
|
timeoutMs: 300 * 1e3,
|
|
2073
2378
|
isConcurrencySafe: () => false,
|
|
2074
2379
|
async execute(args, exec) {
|
|
2380
|
+
assertImageOperation(options.getFeatures?.(), args.referenceImages !== void 0);
|
|
2381
|
+
const defaults = readImageDefaults(options.getFeatures?.());
|
|
2382
|
+
args = {
|
|
2383
|
+
...args,
|
|
2384
|
+
model: args.model ?? defaults.imageModel,
|
|
2385
|
+
quality: args.quality ?? defaults.imageQuality
|
|
2386
|
+
};
|
|
2075
2387
|
const prompt = nonEmpty(args.prompt);
|
|
2076
2388
|
if (prompt === void 0) throw new Error("prompt must be a non-empty string");
|
|
2077
2389
|
const imageOptions = normalizeImageOptions(args);
|
|
@@ -2086,6 +2398,7 @@ function createCodexImageTool(options) {
|
|
|
2086
2398
|
const sessionMessages = editing && typeof options.getSessionMessages === "function" ? await options.getSessionMessages(exec.agent?.id) : void 0;
|
|
2087
2399
|
const images = editing ? await editImages(args.referenceImages, attachments, exec.signal, sessionMessages ?? (options.getSessionMessages ? [] : void 0)) : void 0;
|
|
2088
2400
|
let response;
|
|
2401
|
+
assertImageOperation(options.getFeatures?.(), editing);
|
|
2089
2402
|
try {
|
|
2090
2403
|
response = await fetchImage(editing ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_GENERATION_URL, {
|
|
2091
2404
|
method: "POST",
|
|
@@ -2103,7 +2416,7 @@ function createCodexImageTool(options) {
|
|
|
2103
2416
|
...images === void 0 ? {} : { images },
|
|
2104
2417
|
prompt,
|
|
2105
2418
|
background: imageOptions.background,
|
|
2106
|
-
model:
|
|
2419
|
+
model: resolveImageModel(args.model),
|
|
2107
2420
|
quality: imageOptions.quality,
|
|
2108
2421
|
size: imageOptions.size
|
|
2109
2422
|
}),
|
|
@@ -2135,6 +2448,9 @@ function createCodexImageTool(options) {
|
|
|
2135
2448
|
throw error;
|
|
2136
2449
|
}
|
|
2137
2450
|
const result = {
|
|
2451
|
+
requestedModel: resolveImageModel(args.model),
|
|
2452
|
+
requestedSize: imageOptions.size,
|
|
2453
|
+
...metadata.reportedModel === void 0 ? {} : { reportedModel: metadata.reportedModel },
|
|
2138
2454
|
image: imageReference(ref),
|
|
2139
2455
|
original,
|
|
2140
2456
|
localPath: options.originalImages.originalPath(original.assetId),
|
|
@@ -2154,6 +2470,27 @@ function createCodexImageTool(options) {
|
|
|
2154
2470
|
});
|
|
2155
2471
|
}
|
|
2156
2472
|
//#endregion
|
|
2473
|
+
//#region src/image-tool-registration.js
|
|
2474
|
+
/** Removing the tool also removes its schema from subsequent model requests. */
|
|
2475
|
+
function watchImageTool(settings, register) {
|
|
2476
|
+
let disposeTool;
|
|
2477
|
+
const sync = (value) => {
|
|
2478
|
+
const { imageGeneration, imageEditing } = readImageFeatures(value);
|
|
2479
|
+
if ((imageGeneration || imageEditing) && !disposeTool) disposeTool = register();
|
|
2480
|
+
else if (!imageGeneration && !imageEditing && disposeTool) {
|
|
2481
|
+
disposeTool();
|
|
2482
|
+
disposeTool = void 0;
|
|
2483
|
+
}
|
|
2484
|
+
};
|
|
2485
|
+
sync(settings.get());
|
|
2486
|
+
const unwatch = settings.watch(sync);
|
|
2487
|
+
return () => {
|
|
2488
|
+
unwatch();
|
|
2489
|
+
disposeTool?.();
|
|
2490
|
+
disposeTool = void 0;
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
//#endregion
|
|
2157
2494
|
//#region src/image-original-store.js
|
|
2158
2495
|
const ORIGINAL_IMAGE_DIRECTORY = "dsh-codex-subscription/images/v1";
|
|
2159
2496
|
const METADATA_VERSION = 1;
|
|
@@ -2268,7 +2605,8 @@ var OriginalImageStore = class {
|
|
|
2268
2605
|
await Promise.all([assertPrivateFile(metadataFile), assertPrivateFile(originalFile)]);
|
|
2269
2606
|
const metadata = parseMetadata(await readFile(metadataFile, "utf8"));
|
|
2270
2607
|
if (metadata === void 0 || metadata.image.assetId !== assetId || metadata.sessionId !== sessionId && !originalImageRefsEqual(metadata.image, inherited)) return void 0;
|
|
2271
|
-
const
|
|
2608
|
+
const buffer = await readFile(originalFile);
|
|
2609
|
+
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
2272
2610
|
const dimensions = pngDimensions(data);
|
|
2273
2611
|
if (data.byteLength !== metadata.image.bytes || digest(data) !== metadata.image.sha256 || dimensions.width !== metadata.image.width || dimensions.height !== metadata.image.height) return void 0;
|
|
2274
2612
|
return {
|
|
@@ -2284,10 +2622,11 @@ var OriginalImageStore = class {
|
|
|
2284
2622
|
const stored = await this.read(sessionId, assetId, inherited);
|
|
2285
2623
|
if (stored === void 0 || offset >= stored.data.byteLength || offset % 4194304 !== 0) return void 0;
|
|
2286
2624
|
const end = Math.min(stored.data.byteLength, offset + ORIGINAL_IMAGE_CHUNK_BYTES);
|
|
2625
|
+
const chunk = Buffer.from(stored.data.buffer, stored.data.byteOffset + offset, end - offset);
|
|
2287
2626
|
return {
|
|
2288
2627
|
ref: stored.ref,
|
|
2289
2628
|
offset,
|
|
2290
|
-
encoded:
|
|
2629
|
+
encoded: chunk.toString("base64"),
|
|
2291
2630
|
done: end === stored.data.byteLength
|
|
2292
2631
|
};
|
|
2293
2632
|
}
|
|
@@ -2297,6 +2636,7 @@ var OriginalImageStore = class {
|
|
|
2297
2636
|
const requestAreas = /* @__PURE__ */ new Set([
|
|
2298
2637
|
"login",
|
|
2299
2638
|
"model",
|
|
2639
|
+
"catalog",
|
|
2300
2640
|
"quota",
|
|
2301
2641
|
"quota-reset",
|
|
2302
2642
|
"search",
|
|
@@ -2342,7 +2682,7 @@ function safeRequests(network) {
|
|
|
2342
2682
|
return result;
|
|
2343
2683
|
}
|
|
2344
2684
|
/** Build a support report that deliberately excludes OAuth and account metadata. */
|
|
2345
|
-
async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network }) {
|
|
2685
|
+
async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog }) {
|
|
2346
2686
|
let account = { status: "unknown" };
|
|
2347
2687
|
const issues = [];
|
|
2348
2688
|
try {
|
|
@@ -2351,6 +2691,7 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
|
|
|
2351
2691
|
issues.push({ code: "account-status-unavailable" });
|
|
2352
2692
|
}
|
|
2353
2693
|
const preference = preferences.status();
|
|
2694
|
+
const catalog = modelCatalog?.status?.();
|
|
2354
2695
|
return {
|
|
2355
2696
|
schemaVersion: 3,
|
|
2356
2697
|
package: "dsh-codex-subscription",
|
|
@@ -2363,6 +2704,15 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
|
|
|
2363
2704
|
account,
|
|
2364
2705
|
login,
|
|
2365
2706
|
requests: safeRequests(network),
|
|
2707
|
+
...catalog && ["fallback", "online"].includes(catalog.source) && [
|
|
2708
|
+
"idle",
|
|
2709
|
+
"refreshing",
|
|
2710
|
+
"ok",
|
|
2711
|
+
"failed"
|
|
2712
|
+
].includes(catalog.refresh) ? { catalog: {
|
|
2713
|
+
source: catalog.source,
|
|
2714
|
+
refresh: catalog.refresh
|
|
2715
|
+
} } : {},
|
|
2366
2716
|
configuration: {
|
|
2367
2717
|
contextMode: preference.contextMode,
|
|
2368
2718
|
quickQuotaMode: preference.quickQuotaMode,
|
|
@@ -2601,12 +2951,74 @@ function createCodexUsageReader(options) {
|
|
|
2601
2951
|
});
|
|
2602
2952
|
}
|
|
2603
2953
|
//#endregion
|
|
2954
|
+
//#region src/quota-rate-interval.js
|
|
2955
|
+
function quotaRateInterval(samples, { quantum = 1, rounding = "unknown" } = {}) {
|
|
2956
|
+
if (!(quantum > 0) || !Number.isFinite(quantum)) throw new RangeError("Invalid quantum");
|
|
2957
|
+
const points = samples.map((sample) => {
|
|
2958
|
+
const used = 100 - sample.remainingPercent;
|
|
2959
|
+
const lower = rounding === "floor" ? used : used - quantum / (rounding === "nearest" ? 2 : 1);
|
|
2960
|
+
const upper = rounding === "floor" ? used + quantum : used + quantum / (rounding === "nearest" ? 2 : 1);
|
|
2961
|
+
return {
|
|
2962
|
+
at: sample.at,
|
|
2963
|
+
lower: Math.max(0, lower),
|
|
2964
|
+
upper: Math.min(100, upper)
|
|
2965
|
+
};
|
|
2966
|
+
});
|
|
2967
|
+
let min = 0, max = Infinity;
|
|
2968
|
+
for (let i = 0; i < points.length; i++) for (let j = i + 1; j < points.length; j++) {
|
|
2969
|
+
const minutes = (points[j].at - points[i].at) / 6e4;
|
|
2970
|
+
if (minutes <= 0) continue;
|
|
2971
|
+
min = Math.max(min, (points[j].lower - points[i].upper) / minutes);
|
|
2972
|
+
max = Math.min(max, (points[j].upper - points[i].lower) / minutes);
|
|
2973
|
+
}
|
|
2974
|
+
return {
|
|
2975
|
+
min,
|
|
2976
|
+
max,
|
|
2977
|
+
feasible: min <= max + 1e-10
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
function quotaSharedOffsetInterval(samples, { quantum = 1, maxLagMs = 12e4 } = {}) {
|
|
2981
|
+
if (!(quantum > 0) || !Number.isFinite(quantum) || !Number.isFinite(maxLagMs) || maxLagMs < 0) throw new RangeError("Invalid interval options");
|
|
2982
|
+
let min = 0, max = Infinity;
|
|
2983
|
+
for (let i = 0; i < samples.length; i++) for (let j = i + 1; j < samples.length; j++) {
|
|
2984
|
+
const elapsed = (samples[j].at - samples[i].at) / 6e4, lag = maxLagMs / 6e4;
|
|
2985
|
+
if (elapsed <= 0) continue;
|
|
2986
|
+
const delta = samples[i].remainingPercent - samples[j].remainingPercent;
|
|
2987
|
+
min = Math.max(min, (delta - quantum) / (elapsed + lag));
|
|
2988
|
+
if (elapsed > lag) max = Math.min(max, (delta + quantum) / (elapsed - lag));
|
|
2989
|
+
}
|
|
2990
|
+
return {
|
|
2991
|
+
min,
|
|
2992
|
+
max,
|
|
2993
|
+
feasible: min <= max + 1e-10
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
function refineQuotaRate(samples, conservative) {
|
|
2997
|
+
if (!conservative.feasible || samples.length < 4 || samples.at(-1).at - samples[0].at < 20 * 6e4) return conservative;
|
|
2998
|
+
let crossings = 0;
|
|
2999
|
+
for (let i = 0; i < samples.length; i++) {
|
|
3000
|
+
const value = samples[i].remainingPercent;
|
|
3001
|
+
if (!Number.isInteger(value) || value <= 0 || value >= 100) return conservative;
|
|
3002
|
+
if (i > 0) {
|
|
3003
|
+
if (value > samples[i - 1].remainingPercent || samples[i].at <= samples[i - 1].at) return conservative;
|
|
3004
|
+
if (value < samples[i - 1].remainingPercent) crossings++;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
if (crossings < 3) return conservative;
|
|
3008
|
+
const candidate = quotaSharedOffsetInterval(samples);
|
|
3009
|
+
if (!candidate.feasible || candidate.min <= 0) return conservative;
|
|
3010
|
+
if (candidate.max - candidate.min >= conservative.max - conservative.min) return conservative;
|
|
3011
|
+
return {
|
|
3012
|
+
...candidate,
|
|
3013
|
+
method: "shared-offset",
|
|
3014
|
+
maxLagMs: 12e4
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
//#endregion
|
|
2604
3018
|
//#region src/quota-forecast.js
|
|
2605
3019
|
const HOUR_MS = 3600 * 1e3;
|
|
2606
3020
|
const HISTORY_MS = 24 * HOUR_MS;
|
|
2607
|
-
const
|
|
2608
|
-
const PLATEAU_SAMPLE_MS = 900 * 1e3;
|
|
2609
|
-
const finite = (value) => Number.isFinite(Number(value));
|
|
3021
|
+
const finite = (value) => value !== null && value !== void 0 && Number.isFinite(Number(value));
|
|
2610
3022
|
const clampPercent = (value) => Math.max(0, Math.min(100, Number(value)));
|
|
2611
3023
|
const cleanSegment = (value) => String(value ?? "default").slice(0, 96);
|
|
2612
3024
|
const keyFor = (window, context = {}) => JSON.stringify([
|
|
@@ -2614,17 +3026,6 @@ const keyFor = (window, context = {}) => JSON.stringify([
|
|
|
2614
3026
|
cleanSegment(context.limitId ?? "codex"),
|
|
2615
3027
|
Number(window.windowSeconds) || "limit"
|
|
2616
3028
|
]);
|
|
2617
|
-
const median = (values) => {
|
|
2618
|
-
const ordered = [...values].sort((a, b) => a - b);
|
|
2619
|
-
const middle = Math.floor(ordered.length / 2);
|
|
2620
|
-
return ordered.length % 2 === 0 ? (ordered[middle - 1] + ordered[middle]) / 2 : ordered[middle];
|
|
2621
|
-
};
|
|
2622
|
-
function requiredSpanMs(consumedPercent) {
|
|
2623
|
-
if (consumedPercent >= 2) return 300 * 1e3;
|
|
2624
|
-
if (consumedPercent >= 1) return 600 * 1e3;
|
|
2625
|
-
if (consumedPercent >= .5) return 1200 * 1e3;
|
|
2626
|
-
return 1800 * 1e3;
|
|
2627
|
-
}
|
|
2628
3029
|
function observeQuotaForecast(state, windows, now = Date.now(), context = {}) {
|
|
2629
3030
|
const next = { windows: { ...state?.windows ?? {} } };
|
|
2630
3031
|
let changed = false;
|
|
@@ -2632,12 +3033,13 @@ function observeQuotaForecast(state, windows, now = Date.now(), context = {}) {
|
|
|
2632
3033
|
if (!finite(window?.remainingPercent)) continue;
|
|
2633
3034
|
const key = keyFor(window, context);
|
|
2634
3035
|
const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
|
|
2635
|
-
const remainingPercent =
|
|
3036
|
+
const remainingPercent = clampPercent(window.remainingPercent);
|
|
2636
3037
|
const previous = next.windows[key];
|
|
2637
3038
|
const resetChanged = previous !== void 0 && (previous.resetsAt === null !== (resetsAt === null) || previous.resetsAt !== null && Math.abs(previous.resetsAt - resetsAt) > 300);
|
|
2638
3039
|
const last = previous?.samples?.at(-1);
|
|
2639
3040
|
const quotaIncreased = last !== void 0 && remainingPercent > last.remainingPercent + .5;
|
|
2640
|
-
const
|
|
3041
|
+
const observationGap = last !== void 0 && now - last.at > 90 * 6e4;
|
|
3042
|
+
const record = resetChanged || quotaIncreased || observationGap ? {
|
|
2641
3043
|
resetsAt,
|
|
2642
3044
|
samples: []
|
|
2643
3045
|
} : {
|
|
@@ -2645,7 +3047,7 @@ function observeQuotaForecast(state, windows, now = Date.now(), context = {}) {
|
|
|
2645
3047
|
samples: [...previous?.samples ?? []]
|
|
2646
3048
|
};
|
|
2647
3049
|
const latest = record.samples.at(-1);
|
|
2648
|
-
if (latest === void 0 || now > latest.at
|
|
3050
|
+
if (latest === void 0 || now > latest.at) {
|
|
2649
3051
|
record.samples.push({
|
|
2650
3052
|
at: now,
|
|
2651
3053
|
remainingPercent
|
|
@@ -2666,59 +3068,98 @@ function estimateQuotaForecast(state, window, now = Date.now(), context = {}) {
|
|
|
2666
3068
|
if (record === void 0) return { status: "calibrating" };
|
|
2667
3069
|
const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
|
|
2668
3070
|
if (record.resetsAt === null !== (resetsAt === null) || resetsAt !== null && Math.abs(record.resetsAt - resetsAt) > 300) return { status: "calibrating" };
|
|
2669
|
-
|
|
2670
|
-
if (samples.length <
|
|
3071
|
+
let samples = record.samples.filter((sample) => sample.at >= now - 2 * HOUR_MS && sample.at <= now);
|
|
3072
|
+
if (samples.length < 2) return {
|
|
2671
3073
|
status: "calibrating",
|
|
2672
3074
|
sampleCount: samples.length
|
|
2673
3075
|
};
|
|
2674
|
-
|
|
2675
|
-
const last = samples.at(-1);
|
|
2676
|
-
const spanMs = last.at - first.at;
|
|
2677
|
-
const consumedPercent = Math.max(0, first.remainingPercent - last.remainingPercent);
|
|
2678
|
-
if (spanMs < requiredSpanMs(consumedPercent)) return {
|
|
3076
|
+
if (now - samples.at(-1).at > 20 * 6e4) return {
|
|
2679
3077
|
status: "calibrating",
|
|
3078
|
+
reason: "stale"
|
|
3079
|
+
};
|
|
3080
|
+
let bounds = quotaRateInterval(samples);
|
|
3081
|
+
let changedIntensity = false;
|
|
3082
|
+
while (!bounds.feasible && samples.length > 3) {
|
|
3083
|
+
samples = samples.slice(1);
|
|
3084
|
+
bounds = quotaRateInterval(samples);
|
|
3085
|
+
changedIntensity = true;
|
|
3086
|
+
}
|
|
3087
|
+
bounds = refineQuotaRate(samples, bounds);
|
|
3088
|
+
const spanMs = samples.at(-1).at - samples[0].at;
|
|
3089
|
+
const common = {
|
|
2680
3090
|
sampleCount: samples.length,
|
|
2681
3091
|
observedSpanMs: spanMs,
|
|
2682
|
-
consumedPercent
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
consumedPercent
|
|
3092
|
+
consumedPercent: samples[0].remainingPercent - samples.at(-1).remainingPercent,
|
|
3093
|
+
lowerPacePerHour: bounds.min * 60,
|
|
3094
|
+
upperPacePerHour: bounds.max * 60,
|
|
3095
|
+
changedIntensity,
|
|
3096
|
+
rateMethod: bounds.method ?? "conservative"
|
|
3097
|
+
};
|
|
3098
|
+
if (!bounds.feasible) return {
|
|
3099
|
+
...common,
|
|
3100
|
+
status: "calibrating",
|
|
3101
|
+
reason: "changing-pace"
|
|
3102
|
+
};
|
|
3103
|
+
if (resetsAt !== null && resetsAt <= now / 1e3) return {
|
|
3104
|
+
...common,
|
|
3105
|
+
status: "calibrating",
|
|
3106
|
+
reason: "stale"
|
|
2698
3107
|
};
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
3108
|
+
if (spanMs >= 5 * 6e4 && samples.length >= 3 && bounds.min <= 1e-9 && common.consumedPercent >= 1) {
|
|
3109
|
+
const pacePerHour = common.consumedPercent / (spanMs / HOUR_MS);
|
|
3110
|
+
return {
|
|
3111
|
+
...common,
|
|
3112
|
+
status: "ready",
|
|
3113
|
+
provisional: true,
|
|
3114
|
+
pacePerHour,
|
|
3115
|
+
runwaySeconds: clampPercent(window.remainingPercent) / pacePerHour * 3600,
|
|
3116
|
+
survivesReset: false
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
if (spanMs < 6e4 || bounds.min <= 1e-9) return {
|
|
3120
|
+
...common,
|
|
3121
|
+
status: "calibrating",
|
|
3122
|
+
reason: "resolution"
|
|
3123
|
+
};
|
|
3124
|
+
const pacePerHour = (bounds.min + bounds.max) * 30;
|
|
2703
3125
|
const remaining = clampPercent(window.remainingPercent);
|
|
2704
|
-
const
|
|
2705
|
-
const
|
|
2706
|
-
const
|
|
2707
|
-
|
|
3126
|
+
const runwayMinSeconds = Math.max(0, remaining - 1) / common.upperPacePerHour * 3600;
|
|
3127
|
+
const runwayMaxSeconds = Math.min(100, remaining + 1) / common.lowerPacePerHour * 3600;
|
|
3128
|
+
const resetSeconds = resetsAt === null ? null : resetsAt - now / 1e3;
|
|
3129
|
+
if (resetSeconds !== null && resetSeconds <= 0) return {
|
|
3130
|
+
...common,
|
|
3131
|
+
status: "calibrating",
|
|
3132
|
+
reason: "stale"
|
|
3133
|
+
};
|
|
2708
3134
|
return {
|
|
3135
|
+
...common,
|
|
2709
3136
|
status: "ready",
|
|
2710
3137
|
pacePerHour,
|
|
2711
|
-
|
|
2712
|
-
runwaySeconds,
|
|
3138
|
+
runwaySeconds: remaining / pacePerHour * 3600,
|
|
2713
3139
|
runwayMinSeconds,
|
|
2714
3140
|
runwayMaxSeconds,
|
|
2715
|
-
survivesReset: resetSeconds !== null && runwayMinSeconds >= resetSeconds
|
|
2716
|
-
sampleCount: samples.length,
|
|
2717
|
-
observedSpanMs: spanMs,
|
|
2718
|
-
consumedPercent
|
|
3141
|
+
survivesReset: resetSeconds !== null && runwayMinSeconds >= resetSeconds
|
|
2719
3142
|
};
|
|
2720
3143
|
}
|
|
2721
3144
|
function forecastUsage(usage, state = { windows: {} }, now = Date.now(), options = {}) {
|
|
3145
|
+
const observedAt = Number.isFinite(usage?.fetchedAt) ? usage.fetchedAt : now;
|
|
3146
|
+
if (observedAt > now || now - observedAt > 5 * 6e4) return {
|
|
3147
|
+
state,
|
|
3148
|
+
changed: false,
|
|
3149
|
+
usage: {
|
|
3150
|
+
...usage,
|
|
3151
|
+
rateLimits: (usage?.rateLimits ?? []).map((limit) => ({
|
|
3152
|
+
...limit,
|
|
3153
|
+
windows: limit.windows.map((window) => ({
|
|
3154
|
+
...window,
|
|
3155
|
+
forecast: {
|
|
3156
|
+
status: "calibrating",
|
|
3157
|
+
reason: "stale"
|
|
3158
|
+
}
|
|
3159
|
+
}))
|
|
3160
|
+
}))
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
2722
3163
|
let nextState = state;
|
|
2723
3164
|
let changed = false;
|
|
2724
3165
|
const rateLimits = (usage?.rateLimits ?? []).map((limit) => {
|
|
@@ -2726,7 +3167,7 @@ function forecastUsage(usage, state = { windows: {} }, now = Date.now(), options
|
|
|
2726
3167
|
scope: options.scope,
|
|
2727
3168
|
limitId: limit.id
|
|
2728
3169
|
};
|
|
2729
|
-
const observed = observeQuotaForecast(nextState, limit.windows,
|
|
3170
|
+
const observed = observeQuotaForecast(nextState, limit.windows, observedAt, context);
|
|
2730
3171
|
nextState = observed.state;
|
|
2731
3172
|
changed ||= observed.changed;
|
|
2732
3173
|
return {
|
|
@@ -2749,40 +3190,67 @@ function forecastUsage(usage, state = { windows: {} }, now = Date.now(), options
|
|
|
2749
3190
|
function createQuotaForecastReader({ reader, enabled, now = Date.now, scope = () => "default", stateStore }) {
|
|
2750
3191
|
let state = { windows: {} };
|
|
2751
3192
|
let loaded = false;
|
|
3193
|
+
let loading;
|
|
3194
|
+
let generation = 0;
|
|
3195
|
+
let historyGeneration = 0;
|
|
3196
|
+
let persistence = Promise.resolve();
|
|
3197
|
+
const persist = (operation) => {
|
|
3198
|
+
const pending = persistence.then(operation);
|
|
3199
|
+
persistence = pending.catch(() => {});
|
|
3200
|
+
return pending;
|
|
3201
|
+
};
|
|
2752
3202
|
const load = async () => {
|
|
2753
3203
|
if (loaded) return;
|
|
3204
|
+
if (loading) return loading;
|
|
3205
|
+
const current = historyGeneration;
|
|
3206
|
+
const pending = Promise.resolve().then(() => stateStore?.load?.()).then((restored) => {
|
|
3207
|
+
if (current !== historyGeneration) return;
|
|
3208
|
+
if (restored?.windows !== null && typeof restored?.windows === "object") state = restored;
|
|
3209
|
+
loaded = true;
|
|
3210
|
+
}).finally(() => {
|
|
3211
|
+
if (loading === pending) loading = void 0;
|
|
3212
|
+
});
|
|
3213
|
+
loading = pending;
|
|
3214
|
+
return pending;
|
|
3215
|
+
};
|
|
3216
|
+
const clearHistory = (clearReader = true) => {
|
|
3217
|
+
generation += 1;
|
|
3218
|
+
historyGeneration += 1;
|
|
3219
|
+
state = { windows: {} };
|
|
2754
3220
|
loaded = true;
|
|
2755
|
-
|
|
2756
|
-
|
|
3221
|
+
if (clearReader) reader.clear();
|
|
3222
|
+
return persist(() => stateStore?.clear?.());
|
|
2757
3223
|
};
|
|
2758
3224
|
return Object.freeze({
|
|
2759
3225
|
async read(options) {
|
|
3226
|
+
const current = generation;
|
|
3227
|
+
const account = await scope();
|
|
2760
3228
|
const usage = await reader.read(options);
|
|
3229
|
+
if (current !== generation) return usage;
|
|
2761
3230
|
await load();
|
|
3231
|
+
const activeAccount = await scope();
|
|
3232
|
+
if (current !== generation || account !== activeAccount) return usage;
|
|
2762
3233
|
if (!enabled()) {
|
|
2763
|
-
|
|
2764
|
-
await stateStore?.clear?.();
|
|
3234
|
+
await clearHistory(false);
|
|
2765
3235
|
return usage;
|
|
2766
3236
|
}
|
|
2767
|
-
const forecast = forecastUsage(usage, state, now(), { scope:
|
|
3237
|
+
const forecast = forecastUsage(usage, state, now(), { scope: account });
|
|
2768
3238
|
state = forecast.state;
|
|
2769
|
-
if (forecast.changed) await stateStore?.save?.(state);
|
|
2770
|
-
return forecast.usage;
|
|
2771
|
-
},
|
|
2772
|
-
async clear() {
|
|
2773
|
-
state = { windows: {} };
|
|
2774
|
-
loaded = true;
|
|
2775
|
-
reader.clear();
|
|
2776
|
-
await stateStore?.clear?.();
|
|
3239
|
+
if (forecast.changed) await persist(() => stateStore?.save?.(forecast.state));
|
|
3240
|
+
return current === generation ? forecast.usage : usage;
|
|
2777
3241
|
},
|
|
3242
|
+
clear: () => clearHistory(),
|
|
2778
3243
|
clearCache() {
|
|
3244
|
+
generation += 1;
|
|
2779
3245
|
reader.clear();
|
|
2780
3246
|
},
|
|
2781
3247
|
async clearScope(targetScope) {
|
|
3248
|
+
generation += 1;
|
|
2782
3249
|
await load();
|
|
2783
3250
|
const prefix = `[${JSON.stringify(cleanSegment(targetScope))},`;
|
|
2784
3251
|
state = { windows: Object.fromEntries(Object.entries(state.windows).filter(([key]) => !key.startsWith(prefix))) };
|
|
2785
|
-
|
|
3252
|
+
const snapshot = state;
|
|
3253
|
+
await persist(() => stateStore?.save?.(snapshot));
|
|
2786
3254
|
}
|
|
2787
3255
|
});
|
|
2788
3256
|
}
|
|
@@ -3144,28 +3612,7 @@ function createCodexResetCreditService(options) {
|
|
|
3144
3612
|
});
|
|
3145
3613
|
}
|
|
3146
3614
|
//#endregion
|
|
3147
|
-
//#region src/
|
|
3148
|
-
const name = "codex-subscription";
|
|
3149
|
-
const inject = [
|
|
3150
|
-
"llm",
|
|
3151
|
-
"credentials",
|
|
3152
|
-
"settings",
|
|
3153
|
-
"web",
|
|
3154
|
-
"loader",
|
|
3155
|
-
"tools",
|
|
3156
|
-
"attachments"
|
|
3157
|
-
];
|
|
3158
|
-
const PROVIDER = "openai-codex";
|
|
3159
|
-
const OAUTH_EXPIRY_SKEW_MS = 6e4;
|
|
3160
|
-
const CREDENTIAL_REF = dshCredentials.credentialRef("OPENAI_CODEX_SUBSCRIPTION_OAUTH");
|
|
3161
|
-
const LEGACY_CREDENTIAL_REF = dshCredentials.credentialRef("WSL043_OPENAI_CODEX_OAUTH");
|
|
3162
|
-
const ACCOUNT_VAULT_KEY = typeof dshCredentials.credentialKey === "function" ? dshCredentials.credentialKey("codex-subscription", "accounts") : void 0;
|
|
3163
|
-
const CHANNEL = "/codex-subscription";
|
|
3164
|
-
const WEB_ENTRY_ID = "web";
|
|
3165
|
-
const DSH_SEARCH_PROVIDER_FALLBACK = "deepseek-official";
|
|
3166
|
-
const MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
3167
|
-
const REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
|
|
3168
|
-
const REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
|
|
3615
|
+
//#region src/subscription-rpc.js
|
|
3169
3616
|
const publicError = (code, message) => ({
|
|
3170
3617
|
ok: false,
|
|
3171
3618
|
error: {
|
|
@@ -3209,7 +3656,9 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3209
3656
|
ok: true,
|
|
3210
3657
|
value: {
|
|
3211
3658
|
contextModels: Array.isArray(value?.contextModels) ? value.contextModels : [],
|
|
3212
|
-
verbosityModels: Array.isArray(value?.verbosityModels) ? value.verbosityModels : []
|
|
3659
|
+
verbosityModels: Array.isArray(value?.verbosityModels) ? value.verbosityModels : [],
|
|
3660
|
+
fastModels: Array.isArray(value?.fastModels) ? value.fastModels : [],
|
|
3661
|
+
catalogStatus: value?.catalogStatus
|
|
3213
3662
|
}
|
|
3214
3663
|
};
|
|
3215
3664
|
} catch (error) {
|
|
@@ -3219,44 +3668,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3219
3668
|
if (endpoint === "preferences/status" || endpoint === "preferences/update") try {
|
|
3220
3669
|
signal.throwIfAborted();
|
|
3221
3670
|
if (endpoint === "preferences/update") {
|
|
3222
|
-
const patch =
|
|
3223
|
-
|
|
3224
|
-
if (!
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
"bar",
|
|
3228
|
-
"forecast"
|
|
3229
|
-
].includes(payload["quickQuotaMode"])) return publicError("internal", "Invalid quick quota preference");
|
|
3230
|
-
patch[QUICK_QUOTA_MODE_FIELD] = payload[QUICK_QUOTA_MODE_FIELD];
|
|
3231
|
-
}
|
|
3232
|
-
if (Object.hasOwn(payload ?? {}, "searchProvider")) {
|
|
3233
|
-
if (![
|
|
3234
|
-
"auto",
|
|
3235
|
-
"dsh",
|
|
3236
|
-
"codex"
|
|
3237
|
-
].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
|
|
3238
|
-
patch[SEARCH_PROVIDER_FIELD] = payload[SEARCH_PROVIDER_FIELD];
|
|
3239
|
-
}
|
|
3240
|
-
if (Object.hasOwn(payload ?? {}, "speedMode")) {
|
|
3241
|
-
if (!["standard", "fast"].includes(payload["speedMode"])) return publicError("internal", "Invalid speed mode preference");
|
|
3242
|
-
patch[SPEED_MODE_FIELD] = payload[SPEED_MODE_FIELD];
|
|
3243
|
-
}
|
|
3244
|
-
if (Object.hasOwn(payload ?? {}, "outputVerbosity")) {
|
|
3245
|
-
if (![
|
|
3246
|
-
"default",
|
|
3247
|
-
"low",
|
|
3248
|
-
"medium",
|
|
3249
|
-
"high"
|
|
3250
|
-
].includes(payload["outputVerbosity"])) return publicError("internal", "Invalid output verbosity preference");
|
|
3251
|
-
patch[OUTPUT_VERBOSITY_FIELD] = payload[OUTPUT_VERBOSITY_FIELD];
|
|
3252
|
-
}
|
|
3253
|
-
if (Object.hasOwn(payload ?? {}, "contextMode")) {
|
|
3254
|
-
if (![
|
|
3255
|
-
"standard",
|
|
3256
|
-
"extended",
|
|
3257
|
-
"custom"
|
|
3258
|
-
].includes(payload["contextMode"])) return publicError("internal", "Invalid context mode preference");
|
|
3259
|
-
patch[CONTEXT_MODE_FIELD] = payload[CONTEXT_MODE_FIELD];
|
|
3671
|
+
const patch = capabilityPatch(payload);
|
|
3672
|
+
for (const [field, rule] of Object.entries(PREFERENCE_FIELDS)) {
|
|
3673
|
+
if (!Object.hasOwn(payload ?? {}, field)) continue;
|
|
3674
|
+
if (!rule.choices.includes(payload[field])) return publicError("internal", rule.error);
|
|
3675
|
+
patch[field] = payload[field];
|
|
3260
3676
|
}
|
|
3261
3677
|
if (Object.hasOwn(payload ?? {}, "customContextWindow")) {
|
|
3262
3678
|
if (normalizeCustomContextWindow(payload["customContextWindow"]) !== payload["customContextWindow"]) return publicError("internal", "Invalid custom context window");
|
|
@@ -3339,6 +3755,28 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3339
3755
|
return result;
|
|
3340
3756
|
};
|
|
3341
3757
|
}
|
|
3758
|
+
//#endregion
|
|
3759
|
+
//#region src/index.js
|
|
3760
|
+
const name = "codex-subscription";
|
|
3761
|
+
const inject = [
|
|
3762
|
+
"llm",
|
|
3763
|
+
"credentials",
|
|
3764
|
+
"settings",
|
|
3765
|
+
"web",
|
|
3766
|
+
"loader",
|
|
3767
|
+
"tools",
|
|
3768
|
+
"attachments"
|
|
3769
|
+
];
|
|
3770
|
+
const PROVIDER = "openai-codex";
|
|
3771
|
+
const OAUTH_EXPIRY_SKEW_MS = 6e4;
|
|
3772
|
+
const CREDENTIAL_REF = dshCredentials.credentialRef("OPENAI_CODEX_SUBSCRIPTION_OAUTH");
|
|
3773
|
+
const LEGACY_CREDENTIAL_REF = dshCredentials.credentialRef("WSL043_OPENAI_CODEX_OAUTH");
|
|
3774
|
+
const ACCOUNT_VAULT_KEY = typeof dshCredentials.credentialKey === "function" ? dshCredentials.credentialKey("codex-subscription", "accounts") : void 0;
|
|
3775
|
+
const WEB_ENTRY_ID = "web";
|
|
3776
|
+
const DSH_SEARCH_PROVIDER_FALLBACK = "deepseek-official";
|
|
3777
|
+
const MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
3778
|
+
const REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
|
|
3779
|
+
const REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
|
|
3342
3780
|
function createSearchProviderSwitcher(loader) {
|
|
3343
3781
|
const webEntry = () => [...loader.entries()].find((entry) => entry.options?.id === WEB_ENTRY_ID);
|
|
3344
3782
|
const dshProviderId = () => {
|
|
@@ -3365,30 +3803,23 @@ function createSearchProviderSwitcher(loader) {
|
|
|
3365
3803
|
}
|
|
3366
3804
|
function apply(ctx) {
|
|
3367
3805
|
const settings = ctx.settings.register(SETTINGS_NAMESPACE, z.object({
|
|
3368
|
-
[
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
"
|
|
3372
|
-
QUICK_QUOTA_MODE_FORECAST
|
|
3373
|
-
]),
|
|
3374
|
-
[LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
|
|
3375
|
-
[SEARCH_PROVIDER_FIELD]: z.union([
|
|
3376
|
-
SEARCH_PROVIDER_AUTO,
|
|
3377
|
-
"dsh",
|
|
3378
|
-
SEARCH_PROVIDER_CODEX
|
|
3379
|
-
]).default(DEFAULT_SEARCH_PROVIDER),
|
|
3380
|
-
[SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE),
|
|
3381
|
-
[OUTPUT_VERBOSITY_FIELD]: z.union([
|
|
3382
|
-
OUTPUT_VERBOSITY_DEFAULT,
|
|
3806
|
+
...Object.fromEntries(Object.entries(PREFERENCE_FIELDS).map(([field, rule]) => [field, rule.default === void 0 ? z.union(rule.choices) : z.union(rule.choices).default(rule.default)])),
|
|
3807
|
+
imageModel: z.union(Object.keys(IMAGE_MODELS)).default(DEFAULT_IMAGE_MODEL),
|
|
3808
|
+
imageQuality: z.union([
|
|
3809
|
+
"auto",
|
|
3383
3810
|
"low",
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
]).default(
|
|
3811
|
+
"medium",
|
|
3812
|
+
"high",
|
|
3813
|
+
"xhigh",
|
|
3814
|
+
"max"
|
|
3815
|
+
]).default("auto"),
|
|
3816
|
+
...Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, value]) => [key, z.boolean().default(value)])),
|
|
3817
|
+
[CUSTOM_CONTEXT_OVERRIDES_FIELD]: z.dict(z.number().step(1).min(1).max(MAX_CONTEXT_BUDGET)).default({}),
|
|
3818
|
+
[SEARCH_MODE_FIELD]: z.union(SEARCH_MODES).default("live"),
|
|
3819
|
+
[SEARCH_DOMAINS_FIELD]: z.transform(z.array(z.string()).max(20), (value) => readCapabilitySettings({ searchDomains: value }).searchDomains).default([]),
|
|
3820
|
+
...Object.fromEntries(QUOTA_THRESHOLD_FIELDS.map((key) => [key, z.number().step(1).min(1).max(100).default(20)])),
|
|
3821
|
+
[QUOTA_ALERTS_FIELD]: z.union(QUOTA_ALERT_MODES).default("important"),
|
|
3822
|
+
[LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
|
|
3392
3823
|
[CUSTOM_CONTEXT_WINDOW_FIELD]: z.number().step(1).min(128e3).max(1e6).default(DEFAULT_CUSTOM_CONTEXT_WINDOW),
|
|
3393
3824
|
...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, z.number().step(1).min(128e3).max(CUSTOM_CONTEXT_MODEL_CAPS[modelKey]).default(CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey])]))
|
|
3394
3825
|
}));
|
|
@@ -3417,7 +3848,10 @@ function apply(ctx) {
|
|
|
3417
3848
|
resolveOutputVerbosity: () => normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
|
|
3418
3849
|
resolveContextMode: () => normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
|
|
3419
3850
|
resolveCustomContextWindow: (modelKey) => {
|
|
3851
|
+
const overrides = readCapabilitySettings(settings.get())[CUSTOM_CONTEXT_OVERRIDES_FIELD];
|
|
3852
|
+
if (Object.hasOwn(overrides, modelKey)) return overrides[modelKey];
|
|
3420
3853
|
const field = CUSTOM_CONTEXT_MODEL_FIELDS[modelKey];
|
|
3854
|
+
if (field === void 0) return void 0;
|
|
3421
3855
|
return normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey]);
|
|
3422
3856
|
},
|
|
3423
3857
|
catalog: modelCatalog,
|
|
@@ -3425,6 +3859,7 @@ function apply(ctx) {
|
|
|
3425
3859
|
});
|
|
3426
3860
|
const preferences = {
|
|
3427
3861
|
status: () => ({
|
|
3862
|
+
...readCapabilitySettings(settings.get()),
|
|
3428
3863
|
[QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
|
|
3429
3864
|
[SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
|
|
3430
3865
|
[SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
|
|
@@ -3432,8 +3867,10 @@ function apply(ctx) {
|
|
|
3432
3867
|
[CONTEXT_MODE_FIELD]: normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
|
|
3433
3868
|
[CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(settings.get()[CUSTOM_CONTEXT_WINDOW_FIELD]),
|
|
3434
3869
|
...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])])),
|
|
3435
|
-
contextModels: contextModelGroups(
|
|
3870
|
+
contextModels: contextModelGroups(modelCatalog.getModels(baseProvider.getModels())),
|
|
3871
|
+
catalogStatus: modelCatalog.status(),
|
|
3436
3872
|
verbosityModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportVerbosity ?? model.id !== "gpt-5.3-codex-spark").map((model) => model.id),
|
|
3873
|
+
fastModels: provider.getModels().filter((model) => modelCatalog.metadata(model.id)?.supportsFast ?? supportsCodexFastMode(model.id)).map((model) => model.id),
|
|
3437
3874
|
writable: ctx.settings.writable
|
|
3438
3875
|
}),
|
|
3439
3876
|
update: (patch) => settings.update(patch)
|
|
@@ -3455,11 +3892,12 @@ function apply(ctx) {
|
|
|
3455
3892
|
let profileKey;
|
|
3456
3893
|
let profileSnapshot;
|
|
3457
3894
|
const profiles = () => {
|
|
3458
|
-
const key = [
|
|
3895
|
+
const key = JSON.stringify([
|
|
3459
3896
|
modelCatalog.revision(),
|
|
3460
3897
|
normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
|
|
3898
|
+
settings.get()[CUSTOM_CONTEXT_OVERRIDES_FIELD],
|
|
3461
3899
|
...Object.values(CUSTOM_CONTEXT_MODEL_FIELDS).map((field) => settings.get()[field])
|
|
3462
|
-
]
|
|
3900
|
+
]);
|
|
3463
3901
|
if (key !== profileKey) {
|
|
3464
3902
|
profileKey = key;
|
|
3465
3903
|
profileSnapshot = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
|
|
@@ -3474,14 +3912,15 @@ function apply(ctx) {
|
|
|
3474
3912
|
fileExists: async () => false
|
|
3475
3913
|
})
|
|
3476
3914
|
});
|
|
3477
|
-
ctx.tools.register(createCodexImageTool({
|
|
3915
|
+
ctx.effect(() => watchImageTool(settings, () => ctx.tools.register(createCodexImageTool({
|
|
3916
|
+
getFeatures: () => settings.get(),
|
|
3478
3917
|
getAuth: resolveAuth,
|
|
3479
3918
|
readCredential: (options) => store.read(PROVIDER, options),
|
|
3480
3919
|
attachments: ctx.attachments,
|
|
3481
3920
|
getSessionMessages: (sessionId) => ctx.get?.("sessions")?.get?.(sessionId)?.deriveMessages?.() ?? [],
|
|
3482
3921
|
originalImages,
|
|
3483
3922
|
fetch: (input, init) => network.fetch("image", input, init)
|
|
3484
|
-
}));
|
|
3923
|
+
}))), "codex-subscription: image tool availability");
|
|
3485
3924
|
const adapter = new PiAiAdapter({
|
|
3486
3925
|
profiles,
|
|
3487
3926
|
resolveApiKey: async () => {
|
|
@@ -3500,6 +3939,7 @@ function apply(ctx) {
|
|
|
3500
3939
|
ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
3501
3940
|
const currentAgent = () => ctx.get?.("agents")?.currentInitiator?.();
|
|
3502
3941
|
const codexSearch = createCodexSearchProvider({
|
|
3942
|
+
resolvePreferences: () => readCapabilitySettings(settings.get()),
|
|
3503
3943
|
getAuth: resolveAuth,
|
|
3504
3944
|
readCredential: (options) => store.read(PROVIDER, options),
|
|
3505
3945
|
resolveModel: () => {
|
|
@@ -3547,12 +3987,22 @@ function apply(ctx) {
|
|
|
3547
3987
|
stateStore: new QuotaForecastStateStore({ filename: dshHomePath("state", "codex-subscription", "quota-forecast.json") })
|
|
3548
3988
|
});
|
|
3549
3989
|
ctx.effect(() => {
|
|
3990
|
+
let forecasting = false;
|
|
3550
3991
|
const warmForecast = (value) => {
|
|
3551
|
-
if (normalizeQuickQuotaMode(value["quickQuotaMode"], value["quickQuotaVisible"])
|
|
3992
|
+
if (!(normalizeQuickQuotaMode(value["quickQuotaMode"], value["quickQuotaVisible"]) === "forecast")) {
|
|
3993
|
+
if (forecasting) usageReader.clear().catch((error) => ctx.logger?.debug?.("could not clear Codex quota forecast: %s", error.message));
|
|
3994
|
+
forecasting = false;
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
forecasting = true;
|
|
3552
3998
|
usageReader.read().catch((error) => ctx.logger?.debug?.("could not warm Codex quota forecast: %s", error.message));
|
|
3553
3999
|
};
|
|
3554
4000
|
warmForecast(settings.get());
|
|
3555
|
-
|
|
4001
|
+
const unwatch = settings.watch(warmForecast);
|
|
4002
|
+
return () => {
|
|
4003
|
+
unwatch();
|
|
4004
|
+
usageReader.clearCache();
|
|
4005
|
+
};
|
|
3556
4006
|
}, "codex-subscription: quota forecast warm-up");
|
|
3557
4007
|
const resetCreditService = createCodexResetCreditService({
|
|
3558
4008
|
getAuth: resolveAuth,
|
|
@@ -3569,7 +4019,8 @@ function apply(ctx) {
|
|
|
3569
4019
|
auth,
|
|
3570
4020
|
preferences,
|
|
3571
4021
|
login: coordinator.supportState(),
|
|
3572
|
-
network
|
|
4022
|
+
network,
|
|
4023
|
+
modelCatalog
|
|
3573
4024
|
}),
|
|
3574
4025
|
modelCatalog,
|
|
3575
4026
|
originalImages,
|
|
@@ -3578,7 +4029,7 @@ function apply(ctx) {
|
|
|
3578
4029
|
ctx.effect(() => {
|
|
3579
4030
|
modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
|
|
3580
4031
|
}, "codex-subscription: official model catalog");
|
|
3581
|
-
ctx.inject(["connection"], (connectionContext) => connectionContext.effect(() => connectionContext.connection
|
|
4032
|
+
ctx.inject(["connection"], (connectionContext) => connectionContext.effect(() => registerSubscriptionTransport(connectionContext.connection, handler), "codex-subscription: DSH-trusted account RPC"));
|
|
3582
4033
|
}
|
|
3583
4034
|
//#endregion
|
|
3584
4035
|
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 };
|