UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

82 lines • 5.14 kB
import type { UIMessage } from 'ai'; import type { MessageFeedback } from '../../index.js'; import type { StoredMessage } from './client-wire.js'; /** * Summed usage/cost across the stored-message rows merged into one turn (see * {@link storedThreadToUiMessages}). Mirrors `MessageUsage`, but `costUsd` is always present * (never omitted) since it's a computed aggregate, not a single provider report. */ export interface AggregatedTurnUsage { inputTokens: number; outputTokens: number; /** * `null` only when EVERY merged row reports a null/absent cost (no pricing store bound, or an * unpriced model) — same "unknown, not a real $0" distinction `MessageUsage.costUsd` makes for a * single row. A null cost on SOME rows (mixed with priced ones) contributes `0` to the sum * instead of poisoning the whole turn's total to `null` — the tokens are still real and summed * either way, so only the cost figure would be understated, never wrong in a "$0 turn" sense. */ costUsd: number | null; } /** Metadata the grouped `UIMessage` carries on `.metadata.usage` — see {@link AggregatedTurnUsage}. */ export interface StoredTurnMetadata { usage: AggregatedTurnUsage; } /** * Everything a message's `metadata` may carry from this library: `usage` on a merged replayed turn, * `feedback` on a replayed message that was rated (a merged turn is rated through its last row, * whose id it carries), `runId` on a live-streamed one, `costUsd` from the stream's step ends. */ export interface AgentMessageMetadata extends Partial<StoredTurnMetadata> { feedback?: MessageFeedback; runId?: string; costUsd?: number | null; /** ISO timestamp: the stored row's `createdAt` (a merged turn: its first row's), or when a live turn started streaming. */ createdAt?: string; /** The agent that wrote a replayed message, when the row names one (`StoredMessage.agentName`). */ agentName?: string; /** The persona the turn that wrote a replayed message ran under (`StoredMessage.persona`). */ persona?: string; /** Host-defined keys: `StoredMessage.metadata` and the stream's `message-metadata` frames. */ [key: string]: unknown; } /** * Convert a thread's full `StoredMessage[]` history into `UIMessage[]`, merging consecutive * assistant rows (no user message between them) into ONE `UIMessage` per turn. * * Why: the agent loop persists one row per model iteration (see `agent-loop.ts`'s per-step * `persist:assistant:${i}`) — a turn with tool calls appends "I'll check that..." + tool calls, * then a separate final-answer row. `storedMessageToUiMessage` maps 1:1, so a reloaded thread * renders 2-3 assistant bubbles (each with its own cost/tokens footer) for what the LIVE stream * renders as one continuous response (one `UIMessage`, stepped internally — see * `AgentChatTransport.toChunkStream`'s `start-step`/`finish-step` bracketing). This closes that gap * for replayed history. * * Merge shape: each row is mapped via `storedMessageToUiMessage` (unchanged — same text-then-tool * part ordering per row) and the parts are concatenated in row order, so the grouped message's parts * read as `[row1 text?, row1 tools?, row2 text?, row2 tools?, ...]` — exactly the sequence the live * transport streams for the same turn. The group's `id` is the LAST row's id (the final-answer row — * it's the one carrying `followUps`, and what a host keys turn-level actions like "fork" or * "regenerate" on). Non-assistant rows (user/system) pass through `storedMessageToUiMessage` * unchanged, one row → one message, same as today. * * A lone assistant row between two user rows (the common case: no tool calls, or already a single * iteration) has nothing to merge with, so it comes back byte-for-byte identical to calling * `storedMessageToUiMessage` directly — no `metadata` is added for that case either, preserving * exact backward compatibility for hosts that read stored usage some other way. * * Only an ACTUAL merge (2+ rows) attaches `metadata.usage` — the {@link AggregatedTurnUsage} summed * across the merged rows' `usage` (rows without a `usage` at all, e.g. an older/test fixture, are * skipped rather than treated as zero-and-priced). * * Deliberately NOT reproduced: the live transport's `{type: 'step-start'}` UI parts (one per model * call, emitted by the AI SDK for every `start-step` chunk — see `repro-real-frames.spec.ts` for a * captured wire dump). Those are structural brackets `MessageItem`'s `renderBlocks` never renders * (it draws only text, reasoning, files and tool blocks), and `storedMessageToUiMessage` has never * emitted them for a single row either — inventing them here would make a merged turn diverge from * a same-shaped unmerged one instead of converging on it. "Indistinguishable from the live stream" * is scoped to what actually renders: the text/tool-call content in step order, not the step * brackets around it. */ export declare function storedThreadToUiMessages(messages: StoredMessage[]): UIMessage[]; //# sourceMappingURL=stored-thread-to-ui-messages.d.ts.map