(msgs []chmctx.Message)
| 605 | |
| 606 | var ( |
| 607 | fullContent strings.Builder |
| 608 | slots = map[int]*toolSlot{} |
| 609 | order []int |
| 610 | tokens int |
| 611 | promptTokens int |
| 612 | // complete goes true on any end-of-stream signal: [DONE], a non-empty |
| 613 | // finish_reason, or a usage frame. A stream that ends without one was |
| 614 | // cut, not finished - a proxy/LB gracefully closing the upstream |
| 615 | // mid-generation looks exactly like clean EOF to the scanner, and |
| 616 | // finalizing it would hand the TUI a mid-sentence assistant message as |
| 617 | // a clean finish: a transport-level false green no nudge can see. |
| 618 | complete bool |
| 619 | ) |
| 620 | |
| 621 | for scanner.Scan() { |
| 622 | // Any line (data, blank separator, or ": keepalive" comment) is liveness |
| 623 | // and rearms the idle watchdog. Only a `data:` line means the model has |
| 624 | // actually started PRODUCING, which is the separate question of whether |
| 625 | // the long prefill window is over: a proxy that emits one comment at |
| 626 | // 200 OK (LiteLLM/nginx SSE shims, OpenRouter's ": OPENROUTER |
| 627 | // PROCESSING") must not collapse the prefill window to the inter-frame |
| 628 | // one, nor make the resulting deterministic prefill stall look like a |
| 629 | // replayable mid-stream drop. |
| 630 | line := bytes.TrimSpace(scanner.Bytes()) |
| 631 | isData := bytes.HasPrefix(line, []byte("data:")) |
| 632 | onFrame(isData) |
| 633 | if len(line) == 0 || !isData { |
no outgoing calls