@@ -170,87 +170,91 @@ func TestAnthropicMessages(t *testing.T) {
170170func TestAnthropicMessagesModelThoughts (t * testing.T ) {
171171 t .Parallel ()
172172
173- t .Run ("thinking captured with builtin tool" , func (t * testing.T ) {
174- t .Parallel ()
175-
176- cases := []struct {
177- name string
178- streaming bool
179- fixture []byte
180- expectedToolCallID string
181- expectedThoughts []string
182- }{
183- {
184- name : "single thinking block/streaming" ,
185- streaming : true ,
186- fixture : fixtures .AntSingleBuiltinTool ,
187- expectedToolCallID : "toolu_01RX68weRSquLx6HUTj65iBo" ,
188- expectedThoughts : []string {"The user wants me to read" },
189- },
190- {
191- name : "single thinking block/blocking" ,
192- streaming : false ,
193- fixture : fixtures .AntSingleBuiltinTool ,
194- expectedToolCallID : "toolu_01AusGgY5aKFhzWrFBv9JfHq" ,
195- expectedThoughts : []string {"The user wants me to read" },
196- },
197- {
198- name : "multiple thinking blocks/streaming" ,
199- streaming : true ,
200- fixture : fixtures .AntMultiThinkingBuiltinTool ,
201- expectedToolCallID : "toolu_01RX68weRSquLx6HUTj65iBo" ,
202- expectedThoughts : []string {"The user wants me to read" , "I should use the Read tool" },
203- },
204- {
205- name : "multiple thinking blocks/blocking" ,
206- streaming : false ,
207- fixture : fixtures .AntMultiThinkingBuiltinTool ,
208- expectedToolCallID : "toolu_01AusGgY5aKFhzWrFBv9JfHq" ,
209- expectedThoughts : []string {"The user wants me to read" , "I should use the Read tool" },
210- },
211- }
173+ cases := []struct {
174+ name string
175+ streaming bool
176+ fixture []byte
177+ expectedToolCallID string
178+ expectedThoughts []string // nil means no tool usages expected at all
179+ }{
180+ {
181+ name : "single thinking block/streaming" ,
182+ streaming : true ,
183+ fixture : fixtures .AntSingleBuiltinTool ,
184+ expectedToolCallID : "toolu_01RX68weRSquLx6HUTj65iBo" ,
185+ expectedThoughts : []string {"The user wants me to read" },
186+ },
187+ {
188+ name : "single thinking block/blocking" ,
189+ streaming : false ,
190+ fixture : fixtures .AntSingleBuiltinTool ,
191+ expectedToolCallID : "toolu_01AusGgY5aKFhzWrFBv9JfHq" ,
192+ expectedThoughts : []string {"The user wants me to read" },
193+ },
194+ {
195+ name : "multiple thinking blocks/streaming" ,
196+ streaming : true ,
197+ fixture : fixtures .AntMultiThinkingBuiltinTool ,
198+ expectedToolCallID : "toolu_01RX68weRSquLx6HUTj65iBo" ,
199+ expectedThoughts : []string {"The user wants me to read" , "I should use the Read tool" },
200+ },
201+ {
202+ name : "multiple thinking blocks/blocking" ,
203+ streaming : false ,
204+ fixture : fixtures .AntMultiThinkingBuiltinTool ,
205+ expectedToolCallID : "toolu_01AusGgY5aKFhzWrFBv9JfHq" ,
206+ expectedThoughts : []string {"The user wants me to read" , "I should use the Read tool" },
207+ },
208+ {
209+ name : "no thoughts without tool calls" ,
210+ streaming : true ,
211+ fixture : fixtures .AntSimple , // This fixture contains thoughts, but they're not associated with tool calls.
212+ },
213+ }
212214
213- for _ , tc := range cases {
214- t .Run (tc .name , func (t * testing.T ) {
215- t .Parallel ()
215+ for _ , tc := range cases {
216+ t .Run (tc .name , func (t * testing.T ) {
217+ t .Parallel ()
216218
217- ctx , cancel := context .WithTimeout (t .Context (), time .Second * 30 )
218- t .Cleanup (cancel )
219+ ctx , cancel := context .WithTimeout (t .Context (), time .Second * 30 )
220+ t .Cleanup (cancel )
219221
220- fix := fixtures .Parse (t , tc .fixture )
221- upstream := testutil .NewMockUpstream (t , ctx , testutil .NewFixtureResponse (fix ))
222+ fix := fixtures .Parse (t , tc .fixture )
223+ upstream := testutil .NewMockUpstream (t , ctx , testutil .NewFixtureResponse (fix ))
222224
223- recorderClient := & testutil.MockRecorder {}
224- logger := slogtest .Make (t , & slogtest.Options {}).Leveled (slog .LevelDebug )
225- providers := []aibridge.Provider {provider .NewAnthropic (anthropicCfg (upstream .URL , apiKey ), nil )}
226- b , err := aibridge .NewRequestBridge (ctx , providers , recorderClient , mcp .NewServerProxyManager (nil , testTracer ), logger , nil , testTracer )
227- require .NoError (t , err )
225+ recorderClient := & testutil.MockRecorder {}
226+ logger := slogtest .Make (t , & slogtest.Options {}).Leveled (slog .LevelDebug )
227+ providers := []aibridge.Provider {provider .NewAnthropic (anthropicCfg (upstream .URL , apiKey ), nil )}
228+ b , err := aibridge .NewRequestBridge (ctx , providers , recorderClient , mcp .NewServerProxyManager (nil , testTracer ), logger , nil , testTracer )
229+ require .NoError (t , err )
228230
229- mockSrv := httptest .NewUnstartedServer (b )
230- t .Cleanup (mockSrv .Close )
231- mockSrv .Config .BaseContext = func (_ net.Listener ) context.Context {
232- return aibcontext .AsActor (ctx , userID , nil )
233- }
234- mockSrv .Start ()
231+ mockSrv := httptest .NewUnstartedServer (b )
232+ t .Cleanup (mockSrv .Close )
233+ mockSrv .Config .BaseContext = func (_ net.Listener ) context.Context {
234+ return aibcontext .AsActor (ctx , userID , nil )
235+ }
236+ mockSrv .Start ()
235237
236- reqBody , err := sjson .SetBytes (fix .Request (), "stream" , tc .streaming )
237- require .NoError (t , err )
238- req := createAnthropicMessagesReq (t , mockSrv .URL , reqBody )
239- client := & http.Client {}
240- resp , err := client .Do (req )
241- require .NoError (t , err )
242- require .Equal (t , http .StatusOK , resp .StatusCode )
243- defer resp .Body .Close ()
238+ reqBody , err := sjson .SetBytes (fix .Request (), "stream" , tc .streaming )
239+ require .NoError (t , err )
240+ req := createAnthropicMessagesReq (t , mockSrv .URL , reqBody )
241+ client := & http.Client {}
242+ resp , err := client .Do (req )
243+ require .NoError (t , err )
244+ require .Equal (t , http .StatusOK , resp .StatusCode )
245+ defer resp .Body .Close ()
244246
245- if tc .streaming {
246- sp := aibridge .NewSSEParser ()
247- require .NoError (t , sp .Parse (resp .Body ))
248- assert .Contains (t , sp .AllEvents (), "message_start" )
249- assert .Contains (t , sp .AllEvents (), "message_stop" )
250- }
247+ if tc .streaming {
248+ sp := aibridge .NewSSEParser ()
249+ require .NoError (t , sp .Parse (resp .Body ))
250+ assert .Contains (t , sp .AllEvents (), "message_start" )
251+ assert .Contains (t , sp .AllEvents (), "message_stop" )
252+ }
251253
252- // Verify tool usage was recorded with associated model thoughts.
253- toolUsages := recorderClient .RecordedToolUsages ()
254+ toolUsages := recorderClient .RecordedToolUsages ()
255+ if tc .expectedThoughts == nil {
256+ assert .Empty (t , toolUsages )
257+ } else {
254258 require .Len (t , toolUsages , 1 )
255259 assert .Equal (t , "Read" , toolUsages [0 ].Tool )
256260 assert .Equal (t , tc .expectedToolCallID , toolUsages [0 ].ToolCallID )
@@ -259,55 +263,11 @@ func TestAnthropicMessagesModelThoughts(t *testing.T) {
259263 for i , expected := range tc .expectedThoughts {
260264 assert .Contains (t , toolUsages [0 ].ModelThoughts [i ].Content , expected )
261265 }
266+ }
262267
263- recorderClient .VerifyAllInterceptionsEnded (t )
264- })
265- }
266- })
267-
268- t .Run ("no thoughts without tool calls" , func (t * testing.T ) {
269- t .Parallel ()
270-
271- ctx , cancel := context .WithTimeout (t .Context (), time .Second * 30 )
272- t .Cleanup (cancel )
273-
274- // Use the simple fixture which has no tool calls — any thinking blocks
275- // should not be persisted since they can't be associated with a tool call.
276- fix := fixtures .Parse (t , fixtures .AntSimple )
277- upstream := testutil .NewMockUpstream (t , ctx , testutil .NewFixtureResponse (fix ))
278-
279- recorderClient := & testutil.MockRecorder {}
280- logger := slogtest .Make (t , & slogtest.Options {}).Leveled (slog .LevelDebug )
281- providers := []aibridge.Provider {provider .NewAnthropic (anthropicCfg (upstream .URL , apiKey ), nil )}
282- b , err := aibridge .NewRequestBridge (ctx , providers , recorderClient , mcp .NewServerProxyManager (nil , testTracer ), logger , nil , testTracer )
283- require .NoError (t , err )
284-
285- mockSrv := httptest .NewUnstartedServer (b )
286- t .Cleanup (mockSrv .Close )
287- mockSrv .Config .BaseContext = func (_ net.Listener ) context.Context {
288- return aibcontext .AsActor (ctx , userID , nil )
289- }
290- mockSrv .Start ()
291-
292- reqBody , err := sjson .SetBytes (fix .Request (), "stream" , true )
293- require .NoError (t , err )
294- req := createAnthropicMessagesReq (t , mockSrv .URL , reqBody )
295- client := & http.Client {}
296- resp , err := client .Do (req )
297- require .NoError (t , err )
298- require .Equal (t , http .StatusOK , resp .StatusCode )
299- defer resp .Body .Close ()
300-
301- sp := aibridge .NewSSEParser ()
302- require .NoError (t , sp .Parse (resp .Body ))
303-
304- // No tool usages (and therefore no thoughts) should be recorded
305- // when there are no tool calls.
306- toolUsages := recorderClient .RecordedToolUsages ()
307- assert .Empty (t , toolUsages )
308-
309- recorderClient .VerifyAllInterceptionsEnded (t )
310- })
268+ recorderClient .VerifyAllInterceptionsEnded (t )
269+ })
270+ }
311271}
312272
313273func TestAWSBedrockIntegration (t * testing.T ) {
0 commit comments