fix(ai-chat): tick the live token counter between agent steps (#163) #167

Closed
Ghost wants to merge 1 commits from fix/ai-chat-token-counter-realtime into develop

Closes #163.

Проблема

Бейдж токенов в шапке AI-чата (и строка «Thinking… · N tokens») не тикал в реальном времени между шагами агента — замирал и прыгал скачками. liveTurnTokens возвращал авторитетную серверную usage ВЕРБАТИМ, как только она появлялась. Но сервер кладёт usage только на границе шага (finish-step/finish) и она НАКОПИТЕЛЬНАЯ по ЗАВЕРШЁННЫМ шагам — поэтому во время следующего (текущего) шага число замирало на значении прошлой границы, а бегущая оценка по тексту игнорировалась.

Решение

Объединить оба источника покомпонентно через max: всегда считать бегущую оценку (chars/≈4 по reasoning/text-частям сообщения, включающим текущий незавершённый шаг) и брать max(authoritativeBase, estimate).

  • между границами оценка текущего шага тикает число вверх;
  • на границе авторитетное значение подскакивает к точному;
  • так как серверная usage накопительна, а мы берём только max — счётчик монотонен (никогда не падает вниз).

reasoning/output по-прежнему разделены; авторитетный reasoning-only счёт из #151 сохранён.

Совместимость и тесты

Во всех существующих тестах оценка ≤ авторитетного значения, поэтому max возвращает то же самое — они продолжают проходить. Добавлено 4 теста: оценка текущего шага превышает базу (output и reasoning), авторитетное побеждает грубую оценку, монотонность. Набор ai-chat — 184 зелёных, tsc чисто.

Затронуто

Только count-stream-tokens.ts (+ тесты). chat-thread.tsx/ai-chat-window.tsx/message-list.tsx уже используют liveTurnTokens корректно — не менялись.

🤖 Generated with Claude Code

Closes #163. ## Проблема Бейдж токенов в шапке AI-чата (и строка «Thinking… · N tokens») не тикал в реальном времени между шагами агента — замирал и прыгал скачками. `liveTurnTokens` возвращал авторитетную серверную `usage` ВЕРБАТИМ, как только она появлялась. Но сервер кладёт usage только на границе шага (`finish-step`/`finish`) и она НАКОПИТЕЛЬНАЯ по ЗАВЕРШЁННЫМ шагам — поэтому во время следующего (текущего) шага число замирало на значении прошлой границы, а бегущая оценка по тексту игнорировалась. ## Решение Объединить оба источника покомпонентно через `max`: всегда считать бегущую оценку (chars/≈4 по reasoning/text-частям сообщения, включающим текущий незавершённый шаг) и брать `max(authoritativeBase, estimate)`. - между границами оценка текущего шага тикает число вверх; - на границе авторитетное значение подскакивает к точному; - так как серверная usage накопительна, а мы берём только max — счётчик **монотонен** (никогда не падает вниз). reasoning/output по-прежнему разделены; авторитетный reasoning-only счёт из #151 сохранён. ## Совместимость и тесты Во всех существующих тестах оценка ≤ авторитетного значения, поэтому `max` возвращает то же самое — они продолжают проходить. Добавлено 4 теста: оценка текущего шага превышает базу (output и reasoning), авторитетное побеждает грубую оценку, монотонность. Набор ai-chat — **184** зелёных, `tsc` чисто. ## Затронуто Только `count-stream-tokens.ts` (+ тесты). `chat-thread.tsx`/`ai-chat-window.tsx`/`message-list.tsx` уже используют `liveTurnTokens` корректно — не менялись. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 1 commit 2026-06-24 15:12:50 +03:00
The header token badge (and the "Thinking… · N tokens" line) froze between
agent steps and jumped in chunks instead of ticking smoothly. liveTurnTokens
returned the authoritative server `usage` VERBATIM as soon as it appeared, but
the server only attaches usage at a step boundary and it is cumulative over
COMPLETED steps — so during the next (in-flight) step the figure stayed frozen
at the previous boundary and the running text estimate was ignored.

Combine both sources per component via max: always compute the running estimate
(chars/≈4 over the message's reasoning/text parts, which includes the in-flight
step) and take max(authoritativeBase, estimate). Between boundaries the estimate
ticks the number up; at a boundary the authoritative figure snaps it exact; and
because the server usage is cumulative and we only ever take the max, the counter
is monotonic (never drops). Reasoning/output stay split; the #151 reasoning-only
authoritative count is preserved.

Backward compatible: in every existing test the estimate is <= the authoritative
figure, so max returns the same value. +4 tests for the in-flight-step-exceeds-
base case (output + reasoning), the authoritative-wins case, and monotonicity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vvzvlad added the bug label 2026-06-24 20:48:44 +03:00
vvzvlad closed this pull request 2026-06-25 04:01:11 +03:00

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#167