@eddyskywalker/dsh-chatgpt-subscription 0.1.27 → 0.1.29

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +26 -25
  2. package/LICENSE +21 -21
  3. package/cordis.patch.yml +6 -6
  4. package/lib/client.js +385 -453
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +342 -281
  7. package/lib/types/client/CodexSubscriptionSection.d.ts +2 -2
  8. package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -1
  9. package/lib/types/client/api.d.ts +1 -2
  10. package/lib/types/client/api.d.ts.map +1 -1
  11. package/lib/types/client/index.d.ts.map +1 -1
  12. package/lib/types/client/locales.d.ts +53 -59
  13. package/lib/types/client/locales.d.ts.map +1 -1
  14. package/lib/types/client/quota.d.ts.map +1 -1
  15. package/lib/types/client/styles.d.ts.map +1 -1
  16. package/lib/types/compat.d.ts +1 -0
  17. package/lib/types/compat.d.ts.map +1 -1
  18. package/lib/types/host/codex-fetch.d.ts +10 -0
  19. package/lib/types/host/codex-fetch.d.ts.map +1 -0
  20. package/lib/types/host/model-catalog.d.ts.map +1 -1
  21. package/lib/types/host/preferences.d.ts.map +1 -1
  22. package/lib/types/host/proxy-manager.d.ts +29 -0
  23. package/lib/types/host/proxy-manager.d.ts.map +1 -0
  24. package/lib/types/host/responses-client.d.ts +2 -0
  25. package/lib/types/host/responses-client.d.ts.map +1 -1
  26. package/lib/types/host/responses-mapper.d.ts +2 -1
  27. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  28. package/lib/types/host/routes.d.ts +2 -1
  29. package/lib/types/host/routes.d.ts.map +1 -1
  30. package/lib/types/host/search-provider-switcher.d.ts +2 -1
  31. package/lib/types/host/search-provider-switcher.d.ts.map +1 -1
  32. package/lib/types/index.d.ts +2 -0
  33. package/lib/types/index.d.ts.map +1 -1
  34. package/lib/types/shared/contracts.d.ts +13 -38
  35. package/lib/types/shared/contracts.d.ts.map +1 -1
  36. package/lib/types/shared/preferences.d.ts +2 -1
  37. package/lib/types/shared/preferences.d.ts.map +1 -1
  38. package/package.json +10 -11
  39. package/lib/types/client/SubagentSettingsSection.d.ts +0 -9
  40. package/lib/types/client/SubagentSettingsSection.d.ts.map +0 -1
  41. package/lib/types/host/subagent-context-adapter.d.ts +0 -24
  42. package/lib/types/host/subagent-context-adapter.d.ts.map +0 -1
  43. package/lib/types/host/subagent-model-preference.d.ts +0 -5
  44. package/lib/types/host/subagent-model-preference.d.ts.map +0 -1
  45. package/lib/types/host/subagent-policy.d.ts +0 -17
  46. package/lib/types/host/subagent-policy.d.ts.map +0 -1
