fix: normalize wildcard gateway URLs for Codex

This commit is contained in:
2026-07-20 10:01:19 +08:00
parent 31e673f777
commit 14fae29618
6 changed files with 237 additions and 38 deletions
+112 -13
View File
@@ -11,6 +11,13 @@ import zlib from "node:zlib";
import { TextDecoder } from "node:util";
import { fileURLToPath } from "node:url";
import {
getCodexProviderContext,
getGatewayBaseUrl,
getGatewayListenerBaseUrl,
setCodexProviderBaseUrl,
} from "./scripts/admin-lib.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ADMIN_BASE_PATH = "/__codex_retry_gateway";
@@ -2589,6 +2596,7 @@ function buildRuntimePaths(configPath, logPath) {
const homeDir = process.env.HOME || "";
return {
stateRoot,
backupDir: path.join(stateRoot, "backups"),
statePath: path.join(stateRoot, "state.json"),
pidPath: path.join(stateRoot, "gateway.pid"),
profilesDir: path.join(homeDir, ".config", "codex-retry-gateway", "profiles"),
@@ -2912,6 +2920,80 @@ async function updateRuntimeState(runtime, updates) {
);
}
function resolveOriginalCodexBaseUrl(state, currentBaseUrl, listenHost, listenPort) {
const knownGatewayUrls = new Set([
getGatewayBaseUrl(listenHost, listenPort),
getGatewayListenerBaseUrl(listenHost, listenPort),
`${state?.gateway_base_url || ""}`.trim(),
].filter(Boolean));
const stateOriginalBaseUrl = `${state?.original_base_url || ""}`.trim();
if (stateOriginalBaseUrl && !knownGatewayUrls.has(stateOriginalBaseUrl)) {
return stateOriginalBaseUrl;
}
return knownGatewayUrls.has(currentBaseUrl) ? null : currentBaseUrl;
}
async function syncCurrentCodexProviderToGateway(runtime, gatewayBaseUrl) {
const state = await readOptionalJson(runtime.paths.statePath);
const codexConfigPath = `${state?.codex_config_path || ""}`.trim();
if (!codexConfigPath) {
return {
attempted: false,
changed: false,
providerName: "",
originalBaseUrl: null,
backupPath: null,
rollback: async () => {},
};
}
const providerContext = await getCodexProviderContext(codexConfigPath);
const currentBaseUrl = providerContext.currentBaseUrl;
const originalBaseUrl = resolveOriginalCodexBaseUrl(
state,
currentBaseUrl,
runtime.config.listen_host,
runtime.config.listen_port,
);
let backupPath = `${state?.latest_backup_path || ""}`.trim();
if (currentBaseUrl !== gatewayBaseUrl && (!backupPath || !fs.existsSync(backupPath))) {
await mkdir(runtime.paths.backupDir, { recursive: true });
backupPath = path.join(
runtime.paths.backupDir,
`config-${new Date().toISOString().replace(/[:.]/g, "").replace("T", "-").slice(0, 15)}.toml`,
);
await copyFile(codexConfigPath, backupPath);
}
const changed = currentBaseUrl !== gatewayBaseUrl;
if (changed) {
await setCodexProviderBaseUrl({
codexConfigPath,
providerName: providerContext.providerName,
newBaseUrl: gatewayBaseUrl,
});
}
return {
attempted: true,
changed,
providerName: providerContext.providerName,
originalBaseUrl,
backupPath: backupPath || null,
rollback: async () => {
if (!changed) {
return;
}
await setCodexProviderBaseUrl({
codexConfigPath,
providerName: providerContext.providerName,
newBaseUrl: currentBaseUrl,
});
},
};
}
async function listProfiles(runtime) {
let files = [];
try {
@@ -3137,20 +3219,31 @@ async function applyProfileConfig(runtime, profileName) {
throw new Error("该 profile 的监听地址或端口与当前实例不同,暂不支持无重启热切换");
}
runtime.config = {
...config,
model_remap_map: parseModelRemapMap(config.model_remap),
};
const gatewayBaseUrl = getGatewayBaseUrl(config.listen_host, config.listen_port);
const codexProviderSync = await syncCurrentCodexProviderToGateway(runtime, gatewayBaseUrl);
await writeConfig(runtime.configPath, runtime.config);
await updateRuntimeState(runtime, {
profile_name: runtime.config.profile_name || "default",
profile_env_path: profilePath,
gateway_base_url: `http://${runtime.config.listen_host}:${runtime.config.listen_port}`,
last_started_at: new Date().toISOString(),
});
try {
runtime.config = {
...config,
model_remap_map: parseModelRemapMap(config.model_remap),
};
await writeConfig(runtime.configPath, runtime.config);
await updateRuntimeState(runtime, {
profile_name: runtime.config.profile_name || "default",
profile_env_path: profilePath,
gateway_base_url: gatewayBaseUrl,
...(codexProviderSync.providerName ? { provider_name: codexProviderSync.providerName } : {}),
...(codexProviderSync.originalBaseUrl ? { original_base_url: codexProviderSync.originalBaseUrl } : {}),
...(codexProviderSync.backupPath ? { latest_backup_path: codexProviderSync.backupPath } : {}),
last_started_at: new Date().toISOString(),
});
} catch (error) {
await codexProviderSync.rollback().catch(() => {});
throw error;
}
runtime.logger(
`[profile] hot-swapped profile=${runtime.config.profile_name || "default"} auth=${normalizeAuthMode(runtime.config.upstream_auth_mode)} upstream=${runtime.config.upstream_base_url}`,
`[profile] hot-swapped profile=${runtime.config.profile_name || "default"} auth=${normalizeAuthMode(runtime.config.upstream_auth_mode)} upstream=${runtime.config.upstream_base_url} codex_config_sync=${codexProviderSync.changed ? "updated" : codexProviderSync.attempted ? "current" : "unavailable"}`,
);
return {
@@ -3159,6 +3252,12 @@ async function applyProfileConfig(runtime, profileName) {
hot_swapped: true,
listen: `${runtime.config.listen_host}:${runtime.config.listen_port}`,
upstream_base_url: runtime.config.upstream_base_url,
codex_provider_sync: codexProviderSync.attempted
? {
updated: codexProviderSync.changed,
gateway_base_url: gatewayBaseUrl,
}
: null,
};
}
@@ -6639,7 +6738,7 @@ async function main() {
last_started_at: new Date().toISOString(),
profile_name: runtime.config.profile_name || "default",
image_profile_name: runtime.config.image_profile_name || "",
gateway_base_url: `http://${runtime.config.listen_host}:${runtime.config.listen_port}`,
gateway_base_url: getGatewayBaseUrl(runtime.config.listen_host, runtime.config.listen_port),
}).catch((error) => logger(`[state] failed to update runtime state: ${error?.message || error}`));
logger(
`[start] codex retry gateway profile=${runtime.config.profile_name || "default"} image_profile=${runtime.config.image_profile_name || "-"} auth=${normalizeAuthMode(runtime.config.upstream_auth_mode)} reasoning_match_mode=${normalizeReasoningMatchMode(runtime.config.reasoning_match_mode)} listening on http://${runtime.config.listen_host}:${runtime.config.listen_port} -> ${runtime.config.upstream_base_url}`,