Skip to content

refactor(api-proxy): extract ProviderAdapter abstraction, make core provider-agnostic#2409

Merged
lpcox merged 7 commits into
mainfrom
copilot/refactor-api-proxy-adapter-abstraction
May 3, 2026
Merged

refactor(api-proxy): extract ProviderAdapter abstraction, make core provider-agnostic#2409
lpcox merged 7 commits into
mainfrom
copilot/refactor-api-proxy-adapter-abstraction

Conversation

Copilot AI commented May 2, 2026

Copy link
Copy Markdown
Contributor

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 utilities

normalizeApiTarget, normalizeBasePath, buildUpstreamPath, stripGeminiKeyParam, shouldStripHeader, composeBodyTransforms. No provider knowledge.

providers/<name>.js — one file per provider

Each adapter implements a consistent interface; the core calls only interface methods:

// providers/gemini.js (example)
return {
  name: 'gemini', port: 10003,
  alwaysBind: true,                          // 503 stub when unconfigured
  get participatesInValidation() { return this.isEnabled(); },
  isEnabled()          { return !!apiKey; },
  getTargetHost()      { return rawTarget; },
  getBasePath()        { return basePath; },
  getAuthHeaders()     { return { 'x-goog-api-key': apiKey }; },
  transformRequestUrl(url) { return stripGeminiKeyParam(url); },
  getBodyTransform()   { return bodyTransform; },
  getValidationProbe() { ... },
  getModelsFetchConfig() { ... },
  getReflectionInfo()  { ... },
};
File Encapsulates
providers/openai.js Port 10000, management port, /v1 base-path default for public endpoint
providers/anthropic.js Auto-cache, ANSI stripping, tool-drop, dual beta-header injection
providers/copilot.js Dual-auth (GitHub token vs BYOK), /models GET special-casing, GHEC/GHES target derivation
providers/gemini.js Always-bind 503 fallback, ?key= param stripping
providers/opencode.js Transparent routing layer — delegates all per-request decisions to candidateAdapters
providers/index.js createAllAdapters() + ProviderAdapter typedef

server.js — generic engine, zero provider knowledge

createProviderServer(adapter) replaces 5 duplicated per-provider server blocks. validateApiKeys, fetchStartupModels, reflectEndpoints, healthResponse, and buildModelsJson all iterate over registeredAdapters with no provider names hardcoded.

// Entire startup block is now:
for (const adapter of adaptersToStart) {
  const server = createProviderServer(adapter);
  server.listen(adapter.port, '0.0.0.0', () => {
    if (adapter.participatesInValidation) onListenerReady();
  });
}

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 candidateAdapters array and delegates all per-request decisions — target host, base path, auth headers, body transforms, URL transforms — to the first enabled adapter in that list.

// providers/index.js — the only place routing priority is defined
const opencode = createOpenCodeAdapter(env, {
  candidateAdapters: [openai, anthropic, copilot], // first enabled wins
});

Changing the priority or adding a new provider to OpenCode's routing is a single-line edit in providers/index.jsopencode.js itself never needs to change.

providers/ADDING-A-PROVIDER.md

Step-by-step guide: create adapter file → register in index.js → update Dockerfile → optionally add to OpenCode's candidateAdapters. Adding a provider no longer requires touching core server logic.

Impact

  • Adding a provider now touches 2 files (new adapter + index.js) instead of 7+ locations in one monolith
  • Each adapter is unit-testable in isolation without starting HTTP servers
  • buildUpstreamPath is now fully provider-agnostic; the OpenAI /v1 default moved to the OpenAI adapter's getBasePath()
  • All five providers can be simultaneously active; OpenCode routing priority is data-driven and extensible without modifying opencode.js

Copilot AI added 2 commits May 2, 2026 21:29
- 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
Copilot AI changed the title [WIP] Refactor api-proxy with provider adapter abstraction refactor(api-proxy): extract ProviderAdapter abstraction, make core provider-agnostic May 2, 2026
@lpcox lpcox marked this pull request as ready for review May 2, 2026 21:32
@lpcox lpcox requested a review from Mossaka as a code owner May 2, 2026 21:32
Copilot AI review requested due to automatic review settings May 2, 2026 21:32
Copilot AI requested a review from lpcox May 2, 2026 21:32
Copilot finished work on behalf of lpcox May 2, 2026 21:32
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 85.76% 85.84% 📈 +0.08%
Statements 85.64% 85.72% 📈 +0.08%
Functions 88.11% 88.11% ➡️ +0.00%
Branches 78.65% 78.69% 📈 +0.04%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 87.4% → 87.7% (+0.29%) 87.0% → 87.3% (+0.27%)

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

@lpcox

lpcox commented May 2, 2026

Copy link
Copy Markdown
Collaborator

@copilot make sure that multiple providers can be active at once with extensible logic for switching between active providers

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

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

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Smoke Test Results

  • GitHub MCP: ❌ (Tools not registered)
  • GitHub Connectivity: ✅ (200)
  • File Writing: ✅
  • Bash Tool: ✅

Overall Status: FAIL

PR Titles:

  1. fix: move smoke-gemini tests into agent container (fix: move smoke-gemini tests into agent container #2401)
  2. (N/A - tool failure)

💎 Faceted by Smoke Gemini

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

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

🔬 Smoke Test Results

Test Status
GitHub MCP connectivity
GitHub.com HTTP ✅ 200
File write/read ❌ (pre-step vars not expanded)

Overall: FAIL (file test inconclusive — $\{\{ steps.smoke-data.outputs }} not substituted)

PR: refactor(api-proxy): extract ProviderAdapter abstraction — author @Copilot, assignees @lpcox @Copilot

📰 BREAKING: Report filed by Smoke Copilot

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot BYOK (Offline Mode)

Test Result
GitHub MCP connectivity
GitHub.com HTTP ⚠️ pre-step data unavailable (template not expanded)
File write/read ⚠️ pre-step data unavailable (template not expanded)
BYOK inference (agent → api-proxy → api.githubcopilot.com)

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

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

Overall: PARTIAL (BYOK inference path confirmed working; pre-step smoke data was not injected)

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Smoke Test Results

✅ GitHub MCP: Listed last 2 merged PRs
✅ Playwright: https://github.com title verified
✅ File Writing: Test file created with timestamp
✅ Bash Tool: File content verified

Status: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

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: FAILED — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

fix: move smoke-gemini tests into agent container
feat: unify schema versioning — use repo release tag for all schemas, publish JSONL schemas as release assets
GitHub MCP: ❌
safeinputs-gh: ❌
Playwright: ✅
Tavily: ❌
File/bash: ✅
Discussion comment: ✅
Build: ✅
Overall: 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

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

🏗️ 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 passed ✅ PASS
Node.js execa passed ✅ PASS
Node.js p-limit 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 #2409 · ● 555.9K ·

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Smoke Test Results

  • Redis PING: ❌ (timeout — no response)
  • PostgreSQL pg_isready: ❌ (no response)
  • PostgreSQL SELECT 1: ❌ (skipped — host unreachable)

Overall: FAILhost.docker.internal is not reachable from this runner environment. Service containers may not be running or the hostname is not resolvable.

🔌 Service connectivity validated by Smoke Services

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.

Proposal: Refactor api-proxy with provider adapter abstraction

3 participants