package/lib/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import { CallId, HarnessError, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
2
+ import { WebError } from "@deepseek-ai/dsh-web";
2
3
  import { createUserMessage } from "@deepseek-ai/dsh-llm/message";
3
4
  import { defineTool } from "@deepseek-ai/dsh-tools";
4
5
  import { createHash, randomBytes, randomUUID } from "node:crypto";
5
- import { WebError } from "@deepseek-ai/dsh-web";
6
6
  import http from "node:http";
7
+ import { execSync, spawn } from "node:child_process";
8
+ import { ProxyAgent, fetch as fetch$1 } from "undici";
7
9
  import { settingsNamespace } from "@deepseek-ai/dsh-settings";
8
10
  import z from "@deepseek-ai/schemastery";
9
- import { spawn } from "node:child_process";
10
11
  import { constants } from "node:fs";
11
12
  import { chmod, lstat, mkdir, open, rename, stat, unlink } from "node:fs/promises";
12
13
  import { homedir } from "node:os";
13
14
  import { dirname, join } from "node:path";
14
- import { delegationDepthOf } from "@deepseek-ai/dsh-subagent";
15
15
  //#region src/compat.ts
16
16
  /**
17
17
  * Compatibility constants for the ChatGPT-backed Codex flow. The backend and
@@ -39,6 +39,7 @@ const CODEX_ORIGINATOR = "opencode";
39
39
  const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
40
40
  const CODEX_IMAGE_MODEL = "gpt-image-2";
41
41
  const CODEX_SEARCH_PROVIDER_ID = "codex-subscription";
42
+ const CODEX_FETCH_PROVIDER_ID = "codex-subscription";
42
43
  const QUOTA_MIN_UPSTREAM_INTERVAL_MS = 15e3;
43
44
  const OAUTH_AUTHORIZE_URL = `${CHATGPT_OAUTH_ISSUER}/oauth/authorize`;
44
45
  const OAUTH_TOKEN_URL = `${CHATGPT_OAUTH_ISSUER}/oauth/token`;
@@ -162,7 +163,8 @@ function listCodexModels(preferences) {
162
163
  }
163
164
  function resolveCodexModel(model, preferences) {
164
165
  const entry = resolveCodexCatalogEntry(model);
165
- const configuredContextWindow = isConfigurableContextModelId(model) ? preferences?.status().contextWindowOverrides[model] : void 0;
166
+ const status = preferences?.status();
167
+ const configuredContextWindow = isConfigurableContextModelId(model) ? status?.contextWindowOverrides[model] : void 0;
166
168
  const efforts = reasoningEffortsForModel(model);
167
169
  return {
168
170
  provider: PROVIDER_ID,
@@ -230,6 +232,79 @@ var CodexChatGptAdapter = class extends LlmAdapter {
230
232
  }
231
233
  };
232
234
  //#endregion
235
+ //#region src/host/codex-fetch.ts
236
+ const DEFAULT_MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
237
+ const DEFAULT_MAX_BODY_CHARS = 1e5;
238
+ function createCodexFetchProvider(options = {}) {
239
+ const fetchFn = options.fetchFn ?? fetch;
240
+ const maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
241
+ const maxBodyChars = options.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;
242
+ return {
243
+ id: CODEX_FETCH_PROVIDER_ID,
244
+ available: () => true,
245
+ async fetch(request, signal) {
246
+ const targetUrl = request.url.trim();
247
+ let parsedUrl;
248
+ try {
249
+ parsedUrl = new URL(targetUrl);
250
+ } catch (error) {
251
+ throw new WebError(`invalid URL: ${targetUrl}`, "WEB_INVALID_URL", { cause: error });
252
+ }
253
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new WebError(`unsupported URL scheme "${parsedUrl.protocol}" (only http and https are allowed)`, "WEB_INVALID_URL");
254
+ let response;
255
+ try {
256
+ response = await fetchFn(targetUrl, {
257
+ method: "GET",
258
+ headers: {
259
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
260
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.5"
261
+ },
262
+ redirect: "follow",
263
+ signal
264
+ });
265
+ } catch (error) {
266
+ if (signal?.aborted) throw new WebError("web fetch aborted", "WEB_ABORTED", { cause: error });
267
+ throw new WebError(`web fetch failed: ${error instanceof Error ? error.message : String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
268
+ }
269
+ const contentType = response.headers.get("content-type") || "";
270
+ const mime = contentType.replace(/;.*$/s, "").trim().toLowerCase();
271
+ const kind = mime === "text/html" || mime === "application/xhtml+xml" ? "html" : "text";
272
+ let charset = "utf-8";
273
+ const match = /;\s*charset\s*=\s*"?([^";]+)"?/i.exec(contentType);
274
+ if (match?.[1]) charset = match[1].trim().toLowerCase();
275
+ let decoder;
276
+ try {
277
+ decoder = new TextDecoder(charset);
278
+ } catch {
279
+ decoder = new TextDecoder("utf-8");
280
+ }
281
+ let rawBytes;
282
+ let truncatedByBytes = false;
283
+ try {
284
+ const buffer = await response.arrayBuffer();
285
+ if (buffer.byteLength > maxResponseBytes) {
286
+ rawBytes = new Uint8Array(buffer.slice(0, maxResponseBytes));
287
+ truncatedByBytes = true;
288
+ } else rawBytes = new Uint8Array(buffer);
289
+ } catch (error) {
290
+ throw new WebError(`failed to read response body: ${error instanceof Error ? error.message : String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
291
+ }
292
+ const decodedText = decoder.decode(rawBytes);
293
+ const truncatedByChars = decodedText.length > maxBodyChars;
294
+ const finalContent = truncatedByChars ? decodedText.slice(0, maxBodyChars) : decodedText;
295
+ return {
296
+ url: response.url || targetUrl,
297
+ statusCode: response.status,
298
+ body: {
299
+ kind,
300
+ content: finalContent
301
+ },
302
+ truncated: truncatedByBytes || truncatedByChars
303
+ };
304
+ }
305
+ };
306
+ }
307
+ //#endregion
233
308
  //#region src/host/wire-auth.ts
234
309
  function codexHeaders(credentials, sessionId) {
235
310
  const dshAgent = attributionHeaders()["user-agent"] ?? "dsh/unknown";
@@ -502,10 +577,7 @@ function sendSearch(fetchFn, credentials, query, model, id, signal) {
502
577
  id,
503
578
  model,
504
579
  input: query,
505
- commands: {
506
- search_query: [{ q: query }],
507
- response_length: "short"
508
- },
580
+ commands: { search_query: [{ q: query }] },
509
581
  settings: {
510
582
  allowed_callers: ["direct"],
511
583
  external_web_access: true
@@ -1075,24 +1147,171 @@ async function oauthErrorIdentifier(response) {
1075
1147
  return null;
1076
1148
  }
1077
1149
  //#endregion
1150
+ //#region src/host/proxy-manager.ts
1151
+ const SYSTEM_PROXY_CACHE_TTL_MS = 5e3;
1152
+ function normalizeProxyUrl(rawUrl) {
1153
+ const trimmed = rawUrl.trim();
1154
+ if (!trimmed) return "";
1155
+ if (/^https?:\/\//i.test(trimmed) || /^socks5?:\/\//i.test(trimmed)) return trimmed;
1156
+ return `http://${trimmed}`;
1157
+ }
1158
+ function parseWindowsProxyRegistry(stdout) {
1159
+ const enableMatch = stdout.match(/ProxyEnable\s+REG_DWORD\s+(0x[0-9a-fA-F]+|\d+)/i);
1160
+ if (!enableMatch) return null;
1161
+ if ((enableMatch[1].startsWith("0x") ? parseInt(enableMatch[1], 16) : parseInt(enableMatch[1], 10)) !== 1) return null;
1162
+ const serverMatch = stdout.match(/ProxyServer\s+REG_SZ\s+([^\r\n]+)/i);
1163
+ if (!serverMatch) return null;
1164
+ const rawServer = serverMatch[1].trim();
1165
+ if (!rawServer) return null;
1166
+ if (rawServer.includes("=")) {
1167
+ const pairs = rawServer.split(";");
1168
+ const map = {};
1169
+ for (const pair of pairs) {
1170
+ const [proto, addr] = pair.split("=").map((s) => s.trim());
1171
+ if (proto && addr) map[proto.toLowerCase()] = addr;
1172
+ }
1173
+ const target = map.https || map.http || map.socks;
1174
+ if (target) {
1175
+ if (map.socks && !map.https && !map.http) return normalizeProxyUrl(target.startsWith("socks") ? target : `socks5://${target}`);
1176
+ return normalizeProxyUrl(target);
1177
+ }
1178
+ return null;
1179
+ }
1180
+ return normalizeProxyUrl(rawServer);
1181
+ }
1182
+ function parseMacOsScutilProxy(stdout) {
1183
+ const httpsEnable = /HTTPSEnable\s*:\s*1/i.test(stdout);
1184
+ const httpEnable = /HTTPEnable\s*:\s*1/i.test(stdout);
1185
+ const socksEnable = /SOCKSEnable\s*:\s*1/i.test(stdout);
1186
+ if (httpsEnable) {
1187
+ const host = stdout.match(/HTTPSProxy\s*:\s*([^\s\r\n]+)/i)?.[1];
1188
+ const port = stdout.match(/HTTPSPort\s*:\s*(\d+)/i)?.[1];
1189
+ if (host && port) return normalizeProxyUrl(`${host}:${port}`);
1190
+ }
1191
+ if (httpEnable) {
1192
+ const host = stdout.match(/HTTPProxy\s*:\s*([^\s\r\n]+)/i)?.[1];
1193
+ const port = stdout.match(/HTTPPort\s*:\s*(\d+)/i)?.[1];
1194
+ if (host && port) return normalizeProxyUrl(`${host}:${port}`);
1195
+ }
1196
+ if (socksEnable) {
1197
+ const host = stdout.match(/SOCKSProxy\s*:\s*([^\s\r\n]+)/i)?.[1];
1198
+ const port = stdout.match(/SOCKSPort\s*:\s*(\d+)/i)?.[1];
1199
+ if (host && port) return normalizeProxyUrl(`socks5://${host}:${port}`);
1200
+ }
1201
+ return null;
1202
+ }
1203
+ function parseEnvProxy(env = process.env) {
1204
+ const proxy = env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
1205
+ if (!proxy || !proxy.trim()) return null;
1206
+ return normalizeProxyUrl(proxy);
1207
+ }
1208
+ function detectSystemProxy(platform = process.platform, env = process.env) {
1209
+ try {
1210
+ if (platform === "win32") {
1211
+ const detected = parseWindowsProxyRegistry(execSync("reg query \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", {
1212
+ timeout: 1500,
1213
+ encoding: "utf-8",
1214
+ stdio: [
1215
+ "ignore",
1216
+ "pipe",
1217
+ "ignore"
1218
+ ]
1219
+ }));
1220
+ if (detected) return detected;
1221
+ } else if (platform === "darwin") {
1222
+ const detected = parseMacOsScutilProxy(execSync("scutil --proxy", {
1223
+ timeout: 1500,
1224
+ encoding: "utf-8",
1225
+ stdio: [
1226
+ "ignore",
1227
+ "pipe",
1228
+ "ignore"
1229
+ ]
1230
+ }));
1231
+ if (detected) return detected;
1232
+ }
1233
+ } catch {}
1234
+ return parseEnvProxy(env);
1235
+ }
1236
+ var ProxyManager = class {
1237
+ getPreferences;
1238
+ baseFetch;
1239
+ systemProxyDetector;
1240
+ logger;
1241
+ cachedSystemProxy = null;
1242
+ lastSystemProxyCheck = 0;
1243
+ agents = /* @__PURE__ */ new Map();
1244
+ constructor(options) {
1245
+ this.getPreferences = options.getPreferences;
1246
+ this.baseFetch = options.baseFetch ?? fetch;
1247
+ this.systemProxyDetector = options.systemProxyDetector ?? (() => detectSystemProxy());
1248
+ this.logger = options.logger;
1249
+ }
1250
+ getSystemProxy(force = false) {
1251
+ const now = Date.now();
1252
+ if (!force && now - this.lastSystemProxyCheck < SYSTEM_PROXY_CACHE_TTL_MS) return this.cachedSystemProxy;
1253
+ this.lastSystemProxyCheck = now;
1254
+ try {
1255
+ this.cachedSystemProxy = this.systemProxyDetector();
1256
+ } catch (error) {
1257
+ this.cachedSystemProxy = null;
1258
+ this.logger?.warn?.(`[dsh-chatgpt-subscription] Failed to detect system proxy: ${error instanceof Error ? error.message : String(error)}`);
1259
+ }
1260
+ return this.cachedSystemProxy;
1261
+ }
1262
+ resolveActiveProxyUrl() {
1263
+ const prefs = this.getPreferences();
1264
+ const mode = prefs.proxyMode ?? "auto";
1265
+ if (mode === "direct") return null;
1266
+ if (mode === "custom") return prefs.customProxyUrl ? normalizeProxyUrl(prefs.customProxyUrl) : null;
1267
+ return this.getSystemProxy();
1268
+ }
1269
+ getOrCreateAgent(proxyUrl) {
1270
+ let agent = this.agents.get(proxyUrl);
1271
+ if (!agent) {
1272
+ agent = new ProxyAgent(proxyUrl);
1273
+ this.agents.set(proxyUrl, agent);
1274
+ }
1275
+ return agent;
1276
+ }
1277
+ createFetch() {
1278
+ return async (input, init) => {
1279
+ const activeProxy = this.resolveActiveProxyUrl();
1280
+ if (!activeProxy) return this.baseFetch(input, init);
1281
+ try {
1282
+ const agent = this.getOrCreateAgent(activeProxy);
1283
+ return await fetch$1(input, {
1284
+ ...init,
1285
+ dispatcher: agent
1286
+ });
1287
+ } catch (error) {
1288
+ throw error;
1289
+ }
1290
+ };
1291
+ }
1292
+ dispose() {
1293
+ for (const agent of this.agents.values()) agent.destroy().catch(() => void 0);
1294
+ this.agents.clear();
1295
+ }
1296
+ };
1297
+ //#endregion
1078
1298
  //#region src/shared/preferences.ts
