import { ENVIRONMENT_DEV } from "../protocol/index.mjs"; export const DEV_CODE_AGENT_PROVIDER_CONTRACT = Object.freeze({ contractVersion: "v1", environment: ENVIRONMENT_DEV, provider: "openai", runtimeProvider: "openai-responses", backend: "hwlab-cloud-api/openai-responses", model: "gpt-5.5", requiredEnv: Object.freeze([ "OPENAI_API_KEY", "HWLAB_CODE_AGENT_OPENAI_BASE_URL" ]), secretRefs: Object.freeze([ Object.freeze({ env: "OPENAI_API_KEY", secretName: "hwlab-code-agent-provider", secretKey: "openai-api-key" }) ]), egress: Object.freeze({ env: "HWLAB_CODE_AGENT_OPENAI_BASE_URL", target: "dev-egress-proxy", defaultBaseUrl: "http://172.26.26.227:17680/v1/responses", forbiddenDirectBaseUrl: "https://api.openai.com/v1/responses", reason: "DEV cloud-api pods must use the D601 egress/proxy path for OpenAI Responses." }), forbidden: Object.freeze({ prodAllowed: false, secretPlaintextAllowed: false, directPublicOpenAiFromDevPod: false }) }); export function codeAgentSecretRefPlaceholder() { const ref = DEV_CODE_AGENT_PROVIDER_CONTRACT.secretRefs[0]; return `secretRef:${ref.secretName}/${ref.secretKey}`; } export function buildCodeAgentProviderManifestPlaceholder() { const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT; return { contractVersion: contract.contractVersion, environment: contract.environment, provider: contract.provider, runtimeProvider: contract.runtimeProvider, backend: contract.backend, model: contract.model, requiredEnv: [...contract.requiredEnv], secretRefs: contract.secretRefs.map((item) => ({ ...item })), egress: { ...contract.egress }, fixtureEvidence: { providerConnected: false, summary: "This manifest contract records provider env names, Secret reference names, and DEV egress policy only. It is not live provider evidence." } }; } export function inspectCodeAgentProviderEnv(env = {}) { const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT; 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 directPublicOpenAi = baseUrl === contract.egress.forbiddenDirectBaseUrl || /^https:\/\/api\.openai\.com\/v1\/responses\/?$/u.test(baseUrl); const egressReady = Boolean(baseUrl) && !directPublicOpenAi; const ready = missingEnv.length === 0 && egressReady; return { contractVersion: contract.contractVersion, environment: contract.environment, provider: contract.runtimeProvider, model: firstNonEmpty(env.HWLAB_CODE_AGENT_MODEL, contract.model), backend: contract.backend, status: ready ? "available" : "blocked", ready, requiredEnv: contract.requiredEnv.map((name) => ({ name, present: hasEnvValue(env, name), redacted: name === secretRef.env, source: name === secretRef.env ? "k8s-secret-ref" : "runtime-env" })), missingEnv, secretRefs: [ { env: secretRef.env, secretName: secretRef.secretName, secretKey: secretRef.secretKey, present: hasEnvValue(env, secretRef.env), envInjected: hasEnvValue(env, secretRef.env), secretPresent: "not_observed_by_runtime", secretKeyPresent: "not_observed_by_runtime", redacted: true } ], egress: { env: contract.egress.env, present: hasEnvValue(env, contract.egress.env), target: contract.egress.target, baseUrlConfigured: Boolean(baseUrl), directPublicOpenAi, ready: egressReady, valueRedacted: true }, safety: { devOnly: true, prodAllowed: contract.forbidden.prodAllowed, secretsRead: false, secretMaterialRead: false, valuesRedacted: true, directPublicOpenAiFromDevPod: directPublicOpenAi }, blocker: ready ? null : codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl }) }; } function codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl }) { if (directPublicOpenAi) { return "DEV Code Agent OpenAI base URL points at public api.openai.com instead of the DEV egress/proxy path"; } if (!baseUrl) { return "DEV Code Agent OpenAI base URL is missing; provider calls must use the DEV egress/proxy path"; } return `Code Agent provider runtime config is missing ${missingEnv.join(", ")}`; } function hasEnvValue(env, name) { return typeof env?.[name] === "string" && env[name].trim().length > 0; } function firstNonEmpty(...values) { for (const value of values) { if (typeof value === "string" && value.trim()) return value.trim(); } return ""; }