Skip to content

feat: add API proxy port 10004 for OpenCode engine - #1055

Merged
Mossaka merged 2 commits into
mainfrom
feat/opencode-api-proxy
Feb 25, 2026
Merged

feat: add API proxy port 10004 for OpenCode engine#1055
Mossaka merged 2 commits into
mainfrom
feat/opencode-api-proxy

Conversation

@Mossaka

Copy link
Copy Markdown
Contributor

Summary

  • Add OpenCode API proxy on port 10004, routing to Anthropic API (OpenCode's default BYOK provider)
  • Dynamic port range calculation in host-iptables.ts using Object.values(API_PROXY_PORTS) — future-proof for new engines
  • Expose port 10004 in api-proxy Dockerfile

Files changed

FileChange
src/types.tsAdd OPENCODE: 10004 to API_PROXY_PORTS, update JSDoc
containers/api-proxy/server.jsAdd OpenCode proxy listener (port 10004 → api.anthropic.com)
containers/api-proxy/DockerfileExpose port 10004
src/host-iptables.tsDynamic port range via Object.values() instead of hardcoded port names

Context

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.com with x-api-key auth — same pattern as the existing Anthropic proxy on port 10001.

Companion PR: github/gh-aw#18403

Test plan

  • TypeScript compiles clean (npx tsc --noEmit)
  • Lint passes (0 errors, pre-existing warnings only)
  • Existing tests pass
  • Verify port 10004 is accessible from agent container in integration test

🤖 Generated with Claude Code

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>
CopilotAI review requested due to automatic review settings February 25, 2026 20:46
@github-actions

github-actionsBot commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

✅ Coverage Check Passed

Overall Coverage

MetricBasePRDelta
Lines82.39%82.50%📈 +0.11%
Statements82.32%82.42%📈 +0.10%
Functions82.74%82.74%➡️ +0.00%
Branches74.55%74.65%📈 +0.10%
📁 Per-file Coverage Changes (2 files)
FileLines (Before → After)Statements (Before → After)
src/host-iptables.ts77.9% → 77.5% (-0.38%)78.1% → 77.7% (-0.38%)
src/docker-manager.ts83.6% → 84.1% (+0.56%)82.8% → 83.4% (+0.54%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Comment threadcontainers/api-proxy/server.js Fixed
@github-actions

Copy link
Copy Markdown
Contributor

Build Test: Bun ✅

ProjectInstallTestsStatus
elysia1/1PASS
hono1/1PASS

Overall: PASS

Tested with Bun v1.3.9

Generated by Build Test Bun for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Go Build Test Results

ProjectDownloadTestsStatus
colorPASS✅ PASS
envPASS✅ PASS
uuidPASS✅ PASS

Overall: ✅ PASS

Generated by Build Test Go for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Deno Build Test Results

ProjectTestsStatus
oak1/1✅ PASS
std1/1✅ PASS

Overall: ✅ PASS

Generated by Build Test Deno for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Rust Build Test Results

ProjectBuildTestsStatus
fd1/1PASS
zoxide1/1PASS

Overall: ✅ PASS

Generated by Build Test Rust for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results — Copilot Engine

TestStatus
GitHub MCP: last 2 merged PRs#1036docs: add integration test coverage guide with gap analysis, #1035feat: group --help flags by category, hide dev-only options
Playwright: github.com title contains "GitHub"
File write: smoke-test-copilot-22415250280.txt
Bash: file read-back

Overall: PASS@Mossaka

📰 BREAKING: Report filed by Smoke Copilot for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

🟢 Build Test: Node.js — PASS

ProjectInstallTestsStatus
clsxPASS✅ PASS
execaPASS✅ PASS
p-limitPASS✅ PASS

Overall: ✅ PASS

Generated by Build Test Node.js for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

.NET Build Test Results

ProjectRestoreBuildRunStatus
hello-worldPASS
json-parsePASS

Overall: PASS

Run output

hello-world:

Hello, World!

json-parse:

{
"Name": "AWF Test",
"Version": 1,
"Success": true
}
Name: AWF Test, Success: True

Generated by Build Test .NET for issue #1055

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

FileDescription
src/types.tsAdds OPENCODE: 10004 to API_PROXY_PORTS constant and updates WrapperConfig documentation
containers/api-proxy/server.jsImplements OpenCode proxy listener on port 10004 routing to api.anthropic.com
containers/api-proxy/DockerfileExposes port 10004 for OpenCode proxy
src/host-iptables.tsRefactors 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.

Comment on lines +262 to +283
// 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)');
});
}

CopilotAIFeb 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 +283
// 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)');
});
}

CopilotAIFeb 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.
@github-actions

Copy link
Copy Markdown
Contributor

☕ Java Build Test Results

ProjectCompileTestsStatus
gson1/1PASS
caffeine1/1PASS

Overall: ✅ PASS

All Java projects compiled and tested successfully via Maven with Squid proxy (172.30.0.10:3128).

Generated by Build Test Java for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Smoke test results (overall: FAIL)

  1. GitHub MCP merged PRs ✅ - docs: add integration test coverage guide with gap analysis; feat: group --help flags by category, hide dev-only options
  2. safeinputs-gh PR list ✅ - Remove --allow-full-filesystem-access flag; feat: add API proxy port 10004 for OpenCode engine
  3. Playwright title check ✅
  4. Tavily web search ❌ (tool unavailable)
  5. File write ✅
  6. File read ✅
  7. Discussion comment ✅
  8. Build (npm ci && npm run build) ✅

🔮 The oracle has spoken through Smoke Codex for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Smoke test results for Claude (run 22415250328):