1079
1299
  const PREFERENCES_NAMESPACE = "dsh-chatgpt-subscription";
1080
1300
  const DEFAULT_PREFERENCES = {
1081
1301
  quickQuotaVisible: false,
1302
+ fastMode: false,
1082
1303
  outputVerbosity: null,
1083
1304
  visibleModelIds: [...DEFAULT_VISIBLE_CODEX_MODEL_IDS],
1084
1305
  searchProvider: "dsh",
1085
- subagentProvider: "codex-chatgpt",
1086
- subagentModel: "gpt-5.6-sol",
1087
- subagentReasoningEffort: "medium",
1088
- subagentContextWindow: 272e3,
1089
- subagentMaxDepth: 3,
1090
- subagentMaxAgents: 8,
1091
1306
  contextWindowOverrides: {
1092
1307
  "gpt-5.6-sol": 272e3,
1093
1308
  "gpt-5.6-terra": 272e3,
1094
1309
  "gpt-5.6-luna": 272e3
1095
- }
1310
+ },
1311
+ subagentContextWindow: null,
1312
+ subagentMaxDepth: null,
1313
+ proxyMode: "auto",
1314
+ customProxyUrl: null
1096
1315
  };
1097
1316
  const SEARCH_PROVIDER_CODEX = "codex";
