applyDone closes one LLM round: harvest the live context window, accumulate turn/session tokens, append the assistant message, flush streaming. A turn with tool calls fires one EventDone per round; counters accumulate so the banner reflects the whole turn. Tokens==0 means the backend skipped include
(e llm.Event)
| 783 | case llm.EventToolCall: |
| 784 | m.applyToolCall(e) |
| 785 | case llm.EventDone: |
| 786 | m.applyDone(e) |
| 787 | case llm.EventError: |
| 788 | return m, m.applyError(e) |
| 789 | } |
| 790 | return m, readEvent(m.stream) |
| 791 | } |
| 792 | |
| 793 | // applyContent writes one streamed text chunk to the live buffer and promotes |
| 794 | // phase from thinking to streaming on the first chunk so the status bar shows |
| 795 | // tokens flowing. View() renders the buffer live above the prompt; once the |
| 796 | // block ends it's flushed through glamour into scrollback via tea.Println. |
| 797 | func (m *Model) applyContent(e llm.Event) { |
| 798 | if m.phase == phaseThinking { |
| 799 | m.phase = phaseStreaming |
| 800 | } |
| 801 | // Expand tabs on the way into the display buffer: terminals advance a |
| 802 | // literal tab to the next 8 column stop while every width computation |
| 803 | // downstream (View's live ansi.Wrap, glamour's code fence padding, |
| 804 | // wrapForScrollback) counts it as one cell, so a tab indented code block |
| 805 | // passes the width checks yet physically overflows and drifts the |
| 806 | // renderer's cursor math. Display only: history keeps e.Final untouched. |
| 807 | m.streaming.WriteString(strings.ReplaceAll(e.Content, "\t", " ")) |
| 808 | m.streamingEstimate += len(e.Content) / 4 |
| 809 | } |
| 810 | |
| 811 | // applyToolCall queues a streamed tool call for later dispatch. flushStreaming |
| 812 | // up front commits this round's text to scroll before the inline tool call |
| 813 | // status lands, so the user sees styled text *before* the "▶ bash: ..." line, |
| 814 | // not all at once at turn end. |
| 815 | func (m *Model) applyToolCall(e llm.Event) { |
| 816 | m.flushStreaming() |
| 817 | m.pending = append(m.pending, *e.ToolCall) |
| 818 | } |
| 819 | |
| 820 | // applyDone records one assistant response and adds its output tokens to |
| 821 | // the turn and session totals. When usage is absent, the live byte estimate |
| 822 | // is used instead. Tool rounds accumulate until the turn ends. |
| 823 | func (m *Model) applyDone(e llm.Event) { |
| 824 | delta := e.Tokens |
| 825 | if delta == 0 { |
| 826 | delta = m.streamingEstimate |
| 827 | } |
| 828 | m.turnTokens += delta |
| 829 | m.sessionTokens += delta |
| 830 | m.streamingEstimate = 0 |
| 831 | m.connected = true |