fix: restore codex-api via pod-local forwarder

This commit is contained in:
Codex
2026-05-26 16:30:31 +08:00
parent c2344b2950
commit f1067bb51f
16 changed files with 846 additions and 69 deletions
+137 -11
View File
@@ -10,7 +10,14 @@ export const DEV_CODE_AGENT_PROVIDER_CONTRACT = Object.freeze({
model: "gpt-5.5",
requiredEnv: Object.freeze([
"OPENAI_API_KEY",
"HWLAB_CODE_AGENT_OPENAI_BASE_URL"
"HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE",
"HWLAB_CODE_AGENT_OPENAI_BASE_URL",
"HWLAB_CODE_AGENT_DEEPSEEK_MODEL",
"HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL",
"HWLAB_CODE_AGENT_CODEX_API_MODEL",
"HWLAB_CODE_AGENT_CODEX_API_BASE_URL",
"HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL",
"HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT"
]),
secretRefs: Object.freeze([
Object.freeze({
@@ -21,10 +28,32 @@ export const DEV_CODE_AGENT_PROVIDER_CONTRACT = Object.freeze({
]),
egress: Object.freeze({
env: "HWLAB_CODE_AGENT_OPENAI_BASE_URL",
target: "dev-egress-proxy",
defaultBaseUrl: "http://172.26.26.227:17680/v1/responses",
target: "pod-local-codex-api-forwarder",
defaultBaseUrl: "http://127.0.0.1:49280/responses",
upstreamEnv: "HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL",
upstreamBaseUrl: "https://hyueapi.com",
forbiddenDirectBaseUrl: "https://api.openai.com/v1/responses",
reason: "DEV cloud-api pods must use the D601 egress/proxy path for OpenAI Responses."
reason: "DEV cloud-api pods must send codex-api traffic to a same-Pod loopback forwarder; the forwarder direct-connects to hyueapi with NO_PROXY."
}),
profiles: Object.freeze({
defaultProfile: "deepseek",
deepSeekProfile: "deepseek",
deepSeekModelEnv: "HWLAB_CODE_AGENT_DEEPSEEK_MODEL",
deepSeekBaseUrlEnv: "HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL",
deepSeekBaseUrl: "http://hwlab-deepseek-proxy.hwlab-dev.svc.cluster.local:4000/v1/responses",
codexApiProfile: "codex-api",
codexApiModelEnv: "HWLAB_CODE_AGENT_CODEX_API_MODEL",
codexApiBaseUrlEnv: "HWLAB_CODE_AGENT_CODEX_API_BASE_URL"
}),
forwarder: Object.freeze({
containerName: "hwlab-codex-api-forwarder",
command: Object.freeze(["node", "/app/cmd/hwlab-codex-api-responses-forwarder/main.mjs"]),
port: 49280,
portName: "codex-api",
listenHostEnv: "HWLAB_CODE_AGENT_CODEX_API_FORWARDER_HOST",
portEnv: "HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT",
upstreamEnv: "HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL",
upstreamBaseUrl: "https://hyueapi.com"
}),
codexHome: Object.freeze({
path: "/codex-home",
@@ -65,6 +94,13 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
const expectedValues = {
HWLAB_CODE_AGENT_PROVIDER: contract.codeAgentProvider,
HWLAB_CODE_AGENT_MODEL: contract.model,
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: contract.profiles.defaultProfile,
[contract.profiles.deepSeekModelEnv]: "deepseek-chat",
[contract.profiles.deepSeekBaseUrlEnv]: contract.profiles.deepSeekBaseUrl,
[contract.profiles.codexApiModelEnv]: contract.model,
[contract.profiles.codexApiBaseUrlEnv]: contract.egress.defaultBaseUrl,
[contract.forwarder.upstreamEnv]: contract.forwarder.upstreamBaseUrl,
[contract.forwarder.portEnv]: String(contract.forwarder.port),
[contract.egress.env]: contract.egress.defaultBaseUrl,
CODEX_HOME: contract.codexHome.path,
NO_PROXY: contract.noProxyRequired.join(","),
@@ -115,6 +151,7 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
...noProxyMissing(deployEnv?.no_proxy, contract.noProxyRequired),
...workloadInspection.noProxy.missing
])];
const missingForwarderContract = workloadInspection.forwarder.present ? [] : [contract.forwarder.containerName];
const ready =
missingDeployEnv.length === 0 &&
@@ -123,7 +160,8 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
missingSecretRefs.length === 0 &&
missingEgressContract.length === 0 &&
missingCodexHomeContract.length === 0 &&
missingNoProxyContract.length === 0;
missingNoProxyContract.length === 0 &&
missingForwarderContract.length === 0;
return {
contractVersion: contract.contractVersion,
@@ -160,6 +198,12 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
directPublicOpenAi:
isForbiddenDirectOpenAiBaseUrl(deployEnv?.[contract.egress.env]) ||
workloadInspection.egress.directPublicOpenAi === true,
upstream: {
env: contract.egress.upstreamEnv,
deployMatchesDirectHyueapi: deployEnv?.[contract.egress.upstreamEnv] === contract.egress.upstreamBaseUrl,
workloadMatchesDirectHyueapi: workloadInspection.egress.upstreamMatchesDirectHyueapi,
valueRedacted: true
},
valueRedacted: true
},
missingDeployEnv,
@@ -170,7 +214,9 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
missingEgressContract,
missingCodexHomeContract,
missingNoProxyContract,
missingForwarderContract,
codexHome: workloadInspection.codexHome,
forwarder: workloadInspection.forwarder,
noProxy: workloadInspection.noProxy,
secretMaterialRead: false,
valuesRedacted: true,
@@ -186,6 +232,13 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
const expectedValues = {
HWLAB_CODE_AGENT_PROVIDER: contract.codeAgentProvider,
HWLAB_CODE_AGENT_MODEL: contract.model,
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: contract.profiles.defaultProfile,
[contract.profiles.deepSeekModelEnv]: "deepseek-chat",
[contract.profiles.deepSeekBaseUrlEnv]: contract.profiles.deepSeekBaseUrl,
[contract.profiles.codexApiModelEnv]: contract.model,
[contract.profiles.codexApiBaseUrlEnv]: contract.egress.defaultBaseUrl,
[contract.forwarder.upstreamEnv]: contract.forwarder.upstreamBaseUrl,
[contract.forwarder.portEnv]: String(contract.forwarder.port),
[contract.egress.env]: contract.egress.defaultBaseUrl,
CODEX_HOME: contract.codexHome.path,
NO_PROXY: contract.noProxyRequired.join(","),
@@ -218,24 +271,29 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
secretKeyRef?.name === secretRef.secretName &&
secretKeyRef?.key === secretRef.secretKey;
const baseUrl = envEntryValue(getEnvEntry(workloadEnv, contract.egress.env));
const upstreamBaseUrl = envEntryValue(getEnvEntry(workloadEnv, contract.egress.upstreamEnv));
const directPublicOpenAi = isForbiddenDirectOpenAiBaseUrl(baseUrl);
const matchesDevProxy = baseUrl === contract.egress.defaultBaseUrl;
const upstreamMatchesDirectHyueapi = upstreamBaseUrl === contract.egress.upstreamBaseUrl;
const codexHomePresent = envEntryValue(getEnvEntry(workloadEnv, "CODEX_HOME")) === contract.codexHome.path;
const noProxyMissingEntries = [...new Set([
...noProxyMissing(envEntryValue(getEnvEntry(workloadEnv, "NO_PROXY")), contract.noProxyRequired),
...noProxyMissing(envEntryValue(getEnvEntry(workloadEnv, "no_proxy")), contract.noProxyRequired)
])];
const codexHome = inspectCodexHomeWorkloadMounts(workloadEnv);
const forwarder = inspectCodexApiForwarderWorkload(workloadEnv);
const ready =
missingEnv.length === 0 &&
mismatchedEnv.length === 0 &&
secretPresent &&
matchesDevProxy &&
upstreamMatchesDirectHyueapi &&
!directPublicOpenAi &&
codexHome.present === true &&
codexHome.configMapPresent === true &&
codexHome.authSecretPresent === true &&
noProxyMissingEntries.length === 0;
noProxyMissingEntries.length === 0 &&
forwarder.present === true;
return {
contractVersion: contract.contractVersion,
@@ -261,6 +319,7 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
target: contract.egress.target,
present: Boolean(baseUrl),
matchesDevProxy,
upstreamMatchesDirectHyueapi,
directPublicOpenAi,
valueRedacted: true
},
@@ -268,6 +327,7 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
...codexHome,
present: codexHomePresent && codexHome.present === true
},
forwarder,
noProxy: {
required: [...contract.noProxyRequired],
missing: noProxyMissingEntries,
@@ -291,6 +351,11 @@ export function buildCodeAgentProviderManifestPlaceholder() {
requiredEnv: [...contract.requiredEnv],
secretRefs: contract.secretRefs.map((item) => ({ ...item })),
egress: { ...contract.egress },
profiles: { ...contract.profiles },
forwarder: {
...contract.forwarder,
command: [...contract.forwarder.command]
},
codexHome: { ...contract.codexHome },
noProxyRequired: [...contract.noProxyRequired],
fixtureEvidence: {
@@ -305,10 +370,17 @@ export function inspectCodeAgentProviderEnv(env = {}) {
const missingEnv = contract.requiredEnv.filter((name) => !hasEnvValue(env, name));
const secretRef = contract.secretRefs[0];
const baseUrl = firstNonEmpty(env.HWLAB_CODE_AGENT_OPENAI_BASE_URL);
const codexApiBaseUrl = firstNonEmpty(env[contract.profiles.codexApiBaseUrlEnv]);
const upstreamBaseUrl = firstNonEmpty(env[contract.forwarder.upstreamEnv]);
const forwarderPort = firstNonEmpty(env[contract.forwarder.portEnv]);
const directPublicOpenAi =
baseUrl === contract.egress.forbiddenDirectBaseUrl ||
/^https:\/\/api\.openai\.com\/v1\/responses\/?$/u.test(baseUrl);
const egressReady = Boolean(baseUrl) && !directPublicOpenAi;
const egressMatchesForwarder = baseUrl === contract.egress.defaultBaseUrl;
const codexApiProfileReady = codexApiBaseUrl === contract.egress.defaultBaseUrl;
const upstreamReady = upstreamBaseUrl === contract.forwarder.upstreamBaseUrl;
const forwarderPortReady = forwarderPort === String(contract.forwarder.port);
const egressReady = Boolean(baseUrl) && !directPublicOpenAi && egressMatchesForwarder && codexApiProfileReady && upstreamReady && forwarderPortReady;
const noProxyReady = noProxyMissing(env.NO_PROXY, contract.noProxyRequired).length === 0 &&
noProxyMissing(env.no_proxy, contract.noProxyRequired).length === 0;
const codexHomeReady = firstNonEmpty(env.CODEX_HOME) === contract.codexHome.path;
@@ -347,9 +419,22 @@ export function inspectCodeAgentProviderEnv(env = {}) {
target: contract.egress.target,
baseUrlConfigured: Boolean(baseUrl),
directPublicOpenAi,
matchesForwarder: egressMatchesForwarder,
codexApiProfileReady,
upstreamReady,
forwarderPortReady,
ready: egressReady,
valueRedacted: true
},
forwarder: {
containerName: contract.forwarder.containerName,
expectedBaseUrl: contract.egress.defaultBaseUrl,
upstreamEnv: contract.forwarder.upstreamEnv,
expectedUpstreamHost: new URL(contract.forwarder.upstreamBaseUrl).hostname,
managedByKubernetes: true,
podLocalOnly: true,
serviceRequired: false
},
codexHome: {
path: contract.codexHome.path,
present: codexHomeReady,
@@ -374,16 +459,19 @@ export function inspectCodeAgentProviderEnv(env = {}) {
valuesRedacted: true,
directPublicOpenAiFromDevPod: directPublicOpenAi
},
blocker: ready ? null : codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl })
blocker: ready ? null : codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl, egressMatchesForwarder, codexApiProfileReady, upstreamReady, forwarderPortReady })
};
}
function codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl }) {
function codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl, egressMatchesForwarder, codexApiProfileReady, upstreamReady, forwarderPortReady }) {
if (directPublicOpenAi) {
return "DEV Code Agent OpenAI base URL points at public api.openai.com instead of the DEV egress/proxy path";
return "DEV Code Agent OpenAI base URL points at public api.openai.com instead of the pod-local Codex API forwarder";
}
if (!baseUrl) {
return "DEV Code Agent OpenAI base URL is missing; provider calls must use the DEV egress/proxy path";
return "DEV Code Agent OpenAI base URL is missing; provider calls must use the pod-local Codex API forwarder";
}
if (!egressMatchesForwarder || !codexApiProfileReady || !upstreamReady || !forwarderPortReady) {
return "DEV Code Agent codex-api profile must use the pod-local loopback forwarder with direct hyueapi upstream";
}
return `Code Agent provider runtime config is missing ${missingEnv.join(", ")}`;
}
@@ -482,6 +570,44 @@ function noProxyMissing(value, required) {
return required.filter((item) => !entries.has(String(item).toLowerCase()));
}
function inspectCodexApiForwarderWorkload(workloadEnv = {}) {
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
const containers = Array.isArray(workloadEnv.__podContainers) ? workloadEnv.__podContainers : [];
const sidecar = containers.find((entry) => entry?.name === contract.forwarder.containerName);
const env = new Map((sidecar?.env ?? []).map((entry) => [entry?.name, envEntryValue(entry)]));
const commandMatches = arrayEqual(sidecar?.command, contract.forwarder.command);
const portMatches = (sidecar?.ports ?? []).some((entry) =>
entry?.name === contract.forwarder.portName && entry?.containerPort === contract.forwarder.port
);
const upstreamMatches = env.get(contract.forwarder.upstreamEnv) === contract.forwarder.upstreamBaseUrl;
const listenHostMatches = env.get(contract.forwarder.listenHostEnv) === "127.0.0.1";
const portEnvMatches = env.get(contract.forwarder.portEnv) === String(contract.forwarder.port);
const noProxyReady = noProxyMissing(env.get("NO_PROXY"), contract.noProxyRequired).length === 0 &&
noProxyMissing(env.get("no_proxy"), contract.noProxyRequired).length === 0;
const present = Boolean(sidecar && commandMatches && portMatches && upstreamMatches && listenHostMatches && portEnvMatches && noProxyReady);
return {
containerName: contract.forwarder.containerName,
present,
sidecarPresent: Boolean(sidecar),
commandMatches,
portMatches,
upstreamMatches,
listenHostMatches,
portEnvMatches,
noProxyReady,
podLocalOnly: true,
managedByKubernetes: true,
serviceRequired: false,
valueRedacted: true
};
}
function arrayEqual(actual, expected) {
if (!Array.isArray(actual) || !Array.isArray(expected)) return false;
if (actual.length !== expected.length) return false;
return actual.every((item, index) => Object.is(item, expected[index]));
}
function isForbiddenDirectOpenAiBaseUrl(value) {
const baseUrl = firstNonEmpty(value);
return (