Review of #158 (Request changes) — core logic verified correct; addressed the test-coverage + localization items: 1. i18n pluralization: the token-count keys were called with {count} but had one form, so ru-RU always rendered the genitive ("1 токенов"). Added _one/_other (en) and _one/_few/_many (ru: токен/токена/токенов) for both "Thinking… · {{count}} tokens" and "Thinking · {{count}} tokens"; de-duped the PR-added duplicate "Thinking" key. Call sites unchanged. 2. ReasoningBlock: new reasoning-block.test.tsx (4 branches: authoritative count wins / estimate fallback / header-only when count-but-no-text / body render). 3. Reasoning-token attribution: extracted the #151 anti-double-count rule into a pure `reasoningTokensForPart(message)` (single reasoning part -> authoritative turn total; multiple/none -> undefined so each estimates). message-item uses it; removed the now-dead lastReasoningIndex reduce (review #5). Unit-tested. 6. adopt-chat-id.ts: refreshed 3 stale `chatStreamStartMetadata` -> `chatStreamMetadata` comment references. 7. chat-markdown.test.ts: assert the export footer's `reasoning: N` line appears when reasoningTokens>0 and is absent at 0/undefined. Skipped optional #4 (mantine useThrottledCallback): the manual throttle has two distinct exit paths (turn-end revert-to-null + the captured-total trailing emit) with no guarding test; remapping risks the streaming behavior — non-blocking. Client tsc clean; ai-chat suite green (171 tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
35 lines
1.5 KiB
TypeScript
35 lines
1.5 KiB
TypeScript
import type { UIMessage } from "@ai-sdk/react";
|
|
|
|
/**
|
|
* Decide the authoritative reasoning token count to attribute to a single
|
|
* `reasoning` part of an assistant message — or `undefined` when the part should
|
|
* fall back to its own per-part estimate.
|
|
*
|
|
* `usage.reasoningTokens` is the TURN TOTAL, so it may only be attributed to a
|
|
* block when the turn has exactly ONE reasoning part (the common one-step turn):
|
|
* then that block can show the exact figure. With MULTIPLE reasoning parts (a
|
|
* multi-step agent turn) every block must fall back to its own estimate —
|
|
* attributing the turn total to one of them would double-count against the
|
|
* others' estimates (#151 review anti-double-count rule). When there is no
|
|
* authoritative usage at all, every part estimates.
|
|
*
|
|
* Returns the authoritative `reasoningTokens` only for the single-reasoning-part
|
|
* case; `undefined` otherwise (the caller estimates from the part text).
|
|
*/
|
|
export function reasoningTokensForPart(
|
|
message: UIMessage,
|
|
): number | undefined {
|
|
const reasoningTokens = (
|
|
message.metadata as { usage?: { reasoningTokens?: number } } | undefined
|
|
)?.usage?.reasoningTokens;
|
|
|
|
const reasoningPartCount = (message.parts ?? []).reduce(
|
|
(acc, p) => (p.type === "reasoning" ? acc + 1 : acc),
|
|
0,
|
|
);
|
|
|
|
// Exactly one reasoning part -> attribute the authoritative turn total to it.
|
|
// Otherwise (zero or multiple) each part estimates on its own.
|
|
return reasoningPartCount === 1 ? reasoningTokens : undefined;
|
|
}
|