TestChatToolCallLateIDPreserved: spec ships the tool_call `id` in the first fragment, but a sloppy provider may delay it. The client must update slot.id on any non-empty value (same forgiveness as `name`), else the id stays "" and the next /v1 request 400s on the unpaired tool message.
(t *testing.T)
| 269 | // TestChatToolCallMalformedArgsPreservesMarker: on invalid `arguments` JSON |
| 270 | // (provider bug), the client surfaces a sentinel key rather than an empty args |
| 271 | // map, so the tool result log names what went wrong. |
| 272 | func TestChatToolCallMalformedArgsPreservesMarker(t *testing.T) { |
| 273 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { |
| 274 | sseOK(w, []string{ |
| 275 | callDone(0, "c1", "bash", `{not-json`), |
| 276 | completed(0, 0), |
| 277 | }) |
| 278 | })) |
| 279 | defer srv.Close() |
| 280 | var got *chmctx.ToolCall |
| 281 | for _, e := range collect(New(srv.URL, "m", "").Chat(context.Background(), nil, nil)) { |
| 282 | if e.Kind == EventToolCall { |
| 283 | got = e.ToolCall |
| 284 | } |
| 285 | } |
| 286 | if got == nil { |
| 287 | t.Fatal("tool call event missing") |
| 288 | } |
| 289 | if _, ok := got.Arguments["_parse_error"]; !ok { |
| 290 | t.Fatalf("_parse_error sentinel missing: %+v", got.Arguments) |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | // TestReasoningDeltasAreEmittedAndItemsKept: reasoning text streams as |
| 295 | // EventReasoning (else the UI freezes for the whole thinking phase) and must |
| 296 | // NOT fold into content; the finished reasoning ITEM is kept raw on the final |