(t *testing.T)
| 4285 | } |
| 4286 | |
| 4287 | func TestDynamicToolCallPausesAndResumes(t *testing.T) { |
| 4288 | t.Parallel() |
| 4289 | |
| 4290 | db, ps := dbtestutil.NewDB(t) |
| 4291 | ctx := testutil.Context(t, testutil.WaitLong) |
| 4292 | |
| 4293 | // Track streaming calls to the mock LLM. |
| 4294 | var streamedCallCount atomic.Int32 |
| 4295 | var streamedCallsMu sync.Mutex |
| 4296 | streamedCalls := make([]chattest.OpenAIRequest, 0, 2) |
| 4297 | |
| 4298 | openAIURL := chattest.NewOpenAI(t, func(req *chattest.OpenAIRequest) chattest.OpenAIResponse { |
| 4299 | // Non-streaming requests are title generation. Return a |
| 4300 | // simple title. |
| 4301 | if !req.Stream { |
| 4302 | return chattest.OpenAINonStreamingResponse("Dynamic tool test") |
| 4303 | } |
| 4304 | |
| 4305 | // Capture the full request for later assertions. |
| 4306 | streamedCallsMu.Lock() |
| 4307 | streamedCalls = append(streamedCalls, chattest.OpenAIRequest{ |
| 4308 | Messages: append([]chattest.OpenAIMessage(nil), req.Messages...), |
| 4309 | Tools: append([]chattest.OpenAITool(nil), req.Tools...), |
| 4310 | Stream: req.Stream, |
| 4311 | }) |
| 4312 | streamedCallsMu.Unlock() |
| 4313 | |
| 4314 | if streamedCallCount.Add(1) == 1 { |
| 4315 | // First call: the LLM invokes our dynamic tool. |
| 4316 | return chattest.OpenAIStreamingResponse( |
| 4317 | chattest.OpenAIToolCallChunk( |
| 4318 | "my_dynamic_tool", |
| 4319 | `{"input":"hello world"}`, |
| 4320 | ), |
| 4321 | ) |
| 4322 | } |
| 4323 | // Second call: the LLM returns a normal text response. |
| 4324 | return chattest.OpenAIStreamingResponse( |
| 4325 | chattest.OpenAITextChunks("Dynamic tool result received.")..., |
| 4326 | ) |
| 4327 | }) |
| 4328 | |
| 4329 | user, org, model := seedChatDependenciesWithProvider(t, db, "openai-compat", openAIURL) |
| 4330 | |
| 4331 | // Dynamic tools do not need a workspace connection, but the |
| 4332 | // chatd server always builds workspace tools. Use an active |
| 4333 | // server without an agent connection, so the built-in tools |
| 4334 | // are never invoked because the only tool call targets our |
| 4335 | // dynamic tool. |
| 4336 | server := newActiveTestServer(t, db, ps) |
| 4337 | |
| 4338 | // Create a chat with a dynamic tool. |
| 4339 | dynamicToolsJSON, err := json.Marshal([]mcpgo.Tool{{ |
| 4340 | Name: "my_dynamic_tool", |
| 4341 | Description: "A test dynamic tool.", |
| 4342 | InputSchema: mcpgo.ToolInputSchema{ |
| 4343 | Type: "object", |
| 4344 | Properties: map[string]any{ |
nothing calls this directly
no test coverage detected