TestResult
GitHub MCP: #1036 docs: add integration test coverage guide with gap analysis
GitHub MCP: #1035 feat: group --help flags by category, hide dev-only options
Playwright: github.com title contains "GitHub"
File write/read

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

RuntimeHost VersionChroot VersionMatch?
PythonPython 3.12.12Python 3.12.3❌ No
Node.jsv24.13.1v20.20.0❌ No
Gogo1.22.12go1.22.12✅ Yes

Overall: ❌ FAILED — Python and Node.js versions differ between host and chroot environment.

Tested by Smoke Chroot for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

C++ Build Test Results

ProjectCMakeBuildStatus
fmtPASS
jsonPASS

Overall: PASS

Generated by Build Test C++ for issue #1055

- 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

Log entry depends on a
user-provided value
.

Copilot Autofix

AI 6 months 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:

  1. Enhance sanitizeForLog to also strip Unicode line/paragraph separators and normalize tabs to spaces, while still removing ASCII control chars and limiting length to 200 characters.
  2. Keep using sanitizeForLog on req.method and req.url as already done.
  3. 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 sanitizeForLog implementation 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}"`);

No new imports are needed; all changes are inside the already-present helper and log call.


Suggested changeset 1
containers/api-proxy/server.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js
--- a/containers/api-proxy/server.js
+++ b/containers/api-proxy/server.js
@@ -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']) {
EOF
@@ -37,9 +37,16 @@

/** Sanitize a string for safe logging (strip control chars, limit length). */
functionsanitizeForLog(str){
if(typeofstr!=='string')return'';
if(str==null)return'';
consts=String(str);
// Remove ASCII control chars (including \n, \r, \t, etc.) and DEL
// eslint-disable-next-line no-control-regex
returnstr.replace(/[\x00-\x1f\x7f]/g,'').slice(0,200);
constnoAsciiControls=s.replace(/[\x00-\x1f\x7f]/g,'');
// Also remove Unicode line and paragraph separators to prevent multi-line logs
constnoUnicodeSeparators=noAsciiControls.replace(/[\u2028\u2029]/g,'');
// Normalize any remaining tabs to a single space for readability
constnormalizedWhitespace=noUnicodeSeparators.replace(/\t+/g,' ');
returnnormalizedWhitespace.slice(0,200);
}

// Read API keys from environment (set by docker-compose)
@@ -273,7 +279,7 @@

constlogMethod=sanitizeForLog(req.method);
constlogUrl=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');
constanthropicHeaders={'x-api-key': ANTHROPIC_API_KEY};
if(!req.headers['anthropic-version']){
Copilot is powered by AI and may make mistakes. Always verify output.
@github-actions

Copy link
Copy Markdown
Contributor

Bun Build Test Results

ProjectInstallTestsStatus
elysia1/1PASS
hono1/1PASS

Overall: PASS

Bun version: 1.3.9

Generated by Build Test Bun for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results — PASS

💥 [THE END] — Illustrated by Smoke Claude for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

C++ Build Test Results

ProjectCMakeBuildStatus
fmtPASS
jsonPASS

Overall: PASS

Generated by Build Test C++ for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Smoke test results for @Mossaka (no assignees):

✅ 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"
✅ Playwright — github.com title contains "GitHub"
✅ File Write — /tmp/gh-aw/agent/smoke-test-copilot-22416501482.txt created
✅ Bash — file verified via cat

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Deno Build Test Results

ProjectTestsStatus
oak1/1✅ PASS
std1/1✅ PASS

Overall: ✅ PASS

Generated by Build Test Deno for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

🦀 Rust Build Test Results

ProjectBuildTestsStatus
fd1/1PASS
zoxide1/1PASS

Overall: ✅ PASS

Generated by Build Test Rust for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Go Build Test Results ✅

ProjectDownloadTestsStatus
colorPASS✅ PASS
envPASS✅ PASS
uuidPASS✅ PASS

Overall: PASS

Generated by Build Test Go for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

.NET Build Test Results

ProjectRestoreBuildRunStatus
hello-worldPASS
json-parsePASS

Overall: PASS

Run output

hello-world:

Hello, World!
```
**json-parse:**
```
{
"Name": "AWF Test",
"Version": 1,
"Success": true
}
Name: AWF Test, Success: True

Generated by Build Test .NET for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results
✅ GitHub MCP merged PRs: docs: add integration test coverage guide with gap analysis; feat: group --help flags by category, hide dev-only options
✅ safeinputs-gh pr list
✅ Playwright title contains "GitHub"
❌ Tavily web search (tool unavailable)
✅ File write
✅ Bash cat
✅ Discussion query + comment
✅ npm ci && npm run build
Overall: FAIL

🔮 The oracle has spoken through Smoke Codex for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Node.js Build Test Results

ProjectInstallTestsStatus
clsxPASS✅ PASS
execaPASS✅ PASS
p-limitPASS✅ PASS

Overall: ✅ PASS

Generated by Build Test Node.js for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

☕ Java Build Test Results

ProjectCompileTestsStatus
gson1/1PASS
caffeine1/1PASS

Overall: ✅ PASS

All Java projects compiled and tested successfully via Maven with the AWF proxy.

Generated by Build Test Java for issue #1055

@github-actions

Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

RuntimeHost VersionChroot VersionMatch?
PythonPython 3.12.12Python 3.12.3
Node.jsv24.13.1v20.20.0
Gogo1.22.12go1.22.12

Result: Not all tests passed. Go versions match, but Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot for issue #1055

@Mossaka
Mossaka merged commit 62e4d90 into mainFeb 25, 2026
92 checks passed
@Mossaka
Mossaka deleted the feat/opencode-api-proxy branch February 25, 2026 22:48
CopilotAI added a commit that referenced this pull request Feb 25, 2026
…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Mossaka@github-advanced-security