fix: restore codex-api via pod-local forwarder
This commit is contained in:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user