Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 8 additions & 41 deletions containers/api-proxy/providers/gemini.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@
* Gemini SDK versions append alongside the header.
*/

const { stripGeminiKeyParam, makeUnconfiguredHealthResponse } = require('../proxy-utils');
const { createProviderAuthScaffold, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory');
const { stripGeminiKeyParam } = require('../proxy-utils');
const { GEMINI_ENV } = require('../provider-env-constants');
const { providerKeyHeaders } = require('./auth-headers');
const { createGoogleApiKeyAdapter } = require('./google-adapter');

/**
* Create the Google Gemini provider adapter.
Expand All @@ -26,37 +25,16 @@ const { providerKeyHeaders } = require('./auth-headers');
* @returns {import('./index').ProviderAdapter}
*/
function createGeminiAdapter(env, deps = {}) {
const { apiKey, rawTarget, basePath, bodyTransform } = createProviderAuthScaffold(env, deps, {
keyEnvVar: GEMINI_ENV.KEY,
targetEnvVar: GEMINI_ENV.TARGET,
basePathEnvVar: GEMINI_ENV.BASE_PATH,
defaultTarget: 'generativelanguage.googleapis.com',
});
const buildAuthHeaders = () => providerKeyHeaders('x-goog-api-key', apiKey);

const adapterMethods = createAdapterMethods({
apiKey,
rawTarget,
basePath,
provider: 'gemini',
return createGoogleApiKeyAdapter(env, deps, {
name: 'gemini',
port: 10003,
envConstants: GEMINI_ENV,
defaultTarget: 'generativelanguage.googleapis.com',
validationPath: '/v1beta/models',
validationHeaders: buildAuthHeaders,
modelsPath: '/v1beta/models',
modelsFetchHeaders: buildAuthHeaders,
});

return buildProviderAdapter({
name: 'gemini',
port: 10003,
isManagementPort: false,
adapterMethods,
getAuthHeaders() {
return buildAuthHeaders();
},
bodyTransform,
isEnabled() { return !!apiKey; },
healthServiceName: 'awf-api-proxy-gemini',
unconfiguredErrorMessage: 'Gemini proxy not configured (no GEMINI_API_KEY). Set GEMINI_API_KEY in the AWF runner environment to enable credential isolation.',
healthErrorMessage: 'GEMINI_API_KEY not configured in api-proxy sidecar',
/**
* Strip Gemini SDK auth query parameters before forwarding.
* The SDK injects ?key= (or ?apiKey=, ?api_key=) alongside the header;
Expand All @@ -68,17 +46,6 @@ function createGeminiAdapter(env, deps = {}) {
transformRequestUrl(url) {
return stripGeminiKeyParam(url);
},
/** Response returned for all requests when no GEMINI_API_KEY is configured. */
getUnconfiguredResponse() {
return {
statusCode: 503,
body: { error: 'Gemini proxy not configured (no GEMINI_API_KEY). Set GEMINI_API_KEY in the AWF runner environment to enable credential isolation.' },
};
},
/** /health response when not configured. */
getUnconfiguredHealthResponse() {
return makeUnconfiguredHealthResponse('awf-api-proxy-gemini', 'GEMINI_API_KEY not configured in api-proxy sidecar');
},
});
}

Expand Down
95 changes: 95 additions & 0 deletions containers/api-proxy/providers/google-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict';

/**
* Shared factory for Google API-key–based provider adapters (Gemini, Vertex).
*
* Both providers authenticate via the `x-goog-api-key` header and share the
* same scaffold: createProviderAuthScaffold, createAdapterMethods, and
* buildProviderAdapter. This factory centralises that boilerplate so each
* provider only supplies its name, port, env constants, target, paths, and
* error messages.
*/

const { makeUnconfiguredHealthResponse } = require('../proxy-utils');
const { createProviderAuthScaffold, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory');
const { providerKeyHeaders } = require('./auth-headers');

/**
* Create a Google API-key–based provider adapter.
*
* @param {Record<string, string|undefined>} env - Environment variables
* @param {{ bodyTransform?: ((body: Buffer) => Buffer|null)|null }} [deps={}] - Injected dependencies
* @param {object} opts
Comment on lines +20 to +22
* @param {string} opts.name - Provider slug (e.g. 'gemini')
* @param {number} opts.port - Proxy port (e.g. 10003)
* @param {{ KEY: string, TARGET: string, BASE_PATH: string }} opts.envConstants - Env var name constants
* @param {string} opts.defaultTarget - Default upstream hostname
* @param {string} opts.validationPath - URL path for health/validation probe
* @param {string|null} opts.modelsPath - URL path for models fetch, or null if unsupported
* @param {string} opts.healthServiceName - Service name for health response (e.g. 'awf-api-proxy-gemini')
* @param {string} opts.unconfiguredErrorMessage - Error body when no API key is configured
* @param {string} opts.healthErrorMessage - Health error message when not configured
* @param {((url: string) => string)} [opts.transformRequestUrl] - Optional URL transformer
* @returns {import('./index').ProviderAdapter}
*/
function createGoogleApiKeyAdapter(env, deps = {}, opts) {
const {
name,
port,
envConstants,
defaultTarget,
validationPath,
modelsPath,
healthServiceName,
unconfiguredErrorMessage,
healthErrorMessage,
transformRequestUrl,
} = opts;

const { apiKey, rawTarget, basePath, bodyTransform } = createProviderAuthScaffold(env, deps, {
keyEnvVar: envConstants.KEY,
targetEnvVar: envConstants.TARGET,
basePathEnvVar: envConstants.BASE_PATH,
defaultTarget,
});
const buildAuthHeaders = () => providerKeyHeaders('x-goog-api-key', apiKey);

const adapterMethods = createAdapterMethods({
apiKey,
rawTarget,
basePath,
provider: name,
port,
defaultTarget,
validationPath,
validationHeaders: buildAuthHeaders,
modelsPath,
modelsFetchHeaders: modelsPath ? buildAuthHeaders : null,
});

return buildProviderAdapter({
name,
port,
isManagementPort: false,
adapterMethods,
getAuthHeaders() {
return buildAuthHeaders();
},
bodyTransform,
isEnabled() { return !!apiKey; },
...(transformRequestUrl !== undefined ? { transformRequestUrl } : {}),
/** Response returned for all requests when no API key is configured. */
getUnconfiguredResponse() {
return {
statusCode: 503,
body: { error: unconfiguredErrorMessage },
};
},
/** /health response when not configured. */
getUnconfiguredHealthResponse() {
return makeUnconfiguredHealthResponse(healthServiceName, healthErrorMessage);
},
});
}

module.exports = { createGoogleApiKeyAdapter };
49 changes: 7 additions & 42 deletions containers/api-proxy/providers/vertex.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
* aiplatform.googleapis.com directly, enabling credential isolation.
*/

const { makeUnconfiguredHealthResponse } = require('../proxy-utils');
const { createProviderAuthScaffold, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory');
const { VERTEX_ENV } = require('../provider-env-constants');
const { providerKeyHeaders } = require('./auth-headers');
const { createGoogleApiKeyAdapter } = require('./google-adapter');

/**
* Create the Google Vertex AI provider adapter.
Expand All @@ -28,49 +26,16 @@ const { providerKeyHeaders } = require('./auth-headers');
* @returns {import('./index').ProviderAdapter}
*/
function createVertexAdapter(env, deps = {}) {
const { apiKey, rawTarget, basePath, bodyTransform } = createProviderAuthScaffold(env, deps, {
keyEnvVar: VERTEX_ENV.KEY,
targetEnvVar: VERTEX_ENV.TARGET,
basePathEnvVar: VERTEX_ENV.BASE_PATH,
defaultTarget: 'aiplatform.googleapis.com',
});
const buildAuthHeaders = () => providerKeyHeaders('x-goog-api-key', apiKey);

const adapterMethods = createAdapterMethods({
apiKey,
rawTarget,
basePath,
provider: 'vertex',
return createGoogleApiKeyAdapter(env, deps, {
name: 'vertex',
port: 10004,
envConstants: VERTEX_ENV,
defaultTarget: 'aiplatform.googleapis.com',
validationPath: '/v1/projects',
validationHeaders: buildAuthHeaders,
modelsPath: null,
modelsFetchHeaders: null,
});

return buildProviderAdapter({
name: 'vertex',
port: 10004,
isManagementPort: false,
alwaysBind: true,
adapterMethods,
getAuthHeaders() {
return buildAuthHeaders();
},
bodyTransform,
isEnabled() { return !!apiKey; },
/** Response returned for all requests when no GOOGLE_API_KEY is configured. */
getUnconfiguredResponse() {
return {
statusCode: 503,
body: { error: 'Vertex AI proxy not configured (no GOOGLE_API_KEY). Set GOOGLE_API_KEY in the AWF runner environment to enable credential isolation.' },
};
},
/** /health response when not configured. */
getUnconfiguredHealthResponse() {
return makeUnconfiguredHealthResponse('awf-api-proxy-vertex', 'GOOGLE_API_KEY not configured in api-proxy sidecar');
},
healthServiceName: 'awf-api-proxy-vertex',
unconfiguredErrorMessage: 'Vertex AI proxy not configured (no GOOGLE_API_KEY). Set GOOGLE_API_KEY in the AWF runner environment to enable credential isolation.',
healthErrorMessage: 'GOOGLE_API_KEY not configured in api-proxy sidecar',
});
Comment on lines 28 to 39
}

Expand Down