From d76b22c0867adc1f77ff304d35195e3eb55d036b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:51:21 +0000 Subject: [PATCH 1/3] Initial plan From 0686756fc2d8d089eba4bca2b257acc218d46edc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:59:34 +0000 Subject: [PATCH 2/3] fix: update OpenCode proxy to default to Copilot/OpenAI routing --- .github/workflows/smoke-opencode.md | 89 +++++++++++++++++++++++++++++ containers/api-proxy/server.js | 75 ++++++++++++++++++------ src/types.ts | 6 +- 3 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/smoke-opencode.md diff --git a/.github/workflows/smoke-opencode.md b/.github/workflows/smoke-opencode.md new file mode 100644 index 00000000..d67e6f6c --- /dev/null +++ b/.github/workflows/smoke-opencode.md @@ -0,0 +1,89 @@ +--- +description: Smoke test workflow that validates OpenCode engine functionality by testing AWF firewall capabilities +on: + roles: all + schedule: every 12h + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + reaction: "rocket" +permissions: + contents: read + issues: read + pull-requests: read + discussions: read +name: Smoke OpenCode +engine: opencode +strict: true +imports: + - shared/gh.md + - shared/reporting.md +network: + allowed: + - defaults + - github +tools: + cache-memory: true + github: + toolsets: [repos, pull_requests] + edit: + bash: + - "*" +safe-outputs: + threat-detection: + enabled: false + add-comment: + hide-older-comments: true + max: 2 + create-issue: + expires: 2h + close-older-issues: true + add-labels: + allowed: [smoke-opencode] + hide-comment: + messages: + footer: "> 🌐 *Transmitted by [{workflow_name}]({run_url})*" + run-started: "🌐 [{workflow_name}]({run_url}) is initializing on this {event_type}..." + run-success: "✅ [{workflow_name}]({run_url}) completed successfully. All systems nominal. 🚀" + run-failure: "❌ [{workflow_name}]({run_url}) {status}. Investigation required..." +timeout-minutes: 15 +post-steps: + - name: Validate safe outputs were invoked + run: | + OUTPUTS_FILE="${GH_AW_SAFE_OUTPUTS:-${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl}" + if [ ! -s "$OUTPUTS_FILE" ]; then + echo "::error::No safe outputs were invoked. Smoke tests require the agent to call safe output tools." + exit 1 + fi + echo "Safe output entries found: $(wc -l < "$OUTPUTS_FILE")" + if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + if ! grep -q '"add_comment"' "$OUTPUTS_FILE"; then + echo "::error::Agent did not call add_comment on a pull_request trigger." + exit 1 + fi + echo "add_comment verified for PR trigger" + fi + echo "Safe output validation passed" +--- + +# Smoke Test: OpenCode Engine Validation + +**IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** + +## Test Requirements + +1. **GitHub MCP Testing**: Review the last 2 merged pull requests in `__GH_AW_GITHUB_REPOSITORY__` +2. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-${{ github.run_id }}.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) +3. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) +4. **Build AWF**: Run `npm ci && npm run build` to verify the agent can successfully build the AWF project. If the command fails, mark this test as ❌ and report the failure. +5. **Add Comment**: Use the `add_comment` tool to post a brief summary comment on the current pull request + +## Output + +**REQUIRED**: Call `add_comment` to post a brief comment (max 5-10 lines) on the current pull request (this is validated by the post-step check) containing: +- PR titles only (no descriptions) +- ✅ or ❌ for each test result +- Overall status: PASS or FAIL + +If all tests pass: +- Use the `add_labels` safe-output tool to add the label `smoke-opencode` to the pull request diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js index 27a8b0c1..6582d62d 100644 --- a/containers/api-proxy/server.js +++ b/containers/api-proxy/server.js @@ -1027,11 +1027,16 @@ if (require.main === module) { }); } - // 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) { + // OpenCode API proxy (port 10004) — dynamic provider routing + // Defaults to Copilot/OpenAI routing (OPENAI_API_KEY), with Anthropic as a BYOK fallback. + // OpenCode gets a separate port from Claude (10001) and Codex (10000) for per-engine + // rate limiting and metrics isolation. + // + // Credential priority (first available wins): + // 1. OPENAI_API_KEY → OpenAI/Copilot-compatible route (OPENAI_API_TARGET) + // 2. ANTHROPIC_API_KEY → Anthropic BYOK route (ANTHROPIC_API_TARGET) + // 3. COPILOT_GITHUB_TOKEN / COPILOT_API_KEY → Copilot route (COPILOT_API_TARGET) + if (OPENAI_API_KEY || ANTHROPIC_API_KEY || COPILOT_AUTH_TOKEN) { const opencodeServer = http.createServer((req, res) => { if (req.url === '/health' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); @@ -1046,26 +1051,62 @@ if (require.main === module) { method: logMethod, url: logUrl, }); - logRequest('info', 'opencode_proxy_header_injection', { - message: '[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'; + + if (OPENAI_API_KEY) { + logRequest('info', 'opencode_proxy_header_injection', { + message: '[OpenCode Proxy] Routing to OpenAI/Copilot via OPENAI_API_KEY', + target: OPENAI_API_TARGET, + }); + proxyRequest(req, res, OPENAI_API_TARGET, { + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + }, 'opencode', OPENAI_API_BASE_PATH); + } else if (ANTHROPIC_API_KEY) { + logRequest('info', 'opencode_proxy_header_injection', { + message: '[OpenCode Proxy] Routing to Anthropic via ANTHROPIC_API_KEY', + target: ANTHROPIC_API_TARGET, + }); + const anthropicHeaders = { 'x-api-key': ANTHROPIC_API_KEY }; + if (!req.headers['anthropic-version']) { + anthropicHeaders['anthropic-version'] = '2023-06-01'; + } + proxyRequest(req, res, ANTHROPIC_API_TARGET, anthropicHeaders, 'opencode', ANTHROPIC_API_BASE_PATH); + } else { + // COPILOT_AUTH_TOKEN only — route to Copilot API target + logRequest('info', 'opencode_proxy_header_injection', { + message: '[OpenCode Proxy] Routing to Copilot via COPILOT_AUTH_TOKEN', + target: COPILOT_API_TARGET, + }); + proxyRequest(req, res, COPILOT_API_TARGET, { + 'Authorization': `Bearer ${COPILOT_AUTH_TOKEN}`, + }, 'opencode'); } - proxyRequest(req, res, ANTHROPIC_API_TARGET, anthropicHeaders); }); opencodeServer.on('upgrade', (req, socket, head) => { - const anthropicHeaders = { 'x-api-key': ANTHROPIC_API_KEY }; - if (!req.headers['anthropic-version']) { - anthropicHeaders['anthropic-version'] = '2023-06-01'; + if (OPENAI_API_KEY) { + proxyWebSocket(req, socket, head, OPENAI_API_TARGET, { + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + }, 'opencode', OPENAI_API_BASE_PATH); + } else if (ANTHROPIC_API_KEY) { + const anthropicHeaders = { 'x-api-key': ANTHROPIC_API_KEY }; + if (!req.headers['anthropic-version']) { + anthropicHeaders['anthropic-version'] = '2023-06-01'; + } + proxyWebSocket(req, socket, head, ANTHROPIC_API_TARGET, anthropicHeaders, 'opencode', ANTHROPIC_API_BASE_PATH); + } else { + proxyWebSocket(req, socket, head, COPILOT_API_TARGET, { + 'Authorization': `Bearer ${COPILOT_AUTH_TOKEN}`, + }, 'opencode'); } - proxyWebSocket(req, socket, head, ANTHROPIC_API_TARGET, anthropicHeaders, 'opencode'); }); opencodeServer.listen(10004, '0.0.0.0', () => { - console.log(`[API Proxy] OpenCode proxy listening on port 10004 (-> Anthropic at ${ANTHROPIC_API_TARGET})`); + const routingTarget = OPENAI_API_KEY + ? `OpenAI/Copilot at ${OPENAI_API_TARGET}` + : ANTHROPIC_API_KEY + ? `Anthropic at ${ANTHROPIC_API_TARGET}` + : `Copilot at ${COPILOT_API_TARGET}`; + logRequest('info', 'server_start', { message: `OpenCode proxy listening on port 10004 (-> ${routingTarget})` }); }); } diff --git a/src/types.ts b/src/types.ts index 3098af27..3881787c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -40,8 +40,8 @@ export const API_PROXY_PORTS = { GEMINI: 10003, /** - * OpenCode API proxy port (routes to Anthropic by default) - * OpenCode is BYOK — defaults to Anthropic as the primary provider + * OpenCode API proxy port (defaults to Copilot/OpenAI routing; falls back to Anthropic) + * OpenCode is BYOK — credential priority: OPENAI_API_KEY > ANTHROPIC_API_KEY > COPILOT_AUTH_TOKEN * @see containers/api-proxy/server.js */ OPENCODE: 10004, @@ -611,7 +611,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} + * - http://api-proxy:10004 - OpenCode API proxy (defaults to Copilot/OpenAI routing) {@link API_PROXY_PORTS.OPENCODE} * * When the corresponding API key is provided, the following environment * variables are set in the agent container: From 4ec43116768805b2953ab91b51bcc42c56aa5574 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:01:16 +0000 Subject: [PATCH 3/3] fix: address code review - correct comment to use COPILOT_AUTH_TOKEN --- containers/api-proxy/server.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js index 6582d62d..edf9ec7c 100644 --- a/containers/api-proxy/server.js +++ b/containers/api-proxy/server.js @@ -1033,9 +1033,9 @@ if (require.main === module) { // rate limiting and metrics isolation. // // Credential priority (first available wins): - // 1. OPENAI_API_KEY → OpenAI/Copilot-compatible route (OPENAI_API_TARGET) - // 2. ANTHROPIC_API_KEY → Anthropic BYOK route (ANTHROPIC_API_TARGET) - // 3. COPILOT_GITHUB_TOKEN / COPILOT_API_KEY → Copilot route (COPILOT_API_TARGET) + // 1. OPENAI_API_KEY → OpenAI/Copilot-compatible route (OPENAI_API_TARGET) + // 2. ANTHROPIC_API_KEY → Anthropic BYOK route (ANTHROPIC_API_TARGET) + // 3. COPILOT_AUTH_TOKEN → Copilot route (COPILOT_API_TARGET) if (OPENAI_API_KEY || ANTHROPIC_API_KEY || COPILOT_AUTH_TOKEN) { const opencodeServer = http.createServer((req, res) => { if (req.url === '/health' && req.method === 'GET') {