Files
gemini-cli/packages/core/src/utils/apiConversionUtils.test.ts

178 lines
5.6 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { convertToRestPayload } from './apiConversionUtils.js';
import type { GenerateContentParameters } from '@google/genai';
import {
FunctionCallingConfigMode,
HarmCategory,
HarmBlockThreshold,
} from '@google/genai';
describe('apiConversionUtils', () => {
describe('convertToRestPayload', () => {
it('handles minimal requests with no config', () => {
const req: GenerateContentParameters = {
model: 'gemini-3-flash',
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
};
const result = convertToRestPayload(req);
expect(result).toStrictEqual({
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
});
expect(result['generationConfig']).toBeUndefined();
});
it('normalizes string systemInstruction to REST format', () => {
const req: GenerateContentParameters = {
model: 'gemini-3-flash',
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
config: {
systemInstruction: 'You are a helpful assistant.',
},
};
const result = convertToRestPayload(req);
expect(result['systemInstruction']).toStrictEqual({
parts: [{ text: 'You are a helpful assistant.' }],
});
expect(result['generationConfig']).toBeUndefined();
});
it('preserves object-based systemInstruction', () => {
const sysInstruction = { parts: [{ text: 'Object instruction' }] };
const req: GenerateContentParameters = {
model: 'gemini-3-flash',
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
config: {
systemInstruction: sysInstruction,
},
};
const result = convertToRestPayload(req);
expect(result['systemInstruction']).toStrictEqual(sysInstruction);
});
it('hoists capabilities (tools, safety, cachedContent) to the root level', () => {
const req: GenerateContentParameters = {
model: 'gemini-3-flash',
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
config: {
tools: [{ functionDeclarations: [{ name: 'myTool' }] }],
toolConfig: {
functionCallingConfig: { mode: FunctionCallingConfigMode.ANY },
},
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
],
cachedContent: 'cached-content-id',
},
};
const result = convertToRestPayload(req);
expect(result['tools']).toBeDefined();
expect(result['toolConfig']).toBeDefined();
expect(result['safetySettings']).toBeDefined();
expect(result['cachedContent']).toBe('cached-content-id');
// generationConfig should be omitted since no pure hyperparameters were passed
expect(result['generationConfig']).toBeUndefined();
});
it('retains pure hyperparameters in generationConfig', () => {
const req: GenerateContentParameters = {
model: 'gemini-3-flash',
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
config: {
temperature: 0.7,
topP: 0.9,
maxOutputTokens: 100,
},
};
const result = convertToRestPayload(req);
expect(result['generationConfig']).toStrictEqual({
temperature: 0.7,
topP: 0.9,
maxOutputTokens: 100,
});
});
it('strips JS-specific abortSignal from the final payload', () => {
const req: GenerateContentParameters = {
model: 'gemini-3-flash',
contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
config: {
temperature: 0.5,
abortSignal: new AbortController().signal,
},
};
const result = convertToRestPayload(req);
expect(result['generationConfig']).toStrictEqual({
temperature: 0.5,
});
expect(result['abortSignal']).toBeUndefined();
// @ts-expect-error Checking that the key doesn't exist inside generationConfig
expect(result['generationConfig']?.abortSignal).toBeUndefined();
});
it('handles a complex kitchen-sink request correctly', () => {
const req: GenerateContentParameters = {
model: 'gemini-3-flash',
contents: [{ role: 'user', parts: [{ text: 'Kitchen sink' }] }],
config: {
systemInstruction: 'Be witty.',
temperature: 0.8,
tools: [{ functionDeclarations: [{ name: 'test' }] }],
abortSignal: new AbortController().signal,
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
},
],
topK: 40,
},
};
const result = convertToRestPayload(req);
// Root level checks
expect(result['contents']).toBeDefined();
expect(result['systemInstruction']).toStrictEqual({
parts: [{ text: 'Be witty.' }],
});
expect(result['tools']).toStrictEqual([
{ functionDeclarations: [{ name: 'test' }] },
]);
expect(result['safetySettings']).toStrictEqual([
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
},
]);
expect(result['abortSignal']).toBeUndefined();
// Generation config checks
expect(result['generationConfig']).toStrictEqual({
temperature: 0.8,
topK: 40,
});
});
});
});