Skip to content

refactor: split api-proxy server.js into focused modules#2559

Merged
lpcox merged 4 commits intomainfrom
copilot/refactor-split-api-proxy-server
May 5, 2026
Merged

refactor: split api-proxy server.js into focused modules#2559
lpcox merged 4 commits intomainfrom
copilot/refactor-split-api-proxy-server

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

containers/api-proxy/server.js had grown to 1,284 lines with six distinct responsibilities colocated. The security-critical credential injection path (proxyRequest) was particularly hard to audit mixed in with rate limiting, model discovery, and management endpoints.

New modules

File Lines Responsibility
proxy-request.js ~510 proxyRequest, proxyWebSocket, checkRateLimit, isValidRequestId, extractBillingHeaders — the entire credential injection, SSRF-prevention, and billing-header extraction path
model-discovery.js ~220 fetchJson, httpProbe, extractModelIds, buildModelsJson, writeModelsJson
management.js 127 /health, /metrics, /reflect handlers via createManagementHandlers() factory

server.js (1,284 → ~667 lines)

Retains: state management (cachedModels, keyValidationResults), startup orchestration (validateApiKeys, fetchStartupModels, probeProvider), createProviderServer factory, and re-exports of all symbols for backward compatibility.

Design notes

proxy-request.js is self-contained — reads HTTPS_PROXY at load time, owns its RateLimiter instance (exported so healthResponse can call limiter.getAllStatus()), and contains extractBillingHeaders alongside the proxyRequest function that calls it. All Copilot-bound requests receive X-Initiator: agent by default (respecting any client-supplied value) to prevent billing inflation on autonomous turns.

model-discovery.js imports the already-constructed proxyAgent from proxy-request.js rather than duplicating the HTTPS_PROXY/HttpsProxyAgent bootstrap, ensuring both modules always use the same agent.

management.js uses getter callbacks rather than direct state captures, so /health and /reflect always reflect current values:

const { healthResponse, reflectEndpoints, handleManagementEndpoint } = createManagementHandlers({
  getAdapters:           () => registeredAdapters,
  getCachedModels:       () => cachedModels,
  isModelFetchComplete:  () => modelFetchComplete,
  getKeyValidationState: () => ({ complete: keyValidationComplete, results: keyValidationResults }),
  getLimiter:            () => limiter,
  httpsProxy:            HTTPS_PROXY,
  getModelAliases:       () => MODEL_ALIASES,
});

buildModelsJson / writeModelsJson in model-discovery.js accept explicit (adapters, cachedModels, modelAliases) params; server.js provides zero-argument wrappers that bind current state, preserving the calling convention expected by tests.

Copilot AI and others added 2 commits May 5, 2026 14:23
Create proxy-request.js (~489 lines), model-discovery.js (~221 lines),
and management.js (~127 lines) extracted from server.js (1284→664 lines).

- proxy-request.js: HTTP/WebSocket proxy, rate-limit enforcement (isValidRequestId,
  checkRateLimit, proxyRequest, proxyWebSocket). Self-contained: reads HTTPS_PROXY
  from env at load time, creates own RateLimiter instance, exports it for management.

- model-discovery.js: fetchJson, httpProbe, extractModelIds, buildModelsJson,
  writeModelsJson. buildModelsJson/writeModelsJson accept explicit params (adapters,
  cachedModels, modelAliases) to avoid direct dependency on server.js state.

- management.js: createManagementHandlers() factory that returns healthResponse,
  reflectEndpoints, handleManagementEndpoint. Uses getter callbacks for shared
  server state so functions always reflect current values.

- server.js: imports from new modules, keeps state management (cachedModels,
  keyValidationResults), startup orchestration (validateApiKeys, fetchStartupModels),
  and createProviderServer factory. Re-exports all symbols for backward compatibility.

- Dockerfile: adds proxy-request.js, model-discovery.js, management.js to COPY list.

