Guard pro model usage (#22665)

This commit is contained in:
Sehoon Shon
2026-03-16 13:44:25 -04:00
committed by GitHub
parent ef5627eece
commit 48130ebd25
7 changed files with 252 additions and 9 deletions
@@ -17,6 +17,7 @@ export const ExperimentFlags = {
MASKING_PRUNABLE_THRESHOLD: 45758818,
MASKING_PROTECT_LATEST_TURN: 45758819,
GEMINI_3_1_PRO_LAUNCHED: 45760185,
PRO_MODEL_NO_ACCESS: 45768879,
} as const;
export type ExperimentFlagName =
+42
View File
@@ -65,6 +65,8 @@ import {
DEFAULT_GEMINI_MODEL,
PREVIEW_GEMINI_3_1_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_FLASH_MODEL,
} from './models.js';
import { Storage } from './storage.js';
import type { AgentLoopContext } from './agent-loop-context.js';
@@ -687,6 +689,46 @@ describe('Server Config (config.ts)', () => {
loopContext.geminiClient.stripThoughtsFromHistory,
).not.toHaveBeenCalledWith();
});
it('should switch to flash model if user has no Pro access and model is auto', async () => {
vi.mocked(getExperiments).mockResolvedValue({
experimentIds: [],
flags: {
[ExperimentFlags.PRO_MODEL_NO_ACCESS]: {
boolValue: true,
},
},
});
const config = new Config({
...baseParams,
model: PREVIEW_GEMINI_MODEL_AUTO,
});
await config.refreshAuth(AuthType.LOGIN_WITH_GOOGLE);
expect(config.getModel()).toBe(PREVIEW_GEMINI_FLASH_MODEL);
});
it('should NOT switch to flash model if user has Pro access and model is auto', async () => {
vi.mocked(getExperiments).mockResolvedValue({
experimentIds: [],
flags: {
[ExperimentFlags.PRO_MODEL_NO_ACCESS]: {
boolValue: false,
},
},
});
const config = new Config({
...baseParams,
model: PREVIEW_GEMINI_MODEL_AUTO,
});
await config.refreshAuth(AuthType.LOGIN_WITH_GOOGLE);
expect(config.getModel()).toBe(PREVIEW_GEMINI_MODEL_AUTO);
});
});
it('Config constructor should store userMemory correctly', () => {
+28
View File
@@ -1386,6 +1386,10 @@ export class Config implements McpContext, AgentLoopContext {
},
);
this.setRemoteAdminSettings(adminControls);
if ((await this.getProModelNoAccess()) && isAutoModel(this.model)) {
this.setModel(PREVIEW_GEMINI_FLASH_MODEL);
}
}
async getExperimentsAsync(): Promise<Experiments | undefined> {
@@ -2681,6 +2685,30 @@ export class Config implements McpContext, AgentLoopContext {
);
}
/**
* Returns whether the user has access to Pro models.
* This is determined by the PRO_MODEL_NO_ACCESS experiment flag.
*/
async getProModelNoAccess(): Promise<boolean> {
await this.ensureExperimentsLoaded();
return this.getProModelNoAccessSync();
}
/**
* Returns whether the user has access to Pro models synchronously.
*
* Note: This method should only be called after startup, once experiments have been loaded.
*/
getProModelNoAccessSync(): boolean {
if (this.contentGeneratorConfig?.authType !== AuthType.LOGIN_WITH_GOOGLE) {
return false;
}
return (
this.experiments?.flags[ExperimentFlags.PRO_MODEL_NO_ACCESS]?.boolValue ??
false
);
}
/**
* Returns whether Gemini 3.1 has been launched.
* This method is async and ensures that experiments are loaded before returning the result.
+15
View File
@@ -27,6 +27,7 @@ import {
DEFAULT_GEMINI_MODEL_AUTO,
isActiveModel,
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
isPreviewModel,
isProModel,
@@ -245,6 +246,12 @@ describe('getDisplayString', () => {
);
});
it('should return PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL for PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL', () => {
expect(getDisplayString(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe(
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
);
});
it('should return the model name as is for other models', () => {
expect(getDisplayString('custom-model')).toBe('custom-model');
expect(getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL)).toBe(
@@ -321,6 +328,12 @@ describe('resolveModel', () => {
).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return default flash lite model when access to preview is false and preview flash lite model is requested', () => {
expect(
resolveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, false, false, false),
).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return default model when access to preview is false and auto-gemini-3 is requested', () => {
expect(resolveModel(PREVIEW_GEMINI_MODEL_AUTO, false, false, false)).toBe(
DEFAULT_GEMINI_MODEL,
@@ -439,6 +452,7 @@ describe('isActiveModel', () => {
expect(isActiveModel(DEFAULT_GEMINI_MODEL)).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_MODEL)).toBe(true);
expect(isActiveModel(DEFAULT_GEMINI_FLASH_MODEL)).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe(true);
});
it('should return true for unknown models and aliases', () => {
@@ -452,6 +466,7 @@ describe('isActiveModel', () => {
it('should return true for other valid models when useGemini3_1 is true', () => {
expect(isActiveModel(DEFAULT_GEMINI_MODEL, true)).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, true)).toBe(true);
});
it('should correctly filter Gemini 3.1 models based on useCustomToolModel when useGemini3_1 is true', () => {
+5 -1
View File
@@ -36,6 +36,8 @@ export const PREVIEW_GEMINI_3_1_MODEL = 'gemini-3.1-pro-preview';
export const PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL =
'gemini-3.1-pro-preview-customtools';
export const PREVIEW_GEMINI_FLASH_MODEL = 'gemini-3-flash-preview';
export const PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL =
'gemini-3.1-flash-lite-preview';
export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro';
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite';
@@ -45,6 +47,7 @@ export const VALID_GEMINI_MODELS = new Set([
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
@@ -216,7 +219,8 @@ export function isPreviewModel(
model === PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL ||
model === PREVIEW_GEMINI_FLASH_MODEL ||
model === PREVIEW_GEMINI_MODEL_AUTO ||
model === GEMINI_MODEL_ALIAS_AUTO
model === GEMINI_MODEL_ALIAS_AUTO ||
model === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL
);
}