Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion containers/api-proxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions containers/api-proxy/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,30 @@
console.log('[API Proxy] GitHub Copilot proxy listening on port 10002');
});
}

// OpenCode API proxy (port 10004) — routes to Anthropic (default BYOK provider)
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;
}

console.log(`[OpenCode Proxy] ${sanitizeForLog(req.method)} ${sanitizeForLog(req.url)}`);
Comment thread Fixed
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)');
});
}
Comment on lines +262 to +288

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenCode proxy (port 10004) is functionally identical to the Claude proxy (port 10001) - both route to api.anthropic.com with the same ANTHROPIC_API_KEY and apply the same header transformations. Consider documenting why two separate proxies are needed for the same endpoint, or if there are planned differences in behavior, consider implementing them now or adding TODO comments explaining the future differentiation. This will help maintainers understand the architecture decision.

Copilot uses AI. Check for mistakes.
Comment on lines +262 to +288

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenCode proxy server is set up on port 10004, but there's no corresponding environment variable configuration in docker-manager.ts to tell OpenCode agents how to use this proxy. Other proxies set environment variables like OPENAI_BASE_URL (line 1041), ANTHROPIC_BASE_URL (line 1045), and COPILOT_API_URL (line 1059). Consider adding similar configuration for OpenCode, such as setting an OPENCODE_BASE_URL or OPENCODE_API_URL environment variable when anthropicApiKey is provided, to make the proxy automatically discoverable by OpenCode agents. If this configuration will be added in the companion PR, consider adding a TODO comment noting this.

Copilot uses AI. Check for mistakes.

// Graceful shutdown
process.on('SIGTERM', () => {
console.log('[API Proxy] Received SIGTERM, shutting down gracefully...');
Expand Down
7 changes: 4 additions & 3 deletions src/host-iptables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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:
Expand Down
Loading