All 569 tests pass unchanged.

Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/1966046a-0827-4850-8200-9bdecc4822a6

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor containers/api-proxy/server.js into focused modules refactor: split api-proxy server.js into focused modules May 5, 2026
Copilot finished work on behalf of lpcox May 5, 2026 14:28
Copilot AI requested a review from lpcox May 5, 2026 14:28
@lpcox lpcox marked this pull request as ready for review May 5, 2026 18:28
@lpcox lpcox requested a review from Mossaka as a code owner May 5, 2026 18:28
Copilot AI review requested due to automatic review settings May 5, 2026 18:28
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 86.45% 86.53% 📈 +0.08%
Statements 86.39% 86.46% 📈 +0.07%
Functions 90.61% 90.61% ➡️ +0.00%
Branches 79.23% 79.27% 📈 +0.04%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/container-lifecycle.ts 86.1% → 87.2% (+1.09%) 86.3% → 87.4% (+1.08%)

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

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 refactors the api-proxy into smaller modules so the security-sensitive forwarding path, startup model discovery, and management endpoints are separated while server.js continues to own shared state and startup orchestration.

Changes:

  • Extracts HTTP/WebSocket proxying and rate limiting into proxy-request.js.
  • Extracts model probing/fetching and models.json snapshot helpers into model-discovery.js.
  • Extracts /health, /metrics, and /reflect handling into management.js, and updates the container image to include the new modules.
Show a summary per file
File Description
containers/api-proxy/server.js Rewired the main entrypoint to consume the new modules and keep orchestration/state logic.
containers/api-proxy/proxy-request.js New module containing request forwarding, WebSocket tunneling, request ID validation, and rate-limit enforcement.
containers/api-proxy/model-discovery.js New module for startup probes, model-list fetching/parsing, and writing models.json.
containers/api-proxy/management.js New factory-based management endpoint handlers reading shared state via getters.
containers/api-proxy/Dockerfile Adds the new split modules to the image build.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

containers/api-proxy/server.js:20

  • This header comment is no longer accurate: the file still reads multiple environment variables (for example AWF_MODEL_ALIASES and AWF_VALIDATE_KEYS) and still hard-codes provider names in places like the body-transform wiring. Leaving the "ZERO ... env-var reads" claim here is misleading for future audits of this security-sensitive entrypoint.
 * All provider-specific knowledge (credentials, URLs, auth headers, body
 * transforms, model lists) lives exclusively in providers/*.js.
 * This file contains ZERO hard-coded provider names, ports, or env-var reads.
  • Files reviewed: 5/5 changed files
  • Comments generated: 2

Comment thread containers/api-proxy/server.js Outdated
const tls = require('tls');
const { URL } = require('url');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { generateRequestId, sanitizeForLog, logRequest } = require('./logging');
Comment thread containers/api-proxy/model-discovery.js Outdated
Comment on lines +25 to +27
// ── Module-level constants ────────────────────────────────────────────────────
const HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
const proxyAgent = HTTPS_PROXY ? new HttpsProxyAgent(HTTPS_PROXY) : undefined;
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 5, 2026

@copilot merge main and address review feedback. make sure you preserve recent updates to byok handling

@github-actions github-actions Bot mentioned this pull request May 5, 2026
@github-actions

This comment has been minimized.

- Merge e61f158: X-Initiator billing injection, billing header extraction,
  token-tracker billingInfo/initiatorSent, new tests, workflow updates
- Move extractBillingHeaders into proxy-request.js (lives with proxyRequest)
- Add X-Initiator: agent injection for all Copilot-bound requests
- Pass billingInfo + initiatorSent to trackTokenUsage and request_complete log
- Export extractBillingHeaders from proxy-request.js; re-export from server.js
- Fix review comment 1: remove dead generateRequestId import from server.js
- Fix review comment 2: model-discovery.js imports proxyAgent from
  proxy-request.js instead of duplicating HttpsProxyAgent bootstrap
- 581 tests pass (12 new from main branch)

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 5, 2026

@copilot merge main and address review feedback. make sure you preserve recent updates to byok handling

Done in commit 501566b:

  • Merged main (commit e61f158): X-Initiator billing injection, extractBillingHeaders, token-tracker billingInfo/initiatorSent fields, new tests, and all workflow updates — BYOK handling fully preserved.
  • Review feedback 1 (unused generateRequestId in server.js): removed — it's only needed in proxy-request.js which already imports it directly.
  • Review feedback 2 (duplicate HTTPS_PROXY/HttpsProxyAgent bootstrap): model-discovery.js now imports proxyAgent from proxy-request.js instead of re-constructing its own instance.
  • extractBillingHeaders now lives in proxy-request.js alongside proxyRequest (where it's called), and is re-exported via server.js for tests.

All 581 tests pass (including 12 new ones from main).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • api.example.com
    • Triggering command: /opt/hostedtoolcache/node/20.20.2/x64/bin/node /opt/hostedtoolcache/node/20.20.2/x64/bin/node /home/REDACTED/work/gh-aw-firewall/gh-aw-firewall/containers/api-proxy/node_modules/jest-worker/build/processChild.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of lpcox May 5, 2026 19:01
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🧪 Smoke Test: Copilot BYOK (Offline) Mode

Test Result
GitHub MCP (list PRs) ✅ PR #2580 "chore(deps): Bump postcss..." returned
GitHub.com connectivity ⚠️ Pre-step HTTP code not expanded (template var)
File write/read ⚠️ Pre-step file path not expanded (template var)
BYOK inference (this response) ✅ Agent responding via api-proxy → api.githubcopilot.com

Running in BYOK offline mode (COPILOT_OFFLINE=true) via api-proxy → api.githubcopilot.com.

Author: @Copilot | Assignees: @lpcox, @Copilot

Overall: PASS (BYOK inference confirmed; pre-step vars unexpanded — likely template issue in workflow)

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Smoke Test Results

✅ GitHub MCP: Last 2 merged PRs retrieved
✅ Playwright: github.com page title contains 'GitHub'
✅ File Writing: Test file created at /tmp/gh-aw/agent/smoke-test-claude-25396150588.txt
✅ Bash Tool: File verified successfully

Overall Status: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🔬 Smoke Test Results

Test Result
GitHub MCP connectivity ✅ PR fetched: "refactor: split api-proxy server.js into focused modules"
GitHub.com HTTP connectivity ❌ Template variable not expanded (SMOKE_HTTP_CODE)
File write/read ❌ Template variable not expanded (SMOKE_FILE_PATH)

Overall: FAIL — pre-computed step outputs were not passed to this job (template variables unexpanded).

PR author: @Copilot · Assignees: @lpcox, @Copilot

📰 BREAKING: Report filed by Smoke Copilot

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Codex Smoke: FAIL

  • api-proxy: inject X-Initiator: agent default on all Copilot-bound requests to prevent billing inflation
  • refactor: extract shared parameterised test factory for log sub-commands
  • ✅ GitHub PR review, Playwright, file, bash, discussion comment, build
  • ❌ safeinputs-gh missing; Tavily tools empty
    Overall status: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx all passed ✅ PASS
Node.js execa all passed ✅ PASS
Node.js p-limit all passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #2559 · ● 1.4M ·

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Smoke Test Results — FAIL

Check Result
Redis PING ❌ timeout (no response)
PostgreSQL pg_isready ❌ no response
PostgreSQL SELECT 1 ❌ timeout (no response)

host.docker.internal is not reachable from this runner — service containers are unavailable. FAIL

🔌 Service connectivity validated by Smoke Services

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.14.1 v20.20.2 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot.

Tested by Smoke Chroot

@lpcox lpcox enabled auto-merge (squash) May 5, 2026 21:23
@lpcox lpcox disabled auto-merge May 5, 2026 21:23
@lpcox lpcox merged commit b2d2923 into main May 5, 2026
65 of 69 checks passed
@lpcox lpcox deleted the copilot/refactor-split-api-proxy-server branch May 5, 2026 21:23
Sign up for free to 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.

[Refactoring] Split containers/api-proxy/server.js into focused modules

3 participants