@@ -5,6 +5,7 @@ import { OpenAiHandler } from "../openai"
55import { ApiHandlerOptions } from "../../../shared/api"
66import { Anthropic } from "@anthropic-ai/sdk"
77import OpenAI from "openai"
8+ import { openAiModelInfoSaneDefaults } from "@roo-code/types"
89
910const mockCreate = vitest . fn ( )
1011
@@ -197,6 +198,113 @@ describe("OpenAiHandler", () => {
197198 const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
198199 expect ( callArgs . reasoning_effort ) . toBeUndefined ( )
199200 } )
201+
202+ it ( "should include max_tokens when includeMaxTokens is true" , async ( ) => {
203+ const optionsWithMaxTokens : ApiHandlerOptions = {
204+ ...mockOptions ,
205+ includeMaxTokens : true ,
206+ openAiCustomModelInfo : {
207+ contextWindow : 128_000 ,
208+ maxTokens : 4096 ,
209+ supportsPromptCache : false ,
210+ } ,
211+ }
212+ const handlerWithMaxTokens = new OpenAiHandler ( optionsWithMaxTokens )
213+ const stream = handlerWithMaxTokens . createMessage ( systemPrompt , messages )
214+ // Consume the stream to trigger the API call
215+ for await ( const _chunk of stream ) {
216+ }
217+ // Assert the mockCreate was called with max_tokens
218+ expect ( mockCreate ) . toHaveBeenCalled ( )
219+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
220+ expect ( callArgs . max_tokens ) . toBe ( 4096 )
221+ } )
222+
223+ it ( "should not include max_tokens when includeMaxTokens is false" , async ( ) => {
224+ const optionsWithoutMaxTokens : ApiHandlerOptions = {
225+ ...mockOptions ,
226+ includeMaxTokens : false ,
227+ openAiCustomModelInfo : {
228+ contextWindow : 128_000 ,
229+ maxTokens : 4096 ,
230+ supportsPromptCache : false ,
231+ } ,
232+ }
233+ const handlerWithoutMaxTokens = new OpenAiHandler ( optionsWithoutMaxTokens )
234+ const stream = handlerWithoutMaxTokens . createMessage ( systemPrompt , messages )
235+ // Consume the stream to trigger the API call
236+ for await ( const _chunk of stream ) {
237+ }
238+ // Assert the mockCreate was called without max_tokens
239+ expect ( mockCreate ) . toHaveBeenCalled ( )
240+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
241+ expect ( callArgs . max_tokens ) . toBeUndefined ( )
242+ } )
243+
244+ it ( "should not include max_tokens when includeMaxTokens is undefined" , async ( ) => {
245+ const optionsWithUndefinedMaxTokens : ApiHandlerOptions = {
246+ ...mockOptions ,
247+ // includeMaxTokens is not set, should not include max_tokens
248+ openAiCustomModelInfo : {
249+ contextWindow : 128_000 ,
250+ maxTokens : 4096 ,
251+ supportsPromptCache : false ,
252+ } ,
253+ }
254+ const handlerWithDefaultMaxTokens = new OpenAiHandler ( optionsWithUndefinedMaxTokens )
255+ const stream = handlerWithDefaultMaxTokens . createMessage ( systemPrompt , messages )
256+ // Consume the stream to trigger the API call
257+ for await ( const _chunk of stream ) {
258+ }
259+ // Assert the mockCreate was called without max_tokens
260+ expect ( mockCreate ) . toHaveBeenCalled ( )
261+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
262+ expect ( callArgs . max_tokens ) . toBeUndefined ( )
263+ } )
264+
265+ it ( "should use user-configured modelMaxTokens instead of model default maxTokens" , async ( ) => {
266+ const optionsWithUserMaxTokens : ApiHandlerOptions = {
267+ ...mockOptions ,
268+ includeMaxTokens : true ,
269+ modelMaxTokens : 32000 , // User-configured value
270+ openAiCustomModelInfo : {
271+ contextWindow : 128_000 ,
272+ maxTokens : 4096 , // Model's default value (should not be used)
273+ supportsPromptCache : false ,
274+ } ,
275+ }
276+ const handlerWithUserMaxTokens = new OpenAiHandler ( optionsWithUserMaxTokens )
277+ const stream = handlerWithUserMaxTokens . createMessage ( systemPrompt , messages )
278+ // Consume the stream to trigger the API call
279+ for await ( const _chunk of stream ) {
280+ }
281+ // Assert the mockCreate was called with user-configured modelMaxTokens (32000), not model default maxTokens (4096)
282+ expect ( mockCreate ) . toHaveBeenCalled ( )
283+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
284+ expect ( callArgs . max_tokens ) . toBe ( 32000 )
285+ } )
286+
287+ it ( "should fallback to model default maxTokens when user modelMaxTokens is not set" , async ( ) => {
288+ const optionsWithoutUserMaxTokens : ApiHandlerOptions = {
289+ ...mockOptions ,
290+ includeMaxTokens : true ,
291+ // modelMaxTokens is not set
292+ openAiCustomModelInfo : {
293+ contextWindow : 128_000 ,
294+ maxTokens : 4096 , // Model's default value (should be used as fallback)
295+ supportsPromptCache : false ,
296+ } ,
297+ }
298+ const handlerWithoutUserMaxTokens = new OpenAiHandler ( optionsWithoutUserMaxTokens )
299+ const stream = handlerWithoutUserMaxTokens . createMessage ( systemPrompt , messages )
300+ // Consume the stream to trigger the API call
301+ for await ( const _chunk of stream ) {
302+ }
303+ // Assert the mockCreate was called with model default maxTokens (4096) as fallback
304+ expect ( mockCreate ) . toHaveBeenCalled ( )
305+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
306+ expect ( callArgs . max_tokens ) . toBe ( 4096 )
307+ } )
200308 } )
201309
202310 describe ( "error handling" , ( ) => {
@@ -333,6 +441,7 @@ describe("OpenAiHandler", () => {
333441 stream : true ,
334442 stream_options : { include_usage : true } ,
335443 temperature : 0 ,
444+ max_tokens : - 1 ,
336445 } ,
337446 { path : "/models/chat/completions" } ,
338447 )
@@ -375,6 +484,7 @@ describe("OpenAiHandler", () => {
375484 { role : "user" , content : systemPrompt } ,
376485 { role : "user" , content : "Hello!" } ,
377486 ] ,
487+ max_tokens : - 1 , // Default from openAiModelInfoSaneDefaults
378488 } ,
379489 { path : "/models/chat/completions" } ,
380490 )
@@ -388,6 +498,7 @@ describe("OpenAiHandler", () => {
388498 {
389499 model : azureOptions . openAiModelId ,
390500 messages : [ { role : "user" , content : "Test prompt" } ] ,
501+ max_tokens : - 1 , // Default from openAiModelInfoSaneDefaults
391502 } ,
392503 { path : "/models/chat/completions" } ,
393504 )
0 commit comments