@@ -20,6 +20,7 @@ import { setSimulate429 } from '../utils/testUtils.js';
2020import {
2121 DEFAULT_GEMINI_FLASH_MODEL ,
2222 DEFAULT_GEMINI_MODEL ,
23+ DEFAULT_THINKING_MODE ,
2324 PREVIEW_GEMINI_MODEL ,
2425} from '../config/models.js' ;
2526import { AuthType } from './contentGenerator.js' ;
@@ -131,15 +132,22 @@ describe('GeminiChat', () => {
131132 getContentGenerator : vi . fn ( ) . mockReturnValue ( mockContentGenerator ) ,
132133 getRetryFetchErrors : vi . fn ( ) . mockReturnValue ( false ) ,
133134 modelConfigService : {
134- getResolvedConfig : vi . fn ( ) . mockImplementation ( ( modelConfigKey ) => ( {
135- model : modelConfigKey . model ,
136- generateContentConfig : {
137- temperature : 0 ,
138- thinkingConfig : {
139- thinkingBudget : 1000 ,
135+ getResolvedConfig : vi . fn ( ) . mockImplementation ( ( modelConfigKey ) => {
136+ const thinkingConfig = modelConfigKey . model . startsWith ( 'gemini-3' )
137+ ? {
138+ thinkingLevel : ThinkingLevel . HIGH ,
139+ }
140+ : {
141+ thinkingBudget : DEFAULT_THINKING_MODE ,
142+ } ;
143+ return {
144+ model : modelConfigKey . model ,
145+ generateContentConfig : {
146+ temperature : 0 ,
147+ thinkingConfig,
140148 } ,
141- } ,
142- } ) ) ,
149+ } ;
150+ } ) ,
143151 } ,
144152 isPreviewModelBypassMode : vi . fn ( ) . mockReturnValue ( false ) ,
145153 setPreviewModelBypassMode : vi . fn ( ) ,
@@ -976,7 +984,7 @@ describe('GeminiChat', () => {
976984 tools : [ ] ,
977985 temperature : 0 ,
978986 thinkingConfig : {
979- thinkingBudget : 1000 ,
987+ thinkingBudget : DEFAULT_THINKING_MODE ,
980988 } ,
981989 abortSignal : expect . any ( AbortSignal ) ,
982990 } ,
@@ -1023,6 +1031,45 @@ describe('GeminiChat', () => {
10231031 'prompt-id-thinking-level' ,
10241032 ) ;
10251033 } ) ;
1034+
1035+ it ( 'should use thinkingBudget and remove thinkingLevel for non-gemini-3 models' , async ( ) => {
1036+ const response = ( async function * ( ) {
1037+ yield {
1038+ candidates : [
1039+ {
1040+ content : { parts : [ { text : 'response' } ] , role : 'model' } ,
1041+ finishReason : 'STOP' ,
1042+ } ,
1043+ ] ,
1044+ } as unknown as GenerateContentResponse ;
1045+ } ) ( ) ;
1046+ vi . mocked ( mockContentGenerator . generateContentStream ) . mockResolvedValue (
1047+ response ,
1048+ ) ;
1049+
1050+ const stream = await chat . sendMessageStream (
1051+ { model : 'gemini-2.0-flash' } ,
1052+ 'hello' ,
1053+ 'prompt-id-thinking-budget' ,
1054+ new AbortController ( ) . signal ,
1055+ ) ;
1056+ for await ( const _ of stream ) {
1057+ // consume stream
1058+ }
1059+
1060+ expect ( mockContentGenerator . generateContentStream ) . toHaveBeenCalledWith (
1061+ expect . objectContaining ( {
1062+ model : 'gemini-2.0-flash' ,
1063+ config : expect . objectContaining ( {
1064+ thinkingConfig : {
1065+ thinkingBudget : DEFAULT_THINKING_MODE ,
1066+ thinkingLevel : undefined ,
1067+ } ,
1068+ } ) ,
1069+ } ) ,
1070+ 'prompt-id-thinking-budget' ,
1071+ ) ;
1072+ } ) ;
10261073 } ) ;
10271074
10281075 describe ( 'addHistory' , ( ) => {
@@ -1902,6 +1949,92 @@ describe('GeminiChat', () => {
19021949 expect ( modelTurn . parts ! [ 0 ] ! . text ) . toBe ( 'Success on retry' ) ;
19031950 } ) ;
19041951
1952+ it ( 'should switch to DEFAULT_GEMINI_FLASH_MODEL and use thinkingBudget when falling back from a gemini-3 model' , async ( ) => {
1953+ // ARRANGE
1954+ const authType = AuthType . LOGIN_WITH_GOOGLE ;
1955+ vi . mocked ( mockConfig . getContentGeneratorConfig ) . mockReturnValue ( {
1956+ authType,
1957+ } ) ;
1958+
1959+ // Initial state: Not in fallback mode
1960+ const isInFallbackModeSpy = vi . spyOn ( mockConfig , 'isInFallbackMode' ) ;
1961+ isInFallbackModeSpy . mockReturnValue ( false ) ;
1962+
1963+ // Mock API calls:
1964+ // 1. Fails with 429 (simulating gemini-3 failure)
1965+ // 2. Succeeds (simulating fallback success)
1966+ vi . mocked ( mockContentGenerator . generateContentStream )
1967+ . mockRejectedValueOnce ( error429 )
1968+ . mockResolvedValueOnce (
1969+ ( async function * ( ) {
1970+ yield {
1971+ candidates : [
1972+ {
1973+ content : { parts : [ { text : 'Fallback success' } ] } ,
1974+ finishReason : 'STOP' ,
1975+ } ,
1976+ ] ,
1977+ } as unknown as GenerateContentResponse ;
1978+ } ) ( ) ,
1979+ ) ;
1980+
1981+ // Mock handleFallback to enable fallback mode and signal retry
1982+ mockHandleFallback . mockImplementation ( async ( ) => {
1983+ isInFallbackModeSpy . mockReturnValue ( true ) ; // Next call will see fallback mode = true
1984+ return true ;
1985+ } ) ;
1986+
1987+ // ACT
1988+ const stream = await chat . sendMessageStream (
1989+ { model : 'gemini-3-test-model' } , // Start with a gemini-3 model
1990+ 'test fallback thinking' ,
1991+ 'prompt-id-fb3' ,
1992+ new AbortController ( ) . signal ,
1993+ ) ;
1994+ for await ( const _ of stream ) {
1995+ // consume stream
1996+ }
1997+
1998+ // ASSERT
1999+ expect ( mockContentGenerator . generateContentStream ) . toHaveBeenCalledTimes (
2000+ 2 ,
2001+ ) ;
2002+
2003+ // First call: gemini-3 model, thinkingLevel set
2004+ expect (
2005+ mockContentGenerator . generateContentStream ,
2006+ ) . toHaveBeenNthCalledWith (
2007+ 1 ,
2008+ expect . objectContaining ( {
2009+ model : 'gemini-3-test-model' ,
2010+ config : expect . objectContaining ( {
2011+ thinkingConfig : {
2012+ thinkingBudget : undefined ,
2013+ thinkingLevel : ThinkingLevel . HIGH ,
2014+ } ,
2015+ } ) ,
2016+ } ) ,
2017+ 'prompt-id-fb3' ,
2018+ ) ;
2019+
2020+ // Second call: DEFAULT_GEMINI_FLASH_MODEL (due to fallback), thinkingBudget set (due to fix)
2021+ expect (
2022+ mockContentGenerator . generateContentStream ,
2023+ ) . toHaveBeenNthCalledWith (
2024+ 2 ,
2025+ expect . objectContaining ( {
2026+ model : DEFAULT_GEMINI_FLASH_MODEL ,
2027+ config : expect . objectContaining ( {
2028+ thinkingConfig : {
2029+ thinkingBudget : DEFAULT_THINKING_MODE ,
2030+ thinkingLevel : undefined ,
2031+ } ,
2032+ } ) ,
2033+ } ) ,
2034+ 'prompt-id-fb3' ,
2035+ ) ;
2036+ } ) ;
2037+
19052038 it ( 'should stop retrying if handleFallback returns false (e.g., auth intent)' , async ( ) => {
19062039 vi . mocked ( mockConfig . getModel ) . mockReturnValue ( 'gemini-pro' ) ;
19072040 vi . mocked ( mockContentGenerator . generateContentStream ) . mockRejectedValue (
0 commit comments