forked from boos/BoosAPI
fix: address review feedback and code scanning alerts
This commit is contained in:
@@ -27,6 +27,24 @@ describe('geminiGenerateContentTransformer.inbound', () => {
|
||||
expect(geminiGenerateContentOutbound.resolveActionUrl).toBe(resolveGeminiGenerateContentUrl);
|
||||
});
|
||||
|
||||
it('preserves base-url query params when resolving Gemini endpoints', () => {
|
||||
expect(
|
||||
resolveGeminiNativeBaseUrl('https://example.com/native?alt=sse', 'v1beta'),
|
||||
).toBe('https://example.com/native/v1beta?alt=sse');
|
||||
expect(
|
||||
resolveGeminiModelsUrl('https://example.com/native?alt=sse', 'v1beta', 'api-key'),
|
||||
).toBe('https://example.com/native/v1beta/models?alt=sse&key=api-key');
|
||||
expect(
|
||||
resolveGeminiGenerateContentUrl(
|
||||
'https://example.com/native?alt=sse',
|
||||
'v1beta',
|
||||
'/models/gemini-2.5-pro:generateContent',
|
||||
'api-key',
|
||||
'?trace=1',
|
||||
),
|
||||
).toBe('https://example.com/native/v1beta/models/gemini-2.5-pro:generateContent?alt=sse&trace=1&key=api-key');
|
||||
});
|
||||
|
||||
it('parses native Gemini requests into canonical envelopes', () => {
|
||||
const result = geminiGenerateContentTransformer.parseRequest({
|
||||
model: 'gemini-2.5-pro',
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return (baseUrl || '').replace(/\/+$/, '');
|
||||
function normalizePathname(pathname: string): string {
|
||||
let normalized = pathname || '';
|
||||
while (normalized.endsWith('/')) {
|
||||
normalized = normalized.slice(0, -1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function baseIncludesVersion(baseUrl: string): boolean {
|
||||
return /\/v\d+(?:beta)?(?:\/|$)/i.test(baseUrl);
|
||||
function splitBaseUrl(baseUrl: string): { path: string; query: string } {
|
||||
const raw = baseUrl || '';
|
||||
const queryIndex = raw.indexOf('?');
|
||||
if (queryIndex < 0) {
|
||||
return { path: normalizePathname(raw), query: '' };
|
||||
}
|
||||
return {
|
||||
path: normalizePathname(raw.slice(0, queryIndex)),
|
||||
query: raw.slice(queryIndex + 1),
|
||||
};
|
||||
}
|
||||
|
||||
function baseIncludesVersion(path: string): boolean {
|
||||
return /\/v\d+(?:beta)?(?:\/|$)/i.test(path);
|
||||
}
|
||||
|
||||
export function resolveGeminiNativeBaseUrl(baseUrl: string, apiVersion: string): string {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
if (baseIncludesVersion(normalized)) return normalized;
|
||||
return `${normalized}/${apiVersion}`;
|
||||
const { path, query } = splitBaseUrl(baseUrl);
|
||||
const normalizedPath = baseIncludesVersion(path)
|
||||
? path
|
||||
: `${path}/${apiVersion.replace(/^\/+/, '')}`;
|
||||
return `${normalizedPath}${query ? `?${query}` : ''}`;
|
||||
}
|
||||
|
||||
export function resolveGeminiModelsUrl(
|
||||
@@ -18,8 +36,10 @@ export function resolveGeminiModelsUrl(
|
||||
apiKey: string,
|
||||
): string {
|
||||
const base = resolveGeminiNativeBaseUrl(baseUrl, apiVersion);
|
||||
const separator = base.includes('?') ? '&' : '?';
|
||||
return `${base}/models${separator}key=${encodeURIComponent(apiKey)}`;
|
||||
const [path, query = ''] = base.split('?', 2);
|
||||
const params = new URLSearchParams(query);
|
||||
params.set('key', apiKey);
|
||||
return `${path}/models?${params.toString()}`;
|
||||
}
|
||||
|
||||
export function resolveGeminiGenerateContentUrl(
|
||||
@@ -31,8 +51,13 @@ export function resolveGeminiGenerateContentUrl(
|
||||
): string {
|
||||
const base = resolveGeminiNativeBaseUrl(baseUrl, apiVersion);
|
||||
const normalizedAction = modelActionPath.replace(/^\/+/, '');
|
||||
const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
|
||||
const [path, baseQuery = ''] = base.split('?', 2);
|
||||
const params = new URLSearchParams(baseQuery);
|
||||
const extraParams = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
|
||||
for (const [key, value] of extraParams) {
|
||||
params.set(key, value);
|
||||
}
|
||||
params.set('key', apiKey);
|
||||
const query = params.toString();
|
||||
return `${base}/${normalizedAction}${query ? `?${query}` : ''}`;
|
||||
return `${path}/${normalizedAction}${query ? `?${query}` : ''}`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { normalizeInputFileBlock, toResponsesInputFileBlock } from '../../shared/inputFile.js';
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === 'object';
|
||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function asTrimmedString(value: unknown): string {
|
||||
|
||||
Reference in New Issue
Block a user