(data: SessionData, commits: SessionCommit[], partID: string, interrupted = false)
| 511 | } |
| 512 | |
| 513 | function flushPart(data: SessionData, commits: SessionCommit[], partID: string, interrupted = false) { |
| 514 | const kind = data.part.get(partID) |
| 515 | if (!kind) { |
| 516 | return |
| 517 | } |
| 518 | |
| 519 | const text = data.text.get(partID) ?? "" |
| 520 | const sent = data.sent.get(partID) ?? 0 |
| 521 | let chunk = text.slice(sent) |
| 522 | const msg = data.msg.get(partID) |
| 523 | |
| 524 | if (sent === 0) { |
| 525 | chunk = chunk.replace(/^\n+/, "") |
| 526 | // Some models emit a standalone whitespace token before real content. |
| 527 | // Keep buffering until we have visible text so scrollback doesn't get a blank row. |
| 528 | if (!chunk.trim()) { |
| 529 | return |
| 530 | } |
| 531 | if (kind === "reasoning" && chunk) { |
| 532 | chunk = `Thinking: ${chunk.replace(/\[REDACTED\]/g, "")}` |
| 533 | } |
| 534 | if (kind === "assistant" && chunk) { |
| 535 | chunk = stripEcho(data, msg, chunk) |
| 536 | if (!chunk.trim()) { |
| 537 | return |
| 538 | } |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | if (chunk) { |
| 543 | data.sent.set(partID, text.length) |
| 544 | data.visible.set(partID, (data.visible.get(partID) ?? "") + chunk) |
| 545 | commits.push({ |
| 546 | kind, |
| 547 | text: chunk, |
| 548 | phase: "progress", |
| 549 | source: kind === "user" ? "system" : kind, |
| 550 | messageID: msg, |
| 551 | partID, |
| 552 | }) |
| 553 | } |
| 554 | |
| 555 | if (!interrupted) { |
| 556 | return |
| 557 | } |
| 558 | |
| 559 | commits.push({ |
| 560 | kind, |
| 561 | text: "", |
| 562 | phase: "final", |
| 563 | source: kind === "user" ? "system" : kind, |
| 564 | messageID: msg, |
| 565 | partID, |
| 566 | interrupted: true, |
| 567 | }) |
| 568 | } |
| 569 | |
| 570 | function drop(data: SessionData, partID: string) { |
no test coverage detected