refactor: share platform file and route helpers

This commit is contained in:
Cita
2026-03-22 22:17:50 +08:00
parent 9290e51ecc
commit 32d7037629
24 changed files with 375 additions and 333 deletions
@@ -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}` : ''}`;
}