@iam-brain/opencode-codex-auth 0.1.16 → 0.2.1
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 +145 -1035
- 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.d.ts +5 -1
- package/dist/lib/codex-status-tool.d.ts.map +1 -1
- package/dist/lib/codex-status-tool.js +8 -6
- package/dist/lib/codex-status-tool.js.map +1 -1
- package/dist/lib/codex-status-ui.d.ts +4 -0
- package/dist/lib/codex-status-ui.d.ts.map +1 -1
- package/dist/lib/codex-status-ui.js +91 -28
- 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.map +1 -1
- package/dist/lib/fetch-orchestrator.js +2 -9
- 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";
|
|
@@ -16,13 +10,21 @@ import { toolOutputForStatus } from "./codex-status-tool";
|
|
|
16
10
|
import { FetchOrchestrator, createFetchOrchestratorState } from "./fetch-orchestrator";
|
|
17
11
|
import { formatToastMessage } from "./toast";
|
|
18
12
|
import { runAuthMenuOnce } from "./ui/auth-menu-runner";
|
|
13
|
+
import { shouldUseColor } from "./ui/tty/ansi";
|
|
19
14
|
import { applyCodexCatalogToProviderModels, getCodexModelCatalog, getRuntimeDefaultsForModel, resolveInstructionsForModel } from "./model-catalog";
|
|
20
15
|
import { CODEX_RS_COMPACT_PROMPT } from "./orchestrator-agents";
|
|
21
|
-
import { sanitizeRequestPayloadForCompat } from "./compat-sanitizer";
|
|
22
16
|
import { fetchQuotaSnapshotFromBackend } from "./codex-quota-fetch";
|
|
23
17
|
import { createRequestSnapshots } from "./request-snapshots";
|
|
24
18
|
import { CODEX_OAUTH_SUCCESS_HTML } from "./oauth-pages";
|
|
19
|
+
import { mergeInstructions, resolveCollaborationInstructions, resolveCollaborationModeKind, resolveCollaborationProfile, resolveHookAgentName, resolveSubagentHeaderValue } from "./codex-native/collaboration";
|
|
20
|
+
import { applyCatalogInstructionOverrideToRequest, applyCodexRuntimeDefaultsToParams, findCatalogModelForCandidates, getModelLookupCandidates, getModelThinkingSummariesOverride, getVariantLookupCandidates, resolvePersonalityForModel, sanitizeOutboundRequestIfNeeded } from "./codex-native/request-transform";
|
|
25
21
|
import { createSessionExistsFn, loadSessionAffinity, pruneSessionAffinitySnapshot, readSessionAffinitySnapshot, saveSessionAffinity, writeSessionAffinitySnapshot } from "./session-affinity";
|
|
22
|
+
import { resolveCodexOriginator } from "./codex-native/originator";
|
|
23
|
+
import { tryOpenUrlInBrowser as openUrlInBrowser } from "./codex-native/browser";
|
|
24
|
+
import { selectCatalogAuthCandidate } from "./codex-native/catalog-auth";
|
|
25
|
+
import { buildCodexUserAgent, refreshCodexClientVersionFromGitHub, resolveCodexClientVersion, resolveRequestUserAgent } from "./codex-native/client-identity";
|
|
26
|
+
import { createOAuthServerController } from "./codex-native/oauth-server";
|
|
27
|
+
export { browserOpenInvocationFor } from "./codex-native/browser";
|
|
26
28
|
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
27
29
|
const ISSUER = "https://auth.openai.com";
|
|
28
30
|
const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
|
|
@@ -55,10 +57,10 @@ const OAUTH_SERVER_SHUTDOWN_ERROR_GRACE_MS = (() => {
|
|
|
55
57
|
const parsed = Number(raw);
|
|
56
58
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 60_000;
|
|
57
59
|
})();
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
60
|
+
const OPENAI_OUTBOUND_HOST_ALLOWLIST = new Set(["api.openai.com", "auth.openai.com", "chat.openai.com", "chatgpt.com"]);
|
|
61
|
+
const AUTH_MENU_QUOTA_SNAPSHOT_TTL_MS = 60_000;
|
|
62
|
+
const AUTH_MENU_QUOTA_FAILURE_COOLDOWN_MS = 30_000;
|
|
63
|
+
const AUTH_MENU_QUOTA_FETCH_TIMEOUT_MS = 5000;
|
|
62
64
|
const INTERNAL_COLLABORATION_MODE_HEADER = "x-opencode-collaboration-mode-kind";
|
|
63
65
|
const SESSION_AFFINITY_MISSING_GRACE_MS = 15 * 60 * 1000;
|
|
64
66
|
const CODEX_PLAN_MODE_INSTRUCTIONS = [
|
|
@@ -93,38 +95,12 @@ const STATIC_FALLBACK_MODELS = [
|
|
|
93
95
|
function sleep(ms) {
|
|
94
96
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
97
|
}
|
|
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
98
|
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
|
-
}
|
|
99
|
+
return openUrlInBrowser({
|
|
100
|
+
url,
|
|
101
|
+
log,
|
|
102
|
+
onEvent: (event, meta) => oauthServerController.emitDebug(event, meta ?? {})
|
|
103
|
+
});
|
|
128
104
|
}
|
|
129
105
|
function escapeHtml(value) {
|
|
130
106
|
return value
|
|
@@ -141,11 +117,7 @@ async function generatePKCE() {
|
|
|
141
117
|
return { verifier, challenge };
|
|
142
118
|
}
|
|
143
119
|
function base64UrlEncode(bytes) {
|
|
144
|
-
return Buffer.from(bytes)
|
|
145
|
-
.toString("base64")
|
|
146
|
-
.replace(/\+/g, "-")
|
|
147
|
-
.replace(/\//g, "_")
|
|
148
|
-
.replace(/=+$/g, "");
|
|
120
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
149
121
|
}
|
|
150
122
|
function generateState() {
|
|
151
123
|
return base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)));
|
|
@@ -195,6 +167,8 @@ export const __testOnly = {
|
|
|
195
167
|
modeForRuntimeMode,
|
|
196
168
|
buildCodexUserAgent,
|
|
197
169
|
resolveRequestUserAgent,
|
|
170
|
+
resolveCodexClientVersion,
|
|
171
|
+
refreshCodexClientVersionFromGitHub,
|
|
198
172
|
resolveHookAgentName,
|
|
199
173
|
resolveCollaborationModeKind,
|
|
200
174
|
resolveSubagentHeaderValue,
|
|
@@ -278,8 +252,7 @@ function composeCodexSuccessRedirectUrl(tokens, options = {}) {
|
|
|
278
252
|
const port = options.port ?? OAUTH_PORT;
|
|
279
253
|
const idClaims = getOpenAIAuthClaims(tokens.id_token);
|
|
280
254
|
const accessClaims = getOpenAIAuthClaims(tokens.access_token);
|
|
281
|
-
const needsSetup = !getClaimBoolean(idClaims, "completed_platform_onboarding") &&
|
|
282
|
-
getClaimBoolean(idClaims, "is_org_owner");
|
|
255
|
+
const needsSetup = !getClaimBoolean(idClaims, "completed_platform_onboarding") && getClaimBoolean(idClaims, "is_org_owner");
|
|
283
256
|
const platformUrl = issuer === ISSUER ? "https://platform.openai.com" : "https://platform.api.openai.org";
|
|
284
257
|
const params = new URLSearchParams({
|
|
285
258
|
needs_setup: String(needsSetup),
|
|
@@ -375,254 +348,32 @@ function buildOAuthErrorHtml(error) {
|
|
|
375
348
|
</body>
|
|
376
349
|
</html>`;
|
|
377
350
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
351
|
+
const oauthServerController = createOAuthServerController({
|
|
352
|
+
port: OAUTH_PORT,
|
|
353
|
+
loopbackHost: OAUTH_LOOPBACK_HOST,
|
|
354
|
+
callbackOrigin: OAUTH_CALLBACK_ORIGIN,
|
|
355
|
+
callbackUri: OAUTH_CALLBACK_URI,
|
|
356
|
+
callbackPath: OAUTH_CALLBACK_PATH,
|
|
357
|
+
callbackTimeoutMs: OAUTH_CALLBACK_TIMEOUT_MS,
|
|
358
|
+
buildOAuthErrorHtml,
|
|
359
|
+
buildOAuthSuccessHtml,
|
|
360
|
+
composeCodexSuccessRedirectUrl,
|
|
361
|
+
exchangeCodeForTokens
|
|
362
|
+
});
|
|
381
363
|
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
|
-
}
|
|
364
|
+
return oauthServerController.isDebugEnabled();
|
|
433
365
|
}
|
|
434
366
|
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 };
|
|
367
|
+
return oauthServerController.start();
|
|
569
368
|
}
|
|
570
369
|
function stopOAuthServer() {
|
|
571
|
-
|
|
572
|
-
emitOAuthDebug("server_stopping", { hadPendingOAuth: Boolean(pendingOAuth) });
|
|
573
|
-
oauthServer?.close();
|
|
574
|
-
oauthServer = undefined;
|
|
575
|
-
emitOAuthDebug("server_stopped");
|
|
370
|
+
oauthServerController.stop();
|
|
576
371
|
}
|
|
577
372
|
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);
|
|
373
|
+
oauthServerController.scheduleStop(delayMs, reason);
|
|
589
374
|
}
|
|
590
375
|
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
|
-
});
|
|
376
|
+
return oauthServerController.waitForCallback(pkce, state, authMode);
|
|
626
377
|
}
|
|
627
378
|
function modeForRuntimeMode(runtimeMode) {
|
|
628
379
|
return runtimeMode === "native" ? "native" : "codex";
|
|
@@ -711,319 +462,38 @@ function rewriteUrl(requestInput) {
|
|
|
711
462
|
const parsed = requestInput instanceof URL
|
|
712
463
|
? requestInput
|
|
713
464
|
: new URL(typeof requestInput === "string" ? requestInput : requestInput.url);
|
|
714
|
-
if (parsed.pathname.includes("/v1/responses") ||
|
|
715
|
-
parsed.pathname.includes("/chat/completions")) {
|
|
465
|
+
if (parsed.pathname.includes("/v1/responses") || parsed.pathname.includes("/chat/completions")) {
|
|
716
466
|
return new URL(CODEX_API_ENDPOINT);
|
|
717
467
|
}
|
|
718
468
|
return parsed;
|
|
719
469
|
}
|
|
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")
|
|
470
|
+
function isAllowedOpenAIOutboundHost(hostname) {
|
|
471
|
+
const normalized = hostname.trim().toLowerCase();
|
|
472
|
+
if (!normalized)
|
|
934
473
|
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";
|
|
474
|
+
if (OPENAI_OUTBOUND_HOST_ALLOWLIST.has(normalized))
|
|
475
|
+
return true;
|
|
476
|
+
return normalized.endsWith(".openai.com") || normalized.endsWith(".chatgpt.com");
|
|
477
|
+
}
|
|
478
|
+
function assertAllowedOutboundUrl(url) {
|
|
479
|
+
const protocol = url.protocol.trim().toLowerCase();
|
|
480
|
+
if (protocol !== "https:") {
|
|
481
|
+
throw new PluginFatalError({
|
|
482
|
+
message: `Blocked outbound request with unsupported protocol "${protocol || "unknown"}". ` +
|
|
483
|
+
"This plugin only proxies HTTPS requests to OpenAI/ChatGPT backends.",
|
|
484
|
+
status: 400,
|
|
485
|
+
type: "disallowed_outbound_protocol",
|
|
486
|
+
param: "request"
|
|
487
|
+
});
|
|
1021
488
|
}
|
|
1022
|
-
if (
|
|
1023
|
-
return
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
489
|
+
if (isAllowedOpenAIOutboundHost(url.hostname))
|
|
490
|
+
return;
|
|
491
|
+
throw new PluginFatalError({
|
|
492
|
+
message: `Blocked outbound request to "${url.hostname}". ` + "This plugin only proxies OpenAI/ChatGPT backend traffic.",
|
|
493
|
+
status: 400,
|
|
494
|
+
type: "disallowed_outbound_host",
|
|
495
|
+
param: "request"
|
|
496
|
+
});
|
|
1027
497
|
}
|
|
1028
498
|
async function sessionUsesOpenAIProvider(client, sessionID) {
|
|
1029
499
|
const sessionApi = client?.session;
|
|
@@ -1040,9 +510,7 @@ async function sessionUsesOpenAIProvider(client, sessionID) {
|
|
|
1040
510
|
if (asString(info.role) !== "user")
|
|
1041
511
|
continue;
|
|
1042
512
|
const model = isRecord(info.model) ? info.model : undefined;
|
|
1043
|
-
const providerID = model
|
|
1044
|
-
? asString(model.providerID)
|
|
1045
|
-
: asString(info.providerID);
|
|
513
|
+
const providerID = model ? asString(model.providerID) : asString(info.providerID);
|
|
1046
514
|
if (!providerID)
|
|
1047
515
|
continue;
|
|
1048
516
|
return providerID === "openai";
|
|
@@ -1053,26 +521,11 @@ async function sessionUsesOpenAIProvider(client, sessionID) {
|
|
|
1053
521
|
}
|
|
1054
522
|
return false;
|
|
1055
523
|
}
|
|
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
524
|
function formatAccountLabel(account, index) {
|
|
1068
525
|
const email = account?.email?.trim();
|
|
1069
526
|
const plan = account?.plan?.trim();
|
|
1070
527
|
const accountId = account?.accountId?.trim();
|
|
1071
|
-
const idSuffix = accountId
|
|
1072
|
-
? accountId.length > 6
|
|
1073
|
-
? accountId.slice(-6)
|
|
1074
|
-
: accountId
|
|
1075
|
-
: null;
|
|
528
|
+
const idSuffix = accountId ? (accountId.length > 6 ? accountId.slice(-6) : accountId) : null;
|
|
1076
529
|
if (email && plan)
|
|
1077
530
|
return `${email} (${plan})`;
|
|
1078
531
|
if (email)
|
|
@@ -1082,7 +535,7 @@ function formatAccountLabel(account, index) {
|
|
|
1082
535
|
return `Account ${index + 1}`;
|
|
1083
536
|
}
|
|
1084
537
|
function hasActiveCooldown(account, now) {
|
|
1085
|
-
return typeof account.cooldownUntil === "number" && Number.isFinite(account.cooldownUntil) && account.cooldownUntil > now;
|
|
538
|
+
return (typeof account.cooldownUntil === "number" && Number.isFinite(account.cooldownUntil) && account.cooldownUntil > now);
|
|
1086
539
|
}
|
|
1087
540
|
function ensureAccountAuthTypes(account) {
|
|
1088
541
|
const normalized = normalizeAccountAuthTypes(account.authTypes);
|
|
@@ -1128,13 +581,12 @@ function buildAuthMenuAccounts(input) {
|
|
|
1128
581
|
const existing = rows.get(identity);
|
|
1129
582
|
const currentStatus = hasActiveCooldown(account, now)
|
|
1130
583
|
? "rate-limited"
|
|
1131
|
-
: typeof account.expires === "number" &&
|
|
1132
|
-
Number.isFinite(account.expires) &&
|
|
1133
|
-
account.expires <= now
|
|
584
|
+
: typeof account.expires === "number" && Number.isFinite(account.expires) && account.expires <= now
|
|
1134
585
|
? "expired"
|
|
1135
586
|
: "unknown";
|
|
1136
587
|
if (!existing) {
|
|
1137
|
-
const isCurrentAccount = authMode === input.activeMode &&
|
|
588
|
+
const isCurrentAccount = authMode === input.activeMode &&
|
|
589
|
+
Boolean(domain.activeIdentityKey && account.identityKey === domain.activeIdentityKey);
|
|
1138
590
|
rows.set(identity, {
|
|
1139
591
|
identityKey: account.identityKey,
|
|
1140
592
|
index: rows.size,
|
|
@@ -1150,8 +602,7 @@ function buildAuthMenuAccounts(input) {
|
|
|
1150
602
|
continue;
|
|
1151
603
|
}
|
|
1152
604
|
existing.authTypes = normalizeAccountAuthTypes([...(existing.authTypes ?? []), authMode]);
|
|
1153
|
-
if (typeof account.lastUsed === "number" &&
|
|
1154
|
-
(!existing.lastUsed || account.lastUsed > existing.lastUsed)) {
|
|
605
|
+
if (typeof account.lastUsed === "number" && (!existing.lastUsed || account.lastUsed > existing.lastUsed)) {
|
|
1155
606
|
existing.lastUsed = account.lastUsed;
|
|
1156
607
|
}
|
|
1157
608
|
if (existing.enabled === false && account.enabled !== false) {
|
|
@@ -1160,12 +611,11 @@ function buildAuthMenuAccounts(input) {
|
|
|
1160
611
|
if (existing.status !== "rate-limited" && currentStatus === "rate-limited") {
|
|
1161
612
|
existing.status = "rate-limited";
|
|
1162
613
|
}
|
|
1163
|
-
else if (existing.status !== "rate-limited" &&
|
|
1164
|
-
existing.status !== "expired" &&
|
|
1165
|
-
currentStatus === "expired") {
|
|
614
|
+
else if (existing.status !== "rate-limited" && existing.status !== "expired" && currentStatus === "expired") {
|
|
1166
615
|
existing.status = "expired";
|
|
1167
616
|
}
|
|
1168
|
-
const isCurrentAccount = authMode === input.activeMode &&
|
|
617
|
+
const isCurrentAccount = authMode === input.activeMode &&
|
|
618
|
+
Boolean(domain.activeIdentityKey && account.identityKey === domain.activeIdentityKey);
|
|
1169
619
|
if (isCurrentAccount) {
|
|
1170
620
|
existing.isCurrentAccount = true;
|
|
1171
621
|
existing.status = "active";
|
|
@@ -1177,9 +627,7 @@ function buildAuthMenuAccounts(input) {
|
|
|
1177
627
|
return Array.from(rows.values()).map((row, index) => ({ ...row, index }));
|
|
1178
628
|
}
|
|
1179
629
|
function hydrateAccountIdentityFromAccessClaims(account) {
|
|
1180
|
-
const claims = typeof account.access === "string" && account.access.length > 0
|
|
1181
|
-
? parseJwtClaims(account.access)
|
|
1182
|
-
: undefined;
|
|
630
|
+
const claims = typeof account.access === "string" && account.access.length > 0 ? parseJwtClaims(account.access) : undefined;
|
|
1183
631
|
if (!account.accountId)
|
|
1184
632
|
account.accountId = extractAccountIdFromClaims(claims);
|
|
1185
633
|
if (!account.email)
|
|
@@ -1193,35 +641,6 @@ function hydrateAccountIdentityFromAccessClaims(account) {
|
|
|
1193
641
|
ensureAccountAuthTypes(account);
|
|
1194
642
|
synchronizeIdentityKey(account);
|
|
1195
643
|
}
|
|
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
644
|
function isRecord(value) {
|
|
1226
645
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1227
646
|
}
|
|
@@ -1231,352 +650,9 @@ function asString(value) {
|
|
|
1231
650
|
const trimmed = value.trim();
|
|
1232
651
|
return trimmed ? trimmed : undefined;
|
|
1233
652
|
}
|
|
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, reason: "disabled" };
|
|
1524
|
-
const method = input.request.method.toUpperCase();
|
|
1525
|
-
if (method !== "POST")
|
|
1526
|
-
return { request: input.request, changed: false, reason: "non_post" };
|
|
1527
|
-
let payload;
|
|
1528
|
-
try {
|
|
1529
|
-
const raw = await input.request.clone().text();
|
|
1530
|
-
if (!raw)
|
|
1531
|
-
return { request: input.request, changed: false, reason: "empty_body" };
|
|
1532
|
-
payload = JSON.parse(raw);
|
|
1533
|
-
}
|
|
1534
|
-
catch {
|
|
1535
|
-
return { request: input.request, changed: false, reason: "invalid_json" };
|
|
1536
|
-
}
|
|
1537
|
-
if (!isRecord(payload))
|
|
1538
|
-
return { request: input.request, changed: false, reason: "non_object_body" };
|
|
1539
|
-
const modelSlugRaw = asString(payload.model);
|
|
1540
|
-
if (!modelSlugRaw)
|
|
1541
|
-
return { request: input.request, changed: false, reason: "missing_model" };
|
|
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, reason: "catalog_model_not_found" };
|
|
1559
|
-
const rendered = resolveInstructionsForModel(catalogModel, effectivePersonality);
|
|
1560
|
-
if (!rendered)
|
|
1561
|
-
return { request: input.request, changed: false, reason: "rendered_empty_or_unsafe" };
|
|
1562
|
-
if (asString(payload.instructions) === rendered) {
|
|
1563
|
-
return { request: input.request, changed: false, reason: "already_matches" };
|
|
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, reason: "updated" };
|
|
1575
|
-
}
|
|
1576
653
|
export async function CodexAuthPlugin(input, opts = {}) {
|
|
1577
654
|
opts.log?.debug("codex-native init");
|
|
1578
|
-
const spoofMode = opts.spoofMode === "codex" ||
|
|
1579
|
-
opts.spoofMode === "strict"
|
|
655
|
+
const spoofMode = opts.spoofMode === "codex" || opts.spoofMode === "strict"
|
|
1580
656
|
? "codex"
|
|
1581
657
|
: "native";
|
|
1582
658
|
const runtimeMode = opts.mode === "collab" || opts.mode === "codex" || opts.mode === "native"
|
|
@@ -1586,11 +662,15 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1586
662
|
: "native";
|
|
1587
663
|
const collabModeEnabled = runtimeMode === "collab";
|
|
1588
664
|
const authMode = modeForRuntimeMode(runtimeMode);
|
|
665
|
+
void refreshCodexClientVersionFromGitHub(opts.log).catch(() => { });
|
|
1589
666
|
const resolveCatalogHeaders = () => {
|
|
1590
667
|
const originator = resolveCodexOriginator(spoofMode);
|
|
668
|
+
const codexClientVersion = resolveCodexClientVersion();
|
|
1591
669
|
return {
|
|
1592
670
|
originator,
|
|
1593
671
|
userAgent: resolveRequestUserAgent(spoofMode, originator),
|
|
672
|
+
clientVersion: codexClientVersion,
|
|
673
|
+
versionHeader: codexClientVersion,
|
|
1594
674
|
...(spoofMode === "native" ? { openaiBeta: "responses=experimental" } : {})
|
|
1595
675
|
};
|
|
1596
676
|
};
|
|
@@ -1599,6 +679,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1599
679
|
log: opts.log
|
|
1600
680
|
});
|
|
1601
681
|
let lastCatalogModels;
|
|
682
|
+
const quotaFetchCooldownByIdentity = new Map();
|
|
1602
683
|
const showToast = async (message, variant = "info", quietMode = false) => {
|
|
1603
684
|
if (quietMode)
|
|
1604
685
|
return;
|
|
@@ -1616,6 +697,8 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1616
697
|
};
|
|
1617
698
|
const refreshQuotaSnapshotsForAuthMenu = async () => {
|
|
1618
699
|
const auth = await loadAuthStorage();
|
|
700
|
+
const snapshotPath = defaultSnapshotsPath();
|
|
701
|
+
const existingSnapshots = await loadSnapshots(snapshotPath).catch(() => ({}));
|
|
1619
702
|
const snapshotUpdates = {};
|
|
1620
703
|
for (const { mode, domain } of listOpenAIOAuthDomains(auth)) {
|
|
1621
704
|
for (let index = 0; index < domain.accounts.length; index += 1) {
|
|
@@ -1623,11 +706,22 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1623
706
|
if (!account || account.enabled === false)
|
|
1624
707
|
continue;
|
|
1625
708
|
hydrateAccountIdentityFromAccessClaims(account);
|
|
1626
|
-
|
|
709
|
+
const identityKey = account.identityKey;
|
|
1627
710
|
const now = Date.now();
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
711
|
+
if (identityKey) {
|
|
712
|
+
const cooldownUntil = quotaFetchCooldownByIdentity.get(identityKey);
|
|
713
|
+
if (typeof cooldownUntil === "number" && cooldownUntil > now)
|
|
714
|
+
continue;
|
|
715
|
+
const existing = existingSnapshots[identityKey];
|
|
716
|
+
if (existing &&
|
|
717
|
+
typeof existing.updatedAt === "number" &&
|
|
718
|
+
Number.isFinite(existing.updatedAt) &&
|
|
719
|
+
now - existing.updatedAt < AUTH_MENU_QUOTA_SNAPSHOT_TTL_MS) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
let accessToken = typeof account.access === "string" && account.access.length > 0 ? account.access : undefined;
|
|
724
|
+
const expired = typeof account.expires === "number" && Number.isFinite(account.expires) && account.expires <= now;
|
|
1631
725
|
if ((!accessToken || expired) && account.refresh) {
|
|
1632
726
|
try {
|
|
1633
727
|
await saveAuthStorage(undefined, async (authFile) => {
|
|
@@ -1658,6 +752,9 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1658
752
|
});
|
|
1659
753
|
}
|
|
1660
754
|
catch (error) {
|
|
755
|
+
if (identityKey) {
|
|
756
|
+
quotaFetchCooldownByIdentity.set(identityKey, Date.now() + AUTH_MENU_QUOTA_FAILURE_COOLDOWN_MS);
|
|
757
|
+
}
|
|
1661
758
|
opts.log?.debug("quota check refresh failed", {
|
|
1662
759
|
index,
|
|
1663
760
|
mode,
|
|
@@ -1678,16 +775,20 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1678
775
|
now: Date.now(),
|
|
1679
776
|
modelFamily: "gpt-5.3-codex",
|
|
1680
777
|
userAgent: resolveRequestUserAgent(spoofMode, resolveCodexOriginator(spoofMode)),
|
|
1681
|
-
log: opts.log
|
|
778
|
+
log: opts.log,
|
|
779
|
+
timeoutMs: AUTH_MENU_QUOTA_FETCH_TIMEOUT_MS
|
|
1682
780
|
});
|
|
1683
|
-
if (!snapshot)
|
|
781
|
+
if (!snapshot) {
|
|
782
|
+
quotaFetchCooldownByIdentity.set(account.identityKey, Date.now() + AUTH_MENU_QUOTA_FAILURE_COOLDOWN_MS);
|
|
1684
783
|
continue;
|
|
784
|
+
}
|
|
785
|
+
quotaFetchCooldownByIdentity.delete(account.identityKey);
|
|
1685
786
|
snapshotUpdates[account.identityKey] = snapshot;
|
|
1686
787
|
}
|
|
1687
788
|
}
|
|
1688
789
|
if (Object.keys(snapshotUpdates).length === 0)
|
|
1689
790
|
return;
|
|
1690
|
-
await saveSnapshots(
|
|
791
|
+
await saveSnapshots(snapshotPath, (current) => ({
|
|
1691
792
|
...current,
|
|
1692
793
|
...snapshotUpdates
|
|
1693
794
|
}));
|
|
@@ -1711,7 +812,10 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1711
812
|
handlers: {
|
|
1712
813
|
onCheckQuotas: async () => {
|
|
1713
814
|
await refreshQuotaSnapshotsForAuthMenu();
|
|
1714
|
-
const report = await toolOutputForStatus(
|
|
815
|
+
const report = await toolOutputForStatus(undefined, undefined, {
|
|
816
|
+
style: "menu",
|
|
817
|
+
useColor: shouldUseColor()
|
|
818
|
+
});
|
|
1715
819
|
process.stdout.write(`\n${report}\n\n`);
|
|
1716
820
|
},
|
|
1717
821
|
onConfigureModels: async () => {
|
|
@@ -1779,9 +883,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1779
883
|
},
|
|
1780
884
|
onToggleAccount: async (account) => {
|
|
1781
885
|
await saveAuthStorage(undefined, (authFile) => {
|
|
1782
|
-
const authTypes = account.authTypes && account.authTypes.length > 0
|
|
1783
|
-
? [...account.authTypes]
|
|
1784
|
-
: ["native"];
|
|
886
|
+
const authTypes = account.authTypes && account.authTypes.length > 0 ? [...account.authTypes] : ["native"];
|
|
1785
887
|
for (const mode of authTypes) {
|
|
1786
888
|
const domain = getOpenAIOAuthDomain(authFile, mode);
|
|
1787
889
|
if (!domain)
|
|
@@ -1893,7 +995,9 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1893
995
|
if (!hasOAuth)
|
|
1894
996
|
return {};
|
|
1895
997
|
const sessionAffinityPath = defaultSessionAffinityPath();
|
|
1896
|
-
const loadedSessionAffinity = await loadSessionAffinity(sessionAffinityPath).catch(() => ({
|
|
998
|
+
const loadedSessionAffinity = await loadSessionAffinity(sessionAffinityPath).catch(() => ({
|
|
999
|
+
version: 1
|
|
1000
|
+
}));
|
|
1897
1001
|
const initialSessionAffinity = readSessionAffinitySnapshot(loadedSessionAffinity, authMode);
|
|
1898
1002
|
const sessionExists = createSessionExistsFn(process.env);
|
|
1899
1003
|
await pruneSessionAffinitySnapshot(initialSessionAffinity, sessionExists, {
|
|
@@ -1965,9 +1069,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
1965
1069
|
if (opts.headerTransformDebug === true) {
|
|
1966
1070
|
await requestSnapshots.captureRequest("before-header-transform", baseRequest, {
|
|
1967
1071
|
spoofMode,
|
|
1968
|
-
...(inboundCollaborationModeKind
|
|
1969
|
-
? { collaborationModeKind: inboundCollaborationModeKind }
|
|
1970
|
-
: {})
|
|
1072
|
+
...(inboundCollaborationModeKind ? { collaborationModeKind: inboundCollaborationModeKind } : {})
|
|
1971
1073
|
});
|
|
1972
1074
|
}
|
|
1973
1075
|
let outbound = new Request(rewriteUrl(baseRequest), baseRequest);
|
|
@@ -2140,8 +1242,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2140
1242
|
tokens = await refreshAccessToken(selected.refresh);
|
|
2141
1243
|
}
|
|
2142
1244
|
catch (error) {
|
|
2143
|
-
if (isOAuthTokenRefreshError(error) &&
|
|
2144
|
-
error.oauthCode?.toLowerCase() === "invalid_grant") {
|
|
1245
|
+
if (isOAuthTokenRefreshError(error) && error.oauthCode?.toLowerCase() === "invalid_grant") {
|
|
2145
1246
|
sawInvalidGrant = true;
|
|
2146
1247
|
selected.enabled = false;
|
|
2147
1248
|
delete selected.cooldownUntil;
|
|
@@ -2328,6 +1429,20 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2328
1429
|
sanitized: sanitizedOutbound.changed,
|
|
2329
1430
|
...(collaborationModeKind ? { collaborationModeKind } : {})
|
|
2330
1431
|
});
|
|
1432
|
+
try {
|
|
1433
|
+
assertAllowedOutboundUrl(new URL(sanitizedOutbound.request.url));
|
|
1434
|
+
}
|
|
1435
|
+
catch (error) {
|
|
1436
|
+
if (isPluginFatalError(error)) {
|
|
1437
|
+
return toSyntheticErrorResponse(error);
|
|
1438
|
+
}
|
|
1439
|
+
return toSyntheticErrorResponse(new PluginFatalError({
|
|
1440
|
+
message: "Outbound request validation failed before sending to OpenAI backend.",
|
|
1441
|
+
status: 400,
|
|
1442
|
+
type: "disallowed_outbound_request",
|
|
1443
|
+
param: "request"
|
|
1444
|
+
}));
|
|
1445
|
+
}
|
|
2331
1446
|
let response;
|
|
2332
1447
|
try {
|
|
2333
1448
|
response = await orchestrator.execute(sanitizedOutbound.request);
|
|
@@ -2407,7 +1522,6 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2407
1522
|
return null;
|
|
2408
1523
|
}
|
|
2409
1524
|
finally {
|
|
2410
|
-
pendingOAuth = undefined;
|
|
2411
1525
|
scheduleOAuthServerStop(authFailed ? OAUTH_SERVER_SHUTDOWN_ERROR_GRACE_MS : OAUTH_SERVER_SHUTDOWN_GRACE_MS, authFailed ? "error" : "success");
|
|
2412
1526
|
}
|
|
2413
1527
|
};
|
|
@@ -2445,10 +1559,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2445
1559
|
};
|
|
2446
1560
|
}
|
|
2447
1561
|
};
|
|
2448
|
-
if (inputs &&
|
|
2449
|
-
process.env.OPENCODE_NO_BROWSER !== "1" &&
|
|
2450
|
-
process.stdin.isTTY &&
|
|
2451
|
-
process.stdout.isTTY) {
|
|
1562
|
+
if (inputs && process.env.OPENCODE_NO_BROWSER !== "1" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
2452
1563
|
return runInteractiveBrowserAuthLoop();
|
|
2453
1564
|
}
|
|
2454
1565
|
const { redirectUri } = await startOAuthServer();
|
|
@@ -2473,7 +1584,6 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2473
1584
|
return { type: "failed" };
|
|
2474
1585
|
}
|
|
2475
1586
|
finally {
|
|
2476
|
-
pendingOAuth = undefined;
|
|
2477
1587
|
scheduleOAuthServerStop(authFailed ? OAUTH_SERVER_SHUTDOWN_ERROR_GRACE_MS : OAUTH_SERVER_SHUTDOWN_GRACE_MS, authFailed ? "error" : "success");
|
|
2478
1588
|
}
|
|
2479
1589
|
}
|
|
@@ -2557,9 +1667,8 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2557
1667
|
},
|
|
2558
1668
|
"chat.message": async (hookInput, output) => {
|
|
2559
1669
|
const directProviderID = hookInput.model?.providerID;
|
|
2560
|
-
const isOpenAI = directProviderID === "openai"
|
|
2561
|
-
|
|
2562
|
-
&& (await sessionUsesOpenAIProvider(input.client, hookInput.sessionID)));
|
|
1670
|
+
const isOpenAI = directProviderID === "openai" ||
|
|
1671
|
+
(directProviderID === undefined && (await sessionUsesOpenAIProvider(input.client, hookInput.sessionID)));
|
|
2563
1672
|
if (!isOpenAI)
|
|
2564
1673
|
return;
|
|
2565
1674
|
for (const part of output.parts) {
|
|
@@ -2575,9 +1684,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2575
1684
|
if (hookInput.model.providerID !== "openai")
|
|
2576
1685
|
return;
|
|
2577
1686
|
const initialReasoningEffort = asString(output.options.reasoningEffort);
|
|
2578
|
-
const collaborationProfile = collabModeEnabled
|
|
2579
|
-
? resolveCollaborationProfile(hookInput.agent)
|
|
2580
|
-
: { enabled: false };
|
|
1687
|
+
const collaborationProfile = collabModeEnabled ? resolveCollaborationProfile(hookInput.agent) : { enabled: false };
|
|
2581
1688
|
const modelOptions = isRecord(hookInput.model.options) ? hookInput.model.options : {};
|
|
2582
1689
|
const modelCandidates = getModelLookupCandidates({
|
|
2583
1690
|
id: hookInput.model.id,
|
|
@@ -2633,7 +1740,12 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2633
1740
|
});
|
|
2634
1741
|
if (collabModeEnabled && collaborationProfile.enabled && collaborationProfile.kind) {
|
|
2635
1742
|
const collaborationModeKind = collaborationProfile.kind;
|
|
2636
|
-
const collaborationInstructions = resolveCollaborationInstructions(collaborationModeKind
|
|
1743
|
+
const collaborationInstructions = resolveCollaborationInstructions(collaborationModeKind, {
|
|
1744
|
+
plan: CODEX_PLAN_MODE_INSTRUCTIONS,
|
|
1745
|
+
code: CODEX_CODE_MODE_INSTRUCTIONS,
|
|
1746
|
+
execute: CODEX_EXECUTE_MODE_INSTRUCTIONS,
|
|
1747
|
+
pairProgramming: CODEX_PAIR_PROGRAMMING_MODE_INSTRUCTIONS
|
|
1748
|
+
});
|
|
2637
1749
|
const mergedInstructions = mergeInstructions(asString(output.options.instructions), collaborationInstructions);
|
|
2638
1750
|
if (mergedInstructions) {
|
|
2639
1751
|
output.options.instructions = mergedInstructions;
|
|
@@ -2651,9 +1763,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
|
|
|
2651
1763
|
"chat.headers": async (hookInput, output) => {
|
|
2652
1764
|
if (hookInput.model.providerID !== "openai")
|
|
2653
1765
|
return;
|
|
2654
|
-
const collaborationProfile = collabModeEnabled
|
|
2655
|
-
? resolveCollaborationProfile(hookInput.agent)
|
|
2656
|
-
: { enabled: false };
|
|
1766
|
+
const collaborationProfile = collabModeEnabled ? resolveCollaborationProfile(hookInput.agent) : { enabled: false };
|
|
2657
1767
|
const collaborationModeKind = collaborationProfile.enabled ? collaborationProfile.kind : undefined;
|
|
2658
1768
|
const originator = resolveCodexOriginator(spoofMode);
|
|
2659
1769
|
output.headers.originator = originator;
|