refactor(core): Remove generateJson from GeminiClient (#8529)

This commit is contained in:
Abhi
2025-09-16 20:59:13 -04:00
committed by GitHub
parent dbc0d9c8e2
commit f5dca8b7f7
2 changed files with 0 additions and 217 deletions

View File

@@ -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 = {

View File

@@ -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,