refactor: share platform file and route helpers
This commit is contained in:
@@ -2,11 +2,14 @@ import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
geminiGenerateContentTransformer,
|
||||
resolveGeminiGenerateContentUrl,
|
||||
resolveGeminiModelsUrl,
|
||||
resolveGeminiNativeBaseUrl,
|
||||
reasoningEffortToGeminiThinkingConfig,
|
||||
geminiThinkingConfigToReasoning,
|
||||
} from './index.js';
|
||||
import { extractGeminiUsage } from './usage.js';
|
||||
import { serializeGeminiAggregateResponse, extractResponseMetadata } from './outbound.js';
|
||||
import { serializeGeminiAggregateResponse, extractResponseMetadata, geminiGenerateContentOutbound } from './outbound.js';
|
||||
import { resolveGeminiThinkingConfigFromRequest } from './convert.js';
|
||||
import {
|
||||
parseGeminiStreamPayload,
|
||||
@@ -15,6 +18,15 @@ import {
|
||||
} from './stream.js';
|
||||
|
||||
describe('geminiGenerateContentTransformer.inbound', () => {
|
||||
it('reuses the same gemini url resolver helpers across transformer layers', () => {
|
||||
expect(geminiGenerateContentTransformer.resolveBaseUrl).toBe(resolveGeminiNativeBaseUrl);
|
||||
expect(geminiGenerateContentTransformer.resolveModelsUrl).toBe(resolveGeminiModelsUrl);
|
||||
expect(geminiGenerateContentTransformer.resolveActionUrl).toBe(resolveGeminiGenerateContentUrl);
|
||||
expect(geminiGenerateContentOutbound.resolveBaseUrl).toBe(resolveGeminiNativeBaseUrl);
|
||||
expect(geminiGenerateContentOutbound.resolveModelsUrl).toBe(resolveGeminiModelsUrl);
|
||||
expect(geminiGenerateContentOutbound.resolveActionUrl).toBe(resolveGeminiGenerateContentUrl);
|
||||
});
|
||||
|
||||
it('parses native Gemini requests into canonical envelopes', () => {
|
||||
const result = geminiGenerateContentTransformer.parseRequest({
|
||||
model: 'gemini-2.5-pro',
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { canonicalRequestFromOpenAiBody } from '../../canonical/request.js';
|
||||
import type { CanonicalContentPart, CanonicalRequestEnvelope } from '../../canonical/types.js';
|
||||
import type { ProtocolBuildContext, ProtocolParseContext } from '../../contracts.js';
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return (baseUrl || '').replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function baseIncludesVersion(baseUrl: string): boolean {
|
||||
return /\/v\d+(?:beta)?(?:\/|$)/i.test(baseUrl);
|
||||
}
|
||||
import {
|
||||
resolveGeminiGenerateContentUrl,
|
||||
resolveGeminiModelsUrl,
|
||||
resolveGeminiNativeBaseUrl,
|
||||
} from './urlResolver.js';
|
||||
export {
|
||||
resolveGeminiGenerateContentUrl,
|
||||
resolveGeminiModelsUrl,
|
||||
resolveGeminiNativeBaseUrl,
|
||||
} from './urlResolver.js';
|
||||
|
||||
function asTrimmedString(value: unknown): string {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
@@ -17,37 +20,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export function resolveGeminiNativeBaseUrl(baseUrl: string, apiVersion: string): string {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
if (baseIncludesVersion(normalized)) return normalized;
|
||||
return `${normalized}/${apiVersion}`;
|
||||
}
|
||||
|
||||
export function resolveGeminiModelsUrl(
|
||||
baseUrl: string,
|
||||
apiVersion: string,
|
||||
apiKey: string,
|
||||
): string {
|
||||
const base = resolveGeminiNativeBaseUrl(baseUrl, apiVersion);
|
||||
const separator = base.includes('?') ? '&' : '?';
|
||||
return `${base}/models${separator}key=${encodeURIComponent(apiKey)}`;
|
||||
}
|
||||
|
||||
export function resolveGeminiGenerateContentUrl(
|
||||
baseUrl: string,
|
||||
apiVersion: string,
|
||||
modelActionPath: string,
|
||||
apiKey: string,
|
||||
search: string,
|
||||
): string {
|
||||
const base = resolveGeminiNativeBaseUrl(baseUrl, apiVersion);
|
||||
const normalizedAction = modelActionPath.replace(/^\/+/, '');
|
||||
const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
|
||||
params.set('key', apiKey);
|
||||
const query = params.toString();
|
||||
return `${base}/${normalizedAction}${query ? `?${query}` : ''}`;
|
||||
}
|
||||
|
||||
export function resolveGeminiProxyApiVersion(params: { geminiApiVersion?: unknown } | null | undefined): string {
|
||||
return asTrimmedString(params?.geminiApiVersion) || 'v1beta';
|
||||
}
|
||||
|
||||
@@ -7,45 +7,11 @@ import {
|
||||
toTransformerMetadataRecord,
|
||||
type TransformerMetadata,
|
||||
} from '../../shared/normalized.js';
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return (baseUrl || '').replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function baseIncludesVersion(baseUrl: string): boolean {
|
||||
return /\/v\d+(?:beta)?(?:\/|$)/i.test(baseUrl);
|
||||
}
|
||||
|
||||
function resolveBaseUrl(baseUrl: string, apiVersion: string): string {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
if (baseIncludesVersion(normalized)) return normalized;
|
||||
return `${normalized}/${apiVersion}`;
|
||||
}
|
||||
|
||||
function resolveModelsUrl(
|
||||
baseUrl: string,
|
||||
apiVersion: string,
|
||||
apiKey: string,
|
||||
): string {
|
||||
const resolvedBaseUrl = resolveBaseUrl(baseUrl, apiVersion);
|
||||
const separator = resolvedBaseUrl.includes('?') ? '&' : '?';
|
||||
return `${resolvedBaseUrl}/models${separator}key=${encodeURIComponent(apiKey)}`;
|
||||
}
|
||||
|
||||
function resolveActionUrl(
|
||||
baseUrl: string,
|
||||
apiVersion: string,
|
||||
modelActionPath: string,
|
||||
apiKey: string,
|
||||
search: string,
|
||||
): string {
|
||||
const resolvedBaseUrl = resolveBaseUrl(baseUrl, apiVersion);
|
||||
const normalizedAction = modelActionPath.replace(/^\/+/, '');
|
||||
const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
|
||||
params.set('key', apiKey);
|
||||
const query = params.toString();
|
||||
return `${resolvedBaseUrl}/${normalizedAction}${query ? `?${query}` : ''}`;
|
||||
}
|
||||
import {
|
||||
resolveGeminiGenerateContentUrl,
|
||||
resolveGeminiModelsUrl,
|
||||
resolveGeminiNativeBaseUrl,
|
||||
} from './urlResolver.js';
|
||||
|
||||
type GeminiRecord = Record<string, unknown>;
|
||||
|
||||
@@ -304,9 +270,9 @@ export function extractResponseMetadata(payload: unknown, requestPayload?: unkno
|
||||
}
|
||||
|
||||
export const geminiGenerateContentOutbound = {
|
||||
resolveBaseUrl,
|
||||
resolveModelsUrl,
|
||||
resolveActionUrl,
|
||||
resolveBaseUrl: resolveGeminiNativeBaseUrl,
|
||||
resolveModelsUrl: resolveGeminiModelsUrl,
|
||||
resolveActionUrl: resolveGeminiGenerateContentUrl,
|
||||
extractTransformerMetadata,
|
||||
extractResponseMetadata,
|
||||
serializeAggregateResponse: serializeGeminiAggregateResponse,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return (baseUrl || '').replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function baseIncludesVersion(baseUrl: string): boolean {
|
||||
return /\/v\d+(?:beta)?(?:\/|$)/i.test(baseUrl);
|
||||
}
|
||||
|
||||
export function resolveGeminiNativeBaseUrl(baseUrl: string, apiVersion: string): string {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
if (baseIncludesVersion(normalized)) return normalized;
|
||||
return `${normalized}/${apiVersion}`;
|
||||
}
|
||||
|
||||
export function resolveGeminiModelsUrl(
|
||||
baseUrl: string,
|
||||
apiVersion: string,
|
||||
apiKey: string,
|
||||
): string {
|
||||
const base = resolveGeminiNativeBaseUrl(baseUrl, apiVersion);
|
||||
const separator = base.includes('?') ? '&' : '?';
|
||||
return `${base}/models${separator}key=${encodeURIComponent(apiKey)}`;
|
||||
}
|
||||
|
||||
export function resolveGeminiGenerateContentUrl(
|
||||
baseUrl: string,
|
||||
apiVersion: string,
|
||||
modelActionPath: string,
|
||||
apiKey: string,
|
||||
search: string,
|
||||
): string {
|
||||
const base = resolveGeminiNativeBaseUrl(baseUrl, apiVersion);
|
||||
const normalizedAction = modelActionPath.replace(/^\/+/, '');
|
||||
const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
|
||||
params.set('key', apiKey);
|
||||
const query = params.toString();
|
||||
return `${base}/${normalizedAction}${query ? `?${query}` : ''}`;
|
||||
}
|
||||
Reference in New Issue
Block a user