mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-25 12:34:38 -07:00
feat(cli): add /chat debug command for nightly builds (#16339)
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user