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/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 = EXTENDED_CONTEXT_WINDOWS[model.id];
1574
+ const maximum = modelContextMaximum(model);
1305
1575
  const mode = resolveContextMode();
1306
- if (maximum === void 0 || !["extended", "custom"].includes(mode)) return model;
1576
+ if (model.id === "gpt-5.3-codex-spark" || !["extended", "custom"].includes(mode)) return model;
1307
1577
  if (mode === "extended") {
1308
- const contextWindow = model.id === "gpt-6-astra" ? maximum : Math.max(model.contextWindow, maximum);
1578
+ const contextWindow = maximum;
1309
1579
  return {
1310
1580
  ...model,
1311
1581
  contextWindow
1312
1582
  };
1313
1583
  }
1314
- const requested = normalizeCustomContextWindow(resolveCustomContextWindow(customContextModelKey(model.id)), maximum);
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 = "1.14.4";
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: true
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
- return parseSearchResponse(value);
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
- if (!IMAGE_QUALITIES.has(quality)) throw new Error("quality must be auto, low, medium, or high");
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: "Create a new image or explicitly edit selected prior images using the signed-in Codex subscription. Omit referenceImages only for a completely new image. For an edit, copy each complete image reference from the session image block or read_image result, including attachmentId in the exact form sha256:<64 lowercase hex>; never use a workspace path, absolute path, or filename, and do not omit the references to turn an edit into text-to-image generation. A bare 64-character hex digest is accepted and normalized to sha256:<64 lowercase hex>. If a reference is unavailable, call read_image and retry with its returned reference. Include only the exact prior image references the user asked to edit; never assume every image in the conversation is a reference. For annotation-guided edits, include both the named clean source and its numbered location reference, and preserve the numbered coordinates and requested changes in the prompt. The location-reference markers are guidance only and must not appear in the result. If those references cannot be identified, do not substitute unrelated images or silently ignore the annotations.",
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: IMAGE_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 MIN_SAMPLES = 3;
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 = Math.round(clampPercent(window.remainingPercent) * 1e4) / 1e4;
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 record = resetChanged || quotaIncreased ? {
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 && (Math.abs(remainingPercent - latest.remainingPercent) >= .001 || now - latest.at >= PLATEAU_SAMPLE_MS)) {
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
- const samples = record.samples.filter((sample) => sample.at >= now - HISTORY_MS && sample.at <= now + 6e4);
2701
- if (samples.length < MIN_SAMPLES) return {
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
- const first = samples[0];
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
- const slopes = [];
2716
- for (let left = 0; left < samples.length - 1; left += 1) for (let right = left + 1; right < samples.length; right += 1) {
2717
- const hours = (samples[right].at - samples[left].at) / HOUR_MS;
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
- const deviations = positive.map((value) => Math.abs(value - pacePerHour));
2731
- const uncertaintyPerHour = deviations.length === 0 ? 0 : median(deviations) * 1.4826;
2732
- const lowerPacePerHour = Math.max(.02, pacePerHour - uncertaintyPerHour);
2733
- const upperPacePerHour = pacePerHour + uncertaintyPerHour;
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 runwaySeconds = remaining / pacePerHour * 3600;
2736
- const runwayMinSeconds = remaining / upperPacePerHour * 3600;
2737
- const runwayMaxSeconds = remaining / lowerPacePerHour * 3600;
2738
- const resetSeconds = resetsAt === null ? null : Math.max(0, resetsAt - now / 1e3);
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
- uncertaintyPerHour,
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, now, context);
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/index.js
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
- if (Object.hasOwn(payload ?? {}, "quickQuotaMode")) {
3282
- if (![
3283
- "off",
3284
- "percent",
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
- [QUICK_QUOTA_MODE_FIELD]: z.union([
3427
- "off",
3428
- QUICK_QUOTA_MODE_PERCENT,
3429
- "bar",
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
- OUTPUT_VERBOSITY_MEDIUM,
3443
- OUTPUT_VERBOSITY_HIGH
3444
- ]).default(DEFAULT_OUTPUT_VERBOSITY),
3445
- [CONTEXT_MODE_FIELD]: z.union([
3446
- CONTEXT_MODE_STANDARD,
3447
- CONTEXT_MODE_EXTENDED,
3448
- CONTEXT_MODE_CUSTOM
3449
- ]).default(DEFAULT_CONTEXT_MODE),
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(provider.getModels()),
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
- ].join(":");
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.rpc.handle(CHANNEL, handler, { authority: "trusted-host" }), "codex-subscription: DSH-trusted account RPC"));
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 };