fix: normalize wildcard gateway URLs for Codex
This commit is contained in:
+112
-13
@@ -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}`,
|
||||
|
||||
Reference in New Issue
Block a user