blob: 77643ee0c4a2538706b1ff7a1d124fab27a77f5f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Canonical Runtime v2 event contract.
*
* This is the single internal runtime fact model. It is NOT a UI event
* (see ./events.ts `SessionEvent`) and NOT a trace row (RunTrace) or
* telemetry record. StoredMessage JSONL, renderer SessionEvent,
* AgentRunStore operational rows, RunTrace, and TelemetryRepo are all
* projections that should be written from -- or explicitly linked to -- these
* events.
*
* Architecture: docs/architecture/runtime-core-architecture-draft.md
*
* Phase 1 scope: types + small pure helpers only. No storage, runner,
* projection, or ledger logic lives here. Those arrive in later nodes.
*/
import { isWorkHubActionReceipt, type WorkHubActionReceipt } from './workhub-action-result.js';
import { isModelRetryDecision, type ModelRetryDecision } from './model-failure.js';
import {
isRuntimeHandoffPause,
runtimeHandoffPause,
type RuntimeHandoffPause,
} from './runtime-handoff.js';
import {
hasMeaningfulMessageContent,
isMessageContent,
normalizeMessageContent,
type MessageContent,
type PermissionClosureReason,
} from './events.js';
import {
INTERACTION_ID_MAX_BYTES,
INTERACTION_TOOL_NAME_MAX_BYTES,
decodeInteractionRequest,
type InteractionFormInput,
} from './interaction.js';
import {
isPermissionMode,
type PermissionMode,
type PermissionRequestPayload,
type PermissionResponse,
} from './permission.js';
import { isCollaborationMode, type CollaborationMode } from './collaboration.js';
import {
isAgentSwarmAuthorizationSource,
isEffectiveOrchestrationSource,
isOrchestrationMode,
type AgentSwarmAuthorizationSource,
type EffectiveOrchestrationSource,
type OrchestrationMode,
} from './orchestration.js';
import { isToolMode, type ToolMode } from './tool-mode.js';
import { isExecutorId } from './executor-id.js';
import {
isRuntimeSystemNoteKind,
type PersistedBackendKind,
type RuntimeSystemNoteKind,
} from './session.js';
import { decodeTurnOrigin, type TurnOrigin } from './turn-origin.js';
import type { UserQuestionRequest } from './user-question.js';
import {
defineObjectShape,
hasExactShape,
isFiniteNumber,
isOptionalMember,
isOptionalString,
isRecord,
isStringArray,
} from './record-schema.js';
import {
isPermissionDecisionFields,
isPermissionRequestPayload,
isUserQuestionRequest,
} from './interaction-record-schema.js';
import { isTokenUsageFields, type TokenUsageFields } from './usage-record-schema.js';
import { isToolRecoveryFactEnvelope, type ToolRecoveryFactEnvelope } from './tool-recovery-fact.js';
import {
isRuntimeEventWorkspaceFactEnvelope,
type RuntimeEventWorkspaceFactEnvelope,
} from './workspace-version-authority.js';
import {
decodeDurableToolResultProjection,
type DurableToolResultProjection,
} from './durable-tool-result-projection.js';
// ============================================================================
// Role / Author / Status
// ============================================================================
/**
* Conversation role the event plays in model history. Maps 1:1 with the
* roles providers expect in a message history (user / model / tool /
* system). Role is about *what lane* the content belongs to.
*/
export const RUNTIME_EVENT_ROLES = ['user', 'model', 'tool', 'system'] as const;
export type RuntimeEventRole = (typeof RUNTIME_EVENT_ROLES)[number];
export function isRuntimeEventRole(value: unknown): value is RuntimeEventRole {
return typeof value === 'string' && (RUNTIME_EVENT_ROLES as readonly string[]).includes(value);
}
/**
* Who authored the event inside the runtime. `agent` covers the model +
* flow orchestration; `tool` covers tool execution; `system` covers the
* runner, gate, and recovery. Author is about *which subsystem* produced
* the fact, which is orthogonal to the model-history `role`.
*
* Not every (author, role) combination is meaningful, but the runtime —
* not this type module — owns the policy that constrains them.
*/
export const RUNTIME_EVENT_AUTHORS = ['user', 'host', 'agent', 'tool', 'system'] as const;
export type RuntimeEventAuthor = (typeof RUNTIME_EVENT_AUTHORS)[number];
export function isRuntimeEventAuthor(value: unknown): value is RuntimeEventAuthor {
return typeof value === 'string' && (RUNTIME_EVENT_AUTHORS as readonly string[]).includes(value);
}
/**
* Lifecycle status an event asserts about its invocation/turn. Omitted on
* ordinary in-flight content events. Terminal values (completed / failed /
* aborted / cancelled) mark the last event of an invocation; `streaming`
* marks a non-terminal partial event that still carries lifecycle intent
* (e.g. a flow heartbeat) without being a content delta.
*/
export const RUNTIME_EVENT_STATUSES = [
'streaming',
'completed',
'failed',
'aborted',
'cancelled',
] as const;
export type RuntimeEventStatus = (typeof RUNTIME_EVENT_STATUSES)[number];
export const TERMINAL_RUNTIME_EVENT_STATUSES: readonly RuntimeEventStatus[] = [
'completed',
'failed',
'aborted',
'cancelled',
];
export function isRuntimeEventStatus(value: unknown): value is RuntimeEventStatus {
return typeof value === 'string' && (RUNTIME_EVENT_STATUSES as readonly string[]).includes(value);
}
/** Execution surface that produced a fact; absent on legacy ledgers. */
export const RUNTIME_EVENT_ORIGINS = ['provider', 'code_mode'] as const;
export type RuntimeEventOrigin = (typeof RUNTIME_EVENT_ORIGINS)[number];
/** Explicit provider-history policy; absent means visible. */
export const RUNTIME_EVENT_MODEL_VISIBILITIES = ['visible', 'hidden'] as const;
export type RuntimeEventModelVisibility = (typeof RUNTIME_EVENT_MODEL_VISIBILITIES)[number];
export function isTerminalRuntimeEventStatus(value: unknown): boolean {
return (
typeof value === 'string' &&
(TERMINAL_RUNTIME_EVENT_STATUSES as readonly string[]).includes(value)
);
}
// ============================================================================
// Content (model-facing payload)
// ============================================================================
export interface RuntimeEventTextContent extends MessageContent {
kind: 'text';
/** Failed response fragment retained for display, never for model replay. */
interrupted?: true;
/** Provider-owned text metadata such as Responses URL citations. */
providerOptions?: Record<string, unknown>;
/** Durable provenance for a host-authored user-role turn. */
origin?: TurnOrigin;
/**
* Marks a user message steered into a running turn at a step boundary.
* `text` stays raw for UI/transcript projections; model-replay projections
* MUST wrap it in the steering envelope so the provider request has exactly
* one canonical form — bare text is not an identity (a steer can equal the
* current prompt or any historical user message verbatim).
*/
steering?: true;
}
export interface RuntimeEventThinkingContent {
kind: 'thinking';
text: string;
/** Anthropic signed thinking — MUST be re-sent on replay when present. */
signature?: string;
/** Provider-owned replay metadata that must survive model replay. */
providerOptions?: Record<string, unknown>;
}
export interface RuntimeEventFunctionCallContent {
kind: 'function_call';
/** Matches the tool-call id the provider issued and the matching response. */
id: string;
name: string;
args: unknown;
/** Provider-owned opaque call metadata that must survive model replay. */
providerOptions?: Record<string, unknown>;
providerExecuted?: boolean;
}
export interface RuntimeEventFunctionResponseContent {
kind: 'function_response';
/** Matches RuntimeEventFunctionCallContent.id. */
id: string;
name: string;
result: unknown;
isError?: boolean;
providerExecuted?: boolean;
/** Raw provider result retained for provider-native replay; never rendered directly. */
providerOutput?: unknown;
/** Frozen provider-neutral content consumed by every model-history projection. */
modelProjection?: DurableToolResultProjection;
}
/**
* A note the runtime wrote about what happened during an invocation — context
* was compacted, the step cap was reached, the turn was aborted.
*
* It is a transcript row, not a model-facing payload: nothing replays it to a
* provider. It lives here because it is a fact of the invocation, and the
* invocation's events are the only record of those. Notes that happen between
* turns have no invocation, so they stay Session transcript rows.
*/
export interface RuntimeEventSystemNoteContent {
kind: 'system_note';
note: RuntimeSystemNoteKind;
/** Shape depends on `note`, exactly as it does on the transcript row. */
data?: unknown;
}
export interface RuntimeEventErrorContent {
kind: 'error';
retry?: ModelRetryDecision;
code?: string;
/** Stable machine-readable reason for routing; mirrors ErrorEvent.reason. */
reason?: string;
message: string;
/** Adapter MUST scrub secrets before populating this field. */
details?: string[] | Record<string, unknown>;
}
/**
* Where an invocation's provider route came from. `unknown` is the fail-closed
* marker for data that predates the opening fact: the transcript and tool
* evidence stay readable, but nothing may treat the route as authenticated.
*/
export type RuntimeInvocationRoute =
| {
provenance: 'runtime';
backendKind: Exclude<PersistedBackendKind, 'plugin-executor'>;
llmConnectionId: string;
llmConnectionSlug: string;
modelId: string;
/** Frozen provider endpoint and credential ownership; absent on non-provider runs. */
providerStateIdentity?: `sha256:${string}`;
}
| {
provenance: 'runtime';
backendKind: 'plugin-executor';
executorId: string;
llmConnectionSlug: string;
modelId: string;
/** Frozen plugin package, entry point, and activation generation. */
providerStateIdentity: `sha256:${string}`;
}
| {
provenance: 'unknown';
backendKind: PersistedBackendKind;
llmConnectionSlug: string;
modelId: string;
};
/** Execution configuration frozen before an invocation's first dispatch. */
export interface RuntimeInvocationConfiguration {
cwd: string;
permissionMode: PermissionMode;
collaborationMode: CollaborationMode;
orchestrationMode: OrchestrationMode;
orchestrationSource: EffectiveOrchestrationSource;
toolMode: ToolMode;
agentSwarmAuthorization?: AgentSwarmAuthorizationSource;
/** Authoritative host identity for the workspace observed at open. */
workspaceIdentity?: string;
}
/**
* The authority that caused this invocation to exist. Closed and discriminated,
* so a reader names the root it wants instead of asserting that every other
* optional root field is absent.
*/
export type RuntimeInvocationRootAuthority =
| { kind: 'user' }
| { kind: 'context_compact' }
| { kind: 'scheduled_task'; scheduledTaskId: string }
| { kind: 'goal'; goalId: string }
| { kind: 'agent_graph_supervisor_wake'; wakeId: string; attemptId: string }
| { kind: 'legacy_automation'; legacyAutomationId: string };
/** Turn/session lineage that is immutable once the invocation opens. */
export interface RuntimeInvocationLineage {
parentRunId?: string;
/** The run this one continues, and the run it re-attempts. Never both. */
resumedFromRunId?: string;
retriedFromRunId?: string;
parentTurnId?: string;
parentSessionId?: string;
retriedFromTurnId?: string;
regeneratedFromTurnId?: string;
branchOfTurnId?: string;
agentId?: string;
agentName?: string;
}
/**
* How this invocation was opened. `continuation` carries the same source
* identity the continuation-start action authenticates, so a migrated opening
* fact keeps the lineage edge even where no start event exists.
*/
export type RuntimeInvocationOpenSource =
| { kind: 'fresh' }
| {
kind: 'continuation';
sourceInvocationId: string;
sourceRunId: string;
sourceTurnId: string;
sourceRuntimeEventHighWater: number;
claimId?: string;
boundaryDigest?: `sha256:${string}`;
}
| {
/** A new physical attempt under the original logical Turn admission. */
kind: 'handoff';
rootRunId: string;
sourceInvocationId: string;
sourceRunId: string;
sourceTurnId: string;
sourceRuntimeEventHighWater: number;
claimId: string;
boundaryDigest: `sha256:${string}`;
};
/**
* The one immutable opening fact of a run-kind invocation, committed before any
* provider or tool dispatch. Route provenance lives here once per invocation
* and is joined by `invocationId`; it is never copied onto other events.
*
* Reserved control-plane streams (history compaction checkpoints, workspace
* version authority) have no run and therefore no opening fact.
*/
export interface RuntimeEventInvocationOpenedContent {
kind: 'invocation_opened';
protocol: 'invocation_opened_v1';
route: RuntimeInvocationRoute;
configuration: RuntimeInvocationConfiguration;
root: RuntimeInvocationRootAuthority;
source: RuntimeInvocationOpenSource;
/** Omitted entirely when the invocation has no lineage edges. */
lineage?: RuntimeInvocationLineage;
}
/**
* Content union for user/model text, model thinking, function call,
* function response, error payloads, and the invocation opening fact.
* Discriminated by `kind` to match the existing ToolResultContent convention.
*/
export type RuntimeEventContent =
| RuntimeEventTextContent
| RuntimeEventThinkingContent
| RuntimeEventFunctionCallContent
| RuntimeEventFunctionResponseContent
| RuntimeEventErrorContent
| RuntimeEventSystemNoteContent
| RuntimeEventInvocationOpenedContent;
export const RUNTIME_EVENT_CONTENT_KINDS = [
'text',
'thinking',
'function_call',
'function_response',
'error',
'system_note',
'invocation_opened',
] as const;
export type RuntimeEventContentKind = (typeof RUNTIME_EVENT_CONTENT_KINDS)[number];
// ============================================================================
// Actions (control / side-effect intent)
// ============================================================================
/**
* Token usage carried as a runtime action rather than a content payload.
* Mirrors TokenUsageEvent / TokenUsageMessage so projections can map 1:1.
*/
export interface RuntimeEventTokenUsage extends TokenUsageFields {}
/**
* Permission decision attached to an event. Runtime history may retain the
* originating tool identity for self-contained conversation copies.
*/
export interface RuntimeEventPermissionDecision extends PermissionResponse {
toolName?: string;
/**
* What the prompt told the user they were approving. Normally read off the
* paired request; carried here when the decision is the only surviving
* evidence that the prompt happened.
*/
hint?: string;
}
export const TOOL_BOUNDARY_PROTOCOL_V1 = 't1_after_preflight_v1' as const;
export type ToolBoundaryProtocol = typeof TOOL_BOUNDARY_PROTOCOL_V1;
/**
* Canonical fact that the runtime crossed the durable tool-dispatch boundary.
* Its presence means the implementation may have started; it does not assert
* that the implementation or any external side effect actually completed.
*/
export interface RuntimeEventToolDispatch {
protocol: ToolBoundaryProtocol;
/** New writes require this exact durable Tool Result projection protocol. */
resultProjectionVersion?: 1;
operationId: string;
providerToolCallId: string;
toolName: string;
canonicalArgsHash: string;
recoveryMode: ToolRecoveryMode;
/** T1-frozen managed workspace mutation identity. */
managedMutation?: RuntimeEventManagedWorkspaceMutationV2;
}
/**
* Canonical semantics bound by the managed mutation execution-profile digest.
* Runtime consumes these limits directly, so changing the execution contract
* requires changing this representation and its digest together.
*/
export const MANAGED_MUTATION_EXECUTION_PROFILE_V1_SPEC = Object.freeze({
protocol: 'managed_mutation_execution_profile_v1',
toolNames: Object.freeze(['Write', 'Edit'] as const),
transform: 'pure_frozen_args_only_v1',
objectFormat: 'sha1',
pathPolicyVersion: 3,
resultSnapshot: Object.freeze({
maxBytes: 1_048_576,
maxDepth: 64,
maxNodes: 65_536,
maxProperties: 65_536,
maxArrayLength: 65_536,
format: 'strict_json_v1',
}),
terminalAuthority: 'owner_committed_exact_outcome_v1',
genericFallback: 'forbidden',
} as const);
export const MANAGED_MUTATION_EXECUTION_PROFILE_V1_DIGEST =
'sha256:ffdfdda9cf38f382e0c4db81dac7319cd33586a6c65051a97a15e6c41b88f825' as const;
export interface RuntimeEventManagedWorkspaceMutationV2 {
protocol: 'managed_mutation_v2';
repositoryId: string;
workspaceId: string;
workspaceEpochId: string;
workspaceInstanceId: string;
objectFormat: 'sha1';
baseWorkspaceVersionId: string;
baseAcceptedEventId: string;
baseHeadRevision: number;
baseCommitOid: string;
baseTreeOid: string;
expectedPath: string;
pathPolicyVersion: 3;
executionProfileDigest: typeof MANAGED_MUTATION_EXECUTION_PROFILE_V1_DIGEST;
}
export interface RuntimeEventManagedMutationTerminalV1 {
protocol: 'managed_mutation_terminal_v1';
operationId: string;
dispatchEventId: string;
workspaceInstanceId: string;
terminalKind: 'no_workspace_change' | 'operation_failed_no_effect';
}
export interface RuntimeEventProtocolMarker {
toolBoundary: ToolBoundaryProtocol;
}
export interface RuntimeEventContinuationStartV2 {
protocol: 'continuation_start_v2';
/** Mirrors store-owned claim state; recovery never trusts this field alone. */
provenance: 'runtime_admission' | 'claim_repair';
claimId: string;
boundaryDigest: `sha256:${string}`;
immediateSource: {
sessionId: string;
invocationId: string;
runId: string;
turnId: string;
highWater: number;
prefixDigest: `sha256:${string}`;
};
replayManifestDigest: `sha256:${string}`;
providerProjectionVersion: 1 | 2;
providerReplayDigest: `sha256:${string}`;
}
interface RuntimeEventAnswerAcceptedIdentity {
requestId: string;
}
export interface RuntimeEventUserQuestionAnswerAccepted
extends RuntimeEventAnswerAcceptedIdentity {}
export interface RuntimeEventFormRequest extends InteractionFormInput {
requestId: string;
toolUseId: string;
}
export interface RuntimeEventFormAnswerAccepted extends RuntimeEventAnswerAcceptedIdentity {}
export interface RuntimeEventPermissionAnswerAccepted extends RuntimeEventAnswerAcceptedIdentity {}
export interface RuntimeEventPermissionClosureAccepted {
requestId: string;
reason: PermissionClosureReason;
}
/**
* Control and side-effect intent carried alongside content. An event may
* carry content, actions, both, or (rarely) neither — but a terminal
* event without `actions.endInvocation` MUST assert a terminal `status`.
*/
export interface RuntimeEventActions {
/** Host coordination receipt linked to this admitted Run. */
coordination?: WorkHubActionReceipt;
/** Durable physical pause; does not complete or cancel the owning logical Turn. */
handoffPause?: RuntimeHandoffPause;
/** Patch applied to invocation-scoped runtime state. */
stateDelta?: Record<string, unknown>;
/** Artifact key → primitive delta (size/bytes/version counters, etc.). */
artifactDelta?: Record<string, string | number | boolean>;
/** A permission prompt raised for a tool call. */
permissionRequest?: PermissionRequestPayload;
/** A resolved permission decision (allow/deny) for a prior request. */
permissionDecision?: RuntimeEventPermissionDecision;
/** Audit fact only; the canonical permission outcome remains in InteractionStore. */
permissionAnswerAccepted?: RuntimeEventPermissionAnswerAccepted;
/** Audit fact that an unanswered hosted permission request was durably closed. */
permissionClosureAccepted?: RuntimeEventPermissionClosureAccepted;
/** A bounded in-turn question raised by a tool call. */
userQuestionRequest?: UserQuestionRequest;
/** Audit fact only; the canonical answer remains in InteractionStore. */
userQuestionAnswerAccepted?: RuntimeEventUserQuestionAnswerAccepted;
/** A provider-neutral structured form raised by a tool call. */
formRequest?: RuntimeEventFormRequest;
/** Audit fact only; the canonical form result remains in InteractionStore. */
formAnswerAccepted?: RuntimeEventFormAnswerAccepted;
/** Hand off the invocation to another agent (multi-agent transfer). */
transferToAgent?: string;
/** Marks the event that closes the invocation. */
endInvocation?: boolean;
/** Token accounting for the model call this event summarizes. */
tokenUsage?: RuntimeEventTokenUsage;
/** Durable, non-model-visible T1 tool-dispatch fact. */
toolDispatch?: RuntimeEventToolDispatch;
/** Reserved recovery fact; only the atomic recovery-bundle writer may persist it. */
toolRecovery?: ToolRecoveryFactEnvelope;
/** Protocols that were actually active from the first event of this run. */
runtimeProtocol?: RuntimeEventProtocolMarker;
/** Durable provider-call T1 for a claimed continuation. */
continuationStart?: RuntimeEventContinuationStartV2;
/** Reserved workspace authority fact; only its atomic SQLite writer may persist it. */
workspaceFact?: RuntimeEventWorkspaceFactEnvelope;
/** Reserved no-effect terminal; only the managed mutation terminal writer may persist it. */
managedMutationTerminal?: RuntimeEventManagedMutationTerminalV1;
}
// ============================================================================
// Refs (links to projections / ledgers)
// ============================================================================
/**
* Links back to the projection/ledger rows written from (or correlated
* with) this event. Refs are diagnostics/audit pointers; a missing ref
* never changes runtime behavior. `toolCallId` doubles as the matching
* key for function_call ↔ function_response when provider ids differ.
*/
export interface RuntimeEventRefs {
storedMessageId?: string;
traceEventId?: string;
toolCallId?: string;
providerEventId?: string;
/** Canonical digest of a user submission before host-side preparation. */
sourceMessageDigest?: `sha256:${string}`;
/** Trace-group id linking aggregate usage to physical provider attempts. */
providerRequestTraceId?: string;
artifactId?: string;
/** Runtime-owned durable identity for one tool side-effect boundary. */
operationId?: string;
/** Provider tool-call id of the enclosing exec operation. */
parentToolCallId?: string;
/** Runtime-owned durable identity of the enclosing exec operation. */
parentOperationId?: string;
/**
* Assistant step id for a function_call event: the id of the step's
* text/thinking messages (their `providerEventId`). Model replay pairs a
* step's signed thinking with its tool calls by this id. Absent on legacy
* (per-turn) events; a missing stepId marks history that cannot be paired
* and is replayed with the older degraded semantics.
*/
stepId?: string;
/** Source execution boundary for a safe-boundary continuation start fact. */
sourceInvocationId?: string;
sourceRunId?: string;
sourceTurnId?: string;
sourceRuntimeEventHighWater?: number;
}
/** Tool-owned contract for deciding what a later recovery phase may do. */
export type ToolRecoveryMode =
| 'replay_safe'
| 'idempotent'
| 'reconcile'
| 'reattach'
| 'outcome_unknown'
| 'never_auto_retry';
// ============================================================================
// RuntimeEvent
// ============================================================================
/**
* The canonical runtime fact.
*
* Phase 0-3 identity contract: one `invocationId` maps to one `runId`.
* Invocation identifies provider/tool execution while Run identifies its
* durable operational ledger. Store-owned control-plane streams (for example,
* workspace version authority) use reserved identities and have no AgentRun
* header. A continuation creates fresh values for both;
* `turnId` names the user turn and `ts` is Unix ms.
*
* `partial: true` marks a transient chunk (streaming text, progress) that
* is superseded by a later non-partial event. Projections decide whether
* to persist partials; model history MUST exclude them.
*/
export interface RuntimeEvent {
/** Event uuid — used for dedup on reconnect/replay. */
id: string;
/** Durable invocation spine id; groups every run/turn of one request. */
invocationId: string;
/** Durable operational run identity; names one execution of the invocation. */
runId: string;
sessionId: string;
/** Groups all events from one agent turn (maps to StoredMessage.turnId). */
turnId: string;
/** Unix ms timestamp. */
ts: number;
/** Optional branch/agent lane for future multi-agent trees. */
branch?: string;
/** True for transient streaming chunks superseded by a later event. */
partial: boolean;
role: RuntimeEventRole;
author: RuntimeEventAuthor;
/** Execution surface that produced this fact; absent on legacy ledgers. */
origin?: RuntimeEventOrigin;
/** Explicit provider-history policy; absent means visible for legacy compatibility. */
modelVisibility?: RuntimeEventModelVisibility;
/** Lifecycle assertion; omitted on ordinary in-flight content events. */
status?: RuntimeEventStatus;
content?: RuntimeEventContent;
actions?: RuntimeEventActions;
refs?: RuntimeEventRefs;
}
/**
* Every key a RuntimeEvent envelope may carry. TypeScript forces this list to
* cover the interface, so it moves whenever the interface does — which makes it
* the thing anything re-implementing the envelope check must be pinned to.
*
* A former external consumer reimplemented it and drifted: it never learned
* `origin` or `modelVisibility`, so once Runtime emitted them every event
* failed its envelope check. `runtime-event.test.ts` therefore holds the
* validation corpus to this list instead of a second hand-maintained shape.
*/
export function runtimeEventEnvelopeKeys(): readonly string[] {
return [...RUNTIME_EVENT_SHAPE.allowed];
}
/**
* Every value each closed-domain envelope key may hold.
*
* Pinning the corpus to the key list closed one half of the drift the Harbor
* exporter fell through: a key nobody wrote a case for. The other half is a
* value nobody wrote a case for — the exporter spells these domains out again
* in Python, and a member added here that no case carries would leave the two
* disagreeing about what is valid with nothing red. Only the closed domains
* appear; `branch` is free text and has nothing to enumerate.
*/
export function runtimeEventEnvelopeValueDomains(): Readonly<Record<string, readonly string[]>> {
return {
role: RUNTIME_EVENT_ROLES,
author: RUNTIME_EVENT_AUTHORS,
origin: RUNTIME_EVENT_ORIGINS,
modelVisibility: RUNTIME_EVENT_MODEL_VISIBILITIES,
status: RUNTIME_EVENT_STATUSES,
};
}
const RUNTIME_EVENT_SHAPE = defineObjectShape<RuntimeEvent>()(
['id', 'invocationId', 'runId', 'sessionId', 'turnId', 'ts', 'partial', 'role', 'author'],
['branch', 'origin', 'modelVisibility', 'status', 'content', 'actions', 'refs'],
);
const TEXT_CONTENT_SHAPE = defineObjectShape<RuntimeEventTextContent>()(
['kind', 'text'],
[
'displayText',
'interrupted',
'origin',
'attachments',
'directoryReferences',
'quotes',
'inlineReferences',
'steering',
'providerOptions',
],
);
const THINKING_CONTENT_SHAPE = defineObjectShape<RuntimeEventThinkingContent>()(
['kind', 'text'],
['signature', 'providerOptions'],
);
const FUNCTION_CALL_CONTENT_SHAPE = defineObjectShape<RuntimeEventFunctionCallContent>()(
['kind', 'id', 'name', 'args'],
['providerOptions', 'providerExecuted'],
);
const FUNCTION_RESPONSE_CONTENT_SHAPE = defineObjectShape<RuntimeEventFunctionResponseContent>()(
['kind', 'id', 'name', 'result'],
['isError', 'providerExecuted', 'providerOutput', 'modelProjection'],
);
const ERROR_CONTENT_SHAPE = defineObjectShape<RuntimeEventErrorContent>()(
['kind', 'message'],
['code', 'reason', 'details', 'retry'],
);
const SYSTEM_NOTE_CONTENT_SHAPE = defineObjectShape<RuntimeEventSystemNoteContent>()(
['kind', 'note'],
['data'],
);
const INVOCATION_OPENED_CONTENT_SHAPE = defineObjectShape<RuntimeEventInvocationOpenedContent>()(
['kind', 'protocol', 'route', 'configuration', 'root', 'source'],
['lineage'],
);
const INVOCATION_ROUTE_RUNTIME_MODEL_SHAPE = defineObjectShape<
Extract<RuntimeInvocationRoute, { provenance: 'runtime'; llmConnectionId: string }>
>()(
['provenance', 'backendKind', 'llmConnectionId', 'llmConnectionSlug', 'modelId'],
['providerStateIdentity'],
);
const INVOCATION_ROUTE_RUNTIME_EXECUTOR_SHAPE = defineObjectShape<
Extract<RuntimeInvocationRoute, { provenance: 'runtime'; backendKind: 'plugin-executor' }>
>()(
[
'provenance',
'backendKind',
'executorId',
'llmConnectionSlug',
'modelId',
'providerStateIdentity',
],
[],
);
const INVOCATION_ROUTE_UNKNOWN_SHAPE = defineObjectShape<
Extract<RuntimeInvocationRoute, { provenance: 'unknown' }>
>()(['provenance', 'backendKind', 'llmConnectionSlug', 'modelId'], []);
const INVOCATION_CONFIGURATION_SHAPE = defineObjectShape<RuntimeInvocationConfiguration>()(
[
'cwd',
'permissionMode',
'collaborationMode',
'orchestrationMode',
'orchestrationSource',
'toolMode',
],
['agentSwarmAuthorization', 'workspaceIdentity'],
);
const INVOCATION_LINEAGE_SHAPE = defineObjectShape<RuntimeInvocationLineage>()(
[],
[
'parentRunId',
'resumedFromRunId',
'retriedFromRunId',
'parentTurnId',
'parentSessionId',
'retriedFromTurnId',
'regeneratedFromTurnId',
'branchOfTurnId',
'agentId',
'agentName',
],
);
const INVOCATION_CONTINUATION_SOURCE_SHAPE = defineObjectShape<
Extract<RuntimeInvocationOpenSource, { kind: 'continuation' }>
>()(
['kind', 'sourceInvocationId', 'sourceRunId', 'sourceTurnId', 'sourceRuntimeEventHighWater'],
['claimId', 'boundaryDigest'],
);
const INVOCATION_FRESH_SOURCE_SHAPE = defineObjectShape<
Extract<RuntimeInvocationOpenSource, { kind: 'fresh' }>
>()(['kind'], []);
const INVOCATION_HANDOFF_SOURCE_SHAPE = defineObjectShape<
Extract<RuntimeInvocationOpenSource, { kind: 'handoff' }>
>()(
[
'kind',
'rootRunId',
'sourceInvocationId',
'sourceRunId',
'sourceTurnId',
'sourceRuntimeEventHighWater',
'claimId',
'boundaryDigest',
],
[],
);
const INVOCATION_ROOT_SHAPES = {
user: defineObjectShape<Extract<RuntimeInvocationRootAuthority, { kind: 'user' }>>()(
['kind'],
[],
),
context_compact: defineObjectShape<
Extract<RuntimeInvocationRootAuthority, { kind: 'context_compact' }>
>()(['kind'], []),
scheduled_task: defineObjectShape<
Extract<RuntimeInvocationRootAuthority, { kind: 'scheduled_task' }>
>()(['kind', 'scheduledTaskId'], []),
goal: defineObjectShape<Extract<RuntimeInvocationRootAuthority, { kind: 'goal' }>>()(
['kind', 'goalId'],
[],
),
agent_graph_supervisor_wake: defineObjectShape<
Extract<RuntimeInvocationRootAuthority, { kind: 'agent_graph_supervisor_wake' }>
>()(['kind', 'wakeId', 'attemptId'], []),
legacy_automation: defineObjectShape<
Extract<RuntimeInvocationRootAuthority, { kind: 'legacy_automation' }>
>()(['kind', 'legacyAutomationId'], []),
} as const;
const RUNTIME_ACTIONS_SHAPE = defineObjectShape<RuntimeEventActions>()(
[],
[
'handoffPause',
'coordination',
'stateDelta',
'artifactDelta',
'permissionRequest',
'permissionDecision',
'permissionAnswerAccepted',
'permissionClosureAccepted',
'userQuestionRequest',
'userQuestionAnswerAccepted',
'formRequest',
'formAnswerAccepted',
'transferToAgent',
'endInvocation',
'tokenUsage',
'toolDispatch',
'toolRecovery',
'runtimeProtocol',
'continuationStart',
'workspaceFact',
'managedMutationTerminal',
],
);
const RUNTIME_MANAGED_MUTATION_TERMINAL_SHAPE =
defineObjectShape<RuntimeEventManagedMutationTerminalV1>()(
['protocol', 'operationId', 'dispatchEventId', 'workspaceInstanceId', 'terminalKind'],
[],
);
const ANSWER_ACCEPTED_IDENTITY_SHAPE = defineObjectShape<RuntimeEventAnswerAcceptedIdentity>()(
['requestId'],
[],
);
const PERMISSION_CLOSURE_ACCEPTED_SHAPE =
defineObjectShape<RuntimeEventPermissionClosureAccepted>()(['requestId', 'reason'], []);
const RUNTIME_PERMISSION_DECISION_SHAPE = defineObjectShape<RuntimeEventPermissionDecision>()(
['requestId', 'decision'],
['rememberForTurn', 'reviewer', 'rationale', 'riskLevel', 'toolName', 'hint'],
);
const UTF8 = new TextEncoder();
const RUNTIME_TOOL_DISPATCH_SHAPE = defineObjectShape<RuntimeEventToolDispatch>()(
[
'protocol',
'operationId',
'providerToolCallId',
'toolName',
'canonicalArgsHash',
'recoveryMode',
],
['managedMutation', 'resultProjectionVersion'],
);
const RUNTIME_MANAGED_WORKSPACE_MUTATION_SHAPE =
defineObjectShape<RuntimeEventManagedWorkspaceMutationV2>()(
[
'protocol',
'repositoryId',
'workspaceId',
'workspaceEpochId',
'workspaceInstanceId',
'objectFormat',
'baseWorkspaceVersionId',
'baseAcceptedEventId',
'baseHeadRevision',
'baseCommitOid',
'baseTreeOid',
'expectedPath',
'pathPolicyVersion',
'executionProfileDigest',
],
[],
);
const RUNTIME_PROTOCOL_MARKER_SHAPE = defineObjectShape<RuntimeEventProtocolMarker>()(
['toolBoundary'],
[],
);
const RUNTIME_CONTINUATION_START_SHAPE = defineObjectShape<RuntimeEventContinuationStartV2>()(
[
'protocol',
'provenance',
'claimId',
'boundaryDigest',
'immediateSource',
'replayManifestDigest',
'providerProjectionVersion',
'providerReplayDigest',
],
[],
);
const RUNTIME_CONTINUATION_SOURCE_SHAPE = defineObjectShape<
RuntimeEventContinuationStartV2['immediateSource']
>()(['sessionId', 'invocationId', 'runId', 'turnId', 'highWater', 'prefixDigest'], []);
const RUNTIME_TOKEN_USAGE_SHAPE = defineObjectShape<RuntimeEventTokenUsage>()(
['input', 'output'],
[
'cacheHitInput',
'cacheMissInput',
'cacheWriteInput',
'cacheMissInputSource',
'reasoning',
'total',
'rawFinishReason',
'runtimeSteps',
'cacheRead',
'cacheCreation',
'costUsd',
'systemPromptHash',
'contextRemaining',
'prefixHash',
'prefixChangeReason',
'requestShapeHash',
'requestShapeChangeReason',
'promptSegments',
'contextBudget',
'providerRequestTraceId',
'lastRequestAnchor',
],
);
const RUNTIME_REFS_SHAPE = defineObjectShape<RuntimeEventRefs>()(
[],
[
'storedMessageId',
'traceEventId',
'toolCallId',
'providerEventId',
'sourceMessageDigest',
'providerRequestTraceId',
'artifactId',
'operationId',
'parentToolCallId',
'parentOperationId',
'stepId',
'sourceInvocationId',
'sourceRunId',
'sourceTurnId',
'sourceRuntimeEventHighWater',
],
);
export function decodeRuntimeEvent(value: unknown): RuntimeEvent {
if (
!isRecord(value) ||
!hasExactShape(value, RUNTIME_EVENT_SHAPE) ||
typeof value.id !== 'string' ||
value.id.length === 0 ||
typeof value.invocationId !== 'string' ||
value.invocationId.length === 0 ||
typeof value.runId !== 'string' ||
value.runId.length === 0 ||
typeof value.sessionId !== 'string' ||
value.sessionId.length === 0 ||
typeof value.turnId !== 'string' ||
value.turnId.length === 0 ||
!isFiniteNumber(value.ts) ||
!isOptionalString(value.branch) ||
typeof value.partial !== 'boolean' ||
!isRuntimeEventRole(value.role) ||
!isRuntimeEventAuthor(value.author) ||
!isOptionalMember(value.origin, RUNTIME_EVENT_ORIGINS) ||
!isOptionalMember(value.modelVisibility, RUNTIME_EVENT_MODEL_VISIBILITIES) ||
(value.status !== undefined && !isRuntimeEventStatus(value.status)) ||
(value.content !== undefined && !isRuntimeEventContent(value.content)) ||
!hasOwnedModelProjection(value.content, value.sessionId) ||
(value.actions !== undefined && !isRuntimeEventActions(value.actions)) ||
(value.refs !== undefined && !isRuntimeEventRefs(value.refs))
) {
throw new Error('Invalid RuntimeEvent schema');
}
runtimeHandoffPause(value as unknown as RuntimeEvent);
if (isRecord(value.content) && value.content.kind === 'text') {
return {
...value,
content: {
kind: 'text',
...normalizeMessageContent(value.content as unknown as MessageContent),
...(value.content.origin !== undefined
? { origin: decodeTurnOrigin(value.content.origin) }
: {}),
...(value.content.steering === true ? { steering: true as const } : {}),
},
} as unknown as RuntimeEvent;
}
return value as unknown as RuntimeEvent;
}
function hasOwnedModelProjection(content: unknown, sessionId: string): boolean {
if (!isRecord(content) || content.kind !== 'function_response') return true;
const projection = content.modelProjection;
if (!isRecord(projection) || projection.kind !== 'content' || !Array.isArray(projection.parts)) {
return true;
}
return projection.parts.every(
(part) =>
!isRecord(part) ||
part.kind !== 'artifact' ||
(isRecord(part.ref) && part.ref.sessionId === sessionId),
);
}
function isRuntimeEventContent(value: unknown): value is RuntimeEventContent {
if (!isRecord(value)) return false;
switch (value.kind) {
case 'text':
if (
!hasExactShape(value, TEXT_CONTENT_SHAPE) ||
(value.origin !== undefined && !isTurnOrigin(value.origin)) ||
(value.steering !== undefined && value.steering !== true) ||
(value.interrupted !== undefined && value.interrupted !== true) ||
(value.providerOptions !== undefined && !isRecord(value.providerOptions))
) {
return false;
}
return isMessageContent({
text: value.text,
...(value.displayText !== undefined ? { displayText: value.displayText } : {}),
...(value.attachments !== undefined ? { attachments: value.attachments } : {}),
...(value.directoryReferences !== undefined
? { directoryReferences: value.directoryReferences }
: {}),
...(value.quotes !== undefined ? { quotes: value.quotes } : {}),
...(value.inlineReferences !== undefined
? { inlineReferences: value.inlineReferences }
: {}),
});
case 'thinking':
return (
hasExactShape(value, THINKING_CONTENT_SHAPE) &&
typeof value.text === 'string' &&
isOptionalString(value.signature) &&
(value.providerOptions === undefined || isRecord(value.providerOptions))
);
case 'function_call':
return (
hasExactShape(value, FUNCTION_CALL_CONTENT_SHAPE) &&
typeof value.id === 'string' &&
typeof value.name === 'string' &&
Object.hasOwn(value, 'args') &&
(value.providerOptions === undefined || isRecord(value.providerOptions)) &&
(value.providerExecuted === undefined || typeof value.providerExecuted === 'boolean')
);
case 'function_response':
return (
hasExactShape(value, FUNCTION_RESPONSE_CONTENT_SHAPE) &&
typeof value.id === 'string' &&
typeof value.name === 'string' &&
Object.hasOwn(value, 'result') &&
(value.isError === undefined || typeof value.isError === 'boolean') &&
(value.providerExecuted === undefined || typeof value.providerExecuted === 'boolean') &&
(value.modelProjection === undefined ||
decodesDurableToolResultProjection(value.modelProjection))
);
case 'error':
return (
hasExactShape(value, ERROR_CONTENT_SHAPE) &&
isOptionalString(value.code) &&
isOptionalString(value.reason) &&
(value.retry === undefined || isModelRetryDecision(value.retry)) &&
typeof value.message === 'string' &&
(value.details === undefined || isStringArray(value.details) || isRecord(value.details))
);
case 'system_note':
return (
hasExactShape(value, SYSTEM_NOTE_CONTENT_SHAPE) &&
typeof value.note === 'string' &&
isRuntimeSystemNoteKind(value.note)
);
case 'invocation_opened':
return isRuntimeInvocationOpened(value);
default:
return false;
}
}
/**
* True when the event is the immutable opening fact of its invocation.
* Narrowing here keeps every reader off a hand-rolled `content.kind` test.
*/
export function runtimeEventInvocationOpening(
event: RuntimeEvent,
): RuntimeEventInvocationOpenedContent | undefined {
return event.content?.kind === 'invocation_opened' ? event.content : undefined;
}
/** Strict decode for one persisted opening fact; throws on any drift. */
export function decodeRuntimeInvocationOpened(value: unknown): RuntimeEventInvocationOpenedContent {
if (!isRuntimeInvocationOpened(value)) {
throw new Error('Invalid RuntimeEvent invocation_opened schema');
}
return value;
}
function isRuntimeInvocationOpened(value: unknown): value is RuntimeEventInvocationOpenedContent {
return (
isRecord(value) &&
value.kind === 'invocation_opened' &&
hasExactShape(value, INVOCATION_OPENED_CONTENT_SHAPE) &&
value.protocol === 'invocation_opened_v1' &&
isRuntimeInvocationRoute(value.route) &&
isRuntimeInvocationConfiguration(value.configuration) &&
isRuntimeInvocationRootAuthority(value.root) &&
isRuntimeInvocationOpenSource(value.source) &&
(value.lineage === undefined || isRuntimeInvocationLineage(value.lineage))
);
}
function isRuntimeInvocationRoute(value: unknown): value is RuntimeInvocationRoute {
if (!isRecord(value)) return false;
if (
!isPersistedBackendKind(value.backendKind) ||
!isNonEmptyString(value.llmConnectionSlug) ||
!isNonEmptyString(value.modelId)
) {
return false;
}
if (value.provenance === 'runtime') {
if (value.backendKind === 'plugin-executor') {
return (
hasExactShape(value, INVOCATION_ROUTE_RUNTIME_EXECUTOR_SHAPE) &&
isExecutorId(value.executorId) &&
isSha256Digest(value.providerStateIdentity)
);
}
return (
hasExactShape(value, INVOCATION_ROUTE_RUNTIME_MODEL_SHAPE) &&
isNonEmptyString(value.llmConnectionId) &&
(value.providerStateIdentity === undefined || isSha256Digest(value.providerStateIdentity))
);
}
return value.provenance === 'unknown' && hasExactShape(value, INVOCATION_ROUTE_UNKNOWN_SHAPE);
}
function isPersistedBackendKind(value: unknown): value is PersistedBackendKind {
return value === 'ai-sdk' || value === 'plugin-executor' || value === 'fake';
}
function isRuntimeInvocationConfiguration(value: unknown): value is RuntimeInvocationConfiguration {
return (
isRecord(value) &&
hasExactShape(value, INVOCATION_CONFIGURATION_SHAPE) &&
typeof value.cwd === 'string' &&
isPermissionMode(value.permissionMode) &&
isCollaborationMode(value.collaborationMode) &&
isOrchestrationMode(value.orchestrationMode) &&
isEffectiveOrchestrationSource(value.orchestrationSource) &&
isToolMode(value.toolMode) &&
(value.agentSwarmAuthorization === undefined ||
isAgentSwarmAuthorizationSource(value.agentSwarmAuthorization)) &&
(value.workspaceIdentity === undefined || isNonEmptyString(value.workspaceIdentity))
);
}
function isRuntimeInvocationRootAuthority(value: unknown): value is RuntimeInvocationRootAuthority {
if (!isRecord(value)) return false;
switch (value.kind) {
case 'user':
return hasExactShape(value, INVOCATION_ROOT_SHAPES.user);
case 'context_compact':
return hasExactShape(value, INVOCATION_ROOT_SHAPES.context_compact);
case 'scheduled_task':
return (
hasExactShape(value, INVOCATION_ROOT_SHAPES.scheduled_task) &&
isNonEmptyString(value.scheduledTaskId)
);
case 'goal':
return hasExactShape(value, INVOCATION_ROOT_SHAPES.goal) && isNonEmptyString(value.goalId);
case 'agent_graph_supervisor_wake':
return (
hasExactShape(value, INVOCATION_ROOT_SHAPES.agent_graph_supervisor_wake) &&
isNonEmptyString(value.wakeId) &&
isNonEmptyString(value.attemptId)
);
case 'legacy_automation':
return (
hasExactShape(value, INVOCATION_ROOT_SHAPES.legacy_automation) &&
isNonEmptyString(value.legacyAutomationId)
);
default:
return false;
}
}
function isRuntimeInvocationOpenSource(value: unknown): value is RuntimeInvocationOpenSource {
if (!isRecord(value)) return false;
if (value.kind === 'fresh') return hasExactShape(value, INVOCATION_FRESH_SOURCE_SHAPE);
return (
((value.kind === 'continuation' &&
hasExactShape(value, INVOCATION_CONTINUATION_SOURCE_SHAPE)) ||
(value.kind === 'handoff' &&
hasExactShape(value, INVOCATION_HANDOFF_SOURCE_SHAPE) &&
isNonEmptyString(value.rootRunId) &&
isNonEmptyString(value.claimId) &&
isSha256Digest(value.boundaryDigest) &&
(value.sourceRuntimeEventHighWater as number) > 0)) &&
isNonEmptyString(value.sourceInvocationId) &&
isNonEmptyString(value.sourceRunId) &&
isNonEmptyString(value.sourceTurnId) &&
Number.isSafeInteger(value.sourceRuntimeEventHighWater) &&
(value.sourceRuntimeEventHighWater as number) >= 0 &&
(value.claimId === undefined || isNonEmptyString(value.claimId)) &&
(value.boundaryDigest === undefined || isSha256Digest(value.boundaryDigest))
);
}
function isRuntimeInvocationLineage(value: unknown): value is RuntimeInvocationLineage {
return (
isRecord(value) &&
hasExactShape(value, INVOCATION_LINEAGE_SHAPE) &&
Object.keys(value).length > 0 &&
[
value.parentRunId,
value.resumedFromRunId,
value.retriedFromRunId,
value.parentTurnId,
value.parentSessionId,
value.retriedFromTurnId,
value.regeneratedFromTurnId,
value.branchOfTurnId,
value.agentId,
value.agentName,
].every(isOptionalString)
);
}
function decodesDurableToolResultProjection(value: unknown): boolean {
try {
decodeDurableToolResultProjection(value);
return true;
} catch {
return false;
}
}
function isTurnOrigin(value: unknown): value is TurnOrigin {
return decodeTurnOrigin(value) !== undefined;
}
function isRuntimeEventActions(value: unknown): value is RuntimeEventActions {
if (!isRecord(value) || !hasExactShape(value, RUNTIME_ACTIONS_SHAPE)) return false;
if (
value.permissionClosureAccepted !== undefined &&
Object.keys(value).some((key) => key !== 'permissionClosureAccepted')
) {
return false;
}
return (
(value.handoffPause === undefined || isRuntimeHandoffPause(value.handoffPause)) &&
(value.coordination === undefined || isWorkHubActionReceipt(value.coordination)) &&
(value.stateDelta === undefined || isRecord(value.stateDelta)) &&
(value.artifactDelta === undefined ||
(isRecord(value.artifactDelta) &&
Object.values(value.artifactDelta).every(
(item) => typeof item === 'string' || typeof item === 'boolean' || isFiniteNumber(item),
))) &&
(value.permissionRequest === undefined ||
isPermissionRequestPayload(value.permissionRequest)) &&
(value.permissionDecision === undefined ||
isRuntimeEventPermissionDecision(value.permissionDecision)) &&
(value.permissionAnswerAccepted === undefined ||
isRuntimeEventAnswerAcceptedIdentity(value.permissionAnswerAccepted)) &&
(value.permissionClosureAccepted === undefined ||
isRuntimeEventPermissionClosureAccepted(value.permissionClosureAccepted)) &&
(value.userQuestionRequest === undefined || isUserQuestionRequest(value.userQuestionRequest)) &&
(value.userQuestionAnswerAccepted === undefined ||
isRuntimeEventAnswerAcceptedIdentity(value.userQuestionAnswerAccepted)) &&
(value.formRequest === undefined || isRuntimeEventFormRequest(value.formRequest)) &&
(value.formAnswerAccepted === undefined ||
isRuntimeEventAnswerAcceptedIdentity(value.formAnswerAccepted)) &&
isOptionalString(value.transferToAgent) &&
(value.endInvocation === undefined || typeof value.endInvocation === 'boolean') &&
(value.tokenUsage === undefined || isRuntimeTokenUsage(value.tokenUsage)) &&
(value.toolDispatch === undefined || isRuntimeToolDispatch(value.toolDispatch)) &&
(value.toolRecovery === undefined || isToolRecoveryFactEnvelope(value.toolRecovery)) &&
(value.runtimeProtocol === undefined || isRuntimeProtocolMarker(value.runtimeProtocol)) &&
(value.continuationStart === undefined ||
isRuntimeContinuationStart(value.continuationStart)) &&
(value.workspaceFact === undefined ||
isRuntimeEventWorkspaceFactEnvelope(value.workspaceFact)) &&
(value.managedMutationTerminal === undefined ||
isRuntimeManagedMutationTerminal(value.managedMutationTerminal))
);
}
function isRuntimeEventFormRequest(value: unknown): value is RuntimeEventFormRequest {
if (!isRecord(value)) return false;
const { requestId, ...request } = value;
if (
typeof requestId !== 'string' ||
requestId.length === 0 ||
UTF8.encode(requestId).byteLength > INTERACTION_ID_MAX_BYTES
) {
return false;
}
try {
decodeInteractionRequest({ kind: 'form', ...request });
return true;
} catch {
return false;
}
}
function isRuntimeManagedMutationTerminal(
value: unknown,
): value is RuntimeEventManagedMutationTerminalV1 {
return (
isRecord(value) &&
hasExactShape(value, RUNTIME_MANAGED_MUTATION_TERMINAL_SHAPE) &&
value.protocol === 'managed_mutation_terminal_v1' &&
typeof value.operationId === 'string' &&
typeof value.dispatchEventId === 'string' &&
typeof value.workspaceInstanceId === 'string' &&
/^instance_[0-9a-f]{32}$/u.test(value.workspaceInstanceId) &&
(value.terminalKind === 'no_workspace_change' ||
value.terminalKind === 'operation_failed_no_effect')
);
}
function isRuntimeEventPermissionDecision(value: unknown): value is RuntimeEventPermissionDecision {
return (
isRecord(value) &&
hasExactShape(value, RUNTIME_PERMISSION_DECISION_SHAPE) &&
typeof value.requestId === 'string' &&
isPermissionDecisionFields(value) &&
(value.toolName === undefined ||
(typeof value.toolName === 'string' &&
value.toolName.length > 0 &&
UTF8.encode(value.toolName).byteLength <= INTERACTION_TOOL_NAME_MAX_BYTES)) &&
isOptionalString(value.hint)
);
}
function isRuntimeEventAnswerAcceptedIdentity(
value: unknown,
): value is RuntimeEventAnswerAcceptedIdentity {
return (
isRecord(value) &&
hasExactShape(value, ANSWER_ACCEPTED_IDENTITY_SHAPE) &&
typeof value.requestId === 'string' &&
value.requestId.length > 0 &&
UTF8.encode(value.requestId).byteLength <= INTERACTION_ID_MAX_BYTES
);
}
function isRuntimeEventPermissionClosureAccepted(
value: unknown,
): value is RuntimeEventPermissionClosureAccepted {
return (
isRecord(value) &&
hasExactShape(value, PERMISSION_CLOSURE_ACCEPTED_SHAPE) &&
typeof value.requestId === 'string' &&
value.requestId.length > 0 &&
UTF8.encode(value.requestId).byteLength <= INTERACTION_ID_MAX_BYTES &&
value.reason === 'timed_out'
);
}
function isRuntimeToolDispatch(value: unknown): value is RuntimeEventToolDispatch {
return (
isRecord(value) &&
hasExactShape(value, RUNTIME_TOOL_DISPATCH_SHAPE) &&
value.protocol === TOOL_BOUNDARY_PROTOCOL_V1 &&
(value.resultProjectionVersion === undefined || value.resultProjectionVersion === 1) &&
typeof value.operationId === 'string' &&
typeof value.providerToolCallId === 'string' &&
typeof value.toolName === 'string' &&
typeof value.canonicalArgsHash === 'string' &&
(value.recoveryMode === 'replay_safe' ||
value.recoveryMode === 'idempotent' ||
value.recoveryMode === 'reconcile' ||
value.recoveryMode === 'reattach' ||
value.recoveryMode === 'outcome_unknown' ||
value.recoveryMode === 'never_auto_retry') &&
(value.managedMutation === undefined ||
isRuntimeManagedWorkspaceMutation(value.managedMutation))
);
}
function isRuntimeManagedWorkspaceMutation(
value: unknown,
): value is RuntimeEventManagedWorkspaceMutationV2 {
if (
!isRecord(value) ||
!hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_MUTATION_SHAPE) ||
value.protocol !== 'managed_mutation_v2' ||
typeof value.repositoryId !== 'string' ||
!/^repository_[0-9a-f]{32}$/u.test(value.repositoryId) ||
typeof value.workspaceId !== 'string' ||
!/^workspace_[0-9a-f]{32}$/u.test(value.workspaceId) ||
typeof value.workspaceEpochId !== 'string' ||
!/^epoch_[0-9a-f]{32}$/u.test(value.workspaceEpochId) ||
typeof value.workspaceInstanceId !== 'string' ||
!/^instance_[0-9a-f]{32}$/u.test(value.workspaceInstanceId) ||
value.objectFormat !== 'sha1' ||
typeof value.baseWorkspaceVersionId !== 'string' ||
!/^version_[0-9a-f]{32}$/u.test(value.baseWorkspaceVersionId) ||
typeof value.baseAcceptedEventId !== 'string' ||
!/^[A-Za-z0-9_-]{1,128}$/u.test(value.baseAcceptedEventId) ||
typeof value.baseHeadRevision !== 'number' ||
!Number.isSafeInteger(value.baseHeadRevision) ||
value.baseHeadRevision < 1 ||
typeof value.baseCommitOid !== 'string' ||
typeof value.baseTreeOid !== 'string' ||
value.pathPolicyVersion !== 3 ||
value.executionProfileDigest !== MANAGED_MUTATION_EXECUTION_PROFILE_V1_DIGEST ||
!isCanonicalManagedMutationPathV1(value.expectedPath)
) {
return false;
}
const oidPattern = /^[0-9a-f]{40}$/u;
if (!oidPattern.test(value.baseCommitOid) || !oidPattern.test(value.baseTreeOid)) return false;
return true;
}
/** Platform-independent canonical Git path syntax used by durable mutation facts. */
export function isCanonicalManagedMutationPathV1(path: unknown): path is string {
if (
typeof path !== 'string' ||
path.length === 0 ||
path.length > 4096 ||
path.includes('\\') ||
path.includes('\0') ||
path.includes(':') ||
path.startsWith('/') ||
path.endsWith('/')
) {
return false;
}
const segments = path.split('/');
if (segments.some((segment) => segment === '' || segment === '.' || segment === '..')) {
return false;
}
const firstSegment = segments[0]!.toLowerCase();
return firstSegment !== '.git' && firstSegment !== 'node_modules';
}
function isRuntimeProtocolMarker(value: unknown): value is RuntimeEventProtocolMarker {
return (
isRecord(value) &&
hasExactShape(value, RUNTIME_PROTOCOL_MARKER_SHAPE) &&
value.toolBoundary === TOOL_BOUNDARY_PROTOCOL_V1
);
}
function isRuntimeContinuationStart(value: unknown): value is RuntimeEventContinuationStartV2 {
return (
isRecord(value) &&
hasExactShape(value, RUNTIME_CONTINUATION_START_SHAPE) &&
value.protocol === 'continuation_start_v2' &&
(value.provenance === 'runtime_admission' || value.provenance === 'claim_repair') &&
isNonEmptyString(value.claimId) &&
isSha256Digest(value.boundaryDigest) &&
isRecord(value.immediateSource) &&
hasExactShape(value.immediateSource, RUNTIME_CONTINUATION_SOURCE_SHAPE) &&
isNonEmptyString(value.immediateSource.sessionId) &&
isNonEmptyString(value.immediateSource.invocationId) &&
isNonEmptyString(value.immediateSource.runId) &&
isNonEmptyString(value.immediateSource.turnId) &&
Number.isSafeInteger(value.immediateSource.highWater) &&
(value.immediateSource.highWater as number) > 0 &&
isSha256Digest(value.immediateSource.prefixDigest) &&
isSha256Digest(value.replayManifestDigest) &&
(value.providerProjectionVersion === 1 || value.providerProjectionVersion === 2) &&
isSha256Digest(value.providerReplayDigest)
);
}
function isNonEmptyString(value: unknown): value is string {
return typeof value === 'string' && value.length > 0;
}
function isSha256Digest(value: unknown): value is `sha256:${string}` {
return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);
}
function isRuntimeTokenUsage(value: unknown): value is RuntimeEventTokenUsage {
return (
isRecord(value) && hasExactShape(value, RUNTIME_TOKEN_USAGE_SHAPE) && isTokenUsageFields(value)
);
}
function isRuntimeEventRefs(value: unknown): value is RuntimeEventRefs {
return (
isRecord(value) &&
hasExactShape(value, RUNTIME_REFS_SHAPE) &&
[
value.storedMessageId,
value.traceEventId,
value.toolCallId,
value.providerEventId,
value.providerRequestTraceId,
value.artifactId,
value.operationId,
value.parentToolCallId,
value.parentOperationId,
value.stepId,
value.sourceInvocationId,
value.sourceRunId,
value.sourceTurnId,
].every(isOptionalString) &&
(value.sourceMessageDigest === undefined || isSha256Digest(value.sourceMessageDigest)) &&
(value.sourceRuntimeEventHighWater === undefined ||
(typeof value.sourceRuntimeEventHighWater === 'number' &&
Number.isSafeInteger(value.sourceRuntimeEventHighWater) &&
value.sourceRuntimeEventHighWater >= 0))
);
}
// ============================================================================
// Pure helpers
// ============================================================================
/**
* True if the event marks the end of its invocation — either by asserting
* a terminal `status` or by carrying `actions.endInvocation === true`.
* A single terminal event SHOULD carry exactly one of these signals.
*/
export function isTerminalRuntimeEvent(event: RuntimeEvent): boolean {
if (event.status !== undefined && isTerminalRuntimeEventStatus(event.status)) return true;
return event.actions?.endInvocation === true;
}
/** True for transient streaming/progress chunks that a later event supersedes. */
export function isPartialRuntimeEvent(event: RuntimeEvent): boolean {
return event.partial === true;
}
/**
* True if the event carries content whose kind is eligible for model
* history projection: text, thinking, function_call, or function_response.
* A user-authored text event with structured context (quotes or attachments)
* is model-visible even when the inline text is empty — the structured part
* is what carries the turn (#4804). Error-only content and pure action/refs
* events are NOT model-visible.
*
* This is a content-kind check only. Callers still apply `partial`
* filtering (partial chunks are never replayed into the next model call).
*/
export function runtimeEventHasModelVisibleContent(event: RuntimeEvent): boolean {
if (event.modelVisibility === 'hidden') return false;
const content = event.content;
if (!content) return false;
switch (content.kind) {
case 'text':
return hasMeaningfulMessageContent(content);
case 'thinking':
case 'function_call':
case 'function_response':
return true;
case 'error':
case 'system_note':
case 'invocation_opened':
return false;
}
}
let __runtimeEventSeq = 0;
/**
* Best-effort unique id for runtime events. Monotonic within a process so
* two ids never collide even when generated in the same millisecond.
*
* Runtime/runner layers MAY replace this with a stronger uuid source; it
* exists here only so early adopters and tests have a default. Tests that
* need deterministic ids SHOULD pass literal strings rather than rely on
* this helper's exact output.
*/
export function createRuntimeEventId(prefix = 'rt-event'): string {
__runtimeEventSeq += 1;
return `${prefix}_${Date.now().toString(36)}_${__runtimeEventSeq.toString(36)}`;
}