diff --git a/containers/api-proxy/Dockerfile b/containers/api-proxy/Dockerfile index 11233c51..925a7205 100644 --- a/containers/api-proxy/Dockerfile +++ b/containers/api-proxy/Dockerfile @@ -27,7 +27,8 @@ COPY server.js logging.js metrics.js rate-limiter.js \ ai-credits-pricing.js models-dev-catalog.js models.dev.catalog.json \ oidc-refresh-utils.js body-transform.js body-utils.js rate-limit.js websocket-proxy.js \ deprecated-header-tracker.js billing-headers.js upstream-response.js \ - anthropic-cache.js otel.js token-budget-log.js blocked-request-diagnostics.js ./ + anthropic-cache.js otel.js token-budget-log.js blocked-request-diagnostics.js \ + provider-env-constants.js ./ COPY guards/ ./guards/ COPY providers/ ./providers/ COPY transforms/ ./transforms/ diff --git a/containers/api-proxy/provider-env-constants.js b/containers/api-proxy/provider-env-constants.js new file mode 100644 index 00000000..c6aa8053 --- /dev/null +++ b/containers/api-proxy/provider-env-constants.js @@ -0,0 +1,50 @@ +'use strict'; + +/** + * Environment variable name constants for the API proxy provider adapters. + * + * This is the single source of truth for env var names on the container JS side. + * The TypeScript equivalent lives in src/api-proxy-env-constants.ts. + * + * Both files must be kept in sync when adding or renaming env vars. + */ + +/** Environment variable names for the OpenAI provider adapter. */ +const OPENAI_ENV = /** @type {const} */ ({ + KEY: 'OPENAI_API_KEY', + TARGET: 'OPENAI_API_TARGET', + BASE_PATH: 'OPENAI_API_BASE_PATH', + AUTH_HEADER: 'AWF_OPENAI_AUTH_HEADER', +}); + +/** Environment variable names for the Anthropic provider adapter. */ +const ANTHROPIC_ENV = /** @type {const} */ ({ + KEY: 'ANTHROPIC_API_KEY', + TARGET: 'ANTHROPIC_API_TARGET', + BASE_PATH: 'ANTHROPIC_API_BASE_PATH', + AUTH_HEADER: 'AWF_ANTHROPIC_AUTH_HEADER', +}); + +/** Environment variable names for the Gemini provider adapter. */ +const GEMINI_ENV = /** @type {const} */ ({ + KEY: 'GEMINI_API_KEY', + TARGET: 'GEMINI_API_TARGET', + BASE_PATH: 'GEMINI_API_BASE_PATH', +}); + +/** Environment variable names for the Copilot provider adapter. */ +const COPILOT_ENV = /** @type {const} */ ({ + GITHUB_TOKEN: 'COPILOT_GITHUB_TOKEN', + PROVIDER_API_KEY: 'COPILOT_PROVIDER_API_KEY', + PROVIDER_TYPE: 'COPILOT_PROVIDER_TYPE', + PROVIDER_BASE_URL: 'COPILOT_PROVIDER_BASE_URL', + API_TARGET: 'COPILOT_API_TARGET', + API_BASE_PATH: 'COPILOT_API_BASE_PATH', +}); + +module.exports = { + OPENAI_ENV, + ANTHROPIC_ENV, + GEMINI_ENV, + COPILOT_ENV, +}; diff --git a/containers/api-proxy/providers/anthropic.js b/containers/api-proxy/providers/anthropic.js index c5523173..c4d96ce8 100644 --- a/containers/api-proxy/providers/anthropic.js +++ b/containers/api-proxy/providers/anthropic.js @@ -21,6 +21,7 @@ const { } = require('../proxy-utils'); const { createBaseAdapterConfig, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory'); const { AnthropicOidcTokenProvider } = require('../anthropic-oidc-token-provider'); +const { ANTHROPIC_ENV } = require('../provider-env-constants'); const { createProviderOidcAuth } = require('./cloud-oidc-init'); let makeAnthropicTransform, loadCustomTransform, EXTENDED_CACHE_BETA; @@ -45,12 +46,12 @@ try { */ function createAnthropicAdapter(env, deps = {}) { const { apiKey, rawTarget, basePath } = createBaseAdapterConfig(env, { - keyEnvVar: 'ANTHROPIC_API_KEY', - targetEnvVar: 'ANTHROPIC_API_TARGET', - basePathEnvVar: 'ANTHROPIC_API_BASE_PATH', + keyEnvVar: ANTHROPIC_ENV.KEY, + targetEnvVar: ANTHROPIC_ENV.TARGET, + basePathEnvVar: ANTHROPIC_ENV.BASE_PATH, defaultTarget: 'api.anthropic.com', }); - const authHeaderName = validateAuthHeaderEnv('AWF_ANTHROPIC_AUTH_HEADER', env.AWF_ANTHROPIC_AUTH_HEADER, 'x-api-key'); + const authHeaderName = validateAuthHeaderEnv(ANTHROPIC_ENV.AUTH_HEADER, env[ANTHROPIC_ENV.AUTH_HEADER], 'x-api-key'); // oidcRequested tracks whether the caller asked for Anthropic OIDC, regardless // of whether the token env vars (ACTIONS_ID_TOKEN_REQUEST_*) are also present. diff --git a/containers/api-proxy/providers/copilot.js b/containers/api-proxy/providers/copilot.js index 1ed0d8ac..631e74b6 100644 --- a/containers/api-proxy/providers/copilot.js +++ b/containers/api-proxy/providers/copilot.js @@ -40,6 +40,7 @@ const { } = require('./copilot-auth'); const { createProviderOidcAuth } = require('./cloud-oidc-init'); const { URL } = require('url'); +const { COPILOT_ENV } = require('../provider-env-constants'); /** * Create the GitHub Copilot provider adapter. @@ -49,13 +50,13 @@ const { URL } = require('url'); * @returns {import('./index').ProviderAdapter} */ function createCopilotAdapter(env, deps = {}) { - const githubToken = stripBearerPrefix(env.COPILOT_GITHUB_TOKEN); + const githubToken = stripBearerPrefix(env[COPILOT_ENV.GITHUB_TOKEN]); // resolveApiKey filters out the AWF placeholder so it is never used as a real BYOK credential. const apiKey = resolveApiKey(env); const staticAuthToken = resolveCopilotAuthToken(env); const integrationId = env.COPILOT_INTEGRATION_ID || 'agentic-workflows'; const rawTarget = deriveCopilotApiTarget(env); - const basePath = normalizeBasePath(env.COPILOT_API_BASE_PATH); + const basePath = normalizeBasePath(env[COPILOT_ENV.API_BASE_PATH]); // OIDC auth strategy (Azure OpenAI via Entra, AWS Bedrock, GCP Vertex AI) for // BYOK targets pointed at by COPILOT_PROVIDER_BASE_URL. Mirrors the OpenAI diff --git a/containers/api-proxy/providers/gemini.js b/containers/api-proxy/providers/gemini.js index 2cc3473f..a335f93a 100644 --- a/containers/api-proxy/providers/gemini.js +++ b/containers/api-proxy/providers/gemini.js @@ -15,6 +15,7 @@ const { stripGeminiKeyParam, makeUnconfiguredHealthResponse } = require('../proxy-utils'); const { createBaseAdapterConfig, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory'); +const { GEMINI_ENV } = require('../provider-env-constants'); /** * Create the Google Gemini provider adapter. @@ -25,9 +26,9 @@ const { createBaseAdapterConfig, createAdapterMethods, buildProviderAdapter } = */ function createGeminiAdapter(env, deps = {}) { const { apiKey, rawTarget, basePath } = createBaseAdapterConfig(env, { - keyEnvVar: 'GEMINI_API_KEY', - targetEnvVar: 'GEMINI_API_TARGET', - basePathEnvVar: 'GEMINI_API_BASE_PATH', + keyEnvVar: GEMINI_ENV.KEY, + targetEnvVar: GEMINI_ENV.TARGET, + basePathEnvVar: GEMINI_ENV.BASE_PATH, defaultTarget: 'generativelanguage.googleapis.com', }); diff --git a/containers/api-proxy/providers/openai.js b/containers/api-proxy/providers/openai.js index 9176cbf4..a7736b41 100644 --- a/containers/api-proxy/providers/openai.js +++ b/containers/api-proxy/providers/openai.js @@ -18,6 +18,7 @@ const { const { createBaseAdapterConfig, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory'); const { createProviderOidcAuth } = require('./cloud-oidc-init'); +const { OPENAI_ENV, COPILOT_ENV } = require('../provider-env-constants'); /** * Create the OpenAI provider adapter. @@ -28,26 +29,26 @@ const { createProviderOidcAuth } = require('./cloud-oidc-init'); */ function createOpenAIAdapter(env, deps = {}) { const { apiKey: openaiApiKey, rawTarget: openaiTarget, basePath: openaiBasePath } = createBaseAdapterConfig(env, { - keyEnvVar: 'OPENAI_API_KEY', - targetEnvVar: 'OPENAI_API_TARGET', - basePathEnvVar: 'OPENAI_API_BASE_PATH', + keyEnvVar: OPENAI_ENV.KEY, + targetEnvVar: OPENAI_ENV.TARGET, + basePathEnvVar: OPENAI_ENV.BASE_PATH, defaultTarget: 'api.openai.com', }); - const providerType = (env.COPILOT_PROVIDER_TYPE || '').trim().toLowerCase(); + const providerType = (env[COPILOT_ENV.PROVIDER_TYPE] || '').trim().toLowerCase(); const copilotAzureByokEnabled = providerType === 'azure'; const customAuthHeader = (() => { - const header = validateAuthHeaderEnv('AWF_OPENAI_AUTH_HEADER', env.AWF_OPENAI_AUTH_HEADER); + const header = validateAuthHeaderEnv(OPENAI_ENV.AUTH_HEADER, env[OPENAI_ENV.AUTH_HEADER]); if (header) return header; // Azure OpenAI BYOK uses `api-key` header instead of `Authorization: Bearer` // (but OIDC auth still requires `Authorization: Bearer` unless explicitly overridden) if (copilotAzureByokEnabled && (env.AWF_AUTH_TYPE || '').trim().toLowerCase() !== 'github-oidc') return 'api-key'; return ''; })(); - const copilotByokApiKey = (env.COPILOT_PROVIDER_API_KEY || '').trim() || undefined; - const { target: copilotByokTarget, basePath: copilotByokBasePath } = parseApiTargetAndBasePath(env.COPILOT_PROVIDER_BASE_URL); + const copilotByokApiKey = (env[COPILOT_ENV.PROVIDER_API_KEY] || '').trim() || undefined; + const { target: copilotByokTarget, basePath: copilotByokBasePath } = parseApiTargetAndBasePath(env[COPILOT_ENV.PROVIDER_BASE_URL]); const apiKey = openaiApiKey || (copilotAzureByokEnabled ? copilotByokApiKey : undefined); - const explicitOpenAITarget = env.OPENAI_API_TARGET ? openaiTarget : undefined; + const explicitOpenAITarget = env[OPENAI_ENV.TARGET] ? openaiTarget : undefined; const rawTarget = explicitOpenAITarget || (copilotAzureByokEnabled ? copilotByokTarget : undefined) || 'api.openai.com'; const explicitBasePath = openaiBasePath || (copilotAzureByokEnabled ? copilotByokBasePath : ''); diff --git a/src/api-proxy-env-constants.ts b/src/api-proxy-env-constants.ts new file mode 100644 index 00000000..87835fb9 --- /dev/null +++ b/src/api-proxy-env-constants.ts @@ -0,0 +1,41 @@ +/** + * Environment variable name constants for the API proxy provider adapters. + * + * This is the single source of truth for env var names on the TypeScript host side. + * The CommonJS equivalent lives in containers/api-proxy/provider-env-constants.js. + * + * Both files must be kept in sync when adding or renaming env vars. + */ + +/** Environment variable names for the OpenAI provider adapter. */ +export const OPENAI_ENV = { + KEY: 'OPENAI_API_KEY', + TARGET: 'OPENAI_API_TARGET', + BASE_PATH: 'OPENAI_API_BASE_PATH', + AUTH_HEADER: 'AWF_OPENAI_AUTH_HEADER', +} as const; + +/** Environment variable names for the Anthropic provider adapter. */ +export const ANTHROPIC_ENV = { + KEY: 'ANTHROPIC_API_KEY', + TARGET: 'ANTHROPIC_API_TARGET', + BASE_PATH: 'ANTHROPIC_API_BASE_PATH', + AUTH_HEADER: 'AWF_ANTHROPIC_AUTH_HEADER', +} as const; + +/** Environment variable names for the Gemini provider adapter. */ +export const GEMINI_ENV = { + KEY: 'GEMINI_API_KEY', + TARGET: 'GEMINI_API_TARGET', + BASE_PATH: 'GEMINI_API_BASE_PATH', +} as const; + +/** Environment variable names for the Copilot provider adapter. */ +export const COPILOT_ENV = { + GITHUB_TOKEN: 'COPILOT_GITHUB_TOKEN', + PROVIDER_API_KEY: 'COPILOT_PROVIDER_API_KEY', + PROVIDER_TYPE: 'COPILOT_PROVIDER_TYPE', + PROVIDER_BASE_URL: 'COPILOT_PROVIDER_BASE_URL', + API_TARGET: 'COPILOT_API_TARGET', + API_BASE_PATH: 'COPILOT_API_BASE_PATH', +} as const; diff --git a/src/commands/build-config.ts b/src/commands/build-config.ts index 927ba720..5aa75895 100644 --- a/src/commands/build-config.ts +++ b/src/commands/build-config.ts @@ -1,4 +1,5 @@ import { WrapperConfig, LogLevel, UpstreamProxyConfig } from '../types'; +import { OPENAI_ENV, ANTHROPIC_ENV, GEMINI_ENV, COPILOT_ENV } from '../api-proxy-env-constants'; /** * Inputs required to assemble a {@link WrapperConfig}. @@ -170,38 +171,38 @@ export function buildConfig(inputs: BuildConfigInputs): WrapperConfig { : undefined), maxCapturedBytes: (options.maxCapturedBytes as number | undefined) ?? (process.env.AWF_MAX_BLOCKED_CAPTURE_BYTES ? Number(process.env.AWF_MAX_BLOCKED_CAPTURE_BYTES) : undefined), - openaiApiKey: process.env.OPENAI_API_KEY, - anthropicApiKey: process.env.ANTHROPIC_API_KEY, - copilotGithubToken: process.env.COPILOT_GITHUB_TOKEN, - copilotProviderApiKey: process.env.COPILOT_PROVIDER_API_KEY, + openaiApiKey: process.env[OPENAI_ENV.KEY], + anthropicApiKey: process.env[ANTHROPIC_ENV.KEY], + copilotGithubToken: process.env[COPILOT_ENV.GITHUB_TOKEN], + copilotProviderApiKey: process.env[COPILOT_ENV.PROVIDER_API_KEY], copilotProviderType: - (options.copilotProviderType as string | undefined) || process.env.COPILOT_PROVIDER_TYPE, + (options.copilotProviderType as string | undefined) || process.env[COPILOT_ENV.PROVIDER_TYPE], copilotProviderBaseUrl: - (options.copilotProviderBaseUrl as string | undefined) || process.env.COPILOT_PROVIDER_BASE_URL, - geminiApiKey: process.env.GEMINI_API_KEY, + (options.copilotProviderBaseUrl as string | undefined) || process.env[COPILOT_ENV.PROVIDER_BASE_URL], + geminiApiKey: process.env[GEMINI_ENV.KEY], copilotApiTarget: resolvedCopilotApiTarget, copilotApiBasePath: resolvedCopilotApiBasePath, copilotByokExtraHeaders: options.copilotByokExtraHeaders as Record | undefined, copilotByokExtraBodyFields: options.copilotByokExtraBodyFields as Record | undefined, copilotByokSessionId: options.copilotByokSessionId as string | undefined, openaiApiTarget: - (options.openaiApiTarget as string | undefined) || process.env.OPENAI_API_TARGET, + (options.openaiApiTarget as string | undefined) || process.env[OPENAI_ENV.TARGET], openaiApiBasePath: - (options.openaiApiBasePath as string | undefined) || process.env.OPENAI_API_BASE_PATH, + (options.openaiApiBasePath as string | undefined) || process.env[OPENAI_ENV.BASE_PATH], anthropicApiTarget: - (options.anthropicApiTarget as string | undefined) || process.env.ANTHROPIC_API_TARGET, + (options.anthropicApiTarget as string | undefined) || process.env[ANTHROPIC_ENV.TARGET], anthropicApiBasePath: - (options.anthropicApiBasePath as string | undefined) || process.env.ANTHROPIC_API_BASE_PATH, + (options.anthropicApiBasePath as string | undefined) || process.env[ANTHROPIC_ENV.BASE_PATH], openaiApiAuthHeader: - (options.openaiApiAuthHeader as string | undefined) || process.env.AWF_OPENAI_AUTH_HEADER, + (options.openaiApiAuthHeader as string | undefined) || process.env[OPENAI_ENV.AUTH_HEADER], anthropicApiAuthHeader: - (options.anthropicApiAuthHeader as string | undefined) || process.env.AWF_ANTHROPIC_AUTH_HEADER, + (options.anthropicApiAuthHeader as string | undefined) || process.env[ANTHROPIC_ENV.AUTH_HEADER], anthropicTokenUrl: (options.anthropicTokenUrl as string | undefined) || process.env.AWF_AUTH_ANTHROPIC_TOKEN_URL, geminiApiTarget: - (options.geminiApiTarget as string | undefined) || process.env.GEMINI_API_TARGET, + (options.geminiApiTarget as string | undefined) || process.env[GEMINI_ENV.TARGET], geminiApiBasePath: - (options.geminiApiBasePath as string | undefined) || process.env.GEMINI_API_BASE_PATH, + (options.geminiApiBasePath as string | undefined) || process.env[GEMINI_ENV.BASE_PATH], difcProxyHost: options.difcProxyHost as string | undefined, difcProxyCaCert: options.difcProxyCaCert as string | undefined, githubToken: process.env.GITHUB_TOKEN || process.env.GH_TOKEN, diff --git a/src/services/api-proxy-service-config.ts b/src/services/api-proxy-service-config.ts index 43ac663e..c3203005 100644 --- a/src/services/api-proxy-service-config.ts +++ b/src/services/api-proxy-service-config.ts @@ -9,6 +9,7 @@ import { getConfigEnvValue, getLowerCaseProcessEnvValue, pickEnvVars } from '../ import { NetworkConfig, ImageBuildConfig } from './squid-service'; import { applyHostPathPrefixToVolumes } from './host-path-prefix'; import { buildContainerSecurityHardening } from './service-security'; +import { OPENAI_ENV, ANTHROPIC_ENV, GEMINI_ENV, COPILOT_ENV } from '../api-proxy-env-constants'; interface ApiProxyServiceConfigParams { config: WrapperConfig; @@ -22,17 +23,17 @@ interface ApiProxyServiceConfigParams { * Centralizes the repetitive per-provider target/basePath conditional env generation. */ function buildProviderTargetEnv(config: WrapperConfig): Record { - const copilotProviderType = config.copilotProviderType || getConfigEnvValue(config, 'COPILOT_PROVIDER_TYPE'); - const copilotProviderBaseUrl = config.copilotProviderBaseUrl || getConfigEnvValue(config, 'COPILOT_PROVIDER_BASE_URL'); + const copilotProviderType = config.copilotProviderType || getConfigEnvValue(config, COPILOT_ENV.PROVIDER_TYPE); + const copilotProviderBaseUrl = config.copilotProviderBaseUrl || getConfigEnvValue(config, COPILOT_ENV.PROVIDER_BASE_URL); const copilotProviderApiKey = config.copilotProviderApiKey; const env: Record = {}; const providers: Array<{ target?: string; basePath?: string; envTarget: string; envBasePath: string; stripTarget?: boolean }> = [ - { target: config.copilotApiTarget, basePath: config.copilotApiBasePath, envTarget: 'COPILOT_API_TARGET', envBasePath: 'COPILOT_API_BASE_PATH', stripTarget: true }, - { target: config.openaiApiTarget, basePath: config.openaiApiBasePath, envTarget: 'OPENAI_API_TARGET', envBasePath: 'OPENAI_API_BASE_PATH', stripTarget: true }, - { target: config.anthropicApiTarget, basePath: config.anthropicApiBasePath, envTarget: 'ANTHROPIC_API_TARGET', envBasePath: 'ANTHROPIC_API_BASE_PATH', stripTarget: true }, - { target: config.geminiApiTarget, basePath: config.geminiApiBasePath, envTarget: 'GEMINI_API_TARGET', envBasePath: 'GEMINI_API_BASE_PATH', stripTarget: true }, + { target: config.copilotApiTarget, basePath: config.copilotApiBasePath, envTarget: COPILOT_ENV.API_TARGET, envBasePath: COPILOT_ENV.API_BASE_PATH, stripTarget: true }, + { target: config.openaiApiTarget, basePath: config.openaiApiBasePath, envTarget: OPENAI_ENV.TARGET, envBasePath: OPENAI_ENV.BASE_PATH, stripTarget: true }, + { target: config.anthropicApiTarget, basePath: config.anthropicApiBasePath, envTarget: ANTHROPIC_ENV.TARGET, envBasePath: ANTHROPIC_ENV.BASE_PATH, stripTarget: true }, + { target: config.geminiApiTarget, basePath: config.geminiApiBasePath, envTarget: GEMINI_ENV.TARGET, envBasePath: GEMINI_ENV.BASE_PATH, stripTarget: true }, ]; for (const { target, basePath, envTarget, envBasePath, stripTarget } of providers) { @@ -41,9 +42,9 @@ function buildProviderTargetEnv(config: WrapperConfig): Record { } // Copilot-specific provider passthrough - if (copilotProviderType) env.COPILOT_PROVIDER_TYPE = copilotProviderType; - if (copilotProviderBaseUrl) env.COPILOT_PROVIDER_BASE_URL = copilotProviderBaseUrl; - if (copilotProviderApiKey) env.COPILOT_PROVIDER_API_KEY = copilotProviderApiKey; + if (copilotProviderType) env[COPILOT_ENV.PROVIDER_TYPE] = copilotProviderType; + if (copilotProviderBaseUrl) env[COPILOT_ENV.PROVIDER_BASE_URL] = copilotProviderBaseUrl; + if (copilotProviderApiKey) env[COPILOT_ENV.PROVIDER_API_KEY] = copilotProviderApiKey; // Pre-startup model validation (non-sensitive config value). // Prefer explicit requestedModel, but fall back to COPILOT_MODEL when present so @@ -101,10 +102,10 @@ export function buildApiProxyServiceConfig(params: ApiProxyServiceConfigParams): ), environment: { // Pass API keys securely to sidecar (not visible to agent) - ...(config.openaiApiKey && { OPENAI_API_KEY: config.openaiApiKey }), - ...(config.anthropicApiKey && { ANTHROPIC_API_KEY: config.anthropicApiKey }), - ...(config.copilotGithubToken && { COPILOT_GITHUB_TOKEN: config.copilotGithubToken }), - ...(config.geminiApiKey && { GEMINI_API_KEY: config.geminiApiKey }), + ...(config.openaiApiKey && { [OPENAI_ENV.KEY]: config.openaiApiKey }), + ...(config.anthropicApiKey && { [ANTHROPIC_ENV.KEY]: config.anthropicApiKey }), + ...(config.copilotGithubToken && { [COPILOT_ENV.GITHUB_TOKEN]: config.copilotGithubToken }), + ...(config.geminiApiKey && { [GEMINI_ENV.KEY]: config.geminiApiKey }), // Configurable API targets (for GHES/GHEC / custom endpoints) // Strip any scheme prefix — server.js also normalizes defensively, but // stripping here prevents a scheme-prefixed hostname from reaching the @@ -259,8 +260,8 @@ export function buildApiProxyServiceConfig(params: ApiProxyServiceConfigParams): 'AWF_ANTHROPIC_STRIP_ANSI', ), // Custom auth header names for internal AI gateways - ...(config.openaiApiAuthHeader && { AWF_OPENAI_AUTH_HEADER: config.openaiApiAuthHeader }), - ...(config.anthropicApiAuthHeader && { AWF_ANTHROPIC_AUTH_HEADER: config.anthropicApiAuthHeader }), + ...(config.openaiApiAuthHeader && { [OPENAI_ENV.AUTH_HEADER]: config.openaiApiAuthHeader }), + ...(config.anthropicApiAuthHeader && { [ANTHROPIC_ENV.AUTH_HEADER]: config.anthropicApiAuthHeader }), ...(config.anthropicTokenUrl && { AWF_AUTH_ANTHROPIC_TOKEN_URL: config.anthropicTokenUrl }), // NOTE: AWF_ANTHROPIC_TRANSFORM_FILE is intentionally NOT forwarded from the host. // The api-proxy container holds live API credentials; loading arbitrary host-side JS