03: Chunk Normalization
このコンテンツはまだ日本語訳がありません。
Chunk normalization converts provider-native RawStreamChunk envelopes into uniform ProcessedChunk
objects, extracts reasoning content, and normalizes error and tool-call payloads. It executes synchronously
within BaseStreamAdapter.processChunk().
Flow and ownership
Section titled “Flow and ownership”The stage 04 orchestrator loop
consumes raw chunks from stage 02 and passes each to processChunk(). The method produces no side
effects on orchestrator state or Discord APIs. It can update adapter-local accumulators and guards
and emit diagnostic logs, while shielding the orchestrator from vendor SDK structures.
The returned ProcessedChunk carries one of four primary types:
"text": carries a visible text delta incontentfor buffering and Discord delivery."function_call": carries a normalizedFunctionCall(name,args, and optionalthoughtSignature)."error": carries a normalizedProviderErrorwith classified failure codes."done": carries terminal metadata (such as finish reasons) before the generator finishes.
Any chunk variant may attach thoughts (ThoughtLogEntry[]) for chain-of-thought capture and
servingProvider to track upstream backends on routed endpoints.
Token usage can arrive in a trailing chunk with empty choices. OpenRouter and OpenAI-compatible
adapters preserve its metadata on an empty text chunk, so the orchestrator carries the counts into
StreamResult.usage for statistics and quotas. Reported usage.cost is preserved alongside the token counts.
Reasoning extraction and spill guards
Section titled “Reasoning extraction and spill guards”Reasoning content travels through dedicated fields or in-band tags:
- Structured reasoning: Adapters extract native thought blocks (such as Gemini
part.thoughtor reasoning summaries) intoThoughtLogEntry[]. For OpenRouter, the upstream backend identifier is captured inservingProviderso unexpected reasoning leaks can be traced to specific model hosts. - Leaked reasoning tags: When models output
<think>blocks into visible text deltas,ThinkBlockContentStripperredirects the enclosed text todelta.reasoning. - Tagless reasoning spills: When a provider concatenates reasoning directly onto content without
tags,
ReasoningContentSpillGuarddetects unspaced sentence boundaries at the start of output. It strips the leaked tail while protecting backticked code identifiers.
Tool-call normalization and recovery
Section titled “Tool-call normalization and recovery”Tool calls stream as structured objects or raw text deltas:
- Verbatim tool-call parsing: Models using custom endpoints or lacking native tool calling can
output function calls directly in prose.
CustomStreamAdapterrunsVerbatimToolCallParserover visible content to match<toolName>(...)syntax. Text preceding the call emits as normal prose, followed by the parsed call astype: "function_call". - Truncated argument recovery: Network interruptions or socket resets can cut tool argument
JSON mid-token. In adapters receiving incremental argument deltas (OpenAI-compatible, OpenRouter,
Anthropic),
parseAccumulatedToolArguments()falls back totryRepairIncompleteJson(). The repair closes open containers without inventing keys and markschunk.functionCall.argumentsTruncated = true. The tool loop inspects this flag and refuses execution of incomplete calls.
Error normalization and classification
Section titled “Error normalization and classification”handleProviderError() converts SDK exceptions into a unified ProviderError shape with standard
categories (api_error, rate_limit, content_blocked, timeout, provider_overloaded, model_error).
Specialized classifiers in src/utils/provider/providerErrorClassification.ts inspect error text to
differentiate failure causes:
- Model errors: Distinguishes unknown or retired model IDs from bad credentials.
- Billing limits: Separates exhausted account balances from affordability ceilings that can succeed
with smaller
max_tokenssettings. - Context overflow: Identifies token window limits that prompt history trimming.
- Provider timeouts: Normalizes network read and socket timeouts into the
timeoutcategory.
Public sinks show only the provider’s localized, classified headline from createErrorDescription().
The upstream response text never reaches error embeds, interaction replies, the fallback notice, or
tool results returned to the model, because an endpoint can echo the bearer value it received in an
encoding that pattern redaction misses, and self-debug reads those notices back into context. The
chat error embed instead offers a Show Full Error button that opens the redacted snippet in a
modal, which reaches only the viewer and never enters channel history. Only someone who could replace
the failing key may open it: a Manage Server member (or the DM’s user) for server credentials, and
only the triggering user for a personal key. Error
codes appear only when formatProviderErrorCodeForDisplay() recognizes an HTTP status or single-case
enum word; anything else shows as unknown. The orchestrator reports each failed stream attempt
once through reportStreamProviderFailure(), before deciding whether to show it, so normal chat,
suppressed retries, and hidden image planning share one record shape and severity: a failure that
needs the operator creates an error even when its notice is suppressed. The report caps the
diagnostic snippet from getProviderErrorDetail() at 2,000 characters after removing the exact
key that attempt sent, which may be a personal or rotated key rather than the server’s. Log messages,
attached errors and metadata also remove that key before credential-pattern redaction and optional
truncation. This is best-effort redaction; encoded or transformed secrets can survive. Attached errors
and stack traces can be longer; log string truncation is opt-in. Expected provider failures retain warning diagnostics in JSON logs at every log level,
without creating an error_logs row.
Google streaming failures attach a bounded requestDiagnostics summary: the actual request model,
last five dialogue roles, final-turn part counts, prefill and tool-history presence, received chunk
count, and elapsed time. The summary contains no prompt text, tool arguments, media URLs, or media
bytes. Suppressed error notices carry the summary in the same report.
Empty non-private DeepSeek structured replies retain the finish reason, validated token counts,
reasoning presence, output limit, and provider request ID. Missing or invalid token counts stay
unknown. Expression initialization copies these diagnostics into its existing error record.
Include error_metadata in error exports; expected failures remain in host JSON logs.
Logs remain private operator diagnostics. See
Production tuning for log controls. /persona generate and speech
synthesis also keep upstream text out of public replies: preset generators reply with a classified
message and HTTP status, and speech adapters put only
the status or fixed text in details, which reaches both /generate voice-message and the voice tool.
Credential-scoped recovery tips
Section titled “Credential-scoped recovery tips”The resolved credential source of the turn (StreamContext.textCredentialSource, "server",
"personal", or "supporter") governs the troubleshooting tips displayed in error embeds:
- Server scope: Tips recommend administrative actions, such as adding API keys to rotation pools or configuring server fallback chains.
- Personal scope: Server key rotation tips are suppressed because standard members cannot manage server pools. Error embeds instead recommend checking personal credentials or disabling the personal text override to revert to server defaults.
- Subscription scope: Subscription denials use the billing module’s classified recovery copy and suppress provider troubleshooting tips.
Source pointers
Section titled “Source pointers”src/types/stream/interfaces.ts:ProcessedChunk,ProviderError, andBaseStreamAdapter.processChunk.src/providers/utils/toolCallArguments.ts:parseAccumulatedToolArgumentsand truncation flags.src/utils/text/jsonRepair.ts:tryRepairIncompleteJson.src/utils/provider/providerErrorClassification.ts: error classification helpers.src/utils/provider/providerErrorMetrics.ts:reportStreamProviderFailureandreportProviderFailure.src/utils/discord/stream/errorUi.ts: error embed presentation, credential-scoped tips, and the footer naming whether the provider or TomoriBot failed.src/providers/custom/customStreamAdapter.ts: verbatim tool parsing integration.