/** * AgentEvent → AHP SessionAction mapper (AHP HR1b / RFC #1292). * * Stateless function that translates a single PowerLine `AgentEvent` (or any * structurally-compatible value — see {@link AgentEventFields}) into one or * more AHP `SessionAction` payloads. The mapper requires a {@link MapperContext} * from the caller to track turn state and tool-call pairing. * * Lives in `@grackle-ai/common` because the live gRPC transport * (`GrpcHostTransport` in `@grackle-ai/adapter-sdk`) consumes it, and * HR8d's PowerLine-side AHP wire emits AHP actions by running this mapper * forward against the runtime's `AgentEvent` stream. Decoupling from * `powerline.AgentEvent` lets HR8d remove the gRPC path entirely without * touching the mapper. * * **Wire fidelity notes (HR8d follow-up #1355):** * * AHP `StateAction` is a typed, normalized envelope — it deliberately does * NOT carry the runtime-native event payload (`AgentEvent.raw`). Anything * a consumer used to read out of `event.raw` MUST come through a * first-class structured field on the action instead: * * - Tool ID pairing (was `raw.id` / `raw.tool_use_id`) → use * `event.toolCallId`, which both the forward mapper and PowerLine's * runtime adapters set as a first-class HR3 field. * - Tool result success/error (was `raw.is_error`) → use * `SessionToolCallCompleteAction.result.success`. The reverse mapper * reconstructs a JSON content of `{is_ok, content, past_tense_message}` * that downstream code (`EventRenderer.tsx`) can parse. * - System-context marker (was `raw.systemContext === true`) → the * consumer-side `event-processor.ts` injects its system-context event * directly and sets `raw: '{"systemContext":true}'` locally, so the * wire never has to carry it. * * Token / cost counters (`input_tokens`, `output_tokens`, `cost_millicents`) * are carried via the `_meta` channel on `SessionMetaChangedAction`: * accumulated on the producer side, emitted as a single `usage` event * with the delta on the consumer side. Producer-side accumulation lives * in `MapperContext.metaAccumulator`; reverse-side delta computation in * `ahp-reverse-mapper.ts`'s `SessionMetaChanged` case. * * @module ahp-mapper */ import { type StateAction } from "@grackle-ai/ahp"; /** * Structural subset of `powerline.AgentEvent` that the mapper plus its * downstream consumers actually read. * * Any object with these fields can be passed to `mapAgentEvent` — * proto-generated `AgentEvent` messages from the live gRPC stream and plain * objects reconstructed from `session_actions` rows during replay both qualify. * * The fields the mapper itself reads are `type`, `content`, `toolCallId`, * `turnId`, and `diagnostic`. `timestamp` and `raw` are not consulted by the * mapper but are included here so downstream consumers (e.g. `event-processor` * in `@grackle-ai/core`) can convert envelopes back into `grackle.SessionEvent` * proto messages without re-importing the powerline types. */ export interface AgentEventFields { /** Event type discriminator (e.g. `"turn_started"`, `"tool_use"`). */ type: string; /** Event payload (string, often JSON). Empty/missing maps to `""`. */ content?: string; /** First-class tool call ID for `tool_use` / `tool_result` pairing (HR3). */ toolCallId?: string; /** Turn ID this event belongs to (overrides the active context turn). */ turnId?: string; /** `true` for diagnostic system events that route to OTLP telemetry (HR7). */ diagnostic?: boolean; /** * `true` when a `tool_result` reported a tool failure (#1362). Set by the * runtime adapter from its native outcome signal; this is the authoritative * source for `SessionToolCallComplete.result.success` (the AHP wire drops * `raw`, where the runtimes' error flags used to live). */ toolError?: boolean; /** ISO-8601 timestamp string (passed through from the runtime). */ timestamp?: string; /** Raw event body — opaque to the mapper, used by JSONL persistence. */ raw?: string; } /** * Disposition of a single AgentEvent after mapping. */ export type Disposition = "mapped" | "carried" | "dropped"; /** * Metadata describing how an AgentEvent was handled. */ export interface MappingNote { /** Index of the AgentEvent in the stream (0-based). */ index: number; /** The original AgentEvent type string. */ type: string; /** Whether the event was mapped to AHP actions, carried as metadata, or dropped. */ disposition: Disposition; /** Human-readable detail about the mapping decision. */ detail: string; } /** * Context maintained by the caller across a stream of events. * Tracks turn state and tool-call pairing. */ export interface MapperContext { /** ID of the currently active turn, or `undefined` if no turn has started. */ turnId?: string; /** * Stack of open tool call IDs (LIFO). Used for pairing `tool_result` events * with their corresponding `tool_use` when `toolCallId` is not available. */ openToolCalls: string[]; /** * Monotonically increasing counter for generating unique part IDs within * the current session. */ partCounter: number; /** * Monotonically increasing event index. Stored in snapshots so delta replay * can seed the starting index and generate consistent synthetic turn/tool IDs * (e.g. `turn-${index}`) for events that lack explicit IDs. */ eventIndex: number; /** * Accumulated `_meta` fields carried across events. The mapper merges * cost and runtimeSessionId into this object. */ metaAccumulator: { /** Accumulated cost in millicents (AHP-upstream gap; rides on `_meta`). */ costMillicents?: number; /** * Accumulated input tokens (HR8d follow-up #1355). Same wire treatment as * `costMillicents` — added on every `usage` event, the reverse mapper * emits the delta back as a `usage` event for the consumer's existing * token-tracking pipeline (`event-processor.ts:case "usage"`). */ inputTokens?: number; /** Accumulated output tokens (HR8d follow-up #1355). */ outputTokens?: number; /** Runtime-provided session ID from the `runtime_session_id` event. */ runtimeSessionId?: string; }; } /** Result of mapping a single AgentEvent. */ export interface MapResult { /** AHP StateAction[] produced from this event (all session-specific). */ actions: StateAction[]; /** Mapping disposition for this event (always zero or one). */ note?: MappingNote; } /** * Map a PowerLine `AgentEvent` into AHP `SessionAction` payloads. * * | AgentEvent type | AHP action(s) | Disposition | * |-----------------|---------------|-------------| * | `turn_started` | `SessionTurnStarted` | mapped | * | `turn_complete` | `SessionTurnComplete` | mapped | * | `input_needed` | advisory only | dropped | * | `text` (with turn) | `SessionResponsePart(markdown)` | mapped | * | `text` (no turn) | `SessionTurnStarted` + part + `SessionTurnComplete` | mapped (orphan) | * | `tool_use` (with turn) | `SessionToolCallStart` + `SessionToolCallReady` | mapped | * | `tool_use` (no turn) | orphan-wrapped Start + Ready | mapped (orphan) | * | `tool_result` (with turn) | `SessionToolCallComplete` | mapped | * | `tool_result` (no turn) | orphan-wrapped Complete | mapped (orphan) | * | `usage` | (none — cost accumulated in `_meta`) | carried | * | `error` (in-turn) | `SessionError` | mapped | * | `error` (pre-turn) | `SessionCreationFailed` | mapped | * | `status: failed` | `SessionError` or `SessionCreationFailed` | mapped (dead — forwarder tunnel intercepts) | * | `status: killed/terminated` | `SessionError` (abandoned turn) | conditional (dead — forwarder tunnel intercepts) | * | `status: completed/waiting_input/running` | dropped (dead — forwarder tunnel intercepts) | dropped | * | `system` (diagnostic) | dropped (OTLP telemetry) | carried | * | `system` (non-diagnostic, with turn) | `SessionResponsePart(systemNotification)` | mapped | * | `system` (non-diagnostic, no turn) | orphan-wrapped systemNotification | mapped (orphan) | * | `runtime_session_id` | `_meta.runtimeSessionId` | carried | */ export declare function mapAgentEvent(event: AgentEventFields, index: number, context: MapperContext): MapResult; //# sourceMappingURL=ahp-mapper.d.ts.map