From 8fd5d8f8eb104aab444114e9347a0983630f6f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=8A=80=E8=89=B2=E9=A3=9B=E8=A1=8C=E7=8C=B9?= Date: Mon, 1 Jun 2026 23:31:43 +0800 Subject: [PATCH 1/4] feat(inject): add per-thread endpoint/auth override with header UI --- assets/inject/renderer-inject.js | 433 +++++++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) diff --git a/assets/inject/renderer-inject.js b/assets/inject/renderer-inject.js index d7dbda2..5216dc4 100644 --- a/assets/inject/renderer-inject.js +++ b/assets/inject/renderer-inject.js @@ -61,6 +61,14 @@ const codexThreadServiceTierMaxEntries = 120; const codexThreadServiceTierDraftBindWindowMs = 60 * 1000; const codexServiceTierRequestOverrideVersion = "2"; + const codexThreadEndpointAuthRequestOverrideVersion = "1"; + const codexThreadEndpointAuthStorageKey = "codexThreadEndpointAuthOverrides"; + const codexThreadEndpointAuthCryptoVersion = "xor-v1"; + const codexThreadEndpointAuthSeedSalt = "codex++-per-thread-endpoint-auth"; + const codexThreadEndpointAuthButtonAttr = "data-codex-thread-endpoint-auth-button"; + const codexThreadEndpointAuthStyleId = "codex-thread-endpoint-auth-style"; + const codexThreadEndpointAuthOverlayId = "codex-thread-endpoint-auth-overlay"; + const codexThreadEndpointAuthPanelId = "codex-thread-endpoint-auth-panel"; const codexAppServerModelRequestPatchVersion = "1"; const codexThreadScrollMaxEntries = 120; const codexThreadScrollSaveThrottleMs = 120; @@ -987,6 +995,12 @@ const codexDefaultServiceTierSetting = { key: "default-service-tier", default: null }; const codexServiceTierFallbackFastValue = "priority"; const codexServiceTierModulePromises = new Map(); + const codexThreadEndpointAuthState = { + map: null, + observer: null, + scanTimer: 0, + initialized: false, + }; const codexThreadServiceTierModes = new Set(["inherit", "standard", "fast"]); const codexServiceTierControlModes = new Set(["inherit", "global-standard", "global-fast", "custom"]); @@ -1509,6 +1523,414 @@ return message; } + function codexThreadEndpointAuthNormalizeText(value) { + return String(value || "").trim(); + } + + function codexThreadEndpointAuthNormalizeBaseUrl(value) { + const text = codexThreadEndpointAuthNormalizeText(value).replace(/\/+$/, ""); + if (!text) return ""; + return /^https?:\/\//i.test(text) ? text : ""; + } + + function codexThreadEndpointAuthFNV1A32(text) { + let hash = 0x811c9dc5; + for (let index = 0; index < text.length; index += 1) { + hash ^= text.charCodeAt(index); + hash = (hash * 0x01000193) >>> 0; + } + return hash >>> 0; + } + + function codexThreadEndpointAuthSeed(threadId) { + const host = window.location.host || "codex.local"; + return codexThreadEndpointAuthFNV1A32(`${codexThreadEndpointAuthSeedSalt}|${host}|${threadId || ""}`) || 0x9e3779b9; + } + + function codexThreadEndpointAuthXorBytes(inputBytes, threadId) { + const output = new Uint8Array(inputBytes.length); + let seed = codexThreadEndpointAuthSeed(threadId) >>> 0; + for (let index = 0; index < inputBytes.length; index += 1) { + seed ^= seed << 13; + seed ^= seed >>> 17; + seed ^= seed << 5; + output[index] = inputBytes[index] ^ (seed & 0xff); + } + return output; + } + + function codexThreadEndpointAuthEncryptApiKey(apiKey, threadId) { + const plain = codexThreadEndpointAuthNormalizeText(apiKey); + if (!plain) return ""; + const bytes = new TextEncoder().encode(plain); + const encrypted = codexThreadEndpointAuthXorBytes(bytes, threadId); + let binary = ""; + encrypted.forEach((value) => { + binary += String.fromCharCode(value); + }); + return btoa(binary); + } + + function codexThreadEndpointAuthDecryptApiKey(apiKeyEnc, threadId) { + const text = codexThreadEndpointAuthNormalizeText(apiKeyEnc); + if (!text) return ""; + try { + const binary = atob(text); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index); + const plainBytes = codexThreadEndpointAuthXorBytes(bytes, threadId); + return codexThreadEndpointAuthNormalizeText(new TextDecoder().decode(plainBytes)); + } catch (_) { + return ""; + } + } + + function codexThreadEndpointAuthNowIso() { + return new Date().toISOString(); + } + + function codexThreadEndpointAuthReadMap() { + try { + const parsed = JSON.parse(localStorage.getItem(codexThreadEndpointAuthStorageKey) || "{}"); + const output = Object.create(null); + Object.entries(parsed || {}).forEach(([threadId, value]) => { + const safeThreadId = validThreadScrollSessionKey(threadId); + if (!safeThreadId || !value || typeof value !== "object") return; + const baseUrl = codexThreadEndpointAuthNormalizeBaseUrl(value.baseUrl || ""); + const apiKeyPlain = codexThreadEndpointAuthNormalizeText(value.apiKey || ""); + const apiKeyEnc = codexThreadEndpointAuthNormalizeText(value.apiKeyEnc || ""); + const crypto = codexThreadEndpointAuthNormalizeText(value.crypto || ""); + const apiKey = apiKeyPlain || (crypto === codexThreadEndpointAuthCryptoVersion ? codexThreadEndpointAuthDecryptApiKey(apiKeyEnc, safeThreadId) : ""); + if (!baseUrl && !apiKey) return; + output[safeThreadId] = { + baseUrl, + apiKey, + updatedAt: codexThreadEndpointAuthNormalizeText(value.updatedAt) || codexThreadEndpointAuthNowIso(), + }; + }); + return output; + } catch (_) { + return Object.create(null); + } + } + + function codexThreadEndpointAuthPersistMap(map) { + const persisted = Object.create(null); + Object.entries(map || {}).forEach(([threadId, value]) => { + const safeThreadId = validThreadScrollSessionKey(threadId); + if (!safeThreadId || !value || typeof value !== "object") return; + const baseUrl = codexThreadEndpointAuthNormalizeBaseUrl(value.baseUrl || ""); + const apiKey = codexThreadEndpointAuthNormalizeText(value.apiKey || ""); + if (!baseUrl && !apiKey) return; + persisted[safeThreadId] = { + baseUrl, + apiKeyEnc: codexThreadEndpointAuthEncryptApiKey(apiKey, safeThreadId), + crypto: codexThreadEndpointAuthCryptoVersion, + updatedAt: codexThreadEndpointAuthNormalizeText(value.updatedAt) || codexThreadEndpointAuthNowIso(), + }; + }); + localStorage.setItem(codexThreadEndpointAuthStorageKey, JSON.stringify(persisted)); + } + + function codexThreadEndpointAuthMap() { + if (!codexThreadEndpointAuthState.map) codexThreadEndpointAuthState.map = codexThreadEndpointAuthReadMap(); + return codexThreadEndpointAuthState.map; + } + + function codexThreadEndpointAuthSaveMap(nextMap) { + codexThreadEndpointAuthState.map = nextMap || Object.create(null); + codexThreadEndpointAuthPersistMap(codexThreadEndpointAuthState.map); + } + + function codexThreadEndpointAuthDrop(threadId) { + const safeThreadId = validThreadScrollSessionKey(threadId); + if (!safeThreadId) return false; + const map = { ...codexThreadEndpointAuthMap() }; + if (!Object.prototype.hasOwnProperty.call(map, safeThreadId)) return false; + delete map[safeThreadId]; + codexThreadEndpointAuthSaveMap(map); + return true; + } + + function codexThreadEndpointAuthCurrentThreadId() { + return validThreadScrollSessionKey(currentSessionRef().session_id) + || validThreadScrollSessionKey(locationThreadId()) + || ""; + } + + function codexThreadEndpointAuthEntryForRequest(params = {}, threadIdHint = "") { + const threadId = validThreadScrollSessionKey(params.threadId || params.conversationId || threadIdHint || codexThreadEndpointAuthCurrentThreadId()); + if (!threadId) return null; + const map = codexThreadEndpointAuthMap(); + const entry = map[threadId]; + if (!entry || (!entry.baseUrl && !entry.apiKey)) return null; + return { threadId, entry }; + } + + function codexThreadEndpointAuthApply(method, params, threadIdHint = "") { + if (!["thread/start", "thread/resume", "turn/start"].includes(String(method || ""))) return params; + if (!params || typeof params !== "object") return params; + const found = codexThreadEndpointAuthEntryForRequest(params, threadIdHint); + if (!found) return params; + const { threadId, entry } = found; + const nextParams = { ...(params || {}) }; + if (entry.baseUrl) nextParams.baseUrl = entry.baseUrl; + if (entry.apiKey) { + nextParams.apiKey = entry.apiKey; + nextParams.api_key = entry.apiKey; + nextParams.experimentalApiKey = entry.apiKey; + nextParams.experimental_bearer_token = entry.apiKey; + nextParams.bearerToken = entry.apiKey; + nextParams.authToken = entry.apiKey; + } + sendCodexPlusDiagnostic("thread_endpoint_auth_request_override_applied", { + method: String(method || ""), + threadId, + hasBaseUrl: !!entry.baseUrl, + hasApiKey: !!entry.apiKey, + }); + return nextParams; + } + + function codexThreadEndpointAuthRequestOverride(message) { + if (!message || typeof message !== "object") return message; + if (message.type === "send-cli-request-for-host") { + const method = String(message.method || ""); + const params = codexThreadEndpointAuthApply(method, message.params); + return params === message.params ? message : { ...message, params }; + } + if (message.type === "mcp-request" && message.request && typeof message.request === "object") { + const method = String(message.request.method || ""); + const params = codexThreadEndpointAuthApply(method, message.request.params); + if (params === message.request.params) return message; + return { ...message, request: { ...message.request, params } }; + } + if (message.type === "worker-request" && message.request && typeof message.request === "object") { + const method = String(message.request.method || ""); + const params = codexThreadEndpointAuthApply(method, message.request.params); + if (params === message.request.params) return message; + return { ...message, request: { ...message.request, params } }; + } + if (message.type === "thread-prewarm-start" && message.request && typeof message.request === "object") { + const params = codexThreadEndpointAuthApply("thread/start", message.request.params); + if (params === message.request.params) return message; + return { ...message, request: { ...message.request, params } }; + } + if (message.type === "prewarm-thread-start-for-host" && message.params && typeof message.params === "object") { + const params = codexThreadEndpointAuthApply("thread/start", message.params); + return params === message.params ? message : { ...message, params }; + } + if (message.type === "start-thread-for-host") { + const params = codexThreadEndpointAuthApply("thread/start", message); + return params === message ? message : params; + } + if (message.type === "start-turn-for-host" && message.params && typeof message.params === "object") { + const params = codexThreadEndpointAuthApply("turn/start", message.params, message.conversationId); + return params === message.params ? message : { ...message, params }; + } + return message; + } + + function installCodexThreadEndpointAuthDispatcherPatch() { + if (window.__codexThreadEndpointAuthRequestOverrideInstalled === codexThreadEndpointAuthRequestOverrideVersion) return; + const patch = async () => { + try { + const module = await loadCodexAppModule("setting-storage-"); + const dispatcherClass = typeof module.v === "function" && String(module.v).includes("dispatchMessage") ? module.v : null; + const dispatcher = dispatcherClass?.getInstance?.(); + if (!dispatcher || typeof dispatcher.dispatchMessage !== "function") throw new Error("Codex dispatcher unavailable"); + if (dispatcher.__codexThreadEndpointAuthOriginalDispatchMessage) { + window.__codexThreadEndpointAuthRequestOverrideInstalled = codexThreadEndpointAuthRequestOverrideVersion; + return; + } + dispatcher.__codexThreadEndpointAuthOriginalDispatchMessage = dispatcher.dispatchMessage.bind(dispatcher); + dispatcher.dispatchMessage = (type, payload) => { + const message = codexThreadEndpointAuthRequestOverride({ ...(payload || {}), type }); + const nextType = message?.type || type; + const { type: _type, ...nextPayload } = message || {}; + return dispatcher.__codexThreadEndpointAuthOriginalDispatchMessage(nextType, nextPayload); + }; + window.__codexThreadEndpointAuthRequestOverrideInstalled = codexThreadEndpointAuthRequestOverrideVersion; + sendCodexPlusDiagnostic("thread_endpoint_auth_dispatcher_patch_installed", {}); + } catch (error) { + sendCodexPlusDiagnostic("thread_endpoint_auth_dispatcher_patch_failed", { + errorName: error?.name || "", + errorMessage: error?.message || String(error), + }); + } + }; + void patch(); + } + + function codexThreadEndpointAuthDeleteHook() { + const original = window.__codexSessionDeleteBridge; + if (typeof original !== "function" || original.__codexThreadEndpointAuthDeleteWrapped) return; + const wrapped = async (path, payload, ...rest) => { + if (String(path || "") === "/delete") { + const threadId = validThreadScrollSessionKey( + payload?.session_id + || payload?.sessionId + || payload?.thread_id + || payload?.threadId + || payload?.conversation_id + || payload?.conversationId + || "", + ); + if (threadId) codexThreadEndpointAuthDrop(threadId); + } + return original(path, payload, ...rest); + }; + wrapped.__codexThreadEndpointAuthDeleteWrapped = true; + window.__codexSessionDeleteBridge = wrapped; + } + + function codexThreadEndpointAuthEnsureStyle() { + const existing = document.getElementById(codexThreadEndpointAuthStyleId); + if (existing?.dataset.version === "1") return; + existing?.remove(); + const style = document.createElement("style"); + style.id = codexThreadEndpointAuthStyleId; + style.dataset.version = "1"; + style.textContent = ` + [${codexThreadEndpointAuthButtonAttr}="1"] { + margin-left: 8px; + border: 1px solid rgba(148,163,184,.35); + border-radius: 8px; + background: rgba(15,23,42,.04); + color: inherit; + font: 12px/1.2 ui-sans-serif,system-ui; + padding: 4px 8px; + cursor: pointer; + } + #${codexThreadEndpointAuthOverlayId} { + position: fixed; + inset: 0; + z-index: 2147483200; + background: rgba(0,0,0,.35); + display: flex; + align-items: center; + justify-content: center; + } + #${codexThreadEndpointAuthPanelId} { + width: min(520px, calc(100vw - 24px)); + border: 1px solid rgba(148,163,184,.3); + border-radius: 12px; + background: #fff; + color: #111827; + padding: 14px; + font: 13px/1.4 ui-sans-serif,system-ui; + } + #${codexThreadEndpointAuthPanelId} h3 { margin: 0 0 10px; font-size: 15px; } + #${codexThreadEndpointAuthPanelId} .row { margin: 8px 0; } + #${codexThreadEndpointAuthPanelId} label { display: block; margin-bottom: 4px; color: #4b5563; } + #${codexThreadEndpointAuthPanelId} input { + width: 100%; box-sizing: border-box; border: 1px solid #d1d5db; + border-radius: 8px; padding: 8px 10px; font: 13px ui-sans-serif,system-ui; + } + #${codexThreadEndpointAuthPanelId} .actions { margin-top: 12px; display: flex; justify-content: flex-end; gap: 8px; } + #${codexThreadEndpointAuthPanelId} button { + border: 1px solid #d1d5db; border-radius: 8px; background: #fff; + padding: 7px 10px; cursor: pointer; + } + #${codexThreadEndpointAuthPanelId} button.primary { border-color: #2563eb; background: #2563eb; color: #fff; } + #${codexThreadEndpointAuthPanelId} .hint { margin-top: 8px; color: #6b7280; font-size: 12px; } + #${codexThreadEndpointAuthPanelId} .danger { color: #b91c1c; } + `; + document.documentElement.appendChild(style); + } + + function codexThreadEndpointAuthRemovePanel() { + document.getElementById(codexThreadEndpointAuthOverlayId)?.remove(); + } + + function codexThreadEndpointAuthShowPanel() { + codexThreadEndpointAuthRemovePanel(); + const threadId = codexThreadEndpointAuthCurrentThreadId(); + if (!threadId) { + showToast("未识别当前对话,请先打开一个具体会话", null); + return; + } + const existing = codexThreadEndpointAuthMap()[threadId] || { baseUrl: "", apiKey: "" }; + const hasSavedApiKey = !!codexThreadEndpointAuthNormalizeText(existing.apiKey); + const overlay = document.createElement("div"); + overlay.id = codexThreadEndpointAuthOverlayId; + overlay.innerHTML = ` + + `; + overlay.addEventListener("click", (event) => { + if (event.target === overlay) codexThreadEndpointAuthRemovePanel(); + }); + document.body.appendChild(overlay); + const baseInput = document.getElementById(`${codexThreadEndpointAuthPanelId}-base`); + const keyInput = document.getElementById(`${codexThreadEndpointAuthPanelId}-key`); + document.getElementById(`${codexThreadEndpointAuthPanelId}-cancel`)?.addEventListener("click", codexThreadEndpointAuthRemovePanel); + document.getElementById(`${codexThreadEndpointAuthPanelId}-delete`)?.addEventListener("click", () => { + codexThreadEndpointAuthDrop(threadId); + codexThreadEndpointAuthRemovePanel(); + }); + document.getElementById(`${codexThreadEndpointAuthPanelId}-save`)?.addEventListener("click", () => { + const baseUrl = codexThreadEndpointAuthNormalizeBaseUrl(baseInput?.value || ""); + const enteredApiKey = codexThreadEndpointAuthNormalizeText(keyInput?.value || ""); + const apiKey = enteredApiKey || (hasSavedApiKey ? existing.apiKey : ""); + if (!baseUrl && !apiKey) { + showToast("baseUrl 和 apiKey 至少填写一项", null); + return; + } + if ((baseInput?.value || "").trim() && !baseUrl) { + showToast("baseUrl 必须是 http/https URL", null); + return; + } + const map = { ...codexThreadEndpointAuthMap() }; + map[threadId] = { baseUrl, apiKey, updatedAt: codexThreadEndpointAuthNowIso() }; + codexThreadEndpointAuthSaveMap(map); + codexThreadEndpointAuthRemovePanel(); + showToast("Thread API 配置已保存", null); + }); + } + + function installCodexThreadEndpointAuthButton() { + codexThreadEndpointAuthEnsureStyle(); + const mount = document.querySelector(selectors.appHeader) || document.querySelector("header"); + if (!mount || mount.querySelector(`[${codexThreadEndpointAuthButtonAttr}="1"]`)) return; + const button = document.createElement("button"); + button.type = "button"; + button.setAttribute(codexThreadEndpointAuthButtonAttr, "1"); + button.textContent = "Thread API"; + button.title = "为当前对话设置 baseUrl / apiKey"; + button.addEventListener("click", codexThreadEndpointAuthShowPanel); + mount.appendChild(button); + } + + function installCodexThreadEndpointAuthRuntime() { + if (codexThreadEndpointAuthState.initialized) return; + codexThreadEndpointAuthState.initialized = true; + codexThreadEndpointAuthMap(); + codexThreadEndpointAuthPersistMap(codexThreadEndpointAuthMap()); + installCodexThreadEndpointAuthDispatcherPatch(); + window.__codexThreadEndpointAuth = { + version: codexThreadEndpointAuthRequestOverrideVersion, + getMap: () => ({ ...codexThreadEndpointAuthMap() }), + dropThreadConfig: (threadId) => codexThreadEndpointAuthDrop(threadId), + }; + } + function installCodexServiceTierDispatcherPatch() { if (window.__codexServiceTierRequestOverrideInstalled === codexServiceTierRequestOverrideVersion) return; const patch = async () => { @@ -1540,6 +1962,14 @@ void patch(); } + window.removeEventListener("codex-thread-endpoint-auth-delete-hook", window.__codexThreadEndpointAuthDeleteHookHandler, true); + window.__codexThreadEndpointAuthDeleteHookHandler = () => { + try { + codexThreadEndpointAuthDeleteHook(); + } catch (_) {} + }; + window.addEventListener("codex-thread-endpoint-auth-delete-hook", window.__codexThreadEndpointAuthDeleteHookHandler, true); + async function loadBackendSettings() { try { const settings = await postJson("/settings/get", {}); @@ -6724,6 +7154,9 @@ function scanLightweight() { installStyle(); installCodexServiceTierDispatcherPatch(); + installCodexThreadEndpointAuthRuntime(); + codexThreadEndpointAuthDeleteHook(); + installCodexThreadEndpointAuthButton(); installCodexPlusMenu(); scheduleBackendHeartbeat(); installDeleteButtonEventDelegation(); From 0d271a54a2565edac3844edf33df71a171f07eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=8A=80=E8=89=B2=E9=A3=9B=E8=A1=8C=E7=8C=B9?= Date: Tue, 2 Jun 2026 00:46:38 +0800 Subject: [PATCH 2/4] feat: add per-thread temporary API toggle and endpoint override --- apps/codex-plus-manager/src/App.tsx | 3 ++ assets/inject/renderer-inject.js | 51 +++++++++++++++++++++++--- crates/codex-plus-core/src/settings.rs | 4 ++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index 6dfef0a..9e80666 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -111,6 +111,7 @@ type BackendSettings = { codexAppUpstreamWorktreeCreate: boolean; codexAppNativeMenuPlacement: boolean; codexAppServiceTierControls: boolean; + codexAppThreadEndpointAuth: boolean; codexGoalsEnabled: boolean; launchMode: LaunchMode; relayBaseUrl: string; @@ -455,6 +456,7 @@ const defaultSettings: BackendSettings = { codexAppUpstreamWorktreeCreate: true, codexAppNativeMenuPlacement: true, codexAppServiceTierControls: false, + codexAppThreadEndpointAuth: false, codexGoalsEnabled: false, launchMode: "patch", relayBaseUrl: "", @@ -1827,6 +1829,7 @@ function EnhanceScreen({ setEnhanceFlag("codexAppForcePluginInstall", value)} /> setEnhanceFlag("codexAppModelWhitelistUnlock", value)} /> setEnhanceFlag("codexAppServiceTierControls", value)} /> + setEnhanceFlag("codexAppThreadEndpointAuth", value)} /> setEnhanceFlag("codexAppSessionDelete", value)} /> setEnhanceFlag("codexAppMarkdownExport", value)} /> setEnhanceFlag("codexAppProjectMove", value)} /> diff --git a/assets/inject/renderer-inject.js b/assets/inject/renderer-inject.js index 5216dc4..ae9372c 100644 --- a/assets/inject/renderer-inject.js +++ b/assets/inject/renderer-inject.js @@ -837,7 +837,7 @@ } function defaultCodexPlusSettings() { - return { pluginEntryUnlock: true, forcePluginInstall: true, modelWhitelistUnlock: true, sessionDelete: true, markdownExport: true, projectMove: true, conversationTimeline: true, conversationView: false, conversationViewMaxWidth: conversationViewDefaultWidth, threadScrollRestore: true, zedRemoteOpen: true, upstreamWorktreeCreate: true, nativeMenuPlacement: true, serviceTierControls: false }; + return { pluginEntryUnlock: true, forcePluginInstall: true, modelWhitelistUnlock: true, sessionDelete: true, markdownExport: true, projectMove: true, conversationTimeline: true, conversationView: false, conversationViewMaxWidth: conversationViewDefaultWidth, threadScrollRestore: true, zedRemoteOpen: true, upstreamWorktreeCreate: true, nativeMenuPlacement: true, serviceTierControls: false, threadEndpointAuth: false }; } const codexPlusBackendSettingMap = { @@ -854,6 +854,7 @@ upstreamWorktreeCreate: "codexAppUpstreamWorktreeCreate", nativeMenuPlacement: "codexAppNativeMenuPlacement", serviceTierControls: "codexAppServiceTierControls", + threadEndpointAuth: "codexAppThreadEndpointAuth", }; function backendCodexPlusSettings() { @@ -884,6 +885,7 @@ upstreamWorktreeCreate: false, nativeMenuPlacement: false, serviceTierControls: false, + threadEndpointAuth: false, }; } try { @@ -1674,6 +1676,7 @@ if (!found) return params; const { threadId, entry } = found; const nextParams = { ...(params || {}) }; + const tempProviderId = "codex_plus_thread_temp"; if (entry.baseUrl) nextParams.baseUrl = entry.baseUrl; if (entry.apiKey) { nextParams.apiKey = entry.apiKey; @@ -1683,16 +1686,44 @@ nextParams.bearerToken = entry.apiKey; nextParams.authToken = entry.apiKey; } + if (entry.baseUrl || entry.apiKey) { + const currentConfig = nextParams.config && typeof nextParams.config === "object" ? nextParams.config : {}; + const currentNestedProviders = currentConfig.model_providers && typeof currentConfig.model_providers === "object" + ? currentConfig.model_providers + : {}; + const currentFlatProvider = currentConfig[`model_providers.${tempProviderId}`]; + const providerConfig = { + ...(currentNestedProviders[tempProviderId] && typeof currentNestedProviders[tempProviderId] === "object" ? currentNestedProviders[tempProviderId] : {}), + ...(currentFlatProvider && typeof currentFlatProvider === "object" ? currentFlatProvider : {}), + name: "Codex++ Thread Temp", + wire_api: "responses", + }; + if (entry.baseUrl) providerConfig.base_url = entry.baseUrl; + if (entry.apiKey) providerConfig.experimental_bearer_token = entry.apiKey; + nextParams.modelProvider = tempProviderId; + nextParams.model_provider = tempProviderId; + nextParams.config = { + ...currentConfig, + model_provider: tempProviderId, + [`model_providers.${tempProviderId}`]: providerConfig, + model_providers: { + ...currentNestedProviders, + [tempProviderId]: providerConfig, + }, + }; + } sendCodexPlusDiagnostic("thread_endpoint_auth_request_override_applied", { method: String(method || ""), threadId, hasBaseUrl: !!entry.baseUrl, hasApiKey: !!entry.apiKey, + modelProvider: nextParams.modelProvider || nextParams.model_provider || "", }); return nextParams; } function codexThreadEndpointAuthRequestOverride(message) { + if (!codexPlusSettings().threadEndpointAuth) return message; if (!message || typeof message !== "object") return message; if (message.type === "send-cli-request-for-host") { const method = String(message.method || ""); @@ -1856,7 +1887,7 @@ overlay.id = codexThreadEndpointAuthOverlayId; overlay.innerHTML = `