import type { GthRunStats, GthToolCall, GthToolResult } from '#src/core/types.js'; /** * BATCH-21 / BATCH-49 — the DEFAULT cap on a captured tool-result payload, in **UTF-8 bytes**. * * Deliberately bytes, and deliberately a change from the character count this used to be. The cap * was applied to `text.length`, which counts UTF-16 code units, while every user-facing sentence * about it said "8 KB" — exact only for ASCII, and a different size from the one the code cut at * the moment a payload contained a character outside the ASCII range. Measuring UTF-8 bytes is what * those sentences already claimed, and it is the unit both neighbouring caps * (`builtInTools..maxOutputBytes` / `maxBytes`) already use, so one number now means one size * to the person setting it and to the code applying it. An ASCII payload behaves exactly as it did * at this default; a non-ASCII one is cut somewhat earlier, which is the size the docs promised. * * This constant is the default's single source. A configured `toolResultCaptureMaxBytes` overrides * it at the read site (`resolveToolResultCaptureMaxBytes`); nothing else may restate the number. */ export declare const TOOL_RESULT_CONTENT_CAP = 8192; /** * BATCH-49 — one captured payload after the cap has been applied: the text that is safe to store, * and whether the cap is what produced it. * * `truncated` is recorded HERE, at capture, rather than inferred later from the stored length. The * cut is on a character boundary (see {@link capToolResultText}), so a truncated payload can land * up to three bytes under the cap, and `.length` does not count bytes at all — a check that * compared the stored length with the cap would both miss real truncations and fire on a payload * that merely happened to be the same length. The capture site is the only place that still holds * the original, so it is the only place the fact can be recorded honestly. */ export interface CappedToolResultText { /** The payload as stored: the original when it fitted, the character-boundary prefix when it did not. */ text: string; /** `true` iff `text` is a prefix of a longer payload — the cap is what cut it. */ truncated: boolean; /** * The original payload's size in UTF-8 bytes, recorded only when {@link truncated} is `true`. * * This is the number a person needs in order to choose a new cap: the stored prefix's length * cannot say how far short the cut fell, and a reason string that only names the key still * leaves them guessing what to raise it to. */ originalBytes?: number; } /** * BATCH-49 — apply `maxBytes` to `text`, cutting on a character boundary so the stored prefix is * always valid UTF-8. * * A byte cap applied as `slice` would split a multi-byte character: UTF-8 encodes one code point * in up to four bytes, and a cut that lands inside one yields a string that does not decode. So * the cut walks back to the start of the character the byte budget ran out inside, which means a * truncated payload can be up to three bytes *under* the cap and is never over it. A payload that * fits is returned unchanged, with `truncated: false` and no `originalBytes` — absence is the * record of "nothing was cut", and recording a size on every result would bloat the very trace * the cap exists to bound. * * `maxBytes` is a positive integer by the time it arrives here: the config schema rejects * anything else, and the read site substitutes {@link TOOL_RESULT_CONTENT_CAP} for an absent key. * Zero is not "unlimited" and is not handled here — see the schema comment for why. */ export declare function capToolResultText(text: string, maxBytes: number): CappedToolResultText; /** Mutable tally behind {@link finalizeRunStats}; see {@link createRunStatsAccumulator}. */ export interface RunStatsAccumulator { /** Running sum of input/prompt tokens. */ input: number; /** Running sum of output/completion tokens. */ output: number; /** Whether ANY message reported `usage_metadata` — gates whether tokens are recorded at all. */ sawUsage: boolean; /** Deduplicated set of invoked tool names. */ tools: Set; /** BATCH-21 — one record per executed tool result (`ToolMessage`), in arrival order, un-deduped. */ toolResults: GthToolResult[]; /** BATCH-52 — one record per requested tool call whose arguments are complete, in request order. */ toolCalls: GthToolCall[]; /** * BATCH-52 — the current round's streamed calls, keyed by `tool_call_chunks` index, whose * arguments may still be arriving. Moved into {@link toolCalls} when the round's first * `ToolMessage` lands (indexes restart each round), and read as-is by a mid-round finalize. */ streamingCalls: Map; /** BATCH-52 — ids already in {@link toolCalls}, so a call seen both streamed and whole is kept once. */ recordedCallIds: Set; } /** BATCH-52 — one streamed tool call being reassembled from its `tool_call_chunks` deltas. */ export interface StreamingToolCall { id?: string; name: string; argsText: string; } /** A fresh, empty accumulator. */ export declare function createRunStatsAccumulator(): RunStatsAccumulator; /** * Fold the tool calls of an AI message the tool-call repair promoted from model text into the * accumulator. * * The promoted message keeps the id of the text message it replaces. A streamed run has already * seen that id as plain text and never delivers the promoted message to {@link accumulateMessage}, * so the agent hands it here when it promotes it. Only the requested calls are recorded, not usage: * the text message it replaces was already counted. * * The non-streaming path folds the same message through {@link accumulateMessage} as well; the call * is kept once because {@link RunStatsAccumulator.recordedCallIds} already holds its id. Fail-soft. */ export declare function accumulatePromotedToolCalls(acc: RunStatsAccumulator, message: unknown, captureMaxBytes?: number): void; /** * Fold one LangChain message (or message chunk) into the accumulator. Fail-soft: any unexpected * shape is swallowed so a run is never affected. Harvests, when present: * - `usage_metadata.input_tokens` / `.output_tokens` (summed; marks `sawUsage`), * - tool names from an AIMessage's requested `tool_calls[].name` AND from a `ToolMessage`'s own * `.name` (the executed tool), so both "requested" and "executed" tools are captured, and * - (BATCH-21) a per-`ToolMessage` result record — `name` + `isError` (from `.status`) + capped * `content` — into `acc.toolResults`, so tool-RESULT assertions can grade what a tool returned. * * `configuredMcpServers` is `Object.keys(config.mcpServers)`, used only to resolve which server an * errored MCP tool belongs to (BATCH-43; see the record's `errorPayload`). It is passed per fold * rather than held on the accumulator so this never depends on the accumulator being re-created * after the config is known — the default leaves every non-MCP capture exactly as it was. * * `captureMaxBytes` is the same kind of argument, for the same reason (BATCH-49): the cap is a * config value (`toolResultCaptureMaxBytes`, defaulted at the read site), and holding it on the * accumulator would tie every fold to the accumulator having been rebuilt after the config was * known. The default is {@link TOOL_RESULT_CONTENT_CAP}, so a caller that predates the argument * caps exactly where it always did. */ export declare function accumulateMessage(acc: RunStatsAccumulator, message: unknown, configuredMcpServers?: Iterable, captureMaxBytes?: number): void; /** * Freeze the accumulator into the public {@link GthRunStats}. Tokens omitted unless observed. * * BATCH-52 — calls still streaming (no result has closed their round yet: the run ended on them, * or stats are read mid-round) are included as they stand, without being moved, so a later fold * still closes the round normally. `captureMaxBytes` caps those the same way a fold would. */ export declare function finalizeRunStats(acc: RunStatsAccumulator, captureMaxBytes?: number): GthRunStats; /** * One-shot convenience for the non-streaming path: fold a full `messages[]` (e.g. the final graph * state) into a fresh accumulator and finalize. Fail-soft (a non-iterable input yields empties). */ export declare function extractRunStats(messages: unknown, configuredMcpServers?: Iterable, captureMaxBytes?: number): GthRunStats;