toolCallLeakWarning returns a user-facing diagnostic when the newest assistant message carries a tool-call opener (` `) in its text instead of structured tool_calls, the dominant local-hosting failure: a misconfigured/missing server parser leaks the call as content with finish_reason "stop
(history []chmctx.Message)
| 1031 | // Commit the in-flight round's live estimate before measuring. On a clean |
| 1032 | // finish it's already 0 (applyDone folded the round into turnTokens at |
| 1033 | // EventDone). But a Ctrl+C or error mid-stream interrupts before EventDone, |
| 1034 | // so the cancelled round's tokens (often the whole generation) sit only in |
| 1035 | // streamingEstimate; without this they'd vanish from the avg and the session |
| 1036 | // total would drop backward. char/4 is the best count for a round that never |
| 1037 | // reported usage. |
| 1038 | m.turnTokens += m.streamingEstimate |
| 1039 | m.sessionTokens += m.streamingEstimate |
| 1040 | m.streamingEstimate = 0 |
| 1041 | wall := time.Since(m.turnStart) |
| 1042 | m.lastElapsed = wall |
| 1043 | m.lastTokens = m.turnTokens |
| 1044 | m.lastOutcome = outcome |
| 1045 | avg := humanRate(m.turnTokens, wall) |
| 1046 | if avg != "" { |
| 1047 | avg = " · " + avg + " avg" |
| 1048 | } |
| 1049 | dbgWritef("turn_end", "%s · %s wall%s · session_total=%s", |
| 1050 | humanTokens(m.turnTokens), wall.Round(time.Millisecond), avg, humanTokens(m.sessionTokens)) |
no outgoing calls