diff --git a/containers/api-proxy/providers/gemini.js b/containers/api-proxy/providers/gemini.js deleted file mode 100644 index 8b39a3253..000000000 --- a/containers/api-proxy/providers/gemini.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -/** - * Google Gemini provider adapter. - * - * Port: 10003 (always bound — returns 503 when no key is configured) - * Auth: x-goog-api-key header - * Credentials: GEMINI_API_KEY - * Target: GEMINI_API_TARGET (default: generativelanguage.googleapis.com) - * Base path: GEMINI_API_BASE_PATH - * - * URL transform: strips ?key=, ?apiKey=, ?api_key= query params that some - * Gemini SDK versions append alongside the header. - * - * All configuration lives in GOOGLE_PROVIDER_SPECS.gemini (google-provider-specs.js). - */ - -const { makeGoogleProviderFactory } = require('./google-adapter'); - -/** - * Create the Google Gemini provider adapter. - * - * @param {Record} env - Environment variables - * @param {{ bodyTransform?: ((body: Buffer) => (Buffer | null | Promise))|null }} [deps={}] - Injected dependencies - * @returns {import('./index').ProviderAdapter} - */ -const createGeminiAdapter = makeGoogleProviderFactory('gemini'); - -module.exports = { createGeminiAdapter }; diff --git a/containers/api-proxy/providers/google-adapter.js b/containers/api-proxy/providers/google-adapter.js index 1cd51cbf9..618967cd9 100644 --- a/containers/api-proxy/providers/google-adapter.js +++ b/containers/api-proxy/providers/google-adapter.js @@ -136,4 +136,16 @@ function makeGoogleProviderFactory(providerKey) { return (env, deps = {}) => createGoogleProviderAdapter(providerKey, env, deps); } -module.exports = { createGoogleApiKeyAdapter, createGoogleProviderAdapter, makeGoogleProviderFactory }; +const GOOGLE_PROVIDER_ADAPTER_FACTORIES = Object.fromEntries( + Object.keys(GOOGLE_PROVIDER_SPECS).map((providerKey) => [ + providerKey, + makeGoogleProviderFactory(providerKey), + ]), +); + +module.exports = { + createGoogleApiKeyAdapter, + createGoogleProviderAdapter, + makeGoogleProviderFactory, + GOOGLE_PROVIDER_ADAPTER_FACTORIES, +}; diff --git a/containers/api-proxy/providers/google-adapter.test.js b/containers/api-proxy/providers/google-adapter.test.js index 6cf84e7c3..3b14cca98 100644 --- a/containers/api-proxy/providers/google-adapter.test.js +++ b/containers/api-proxy/providers/google-adapter.test.js @@ -1,9 +1,11 @@ 'use strict'; -const { createGoogleProviderAdapter, makeGoogleProviderFactory } = require('./google-adapter'); +const { + createGoogleProviderAdapter, + makeGoogleProviderFactory, + GOOGLE_PROVIDER_ADAPTER_FACTORIES, +} = require('./google-adapter'); const { GOOGLE_PROVIDER_SPECS } = require('./google-provider-specs'); -const { createGeminiAdapter } = require('./gemini'); -const { createVertexAdapter } = require('./vertex'); describe('createGoogleProviderAdapter', () => { it('throws for an unknown provider key', () => { @@ -17,6 +19,16 @@ describe('createGoogleProviderAdapter', () => { }); }); + it('exports factories for every declared Google provider', () => { + expect(Object.keys(GOOGLE_PROVIDER_ADAPTER_FACTORIES)).toEqual(Object.keys(GOOGLE_PROVIDER_SPECS)); + expect(GOOGLE_PROVIDER_ADAPTER_FACTORIES.gemini({ GEMINI_API_KEY: 'key' }).getAuthHeaders()).toEqual({ + 'x-goog-api-key': 'key', + }); + expect(GOOGLE_PROVIDER_ADAPTER_FACTORIES.vertex({ GOOGLE_API_KEY: 'key' }).getAuthHeaders()).toEqual({ + 'x-goog-api-key': 'key', + }); + }); + it('derives gemini ports, targets and messages from the spec', () => { const adapter = createGoogleProviderAdapter('gemini', {}); expect(adapter.name).toBe('gemini'); @@ -47,15 +59,15 @@ describe('createGoogleProviderAdapter', () => { }); it('applies the gemini URL transform and omits it for vertex', () => { - const gemini = createGeminiAdapter({ GEMINI_API_KEY: 'k' }); - const vertex = createVertexAdapter({ GOOGLE_API_KEY: 'k' }); + const gemini = GOOGLE_PROVIDER_ADAPTER_FACTORIES.gemini({ GEMINI_API_KEY: 'k' }); + const vertex = GOOGLE_PROVIDER_ADAPTER_FACTORIES.vertex({ GOOGLE_API_KEY: 'k' }); expect(gemini.transformRequestUrl('/v1beta/models?key=secret')).toBe('/v1beta/models'); expect(vertex.transformRequestUrl).toBeUndefined(); }); it('exposes a models fetch config only when the spec defines a models path', () => { - const gemini = createGeminiAdapter({ GEMINI_API_KEY: 'k' }); - const vertex = createVertexAdapter({ GOOGLE_API_KEY: 'k' }); + const gemini = GOOGLE_PROVIDER_ADAPTER_FACTORIES.gemini({ GEMINI_API_KEY: 'k' }); + const vertex = GOOGLE_PROVIDER_ADAPTER_FACTORIES.vertex({ GOOGLE_API_KEY: 'k' }); expect(gemini.getModelsFetchConfig()).toMatchObject({ url: 'https://generativelanguage.googleapis.com/v1beta/models', }); @@ -64,7 +76,7 @@ describe('createGoogleProviderAdapter', () => { }); it('authenticates both providers with the x-goog-api-key header', () => { - expect(createGeminiAdapter({ GEMINI_API_KEY: 'g' }).getAuthHeaders()).toEqual({ 'x-goog-api-key': 'g' }); - expect(createVertexAdapter({ GOOGLE_API_KEY: 'v' }).getAuthHeaders()).toEqual({ 'x-goog-api-key': 'v' }); + expect(GOOGLE_PROVIDER_ADAPTER_FACTORIES.gemini({ GEMINI_API_KEY: 'g' }).getAuthHeaders()).toEqual({ 'x-goog-api-key': 'g' }); + expect(GOOGLE_PROVIDER_ADAPTER_FACTORIES.vertex({ GOOGLE_API_KEY: 'v' }).getAuthHeaders()).toEqual({ 'x-goog-api-key': 'v' }); }); }); diff --git a/containers/api-proxy/providers/index.js b/containers/api-proxy/providers/index.js index c1575868b..d027dd92e 100644 --- a/containers/api-proxy/providers/index.js +++ b/containers/api-proxy/providers/index.js @@ -12,8 +12,7 @@ const { createOpenAIAdapter } = require('./openai'); const { createAnthropicAdapter } = require('./anthropic'); const { createCopilotAdapter } = require('./copilot'); -const { createGeminiAdapter } = require('./gemini'); -const { createVertexAdapter } = require('./vertex'); +const { GOOGLE_PROVIDER_ADAPTER_FACTORIES } = require('./google-adapter'); /** * @typedef {Object} ProbeConfig @@ -104,8 +103,8 @@ function createAllAdapters(env, deps = {}) { const openai = createOpenAIAdapter(env, { bodyTransform: deps.openaiBodyTransform || null }); const anthropic = createAnthropicAdapter(env, { bodyTransform: deps.anthropicBodyTransform || null }); const copilot = createCopilotAdapter(env, { bodyTransform: deps.copilotBodyTransform || null }); - const gemini = createGeminiAdapter(env, { bodyTransform: deps.geminiBodyTransform || null }); - const vertex = createVertexAdapter(env, { bodyTransform: deps.vertexBodyTransform || null }); + const gemini = GOOGLE_PROVIDER_ADAPTER_FACTORIES.gemini(env, { bodyTransform: deps.geminiBodyTransform || null }); + const vertex = GOOGLE_PROVIDER_ADAPTER_FACTORIES.vertex(env, { bodyTransform: deps.vertexBodyTransform || null }); return [openai, anthropic, copilot, gemini, vertex]; } diff --git a/containers/api-proxy/providers/vertex.js b/containers/api-proxy/providers/vertex.js deleted file mode 100644 index 43c86fc44..000000000 --- a/containers/api-proxy/providers/vertex.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -/** - * Google Vertex AI provider adapter. - * - * Port: 10004 (always bound — returns 503 when no key is configured) - * Auth: x-goog-api-key header - * Credentials: GOOGLE_API_KEY - * Target: VERTEX_API_TARGET (default: aiplatform.googleapis.com) - * Base path: VERTEX_API_BASE_PATH - * - * Used by the Gemini CLI (google-gemini/gemini-cli) when authType === USE_VERTEX - * (i.e. GOOGLE_GENAI_USE_VERTEXAI=true). Setting GOOGLE_VERTEX_BASE_URL routes - * all Vertex AI traffic through the api-proxy sidecar instead of calling - * aiplatform.googleapis.com directly, enabling credential isolation. - * - * All configuration lives in GOOGLE_PROVIDER_SPECS.vertex (google-provider-specs.js). - */ - -const { makeGoogleProviderFactory } = require('./google-adapter'); - -/** - * Create the Google Vertex AI provider adapter. - * - * @param {Record} env - Environment variables - * @param {{ bodyTransform?: ((body: Buffer) => (Buffer | null | Promise))|null }} [deps={}] - Injected dependencies - * @returns {import('./index').ProviderAdapter} - */ -const createVertexAdapter = makeGoogleProviderFactory('vertex'); - -module.exports = { createVertexAdapter }; diff --git a/containers/api-proxy/server.auth-matrix.test.js b/containers/api-proxy/server.auth-matrix.test.js index 62ad08d01..02f1b5303 100644 --- a/containers/api-proxy/server.auth-matrix.test.js +++ b/containers/api-proxy/server.auth-matrix.test.js @@ -13,8 +13,8 @@ const { createOpenAIAdapter } = require('./providers/openai'); const { createAnthropicAdapter } = require('./providers/anthropic'); const { createCopilotAdapter } = require('./providers/copilot'); -const { createGeminiAdapter } = require('./providers/gemini'); -const { createVertexAdapter } = require('./providers/vertex'); +const { GOOGLE_PROVIDER_ADAPTER_FACTORIES } = require('./providers/google-adapter'); +const { gemini: createGeminiAdapter, vertex: createVertexAdapter } = GOOGLE_PROVIDER_ADAPTER_FACTORIES; // --------------------------------------------------------------------------- // Helpers diff --git a/docs/auth-matrix.md b/docs/auth-matrix.md index 42fc4251b..855d896dd 100644 --- a/docs/auth-matrix.md +++ b/docs/auth-matrix.md @@ -247,7 +247,7 @@ Selecting `AWF_AUTH_PROVIDER=aws` signs Copilot-adapter HTTP requests at final d ## Provider: Google Gemini **Port:** 10003 -**Implementation:** `containers/api-proxy/providers/gemini.js` +**Implementation:** `containers/api-proxy/providers/google-adapter.js` (Gemini factory from the declarative `google-provider-specs.js` registry) ### Static API Key @@ -277,7 +277,7 @@ Google says the Gemini API will reject standard API keys beginning in September ## Provider: Google Vertex AI **Port:** 10004 -**Implementation:** `containers/api-proxy/providers/vertex.js` (shares `createGoogleApiKeyAdapter` with the Gemini adapter via `google-adapter.js`) +**Implementation:** `containers/api-proxy/providers/google-adapter.js` (Vertex factory from the declarative `google-provider-specs.js` registry) ### Static API Key @@ -461,10 +461,10 @@ Adds `x-session-id` header automatically in BYOK mode unless already present. | Copilot | AWS Bedrock OIDC + SigV4 | — | ✅ | `aws-oidc-token-provider.js`, `server.auth-matrix.test.js` | | Copilot | GCP OIDC | — | ✅ | `gcp-oidc-token-provider.js`, `server.auth-matrix.test.js` | | Copilot | GHES + BYOK | GHES | ✅ | `server.auth-matrix.test.js` | -| Gemini | Static key | — | ✅ | `gemini.js`, `google-adapter.js` | +| Gemini | Static key | — | ✅ | `google-adapter.js`, `google-provider-specs.js` | | Gemini | GCP WIF | — | ❌ not impl | Use the OpenAI adapter with GCP OIDC pointed at a Vertex endpoint instead (see [Google Gemini](#provider-google-gemini)) | -| Vertex AI | Static key | — | ✅ | `vertex.js`, `google-adapter.js` | -| Vertex AI | GCP WIF | — | ❌ not impl | No OIDC support in `vertex.js`; see [Provider: Google Vertex AI](#provider-google-vertex-ai) | +| Vertex AI | Static key | — | ✅ | `google-adapter.js`, `google-provider-specs.js` | +| Vertex AI | GCP WIF | — | ❌ not impl | No OIDC support in the shared Google adapter; see [Provider: Google Vertex AI](#provider-google-vertex-ai) | :::note "Implementation" column lists source files, not line numbers — line references go stale quickly as the code evolves. Use your editor's search to locate the relevant logic within each file. diff --git a/docs/authentication-architecture.md b/docs/authentication-architecture.md index 78126d44f..a89cceab3 100644 --- a/docs/authentication-architecture.md +++ b/docs/authentication-architecture.md @@ -796,7 +796,7 @@ OIDC authentication is configured via `apiProxy.auth` in the AWF config file or | `containers/api-proxy/providers/openai.js` | OpenAI adapter — selects OIDC provider based on `AWF_AUTH_PROVIDER` | | `containers/api-proxy/providers/anthropic.js` | Anthropic adapter — static `x-api-key` or WIF `Authorization: Bearer` | | `containers/api-proxy/providers/copilot.js`, `copilot-auth.js`, `copilot-byok.js` | Copilot adapter — GitHub token, BYOK, and OIDC handling, `token`/`Bearer` prefix logic | -| `containers/api-proxy/providers/gemini.js`, `vertex.js`, `google-adapter.js`, `google-provider-specs.js` | Gemini and Vertex AI adapters (declarative specs) — static `x-goog-api-key` only, no OIDC | +| `containers/api-proxy/providers/google-adapter.js`, `google-provider-specs.js` | Gemini and Vertex AI adapters (declarative specs) — static `x-goog-api-key` only, no OIDC | | `containers/agent/setup-iptables.sh` | iptables rules for api-proxy routing | | `containers/agent/entrypoint.sh` | Entrypoint token cleanup, capability drop | | `containers/agent/api-proxy-health-check.sh` | Pre-flight credential isolation verification |