mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
refactor(core): Remove generateJson from GeminiClient (#8529)
This commit is contained in:
@@ -312,101 +312,6 @@ describe('Gemini Client (client.ts)', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('generateJson', () => {
|
||||
it('should call generateContent with the correct parameters', async () => {
|
||||
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
||||
const schema = { type: 'string' };
|
||||
const abortSignal = new AbortController().signal;
|
||||
|
||||
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
||||
totalTokens: 1,
|
||||
});
|
||||
|
||||
await client.generateJson(
|
||||
contents,
|
||||
schema,
|
||||
abortSignal,
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
);
|
||||
|
||||
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith(
|
||||
{
|
||||
model: DEFAULT_GEMINI_FLASH_MODEL,
|
||||
config: {
|
||||
abortSignal,
|
||||
systemInstruction: getCoreSystemPrompt(''),
|
||||
temperature: 0,
|
||||
topP: 1,
|
||||
responseJsonSchema: schema,
|
||||
responseMimeType: 'application/json',
|
||||
},
|
||||
contents,
|
||||
},
|
||||
'test-session-id',
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow overriding model and config', async () => {
|
||||
const contents: Content[] = [
|
||||
{ role: 'user', parts: [{ text: 'hello' }] },
|
||||
];
|
||||
const schema = { type: 'string' };
|
||||
const abortSignal = new AbortController().signal;
|
||||
const customModel = 'custom-json-model';
|
||||
const customConfig = { temperature: 0.9, topK: 20 };
|
||||
|
||||
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
||||
totalTokens: 1,
|
||||
});
|
||||
|
||||
await client.generateJson(
|
||||
contents,
|
||||
schema,
|
||||
abortSignal,
|
||||
customModel,
|
||||
customConfig,
|
||||
);
|
||||
|
||||
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith(
|
||||
{
|
||||
model: customModel,
|
||||
config: {
|
||||
abortSignal,
|
||||
systemInstruction: getCoreSystemPrompt(''),
|
||||
temperature: 0.9,
|
||||
topP: 1, // from default
|
||||
topK: 20,
|
||||
responseJsonSchema: schema,
|
||||
responseMimeType: 'application/json',
|
||||
},
|
||||
contents,
|
||||
},
|
||||
'test-session-id',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the Flash model when fallback mode is active', async () => {
|
||||
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
||||
const schema = { type: 'string' };
|
||||
const abortSignal = new AbortController().signal;
|
||||
const requestedModel = 'gemini-2.5-pro'; // A non-flash model
|
||||
|
||||
// Mock config to be in fallback mode
|
||||
// We access the mock via the client instance which holds the mocked config
|
||||
vi.spyOn(client['config'], 'isInFallbackMode').mockReturnValue(true);
|
||||
|
||||
await client.generateJson(contents, schema, abortSignal, requestedModel);
|
||||
|
||||
// Assert that the Flash model was used, not the requested model
|
||||
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: DEFAULT_GEMINI_FLASH_MODEL,
|
||||
}),
|
||||
'test-session-id',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addHistory', () => {
|
||||
it('should call chat.addHistory with the provided content', async () => {
|
||||
const mockChat = {
|
||||
|
||||
@@ -43,11 +43,9 @@ import { ideContextStore } from '../ide/ideContext.js';
|
||||
import {
|
||||
logChatCompression,
|
||||
logNextSpeakerCheck,
|
||||
logMalformedJsonResponse,
|
||||
} from '../telemetry/loggers.js';
|
||||
import {
|
||||
makeChatCompressionEvent,
|
||||
MalformedJsonResponseEvent,
|
||||
NextSpeakerCheckEvent,
|
||||
} from '../telemetry/types.js';
|
||||
import type { IdeContext, File } from '../ide/types.js';
|
||||
@@ -567,126 +565,6 @@ export class GeminiClient {
|
||||
return turn;
|
||||
}
|
||||
|
||||
async generateJson(
|
||||
contents: Content[],
|
||||
schema: Record<string, unknown>,
|
||||
abortSignal: AbortSignal,
|
||||
model: string,
|
||||
config: GenerateContentConfig = {},
|
||||
): Promise<Record<string, unknown>> {
|
||||
let currentAttemptModel: string = model;
|
||||
|
||||
try {
|
||||
const userMemory = this.config.getUserMemory();
|
||||
const systemInstruction = getCoreSystemPrompt(userMemory);
|
||||
const requestConfig = {
|
||||
abortSignal,
|
||||
...this.generateContentConfig,
|
||||
...config,
|
||||
};
|
||||
|
||||
const apiCall = () => {
|
||||
const modelToUse = this.config.isInFallbackMode()
|
||||
? DEFAULT_GEMINI_FLASH_MODEL
|
||||
: model;
|
||||
currentAttemptModel = modelToUse;
|
||||
|
||||
return this.getContentGeneratorOrFail().generateContent(
|
||||
{
|
||||
model: modelToUse,
|
||||
config: {
|
||||
...requestConfig,
|
||||
systemInstruction,
|
||||
responseJsonSchema: schema,
|
||||
responseMimeType: 'application/json',
|
||||
},
|
||||
contents,
|
||||
},
|
||||
this.lastPromptId,
|
||||
);
|
||||
};
|
||||
|
||||
const onPersistent429Callback = async (
|
||||
authType?: string,
|
||||
error?: unknown,
|
||||
) =>
|
||||
// Pass the captured model to the centralized handler.
|
||||
await handleFallback(this.config, currentAttemptModel, authType, error);
|
||||
|
||||
const result = await retryWithBackoff(apiCall, {
|
||||
onPersistent429: onPersistent429Callback,
|
||||
authType: this.config.getContentGeneratorConfig()?.authType,
|
||||
});
|
||||
|
||||
let text = getResponseText(result);
|
||||
if (!text) {
|
||||
const error = new Error(
|
||||
'API returned an empty response for generateJson.',
|
||||
);
|
||||
await reportError(
|
||||
error,
|
||||
'Error in generateJson: API returned an empty response.',
|
||||
contents,
|
||||
'generateJson-empty-response',
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const prefix = '```json';
|
||||
const suffix = '```';
|
||||
if (text.startsWith(prefix) && text.endsWith(suffix)) {
|
||||
logMalformedJsonResponse(
|
||||
this.config,
|
||||
new MalformedJsonResponseEvent(currentAttemptModel),
|
||||
);
|
||||
text = text
|
||||
.substring(prefix.length, text.length - suffix.length)
|
||||
.trim();
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
await reportError(
|
||||
parseError,
|
||||
'Failed to parse JSON response from generateJson.',
|
||||
{
|
||||
responseTextFailedToParse: text,
|
||||
originalRequestContents: contents,
|
||||
},
|
||||
'generateJson-parse',
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to parse API response as JSON: ${getErrorMessage(
|
||||
parseError,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (abortSignal.aborted) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Avoid double reporting for the empty response case handled above
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message === 'API returned an empty response for generateJson.'
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await reportError(
|
||||
error,
|
||||
'Error generating JSON content via API.',
|
||||
contents,
|
||||
'generateJson-api',
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to generate JSON content: ${getErrorMessage(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async generateContent(
|
||||
contents: Content[],
|
||||
generationConfig: GenerateContentConfig,
|
||||
|
||||
Reference in New Issue
Block a user