mikemikimike’s first PR #54314 head d58094a88ec2 made is_reasoning_end treat a newest <|open|>tools<|sep|> as ended. The two new tests only asserted that gate. Non-streaming DelegatingParser.parse never consults it: extract_reasoning on skipped-close still returned rest=None (“think not closed -> still reasoning, no content yet”), so _extract_tool_calls saw no tools channel. finish_reason: "stop" with zero tools still looks like a decline. Streaming can latch reasoning_ended while extract_reasoning_streaming still split only on think-close, handing the tool parser an empty buffer on the transition delta.

Head 9d0ad5dc0 splits extract_reasoning on the implicit channel markers and keeps tools-open in rest (start of the marker, not after it). parse_delta hands that content to the tool parser. Streaming vs non-streaming <|close|>message<|sep|> is compared. test_parse_skipped_think_close_reaches_tool_parser covers that parse path. test_tools_channel_ends_reasoning_when_think_close_is_missing still only covers the gate.

On 2026-09-03 the PR is open at c39561595997. 94198ac treats a missing think-open as reasoning starting at offset 0 when a tools, response, or message marker is present. That is the serving-layer generation prefix. I reran the two added tests on prior head 14651ac54dc: the tools-channel case failed None == "step". On current head the focused file is 52/52 in 0.63s. A non-streaming parse() with the think-open already consumed, not a live Kimi-K3 request, returned reasoning step, no visible content, and the read_file tool call.

source ↗

← all notes