diff --git a/containers/api-proxy/Dockerfile b/containers/api-proxy/Dockerfile index 505ec49c..4aa3f8b0 100644 --- a/containers/api-proxy/Dockerfile +++ b/containers/api-proxy/Dockerfile @@ -27,7 +27,8 @@ USER apiproxy # 10000 - OpenAI API proxy (also serves as health check endpoint) # 10001 - Anthropic API proxy # 10002 - GitHub Copilot API proxy -EXPOSE 10000 10001 10002 +# 10004 - OpenCode API proxy (routes to Anthropic) +EXPOSE 10000 10001 10002 10004 # Redirect stdout/stderr to log file for persistence # Use shell form to enable redirection and tee for both file and console diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js index aec9ab00..e6bdccd3 100644 --- a/containers/api-proxy/server.js +++ b/containers/api-proxy/server.js @@ -258,6 +258,35 @@ if (COPILOT_GITHUB_TOKEN) { console.log('[API Proxy] GitHub Copilot proxy listening on port 10002'); }); } + +// OpenCode API proxy (port 10004) — routes to Anthropic (default BYOK provider) +// OpenCode gets a separate port from Claude (10001) for per-engine rate limiting, +// metrics isolation, and future provider routing (OpenCode is BYOK and may route +// to different providers in the future based on model prefix). +if (ANTHROPIC_API_KEY) { + const opencodeServer = http.createServer((req, res) => { + if (req.url === '/health' && req.method === 'GET') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'healthy', service: 'opencode-proxy' })); + return; + } + + const logMethod = sanitizeForLog(req.method); + const logUrl = sanitizeForLog(req.url); + console.log(`[OpenCode Proxy] ${logMethod} ${logUrl}`); + console.log('[OpenCode Proxy] Injecting x-api-key header with ANTHROPIC_API_KEY'); + const anthropicHeaders = { 'x-api-key': ANTHROPIC_API_KEY }; + if (!req.headers['anthropic-version']) { + anthropicHeaders['anthropic-version'] = '2023-06-01'; + } + proxyRequest(req, res, 'api.anthropic.com', anthropicHeaders); + }); + + opencodeServer.listen(10004, '0.0.0.0', () => { + console.log('[API Proxy] OpenCode proxy listening on port 10004 (-> Anthropic)'); + }); +} + // Graceful shutdown process.on('SIGTERM', () => { console.log('[API Proxy] Received SIGTERM, shutting down gracefully...'); diff --git a/src/host-iptables.ts b/src/host-iptables.ts index 5f130b73..4d97689f 100644 --- a/src/host-iptables.ts +++ b/src/host-iptables.ts @@ -443,11 +443,12 @@ export async function setupHostIptables(squidIp: string, squidPort: number, dnsS ]); // 5b. Allow traffic to API proxy sidecar (when enabled) - // Allow all API proxy ports (OpenAI, Anthropic, GitHub Copilot). + // Allow all API proxy ports (OpenAI, Anthropic, GitHub Copilot, OpenCode). // The sidecar itself routes through Squid, so domain whitelisting is still enforced. if (apiProxyIp) { - const minPort = Math.min(API_PROXY_PORTS.OPENAI, API_PROXY_PORTS.ANTHROPIC, API_PROXY_PORTS.COPILOT); - const maxPort = Math.max(API_PROXY_PORTS.OPENAI, API_PROXY_PORTS.ANTHROPIC, API_PROXY_PORTS.COPILOT); + const allPorts = Object.values(API_PROXY_PORTS); + const minPort = Math.min(...allPorts); + const maxPort = Math.max(...allPorts); logger.debug(`Allowing traffic to API proxy sidecar at ${apiProxyIp}:${minPort}-${maxPort}`); await execa('iptables', [ '-t', 'filter', '-A', CHAIN_NAME, diff --git a/src/types.ts b/src/types.ts index 4e95b517..81a891d2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,6 +32,13 @@ export const API_PROXY_PORTS = { * @see containers/api-proxy/server.js */ COPILOT: 10002, + + /** + * OpenCode API proxy port (routes to Anthropic by default) + * OpenCode is BYOK — defaults to Anthropic as the primary provider + * @see containers/api-proxy/server.js + */ + OPENCODE: 10004, } as const; /** @@ -432,6 +439,7 @@ export interface WrapperConfig { * - http://api-proxy:10000 - OpenAI API proxy (for Codex) {@link API_PROXY_PORTS.OPENAI} * - http://api-proxy:10001 - Anthropic API proxy (for Claude) {@link API_PROXY_PORTS.ANTHROPIC} * - http://api-proxy:10002 - GitHub Copilot API proxy {@link API_PROXY_PORTS.COPILOT} + * - http://api-proxy:10004 - OpenCode API proxy (routes to Anthropic) {@link API_PROXY_PORTS.OPENCODE} * * When the corresponding API key is provided, the following environment * variables are set in the agent container: