refactor(api-proxy): extract ProviderAdapter abstraction, make core provider-agnostic#2409
Conversation
- Add proxy-utils.js: normalizeApiTarget, normalizeBasePath, buildUpstreamPath, stripGeminiKeyParam, shouldStripHeader, composeBodyTransforms (provider-agnostic utilities) - Add providers/ directory with per-provider adapter classes: - openai.js (port 10000, management port, always-bind) - anthropic.js (port 10001, auto-cache/transform composition) - copilot.js (port 10002, dual-auth /models routing) - gemini.js (port 10003, always-bind 503 fallback, key-param strip) - opencode.js (port 10004, dynamic credential routing) - index.js (createAllAdapters factory) - ADDING-A-PROVIDER.md (onboarding documentation) - Refactor server.js: remove all hard-coded provider names/ports/ env-var reads; use createProviderServer() generic factory; validateApiKeys/fetchStartupModels/reflectEndpoints/healthResponse/ buildModelsJson all iterate over registeredAdapters - Update server.test.js imports to point to correct source modules - Update Dockerfile COPY list to include proxy-utils.js + providers/" Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/74f33cad-9e60-4ce7-88cb-e111ef9c2c1b
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
|
@copilot make sure that multiple providers can be active at once with extensible logic for switching between active providers |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…ers active simultaneously
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Smoke Test Results
Overall Status: FAIL PR Titles:
|
- Remove re-export of stripGeminiKeyParam from gemini.js (already exported from proxy-utils.js; no consumers import from gemini.js) - Remove anthropic-cache.js from Dockerfile COPY — it is only used by tests, not required at runtime by any production module Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔬 Smoke Test Results
Overall: FAIL (file test inconclusive — PR: refactor(api-proxy): extract ProviderAdapter abstraction — author
|
|
Smoke Test: Copilot BYOK (Offline Mode)
Running in BYOK offline mode ( PR author: Overall: PARTIAL (BYOK inference path confirmed working; pre-step smoke data was not injected)
|
Smoke Test Results✅ GitHub MCP: Listed last 2 merged PRs Status: PASS
|
Chroot Version Comparison Results
Overall: FAILED — Python and Node.js versions differ between host and chroot environments.
|
|
fix: move smoke-gemini tests into agent container Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "registry.npmjs.org"See Network Configuration for more information.
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS
|
Smoke Test Results
Overall: FAIL —
|
server.js(2,164 lines) had 5 near-identical per-provider server creation blocks with all auth, URL transform, body transform, and validation logic inlined. Adding a provider required touching 7+ locations; there was no isolation for testing provider logic independently.Architecture
proxy-utils.js— shared pure utilitiesnormalizeApiTarget,normalizeBasePath,buildUpstreamPath,stripGeminiKeyParam,shouldStripHeader,composeBodyTransforms. No provider knowledge.providers/<name>.js— one file per providerEach adapter implements a consistent interface; the core calls only interface methods:
providers/openai.js/v1base-path default for public endpointproviders/anthropic.jsproviders/copilot.js/modelsGET special-casing, GHEC/GHES target derivationproviders/gemini.js?key=param strippingproviders/opencode.jscandidateAdaptersproviders/index.jscreateAllAdapters()+ProviderAdaptertypedefserver.js— generic engine, zero provider knowledgecreateProviderServer(adapter)replaces 5 duplicated per-provider server blocks.validateApiKeys,fetchStartupModels,reflectEndpoints,healthResponse, andbuildModelsJsonall iterate overregisteredAdapterswith no provider names hardcoded.Multiple providers active simultaneously
Every provider binds its own port and is independently reachable regardless of what else is configured. OpenCode (port 10004) is a routing layer on top: it accepts a
candidateAdaptersarray and delegates all per-request decisions — target host, base path, auth headers, body transforms, URL transforms — to the first enabled adapter in that list.Changing the priority or adding a new provider to OpenCode's routing is a single-line edit in
providers/index.js—opencode.jsitself never needs to change.providers/ADDING-A-PROVIDER.mdStep-by-step guide: create adapter file → register in
index.js→ updateDockerfile→ optionally add to OpenCode'scandidateAdapters. Adding a provider no longer requires touching core server logic.Impact
index.js) instead of 7+ locations in one monolithbuildUpstreamPathis now fully provider-agnostic; the OpenAI/v1default moved to the OpenAI adapter'sgetBasePath()opencode.js