1098
1317
  function isSearchProviderPreference(value) {
@@ -1101,11 +1320,15 @@ function isSearchProviderPreference(value) {
1101
1320
  function isCodexOutputVerbosity(value) {
1102
1321
  return value === "low" || value === "medium" || value === "high";
1103
1322
  }
1323
+ function isProxyMode(value) {
1324
+ return value === "auto" || value === "custom" || value === "direct";
1325
+ }
1104
1326
  //#endregion
1105
1327
  //#region src/host/preferences.ts
1106
1328
  function registerPreferenceStore(settings) {
1107
1329
  return new SettingsPreferenceStore(settings.register(settingsNamespace(PREFERENCES_NAMESPACE), z.object({
1108
1330
  quickQuotaVisible: z.boolean().default(DEFAULT_PREFERENCES.quickQuotaVisible),
1331
+ fastMode: z.boolean().default(DEFAULT_PREFERENCES.fastMode),
1109
1332
  outputVerbosity: z.union([
1110
1333
  z.const("low"),
1111
1334
  z.const("medium"),
@@ -1114,17 +1337,19 @@ function registerPreferenceStore(settings) {
1114
1337
  ]).default(DEFAULT_PREFERENCES.outputVerbosity),
1115
1338
  visibleModelIds: z.array(z.string()).default(DEFAULT_PREFERENCES.visibleModelIds),
1116
1339
  searchProvider: z.union([z.const("dsh"), z.const(SEARCH_PROVIDER_CODEX)]).default(DEFAULT_PREFERENCES.searchProvider),
1117
- subagentProvider: z.string().default(DEFAULT_PREFERENCES.subagentProvider),
1118
- subagentModel: z.string().default(DEFAULT_PREFERENCES.subagentModel),
1119
- subagentReasoningEffort: z.union([z.string(), z.const(null)]).default(DEFAULT_PREFERENCES.subagentReasoningEffort),
1120
- subagentContextWindow: z.number().step(1).min(1).default(DEFAULT_PREFERENCES.subagentContextWindow),
1121
- subagentMaxDepth: z.number().step(1).min(0).max(3).default(DEFAULT_PREFERENCES.subagentMaxDepth),
1122
- subagentMaxAgents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_PREFERENCES.subagentMaxAgents),
1123
1340
  contextWindowOverrides: z.object({
1124
1341
  "gpt-5.6-sol": z.number().step(1).min(1).max(GPT_56_MAX_CONTEXT_WINDOW).default(DEFAULT_PREFERENCES.contextWindowOverrides["gpt-5.6-sol"]),
1125
1342
  "gpt-5.6-terra": z.number().step(1).min(1).max(GPT_56_MAX_CONTEXT_WINDOW).default(DEFAULT_PREFERENCES.contextWindowOverrides["gpt-5.6-terra"]),
1126
1343
  "gpt-5.6-luna": z.number().step(1).min(1).max(GPT_56_MAX_CONTEXT_WINDOW).default(DEFAULT_PREFERENCES.contextWindowOverrides["gpt-5.6-luna"])
1127
- }).default(DEFAULT_PREFERENCES.contextWindowOverrides)
1344
+ }).default(DEFAULT_PREFERENCES.contextWindowOverrides),
1345
+ subagentContextWindow: z.union([z.number().step(1).min(1), z.const(null)]).default(DEFAULT_PREFERENCES.subagentContextWindow),
1346
+ subagentMaxDepth: z.union([z.number().step(1).min(0).max(3), z.const(null)]).default(DEFAULT_PREFERENCES.subagentMaxDepth),
1347
+ proxyMode: z.union([
1348
+ z.const("auto"),
1349
+ z.const("custom"),
1350
+ z.const("direct")
1351
+ ]).default(DEFAULT_PREFERENCES.proxyMode),
1352
+ customProxyUrl: z.union([z.string(), z.const(null)]).default(DEFAULT_PREFERENCES.customProxyUrl)
1128
1353
  })));
1129
1354
  }
