dsh-codex-subscription 1.14.4 → 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 +4329 -1049
- package/lib/index.js +661 -279
- package/package.json +16 -16
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,
|
|
@@ -1598,6 +1870,9 @@ function createCodexSearchProvider(options) {
|
|
|
1598
1870
|
id: CODEX_SEARCH_PROVIDER_ID,
|
|
1599
1871
|
available: () => true,
|
|
1600
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");
|
|
1601
1876
|
const auth = await options.getAuth({ signal });
|
|
1602
1877
|
const credential = await options.readCredential({ signal });
|
|
1603
1878
|
const access = auth?.auth?.apiKey;
|
|
@@ -1628,7 +1903,7 @@ function createCodexSearchProvider(options) {
|
|
|
1628
1903
|
},
|
|
1629
1904
|
settings: {
|
|
1630
1905
|
allowed_callers: ["direct"],
|
|
1631
|
-
external_web_access:
|
|
1906
|
+
external_web_access: preferences.searchMode === "live"
|
|
1632
1907
|
},
|
|
1633
1908
|
max_output_tokens: MAX_OUTPUT_TOKENS
|
|
1634
1909
|
}),
|
|
@@ -1646,7 +1921,12 @@ function createCodexSearchProvider(options) {
|
|
|
1646
1921
|
throw new WebError("Codex returned an unreadable search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
1647
1922
|
}
|
|
1648
1923
|
try {
|
|
1649
|
-
|
|
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;
|
|
1650
1930
|
} catch (error) {
|
|
1651
1931
|
throw new WebError("Codex returned a malformed search response", "WEB_PROVIDER_ERROR", { cause: error });
|
|
1652
1932
|
}
|
|
@@ -1714,15 +1994,8 @@ function inheritedOriginalImageRef(session, assetId) {
|
|
|
1714
1994
|
const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
|
|
1715
1995
|
const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
|
|
1716
1996
|
const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits";
|
|
1717
|
-
const IMAGE_MODEL = "gpt-image-2";
|
|
1718
1997
|
const MAX_REFERENCE_IMAGES = 5;
|
|
1719
1998
|
const RESPONSE_ENVELOPE_BYTES = 1024 * 1024;
|
|
1720
|
-
const IMAGE_QUALITIES = /* @__PURE__ */ new Set([
|
|
1721
|
-
"auto",
|
|
1722
|
-
"low",
|
|
1723
|
-
"medium",
|
|
1724
|
-
"high"
|
|
1725
|
-
]);
|
|
1726
1999
|
const IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
|
|
1727
2000
|
"auto",
|
|
1728
2001
|
"transparent",
|
|
@@ -1748,7 +2021,7 @@ function normalizeImageOptions(args) {
|
|
|
1748
2021
|
const quality = nonEmpty(args?.quality) ?? "auto";
|
|
1749
2022
|
const background = nonEmpty(args?.background) ?? "auto";
|
|
1750
2023
|
const size = nonEmpty(args?.size) ?? "auto";
|
|
1751
|
-
|
|
2024
|
+
validateImageQuality(resolveImageModel(args?.model), quality);
|
|
1752
2025
|
if (!IMAGE_BACKGROUNDS.has(background)) throw new Error("background must be auto, transparent, or opaque");
|
|
1753
2026
|
if (size !== "auto") {
|
|
1754
2027
|
const match = /^(\d+)x(\d+)$/u.exec(size);
|
|
@@ -1974,6 +2247,9 @@ function imageOutputSchema() {
|
|
|
1974
2247
|
}
|
|
1975
2248
|
},
|
|
1976
2249
|
background: { type: "string" },
|
|
2250
|
+
requestedModel: { type: "string" },
|
|
2251
|
+
reportedModel: { type: "string" },
|
|
2252
|
+
requestedSize: { type: "string" },
|
|
1977
2253
|
localPath: {
|
|
1978
2254
|
type: "string",
|
|
1979
2255
|
required: true,
|
|
@@ -1992,7 +2268,8 @@ function responseMetadata(value) {
|
|
|
1992
2268
|
encoded,
|
|
1993
2269
|
background: nonEmpty(value.background),
|
|
1994
2270
|
quality: nonEmpty(value.quality),
|
|
1995
|
-
size: nonEmpty(value.size)
|
|
2271
|
+
size: nonEmpty(value.size),
|
|
2272
|
+
reportedModel: nonEmpty(value.model)
|
|
1996
2273
|
};
|
|
1997
2274
|
}
|
|
1998
2275
|
/** Create the DSH-native image-generation tool backed only by the ChatGPT subscription. */
|
|
@@ -2001,8 +2278,13 @@ function createCodexImageTool(options) {
|
|
|
2001
2278
|
const attachments = options.attachments;
|
|
2002
2279
|
return defineTool({
|
|
2003
2280
|
name: CODEX_IMAGE_TOOL_NAME,
|
|
2004
|
-
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.",
|
|
2005
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
|
+
},
|
|
2006
2288
|
prompt: {
|
|
2007
2289
|
type: "string",
|
|
2008
2290
|
required: true,
|
|
@@ -2018,7 +2300,9 @@ function createCodexImageTool(options) {
|
|
|
2018
2300
|
"auto",
|
|
2019
2301
|
"low",
|
|
2020
2302
|
"medium",
|
|
2021
|
-
"high"
|
|
2303
|
+
"high",
|
|
2304
|
+
"xhigh",
|
|
2305
|
+
"max"
|
|
2022
2306
|
],
|
|
2023
2307
|
description: "Optional rendering quality. Use auto unless the user requests draft speed or final quality."
|
|
2024
2308
|
},
|
|
@@ -2084,12 +2368,22 @@ function createCodexImageTool(options) {
|
|
|
2084
2368
|
presentationMeta: (_args, value) => ({
|
|
2085
2369
|
kind: "codex-subscription-image",
|
|
2086
2370
|
schemaVersion: 1,
|
|
2087
|
-
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 }
|
|
2088
2375
|
})
|
|
2089
2376
|
},
|
|
2090
2377
|
timeoutMs: 300 * 1e3,
|
|
2091
2378
|
isConcurrencySafe: () => false,
|
|
2092
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
|
+
};
|
|
2093
2387
|
const prompt = nonEmpty(args.prompt);
|
|
2094
2388
|
if (prompt === void 0) throw new Error("prompt must be a non-empty string");
|
|
2095
2389
|
const imageOptions = normalizeImageOptions(args);
|
|
@@ -2104,6 +2398,7 @@ function createCodexImageTool(options) {
|
|
|
2104
2398
|
const sessionMessages = editing && typeof options.getSessionMessages === "function" ? await options.getSessionMessages(exec.agent?.id) : void 0;
|
|
2105
2399
|
const images = editing ? await editImages(args.referenceImages, attachments, exec.signal, sessionMessages ?? (options.getSessionMessages ? [] : void 0)) : void 0;
|
|
2106
2400
|
let response;
|
|
2401
|
+
assertImageOperation(options.getFeatures?.(), editing);
|
|
2107
2402
|
try {
|
|
2108
2403
|
response = await fetchImage(editing ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_GENERATION_URL, {
|
|
2109
2404
|
method: "POST",
|
|
@@ -2121,7 +2416,7 @@ function createCodexImageTool(options) {
|
|
|
2121
2416
|
...images === void 0 ? {} : { images },
|
|
2122
2417
|
prompt,
|
|
2123
2418
|
background: imageOptions.background,
|
|
2124
|
-
model:
|
|
2419
|
+
model: resolveImageModel(args.model),
|
|
2125
2420
|
quality: imageOptions.quality,
|
|
2126
2421
|
size: imageOptions.size
|
|
2127
2422
|
}),
|
|
@@ -2153,6 +2448,9 @@ function createCodexImageTool(options) {
|
|
|
2153
2448
|
throw error;
|
|
2154
2449
|
}
|
|
2155
2450
|
const result = {
|
|
2451
|
+
requestedModel: resolveImageModel(args.model),
|
|
2452
|
+
requestedSize: imageOptions.size,
|
|
2453
|
+
...metadata.reportedModel === void 0 ? {} : { reportedModel: metadata.reportedModel },
|
|
2156
2454
|
image: imageReference(ref),
|
|
2157
2455
|
original,
|
|
2158
2456
|
localPath: options.originalImages.originalPath(original.assetId),
|
|
@@ -2172,6 +2470,27 @@ function createCodexImageTool(options) {
|
|
|
2172
2470
|
});
|
|
2173
2471
|
}
|
|
2174
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
|
|
2175
2494
|
//#region src/image-original-store.js
|
|
2176
2495
|
const ORIGINAL_IMAGE_DIRECTORY = "dsh-codex-subscription/images/v1";
|
|
2177
2496
|
const METADATA_VERSION = 1;
|
|
@@ -2632,12 +2951,74 @@ function createCodexUsageReader(options) {
|
|
|
2632
2951
|
});
|
|
2633
2952
|
}
|
|
2634
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
|
|
2635
3018
|
//#region src/quota-forecast.js
|
|
2636
3019
|
const HOUR_MS = 3600 * 1e3;
|
|
2637
3020
|
const HISTORY_MS = 24 * HOUR_MS;
|
|
2638
|
-
const
|
|
2639
|
-
const PLATEAU_SAMPLE_MS = 900 * 1e3;
|
|
2640
|
-
const finite = (value) => Number.isFinite(Number(value));
|
|
3021
|
+
const finite = (value) => value !== null && value !== void 0 && Number.isFinite(Number(value));
|
|
2641
3022
|
const clampPercent = (value) => Math.max(0, Math.min(100, Number(value)));
|
|
2642
3023
|
const cleanSegment = (value) => String(value ?? "default").slice(0, 96);
|
|
2643
3024
|
const keyFor = (window, context = {}) => JSON.stringify([
|
|
@@ -2645,17 +3026,6 @@ const keyFor = (window, context = {}) => JSON.stringify([
|
|
|
2645
3026
|
cleanSegment(context.limitId ?? "codex"),
|
|
2646
3027
|
Number(window.windowSeconds) || "limit"
|
|
2647
3028
|
]);
|
|
2648
|
-
const median = (values) => {
|
|
2649
|
-
const ordered = [...values].sort((a, b) => a - b);
|
|
2650
|
-
const middle = Math.floor(ordered.length / 2);
|
|
2651
|
-
return ordered.length % 2 === 0 ? (ordered[middle - 1] + ordered[middle]) / 2 : ordered[middle];
|
|
2652
|
-
};
|
|
2653
|
-
function requiredSpanMs(consumedPercent) {
|
|
2654
|
-
if (consumedPercent >= 2) return 300 * 1e3;
|
|
2655
|
-
if (consumedPercent >= 1) return 600 * 1e3;
|
|
2656
|
-
if (consumedPercent >= .5) return 1200 * 1e3;
|
|
2657
|
-
return 1800 * 1e3;
|
|
2658
|
-
}
|
|
2659
3029
|
function observeQuotaForecast(state, windows, now = Date.now(), context = {}) {
|
|
2660
3030
|
const next = { windows: { ...state?.windows ?? {} } };
|
|
2661
3031
|
let changed = false;
|
|
@@ -2663,12 +3033,13 @@ function observeQuotaForecast(state, windows, now = Date.now(), context = {}) {
|
|
|
2663
3033
|
if (!finite(window?.remainingPercent)) continue;
|
|
2664
3034
|
const key = keyFor(window, context);
|
|
2665
3035
|
const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
|
|
2666
|
-
const remainingPercent =
|
|
3036
|
+
const remainingPercent = clampPercent(window.remainingPercent);
|
|
2667
3037
|
const previous = next.windows[key];
|
|
2668
3038
|
const resetChanged = previous !== void 0 && (previous.resetsAt === null !== (resetsAt === null) || previous.resetsAt !== null && Math.abs(previous.resetsAt - resetsAt) > 300);
|
|
2669
3039
|
const last = previous?.samples?.at(-1);
|
|
2670
3040
|
const quotaIncreased = last !== void 0 && remainingPercent > last.remainingPercent + .5;
|
|
2671
|
-
const
|
|
3041
|
+
const observationGap = last !== void 0 && now - last.at > 90 * 6e4;
|
|
3042
|
+
const record = resetChanged || quotaIncreased || observationGap ? {
|
|
2672
3043
|
resetsAt,
|
|
2673
3044
|
samples: []
|
|
2674
3045
|
} : {
|
|
@@ -2676,7 +3047,7 @@ function observeQuotaForecast(state, windows, now = Date.now(), context = {}) {
|
|
|
2676
3047
|
samples: [...previous?.samples ?? []]
|
|
2677
3048
|
};
|
|
2678
3049
|
const latest = record.samples.at(-1);
|
|
2679
|
-
if (latest === void 0 || now > latest.at
|
|
3050
|
+
if (latest === void 0 || now > latest.at) {
|
|
2680
3051
|
record.samples.push({
|
|
2681
3052
|
at: now,
|
|
2682
3053
|
remainingPercent
|
|
@@ -2697,59 +3068,98 @@ function estimateQuotaForecast(state, window, now = Date.now(), context = {}) {
|
|
|
2697
3068
|
if (record === void 0) return { status: "calibrating" };
|
|
2698
3069
|
const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
|
|
2699
3070
|
if (record.resetsAt === null !== (resetsAt === null) || resetsAt !== null && Math.abs(record.resetsAt - resetsAt) > 300) return { status: "calibrating" };
|
|
2700
|
-
|
|
2701
|
-
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 {
|
|
2702
3073
|
status: "calibrating",
|
|
2703
3074
|
sampleCount: samples.length
|
|
2704
3075
|
};
|
|
2705
|
-
|
|
2706
|
-
const last = samples.at(-1);
|
|
2707
|
-
const spanMs = last.at - first.at;
|
|
2708
|
-
const consumedPercent = Math.max(0, first.remainingPercent - last.remainingPercent);
|
|
2709
|
-
if (spanMs < requiredSpanMs(consumedPercent)) return {
|
|
3076
|
+
if (now - samples.at(-1).at > 20 * 6e4) return {
|
|
2710
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 = {
|
|
2711
3090
|
sampleCount: samples.length,
|
|
2712
3091
|
observedSpanMs: spanMs,
|
|
2713
|
-
consumedPercent
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
if (hours <= 0) continue;
|
|
2719
|
-
slopes.push((samples[left].remainingPercent - samples[right].remainingPercent) / hours);
|
|
2720
|
-
}
|
|
2721
|
-
const positive = slopes.filter((value) => Number.isFinite(value) && value >= 0);
|
|
2722
|
-
const pacePerHour = positive.length === 0 ? 0 : median(positive);
|
|
2723
|
-
if (!Number.isFinite(pacePerHour) || pacePerHour < .02) return {
|
|
2724
|
-
status: "idle",
|
|
2725
|
-
pacePerHour: 0,
|
|
2726
|
-
sampleCount: samples.length,
|
|
2727
|
-
observedSpanMs: spanMs,
|
|
2728
|
-
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"
|
|
2729
3097
|
};
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
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"
|
|
3107
|
+
};
|
|
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;
|
|
2734
3125
|
const remaining = clampPercent(window.remainingPercent);
|
|
2735
|
-
const
|
|
2736
|
-
const
|
|
2737
|
-
const
|
|
2738
|
-
|
|
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
|
+
};
|
|
2739
3134
|
return {
|
|
3135
|
+
...common,
|
|
2740
3136
|
status: "ready",
|
|
2741
3137
|
pacePerHour,
|
|
2742
|
-
|
|
2743
|
-
runwaySeconds,
|
|
3138
|
+
runwaySeconds: remaining / pacePerHour * 3600,
|
|
2744
3139
|
runwayMinSeconds,
|
|
2745
3140
|
runwayMaxSeconds,
|
|
2746
|
-
survivesReset: resetSeconds !== null && runwayMinSeconds >= resetSeconds
|
|
2747
|
-
sampleCount: samples.length,
|
|
2748
|
-
observedSpanMs: spanMs,
|
|
2749
|
-
consumedPercent
|
|
3141
|
+
survivesReset: resetSeconds !== null && runwayMinSeconds >= resetSeconds
|
|
2750
3142
|
};
|
|
2751
3143
|
}
|
|
2752
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
|
+
};
|
|
2753
3163
|
let nextState = state;
|
|
2754
3164
|
let changed = false;
|
|
2755
3165
|
const rateLimits = (usage?.rateLimits ?? []).map((limit) => {
|
|
@@ -2757,7 +3167,7 @@ function forecastUsage(usage, state = { windows: {} }, now = Date.now(), options
|
|
|
2757
3167
|
scope: options.scope,
|
|
2758
3168
|
limitId: limit.id
|
|
2759
3169
|
};
|
|
2760
|
-
const observed = observeQuotaForecast(nextState, limit.windows,
|
|
3170
|
+
const observed = observeQuotaForecast(nextState, limit.windows, observedAt, context);
|
|
2761
3171
|
nextState = observed.state;
|
|
2762
3172
|
changed ||= observed.changed;
|
|
2763
3173
|
return {
|
|
@@ -3202,28 +3612,7 @@ function createCodexResetCreditService(options) {
|
|
|
3202
3612
|
});
|
|
3203
3613
|
}
|
|
3204
3614
|
//#endregion
|
|
3205
|
-
//#region src/
|
|
3206
|
-
const name = "codex-subscription";
|
|
3207
|
-
const inject = [
|
|
3208
|
-
"llm",
|
|
3209
|
-
"credentials",
|
|
3210
|
-
"settings",
|
|
3211
|
-
"web",
|
|
3212
|
-
"loader",
|
|
3213
|
-
"tools",
|
|
3214
|
-
"attachments"
|
|
3215
|
-
];
|
|
3216
|
-
const PROVIDER = "openai-codex";
|
|
3217
|
-
const OAUTH_EXPIRY_SKEW_MS = 6e4;
|
|
3218
|
-
const CREDENTIAL_REF = dshCredentials.credentialRef("OPENAI_CODEX_SUBSCRIPTION_OAUTH");
|
|
3219
|
-
const LEGACY_CREDENTIAL_REF = dshCredentials.credentialRef("WSL043_OPENAI_CODEX_OAUTH");
|
|
3220
|
-
const ACCOUNT_VAULT_KEY = typeof dshCredentials.credentialKey === "function" ? dshCredentials.credentialKey("codex-subscription", "accounts") : void 0;
|
|
3221
|
-
const CHANNEL = "/codex-subscription";
|
|
3222
|
-
const WEB_ENTRY_ID = "web";
|
|
3223
|
-
const DSH_SEARCH_PROVIDER_FALLBACK = "deepseek-official";
|
|
3224
|
-
const MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
3225
|
-
const REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
|
|
3226
|
-
const REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
|
|
3615
|
+
//#region src/subscription-rpc.js
|
|
3227
3616
|
const publicError = (code, message) => ({
|
|
3228
3617
|
ok: false,
|
|
3229
3618
|
error: {
|
|
@@ -3267,7 +3656,9 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3267
3656
|
ok: true,
|
|
3268
3657
|
value: {
|
|
3269
3658
|
contextModels: Array.isArray(value?.contextModels) ? value.contextModels : [],
|
|
3270
|
-
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
|
|
3271
3662
|
}
|
|
3272
3663
|
};
|
|
3273
3664
|
} catch (error) {
|
|
@@ -3277,44 +3668,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3277
3668
|
if (endpoint === "preferences/status" || endpoint === "preferences/update") try {
|
|
3278
3669
|
signal.throwIfAborted();
|
|
3279
3670
|
if (endpoint === "preferences/update") {
|
|
3280
|
-
const patch =
|
|
3281
|
-
|
|
3282
|
-
if (!
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
"bar",
|
|
3286
|
-
"forecast"
|
|
3287
|
-
].includes(payload["quickQuotaMode"])) return publicError("internal", "Invalid quick quota preference");
|
|
3288
|
-
patch[QUICK_QUOTA_MODE_FIELD] = payload[QUICK_QUOTA_MODE_FIELD];
|
|
3289
|
-
}
|
|
3290
|
-
if (Object.hasOwn(payload ?? {}, "searchProvider")) {
|
|
3291
|
-
if (![
|
|
3292
|
-
"auto",
|
|
3293
|
-
"dsh",
|
|
3294
|
-
"codex"
|
|
3295
|
-
].includes(payload["searchProvider"])) return publicError("internal", "Invalid search provider preference");
|
|
3296
|
-
patch[SEARCH_PROVIDER_FIELD] = payload[SEARCH_PROVIDER_FIELD];
|
|
3297
|
-
}
|
|
3298
|
-
if (Object.hasOwn(payload ?? {}, "speedMode")) {
|
|
3299
|
-
if (!["standard", "fast"].includes(payload["speedMode"])) return publicError("internal", "Invalid speed mode preference");
|
|
3300
|
-
patch[SPEED_MODE_FIELD] = payload[SPEED_MODE_FIELD];
|
|
3301
|
-
}
|
|
3302
|
-
if (Object.hasOwn(payload ?? {}, "outputVerbosity")) {
|
|
3303
|
-
if (![
|
|
3304
|
-
"default",
|
|
3305
|
-
"low",
|
|
3306
|
-
"medium",
|
|
3307
|
-
"high"
|
|
3308
|
-
].includes(payload["outputVerbosity"])) return publicError("internal", "Invalid output verbosity preference");
|
|
3309
|
-
patch[OUTPUT_VERBOSITY_FIELD] = payload[OUTPUT_VERBOSITY_FIELD];
|
|
3310
|
-
}
|
|
3311
|
-
if (Object.hasOwn(payload ?? {}, "contextMode")) {
|
|
3312
|
-
if (![
|
|
3313
|
-
"standard",
|
|
3314
|
-
"extended",
|
|
3315
|
-
"custom"
|
|
3316
|
-
].includes(payload["contextMode"])) return publicError("internal", "Invalid context mode preference");
|
|
3317
|
-
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];
|
|
3318
3676
|
}
|
|
3319
3677
|
if (Object.hasOwn(payload ?? {}, "customContextWindow")) {
|
|
3320
3678
|
if (normalizeCustomContextWindow(payload["customContextWindow"]) !== payload["customContextWindow"]) return publicError("internal", "Invalid custom context window");
|
|
@@ -3397,6 +3755,28 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3397
3755
|
return result;
|
|
3398
3756
|
};
|
|
3399
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;
|
|
3400
3780
|
function createSearchProviderSwitcher(loader) {
|
|
3401
3781
|
const webEntry = () => [...loader.entries()].find((entry) => entry.options?.id === WEB_ENTRY_ID);
|
|
3402
3782
|
const dshProviderId = () => {
|
|
@@ -3423,30 +3803,23 @@ function createSearchProviderSwitcher(loader) {
|
|
|
3423
3803
|
}
|
|
3424
3804
|
function apply(ctx) {
|
|
3425
3805
|
const settings = ctx.settings.register(SETTINGS_NAMESPACE, z.object({
|
|
3426
|
-
[
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
"
|
|
3430
|
-
QUICK_QUOTA_MODE_FORECAST
|
|
3431
|
-
]),
|
|
3432
|
-
[LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
|
|
3433
|
-
[SEARCH_PROVIDER_FIELD]: z.union([
|
|
3434
|
-
SEARCH_PROVIDER_AUTO,
|
|
3435
|
-
"dsh",
|
|
3436
|
-
SEARCH_PROVIDER_CODEX
|
|
3437
|
-
]).default(DEFAULT_SEARCH_PROVIDER),
|
|
3438
|
-
[SPEED_MODE_FIELD]: z.union([SPEED_MODE_STANDARD, SPEED_MODE_FAST]).default(DEFAULT_SPEED_MODE),
|
|
3439
|
-
[OUTPUT_VERBOSITY_FIELD]: z.union([
|
|
3440
|
-
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",
|
|
3441
3810
|
"low",
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
]).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(),
|
|
3450
3823
|
[CUSTOM_CONTEXT_WINDOW_FIELD]: z.number().step(1).min(128e3).max(1e6).default(DEFAULT_CUSTOM_CONTEXT_WINDOW),
|
|
3451
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])]))
|
|
3452
3825
|
}));
|
|
@@ -3475,7 +3848,10 @@ function apply(ctx) {
|
|
|
3475
3848
|
resolveOutputVerbosity: () => normalizeOutputVerbosity(settings.get()[OUTPUT_VERBOSITY_FIELD]),
|
|
3476
3849
|
resolveContextMode: () => normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
|
|
3477
3850
|
resolveCustomContextWindow: (modelKey) => {
|
|
3851
|
+
const overrides = readCapabilitySettings(settings.get())[CUSTOM_CONTEXT_OVERRIDES_FIELD];
|
|
3852
|
+
if (Object.hasOwn(overrides, modelKey)) return overrides[modelKey];
|
|
3478
3853
|
const field = CUSTOM_CONTEXT_MODEL_FIELDS[modelKey];
|
|
3854
|
+
if (field === void 0) return void 0;
|
|
3479
3855
|
return normalizeCustomContextWindow(settings.get()[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey]);
|
|
3480
3856
|
},
|
|
3481
3857
|
catalog: modelCatalog,
|
|
@@ -3483,6 +3859,7 @@ function apply(ctx) {
|
|
|
3483
3859
|
});
|
|
3484
3860
|
const preferences = {
|
|
3485
3861
|
status: () => ({
|
|
3862
|
+
...readCapabilitySettings(settings.get()),
|
|
3486
3863
|
[QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]),
|
|
3487
3864
|
[SEARCH_PROVIDER_FIELD]: settings.get()[SEARCH_PROVIDER_FIELD],
|
|
3488
3865
|
[SPEED_MODE_FIELD]: settings.get()[SPEED_MODE_FIELD],
|
|
@@ -3490,8 +3867,10 @@ function apply(ctx) {
|
|
|
3490
3867
|
[CONTEXT_MODE_FIELD]: normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
|
|
3491
3868
|
[CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(settings.get()[CUSTOM_CONTEXT_WINDOW_FIELD]),
|
|
3492
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])])),
|
|
3493
|
-
contextModels: contextModelGroups(
|
|
3870
|
+
contextModels: contextModelGroups(modelCatalog.getModels(baseProvider.getModels())),
|
|
3871
|
+
catalogStatus: modelCatalog.status(),
|
|
3494
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),
|
|
3495
3874
|
writable: ctx.settings.writable
|
|
3496
3875
|
}),
|
|
3497
3876
|
update: (patch) => settings.update(patch)
|
|
@@ -3513,11 +3892,12 @@ function apply(ctx) {
|
|
|
3513
3892
|
let profileKey;
|
|
3514
3893
|
let profileSnapshot;
|
|
3515
3894
|
const profiles = () => {
|
|
3516
|
-
const key = [
|
|
3895
|
+
const key = JSON.stringify([
|
|
3517
3896
|
modelCatalog.revision(),
|
|
3518
3897
|
normalizeContextMode(settings.get()[CONTEXT_MODE_FIELD]),
|
|
3898
|
+
settings.get()[CUSTOM_CONTEXT_OVERRIDES_FIELD],
|
|
3519
3899
|
...Object.values(CUSTOM_CONTEXT_MODEL_FIELDS).map((field) => settings.get()[field])
|
|
3520
|
-
]
|
|
3900
|
+
]);
|
|
3521
3901
|
if (key !== profileKey) {
|
|
3522
3902
|
profileKey = key;
|
|
3523
3903
|
profileSnapshot = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
|
|
@@ -3532,14 +3912,15 @@ function apply(ctx) {
|
|
|
3532
3912
|
fileExists: async () => false
|
|
3533
3913
|
})
|
|
3534
3914
|
});
|
|
3535
|
-
ctx.tools.register(createCodexImageTool({
|
|
3915
|
+
ctx.effect(() => watchImageTool(settings, () => ctx.tools.register(createCodexImageTool({
|
|
3916
|
+
getFeatures: () => settings.get(),
|
|
3536
3917
|
getAuth: resolveAuth,
|
|
3537
3918
|
readCredential: (options) => store.read(PROVIDER, options),
|
|
3538
3919
|
attachments: ctx.attachments,
|
|
3539
3920
|
getSessionMessages: (sessionId) => ctx.get?.("sessions")?.get?.(sessionId)?.deriveMessages?.() ?? [],
|
|
3540
3921
|
originalImages,
|
|
3541
3922
|
fetch: (input, init) => network.fetch("image", input, init)
|
|
3542
|
-
}));
|
|
3923
|
+
}))), "codex-subscription: image tool availability");
|
|
3543
3924
|
const adapter = new PiAiAdapter({
|
|
3544
3925
|
profiles,
|
|
3545
3926
|
resolveApiKey: async () => {
|
|
@@ -3558,6 +3939,7 @@ function apply(ctx) {
|
|
|
3558
3939
|
ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
3559
3940
|
const currentAgent = () => ctx.get?.("agents")?.currentInitiator?.();
|
|
3560
3941
|
const codexSearch = createCodexSearchProvider({
|
|
3942
|
+
resolvePreferences: () => readCapabilitySettings(settings.get()),
|
|
3561
3943
|
getAuth: resolveAuth,
|
|
3562
3944
|
readCredential: (options) => store.read(PROVIDER, options),
|
|
3563
3945
|
resolveModel: () => {
|
|
@@ -3647,7 +4029,7 @@ function apply(ctx) {
|
|
|
3647
4029
|
ctx.effect(() => {
|
|
3648
4030
|
modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
|
|
3649
4031
|
}, "codex-subscription: official model catalog");
|
|
3650
|
-
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"));
|
|
3651
4033
|
}
|
|
3652
4034
|
//#endregion
|
|
3653
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 };
|