TestChatDispatchesToolCallsOnFinishStop: Ollama's /v1 shim sometimes emits finish_reason="stop" even after streaming tool_calls. The client must still emit EventToolCall, or the call vanishes and the agent faces an empty turn.
(t *testing.T)
| 235 | func TestChatParallelToolCallsByOutputIndex(t *testing.T) { |
| 236 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { |
| 237 | sseOK(w, []string{ |
| 238 | callAdded(0, "c1", "read_file"), |
| 239 | callAdded(1, "c2", "read_file"), |
| 240 | argsDelta(0, `{"path":`), |
| 241 | argsDelta(1, `{"path":`), |
| 242 | argsDelta(0, `"a.go"}`), |
| 243 | argsDelta(1, `"b.go"}`), |
| 244 | callDone(0, "c1", "read_file", `{"path":"a.go"}`), |
| 245 | callDone(1, "c2", "read_file", `{"path":"b.go"}`), |
| 246 | completed(9, 0), |
| 247 | }) |
| 248 | })) |
| 249 | defer srv.Close() |
| 250 | var calls []chmctx.ToolCall |
| 251 | for _, e := range collect(New(srv.URL, "m", "").Chat(context.Background(), nil, nil)) { |
| 252 | if e.Kind == EventToolCall { |
| 253 | calls = append(calls, *e.ToolCall) |
| 254 | } |
| 255 | } |
| 256 | if len(calls) != 2 { |
| 257 | t.Fatalf("want 2 tool call events, got %d: %+v", len(calls), calls) |
| 258 | } |
| 259 | for i, want := range []string{"a.go", "b.go"} { |
| 260 | if got, _ := calls[i].Arguments["path"].(string); got != want { |
| 261 | t.Fatalf("call %d path = %q, want %q", i, got, want) |
| 262 | } |
| 263 | } |
| 264 | if calls[0].ID != "c1" || calls[1].ID != "c2" { |
| 265 | t.Fatalf("output order lost: got ids %s, %s", calls[0].ID, calls[1].ID) |
| 266 | } |
| 267 | } |
| 268 |