mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 15:40:57 -07:00
178 lines
5.6 KiB
TypeScript
178 lines
5.6 KiB
TypeScript
/**
|
|
* @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,
|
|
});
|
|
});
|
|
});
|
|
});
|