@@ -24,15 +24,16 @@ vi.mock("./custom-api-registry.js", () => ({
2424 ensureCustomApiRegistered,
2525} ) ) ;
2626
27- const { prepareGoogleSimpleCompletionModel } = await import (
28- "./google-simple-completion-stream.js"
29- ) ;
27+ const { prepareGoogleSimpleCompletionModel } = await import ( "./google-simple-completion-stream.js" ) ;
3028
3129const GOOGLE_SIMPLE_COMPLETION_API = "openclaw-google-generative-ai-simple" ;
3230
3331// Mirrors the provider catalog shape closely enough for wrapper registration
3432// without pulling live Google model discovery into unit tests.
35- function makeGoogleModel ( id = "gemini-flash-latest" ) : Model < "google-generative-ai" > {
33+ function makeGoogleModel (
34+ id = "gemini-flash-latest" ,
35+ overrides : Partial < Model < "google-generative-ai" > > = { } ,
36+ ) : Model < "google-generative-ai" > {
3637 return {
3738 id,
3839 name : id ,
@@ -45,6 +46,7 @@ function makeGoogleModel(id = "gemini-flash-latest"): Model<"google-generative-a
4546 contextWindow : 1_000_000 ,
4647 maxTokens : 8192 ,
4748 headers : { } ,
49+ ...overrides ,
4850 } ;
4951}
5052
@@ -152,4 +154,63 @@ describe("prepareGoogleSimpleCompletionModel", () => {
152154 ) . payload . generationConfig . thinkingConfig ,
153155 ) . not . toHaveProperty ( "thinkingBudget" ) ;
154156 } ) ;
157+
158+ it . each ( [ "xhigh" , "max" ] as const ) (
159+ "preserves clamped-off intent in the final Gemini 3 payload for reasoning=%s" ,
160+ async ( reasoning ) => {
161+ const actual = await vi . importActual <
162+ typeof import ( "../plugin-sdk/provider-stream-shared.js" )
163+ > ( "../plugin-sdk/provider-stream-shared.js" ) ;
164+ sanitizeGoogleThinkingPayload . mockImplementationOnce ( actual . sanitizeGoogleThinkingPayload ) ;
165+ streamSimple . mockImplementationOnce ( ( _model , _context , options ) => {
166+ const payload = {
167+ generationConfig : {
168+ thinkingConfig : { thinkingLevel : "MINIMAL" } ,
169+ } ,
170+ } ;
171+ options ?. onPayload ?.( payload , _model ) ;
172+ return { content : [ { type : "text" , text : "ok" } ] , payload } ;
173+ } ) ;
174+ const model = makeGoogleModel ( "gemini-3-flash-preview" , {
175+ thinkingLevelMap : {
176+ minimal : null ,
177+ low : null ,
178+ medium : null ,
179+ high : null ,
180+ xhigh : null ,
181+ max : null ,
182+ } ,
183+ } ) ;
184+ const wrapped = prepareGoogleSimpleCompletionModel ( model ) ;
185+ const streamFn = ensureCustomApiRegistered . mock . calls [ 0 ] ?. [ 1 ] as (
186+ ...args : unknown [ ]
187+ ) => unknown ;
188+
189+ const result = await streamFn ( wrapped , { messages : [ ] } , { apiKey : "key" , reasoning } ) ;
190+
191+ expect ( sanitizeGoogleThinkingPayload ) . toHaveBeenCalledWith ( {
192+ payload : {
193+ generationConfig : {
194+ thinkingConfig : { thinkingLevel : "MINIMAL" } ,
195+ } ,
196+ } ,
197+ modelId : "gemini-3-flash-preview" ,
198+ thinkingLevel : "off" ,
199+ } ) ;
200+ expect ( result ) . toMatchObject ( {
201+ payload : {
202+ generationConfig : {
203+ thinkingConfig : { thinkingLevel : "MINIMAL" } ,
204+ } ,
205+ } ,
206+ } ) ;
207+ expect (
208+ (
209+ result as {
210+ payload : { generationConfig : { thinkingConfig : Record < string , unknown > } } ;
211+ }
212+ ) . payload . generationConfig . thinkingConfig ,
213+ ) . not . toHaveProperty ( "includeThoughts" ) ;
214+ } ,
215+ ) ;
155216} ) ;
0 commit comments