1130
1355
  var SettingsPreferenceStore = class {
@@ -1138,6 +1363,7 @@ var SettingsPreferenceStore = class {
1138
1363
  async update(patch) {
1139
1364
  const normalized = {};
1140
1365
  if (patch.quickQuotaVisible !== void 0) normalized.quickQuotaVisible = patch.quickQuotaVisible;
1366
+ if (patch.fastMode !== void 0) normalized.fastMode = patch.fastMode;
1141
1367
  if (patch.outputVerbosity !== void 0) {
1142
1368
  if (patch.outputVerbosity !== null && !isCodexOutputVerbosity(patch.outputVerbosity)) throw new PreferenceError("Unsupported output verbosity preference.");
1143
1369
  normalized.outputVerbosity = patch.outputVerbosity;
@@ -1150,16 +1376,21 @@ var SettingsPreferenceStore = class {
1150
1376
  if (!isSearchProviderPreference(patch.searchProvider)) throw new PreferenceError("Unsupported search provider preference.");
1151
1377
  normalized.searchProvider = patch.searchProvider;
1152
1378
  }
1153
- if (patch.subagentProvider !== void 0) normalized.subagentProvider = patch.subagentProvider;
1154
- if (patch.subagentModel !== void 0) normalized.subagentModel = patch.subagentModel;
1155
- if (patch.subagentReasoningEffort !== void 0) normalized.subagentReasoningEffort = patch.subagentReasoningEffort;
1156
- if (patch.subagentContextWindow !== void 0) normalized.subagentContextWindow = patch.subagentContextWindow;
1157
- if (patch.subagentMaxDepth !== void 0) normalized.subagentMaxDepth = patch.subagentMaxDepth;
1158
- if (patch.subagentMaxAgents !== void 0) normalized.subagentMaxAgents = patch.subagentMaxAgents;
1159
1379
  if (patch.contextWindowOverrides !== void 0) normalized.contextWindowOverrides = {
1160
1380
  ...this.scope.get().contextWindowOverrides,
1161
1381
  ...patch.contextWindowOverrides
1162
1382
  };
1383
+ if (patch.subagentContextWindow !== void 0) normalized.subagentContextWindow = patch.subagentContextWindow;
1384
+ if (patch.subagentMaxDepth !== void 0) normalized.subagentMaxDepth = patch.subagentMaxDepth;
1385
+ if (patch.proxyMode !== void 0) {
1386
+ if (!isProxyMode(patch.proxyMode)) throw new PreferenceError("Unsupported proxy mode preference.");
1387
+ normalized.proxyMode = patch.proxyMode;
1388
+ }
1389
+ if (patch.customProxyUrl !== void 0) if (patch.customProxyUrl !== null) {
1390
+ const trimmed = patch.customProxyUrl.trim();
1391
+ if (trimmed.length > 0 && !/^https?:\/\//i.test(trimmed) && !/^socks5?:\/\//i.test(trimmed)) normalized.customProxyUrl = `http://${trimmed}`;
1392
+ else normalized.customProxyUrl = trimmed.length === 0 ? null : trimmed;
1393
+ } else normalized.customProxyUrl = null;
1163
1394
  await this.scope.update(normalized);
1164
1395
  return this.status();
1165
1396
  }
@@ -1213,13 +1444,14 @@ function normalizeReplayCallIds(items, normalizeCallId) {
1213
1444
  call_id: normalizeCallId(item.call_id)
1214
1445
  } : item);
1215
1446
  }
1216
- async function buildResponsesPayload(options, attachments, localRawImages = {}, outputVerbosity = null) {
1447
+ async function buildResponsesPayload(options, attachments, localRawImages = {}, outputVerbosity = null, fastMode = false) {
1217
1448
  const sandboxRetryTools = recentSandboxRetryToolNames(options.messages);
1218
1449
  const resolveLocalRawImages = supportsImageInput(options);
1219
1450
  const normalizeCallId = createCallIdNormalizer();
1220
1451
  const instructionParts = [
1221
1452
  options.system?.trim(),
1222
1453
  ...options.messages.filter((message) => message.role === "system").map((message) => blocksToText(message.content).trim()),
1454
+ progressExplanationInstruction(options.tools),
1223
1455
  sandboxToolInstruction(options.tools, sandboxRetryTools),
1224
1456
  commandToolInstruction(options.tools),
1225
1457
  runCodeInstruction(options.tools)
@@ -1295,12 +1527,17 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
1295
1527
  payload.parallel_tool_calls = true;
1296
1528
  }
1297
1529
  if (outputVerbosity !== null) payload.text = { verbosity: outputVerbosity };
1530
+ if (fastMode) payload.service_tier = "priority";
1298
1531
  if (options.reasoningEffort !== void 0) payload.reasoning = codexModelSupportsReasoningSummary(options.model) ? {
1299
1532
  effort: options.reasoningEffort,
1300
1533
  summary: "auto"
1301
1534
  } : { effort: options.reasoningEffort };
1302
1535
  return payload;
1303
1536
  }
1537
+ function progressExplanationInstruction(tools) {
1538
+ if (!tools?.length) return void 0;
1539
+ return "Progress and tool execution rule: when executing multi-step tasks or invoking tools, output 1-2 concise sentences of progress, intent, or intermediate findings before each tool call. Keep progress text brief, professional, and factual. Only present the comprehensive final answer and summary in the final turn after all tool operations are completed.";
1540
+ }
1304
1541
  function runCodeInstruction(tools) {
1305
1542
  if (!tools?.some((tool) => tool.name === "run_code")) return void 0;
1306
1543
  return "run_code compatibility rule: its code is parsed as strict JavaScript/TypeScript before execution. Shell commands are nested string data: JavaScript template literals may consume ${...}, backticks, backslashes, and escape sequences before PowerShell, Bash, or POSIX sh sees them. On Windows, avoid embedding PowerShell containing $, ${...}, backslashes, or here-strings in template literals; String.raw does not disable ${...} interpolation. On Linux, prefer ordinary quoted strings or write a script file before invoking bash/sh, especially for commands containing backticks or ${...}. Prefer arrays of ordinary quoted strings joined with \"\\n\", escaping backslashes, or use a file-write tool for large scripts.";
@@ -1595,6 +1832,7 @@ var ResponsesClient = class {
1595
1832
  fetchFn;
1596
1833
  onGenerationFinished;
1597
1834
  outputVerbosity;
1835
+ fastMode;
1598
1836
  constructor(oauth, attachments, options = {}) {
1599
1837
  this.oauth = oauth;
1600
1838
  this.attachments = attachments;
@@ -1602,10 +1840,11 @@ var ResponsesClient = class {
1602
1840
  this.localRawImages = options.localRawImages ?? {};
1603
1841
  this.onGenerationFinished = options.onGenerationFinished ?? (() => void 0);
1604
1842
  this.outputVerbosity = options.outputVerbosity ?? (() => null);
1843
+ this.fastMode = options.fastMode ?? (() => false);
1605
1844
  }
1606
1845
  localRawImages;
1607
1846
  async *stream(options) {
1608
- const payload = await buildResponsesPayload(options, this.attachments, this.localRawImages, this.outputVerbosity());
1847
+ const payload = await buildResponsesPayload(options, this.attachments, this.localRawImages, this.outputVerbosity(), this.fastMode());
1609
1848
  const hiddenSandboxControls = hiddenSandboxControlToolNames(options);
1610
1849
  const sessionId = stableSessionId(options.sessionId);
1611
1850
  try {
@@ -2373,61 +2612,11 @@ function identityKey(credentials) {
2373
2612
  return credentials.accountId ?? credentials.email ?? credentials.planType ?? "signed-in";
2374
2613
  }
2375
2614
  //#endregion
2376
- //#region src/host/subagent-context-adapter.ts
2377
- /**
2378
- * A private provider route that delegates streaming to a selected registered
2379
- * provider while exposing a user-selected effective context window to DSH.
2380
- */
2381
- var SubagentContextAdapter = class SubagentContextAdapter extends LlmAdapter {
2382
- llm;
2383
- selection;
2384
- static provider = "dsh-subagent-model-override";
2385
- constructor(llm, selection) {
2386
- super();
2387
- this.llm = llm;
2388
- this.selection = selection;
2389
- }
2390
- providerInfo() {
2391
- return {
2392
- id: SubagentContextAdapter.provider,
2393
- name: "Subagent model override"
2394
- };
2395
- }
2396
- async listModels() {
2397
- return [];
2398
- }
2399
- async resolveModel(_provider, _model, signal) {
2400
- const selected = this.selection();
2401
- return {
2402
- ...await this.llm.resolveModelInfo(selected.provider, selected.model, signal),
2403
- provider: SubagentContextAdapter.provider,
2404
- id: selected.model,
2405
- context: { contextWindow: selected.contextWindow }
2406
- };
2407
- }
2408
- stream(options) {
2409
- const selected = this.selection();
2410
- const { preparedCall: _preparedCall, ...forwarded } = options;
2411
- return this.llm.stream({
2412
- ...forwarded,
2413
- provider: selected.provider,
2414
- model: selected.model
2415
- });
2416
- }
2417
- };
2418
- //#endregion
2419
2615
  //#region src/host/routes.ts
2420
2616
  const MAX_BODY_BYTES = 64 * 1024;
2421
- function registerRoutes(ctx, oauth, usage, preferences) {
2617
+ function registerRoutes(ctx, oauth, usage, preferences, proxyManager) {
2422
2618
  const handler = async (request, response) => {
2423
2619
  const url = new URL(request.url ?? "/", "http://dsh.local");
2424
- if (request.method === "GET" && url.pathname === `/api/dsh-chatgpt-subscription/models`) {
2425
- json(response, {
2426
- ok: true,
2427
- value: await subagentModelCatalog(ctx)
2428
- });
2429
- return;
2430
- }
2431
2620
  if (request.method === "GET" && url.pathname === `/api/dsh-chatgpt-subscription/status`) {
2432
2621
  const oauthStatus = await oauth.status();
2433
2622
  json(response, {
@@ -2435,7 +2624,9 @@ function registerRoutes(ctx, oauth, usage, preferences) {
2435
2624
  value: {
2436
2625
  ...oauthStatus,
2437
2626
  quota: await usage.status(oauthStatus.authenticated),
2438
- preferences: preferences.status()
2627
+ preferences: preferences.status(),
2628
+ detectedProxy: proxyManager?.getSystemProxy() ?? null,
2629
+ activeProxy: proxyManager?.resolveActiveProxyUrl() ?? null
2439
2630
  }
2440
2631
  });
2441
2632
  return;
@@ -2656,6 +2847,10 @@ function readPreferencesUpdate(value, current) {
2656
2847
  if (typeof value.quickQuotaVisible !== "boolean") throw new PreferenceError("quickQuotaVisible must be a boolean.");
2657
2848
  patch.quickQuotaVisible = value.quickQuotaVisible;
2658
2849
  }
2850
+ if ("fastMode" in value) {
2851
+ if (typeof value.fastMode !== "boolean") throw new PreferenceError("fastMode must be a boolean.");
2852
+ patch.fastMode = value.fastMode;
2853
+ }
2659
2854
  if ("outputVerbosity" in value) {
2660
2855
  if (value.outputVerbosity !== null && value.outputVerbosity !== "low" && value.outputVerbosity !== "medium" && value.outputVerbosity !== "high") throw new PreferenceError("outputVerbosity must be null, low, medium, or high.");
2661
2856
  patch.outputVerbosity = value.outputVerbosity;
@@ -2664,30 +2859,6 @@ function readPreferencesUpdate(value, current) {
2664
2859
  if (value.searchProvider !== "dsh" && value.searchProvider !== "codex") throw new PreferenceError("searchProvider must be dsh or codex.");
2665
2860
  patch.searchProvider = value.searchProvider;
2666
2861
  }
2667
- if ("subagentProvider" in value) {
2668
- if (typeof value.subagentProvider !== "string" || value.subagentProvider === "") throw new PreferenceError("subagentProvider must be a non-empty string.");
2669
- patch.subagentProvider = value.subagentProvider;
2670
- }
2671
- if ("subagentModel" in value) {
2672
- if (typeof value.subagentModel !== "string" || value.subagentModel === "") throw new PreferenceError("subagentModel must be a non-empty string.");
2673
- patch.subagentModel = value.subagentModel;
2674
- }
2675
- if ("subagentReasoningEffort" in value) {
2676
- if (value.subagentReasoningEffort !== null && (typeof value.subagentReasoningEffort !== "string" || value.subagentReasoningEffort === "")) throw new PreferenceError("subagentReasoningEffort must be null or a non-empty string.");
2677
- patch.subagentReasoningEffort = value.subagentReasoningEffort;
2678
- }
2679
- if ("subagentContextWindow" in value) {
2680
- if (!Number.isSafeInteger(value.subagentContextWindow) || value.subagentContextWindow < 1) throw new PreferenceError("subagentContextWindow must be a positive integer.");
2681
- patch.subagentContextWindow = value.subagentContextWindow;
2682
- }
2683
- if ("subagentMaxDepth" in value) {
2684
- if (!Number.isSafeInteger(value.subagentMaxDepth) || value.subagentMaxDepth < 0 || value.subagentMaxDepth > 3) throw new PreferenceError(`subagentMaxDepth must be an integer from 0 to 3.`);
2685
- patch.subagentMaxDepth = value.subagentMaxDepth;
2686
- }
2687
- if ("subagentMaxAgents" in value) {
2688
- if (!Number.isSafeInteger(value.subagentMaxAgents) || value.subagentMaxAgents < 1) throw new PreferenceError("subagentMaxAgents must be a positive integer.");
2689
- patch.subagentMaxAgents = value.subagentMaxAgents;
2690
- }
2691
2862
  if ("contextWindowOverrides" in value) {
2692
2863
  if (!isRecord(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
2693
2864
  const overrides = {};
@@ -2698,55 +2869,24 @@ function readPreferencesUpdate(value, current) {
2698
2869
  }
2699
2870
  patch.contextWindowOverrides = overrides;
2700
2871
  }
2872
+ if ("subagentContextWindow" in value) {
2873
+ if (value.subagentContextWindow !== null && (!Number.isSafeInteger(value.subagentContextWindow) || value.subagentContextWindow < 1)) throw new PreferenceError("subagentContextWindow must be null or a positive integer.");
2874
+ patch.subagentContextWindow = value.subagentContextWindow;
2875
+ }
2876
+ if ("subagentMaxDepth" in value) {
2877
+ if (value.subagentMaxDepth !== null && (!Number.isSafeInteger(value.subagentMaxDepth) || value.subagentMaxDepth < 0 || value.subagentMaxDepth > 3)) throw new PreferenceError(`subagentMaxDepth must be null or an integer from 0 to 3.`);
2878
+ patch.subagentMaxDepth = value.subagentMaxDepth;
2879
+ }
2880
+ if ("proxyMode" in value) {
2881
+ if (value.proxyMode !== "auto" && value.proxyMode !== "custom" && value.proxyMode !== "direct") throw new PreferenceError("proxyMode must be auto, custom, or direct.");
2882
+ patch.proxyMode = value.proxyMode;
2883
+ }
2884
+ if ("customProxyUrl" in value) {
2885
+ if (value.customProxyUrl !== null && typeof value.customProxyUrl !== "string") throw new PreferenceError("customProxyUrl must be a string or null.");
2886
+ patch.customProxyUrl = value.customProxyUrl;
2887
+ }
2701
2888
  return patch;
2702
2889
  }
2703
- async function subagentModelCatalog(ctx) {
2704
- const results = await Promise.all(ctx.llm.listProviders().filter((provider) => provider.id !== SubagentContextAdapter.provider).map(async (provider) => {
2705
- try {
2706
- const models = await ctx.llm.listModels(provider.id);
2707
- return {
2708
- kind: "provider",
2709
- provider: {
2710
- id: provider.id,
2711
- name: provider.name,
2712
- models: await Promise.all(models.map(async (model) => {
2713
- const resolved = await ctx.llm.resolveModelInfo(provider.id, model.id);
2714
- return {
2715
- id: model.id,
2716
- name: model.name,
2717
- ...model.description === void 0 ? {} : { description: model.description },
2718
- ...resolved.context === void 0 ? {} : {
2719
- contextWindow: resolved.context.contextWindow,
2720
- maxContextWindow: provider.id === "codex-chatgpt" && isConfigurableContextModelId(model.id) ? GPT_56_MAX_CONTEXT_WINDOW : resolved.context.contextWindow
2721
- },
2722
- ...resolved.reasoning === void 0 ? {} : { reasoning: {
2723
- efforts: resolved.reasoning.efforts.map((effort) => ({
2724
- id: effort.id,
2725
- name: effort.name,
2726
- ...effort.description === void 0 ? {} : { description: effort.description }
2727
- })),
2728
- ...resolved.reasoning.defaultEffort === void 0 ? {} : { defaultEffort: resolved.reasoning.defaultEffort }
2729
- } }
2730
- };
2731
- }))
2732
- }
2733
- };
2734
- } catch (error) {
2735
- return {
2736
- kind: "failure",
2737
- failure: {
2738
- id: provider.id,
2739
- name: provider.name,
2740
- message: error instanceof Error ? error.message : String(error)
2741
- }
2742
- };
2743
- }
2744
- }));
2745
- return {
2746
- providers: results.filter((result) => result.kind === "provider").map((result) => result.provider),
2747
- failures: results.filter((result) => result.kind === "failure").map((result) => result.failure)
2748
- };
2749
- }
2750
2890
  function json(response, envelope, status = 200) {
2751
2891
  response.writeHead(status, {
2752
2892
  "content-type": "application/json; charset=utf-8",
@@ -3108,7 +3248,8 @@ function createPlatformTokenStore(platform = process.platform) {
3108
3248
  //#region src/host/search-provider-switcher.ts
3109
3249
  var SearchProviderSwitcher = class {
3110
3250
  loader;
3111
- originalProvider;
3251
+ originalSearchProvider;
3252
+ originalFetchProvider;
3112
3253
  initialized = false;
3113
3254
  constructor(loader) {
3114
3255
  this.loader = loader;
@@ -3118,14 +3259,19 @@ var SearchProviderSwitcher = class {
3118
3259
  if (entry === null) return;
3119
3260
  const config = currentConfig(entry);
3120
3261
  if (!this.initialized) {
3121
- this.originalProvider = typeof config.searchProvider === "string" && config.searchProvider !== "codex-subscription" ? config.searchProvider : void 0;
3262
+ this.originalSearchProvider = typeof config.searchProvider === "string" && config.searchProvider !== "codex-subscription" ? config.searchProvider : void 0;
3263
+ this.originalFetchProvider = typeof config.fetchProvider === "string" && config.fetchProvider !== "codex-subscription" ? config.fetchProvider : void 0;
3122
3264
  this.initialized = true;
3123
3265
  }
3124
- const selected = preference === "codex" ? CODEX_SEARCH_PROVIDER_ID : this.originalProvider;
3125
- if (config.searchProvider === selected) return;
3266
+ const selected = preference === "codex" ? CODEX_SEARCH_PROVIDER_ID : void 0;
3267
+ const nextSearch = selected ?? this.originalSearchProvider;
3268
+ const nextFetch = selected ? CODEX_FETCH_PROVIDER_ID : this.originalFetchProvider;
3269
+ if (config.searchProvider === nextSearch && config.fetchProvider === nextFetch) return;
3126
3270
  const nextConfig = { ...config };
3127
- if (selected === void 0) delete nextConfig.searchProvider;
3128
- else nextConfig.searchProvider = selected;
3271
+ if (nextSearch === void 0) delete nextConfig.searchProvider;
3272
+ else nextConfig.searchProvider = nextSearch;
3273
+ if (nextFetch === void 0) delete nextConfig.fetchProvider;
3274
+ else nextConfig.fetchProvider = nextFetch;
3129
3275
  await entry.update({ config: nextConfig }, true);
3130
3276
  }
3131
3277
  findWebEntry() {
@@ -3141,100 +3287,6 @@ function currentConfig(entry) {
3141
3287
  return typeof config === "object" && config !== null && !Array.isArray(config) ? config : {};
3142
3288
  }
3143
3289
  //#endregion
3144
- //#region src/host/subagent-model-preference.ts
3145
- /** Apply the configured provider/model/reasoning route to every delegated Agent. */
3146
- function installSubagentModelPreference(ctx, preferences) {
3147
- return ctx.on("agent/request", async (payload, next) => {
3148
- const resolved = await next();
3149
- if (payload.agent.session.header.origin !== "subagent") return resolved;
3150
- const selection = preferences.status();
3151
- return {
3152
- ...resolved,
3153
- provider: SubagentContextAdapter.provider,
3154
- model: selection.subagentModel,
3155
- ...selection.subagentReasoningEffort === null ? { reasoningEffort: void 0 } : { reasoningEffort: ReasoningEffortId(selection.subagentReasoningEffort) }
3156
- };
3157
- });
3158
- }
3159
- //#endregion
3160
- //#region src/host/subagent-policy.ts
3161
- const MANAGED_DELEGATION_TOOLS = /* @__PURE__ */ new Set(["subagent", "subagent_fork"]);
3162
- /**
3163
- * Enforce global limits with a user-facing preflight on built-in delegation
3164
- * tools and an authoritative synchronous Agent-publication gate. The latter
3165
- * also covers workflows, Ralph, and nested subagents without depending on the
3166
- * parent's provider or model. Throwing from `agent/created` vetoes and rolls
3167
- * back the new child before its loop starts; already-running children remain.
3168
- */
3169
- function installSubagentPolicy(ctx, preferences) {
3170
- const disposePreflight = ctx.on("tools/pre-execute", async (exec, next) => {
3171
- const parent = exec.agent;
3172
- if (parent === void 0 || !MANAGED_DELEGATION_TOOLS.has(exec.name)) return next();
3173
- const { subagentMaxDepth, subagentMaxAgents } = preferences.status();
3174
- const attemptedDepth = delegationDepthOf(parent) + 1;
3175
- if (attemptedDepth > subagentMaxDepth) return {
3176
- kind: "deny",
3177
- reason: `Subagent nesting depth limit reached: attempted depth ${attemptedDepth}, configured maximum ${subagentMaxDepth}.`
3178
- };
3179
- const activeAgents = activeDescendantCount(ctx, subagentTreeRoot(ctx, parent).id);
3180
- if (activeAgents >= subagentMaxAgents) return {
3181
- kind: "deny",
3182
- reason: `Subagent limit reached: ${activeAgents} active descendants in the Agent tree, configured maximum ${subagentMaxAgents}. Wait for one to finish or raise the global limit in Subagents settings.`
3183
- };
3184
- return next();
3185
- });
3186
- const disposePublicationGate = ctx.on("agent/created", ({ agent }) => {
3187
- if (agent.session.header.origin !== "subagent") return;
3188
- const { subagentMaxDepth, subagentMaxAgents } = preferences.status();
3189
- const depth = delegationDepthOf(agent);
3190
- if (depth > subagentMaxDepth) throw new Error(`Subagent nesting depth limit reached: attempted depth ${depth}, configured maximum ${subagentMaxDepth}.`);
3191
- const activeAgents = activeDescendantCount(ctx, subagentTreeRoot(ctx, agent).id);
3192
- if (activeAgents > subagentMaxAgents) throw new Error(`Subagent limit reached: attempted ${activeAgents} active descendants in the Agent tree, configured maximum ${subagentMaxAgents}. Wait for one to finish or raise the global limit in Subagents settings.`);
3193
- });
3194
- return () => {
3195
- disposePublicationGate();
3196
- disposePreflight();
3197
- };
3198
- }
3199
- /** Return the highest currently live ancestor that owns this subagent tree. */
3200
- function subagentTreeRoot(ctx, agent) {
3201
- let current = agent;
3202
- const visited = /* @__PURE__ */ new Set([agent.id]);
3203
- while (current.session.header.origin === "subagent") {
3204
- const parentId = current.session.header.parentSession;
3205
- if (parentId === void 0 || visited.has(parentId)) break;
3206
- const parent = ctx.agents.get(parentId);
3207
- if (parent === void 0) break;
3208
- visited.add(parent.id);
3209
- current = parent;
3210
- }
3211
- return current;
3212
- }
3213
- /** Return currently live descendants in one caller's subagent tree. */
3214
- function activeDescendantIds(ctx, parentId) {
3215
- const childrenByParent = /* @__PURE__ */ new Map();
3216
- for (const agent of ctx.agents.list()) {
3217
- if (agent.session.header.origin !== "subagent") continue;
3218
- const directParent = agent.session.header.parentSession;
3219
- if (directParent === void 0) continue;
3220
- const children = childrenByParent.get(directParent) ?? [];
3221
- children.push(agent.id);
3222
- childrenByParent.set(directParent, children);
3223
- }
3224
- const descendants = /* @__PURE__ */ new Set();
3225
- const pending = [...childrenByParent.get(parentId) ?? []];
3226
- while (pending.length > 0) {
3227
- const id = pending.pop();
3228
- if (descendants.has(id)) continue;
3229
- descendants.add(id);
3230
- pending.push(...childrenByParent.get(id) ?? []);
3231
- }
3232
- return descendants;
3233
- }
3234
- function activeDescendantCount(ctx, parentId) {
3235
- return activeDescendantIds(ctx, parentId).size;
3236
- }
3237
- //#endregion
3238
3290
  //#region src/index.ts
3239
3291
  const inject = [
3240
3292
  "webServer",
@@ -3243,52 +3295,61 @@ const inject = [
3243
3295
  "tools",
3244
3296
  "web",
3245
3297
  "settings",
3246
- "loader",
3247
- "agents"
3298
+ "loader"
3248
3299
  ];
3249
3300
  function apply(ctx) {
3250
- const oauth = new OAuthService(createPlatformTokenStore(), { logger: ctx.logger });
3251
- const usage = new UsageService(oauth);
3301
+ const store = createPlatformTokenStore();
3252
3302
  const preferences = registerPreferenceStore(ctx.settings);
3253
- const adapter = new CodexChatGptAdapter(new ResponsesClient(oauth, ctx.attachments, {
3254
- localRawImages: { baseUrl: localWebServerBaseUrl(ctx.webServer.host, ctx.webServer.port) },
3255
- onGenerationFinished: () => usage.invalidate(),
3256
- outputVerbosity: () => preferences.status().outputVerbosity
3257
- }), preferences);
3258
3303
  ctx.effect(() => {
3304
+ const proxyManager = new ProxyManager({
3305
+ getPreferences: () => preferences.status(),
3306
+ logger: ctx.logger
3307
+ });
3308
+ const proxyFetch = proxyManager.createFetch();
3309
+ const oauth = new OAuthService(store, {
3310
+ fetchFn: proxyFetch,
3311
+ logger: ctx.logger
3312
+ });
3313
+ const usage = new UsageService(oauth, { fetchFn: proxyFetch });
3314
+ const adapter = new CodexChatGptAdapter(new ResponsesClient(oauth, ctx.attachments, {
3315
+ fetchFn: proxyFetch,
3316
+ localRawImages: { baseUrl: localWebServerBaseUrl(ctx.webServer.host, ctx.webServer.port) },
3317
+ onGenerationFinished: () => usage.invalidate(),
3318
+ outputVerbosity: () => preferences.status().outputVerbosity,
3319
+ fastMode: () => preferences.status().fastMode
3320
+ }), preferences);
3259
3321
  const searchSwitcher = new SearchProviderSwitcher(ctx.loader);
3260
3322
  const applySearchPreference = (searchProvider = preferences.status().searchProvider) => {
3261
3323
  searchSwitcher.select(searchProvider).catch((error) => {
3262
3324
  ctx.logger.warn(`[dsh-chatgpt-subscription] Search provider preference could not be applied: ${error instanceof Error ? error.message : String(error)}`);
3263
3325
  });
3264
3326
  };
3265
- const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences);
3327
+ const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences, proxyManager);
3266
3328
  const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID], adapter);
3267
- const subagentContextAdapter = new SubagentContextAdapter(ctx.llm, () => {
3268
- const selected = preferences.status();
3269
- return {
3270
- provider: selected.subagentProvider,
3271
- model: selected.subagentModel,
3272
- contextWindow: selected.subagentContextWindow
3273
- };
3274
- });
3275
- const disposeSubagentContextAdapter = ctx.llm.registerAdapter([SubagentContextAdapter.provider], subagentContextAdapter);
3276
- const disposeSubagentModelPreference = installSubagentModelPreference(ctx, preferences);
3277
- const disposeSubagentPolicy = installSubagentPolicy(ctx, preferences);
3278
- const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments));
3279
- const disposeSearchProvider = ctx.web.registerSearchProvider(createCodexSearchProvider(oauth));
3329
+ const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
3330
+ let disposeWebProviders = () => {};
3331
+ const registerWebProviders = () => {
3332
+ disposeWebProviders();
3333
+ if (ctx.web) {
3334
+ const d1 = ctx.web.registerSearchProvider(createCodexSearchProvider(oauth, { fetchFn: proxyFetch }));
3335
+ const d2 = ctx.web.registerFetchProvider(createCodexFetchProvider({ fetchFn: proxyFetch }));
3336
+ disposeWebProviders = () => {
3337
+ d1();
3338
+ d2();
3339
+ };
3340
+ }
3341
+ };
3342
+ registerWebProviders();
3280
3343
  const disposePreferenceWatch = preferences.watch((next) => applySearchPreference(next.searchProvider));
3281
3344
  applySearchPreference();
3282
3345
  return () => {
3283
3346
  disposePreferenceWatch();
3284
- disposeSubagentPolicy();
3285
- disposeSubagentModelPreference();
3286
- disposeSearchProvider();
3347
+ disposeWebProviders();
3287
3348
  disposeImageTool();
3288
- disposeSubagentContextAdapter();
3289
3349
  disposeAdapter();
3290
3350
  disposeRoutes();
3291
3351
  oauth.dispose();
3352
+ proxyManager.dispose();
3292
3353
  };
3293
3354
  }, "dsh-chatgpt-subscription: adapter, routes, and lifecycle");
3294
3355
  }
@@ -3296,4 +3357,4 @@ function localWebServerBaseUrl(host, port) {
3296
3357
  return `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
3297
3358
  }
3298
3359
  //#endregion
3299
- export { CodexChatGptAdapter, LinuxFileTokenStore, MacKeychainTokenStore, OAuthService, ResponsesClient, UsageService, WindowsDpapiTokenStore, apply, createCodexImageTool, createCodexSearchProvider, createPlatformTokenStore, inject, mapCodexUsage, parseCodexUsage, parseResponsesStream };
3360
+ export { CodexChatGptAdapter, LinuxFileTokenStore, MacKeychainTokenStore, OAuthService, ProxyManager, ResponsesClient, UsageService, WindowsDpapiTokenStore, apply, createCodexFetchProvider, createCodexImageTool, createCodexSearchProvider, createPlatformTokenStore, detectSystemProxy, inject, mapCodexUsage, parseCodexUsage, parseResponsesStream };