diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 65ad13ec45..3a5cced429 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -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 = { diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index c070488fb7..cc20c55f88 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -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, - abortSignal: AbortSignal, - model: string, - config: GenerateContentConfig = {}, - ): Promise> { - 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,