mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 06:12:50 -07:00
refactor(context): merge non-strict logic back into FakeContentGenerator
This commit is contained in:
@@ -23,7 +23,6 @@ import type { UserTierId, GeminiUserTier } from '../code_assist/types.js';
|
||||
import { LoggingContentGenerator } from './loggingContentGenerator.js';
|
||||
import { InstallationManager } from '../utils/installationManager.js';
|
||||
import { FakeContentGenerator } from './fakeContentGenerator.js';
|
||||
import { NonStrictFakeContentGenerator } from './nonStrictFakeContentGenerator.js';
|
||||
import { parseCustomHeaders } from '../utils/customHeaderUtils.js';
|
||||
import { determineSurface } from '../utils/surface.js';
|
||||
import { RecordingContentGenerator } from './recordingContentGenerator.js';
|
||||
@@ -195,8 +194,9 @@ export async function createContentGenerator(
|
||||
): Promise<ContentGenerator> {
|
||||
const generator = await (async () => {
|
||||
if (gcConfig.fakeResponsesNonStrict) {
|
||||
const fakeGenerator = await NonStrictFakeContentGenerator.fromFile(
|
||||
const fakeGenerator = await FakeContentGenerator.fromFile(
|
||||
gcConfig.fakeResponsesNonStrict,
|
||||
{ nonStrict: true },
|
||||
);
|
||||
return new LoggingContentGenerator(fakeGenerator, gcConfig);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,18 @@ export type FakeResponse =
|
||||
response: EmbedContentResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the FakeContentGenerator.
|
||||
*/
|
||||
export interface FakeContentGeneratorOptions {
|
||||
/**
|
||||
* If true, the generator will find the first available response that matches
|
||||
* the requested method, rather than strictly following the input order.
|
||||
* Useful for non-deterministic background tasks.
|
||||
*/
|
||||
nonStrict?: boolean;
|
||||
}
|
||||
|
||||
// A ContentGenerator that responds with canned responses.
|
||||
//
|
||||
// Typically these would come from a file, provided by the `--fake-responses`
|
||||
@@ -46,22 +58,41 @@ export class FakeContentGenerator implements ContentGenerator {
|
||||
userTierName?: string;
|
||||
paidTier?: GeminiUserTier;
|
||||
|
||||
constructor(private readonly responses: FakeResponse[]) {}
|
||||
constructor(
|
||||
private readonly responses: FakeResponse[],
|
||||
private readonly options: FakeContentGeneratorOptions = {},
|
||||
) {}
|
||||
|
||||
static async fromFile(filePath: string): Promise<FakeContentGenerator> {
|
||||
static async fromFile(
|
||||
filePath: string,
|
||||
options: FakeContentGeneratorOptions = {},
|
||||
): Promise<FakeContentGenerator> {
|
||||
const fileContent = await promises.readFile(filePath, 'utf-8');
|
||||
const responses = fileContent
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() !== '')
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
.map((line) => JSON.parse(line) as FakeResponse);
|
||||
return new FakeContentGenerator(responses);
|
||||
return new FakeContentGenerator(responses, options);
|
||||
}
|
||||
|
||||
private getNextResponse<
|
||||
M extends FakeResponse['method'],
|
||||
R = Extract<FakeResponse, { method: M }>['response'],
|
||||
>(method: M, request: unknown): R {
|
||||
if (this.options.nonStrict) {
|
||||
const index = this.responses.findIndex((r) => r.method === method);
|
||||
if (index === -1) {
|
||||
throw new Error(
|
||||
`No more mock responses for ${method}, got request:\n` +
|
||||
safeJsonStringify(request),
|
||||
);
|
||||
}
|
||||
const response = this.responses.splice(index, 1)[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
return response.response as R;
|
||||
}
|
||||
|
||||
const response = this.responses[this.callCounter++];
|
||||
if (!response) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
GenerateContentResponse,
|
||||
type CountTokensResponse,
|
||||
type GenerateContentParameters,
|
||||
type CountTokensParameters,
|
||||
EmbedContentResponse,
|
||||
type EmbedContentParameters,
|
||||
} from '@google/genai';
|
||||
import { promises } from 'node:fs';
|
||||
import type { ContentGenerator } from './contentGenerator.js';
|
||||
import type { UserTierId, GeminiUserTier } from '../code_assist/types.js';
|
||||
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
|
||||
import type { LlmRole } from '../telemetry/types.js';
|
||||
import type { FakeResponse } from './fakeContentGenerator.js';
|
||||
|
||||
/**
|
||||
* A ContentGenerator that responds with canned responses, but unlike FakeContentGenerator,
|
||||
* it is "non-strict": it will find and use the first available response that matches
|
||||
* the requested method, rather than strictly following the input order.
|
||||
*
|
||||
* This is useful for testing asynchronous or non-deterministic background tasks
|
||||
* (like token calibration or background snapshots) that might fire out-of-order.
|
||||
*/
|
||||
export class NonStrictFakeContentGenerator implements ContentGenerator {
|
||||
userTier?: UserTierId;
|
||||
userTierName?: string;
|
||||
paidTier?: GeminiUserTier;
|
||||
|
||||
constructor(private readonly responses: FakeResponse[]) {}
|
||||
|
||||
static async fromFile(
|
||||
filePath: string,
|
||||
): Promise<NonStrictFakeContentGenerator> {
|
||||
const fileContent = await promises.readFile(filePath, 'utf-8');
|
||||
const responses = fileContent
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() !== '')
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
.map((line) => JSON.parse(line) as FakeResponse);
|
||||
return new NonStrictFakeContentGenerator(responses);
|
||||
}
|
||||
|
||||
private getNextResponse<
|
||||
M extends FakeResponse['method'],
|
||||
R = Extract<FakeResponse, { method: M }>['response'],
|
||||
>(method: M, request: unknown): R {
|
||||
const index = this.responses.findIndex((r) => r.method === method);
|
||||
if (index === -1) {
|
||||
throw new Error(
|
||||
`No more mock responses for ${method}, got request:\n` +
|
||||
safeJsonStringify(request),
|
||||
);
|
||||
}
|
||||
const response = this.responses.splice(index, 1)[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
return response.response as R;
|
||||
}
|
||||
|
||||
async generateContent(
|
||||
request: GenerateContentParameters,
|
||||
_userPromptId: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
role: LlmRole,
|
||||
): Promise<GenerateContentResponse> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return Object.setPrototypeOf(
|
||||
this.getNextResponse('generateContent', request),
|
||||
GenerateContentResponse.prototype,
|
||||
);
|
||||
}
|
||||
|
||||
async generateContentStream(
|
||||
request: GenerateContentParameters,
|
||||
_userPromptId: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
role: LlmRole,
|
||||
): Promise<AsyncGenerator<GenerateContentResponse>> {
|
||||
const responses = this.getNextResponse('generateContentStream', request);
|
||||
async function* stream() {
|
||||
for (const response of responses) {
|
||||
yield Object.setPrototypeOf(
|
||||
response,
|
||||
GenerateContentResponse.prototype,
|
||||
);
|
||||
}
|
||||
}
|
||||
return stream();
|
||||
}
|
||||
|
||||
async countTokens(
|
||||
request: CountTokensParameters,
|
||||
): Promise<CountTokensResponse> {
|
||||
return this.getNextResponse('countTokens', request);
|
||||
}
|
||||
|
||||
async embedContent(
|
||||
request: EmbedContentParameters,
|
||||
): Promise<EmbedContentResponse> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return Object.setPrototypeOf(
|
||||
this.getNextResponse('embedContent', request),
|
||||
EmbedContentResponse.prototype,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user