@iam-brain/opencode-codex-auth 0.1.15 → 0.2.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/dist/lib/accounts-tools.d.ts.map +1 -1
- package/dist/lib/accounts-tools.js +8 -3
- package/dist/lib/accounts-tools.js.map +1 -1
- package/dist/lib/codex-native/browser.d.ts +12 -0
- package/dist/lib/codex-native/browser.d.ts.map +1 -0
- package/dist/lib/codex-native/browser.js +38 -0
- package/dist/lib/codex-native/browser.js.map +1 -0
- package/dist/lib/codex-native/catalog-auth.d.ts +6 -0
- package/dist/lib/codex-native/catalog-auth.d.ts.map +1 -0
- package/dist/lib/codex-native/catalog-auth.js +32 -0
- package/dist/lib/codex-native/catalog-auth.js.map +1 -0
- package/dist/lib/codex-native/client-identity.d.ts +13 -0
- package/dist/lib/codex-native/client-identity.d.ts.map +1 -0
- package/dist/lib/codex-native/client-identity.js +342 -0
- package/dist/lib/codex-native/client-identity.js.map +1 -0
- package/dist/lib/codex-native/collaboration.d.ts +19 -0
- package/dist/lib/codex-native/collaboration.d.ts.map +1 -0
- package/dist/lib/codex-native/collaboration.js +126 -0
- package/dist/lib/codex-native/collaboration.js.map +1 -0
- package/dist/lib/codex-native/oauth-server.d.ts +28 -0
- package/dist/lib/codex-native/oauth-server.d.ts.map +1 -0
- package/dist/lib/codex-native/oauth-server.js +267 -0
- package/dist/lib/codex-native/oauth-server.js.map +1 -0
- package/dist/lib/codex-native/originator.d.ts +4 -0
- package/dist/lib/codex-native/originator.d.ts.map +1 -0
- package/dist/lib/codex-native/originator.js +12 -0
- package/dist/lib/codex-native/originator.js.map +1 -0
- package/dist/lib/codex-native/request-transform.d.ts +50 -0
- package/dist/lib/codex-native/request-transform.d.ts.map +1 -0
- package/dist/lib/codex-native/request-transform.js +353 -0
- package/dist/lib/codex-native/request-transform.js.map +1 -0
- package/dist/lib/codex-native.d.ts +5 -13
- package/dist/lib/codex-native.d.ts.map +1 -1
- package/dist/lib/codex-native.js +193 -1048
- package/dist/lib/codex-native.js.map +1 -1
- package/dist/lib/codex-quota-fetch.d.ts +2 -0
- package/dist/lib/codex-quota-fetch.d.ts.map +1 -1
- package/dist/lib/codex-quota-fetch.js +17 -12
- package/dist/lib/codex-quota-fetch.js.map +1 -1
- package/dist/lib/codex-status-tool.js.map +1 -1
- package/dist/lib/codex-status-ui.d.ts.map +1 -1
- package/dist/lib/codex-status-ui.js +4 -20
- package/dist/lib/codex-status-ui.js.map +1 -1
- package/dist/lib/compat-sanitizer.d.ts.map +1 -1
- package/dist/lib/compat-sanitizer.js +2 -1
- package/dist/lib/compat-sanitizer.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +28 -70
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/fetch-orchestrator.d.ts +1 -1
- package/dist/lib/fetch-orchestrator.d.ts.map +1 -1
- package/dist/lib/fetch-orchestrator.js +7 -11
- package/dist/lib/fetch-orchestrator.js.map +1 -1
- package/dist/lib/identity.d.ts.map +1 -1
- package/dist/lib/identity.js.map +1 -1
- package/dist/lib/installer-cli.d.ts.map +1 -1
- package/dist/lib/installer-cli.js +1 -3
- package/dist/lib/installer-cli.js.map +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +13 -5
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/model-catalog.d.ts +2 -0
- package/dist/lib/model-catalog.d.ts.map +1 -1
- package/dist/lib/model-catalog.js +117 -41
- package/dist/lib/model-catalog.js.map +1 -1
- package/dist/lib/orchestrator-agents.d.ts.map +1 -1
- package/dist/lib/orchestrator-agents.js.map +1 -1
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/persona-tool-cli.d.ts.map +1 -1
- package/dist/lib/persona-tool-cli.js.map +1 -1
- package/dist/lib/persona-tool.d.ts.map +1 -1
- package/dist/lib/persona-tool.js +10 -31
- package/dist/lib/persona-tool.js.map +1 -1
- package/dist/lib/personalities.d.ts.map +1 -1
- package/dist/lib/personalities.js.map +1 -1
- package/dist/lib/proactive-refresh.d.ts.map +1 -1
- package/dist/lib/proactive-refresh.js +3 -6
- package/dist/lib/proactive-refresh.js.map +1 -1
- package/dist/lib/refresh-queue.d.ts.map +1 -1
- package/dist/lib/refresh-queue.js +4 -2
- package/dist/lib/refresh-queue.js.map +1 -1
- package/dist/lib/request-snapshots.d.ts.map +1 -1
- package/dist/lib/request-snapshots.js.map +1 -1
- package/dist/lib/rotation.d.ts.map +1 -1
- package/dist/lib/rotation.js +2 -5
- package/dist/lib/rotation.js.map +1 -1
- package/dist/lib/session-affinity.d.ts.map +1 -1
- package/dist/lib/session-affinity.js.map +1 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +2 -6
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/ui/auth-menu.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu.js +1 -3
- package/dist/lib/ui/auth-menu.js.map +1 -1
- package/dist/lib/ui/tty/ansi.d.ts.map +1 -1
- package/dist/lib/ui/tty/ansi.js.map +1 -1
- package/dist/lib/ui/tty/confirm.d.ts.map +1 -1
- package/dist/lib/ui/tty/confirm.js.map +1 -1
- package/dist/lib/ui/tty/select.d.ts.map +1 -1
- package/dist/lib/ui/tty/select.js +1 -3
- package/dist/lib/ui/tty/select.js.map +1 -1
- package/package.json +9 -2
package/dist/lib/codex-native.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import { execFile, execFileSync } from "node:child_process";
|
|
4
|
-
import { appendFileSync, chmodSync, mkdirSync, readFileSync } from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
1
|
import { extractAccountIdFromClaims as extractAccountIdFromClaimsBase, extractEmailFromClaims, extractPlanFromClaims, parseJwtClaims } from "./claims";
|
|
8
2
|
import { CodexStatus } from "./codex-status";
|
|
9
|
-
import { saveSnapshots } from "./codex-status-storage";
|
|
3
|
+
import { loadSnapshots, saveSnapshots } from "./codex-status-storage";
|
|
10
4
|
import { PluginFatalError, formatWaitTime, isPluginFatalError, toSyntheticErrorResponse } from "./fatal-errors";
|
|
11
5
|
import { buildIdentityKey, ensureIdentityKey, normalizeEmail, normalizePlan, synchronizeIdentityKey } from "./identity";
|
|
12
6
|
import { defaultSessionAffinityPath, defaultSnapshotsPath } from "./paths";
|
|
@@ -18,11 +12,18 @@ import { formatToastMessage } from "./toast";
|
|
|
18
12
|
import { runAuthMenuOnce } from "./ui/auth-menu-runner";
|
|
19
13
|
import { applyCodexCatalogToProviderModels, getCodexModelCatalog, getRuntimeDefaultsForModel, resolveInstructionsForModel } from "./model-catalog";
|
|
20
14
|
import { CODEX_RS_COMPACT_PROMPT } from "./orchestrator-agents";
|
|
21
|
-
import { sanitizeRequestPayloadForCompat } from "./compat-sanitizer";
|
|
22
15
|
import { fetchQuotaSnapshotFromBackend } from "./codex-quota-fetch";
|
|
23
16
|
import { createRequestSnapshots } from "./request-snapshots";
|
|
24
17
|
import { CODEX_OAUTH_SUCCESS_HTML } from "./oauth-pages";
|
|
18
|
+
import { mergeInstructions, resolveCollaborationInstructions, resolveCollaborationModeKind, resolveCollaborationProfile, resolveHookAgentName, resolveSubagentHeaderValue } from "./codex-native/collaboration";
|
|
19
|
+
import { applyCatalogInstructionOverrideToRequest, applyCodexRuntimeDefaultsToParams, findCatalogModelForCandidates, getModelLookupCandidates, getModelThinkingSummariesOverride, getVariantLookupCandidates, resolvePersonalityForModel, sanitizeOutboundRequestIfNeeded } from "./codex-native/request-transform";
|
|
25
20
|
import { createSessionExistsFn, loadSessionAffinity, pruneSessionAffinitySnapshot, readSessionAffinitySnapshot, saveSessionAffinity, writeSessionAffinitySnapshot } from "./session-affinity";
|
|
21
|
+
import { resolveCodexOriginator } from "./codex-native/originator";
|
|
22
|
+
import { tryOpenUrlInBrowser as openUrlInBrowser } from "./codex-native/browser";
|
|
23
|
+
import { selectCatalogAuthCandidate } from "./codex-native/catalog-auth";
|
|
24
|
+
import { buildCodexUserAgent, refreshCodexClientVersionFromGitHub, resolveCodexClientVersion, resolveRequestUserAgent } from "./codex-native/client-identity";
|
|
25
|
+
import { createOAuthServerController } from "./codex-native/oauth-server";
|
|
26
|
+
export { browserOpenInvocationFor } from "./codex-native/browser";
|
|
26
27
|
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
27
28
|
const ISSUER = "https://auth.openai.com";
|
|
28
29
|
const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
|
|
@@ -55,10 +56,10 @@ const OAUTH_SERVER_SHUTDOWN_ERROR_GRACE_MS = (() => {
|
|
|
55
56
|
const parsed = Number(raw);
|
|
56
57
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 60_000;
|
|
57
58
|
})();
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
59
|
+
const OPENAI_OUTBOUND_HOST_ALLOWLIST = new Set(["api.openai.com", "auth.openai.com", "chat.openai.com", "chatgpt.com"]);
|
|
60
|
+
const AUTH_MENU_QUOTA_SNAPSHOT_TTL_MS = 60_000;
|
|
61
|
+
const AUTH_MENU_QUOTA_FAILURE_COOLDOWN_MS = 30_000;
|
|
62
|
+
const AUTH_MENU_QUOTA_FETCH_TIMEOUT_MS = 5000;
|
|
62
63
|
const INTERNAL_COLLABORATION_MODE_HEADER = "x-opencode-collaboration-mode-kind";
|
|
63
64
|
const SESSION_AFFINITY_MISSING_GRACE_MS = 15 * 60 * 1000;
|
|
64
65
|
const CODEX_PLAN_MODE_INSTRUCTIONS = [
|
|
@@ -93,38 +94,12 @@ const STATIC_FALLBACK_MODELS = [
|
|
|
93
94
|
function sleep(ms) {
|
|
94
95
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
96
|
}
|
|
96
|
-
export function browserOpenInvocationFor(url, platform = process.platform) {
|
|
97
|
-
if (platform === "darwin") {
|
|
98
|
-
return { command: "open", args: [url] };
|
|
99
|
-
}
|
|
100
|
-
if (platform === "win32") {
|
|
101
|
-
return { command: "cmd", args: ["/c", "start", "", url] };
|
|
102
|
-
}
|
|
103
|
-
return { command: "xdg-open", args: [url] };
|
|
104
|
-
}
|
|
105
97
|
export async function tryOpenUrlInBrowser(url, log) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
emitOAuthDebug("browser_open_attempt", { command: invocation.command });
|
|
112
|
-
try {
|
|
113
|
-
await execFileAsync(invocation.command, invocation.args, { windowsHide: true, timeout: 5000 });
|
|
114
|
-
emitOAuthDebug("browser_open_success", { command: invocation.command });
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
emitOAuthDebug("browser_open_failure", {
|
|
119
|
-
command: invocation.command,
|
|
120
|
-
error: error instanceof Error ? error.message : String(error)
|
|
121
|
-
});
|
|
122
|
-
log?.warn("failed to auto-open oauth URL", {
|
|
123
|
-
command: invocation.command,
|
|
124
|
-
error: error instanceof Error ? error.message : String(error)
|
|
125
|
-
});
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
98
|
+
return openUrlInBrowser({
|
|
99
|
+
url,
|
|
100
|
+
log,
|
|
101
|
+
onEvent: (event, meta) => oauthServerController.emitDebug(event, meta ?? {})
|
|
102
|
+
});
|
|
128
103
|
}
|
|
129
104
|
function escapeHtml(value) {
|
|
130
105
|
return value
|
|
@@ -141,11 +116,7 @@ async function generatePKCE() {
|
|
|
141
116
|
return { verifier, challenge };
|
|
142
117
|
}
|
|
143
118
|
function base64UrlEncode(bytes) {
|
|
144
|
-
return Buffer.from(bytes)
|
|
145
|
-
.toString("base64")
|
|
146
|
-
.replace(/\+/g, "-")
|
|
147
|
-
.replace(/\//g, "_")
|
|
148
|
-
.replace(/=+$/g, "");
|
|
119
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
149
120
|
}
|
|
150
121
|
function generateState() {
|
|
151
122
|
return base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)));
|
|
@@ -195,6 +166,8 @@ export const __testOnly = {
|
|
|
195
166
|
modeForRuntimeMode,
|
|
196
167
|
buildCodexUserAgent,
|
|
197
168
|
resolveRequestUserAgent,
|
|
169
|
+
resolveCodexClientVersion,
|
|
170
|
+
refreshCodexClientVersionFromGitHub,
|
|
198
171
|
resolveHookAgentName,
|
|
199
172
|
resolveCollaborationModeKind,
|
|
200
173
|
resolveSubagentHeaderValue,
|
|
@@ -278,8 +251,7 @@ function composeCodexSuccessRedirectUrl(tokens, options = {}) {
|
|
|
278
251
|
const port = options.port ?? OAUTH_PORT;
|
|
279
252
|
const idClaims = getOpenAIAuthClaims(tokens.id_token);
|
|
280
253
|
const accessClaims = getOpenAIAuthClaims(tokens.access_token);
|
|
281
|
-
const needsSetup = !getClaimBoolean(idClaims, "completed_platform_onboarding") &&
|
|
282
|
-
getClaimBoolean(idClaims, "is_org_owner");
|
|
254
|
+
const needsSetup = !getClaimBoolean(idClaims, "completed_platform_onboarding") && getClaimBoolean(idClaims, "is_org_owner");
|
|
283
255
|
const platformUrl = issuer === ISSUER ? "https://platform.openai.com" : "https://platform.api.openai.org";
|
|
284
256
|
const params = new URLSearchParams({
|
|
285
257
|
needs_setup: String(needsSetup),
|
|
@@ -375,254 +347,32 @@ function buildOAuthErrorHtml(error) {
|
|
|
375
347
|
</body>
|
|
376
348
|
</html>`;
|
|
377
349
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
350
|
+
const oauthServerController = createOAuthServerController({
|
|
351
|
+
port: OAUTH_PORT,
|
|
352
|
+
loopbackHost: OAUTH_LOOPBACK_HOST,
|
|
353
|
+
callbackOrigin: OAUTH_CALLBACK_ORIGIN,
|
|
354
|
+
callbackUri: OAUTH_CALLBACK_URI,
|
|
355
|
+
callbackPath: OAUTH_CALLBACK_PATH,
|
|
356
|
+
callbackTimeoutMs: OAUTH_CALLBACK_TIMEOUT_MS,
|
|
357
|
+
buildOAuthErrorHtml,
|
|
358
|
+
buildOAuthSuccessHtml,
|
|
359
|
+
composeCodexSuccessRedirectUrl,
|
|
360
|
+
exchangeCodeForTokens
|
|
361
|
+
});
|
|
381
362
|
function isOAuthDebugEnabled() {
|
|
382
|
-
|
|
383
|
-
return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
|
|
384
|
-
}
|
|
385
|
-
function emitOAuthDebug(event, meta = {}) {
|
|
386
|
-
if (!isOAuthDebugEnabled())
|
|
387
|
-
return;
|
|
388
|
-
const payload = {
|
|
389
|
-
ts: new Date().toISOString(),
|
|
390
|
-
pid: process.pid,
|
|
391
|
-
event,
|
|
392
|
-
...meta
|
|
393
|
-
};
|
|
394
|
-
const line = JSON.stringify(payload);
|
|
395
|
-
try {
|
|
396
|
-
console.error(`[codex-auth-debug] ${line}`);
|
|
397
|
-
}
|
|
398
|
-
catch {
|
|
399
|
-
// best effort stderr logging
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
mkdirSync(OAUTH_DEBUG_LOG_DIR, { recursive: true, mode: 0o700 });
|
|
403
|
-
appendFileSync(OAUTH_DEBUG_LOG_FILE, `${line}\n`, { encoding: "utf8", mode: 0o600 });
|
|
404
|
-
chmodSync(OAUTH_DEBUG_LOG_FILE, 0o600);
|
|
405
|
-
}
|
|
406
|
-
catch {
|
|
407
|
-
// best effort file logging
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
function clearOAuthServerCloseTimer() {
|
|
411
|
-
if (!oauthServerCloseTimer)
|
|
412
|
-
return;
|
|
413
|
-
clearTimeout(oauthServerCloseTimer);
|
|
414
|
-
oauthServerCloseTimer = undefined;
|
|
415
|
-
}
|
|
416
|
-
function isLoopbackRemoteAddress(remoteAddress) {
|
|
417
|
-
if (!remoteAddress)
|
|
418
|
-
return false;
|
|
419
|
-
const normalized = remoteAddress.split("%")[0]?.toLowerCase();
|
|
420
|
-
return normalized === "127.0.0.1" || normalized === "::1" || normalized === "::ffff:127.0.0.1";
|
|
421
|
-
}
|
|
422
|
-
function setOAuthResponseHeaders(res, options) {
|
|
423
|
-
res.setHeader("Cache-Control", "no-store");
|
|
424
|
-
res.setHeader("Pragma", "no-cache");
|
|
425
|
-
res.setHeader("Referrer-Policy", "no-referrer");
|
|
426
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
427
|
-
if (options?.contentType) {
|
|
428
|
-
res.setHeader("Content-Type", options.contentType);
|
|
429
|
-
}
|
|
430
|
-
if (options?.isHtml) {
|
|
431
|
-
res.setHeader("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'");
|
|
432
|
-
}
|
|
363
|
+
return oauthServerController.isDebugEnabled();
|
|
433
364
|
}
|
|
434
365
|
async function startOAuthServer() {
|
|
435
|
-
|
|
436
|
-
if (oauthServer) {
|
|
437
|
-
emitOAuthDebug("server_reuse", { port: OAUTH_PORT });
|
|
438
|
-
return { redirectUri: OAUTH_CALLBACK_URI };
|
|
439
|
-
}
|
|
440
|
-
emitOAuthDebug("server_starting", { port: OAUTH_PORT });
|
|
441
|
-
oauthServer = http.createServer((req, res) => {
|
|
442
|
-
try {
|
|
443
|
-
if (!isLoopbackRemoteAddress(req.socket.remoteAddress)) {
|
|
444
|
-
emitOAuthDebug("callback_rejected_non_loopback", {
|
|
445
|
-
remoteAddress: req.socket.remoteAddress
|
|
446
|
-
});
|
|
447
|
-
res.statusCode = 403;
|
|
448
|
-
setOAuthResponseHeaders(res, { contentType: "text/plain; charset=utf-8" });
|
|
449
|
-
res.end("Forbidden");
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
const url = new URL(req.url ?? "/", OAUTH_CALLBACK_ORIGIN);
|
|
453
|
-
const sendHtml = (status, html) => {
|
|
454
|
-
res.statusCode = status;
|
|
455
|
-
setOAuthResponseHeaders(res, { contentType: "text/html; charset=utf-8", isHtml: true });
|
|
456
|
-
res.end(html);
|
|
457
|
-
};
|
|
458
|
-
const redirect = (location) => {
|
|
459
|
-
res.statusCode = 302;
|
|
460
|
-
setOAuthResponseHeaders(res, { contentType: "text/plain; charset=utf-8" });
|
|
461
|
-
res.setHeader("Location", location);
|
|
462
|
-
res.end();
|
|
463
|
-
};
|
|
464
|
-
if (url.pathname === "/auth/callback") {
|
|
465
|
-
const code = url.searchParams.get("code");
|
|
466
|
-
const state = url.searchParams.get("state");
|
|
467
|
-
const error = url.searchParams.get("error");
|
|
468
|
-
const errorDescription = url.searchParams.get("error_description");
|
|
469
|
-
emitOAuthDebug("callback_hit", {
|
|
470
|
-
hasCode: Boolean(code),
|
|
471
|
-
hasState: Boolean(state),
|
|
472
|
-
hasError: Boolean(error)
|
|
473
|
-
});
|
|
474
|
-
if (error) {
|
|
475
|
-
const errorMsg = errorDescription || error;
|
|
476
|
-
emitOAuthDebug("callback_error", { reason: errorMsg });
|
|
477
|
-
pendingOAuth?.reject(new Error(errorMsg));
|
|
478
|
-
pendingOAuth = undefined;
|
|
479
|
-
sendHtml(200, buildOAuthErrorHtml(errorMsg));
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
if (!code) {
|
|
483
|
-
const errorMsg = "Missing authorization code";
|
|
484
|
-
emitOAuthDebug("callback_error", { reason: errorMsg });
|
|
485
|
-
pendingOAuth?.reject(new Error(errorMsg));
|
|
486
|
-
pendingOAuth = undefined;
|
|
487
|
-
sendHtml(400, buildOAuthErrorHtml(errorMsg));
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
if (!pendingOAuth || state !== pendingOAuth.state) {
|
|
491
|
-
const errorMsg = "Invalid state - potential CSRF attack";
|
|
492
|
-
emitOAuthDebug("callback_error", { reason: errorMsg });
|
|
493
|
-
pendingOAuth?.reject(new Error(errorMsg));
|
|
494
|
-
pendingOAuth = undefined;
|
|
495
|
-
sendHtml(400, buildOAuthErrorHtml(errorMsg));
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const current = pendingOAuth;
|
|
499
|
-
pendingOAuth = undefined;
|
|
500
|
-
emitOAuthDebug("token_exchange_start", { authMode: current.authMode });
|
|
501
|
-
exchangeCodeForTokens(code, OAUTH_CALLBACK_URI, current.pkce)
|
|
502
|
-
.then((tokens) => {
|
|
503
|
-
current.resolve(tokens);
|
|
504
|
-
emitOAuthDebug("token_exchange_success", { authMode: current.authMode });
|
|
505
|
-
if (res.writableEnded)
|
|
506
|
-
return;
|
|
507
|
-
if (current.authMode === "codex") {
|
|
508
|
-
redirect(composeCodexSuccessRedirectUrl(tokens));
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
sendHtml(200, buildOAuthSuccessHtml("native"));
|
|
512
|
-
})
|
|
513
|
-
.catch((err) => {
|
|
514
|
-
const oauthError = err instanceof Error ? err : new Error(String(err));
|
|
515
|
-
current.reject(oauthError);
|
|
516
|
-
emitOAuthDebug("token_exchange_error", { error: oauthError.message });
|
|
517
|
-
if (res.writableEnded)
|
|
518
|
-
return;
|
|
519
|
-
sendHtml(500, buildOAuthErrorHtml(oauthError.message));
|
|
520
|
-
});
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
if (url.pathname === "/success") {
|
|
524
|
-
emitOAuthDebug("callback_success_page");
|
|
525
|
-
sendHtml(200, buildOAuthSuccessHtml("codex"));
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
if (url.pathname === "/cancel") {
|
|
529
|
-
emitOAuthDebug("callback_cancel");
|
|
530
|
-
pendingOAuth?.reject(new Error("Login cancelled"));
|
|
531
|
-
pendingOAuth = undefined;
|
|
532
|
-
res.statusCode = 200;
|
|
533
|
-
setOAuthResponseHeaders(res, { contentType: "text/plain; charset=utf-8" });
|
|
534
|
-
res.end("Login cancelled");
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
res.statusCode = 404;
|
|
538
|
-
setOAuthResponseHeaders(res, { contentType: "text/plain; charset=utf-8" });
|
|
539
|
-
res.end("Not found");
|
|
540
|
-
}
|
|
541
|
-
catch (error) {
|
|
542
|
-
res.statusCode = 500;
|
|
543
|
-
setOAuthResponseHeaders(res, { contentType: "text/plain; charset=utf-8" });
|
|
544
|
-
res.end(`Server error: ${error.message}`);
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
try {
|
|
548
|
-
await new Promise((resolve, reject) => {
|
|
549
|
-
oauthServer?.once("error", reject);
|
|
550
|
-
oauthServer?.listen(OAUTH_PORT, OAUTH_LOOPBACK_HOST, () => resolve());
|
|
551
|
-
});
|
|
552
|
-
emitOAuthDebug("server_started", { port: OAUTH_PORT });
|
|
553
|
-
}
|
|
554
|
-
catch (error) {
|
|
555
|
-
emitOAuthDebug("server_start_error", {
|
|
556
|
-
error: error instanceof Error ? error.message : String(error)
|
|
557
|
-
});
|
|
558
|
-
const server = oauthServer;
|
|
559
|
-
oauthServer = undefined;
|
|
560
|
-
try {
|
|
561
|
-
server?.close();
|
|
562
|
-
}
|
|
563
|
-
catch {
|
|
564
|
-
// best-effort cleanup
|
|
565
|
-
}
|
|
566
|
-
throw error;
|
|
567
|
-
}
|
|
568
|
-
return { redirectUri: OAUTH_CALLBACK_URI };
|
|
366
|
+
return oauthServerController.start();
|
|
569
367
|
}
|
|
570
368
|
function stopOAuthServer() {
|
|
571
|
-
|
|
572
|
-
emitOAuthDebug("server_stopping", { hadPendingOAuth: Boolean(pendingOAuth) });
|
|
573
|
-
oauthServer?.close();
|
|
574
|
-
oauthServer = undefined;
|
|
575
|
-
emitOAuthDebug("server_stopped");
|
|
369
|
+
oauthServerController.stop();
|
|
576
370
|
}
|
|
577
371
|
function scheduleOAuthServerStop(delayMs = OAUTH_SERVER_SHUTDOWN_GRACE_MS, reason = "other") {
|
|
578
|
-
|
|
579
|
-
return;
|
|
580
|
-
clearOAuthServerCloseTimer();
|
|
581
|
-
emitOAuthDebug("server_stop_scheduled", { delayMs, reason });
|
|
582
|
-
oauthServerCloseTimer = setTimeout(() => {
|
|
583
|
-
oauthServerCloseTimer = undefined;
|
|
584
|
-
if (pendingOAuth)
|
|
585
|
-
return;
|
|
586
|
-
emitOAuthDebug("server_stop_timer_fired", { reason });
|
|
587
|
-
stopOAuthServer();
|
|
588
|
-
}, delayMs);
|
|
372
|
+
oauthServerController.scheduleStop(delayMs, reason);
|
|
589
373
|
}
|
|
590
374
|
function waitForOAuthCallback(pkce, state, authMode) {
|
|
591
|
-
|
|
592
|
-
authMode,
|
|
593
|
-
stateTail: state.slice(-6)
|
|
594
|
-
});
|
|
595
|
-
return new Promise((resolve, reject) => {
|
|
596
|
-
let settled = false;
|
|
597
|
-
const resolveOnce = (tokens) => {
|
|
598
|
-
if (settled)
|
|
599
|
-
return;
|
|
600
|
-
settled = true;
|
|
601
|
-
clearTimeout(timeout);
|
|
602
|
-
emitOAuthDebug("callback_wait_resolved", { authMode });
|
|
603
|
-
resolve(tokens);
|
|
604
|
-
};
|
|
605
|
-
const rejectOnce = (error) => {
|
|
606
|
-
if (settled)
|
|
607
|
-
return;
|
|
608
|
-
settled = true;
|
|
609
|
-
clearTimeout(timeout);
|
|
610
|
-
emitOAuthDebug("callback_wait_rejected", { authMode, error: error.message });
|
|
611
|
-
reject(error);
|
|
612
|
-
};
|
|
613
|
-
const timeout = setTimeout(() => {
|
|
614
|
-
pendingOAuth = undefined;
|
|
615
|
-
emitOAuthDebug("callback_wait_timeout", { authMode, timeoutMs: OAUTH_CALLBACK_TIMEOUT_MS });
|
|
616
|
-
rejectOnce(new Error("OAuth callback timeout - authorization took too long"));
|
|
617
|
-
}, OAUTH_CALLBACK_TIMEOUT_MS);
|
|
618
|
-
pendingOAuth = {
|
|
619
|
-
pkce,
|
|
620
|
-
state,
|
|
621
|
-
authMode,
|
|
622
|
-
resolve: resolveOnce,
|
|
623
|
-
reject: rejectOnce
|
|
624
|
-
};
|
|
625
|
-
});
|
|
375
|
+
return oauthServerController.waitForCallback(pkce, state, authMode);
|
|
626
376
|
}
|
|
627
377
|
function modeForRuntimeMode(runtimeMode) {
|
|
628
378
|
return runtimeMode === "native" ? "native" : "codex";
|
|
@@ -711,319 +461,38 @@ function rewriteUrl(requestInput) {
|
|
|
711
461
|
const parsed = requestInput instanceof URL
|
|
712
462
|
? requestInput
|
|
713
463
|
: new URL(typeof requestInput === "string" ? requestInput : requestInput.url);
|
|
714
|
-
if (parsed.pathname.includes("/v1/responses") ||
|
|
715
|
-
parsed.pathname.includes("/chat/completions")) {
|
|
464
|
+
if (parsed.pathname.includes("/v1/responses") || parsed.pathname.includes("/chat/completions")) {
|
|
716
465
|
return new URL(CODEX_API_ENDPOINT);
|
|
717
466
|
}
|
|
718
467
|
return parsed;
|
|
719
468
|
}
|
|
720
|
-
function
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
}
|
|
724
|
-
let cachedPluginVersion;
|
|
725
|
-
let cachedMacProductVersion;
|
|
726
|
-
let cachedTerminalUserAgentToken;
|
|
727
|
-
function isPrintableAscii(value) {
|
|
728
|
-
if (!value)
|
|
729
|
-
return false;
|
|
730
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
731
|
-
const code = value.charCodeAt(index);
|
|
732
|
-
if (code < 0x20 || code > 0x7e)
|
|
733
|
-
return false;
|
|
734
|
-
}
|
|
735
|
-
return true;
|
|
736
|
-
}
|
|
737
|
-
function sanitizeUserAgentCandidate(candidate, fallback, originator) {
|
|
738
|
-
if (isPrintableAscii(candidate))
|
|
739
|
-
return candidate;
|
|
740
|
-
const sanitized = Array.from(candidate)
|
|
741
|
-
.map((char) => {
|
|
742
|
-
const code = char.charCodeAt(0);
|
|
743
|
-
return code >= 0x20 && code <= 0x7e ? char : "_";
|
|
744
|
-
})
|
|
745
|
-
.join("");
|
|
746
|
-
if (isPrintableAscii(sanitized))
|
|
747
|
-
return sanitized;
|
|
748
|
-
if (isPrintableAscii(fallback))
|
|
749
|
-
return fallback;
|
|
750
|
-
return originator;
|
|
751
|
-
}
|
|
752
|
-
function sanitizeTerminalToken(value) {
|
|
753
|
-
return value.replace(/[^A-Za-z0-9._/-]/g, "_");
|
|
754
|
-
}
|
|
755
|
-
function nonEmptyEnv(env, key) {
|
|
756
|
-
const value = env[key]?.trim();
|
|
757
|
-
return value ? value : undefined;
|
|
758
|
-
}
|
|
759
|
-
function splitProgramAndVersion(value) {
|
|
760
|
-
const [program, version] = value.trim().split(/\s+/, 2);
|
|
761
|
-
return {
|
|
762
|
-
program: program ?? "unknown",
|
|
763
|
-
...(version ? { version } : {})
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
function tmuxDisplayMessage(format) {
|
|
767
|
-
try {
|
|
768
|
-
const value = execFileSync("tmux", ["display-message", "-p", format], { encoding: "utf8" }).trim();
|
|
769
|
-
return value || undefined;
|
|
770
|
-
}
|
|
771
|
-
catch {
|
|
772
|
-
return undefined;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
function resolveTerminalUserAgentToken(env = process.env) {
|
|
776
|
-
if (cachedTerminalUserAgentToken)
|
|
777
|
-
return cachedTerminalUserAgentToken;
|
|
778
|
-
const termProgram = nonEmptyEnv(env, "TERM_PROGRAM");
|
|
779
|
-
const termProgramVersion = nonEmptyEnv(env, "TERM_PROGRAM_VERSION");
|
|
780
|
-
const term = nonEmptyEnv(env, "TERM");
|
|
781
|
-
const hasTmux = Boolean(nonEmptyEnv(env, "TMUX") || nonEmptyEnv(env, "TMUX_PANE"));
|
|
782
|
-
if (termProgram && termProgram.toLowerCase() === "tmux" && hasTmux) {
|
|
783
|
-
const tmuxTermType = tmuxDisplayMessage("#{client_termtype}");
|
|
784
|
-
if (tmuxTermType) {
|
|
785
|
-
const { program, version } = splitProgramAndVersion(tmuxTermType);
|
|
786
|
-
cachedTerminalUserAgentToken = sanitizeTerminalToken(version ? `${program}/${version}` : program);
|
|
787
|
-
return cachedTerminalUserAgentToken;
|
|
788
|
-
}
|
|
789
|
-
const tmuxTermName = tmuxDisplayMessage("#{client_termname}");
|
|
790
|
-
if (tmuxTermName) {
|
|
791
|
-
cachedTerminalUserAgentToken = sanitizeTerminalToken(tmuxTermName);
|
|
792
|
-
return cachedTerminalUserAgentToken;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
if (termProgram) {
|
|
796
|
-
cachedTerminalUserAgentToken = sanitizeTerminalToken(termProgramVersion ? `${termProgram}/${termProgramVersion}` : termProgram);
|
|
797
|
-
return cachedTerminalUserAgentToken;
|
|
798
|
-
}
|
|
799
|
-
const weztermVersion = nonEmptyEnv(env, "WEZTERM_VERSION");
|
|
800
|
-
if (weztermVersion) {
|
|
801
|
-
cachedTerminalUserAgentToken = sanitizeTerminalToken(`WezTerm/${weztermVersion}`);
|
|
802
|
-
return cachedTerminalUserAgentToken;
|
|
803
|
-
}
|
|
804
|
-
if (env.ITERM_SESSION_ID || env.ITERM_PROFILE || env.ITERM_PROFILE_NAME) {
|
|
805
|
-
cachedTerminalUserAgentToken = "iTerm.app";
|
|
806
|
-
return cachedTerminalUserAgentToken;
|
|
807
|
-
}
|
|
808
|
-
if (env.TERM_SESSION_ID) {
|
|
809
|
-
cachedTerminalUserAgentToken = "Apple_Terminal";
|
|
810
|
-
return cachedTerminalUserAgentToken;
|
|
811
|
-
}
|
|
812
|
-
if (env.KITTY_WINDOW_ID || term?.includes("kitty")) {
|
|
813
|
-
cachedTerminalUserAgentToken = "kitty";
|
|
814
|
-
return cachedTerminalUserAgentToken;
|
|
815
|
-
}
|
|
816
|
-
if (env.ALACRITTY_SOCKET || term === "alacritty") {
|
|
817
|
-
cachedTerminalUserAgentToken = "Alacritty";
|
|
818
|
-
return cachedTerminalUserAgentToken;
|
|
819
|
-
}
|
|
820
|
-
const konsoleVersion = nonEmptyEnv(env, "KONSOLE_VERSION");
|
|
821
|
-
if (konsoleVersion) {
|
|
822
|
-
cachedTerminalUserAgentToken = sanitizeTerminalToken(`Konsole/${konsoleVersion}`);
|
|
823
|
-
return cachedTerminalUserAgentToken;
|
|
824
|
-
}
|
|
825
|
-
if (env.GNOME_TERMINAL_SCREEN) {
|
|
826
|
-
cachedTerminalUserAgentToken = "gnome-terminal";
|
|
827
|
-
return cachedTerminalUserAgentToken;
|
|
828
|
-
}
|
|
829
|
-
const vteVersion = nonEmptyEnv(env, "VTE_VERSION");
|
|
830
|
-
if (vteVersion) {
|
|
831
|
-
cachedTerminalUserAgentToken = sanitizeTerminalToken(`VTE/${vteVersion}`);
|
|
832
|
-
return cachedTerminalUserAgentToken;
|
|
833
|
-
}
|
|
834
|
-
if (env.WT_SESSION) {
|
|
835
|
-
cachedTerminalUserAgentToken = "WindowsTerminal";
|
|
836
|
-
return cachedTerminalUserAgentToken;
|
|
837
|
-
}
|
|
838
|
-
if (term) {
|
|
839
|
-
cachedTerminalUserAgentToken = sanitizeTerminalToken(term);
|
|
840
|
-
return cachedTerminalUserAgentToken;
|
|
841
|
-
}
|
|
842
|
-
cachedTerminalUserAgentToken = "unknown";
|
|
843
|
-
return cachedTerminalUserAgentToken;
|
|
844
|
-
}
|
|
845
|
-
function resolvePluginVersion() {
|
|
846
|
-
if (cachedPluginVersion)
|
|
847
|
-
return cachedPluginVersion;
|
|
848
|
-
const fromEnv = process.env.npm_package_version?.trim();
|
|
849
|
-
if (fromEnv) {
|
|
850
|
-
cachedPluginVersion = fromEnv;
|
|
851
|
-
return cachedPluginVersion;
|
|
852
|
-
}
|
|
853
|
-
try {
|
|
854
|
-
const raw = readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
855
|
-
const parsed = JSON.parse(raw);
|
|
856
|
-
if (typeof parsed.version === "string" && parsed.version.trim()) {
|
|
857
|
-
cachedPluginVersion = parsed.version.trim();
|
|
858
|
-
return cachedPluginVersion;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
catch {
|
|
862
|
-
// Use fallback version below.
|
|
863
|
-
}
|
|
864
|
-
cachedPluginVersion = DEFAULT_PLUGIN_VERSION;
|
|
865
|
-
return cachedPluginVersion;
|
|
866
|
-
}
|
|
867
|
-
function resolveMacProductVersion() {
|
|
868
|
-
if (cachedMacProductVersion)
|
|
869
|
-
return cachedMacProductVersion;
|
|
870
|
-
try {
|
|
871
|
-
const value = execFileSync("sw_vers", ["-productVersion"], { encoding: "utf8" }).trim();
|
|
872
|
-
cachedMacProductVersion = value || os.release();
|
|
873
|
-
}
|
|
874
|
-
catch {
|
|
875
|
-
cachedMacProductVersion = os.release();
|
|
876
|
-
}
|
|
877
|
-
return cachedMacProductVersion;
|
|
878
|
-
}
|
|
879
|
-
function normalizeArchitecture(architecture) {
|
|
880
|
-
if (architecture === "x64")
|
|
881
|
-
return "x86_64";
|
|
882
|
-
if (architecture === "arm64")
|
|
883
|
-
return "arm64";
|
|
884
|
-
return architecture || "unknown";
|
|
885
|
-
}
|
|
886
|
-
function resolveCodexPlatformSignature(platform = process.platform) {
|
|
887
|
-
const architecture = normalizeArchitecture(os.arch());
|
|
888
|
-
if (platform === "darwin") {
|
|
889
|
-
return `Mac OS ${resolveMacProductVersion()}; ${architecture}`;
|
|
890
|
-
}
|
|
891
|
-
if (platform === "win32") {
|
|
892
|
-
return `Windows ${os.release()}; ${architecture}`;
|
|
893
|
-
}
|
|
894
|
-
if (platform === "linux") {
|
|
895
|
-
return `Linux ${os.release()}; ${architecture}`;
|
|
896
|
-
}
|
|
897
|
-
return `${platform} ${os.release()}; ${architecture}`;
|
|
898
|
-
}
|
|
899
|
-
function buildCodexUserAgent(originator) {
|
|
900
|
-
if (originator === "opencode")
|
|
901
|
-
return opencodeUserAgent();
|
|
902
|
-
const buildVersion = resolvePluginVersion();
|
|
903
|
-
const terminalToken = resolveTerminalUserAgentToken();
|
|
904
|
-
const prefix = `${originator}/${buildVersion} (${resolveCodexPlatformSignature()}) ${terminalToken}`;
|
|
905
|
-
return sanitizeUserAgentCandidate(prefix, prefix, originator);
|
|
906
|
-
}
|
|
907
|
-
function resolveRequestUserAgent(spoofMode, originator) {
|
|
908
|
-
if (spoofMode === "codex")
|
|
909
|
-
return buildCodexUserAgent(originator);
|
|
910
|
-
return opencodeUserAgent();
|
|
911
|
-
}
|
|
912
|
-
function resolveHookAgentName(agent) {
|
|
913
|
-
const direct = asString(agent);
|
|
914
|
-
if (direct)
|
|
915
|
-
return direct;
|
|
916
|
-
if (!isRecord(agent))
|
|
917
|
-
return undefined;
|
|
918
|
-
return asString(agent.name) ?? asString(agent.agent);
|
|
919
|
-
}
|
|
920
|
-
function normalizeAgentNameForCollaboration(agentName) {
|
|
921
|
-
return agentName.trim().toLowerCase().replace(/\s+/g, "-");
|
|
922
|
-
}
|
|
923
|
-
function tokenizeAgentName(normalizedAgentName) {
|
|
924
|
-
return normalizedAgentName
|
|
925
|
-
.split(/[-./:_]+/)
|
|
926
|
-
.map((token) => token.trim())
|
|
927
|
-
.filter((token) => token.length > 0);
|
|
928
|
-
}
|
|
929
|
-
function isPluginCollaborationAgent(normalizedAgentName) {
|
|
930
|
-
const tokens = tokenizeAgentName(normalizedAgentName);
|
|
931
|
-
if (tokens.length === 0)
|
|
932
|
-
return false;
|
|
933
|
-
if (tokens[0] !== "codex")
|
|
469
|
+
function isAllowedOpenAIOutboundHost(hostname) {
|
|
470
|
+
const normalized = hostname.trim().toLowerCase();
|
|
471
|
+
if (!normalized)
|
|
934
472
|
return false;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
function resolveCollaborationModeKindFromName(normalizedAgentName) {
|
|
950
|
-
const tokens = tokenizeAgentName(normalizedAgentName);
|
|
951
|
-
if (tokens.includes("plan") || tokens.includes("planner"))
|
|
952
|
-
return "plan";
|
|
953
|
-
if (tokens.includes("execute"))
|
|
954
|
-
return "execute";
|
|
955
|
-
if (tokens.includes("pair") || tokens.includes("pairprogramming"))
|
|
956
|
-
return "pair_programming";
|
|
957
|
-
return "code";
|
|
958
|
-
}
|
|
959
|
-
function resolveCollaborationProfile(agent) {
|
|
960
|
-
const name = resolveHookAgentName(agent);
|
|
961
|
-
if (!name)
|
|
962
|
-
return { enabled: false };
|
|
963
|
-
const normalizedAgentName = normalizeAgentNameForCollaboration(name);
|
|
964
|
-
if (!isPluginCollaborationAgent(normalizedAgentName)) {
|
|
965
|
-
return { enabled: false, normalizedAgentName };
|
|
966
|
-
}
|
|
967
|
-
return {
|
|
968
|
-
enabled: true,
|
|
969
|
-
normalizedAgentName,
|
|
970
|
-
kind: resolveCollaborationModeKindFromName(normalizedAgentName)
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
function resolveCollaborationModeKind(agent) {
|
|
974
|
-
const profile = resolveCollaborationProfile(agent);
|
|
975
|
-
return profile.kind ?? "code";
|
|
976
|
-
}
|
|
977
|
-
function resolveCollaborationInstructions(kind) {
|
|
978
|
-
if (kind === "plan")
|
|
979
|
-
return CODEX_PLAN_MODE_INSTRUCTIONS;
|
|
980
|
-
if (kind === "execute")
|
|
981
|
-
return CODEX_EXECUTE_MODE_INSTRUCTIONS;
|
|
982
|
-
if (kind === "pair_programming")
|
|
983
|
-
return CODEX_PAIR_PROGRAMMING_MODE_INSTRUCTIONS;
|
|
984
|
-
return CODEX_CODE_MODE_INSTRUCTIONS;
|
|
985
|
-
}
|
|
986
|
-
function mergeInstructions(base, extra) {
|
|
987
|
-
const normalizedExtra = extra.trim();
|
|
988
|
-
if (!normalizedExtra)
|
|
989
|
-
return base?.trim() ?? "";
|
|
990
|
-
const normalizedBase = base?.trim();
|
|
991
|
-
if (!normalizedBase)
|
|
992
|
-
return normalizedExtra;
|
|
993
|
-
if (normalizedBase.includes(normalizedExtra))
|
|
994
|
-
return normalizedBase;
|
|
995
|
-
return `${normalizedBase}\n\n${normalizedExtra}`;
|
|
996
|
-
}
|
|
997
|
-
function resolveSubagentHeaderValue(agent) {
|
|
998
|
-
const profile = resolveCollaborationProfile(agent);
|
|
999
|
-
const normalized = profile.normalizedAgentName;
|
|
1000
|
-
if (!profile.enabled || !normalized) {
|
|
1001
|
-
return undefined;
|
|
1002
|
-
}
|
|
1003
|
-
const tokens = tokenizeAgentName(normalized);
|
|
1004
|
-
const isCodexPrimary = tokens[0] === "codex" &&
|
|
1005
|
-
(tokens.includes("orchestrator") ||
|
|
1006
|
-
tokens.includes("default") ||
|
|
1007
|
-
tokens.includes("code") ||
|
|
1008
|
-
tokens.includes("plan") ||
|
|
1009
|
-
tokens.includes("planner") ||
|
|
1010
|
-
tokens.includes("execute") ||
|
|
1011
|
-
tokens.includes("pair") ||
|
|
1012
|
-
tokens.includes("pairprogramming"));
|
|
1013
|
-
if (isCodexPrimary) {
|
|
1014
|
-
return undefined;
|
|
1015
|
-
}
|
|
1016
|
-
if (tokens.includes("plan") || tokens.includes("planner")) {
|
|
1017
|
-
return undefined;
|
|
1018
|
-
}
|
|
1019
|
-
if (normalized === "compaction") {
|
|
1020
|
-
return "compact";
|
|
473
|
+
if (OPENAI_OUTBOUND_HOST_ALLOWLIST.has(normalized))
|
|
474
|
+
return true;
|
|
475
|
+
return normalized.endsWith(".openai.com") || normalized.endsWith(".chatgpt.com");
|
|
476
|
+
}
|
|
477
|
+
function assertAllowedOutboundUrl(url) {
|
|
478
|
+
const protocol = url.protocol.trim().toLowerCase();
|
|
479
|
+
if (protocol !== "https:") {
|
|
480
|
+
throw new PluginFatalError({
|
|
481
|
+
message: `Blocked outbound request with unsupported protocol "${protocol || "unknown"}". ` +
|
|
482
|
+
"This plugin only proxies HTTPS requests to OpenAI/ChatGPT backends.",
|
|
483
|
+
status: 400,
|
|
484
|
+
type: "disallowed_outbound_protocol",
|
|
485
|
+
param: "request"
|
|
486
|
+
});
|
|
1021
487
|
}
|
|
1022
|
-
if (
|
|
1023
|
-
return
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
488
|
+
if (isAllowedOpenAIOutboundHost(url.hostname))
|
|
489
|
+
return;
|
|
490
|
+
throw new PluginFatalError({
|
|
491
|
+
message: `Blocked outbound request to "${url.hostname}". ` + "This plugin only proxies OpenAI/ChatGPT backend traffic.",
|
|
492
|
+
status: 400,
|
|
493
|
+
type: "disallowed_outbound_host",
|
|
494
|
+
param: "request"
|
|
495
|
+
});
|
|
1027
496
|
}
|
|
1028
497
|
async function sessionUsesOpenAIProvider(client, sessionID) {
|
|
1029
498
|
const sessionApi = client?.session;
|
|
@@ -1040,9 +509,7 @@ async function sessionUsesOpenAIProvider(client, sessionID) {
|
|
|
1040
509
|
if (asString(info.role) !== "user")
|
|
1041
510
|
continue;
|
|
1042
511
|
const model = isRecord(info.model) ? info.model : undefined;
|
|
1043
|
-
const providerID = model
|
|
1044
|
-
? asString(model.providerID)
|
|
1045
|
-
: asString(info.providerID);
|
|
512
|
+
const providerID = model ? asString(model.providerID) : asString(info.providerID);
|
|
1046
513
|
if (!providerID)
|
|
1047
514
|
continue;
|
|
1048
515
|
return providerID === "openai";
|
|
@@ -1053,26 +520,11 @@ async function sessionUsesOpenAIProvider(client, sessionID) {
|
|
|
1053
520
|
}
|
|
1054
521
|
return false;
|
|
1055
522
|
}
|
|
1056
|
-
function isTuiWorkerInvocation(argv) {
|
|
1057
|
-
return argv.some((entry) => /(?:^|[\\/])tui[\\/]worker\.(?:js|ts)$/i.test(entry));
|
|
1058
|
-
}
|
|
1059
|
-
function resolveCodexOriginator(spoofMode, argv = process.argv) {
|
|
1060
|
-
if (spoofMode !== "codex")
|
|
1061
|
-
return "opencode";
|
|
1062
|
-
const normalizedArgv = argv.map((entry) => String(entry));
|
|
1063
|
-
if (isTuiWorkerInvocation(normalizedArgv))
|
|
1064
|
-
return "codex_cli_rs";
|
|
1065
|
-
return normalizedArgv.includes("run") ? "codex_exec" : "codex_cli_rs";
|
|
1066
|
-
}
|
|
1067
523
|
function formatAccountLabel(account, index) {
|
|
1068
524
|
const email = account?.email?.trim();
|
|
1069
525
|
const plan = account?.plan?.trim();
|
|
1070
526
|
const accountId = account?.accountId?.trim();
|
|
1071
|
-
const idSuffix = accountId
|
|
1072
|
-
? accountId.length > 6
|
|
1073
|
-
? accountId.slice(-6)
|
|
1074
|
-
: accountId
|
|
1075
|
-
: null;
|
|
527
|
+
const idSuffix = accountId ? (accountId.length > 6 ? accountId.slice(-6) : accountId) : null;
|
|
1076
528
|
if (email && plan)
|
|
1077
529
|
return `${email} (${plan})`;
|
|
1078
530
|
if (email)
|
|
@@ -1082,7 +534,7 @@ function formatAccountLabel(account, index) {
|
|
|
1082
534
|
return `Account ${index + 1}`;
|
|
1083
535
|
}
|
|
1084
536
|
function hasActiveCooldown(account, now) {
|
|
1085
|
-
return typeof account.cooldownUntil === "number" && Number.isFinite(account.cooldownUntil) && account.cooldownUntil > now;
|
|
537
|
+
return (typeof account.cooldownUntil === "number" && Number.isFinite(account.cooldownUntil) && account.cooldownUntil > now);
|
|
1086
538
|
}
|
|
1087
539
|
function ensureAccountAuthTypes(account) {
|
|
1088
540
|
const normalized = normalizeAccountAuthTypes(account.authTypes);
|
|
@@ -1128,13 +580,12 @@ function buildAuthMenuAccounts(input) {
|
|
|
1128
580
|
const existing = rows.get(identity);
|
|
1129
581
|
const currentStatus = hasActiveCooldown(account, now)
|
|
1130
582
|
? "rate-limited"
|
|
1131
|
-
: typeof account.expires === "number" &&
|
|
1132
|
-
Number.isFinite(account.expires) &&
|
|
1133
|
-
account.expires <= now
|
|
583
|
+
: typeof account.expires === "number" && Number.isFinite(account.expires) && account.expires <= now
|
|
1134
584
|
? "expired"
|
|
1135
585
|
: "unknown";
|
|
1136
586
|
if (!existing) {
|
|
1137
|
-
const isCurrentAccount = authMode === input.activeMode &&
|
|
587
|
+
const isCurrentAccount = authMode === input.activeMode &&
|
|
588
|
+
Boolean(domain.activeIdentityKey && account.identityKey === domain.activeIdentityKey);
|
|
1138
589
|
rows.set(identity, {
|
|
1139
590
|
identityKey: account.identityKey,
|
|
1140
591
|
index: rows.size,
|
|
@@ -1150,8 +601,7 @@ function buildAuthMenuAccounts(input) {
|
|
|
1150
601
|
continue;
|
|
1151
602
|
}
|
|
1152
603
|
existing.authTypes = normalizeAccountAuthTypes([...(existing.authTypes ?? []), authMode]);
|
|
1153
|
-
if (typeof account.lastUsed === "number" &&
|
|
1154
|
-
(!existing.lastUsed || account.lastUsed > existing.lastUsed)) {
|
|
604
|
+
if (typeof account.lastUsed === "number" && (!existing.lastUsed || account.lastUsed > existing.lastUsed)) {
|
|
1155
605
|
existing.lastUsed = account.lastUsed;
|
|
1156
606
|
}
|
|
1157
607
|
if (existing.enabled === false && account.enabled !== false) {
|
|
@@ -1160,12 +610,11 @@ function buildAuthMenuAccounts(input) {
|
|
|
1160
610
|
if (existing.status !== "rate-limited" && currentStatus === "rate-limited") {
|
|
1161
611
|
existing.status = "rate-limited";
|
|
1162
612
|
}
|
|
1163
|
-
else if (existing.status !== "rate-limited" &&
|
|
1164
|
-
existing.status !== "expired" &&
|
|
1165
|
-
currentStatus === "expired") {
|
|
613
|
+
else if (existing.status !== "rate-limited" && existing.status !== "expired" && currentStatus === "expired") {
|
|
1166
614
|
existing.status = "expired";
|
|
1167
615
|
}
|
|
1168
|
-
const isCurrentAccount = authMode === input.activeMode &&
|
|
616
|
+
const isCurrentAccount = authMode === input.activeMode &&
|
|
617
|
+
Boolean(domain.activeIdentityKey && account.identityKey === domain.activeIdentityKey);
|
|
1169
618
|
if (isCurrentAccount) {
|
|
1170
619
|
existing.isCurrentAccount = true;
|
|
1171
620
|
existing.status = "active";
|
|
@@ -1177,9 +626,7 @@ function buildAuthMenuAccounts(input) {
|
|
|
1177
626
|
return Array.from(rows.values()).map((row, index) => ({ ...row, index }));
|
|
1178
627
|
}
|
|
1179
628
|
function hydrateAccountIdentityFromAccessClaims(account) {
|
|
1180
|
-
const claims = typeof account.access === "string" && account.access.length > 0
|
|
1181
|
-
? parseJwtClaims(account.access)
|
|
1182
|
-
: undefined;
|
|
629
|
+
const claims = typeof account.access === "string" && account.access.length > 0 ? parseJwtClaims(account.access) : undefined;
|
|
1183
630
|
if (!account.accountId)
|
|
1184
631
|
account.accountId = extractAccountIdFromClaims(claims);
|
|
1185
632
|
if (!account.email)
|
|
@@ -1193,35 +640,6 @@ function hydrateAccountIdentityFromAccessClaims(account) {
|
|
|
1193
640
|
ensureAccountAuthTypes(account);
|
|
1194
641
|
synchronizeIdentityKey(account);
|
|
1195
642
|
}
|
|
1196
|
-
async function selectCatalogAuthCandidate(authMode, pidOffsetEnabled, rotationStrategy) {
|
|
1197
|
-
try {
|
|
1198
|
-
const auth = await loadAuthStorage();
|
|
1199
|
-
const domain = getOpenAIOAuthDomain(auth, authMode);
|
|
1200
|
-
if (!domain) {
|
|
1201
|
-
return {};
|
|
1202
|
-
}
|
|
1203
|
-
const selected = selectAccount({
|
|
1204
|
-
accounts: domain.accounts,
|
|
1205
|
-
strategy: rotationStrategy ?? domain.strategy,
|
|
1206
|
-
activeIdentityKey: domain.activeIdentityKey,
|
|
1207
|
-
now: Date.now(),
|
|
1208
|
-
stickyPidOffset: pidOffsetEnabled
|
|
1209
|
-
});
|
|
1210
|
-
if (!selected?.access) {
|
|
1211
|
-
return { accountId: selected?.accountId };
|
|
1212
|
-
}
|
|
1213
|
-
if (selected.expires && selected.expires <= Date.now()) {
|
|
1214
|
-
return { accountId: selected.accountId };
|
|
1215
|
-
}
|
|
1216
|
-
return {
|
|
1217
|
-
accessToken: selected.access,
|
|
1218
|
-
accountId: selected.accountId
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1221
|
-
catch {
|
|
1222
|
-
return {};
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
643
|
function isRecord(value) {
|
|
1226
644
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1227
645
|
}
|
|
@@ -1231,352 +649,9 @@ function asString(value) {
|
|
|
1231
649
|
const trimmed = value.trim();
|
|
1232
650
|
return trimmed ? trimmed : undefined;
|
|
1233
651
|
}
|
|
1234
|
-
function asStringArray(value) {
|
|
1235
|
-
if (!Array.isArray(value))
|
|
1236
|
-
return undefined;
|
|
1237
|
-
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
1238
|
-
}
|
|
1239
|
-
function normalizeReasoningSummaryOption(value) {
|
|
1240
|
-
const normalized = asString(value)?.toLowerCase();
|
|
1241
|
-
if (!normalized || normalized === "none")
|
|
1242
|
-
return undefined;
|
|
1243
|
-
if (normalized === "auto" || normalized === "concise" || normalized === "detailed")
|
|
1244
|
-
return normalized;
|
|
1245
|
-
return undefined;
|
|
1246
|
-
}
|
|
1247
|
-
function readModelRuntimeDefaults(options) {
|
|
1248
|
-
const raw = options.codexRuntimeDefaults;
|
|
1249
|
-
if (!isRecord(raw))
|
|
1250
|
-
return {};
|
|
1251
|
-
return {
|
|
1252
|
-
applyPatchToolType: asString(raw.applyPatchToolType),
|
|
1253
|
-
defaultReasoningEffort: asString(raw.defaultReasoningEffort),
|
|
1254
|
-
supportsReasoningSummaries: typeof raw.supportsReasoningSummaries === "boolean" ? raw.supportsReasoningSummaries : undefined,
|
|
1255
|
-
reasoningSummaryFormat: asString(raw.reasoningSummaryFormat),
|
|
1256
|
-
defaultVerbosity: raw.defaultVerbosity === "low" || raw.defaultVerbosity === "medium" || raw.defaultVerbosity === "high"
|
|
1257
|
-
? raw.defaultVerbosity
|
|
1258
|
-
: undefined,
|
|
1259
|
-
supportsVerbosity: typeof raw.supportsVerbosity === "boolean" ? raw.supportsVerbosity : undefined
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
function mergeUnique(values) {
|
|
1263
|
-
const out = [];
|
|
1264
|
-
const seen = new Set();
|
|
1265
|
-
for (const value of values) {
|
|
1266
|
-
if (seen.has(value))
|
|
1267
|
-
continue;
|
|
1268
|
-
seen.add(value);
|
|
1269
|
-
out.push(value);
|
|
1270
|
-
}
|
|
1271
|
-
return out;
|
|
1272
|
-
}
|
|
1273
|
-
function normalizePersonalityKey(value) {
|
|
1274
|
-
const normalized = asString(value)?.toLowerCase();
|
|
1275
|
-
if (!normalized)
|
|
1276
|
-
return undefined;
|
|
1277
|
-
if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("..")) {
|
|
1278
|
-
return undefined;
|
|
1279
|
-
}
|
|
1280
|
-
return normalized;
|
|
1281
|
-
}
|
|
1282
|
-
function getModelLookupCandidates(model) {
|
|
1283
|
-
const out = [];
|
|
1284
|
-
const seen = new Set();
|
|
1285
|
-
const add = (value) => {
|
|
1286
|
-
const trimmed = value?.trim();
|
|
1287
|
-
if (!trimmed)
|
|
1288
|
-
return;
|
|
1289
|
-
if (seen.has(trimmed))
|
|
1290
|
-
return;
|
|
1291
|
-
seen.add(trimmed);
|
|
1292
|
-
out.push(trimmed);
|
|
1293
|
-
};
|
|
1294
|
-
add(model.id);
|
|
1295
|
-
add(model.api?.id);
|
|
1296
|
-
add(model.id?.split("/").pop());
|
|
1297
|
-
add(model.api?.id?.split("/").pop());
|
|
1298
|
-
return out;
|
|
1299
|
-
}
|
|
1300
|
-
function getVariantLookupCandidates(input) {
|
|
1301
|
-
const out = [];
|
|
1302
|
-
const seen = new Set();
|
|
1303
|
-
const add = (value) => {
|
|
1304
|
-
const trimmed = value?.trim();
|
|
1305
|
-
if (!trimmed)
|
|
1306
|
-
return;
|
|
1307
|
-
if (seen.has(trimmed))
|
|
1308
|
-
return;
|
|
1309
|
-
seen.add(trimmed);
|
|
1310
|
-
out.push(trimmed);
|
|
1311
|
-
};
|
|
1312
|
-
if (isRecord(input.message)) {
|
|
1313
|
-
add(asString(input.message.variant));
|
|
1314
|
-
}
|
|
1315
|
-
for (const candidate of input.modelCandidates) {
|
|
1316
|
-
const slash = candidate.lastIndexOf("/");
|
|
1317
|
-
if (slash <= 0 || slash >= candidate.length - 1)
|
|
1318
|
-
continue;
|
|
1319
|
-
add(candidate.slice(slash + 1));
|
|
1320
|
-
}
|
|
1321
|
-
return out;
|
|
1322
|
-
}
|
|
1323
|
-
const EFFORT_SUFFIX_REGEX = /-(none|minimal|low|medium|high|xhigh)$/i;
|
|
1324
|
-
function stripEffortSuffix(value) {
|
|
1325
|
-
return value.replace(EFFORT_SUFFIX_REGEX, "");
|
|
1326
|
-
}
|
|
1327
|
-
function findCatalogModelForCandidates(catalogModels, modelCandidates) {
|
|
1328
|
-
if (!catalogModels || catalogModels.length === 0)
|
|
1329
|
-
return undefined;
|
|
1330
|
-
const wanted = new Set();
|
|
1331
|
-
for (const candidate of modelCandidates) {
|
|
1332
|
-
const normalized = candidate.trim().toLowerCase();
|
|
1333
|
-
if (!normalized)
|
|
1334
|
-
continue;
|
|
1335
|
-
wanted.add(normalized);
|
|
1336
|
-
wanted.add(stripEffortSuffix(normalized));
|
|
1337
|
-
}
|
|
1338
|
-
return catalogModels.find((model) => {
|
|
1339
|
-
const slug = model.slug.trim().toLowerCase();
|
|
1340
|
-
if (!slug)
|
|
1341
|
-
return false;
|
|
1342
|
-
return wanted.has(slug) || wanted.has(stripEffortSuffix(slug));
|
|
1343
|
-
});
|
|
1344
|
-
}
|
|
1345
|
-
function resolveCaseInsensitiveEntry(entries, candidate) {
|
|
1346
|
-
if (!entries)
|
|
1347
|
-
return undefined;
|
|
1348
|
-
const direct = entries[candidate];
|
|
1349
|
-
if (direct !== undefined)
|
|
1350
|
-
return direct;
|
|
1351
|
-
const lowered = entries[candidate.toLowerCase()];
|
|
1352
|
-
if (lowered !== undefined)
|
|
1353
|
-
return lowered;
|
|
1354
|
-
const loweredCandidate = candidate.toLowerCase();
|
|
1355
|
-
for (const [name, entry] of Object.entries(entries)) {
|
|
1356
|
-
if (name.trim().toLowerCase() === loweredCandidate) {
|
|
1357
|
-
return entry;
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
return undefined;
|
|
1361
|
-
}
|
|
1362
|
-
function getModelPersonalityOverride(customSettings, modelCandidates, variantCandidates) {
|
|
1363
|
-
const models = customSettings?.models;
|
|
1364
|
-
if (!models)
|
|
1365
|
-
return undefined;
|
|
1366
|
-
for (const candidate of modelCandidates) {
|
|
1367
|
-
const entry = resolveCaseInsensitiveEntry(models, candidate);
|
|
1368
|
-
if (!entry)
|
|
1369
|
-
continue;
|
|
1370
|
-
for (const variantCandidate of variantCandidates) {
|
|
1371
|
-
const variantEntry = resolveCaseInsensitiveEntry(entry.variants, variantCandidate);
|
|
1372
|
-
const variantPersonality = normalizePersonalityKey(variantEntry?.options?.personality);
|
|
1373
|
-
if (variantPersonality)
|
|
1374
|
-
return variantPersonality;
|
|
1375
|
-
}
|
|
1376
|
-
const modelPersonality = normalizePersonalityKey(entry.options?.personality);
|
|
1377
|
-
if (modelPersonality)
|
|
1378
|
-
return modelPersonality;
|
|
1379
|
-
}
|
|
1380
|
-
return undefined;
|
|
1381
|
-
}
|
|
1382
|
-
function getModelThinkingSummariesOverride(customSettings, modelCandidates, variantCandidates) {
|
|
1383
|
-
const models = customSettings?.models;
|
|
1384
|
-
if (!models)
|
|
1385
|
-
return undefined;
|
|
1386
|
-
for (const candidate of modelCandidates) {
|
|
1387
|
-
const entry = resolveCaseInsensitiveEntry(models, candidate);
|
|
1388
|
-
if (!entry)
|
|
1389
|
-
continue;
|
|
1390
|
-
for (const variantCandidate of variantCandidates) {
|
|
1391
|
-
const variantEntry = resolveCaseInsensitiveEntry(entry.variants, variantCandidate);
|
|
1392
|
-
if (typeof variantEntry?.thinkingSummaries === "boolean") {
|
|
1393
|
-
return variantEntry.thinkingSummaries;
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
if (typeof entry.thinkingSummaries === "boolean") {
|
|
1397
|
-
return entry.thinkingSummaries;
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
return undefined;
|
|
1401
|
-
}
|
|
1402
|
-
function resolvePersonalityForModel(input) {
|
|
1403
|
-
const modelOverride = getModelPersonalityOverride(input.customSettings, input.modelCandidates, input.variantCandidates);
|
|
1404
|
-
if (modelOverride)
|
|
1405
|
-
return modelOverride;
|
|
1406
|
-
const globalOverride = normalizePersonalityKey(input.customSettings?.options?.personality);
|
|
1407
|
-
if (globalOverride)
|
|
1408
|
-
return globalOverride;
|
|
1409
|
-
return normalizePersonalityKey(input.fallback);
|
|
1410
|
-
}
|
|
1411
|
-
function applyCodexRuntimeDefaultsToParams(input) {
|
|
1412
|
-
const options = input.output.options;
|
|
1413
|
-
const modelOptions = input.modelOptions;
|
|
1414
|
-
const defaults = readModelRuntimeDefaults(modelOptions);
|
|
1415
|
-
const codexInstructions = asString(modelOptions.codexInstructions);
|
|
1416
|
-
if (codexInstructions && (input.preferCodexInstructions || asString(options.instructions) === undefined)) {
|
|
1417
|
-
options.instructions = codexInstructions;
|
|
1418
|
-
}
|
|
1419
|
-
if (asString(options.reasoningEffort) === undefined && defaults.defaultReasoningEffort) {
|
|
1420
|
-
options.reasoningEffort = defaults.defaultReasoningEffort;
|
|
1421
|
-
}
|
|
1422
|
-
const reasoningEffort = asString(options.reasoningEffort);
|
|
1423
|
-
const hasReasoning = reasoningEffort !== undefined && reasoningEffort !== "none";
|
|
1424
|
-
const rawReasoningSummary = asString(options.reasoningSummary);
|
|
1425
|
-
const hadExplicitReasoningSummary = rawReasoningSummary !== undefined;
|
|
1426
|
-
const currentReasoningSummary = normalizeReasoningSummaryOption(rawReasoningSummary);
|
|
1427
|
-
if (rawReasoningSummary !== undefined) {
|
|
1428
|
-
if (currentReasoningSummary) {
|
|
1429
|
-
options.reasoningSummary = currentReasoningSummary;
|
|
1430
|
-
}
|
|
1431
|
-
else {
|
|
1432
|
-
delete options.reasoningSummary;
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
if (!hadExplicitReasoningSummary && currentReasoningSummary === undefined) {
|
|
1436
|
-
if (hasReasoning &&
|
|
1437
|
-
(defaults.supportsReasoningSummaries === true || input.thinkingSummariesOverride === true)) {
|
|
1438
|
-
if (input.thinkingSummariesOverride === false) {
|
|
1439
|
-
delete options.reasoningSummary;
|
|
1440
|
-
}
|
|
1441
|
-
else {
|
|
1442
|
-
if (defaults.reasoningSummaryFormat?.toLowerCase() === "none") {
|
|
1443
|
-
delete options.reasoningSummary;
|
|
1444
|
-
}
|
|
1445
|
-
else {
|
|
1446
|
-
options.reasoningSummary = defaults.reasoningSummaryFormat ?? "auto";
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
if (asString(options.textVerbosity) === undefined &&
|
|
1452
|
-
defaults.defaultVerbosity &&
|
|
1453
|
-
(defaults.supportsVerbosity ?? true)) {
|
|
1454
|
-
options.textVerbosity = defaults.defaultVerbosity;
|
|
1455
|
-
}
|
|
1456
|
-
if (asString(options.applyPatchToolType) === undefined && defaults.applyPatchToolType) {
|
|
1457
|
-
options.applyPatchToolType = defaults.applyPatchToolType;
|
|
1458
|
-
}
|
|
1459
|
-
if (typeof options.parallelToolCalls !== "boolean" && input.modelToolCallCapable !== undefined) {
|
|
1460
|
-
options.parallelToolCalls = input.modelToolCallCapable;
|
|
1461
|
-
}
|
|
1462
|
-
const shouldIncludeReasoning = hasReasoning &&
|
|
1463
|
-
((asString(options.reasoningSummary) !== undefined &&
|
|
1464
|
-
asString(options.reasoningSummary)?.toLowerCase() !== "none") ||
|
|
1465
|
-
defaults.supportsReasoningSummaries === true);
|
|
1466
|
-
if (shouldIncludeReasoning) {
|
|
1467
|
-
const include = asStringArray(options.include) ?? [];
|
|
1468
|
-
options.include = mergeUnique([...include, "reasoning.encrypted_content"]);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
async function sanitizeOutboundRequestIfNeeded(request, enabled) {
|
|
1472
|
-
if (!enabled)
|
|
1473
|
-
return { request, changed: false };
|
|
1474
|
-
const method = request.method.toUpperCase();
|
|
1475
|
-
if (method !== "POST")
|
|
1476
|
-
return { request, changed: false };
|
|
1477
|
-
let payload;
|
|
1478
|
-
try {
|
|
1479
|
-
const raw = await request.clone().text();
|
|
1480
|
-
if (!raw)
|
|
1481
|
-
return { request, changed: false };
|
|
1482
|
-
payload = JSON.parse(raw);
|
|
1483
|
-
}
|
|
1484
|
-
catch {
|
|
1485
|
-
return { request, changed: false };
|
|
1486
|
-
}
|
|
1487
|
-
if (!isRecord(payload))
|
|
1488
|
-
return { request, changed: false };
|
|
1489
|
-
const sanitized = sanitizeRequestPayloadForCompat(payload);
|
|
1490
|
-
if (!sanitized.changed)
|
|
1491
|
-
return { request, changed: false };
|
|
1492
|
-
const headers = new Headers(request.headers);
|
|
1493
|
-
headers.set("content-type", "application/json");
|
|
1494
|
-
const sanitizedRequest = new Request(request.url, {
|
|
1495
|
-
method: request.method,
|
|
1496
|
-
headers,
|
|
1497
|
-
body: JSON.stringify(sanitized.payload),
|
|
1498
|
-
redirect: request.redirect
|
|
1499
|
-
});
|
|
1500
|
-
return { request: sanitizedRequest, changed: true };
|
|
1501
|
-
}
|
|
1502
|
-
function getVariantCandidatesFromBody(input) {
|
|
1503
|
-
const out = [];
|
|
1504
|
-
const seen = new Set();
|
|
1505
|
-
const add = (value) => {
|
|
1506
|
-
const trimmed = value?.trim().toLowerCase();
|
|
1507
|
-
if (!trimmed)
|
|
1508
|
-
return;
|
|
1509
|
-
if (seen.has(trimmed))
|
|
1510
|
-
return;
|
|
1511
|
-
seen.add(trimmed);
|
|
1512
|
-
out.push(trimmed);
|
|
1513
|
-
};
|
|
1514
|
-
const reasoning = isRecord(input.body.reasoning) ? input.body.reasoning : undefined;
|
|
1515
|
-
add(asString(reasoning?.effort));
|
|
1516
|
-
const normalizedSlug = input.modelSlug.trim().toLowerCase();
|
|
1517
|
-
const suffix = normalizedSlug.match(EFFORT_SUFFIX_REGEX)?.[1];
|
|
1518
|
-
add(suffix);
|
|
1519
|
-
return out;
|
|
1520
|
-
}
|
|
1521
|
-
async function applyCatalogInstructionOverrideToRequest(input) {
|
|
1522
|
-
if (!input.enabled)
|
|
1523
|
-
return { request: input.request, changed: false };
|
|
1524
|
-
const method = input.request.method.toUpperCase();
|
|
1525
|
-
if (method !== "POST")
|
|
1526
|
-
return { request: input.request, changed: false };
|
|
1527
|
-
let payload;
|
|
1528
|
-
try {
|
|
1529
|
-
const raw = await input.request.clone().text();
|
|
1530
|
-
if (!raw)
|
|
1531
|
-
return { request: input.request, changed: false };
|
|
1532
|
-
payload = JSON.parse(raw);
|
|
1533
|
-
}
|
|
1534
|
-
catch {
|
|
1535
|
-
return { request: input.request, changed: false };
|
|
1536
|
-
}
|
|
1537
|
-
if (!isRecord(payload))
|
|
1538
|
-
return { request: input.request, changed: false };
|
|
1539
|
-
const modelSlugRaw = asString(payload.model);
|
|
1540
|
-
if (!modelSlugRaw)
|
|
1541
|
-
return { request: input.request, changed: false };
|
|
1542
|
-
const modelCandidates = getModelLookupCandidates({
|
|
1543
|
-
id: modelSlugRaw,
|
|
1544
|
-
api: { id: modelSlugRaw }
|
|
1545
|
-
});
|
|
1546
|
-
const variantCandidates = getVariantCandidatesFromBody({
|
|
1547
|
-
body: payload,
|
|
1548
|
-
modelSlug: modelSlugRaw
|
|
1549
|
-
});
|
|
1550
|
-
const effectivePersonality = resolvePersonalityForModel({
|
|
1551
|
-
customSettings: input.customSettings,
|
|
1552
|
-
modelCandidates,
|
|
1553
|
-
variantCandidates,
|
|
1554
|
-
fallback: input.fallbackPersonality
|
|
1555
|
-
});
|
|
1556
|
-
const catalogModel = findCatalogModelForCandidates(input.catalogModels, modelCandidates);
|
|
1557
|
-
if (!catalogModel)
|
|
1558
|
-
return { request: input.request, changed: false };
|
|
1559
|
-
const rendered = resolveInstructionsForModel(catalogModel, effectivePersonality);
|
|
1560
|
-
if (!rendered)
|
|
1561
|
-
return { request: input.request, changed: false };
|
|
1562
|
-
if (asString(payload.instructions) === rendered) {
|
|
1563
|
-
return { request: input.request, changed: false };
|
|
1564
|
-
}
|
|
1565
|
-
payload.instructions = rendered;
|
|
1566
|
-
const headers = new Headers(input.request.headers);
|
|
1567
|
-
headers.set("content-type", "application/json");
|
|
1568
|
-
const updatedRequest = new Request(input.request.url, {
|
|
1569
|
-
method: input.request.method,
|
|
1570
|
-
headers,
|
|
1571
|
-
body: JSON.stringify(payload),
|
|
1572
|
-
redirect: input.request.redirect
|
|
1573
|
-
});
|
|
1574
|
-
return { request: updatedRequest, changed: true };
|
|
1575
|
-
}
|
|
1576
652
|
export async function CodexAuthPlugin(input, opts = {}) {
|
|
1577
653
|
opts.log?.debug("codex-native init");
|
|
1578
|
-
const spoofMode = opts.spoofMode === "codex" ||
|
|
1579
|
-
opts.spoofMode === "strict"
|
|
654
|
+
const spoofMode = opts.spoofMode === "codex" || opts.spoofMode === "strict"
|
|
1580
655
|
? "codex"
|
|
1581
656
|
: "native";
|
|
1582
657
|
const runtimeMode = opts.mode === "collab" || opts.mode === "codex" || opts.mode === "native"
|
|
@@ -1586,11 +661,15 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1586
661
|
: "native";
|
|
1587
662
|
const collabModeEnabled = runtimeMode === "collab";
|
|
1588
663
|
const authMode = modeForRuntimeMode(runtimeMode);
|
|
664
|
+
void refreshCodexClientVersionFromGitHub(opts.log).catch(() => { });
|
|
1589
665
|
const resolveCatalogHeaders = () => {
|
|
1590
666
|
const originator = resolveCodexOriginator(spoofMode);
|
|
667
|
+
const codexClientVersion = resolveCodexClientVersion();
|
|
1591
668
|
return {
|
|
1592
669
|
originator,
|
|
1593
670
|
userAgent: resolveRequestUserAgent(spoofMode, originator),
|
|
671
|
+
clientVersion: codexClientVersion,
|
|
672
|
+
versionHeader: codexClientVersion,
|
|
1594
673
|
...(spoofMode === "native" ? { openaiBeta: "responses=experimental" } : {})
|
|
1595
674
|
};
|
|
1596
675
|
};
|
|
@@ -1599,6 +678,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1599
678
|
log: opts.log
|
|
1600
679
|
});
|
|
1601
680
|
let lastCatalogModels;
|
|
681
|
+
const quotaFetchCooldownByIdentity = new Map();
|
|
1602
682
|
const showToast = async (message, variant = "info", quietMode = false) => {
|
|
1603
683
|
if (quietMode)
|
|
1604
684
|
return;
|
|
@@ -1616,6 +696,8 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1616
696
|
};
|
|
1617
697
|
const refreshQuotaSnapshotsForAuthMenu = async () => {
|
|
1618
698
|
const auth = await loadAuthStorage();
|
|
699
|
+
const snapshotPath = defaultSnapshotsPath();
|
|
700
|
+
const existingSnapshots = await loadSnapshots(snapshotPath).catch(() => ({}));
|
|
1619
701
|
const snapshotUpdates = {};
|
|
1620
702
|
for (const { mode, domain } of listOpenAIOAuthDomains(auth)) {
|
|
1621
703
|
for (let index = 0; index < domain.accounts.length; index += 1) {
|
|
@@ -1623,11 +705,22 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1623
705
|
if (!account || account.enabled === false)
|
|
1624
706
|
continue;
|
|
1625
707
|
hydrateAccountIdentityFromAccessClaims(account);
|
|
1626
|
-
|
|
708
|
+
const identityKey = account.identityKey;
|
|
1627
709
|
const now = Date.now();
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
710
|
+
if (identityKey) {
|
|
711
|
+
const cooldownUntil = quotaFetchCooldownByIdentity.get(identityKey);
|
|
712
|
+
if (typeof cooldownUntil === "number" && cooldownUntil > now)
|
|
713
|
+
continue;
|
|
714
|
+
const existing = existingSnapshots[identityKey];
|
|
715
|
+
if (existing &&
|
|
716
|
+
typeof existing.updatedAt === "number" &&
|
|
717
|
+
Number.isFinite(existing.updatedAt) &&
|
|
718
|
+
now - existing.updatedAt < AUTH_MENU_QUOTA_SNAPSHOT_TTL_MS) {
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
let accessToken = typeof account.access === "string" && account.access.length > 0 ? account.access : undefined;
|
|
723
|
+
const expired = typeof account.expires === "number" && Number.isFinite(account.expires) && account.expires <= now;
|
|
1631
724
|
if ((!accessToken || expired) && account.refresh) {
|
|
1632
725
|
try {
|
|
1633
726
|
await saveAuthStorage(undefined, async (authFile) => {
|
|
@@ -1658,6 +751,9 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1658
751
|
});
|
|
1659
752
|
}
|
|
1660
753
|
catch (error) {
|
|
754
|
+
if (identityKey) {
|
|
755
|
+
quotaFetchCooldownByIdentity.set(identityKey, Date.now() + AUTH_MENU_QUOTA_FAILURE_COOLDOWN_MS);
|
|
756
|
+
}
|
|
1661
757
|
opts.log?.debug("quota check refresh failed", {
|
|
1662
758
|
index,
|
|
1663
759
|
mode,
|
|
@@ -1678,16 +774,20 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1678
774
|
now: Date.now(),
|
|
1679
775
|
modelFamily: "gpt-5.3-codex",
|
|
1680
776
|
userAgent: resolveRequestUserAgent(spoofMode, resolveCodexOriginator(spoofMode)),
|
|
1681
|
-
log: opts.log
|
|
777
|
+
log: opts.log,
|
|
778
|
+
timeoutMs: AUTH_MENU_QUOTA_FETCH_TIMEOUT_MS
|
|
1682
779
|
});
|
|
1683
|
-
if (!snapshot)
|
|
780
|
+
if (!snapshot) {
|
|
781
|
+
quotaFetchCooldownByIdentity.set(account.identityKey, Date.now() + AUTH_MENU_QUOTA_FAILURE_COOLDOWN_MS);
|
|
1684
782
|
continue;
|
|
783
|
+
}
|
|
784
|
+
quotaFetchCooldownByIdentity.delete(account.identityKey);
|
|
1685
785
|
snapshotUpdates[account.identityKey] = snapshot;
|
|
1686
786
|
}
|
|
1687
787
|
}
|
|
1688
788
|
if (Object.keys(snapshotUpdates).length === 0)
|
|
1689
789
|
return;
|
|
1690
|
-
await saveSnapshots(
|
|
790
|
+
await saveSnapshots(snapshotPath, (current) => ({
|
|
1691
791
|
...current,
|
|
1692
792
|
...snapshotUpdates
|
|
1693
793
|
}));
|
|
@@ -1779,9 +879,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1779
879
|
},
|
|
1780
880
|
onToggleAccount: async (account) => {
|
|
1781
881
|
await saveAuthStorage(undefined, (authFile) => {
|
|
1782
|
-
const authTypes = account.authTypes && account.authTypes.length > 0
|
|
1783
|
-
? [...account.authTypes]
|
|
1784
|
-
: ["native"];
|
|
882
|
+
const authTypes = account.authTypes && account.authTypes.length > 0 ? [...account.authTypes] : ["native"];
|
|
1785
883
|
for (const mode of authTypes) {
|
|
1786
884
|
const domain = getOpenAIOAuthDomain(authFile, mode);
|
|
1787
885
|
if (!domain)
|
|
@@ -1893,7 +991,9 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1893
991
|
if (!hasOAuth)
|
|
1894
992
|
return {};
|
|
1895
993
|
const sessionAffinityPath = defaultSessionAffinityPath();
|
|
1896
|
-
const loadedSessionAffinity = await loadSessionAffinity(sessionAffinityPath).catch(() => ({
|
|
994
|
+
const loadedSessionAffinity = await loadSessionAffinity(sessionAffinityPath).catch(() => ({
|
|
995
|
+
version: 1
|
|
996
|
+
}));
|
|
1897
997
|
const initialSessionAffinity = readSessionAffinitySnapshot(loadedSessionAffinity, authMode);
|
|
1898
998
|
const sessionExists = createSessionExistsFn(process.env);
|
|
1899
999
|
await pruneSessionAffinitySnapshot(initialSessionAffinity, sessionExists, {
|
|
@@ -1933,13 +1033,30 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1933
1033
|
...resolveCatalogHeaders(),
|
|
1934
1034
|
onEvent: (event) => opts.log?.debug("codex model catalog", event)
|
|
1935
1035
|
});
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1036
|
+
const applyCatalogModels = (models) => {
|
|
1037
|
+
if (models) {
|
|
1038
|
+
lastCatalogModels = models;
|
|
1039
|
+
}
|
|
1040
|
+
applyCodexCatalogToProviderModels({
|
|
1041
|
+
providerModels: provider.models,
|
|
1042
|
+
catalogModels: models ?? lastCatalogModels,
|
|
1043
|
+
fallbackModels: STATIC_FALLBACK_MODELS,
|
|
1044
|
+
personality: opts.personality
|
|
1045
|
+
});
|
|
1046
|
+
};
|
|
1047
|
+
applyCatalogModels(catalogModels);
|
|
1048
|
+
const syncCatalogFromAuth = async (auth) => {
|
|
1049
|
+
if (!auth.accessToken)
|
|
1050
|
+
return undefined;
|
|
1051
|
+
const refreshedCatalog = await getCodexModelCatalog({
|
|
1052
|
+
accessToken: auth.accessToken,
|
|
1053
|
+
accountId: auth.accountId,
|
|
1054
|
+
...resolveCatalogHeaders(),
|
|
1055
|
+
onEvent: (event) => opts.log?.debug("codex model catalog", event)
|
|
1056
|
+
});
|
|
1057
|
+
applyCatalogModels(refreshedCatalog);
|
|
1058
|
+
return refreshedCatalog;
|
|
1059
|
+
};
|
|
1943
1060
|
return {
|
|
1944
1061
|
apiKey: OAUTH_DUMMY_KEY,
|
|
1945
1062
|
async fetch(requestInput, init) {
|
|
@@ -1948,9 +1065,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1948
1065
|
if (opts.headerTransformDebug === true) {
|
|
1949
1066
|
await requestSnapshots.captureRequest("before-header-transform", baseRequest, {
|
|
1950
1067
|
spoofMode,
|
|
1951
|
-
...(inboundCollaborationModeKind
|
|
1952
|
-
? { collaborationModeKind: inboundCollaborationModeKind }
|
|
1953
|
-
: {})
|
|
1068
|
+
...(inboundCollaborationModeKind ? { collaborationModeKind: inboundCollaborationModeKind } : {})
|
|
1954
1069
|
});
|
|
1955
1070
|
}
|
|
1956
1071
|
let outbound = new Request(rewriteUrl(baseRequest), baseRequest);
|
|
@@ -1986,6 +1101,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1986
1101
|
await requestSnapshots.captureRequest("after-header-transform", outbound, {
|
|
1987
1102
|
spoofMode,
|
|
1988
1103
|
instructionsOverridden: instructionOverride.changed,
|
|
1104
|
+
instructionOverrideReason: instructionOverride.reason,
|
|
1989
1105
|
...(collaborationModeKind ? { collaborationModeKind } : {}),
|
|
1990
1106
|
...(isSubagentRequest ? { subagent: subagentHeader } : {})
|
|
1991
1107
|
});
|
|
@@ -2122,8 +1238,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2122
1238
|
tokens = await refreshAccessToken(selected.refresh);
|
|
2123
1239
|
}
|
|
2124
1240
|
catch (error) {
|
|
2125
|
-
if (isOAuthTokenRefreshError(error) &&
|
|
2126
|
-
error.oauthCode?.toLowerCase() === "invalid_grant") {
|
|
1241
|
+
if (isOAuthTokenRefreshError(error) && error.oauthCode?.toLowerCase() === "invalid_grant") {
|
|
2127
1242
|
sawInvalidGrant = true;
|
|
2128
1243
|
selected.enabled = false;
|
|
2129
1244
|
delete selected.cooldownUntil;
|
|
@@ -2233,12 +1348,23 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2233
1348
|
param: "auth"
|
|
2234
1349
|
});
|
|
2235
1350
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
1351
|
+
if (spoofMode === "codex") {
|
|
1352
|
+
const shouldAwaitCatalog = !lastCatalogModels || lastCatalogModels.length === 0;
|
|
1353
|
+
if (shouldAwaitCatalog) {
|
|
1354
|
+
try {
|
|
1355
|
+
await syncCatalogFromAuth({ accessToken: access, accountId });
|
|
1356
|
+
}
|
|
1357
|
+
catch {
|
|
1358
|
+
// best-effort catalog load; request can still proceed
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
void syncCatalogFromAuth({ accessToken: access, accountId }).catch(() => { });
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
void syncCatalogFromAuth({ accessToken: access, accountId }).catch(() => { });
|
|
1367
|
+
}
|
|
2242
1368
|
selectedIdentityKey = identityKey;
|
|
2243
1369
|
return { access, accountId, identityKey, accountLabel, email, plan };
|
|
2244
1370
|
},
|
|
@@ -2260,14 +1386,24 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2260
1386
|
},
|
|
2261
1387
|
showToast,
|
|
2262
1388
|
onAttemptRequest: async ({ attempt, maxAttempts, request, auth, sessionKey }) => {
|
|
2263
|
-
await
|
|
1389
|
+
const instructionOverride = await applyCatalogInstructionOverrideToRequest({
|
|
1390
|
+
request,
|
|
1391
|
+
enabled: spoofMode === "codex",
|
|
1392
|
+
catalogModels: lastCatalogModels,
|
|
1393
|
+
customSettings: opts.customSettings,
|
|
1394
|
+
fallbackPersonality: opts.personality
|
|
1395
|
+
});
|
|
1396
|
+
await requestSnapshots.captureRequest("outbound-attempt", instructionOverride.request, {
|
|
2264
1397
|
attempt: attempt + 1,
|
|
2265
1398
|
maxAttempts,
|
|
2266
1399
|
sessionKey,
|
|
2267
1400
|
identityKey: auth.identityKey,
|
|
2268
1401
|
accountLabel: auth.accountLabel,
|
|
1402
|
+
instructionsOverridden: instructionOverride.changed,
|
|
1403
|
+
instructionOverrideReason: instructionOverride.reason,
|
|
2269
1404
|
...(collaborationModeKind ? { collaborationModeKind } : {})
|
|
2270
1405
|
});
|
|
1406
|
+
return instructionOverride.request;
|
|
2271
1407
|
},
|
|
2272
1408
|
onAttemptResponse: async ({ attempt, maxAttempts, response, auth, sessionKey }) => {
|
|
2273
1409
|
await requestSnapshots.captureResponse("outbound-response", response, {
|
|
@@ -2289,6 +1425,20 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2289
1425
|
sanitized: sanitizedOutbound.changed,
|
|
2290
1426
|
...(collaborationModeKind ? { collaborationModeKind } : {})
|
|
2291
1427
|
});
|
|
1428
|
+
try {
|
|
1429
|
+
assertAllowedOutboundUrl(new URL(sanitizedOutbound.request.url));
|
|
1430
|
+
}
|
|
1431
|
+
catch (error) {
|
|
1432
|
+
if (isPluginFatalError(error)) {
|
|
1433
|
+
return toSyntheticErrorResponse(error);
|
|
1434
|
+
}
|
|
1435
|
+
return toSyntheticErrorResponse(new PluginFatalError({
|
|
1436
|
+
message: "Outbound request validation failed before sending to OpenAI backend.",
|
|
1437
|
+
status: 400,
|
|
1438
|
+
type: "disallowed_outbound_request",
|
|
1439
|
+
param: "request"
|
|
1440
|
+
}));
|
|
1441
|
+
}
|
|
2292
1442
|
let response;
|
|
2293
1443
|
try {
|
|
2294
1444
|
response = await orchestrator.execute(sanitizedOutbound.request);
|
|
@@ -2368,7 +1518,6 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2368
1518
|
return null;
|
|
2369
1519
|
}
|
|
2370
1520
|
finally {
|
|
2371
|
-
pendingOAuth = undefined;
|
|
2372
1521
|
scheduleOAuthServerStop(authFailed ? OAUTH_SERVER_SHUTDOWN_ERROR_GRACE_MS : OAUTH_SERVER_SHUTDOWN_GRACE_MS, authFailed ? "error" : "success");
|
|
2373
1522
|
}
|
|
2374
1523
|
};
|
|
@@ -2406,10 +1555,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2406
1555
|
};
|
|
2407
1556
|
}
|
|
2408
1557
|
};
|
|
2409
|
-
if (inputs &&
|
|
2410
|
-
process.env.OPENCODE_NO_BROWSER !== "1" &&
|
|
2411
|
-
process.stdin.isTTY &&
|
|
2412
|
-
process.stdout.isTTY) {
|
|
1558
|
+
if (inputs && process.env.OPENCODE_NO_BROWSER !== "1" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
2413
1559
|
return runInteractiveBrowserAuthLoop();
|
|
2414
1560
|
}
|
|
2415
1561
|
const { redirectUri } = await startOAuthServer();
|
|
@@ -2434,7 +1580,6 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2434
1580
|
return { type: "failed" };
|
|
2435
1581
|
}
|
|
2436
1582
|
finally {
|
|
2437
|
-
pendingOAuth = undefined;
|
|
2438
1583
|
scheduleOAuthServerStop(authFailed ? OAUTH_SERVER_SHUTDOWN_ERROR_GRACE_MS : OAUTH_SERVER_SHUTDOWN_GRACE_MS, authFailed ? "error" : "success");
|
|
2439
1584
|
}
|
|
2440
1585
|
}
|
|
@@ -2518,9 +1663,8 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2518
1663
|
},
|
|
2519
1664
|
"chat.message": async (hookInput, output) => {
|
|
2520
1665
|
const directProviderID = hookInput.model?.providerID;
|
|
2521
|
-
const isOpenAI = directProviderID === "openai"
|
|
2522
|
-
|
|
2523
|
-
&& (await sessionUsesOpenAIProvider(input.client, hookInput.sessionID)));
|
|
1666
|
+
const isOpenAI = directProviderID === "openai" ||
|
|
1667
|
+
(directProviderID === undefined && (await sessionUsesOpenAIProvider(input.client, hookInput.sessionID)));
|
|
2524
1668
|
if (!isOpenAI)
|
|
2525
1669
|
return;
|
|
2526
1670
|
for (const part of output.parts) {
|
|
@@ -2536,9 +1680,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2536
1680
|
if (hookInput.model.providerID !== "openai")
|
|
2537
1681
|
return;
|
|
2538
1682
|
const initialReasoningEffort = asString(output.options.reasoningEffort);
|
|
2539
|
-
const collaborationProfile = collabModeEnabled
|
|
2540
|
-
? resolveCollaborationProfile(hookInput.agent)
|
|
2541
|
-
: { enabled: false };
|
|
1683
|
+
const collaborationProfile = collabModeEnabled ? resolveCollaborationProfile(hookInput.agent) : { enabled: false };
|
|
2542
1684
|
const modelOptions = isRecord(hookInput.model.options) ? hookInput.model.options : {};
|
|
2543
1685
|
const modelCandidates = getModelLookupCandidates({
|
|
2544
1686
|
id: hookInput.model.id,
|
|
@@ -2594,7 +1736,12 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2594
1736
|
});
|
|
2595
1737
|
if (collabModeEnabled && collaborationProfile.enabled && collaborationProfile.kind) {
|
|
2596
1738
|
const collaborationModeKind = collaborationProfile.kind;
|
|
2597
|
-
const collaborationInstructions = resolveCollaborationInstructions(collaborationModeKind
|
|
1739
|
+
const collaborationInstructions = resolveCollaborationInstructions(collaborationModeKind, {
|
|
1740
|
+
plan: CODEX_PLAN_MODE_INSTRUCTIONS,
|
|
1741
|
+
code: CODEX_CODE_MODE_INSTRUCTIONS,
|
|
1742
|
+
execute: CODEX_EXECUTE_MODE_INSTRUCTIONS,
|
|
1743
|
+
pairProgramming: CODEX_PAIR_PROGRAMMING_MODE_INSTRUCTIONS
|
|
1744
|
+
});
|
|
2598
1745
|
const mergedInstructions = mergeInstructions(asString(output.options.instructions), collaborationInstructions);
|
|
2599
1746
|
if (mergedInstructions) {
|
|
2600
1747
|
output.options.instructions = mergedInstructions;
|
|
@@ -2612,9 +1759,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2612
1759
|
"chat.headers": async (hookInput, output) => {
|
|
2613
1760
|
if (hookInput.model.providerID !== "openai")
|
|
2614
1761
|
return;
|
|
2615
|
-
const collaborationProfile = collabModeEnabled
|
|
2616
|
-
? resolveCollaborationProfile(hookInput.agent)
|
|
2617
|
-
: { enabled: false };
|
|
1762
|
+
const collaborationProfile = collabModeEnabled ? resolveCollaborationProfile(hookInput.agent) : { enabled: false };
|
|
2618
1763
|
const collaborationModeKind = collaborationProfile.enabled ? collaborationProfile.kind : undefined;
|
|
2619
1764
|
const originator = resolveCodexOriginator(spoofMode);
|
|
2620
1765
|
output.headers.originator = originator;
|