feat: add API proxy port 10004 for OpenCode engine#1055
Conversation
Add OpenCode API proxy support on port 10004, routing to Anthropic API (OpenCode's default BYOK provider). Dynamic port range calculation in host-iptables ensures future ports are auto-included. - src/types.ts: Add OPENCODE port 10004 to API_PROXY_PORTS - containers/api-proxy/server.js: Add OpenCode proxy listener (-> Anthropic) - containers/api-proxy/Dockerfile: Expose port 10004 - src/host-iptables.ts: Use Object.values() for dynamic port range Companion to github/gh-aw#18403 (OpenCode engine integration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (2 files)
Coverage comparison generated by |
Build Test: Bun ✅
Overall: PASS Tested with Bun v1.3.9
|
Go Build Test Results
Overall: ✅ PASS
|
Deno Build Test Results
Overall: ✅ PASS
|
Rust Build Test Results
Overall: ✅ PASS
|
Smoke Test Results — Copilot Engine
Overall: PASS —
|
🟢 Build Test: Node.js — PASS
Overall: ✅ PASS
|
.NET Build Test Results
Overall: PASS Run outputhello-world: json-parse: {
"Name": "AWF Test",
"Version": 1,
"Success": true
}
Name: AWF Test, Success: True
|
There was a problem hiding this comment.
Pull request overview
This PR adds infrastructure for an OpenCode API proxy on port 10004 that routes to Anthropic's API, supporting OpenCode as a provider-agnostic (BYOK) AI coding agent. The PR improves maintainability by making the iptables port range calculation dynamic instead of hardcoded.
Changes:
- Added OPENCODE port (10004) to API_PROXY_PORTS constant with documentation
- Implemented OpenCode proxy server on port 10004 that reuses ANTHROPIC_API_KEY credentials
- Made host-level iptables firewall rules calculate port ranges dynamically using Object.values()
- Exposed port 10004 in api-proxy Dockerfile
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/types.ts | Adds OPENCODE: 10004 to API_PROXY_PORTS constant and updates WrapperConfig documentation |
| containers/api-proxy/server.js | Implements OpenCode proxy listener on port 10004 routing to api.anthropic.com |
| containers/api-proxy/Dockerfile | Exposes port 10004 for OpenCode proxy |
| src/host-iptables.ts | Refactors iptables rules to use dynamic port range calculation from API_PROXY_PORTS |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 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)}`); | ||
| 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)'); | ||
| }); | ||
| } |
There was a problem hiding this comment.
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.
| // 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)}`); | ||
| 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)'); | ||
| }); | ||
| } |
There was a problem hiding this comment.
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.
☕ Java Build Test Results
Overall: ✅ PASS All Java projects compiled and tested successfully via Maven with Squid proxy (
|
|
Smoke test results (overall: FAIL)
|
|
Smoke test results for Claude (run 22415250328):
Overall: PASS
|
Chroot Version Comparison Results
Overall: ❌ FAILED — Python and Node.js versions differ between host and chroot environment.
|
C++ Build Test Results
Overall: PASS ✅
|
- Fix log injection: extract sanitized values before template literal - Add comment explaining why OpenCode gets a separate port from Claude (rate limiting isolation, metrics, future multi-provider routing) - docker-manager.ts env var not needed: gh-aw passes ANTHROPIC_BASE_URL via --env-all at the GitHub Actions level Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
||
| const logMethod = sanitizeForLog(req.method); | ||
| const logUrl = sanitizeForLog(req.url); | ||
| console.log(`[OpenCode Proxy] ${logMethod} ${logUrl}`); |
Check warning
Code scanning / CodeQL
Log injection Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 23 hours ago
In general, to fix log injection, all user-controlled data should be sanitized before logging: remove or normalize line breaks and control characters, optionally collapse or escape other whitespace that could visually obscure boundaries, and clearly delimit user input in log messages so operators can see what part is untrusted.
For this specific code, the best low-impact fix is:
- Enhance
sanitizeForLogto also strip Unicode line/paragraph separators and normalize tabs to spaces, while still removing ASCII control chars and limiting length to 200 characters. - Keep using
sanitizeForLogonreq.methodandreq.urlas already done. - Slightly adjust the log format to clearly bracket user-controlled fields, e.g.
"[OpenCode Proxy] method=%s url=%s"where the method and URL are enclosed in quotes. This doesn’t change functionality but makes the boundary between fixed text and user data explicit.
Concretely in containers/api-proxy/server.js:
- Modify the
sanitizeForLogimplementation around lines 38–43 to:- Convert input to string safely.
- Remove ASCII control chars as before.
- Remove Unicode line (
\u2028) and paragraph (\u2029) separators. - Replace tab characters with a single space.
- Update the logging line around 276 to include clear delimiters around the sanitized values, e.g.:
- From:
console.log(`[OpenCode Proxy] ${logMethod} ${logUrl}`); - To:
console.log(`[OpenCode Proxy] method="${logMethod}" url="${logUrl}"`);
- From:
No new imports are needed; all changes are inside the already-present helper and log call.
| @@ -37,9 +37,16 @@ | ||
|
|
||
| /** Sanitize a string for safe logging (strip control chars, limit length). */ | ||
| function sanitizeForLog(str) { | ||
| if (typeof str !== 'string') return ''; | ||
| if (str == null) return ''; | ||
| const s = String(str); | ||
| // Remove ASCII control chars (including \n, \r, \t, etc.) and DEL | ||
| // eslint-disable-next-line no-control-regex | ||
| return str.replace(/[\x00-\x1f\x7f]/g, '').slice(0, 200); | ||
| const noAsciiControls = s.replace(/[\x00-\x1f\x7f]/g, ''); | ||
| // Also remove Unicode line and paragraph separators to prevent multi-line logs | ||
| const noUnicodeSeparators = noAsciiControls.replace(/[\u2028\u2029]/g, ''); | ||
| // Normalize any remaining tabs to a single space for readability | ||
| const normalizedWhitespace = noUnicodeSeparators.replace(/\t+/g, ' '); | ||
| return normalizedWhitespace.slice(0, 200); | ||
| } | ||
|
|
||
| // Read API keys from environment (set by docker-compose) | ||
| @@ -273,7 +279,7 @@ | ||
|
|
||
| const logMethod = sanitizeForLog(req.method); | ||
| const logUrl = sanitizeForLog(req.url); | ||
| console.log(`[OpenCode Proxy] ${logMethod} ${logUrl}`); | ||
| console.log(`[OpenCode Proxy] method="${logMethod}" url="${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']) { |
Bun Build Test Results
Overall: PASS ✅ Bun version: 1.3.9
|
|
Smoke Test Results — PASS
|
C++ Build Test Results
Overall: PASS
|
|
🤖 Smoke test results for ✅ GitHub MCP — Last 2 merged PRs: #1036 "docs: add integration test coverage guide with gap analysis", #1035 "feat: group --help flags by category, hide dev-only options" Overall: PASS
|
Deno Build Test Results
Overall: ✅ PASS
|
🦀 Rust Build Test Results
Overall: ✅ PASS
|
Go Build Test Results ✅
Overall: PASS
|
.NET Build Test Results
Overall: PASS Run outputhello-world:
|
|
Smoke Test Results
|
Node.js Build Test Results
Overall: ✅ PASS
|
☕ Java Build Test Results
Overall: ✅ PASS All Java projects compiled and tested successfully via Maven with the AWF proxy.
|
Chroot Version Comparison Results
Result: Not all tests passed. Go versions match, but Python and Node.js versions differ between host and chroot environments.
|
…r docs Update documentation to reflect the addition of OpenCode engine support on port 10004, as introduced in PR #1055: - Update overview to list all four supported providers - Add OpenCode to architecture diagram and traffic flow - Add OpenCode usage example with note about separate port rationale - Document OpenCode header injection (same as Anthropic) - Add port 10004 to container configuration - Update limitations to reflect all supported providers
Summary
host-iptables.tsusingObject.values(API_PROXY_PORTS)— future-proof for new enginesFiles changed
src/types.tsOPENCODE: 10004toAPI_PROXY_PORTS, update JSDoccontainers/api-proxy/server.jscontainers/api-proxy/Dockerfilesrc/host-iptables.tsObject.values()instead of hardcoded port namesContext
OpenCode is a provider-agnostic (BYOK) AI coding agent being added to gh-aw. It defaults to Anthropic as its LLM provider, so port 10004 routes to
api.anthropic.comwithx-api-keyauth — same pattern as the existing Anthropic proxy on port 10001.Companion PR: github/gh-aw#18403
Test plan
npx tsc --noEmit)🤖 Generated with Claude Code