feat(ai-chat): realtime token counter + reasoning tokens (#151) #158
Reference in New Issue
Block a user
Delete Branch "feat/ai-chat-realtime-tokens"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Закрывает #151.
Что
Счётчик токенов, который тикает в реальном времени во время генерации и отдельно показывает токены размышления (reasoning) — как в Claude Code (
Thinking… · N tokens). Раньше токены считались только пост-фактум, бейдж обновлялся лишь при открытии/переключении чата, reasoning не запрашивался.Архитектура (AI SDK v6)
Точного per-token usage в реальном времени API не отдаёт никто → живая цифра это дешёвая клиентская оценка (символы/≈4), сверяемая с авторитетными данными провайдера на границах шага и в конце хода. Реалтайм-движок — перерисовка
useChatна каждой дельте.chatStreamMetadataпробрасывает usage наfinish-step+finish;sendReasoning: true; в persistedmetadata.usage—reasoningTokens(нормализуется изoutputTokenDetailsили deprecated-поля).count-stream-tokens(estimateTokens/liveTurnTokens, предпочитает авторитет, иначе оценка);Thinking… · N tokensв индикаторе; сворачиваемый блок «Thinking»; троттл-бейдж (~8 Гц) токенов хода в шапке;reasoningTokensв типах + Markdown-экспорт.По ревью (мой review-агент, NEEDS CHANGES — закрыто)
finish-step.usageв v6 — per-step, не накопительная. На многошаговом ходе агента (норма) это перетирало бы метаданные частичными числами → счётчик прыгал бы ВНИЗ. Сервер теперь аккумулирует running-sum (новая чистаяaccumulateStepUsage) и шлёт кумулятив, сходящийся кfinish.totalUsage.Проверка
Сервер:
tsc --noEmitчисто, suiteai-chat.service.spec34 теста (вкл.accumulateStepUsage). Клиент:tsc --noEmitчисто, 162 тестаai-chatзелёные. Чисто живой моделью проверяется только сам «допрыг» оценки до авторитета — оба пути покрыты кодом/тестами.🤖 Generated with Claude Code
Tokens were only counted post-hoc (onFinish) and the header badge updated only on chat open/switch; reasoning wasn't requested or shown. Now a counter ticks LIVE during generation and surfaces reasoning ("thinking") tokens separately, like Claude Code's `Thinking… · N tokens`. Architecture (AI SDK v6): no provider gives exact per-token usage mid-stream, so the live number is a cheap client estimate (chars/≈4) reconciled to AUTHORITATIVE provider usage at step boundaries and turn end. The useChat per-delta re-render is the existing realtime engine. - server: `chatStreamMetadata` now also forwards usage on `finish-step` + `finish`; `sendReasoning: true`; persisted `metadata.usage` carries `reasoningTokens` (normalized from `outputTokenDetails` or the deprecated field). - client: pure `count-stream-tokens` (estimateTokens / liveTurnTokens, prefers authoritative usage else estimate); `Thinking… · N tokens` in the typing indicator; collapsible "Thinking" reasoning block; throttled (~8 Hz) live turn-token header badge; `reasoningTokens` in types + Markdown export. Review fixes folded in: - v6 `finish-step.usage` is PER-STEP, not cumulative — the server now ACCUMULATES a running sum (new pure `accumulateStepUsage`) and sends the cumulative, which converges to `finish.totalUsage`, so the live counter never jumps DOWN on a multi-step agent turn. - reasoning double-count: the authoritative turn-total is attributed to a block ONLY for a single-reasoning-part (one-step) turn; multi-step blocks each show their own estimate (the authoritative total stays in the header). - no "0" badge flash at turn start (require live > 0, else show context size). - comment refreshed (finish-step trigger). Tests: server `accumulateStepUsage` + updated `chatStreamMetadata` (34 in the suite); client pure-fn tests. Both tsc clean; 162 client ai-chat + the ai-chat server suite pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>