Olava streaming restored, then corrected after live endpoint testing
Two commits, same day, in opposite directions. feat-001 restores streaming during tool-using turns by adding a state machine that hides `<think>` blocks and `<tool_call>` markup from the user-visible stream while keeping the raw buffer intact for the custom parser. bug-002 then discovers that on the actual RunPod endpoint the markup never lands in `delta.content` at all - vLLM just drops it - and pivots to re-issuing the first iter as a non-streaming request to recover the tool call.
The prior code fell back to non-streaming on any turn with tools in scope. feat-001's fix was a StreamingMarkupFilter class: it maintains a state machine across chunk boundaries, suppressing <think>...</think> blocks and everything after a <tool_call> open tag, while passing through visible tokens in real time. It holds back any trailing slice that could be the start of a tag (e.g. <, <t, <tool_ca) so markup split across two chunks isn't accidentally emitted. After the stream ends, parseCustomToolCall() runs on the raw accumulated buffer. The emergency rollback path is OLAVA_FORCE_NONSTREAM_TOOLS=true.
Then bug-002 tested against the live endpoint. The actual behavior: vLLM sets finish_reason="tool_calls" but delta.content comes through as just "\n\n" - no markup, no delta.tool_calls, nothing. feat-001's premise was wrong.
The fix is recoverToolCallNonStreaming(): when a streaming iter ends with finish_reason="tool_calls" but zero extracted calls, re-issue the same request with stream: false. The non-streaming response puts the markup in message.content where the existing parser can reach it. There's about 2 seconds of dead air on iter 0 of any tool-using turn. Iter 1 and later - the prose-generating iterations after the tool runs - stream normally, which is where the streaming benefit actually lives.
The StreamingMarkupFilter is still in the codebase and handles the case where vLLM does stream the markup. The non-streaming recovery path handles the case where it doesn't. Both run.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?