@@ -26,6 +26,7 @@ jest.mock("@anthropic-ai/vertex-sdk", () => ({
2626 async * [ Symbol . asyncIterator ] ( ) {
2727 yield {
2828 type : "message_start" ,
29+ index : 0 ,
2930 message : {
3031 usage : {
3132 input_tokens : 10 ,
@@ -35,6 +36,15 @@ jest.mock("@anthropic-ai/vertex-sdk", () => ({
3536 }
3637 yield {
3738 type : "content_block_start" ,
39+ index : 0 ,
40+ content_block : {
41+ type : "thinking" ,
42+ thinking : "Thinking test" ,
43+ } ,
44+ }
45+ yield {
46+ type : "content_block_start" ,
47+ index : 1 ,
3848 content_block : {
3949 type : "text" ,
4050 text : "Test response" ,
@@ -53,6 +63,7 @@ describe("VertexHandler", () => {
5363 beforeEach ( ( ) => {
5464 handler = new VertexHandler ( {
5565 apiModelId : "claude-3-5-sonnet-v2@20241022" ,
66+ anthropicThinking : undefined ,
5667 vertexProjectId : "test-project" ,
5768 vertexRegion : "us-central1" ,
5869 } )
@@ -124,7 +135,7 @@ describe("VertexHandler", () => {
124135 } ,
125136 }
126137
127- const mockCreate = jest . fn ( ) . mockResolvedValue ( asyncIterator )
138+ const mockCreate = jest . fn ( ) . mockImplementation ( ( ) => asyncIterator )
128139 ; ( handler [ "client" ] . messages as any ) . create = mockCreate
129140
130141 const stream = handler . createMessage ( systemPrompt , mockMessages )
@@ -160,10 +171,168 @@ describe("VertexHandler", () => {
160171 temperature : 0 ,
161172 system : systemPrompt ,
162173 messages : mockMessages ,
174+ thinking : undefined ,
163175 stream : true ,
164176 } )
165177 } )
166178
179+ it ( "should include thinking configuration for supported models" , async ( ) => {
180+ // Create a handler with the thinking-capable model
181+ const thinkingHandler = new VertexHandler ( {
182+ apiModelId : "claude-3-7-sonnet@20250219" ,
183+ anthropicThinking : 16384 , // Set thinking budget
184+ vertexProjectId : "test-project" ,
185+ vertexRegion : "us-central1" ,
186+ } )
187+
188+ const mockCreate = jest . fn ( ) . mockResolvedValue ( {
189+ async * [ Symbol . asyncIterator ] ( ) {
190+ yield { type : "message_start" , message : { usage : { input_tokens : 10 , output_tokens : 0 } } }
191+ } ,
192+ usage : { } ,
193+ } )
194+ ; ( thinkingHandler [ "client" ] . messages as any ) . create = mockCreate
195+
196+ const stream = thinkingHandler . createMessage ( systemPrompt , mockMessages )
197+ for await ( const _ of stream ) {
198+ // Just consuming the stream
199+ }
200+
201+ // Verify that mock was called
202+ expect ( mockCreate ) . toHaveBeenCalled ( )
203+
204+ // Get the actual call arguments
205+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
206+
207+ // Verify specific properties instead of the entire object
208+ expect ( callArgs . model ) . toBe ( "claude-3-7-sonnet@20250219" )
209+ expect ( callArgs . max_tokens ) . toBe ( 17408 ) // thinking.budget_tokens (16384) + 1024
210+ expect ( callArgs . temperature ) . toBe ( 1.0 )
211+ expect ( callArgs . system ) . toBe ( systemPrompt )
212+ expect ( callArgs . messages ) . toEqual ( mockMessages )
213+ expect ( callArgs . stream ) . toBe ( true )
214+ expect ( callArgs . thinking ) . toEqual ( { type : "enabled" , budget_tokens : 16384 } )
215+ } )
216+
217+ it ( "should explicitly disable thinking if the option is undefined" , async ( ) => {
218+ // Create a handler with the thinking-capable model but no thinking budget
219+ const thinkingHandler = new VertexHandler ( {
220+ apiModelId : "claude-3-7-sonnet@20250219" ,
221+ anthropicThinking : undefined ,
222+ vertexProjectId : "test-project" ,
223+ vertexRegion : "us-central1" ,
224+ } )
225+
226+ const mockCreate = jest . fn ( ) . mockResolvedValue ( {
227+ async * [ Symbol . asyncIterator ] ( ) {
228+ yield { type : "message_start" , message : { usage : { input_tokens : 10 , output_tokens : 0 } } }
229+ } ,
230+ usage : { } ,
231+ } )
232+ ; ( thinkingHandler [ "client" ] . messages as any ) . create = mockCreate
233+
234+ const stream = thinkingHandler . createMessage ( systemPrompt , mockMessages )
235+ for await ( const _ of stream ) {
236+ // Just consuming the stream
237+ }
238+
239+ // Verify that mock was called
240+ expect ( mockCreate ) . toHaveBeenCalled ( )
241+
242+ // Get the actual call arguments
243+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
244+
245+ // Verify specific properties instead of the entire object
246+ expect ( callArgs . model ) . toBe ( "claude-3-7-sonnet@20250219" )
247+ expect ( callArgs . max_tokens ) . toBe ( 8192 ) // Default max_tokens when thinking is disabled
248+ expect ( callArgs . temperature ) . toBe ( 1.0 )
249+ expect ( callArgs . system ) . toBe ( systemPrompt )
250+ expect ( callArgs . messages ) . toEqual ( mockMessages )
251+ expect ( callArgs . stream ) . toBe ( true )
252+ expect ( callArgs . thinking ) . toEqual ( { type : "disabled" } )
253+ } )
254+
255+ it ( "should handle thinking content blocks" , async ( ) => {
256+ const mockStream = [
257+ {
258+ type : "message_start" ,
259+ message : {
260+ usage : {
261+ input_tokens : 10 ,
262+ output_tokens : 0 ,
263+ } ,
264+ } ,
265+ } ,
266+ {
267+ type : "content_block_start" ,
268+ index : 0 ,
269+ content_block : {
270+ type : "thinking" ,
271+ thinking : "Let me reason through this step by step" ,
272+ } ,
273+ } ,
274+ {
275+ type : "content_block_delta" ,
276+ delta : {
277+ type : "thinking_delta" ,
278+ thinking : ". First, I need to understand..." ,
279+ } ,
280+ } ,
281+ {
282+ type : "content_block_start" ,
283+ index : 1 ,
284+ content_block : {
285+ type : "text" ,
286+ text : "Based on my analysis," ,
287+ } ,
288+ } ,
289+ {
290+ type : "content_block_delta" ,
291+ delta : {
292+ type : "text_delta" ,
293+ text : " here is my response." ,
294+ } ,
295+ } ,
296+ {
297+ type : "message_delta" ,
298+ usage : { } ,
299+ } ,
300+ ]
301+
302+ const asyncIterator = {
303+ async * [ Symbol . asyncIterator ] ( ) {
304+ for ( const chunk of mockStream ) {
305+ yield chunk
306+ }
307+ } ,
308+ }
309+
310+ const mockCreate = jest . fn ( ) . mockResolvedValue ( asyncIterator )
311+ ; ( handler [ "client" ] . messages as any ) . create = mockCreate
312+
313+ const stream = handler . createMessage ( systemPrompt , mockMessages )
314+ const chunks = [ ]
315+
316+ for await ( const chunk of stream ) {
317+ chunks . push ( chunk )
318+ }
319+
320+ // The actual chunks may vary depending on how the stream is handled
321+ // So instead of checking the exact count, we'll check the specific chunks we care about
322+ expect ( chunks [ 0 ] ) . toEqual ( { type : "usage" , inputTokens : 10 , outputTokens : 0 } )
323+
324+ // Check that reasoning and text chunks exist in the output
325+ expect (
326+ chunks . some (
327+ ( chunk ) => chunk . type === "reasoning" && chunk . text === "Let me reason through this step by step" ,
328+ ) ,
329+ ) . toBeTruthy ( )
330+ expect (
331+ chunks . some ( ( chunk ) => chunk . type === "reasoning" && chunk . text === ". First, I need to understand..." ) ,
332+ ) . toBeTruthy ( )
333+ expect ( chunks . some ( ( chunk ) => chunk . type === "text" && chunk . text === "Based on my analysis," ) ) . toBeTruthy ( )
334+ } )
335+
167336 it ( "should handle multiple content blocks with line breaks" , async ( ) => {
168337 const mockStream = [
169338 {
@@ -242,6 +411,7 @@ describe("VertexHandler", () => {
242411 temperature : 0 ,
243412 messages : [ { role : "user" , content : "Test prompt" } ] ,
244413 stream : false ,
414+ thinking : undefined ,
245415 } )
246416 } )
247417
@@ -265,6 +435,38 @@ describe("VertexHandler", () => {
265435 expect ( result ) . toBe ( "" )
266436 } )
267437
438+ it ( "should include thinking configuration for supported models" , async ( ) => {
439+ // Create a handler with the thinking-capable model
440+ const thinkingHandler = new VertexHandler ( {
441+ apiModelId : "claude-3-7-sonnet@20250219" ,
442+ anthropicThinking : 16384 ,
443+ vertexProjectId : "test-project" ,
444+ vertexRegion : "us-central1" ,
445+ } )
446+
447+ const mockCreate = jest . fn ( ) . mockResolvedValue ( {
448+ content : [ { type : "text" , text : "Test response with thinking" } ] ,
449+ usage : { } ,
450+ } )
451+ ; ( thinkingHandler [ "client" ] . messages as any ) . create = mockCreate
452+
453+ await thinkingHandler . completePrompt ( "Test prompt" )
454+
455+ // Verify that mock was called
456+ expect ( mockCreate ) . toHaveBeenCalled ( )
457+
458+ // Get the actual call arguments
459+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ]
460+
461+ // Verify specific properties instead of the entire object
462+ expect ( callArgs . model ) . toBe ( "claude-3-7-sonnet@20250219" )
463+ expect ( callArgs . max_tokens ) . toBe ( 17408 ) // thinking.budget_tokens (16384) + 1024
464+ expect ( callArgs . temperature ) . toBe ( 1.0 )
465+ expect ( callArgs . thinking ) . toEqual ( { type : "enabled" , budget_tokens : 16384 } )
466+ expect ( callArgs . messages ) . toEqual ( [ { role : "user" , content : "Test prompt" } ] )
467+ expect ( callArgs . stream ) . toBe ( false )
468+ } )
469+
268470 it ( "should handle empty response" , async ( ) => {
269471 const mockCreate = jest . fn ( ) . mockResolvedValue ( {
270472 content : [ { type : "text" , text : "" } ] ,
0 commit comments