blob: 166655fb6bb4897e95fd151e72502a2bc24042b9 [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.
*/
import {
AgentGraphClientOperationError,
type AgentGraphClientChangedEvent,
type AgentGraphCoordinator,
} from '@maka/runtime/stream-graph-coordinator';
import {
encodeAgentGraphTerminalCursor,
type AgentGraphClientOperator as RuntimeAgentGraphClientOperator,
type AgentGraphClientSnapshot as RuntimeAgentGraphClientSnapshot,
type AgentGraphOperatorInspection as RuntimeAgentGraphOperatorInspection,
} from '@maka/runtime/stream-graph-read-model';
import { isSessionNotFoundError } from '@maka/storage/execution-stores';
import {
AGENT_GRAPH_MAX_ACTIVITY,
AGENT_GRAPH_MAX_CLAIMS,
AGENT_GRAPH_MAX_CONTROL_DECISIONS,
AGENT_GRAPH_MAX_EDGES,
AGENT_GRAPH_MAX_INPUT_ROUTE_OPERATORS,
AGENT_GRAPH_MAX_INSPECTION_ACTIVATIONS,
AGENT_GRAPH_MAX_INSPECTION_CLAIMS,
AGENT_GRAPH_MAX_INSPECTION_EDGES,
AGENT_GRAPH_MAX_INSPECTION_RECORDS,
AGENT_GRAPH_MAX_INSPECTION_WORK,
AGENT_GRAPH_MAX_OPERATORS,
AGENT_GRAPH_MAX_OPERATOR_READINESS,
AGENT_GRAPH_MAX_OPERATOR_REFS,
AGENT_GRAPH_MAX_READINESS_WAITS,
AGENT_GRAPH_MAX_RECONCILIATION_FAILURES,
AGENT_GRAPH_MAX_STOPPED_TARGETS,
AGENT_GRAPH_MAX_TERMINAL_ACTIVITY,
AGENT_GRAPH_MAX_WORK,
AGENT_GRAPH_EPOCH_PAGE_SIZE,
AGENT_GRAPH_RESULT_MAX_BYTES,
type AgentGraphClientOperator,
type AgentGraphClientSnapshot,
type AgentGraphEpochListInput,
type AgentGraphOperatorInspection,
type AgentGraphOperatorQueryInput,
type AgentGraphQueryInput,
type AgentGraphStopInput,
type OperationOutcome,
} from '../protocol/index.js';
import type { AgentGraphOperationHandlerMap } from './operation-dispatcher.js';
import type { SessionContinuityCoordinator } from './session-continuity-coordinator.js';
type AgentGraphAuthority = Pick<
AgentGraphCoordinator,
| 'currentGraphId'
| 'getGraphSnapshot'
| 'getSnapshot'
| 'inspectGraphOperator'
| 'inspectOperator'
| 'listGraphEpochPage'
| 'subscribeAll'
>;
type GraphContinuity = Pick<SessionContinuityCoordinator, 'enqueueAgentGraphChanged'>;
type Mutable<T> = {
-readonly [K in keyof T]: T[K] extends readonly (infer Item)[]
? Mutable<Item>[]
: T[K] extends object
? Mutable<T[K]>
: T[K];
};
type RootOperationFailureCode = AgentGraphClientOperationError['code'];
type GraphQueryFailureCode =
| Exclude<RootOperationFailureCode, 'session_archived'>
| 'persistence_failed';
/** Client-facing Runtime Host adapter over the durable Agent Graph read model. */
export class HostAgentGraphCoordinator {
readonly handlers: AgentGraphOperationHandlerMap = {
'agent.graph.epochs.query': (input) => this.#queryEpochs(input),
'agent.graph.query': (input) => this.#query(input),
'agent.graph.operator.query': (input) => this.#queryOperator(input),
'agent.graph.stop': (input) => this.#stop(input),
};
readonly #authority: AgentGraphAuthority;
readonly #stopExecution: (rootSessionId: string, expectedGraphId?: string) => Promise<void>;
#unsubscribe: (() => void) | undefined;
constructor(options: {
authority: AgentGraphAuthority;
continuity: GraphContinuity;
stopExecution: (rootSessionId: string, expectedGraphId?: string) => Promise<void>;
}) {
this.#authority = options.authority;
this.#stopExecution = options.stopExecution;
this.#unsubscribe = options.authority.subscribeAll((event) =>
options.continuity.enqueueAgentGraphChanged(projectChangedEvent(event)),
);
}
close(): void {
this.#unsubscribe?.();
this.#unsubscribe = undefined;
}
async #query(input: AgentGraphQueryInput): Promise<OperationOutcome<'agent.graph.query'>> {
try {
const options = input.terminalCursor ? { terminalCursor: input.terminalCursor } : {};
const snapshot = input.graphId
? await this.#authority.getGraphSnapshot(input.rootSessionId, input.graphId, options)
: await this.#authority.getSnapshot(input.rootSessionId, options);
return { ok: true, result: projectSnapshot(snapshot) };
} catch (error) {
return graphQueryFailure(error);
}
}
async #queryOperator(
input: AgentGraphOperatorQueryInput,
): Promise<OperationOutcome<'agent.graph.operator.query'>> {
try {
const inspection = input.graphId
? await this.#authority.inspectGraphOperator(
input.rootSessionId,
input.graphId,
input.operatorId,
)
: await this.#authority.inspectOperator(input.rootSessionId, input.operatorId);
return { ok: true, result: projectInspection(inspection) };
} catch (error) {
return graphQueryFailure(error);
}
}
async #queryEpochs(
input: AgentGraphEpochListInput,
): Promise<OperationOutcome<'agent.graph.epochs.query'>> {
try {
const page = await this.#authority.listGraphEpochPage(input.rootSessionId, {
...(input.beforeEpoch === undefined ? {} : { beforeEpoch: input.beforeEpoch }),
limit: AGENT_GRAPH_EPOCH_PAGE_SIZE,
});
if (input.beforeEpoch !== undefined && input.beforeEpoch > page.currentEpoch) {
throw new AgentGraphClientOperationError(
'invalid_request',
`Agent graph epoch cursor ${input.beforeEpoch} is ahead of current epoch ${page.currentEpoch}`,
);
}
return {
ok: true,
result: {
rootSessionId: input.rootSessionId,
epochs: page.epochs.map((binding) => ({
epoch: binding.epoch,
graphId: binding.graphId,
createdAt: binding.createdAt,
current: binding.epoch === page.currentEpoch,
})),
nextBeforeEpoch: page.nextBeforeEpoch,
},
};
} catch (error) {
return graphQueryFailure(error);
}
}
async #stop(input: AgentGraphStopInput): Promise<OperationOutcome<'agent.graph.stop'>> {
try {
await this.#stopExecution(input.rootSessionId, input.expectedGraphId);
const graphId =
input.expectedGraphId ?? (await this.#authority.currentGraphId(input.rootSessionId));
return {
ok: true,
result: {
rootSessionId: input.rootSessionId,
graphId,
},
};
} catch (error) {
if (error instanceof AgentGraphClientOperationError || isSessionNotFoundError(error)) {
const code = error instanceof AgentGraphClientOperationError ? error.code : 'not_found';
if (code === 'invalid_request') {
return failure('internal_failure', 'Agent graph stop failed');
}
return failure(code, error instanceof Error ? error.message : 'Session was not found');
}
return failure('internal_failure', 'Agent graph stop failed');
}
}
}
export function projectAgentGraphClientSnapshot(
snapshot: RuntimeAgentGraphClientSnapshot,
): AgentGraphClientSnapshot {
return projectSnapshot(snapshot);
}
function projectSnapshot(snapshot: RuntimeAgentGraphClientSnapshot): AgentGraphClientSnapshot {
const operators = snapshot.operators.slice(0, AGENT_GRAPH_MAX_OPERATORS).map(projectOperator);
const visibleOperatorIds = new Set(operators.map((operator) => operator.operatorId));
const candidateEdges = snapshot.edges.filter(
(edge) =>
visibleOperatorIds.has(edge.fromOperatorId) && visibleOperatorIds.has(edge.toOperatorId),
);
const projected: Mutable<AgentGraphClientSnapshot> = {
schemaVersion: 1,
rootSessionId: snapshot.rootSessionId,
graphId: snapshot.graphId,
orchestrationMode: snapshot.orchestrationMode,
snapshotVersion: requireFingerprint(snapshot.snapshotVersion),
status: snapshot.status,
scheduleRevision: snapshot.scheduleRevision,
topologyFingerprint: requireFingerprint(snapshot.topologyFingerprint),
closed: snapshot.closed,
...(snapshot.latestEventTime === undefined
? {}
: { latestEventTime: snapshot.latestEventTime }),
operators,
edges: candidateEdges.slice(0, AGENT_GRAPH_MAX_EDGES).map(projectEdge),
work: snapshot.work.slice(0, AGENT_GRAPH_MAX_WORK).map(projectWork),
reconciliationFailures: snapshot.reconciliationFailures
.slice(-AGENT_GRAPH_MAX_RECONCILIATION_FAILURES)
.map((failure) => ({ ...failure })),
stoppedTargets: snapshot.stoppedTargets
.slice(-AGENT_GRAPH_MAX_STOPPED_TARGETS)
.map(projectStoppedTarget),
...(snapshot.finish ? { finish: projectFinish(snapshot.finish) } : {}),
claims: snapshot.claims.slice(-AGENT_GRAPH_MAX_CLAIMS).map(projectClaim),
recentControlDecisions: snapshot.recentControlDecisions
.slice(-AGENT_GRAPH_MAX_CONTROL_DECISIONS)
.map(projectControlDecision),
recentActivity: snapshot.recentActivity.slice(-AGENT_GRAPH_MAX_ACTIVITY).map(projectActivity),
terminalHistory: {
records: snapshot.terminalHistory.records
.slice(0, AGENT_GRAPH_MAX_TERMINAL_ACTIVITY)
.map(projectActivity),
...(snapshot.terminalHistory.nextCursor
? { nextCursor: snapshot.terminalHistory.nextCursor }
: {}),
},
omitted: {
operators: snapshot.omitted.operators + snapshot.operators.length - operators.length,
edges:
snapshot.omitted.edges +
snapshot.edges.length -
Math.min(candidateEdges.length, AGENT_GRAPH_MAX_EDGES),
work: snapshot.omitted.work + Math.max(0, snapshot.work.length - AGENT_GRAPH_MAX_WORK),
reconciliationFailures:
snapshot.omitted.reconciliationFailures +
Math.max(
0,
snapshot.reconciliationFailures.length - AGENT_GRAPH_MAX_RECONCILIATION_FAILURES,
),
stoppedTargets:
snapshot.omitted.stoppedTargets +
Math.max(0, snapshot.stoppedTargets.length - AGENT_GRAPH_MAX_STOPPED_TARGETS),
claims:
snapshot.omitted.claims + Math.max(0, snapshot.claims.length - AGENT_GRAPH_MAX_CLAIMS),
controlDecisions:
snapshot.omitted.controlDecisions +
Math.max(0, snapshot.recentControlDecisions.length - AGENT_GRAPH_MAX_CONTROL_DECISIONS),
recentActivity:
snapshot.omitted.recentActivity +
Math.max(0, snapshot.recentActivity.length - AGENT_GRAPH_MAX_ACTIVITY),
},
};
if (snapshot.terminalHistory.records.length > projected.terminalHistory.records.length) {
updateTerminalCursor(projected);
}
fitSnapshot(projected);
return projected;
}
function projectInspection(
inspection: RuntimeAgentGraphOperatorInspection,
): AgentGraphOperatorInspection {
const projected: Mutable<AgentGraphOperatorInspection> = {
schemaVersion: 1,
rootSessionId: inspection.rootSessionId,
graphId: inspection.graphId,
snapshotVersion: requireFingerprint(inspection.snapshotVersion),
operator: projectOperator(inspection.operator),
inboundEdges: inspection.inboundEdges.slice(-AGENT_GRAPH_MAX_INSPECTION_EDGES).map(projectEdge),
outboundEdges: inspection.outboundEdges
.slice(-AGENT_GRAPH_MAX_INSPECTION_EDGES)
.map(projectEdge),
work: inspection.work.slice(-AGENT_GRAPH_MAX_INSPECTION_WORK).map(projectWork),
claims: inspection.claims.slice(-AGENT_GRAPH_MAX_INSPECTION_CLAIMS).map(projectClaim),
activations: inspection.activations
.slice(-AGENT_GRAPH_MAX_INSPECTION_ACTIVATIONS)
.map(projectActivation),
recentRecords: inspection.recentRecords
.slice(-AGENT_GRAPH_MAX_INSPECTION_RECORDS)
.map(projectActivity),
omitted: {
inboundEdges:
inspection.omitted.inboundEdges +
Math.max(0, inspection.inboundEdges.length - AGENT_GRAPH_MAX_INSPECTION_EDGES),
outboundEdges:
inspection.omitted.outboundEdges +
Math.max(0, inspection.outboundEdges.length - AGENT_GRAPH_MAX_INSPECTION_EDGES),
work:
inspection.omitted.work +
Math.max(0, inspection.work.length - AGENT_GRAPH_MAX_INSPECTION_WORK),
claims:
inspection.omitted.claims +
Math.max(0, inspection.claims.length - AGENT_GRAPH_MAX_INSPECTION_CLAIMS),
activations:
inspection.omitted.activations +
Math.max(0, inspection.activations.length - AGENT_GRAPH_MAX_INSPECTION_ACTIVATIONS),
records:
inspection.omitted.records +
Math.max(0, inspection.recentRecords.length - AGENT_GRAPH_MAX_INSPECTION_RECORDS),
},
};
fitInspection(projected);
return projected;
}
function projectOperator(
operator: RuntimeAgentGraphClientOperator,
): Mutable<AgentGraphClientOperator> {
const readiness = operator.readiness.slice(0, AGENT_GRAPH_MAX_OPERATOR_READINESS).map((entry) => {
const eligibleWaits = entry.waitingFor.filter(
(wait) =>
wait.kind !== 'input_route' ||
wait.upstreamOperatorIds.length <= AGENT_GRAPH_MAX_INPUT_ROUTE_OPERATORS,
);
const waitingFor = eligibleWaits
.slice(0, AGENT_GRAPH_MAX_READINESS_WAITS)
.map(projectReadinessWait);
return {
readinessId: entry.readinessId,
status: entry.status,
waitingFor,
omittedWaitingFor: entry.omittedWaitingFor + entry.waitingFor.length - waitingFor.length,
};
});
const extraOmittedWaits = operator.readiness
.slice(0, AGENT_GRAPH_MAX_OPERATOR_READINESS)
.reduce((count, entry, index) => {
const visible = readiness[index]?.waitingFor.length ?? 0;
return count + entry.waitingFor.length - visible;
}, 0);
const omittedReadinessWaits = operator.readiness
.slice(AGENT_GRAPH_MAX_OPERATOR_READINESS)
.reduce((count, entry) => count + entry.waitingFor.length, 0);
const inboundEdgeIds = operator.inboundEdgeIds.slice(-AGENT_GRAPH_MAX_OPERATOR_REFS);
const outboundEdgeIds = operator.outboundEdgeIds.slice(-AGENT_GRAPH_MAX_OPERATOR_REFS);
const scheduledWorkIds = operator.scheduledWorkIds.slice(-AGENT_GRAPH_MAX_OPERATOR_REFS);
return {
operatorId: operator.operatorId,
childSessionId: operator.childSessionId,
provisionId: operator.provisionId,
agentId: operator.agentId,
provisionedAt: operator.provisionedAt,
status: operator.status,
inboundEdgeIds,
outboundEdgeIds,
scheduledWorkIds,
readiness,
omitted: {
inboundEdgeIds:
operator.omitted.inboundEdgeIds + operator.inboundEdgeIds.length - inboundEdgeIds.length,
outboundEdgeIds:
operator.omitted.outboundEdgeIds + operator.outboundEdgeIds.length - outboundEdgeIds.length,
scheduledWorkIds:
operator.omitted.scheduledWorkIds +
operator.scheduledWorkIds.length -
scheduledWorkIds.length,
readiness: operator.omitted.readiness + operator.readiness.length - readiness.length,
readinessWaits: operator.omitted.readinessWaits + extraOmittedWaits + omittedReadinessWaits,
},
...(operator.currentActivation
? {
currentActivation: {
activationId: operator.currentActivation.activationId,
status: operator.currentActivation.status,
recordCount: operator.currentActivation.recordCount,
firstEventTime: operator.currentActivation.firstEventTime,
lastEventTime: operator.currentActivation.lastEventTime,
...(operator.currentActivation.terminalRecordId
? { terminalRecordId: operator.currentActivation.terminalRecordId }
: {}),
run: projectRunRef(operator.currentActivation.run),
},
}
: {}),
};
}
function projectReadinessWait(
wait: RuntimeAgentGraphClientOperator['readiness'][number]['waitingFor'][number],
): Mutable<AgentGraphClientOperator['readiness'][number]['waitingFor'][number]> {
return wait.kind === 'input_route'
? { kind: wait.kind, upstreamOperatorIds: [...wait.upstreamOperatorIds] }
: {
kind: wait.kind,
operatorId: wait.operatorId,
activationId: wait.activationId,
};
}
function projectEdge(
edge: RuntimeAgentGraphClientSnapshot['edges'][number],
): Mutable<AgentGraphClientSnapshot['edges'][number]> {
return {
edgeId: edge.edgeId,
fromOperatorId: edge.fromOperatorId,
toOperatorId: edge.toOperatorId,
};
}
function projectWork(
work: RuntimeAgentGraphClientSnapshot['work'][number],
): Mutable<AgentGraphClientSnapshot['work'][number]> {
return {
workId: work.workId,
target:
work.target.kind === 'agent'
? { kind: work.target.kind, agentId: work.target.agentId }
: work.target.kind === 'preset'
? { kind: work.target.kind, presetId: work.target.presetId }
: { kind: work.target.kind, operatorId: work.target.operatorId },
inputIds: [...work.inputIds],
...(work.selectedResultInputs
? { selectedResultInputs: work.selectedResultInputs.map((input) => ({ ...input })) }
: {}),
...(work.replaces ? { replaces: work.replaces } : {}),
status: work.status,
instructionPreview: work.instructionPreview,
instructionTruncated: work.instructionTruncated,
revision: work.revision,
committedAt: work.committedAt,
};
}
function projectStoppedTarget(
target: RuntimeAgentGraphClientSnapshot['stoppedTargets'][number],
): Mutable<AgentGraphClientSnapshot['stoppedTargets'][number]> {
return {
targetId: target.targetId,
reason: target.reason,
revision: target.revision,
committedAt: target.committedAt,
};
}
function projectFinish(
finish: NonNullable<RuntimeAgentGraphClientSnapshot['finish']>,
): Mutable<NonNullable<AgentGraphClientSnapshot['finish']>> {
return {
resultIds: [...finish.resultIds],
reason: finish.reason,
revision: finish.revision,
committedAt: finish.committedAt,
};
}
function projectClaim(
claim: RuntimeAgentGraphClientSnapshot['claims'][number],
): Mutable<AgentGraphClientSnapshot['claims'][number]> {
return {
claimId: claim.claimId,
intentId: claim.intentId,
operatorId: claim.operatorId,
childSessionId: claim.childSessionId,
run: projectRunRef(claim.run),
admissionState: claim.admissionState,
claimedAt: claim.claimedAt,
};
}
function projectControlDecision(
decision: RuntimeAgentGraphClientSnapshot['recentControlDecisions'][number],
): Mutable<AgentGraphClientSnapshot['recentControlDecisions'][number]> {
return {
updateId: decision.updateId,
revision: decision.revision,
committedAt: decision.committedAt,
source: {
sessionId: decision.source.sessionId,
agentRunId: decision.source.agentRunId,
turnId: decision.source.turnId,
toolCallId: decision.source.toolCallId,
},
addedWorkIds: [...decision.addedWorkIds],
stoppedTargetIds: [...decision.stoppedTargetIds],
selectedResultIds: [...decision.selectedResultIds],
};
}
function projectActivity(
activity: RuntimeAgentGraphClientSnapshot['recentActivity'][number],
): Mutable<AgentGraphClientSnapshot['recentActivity'][number]> {
return {
recordId: activity.recordId,
operatorId: activity.operatorId,
activationId: activity.activationId,
eventTime: activity.eventTime,
facets: [...activity.facets],
signals: activity.signals.map((signal) =>
signal.kind === 'attention'
? { kind: signal.kind, reason: signal.reason }
: { kind: signal.kind, status: signal.status },
),
run: projectRunRef(activity.run),
};
}
function projectActivation(
activation: RuntimeAgentGraphOperatorInspection['activations'][number],
): Mutable<AgentGraphOperatorInspection['activations'][number]> {
return {
activationId: activation.activationId,
status: activation.status,
recordCount: activation.recordCount,
firstEventTime: activation.firstEventTime,
lastEventTime: activation.lastEventTime,
lastRecordId: activation.lastRecordId,
...(activation.terminalRecordId ? { terminalRecordId: activation.terminalRecordId } : {}),
run: projectRunRef(activation.run),
};
}
function projectRunRef(
run: RuntimeAgentGraphClientSnapshot['recentActivity'][number]['run'],
): Mutable<AgentGraphClientSnapshot['recentActivity'][number]['run']> {
return {
sessionId: run.sessionId,
agentRunId: run.agentRunId,
...(run.turnId ? { turnId: run.turnId } : {}),
};
}
function fitSnapshot(snapshot: Mutable<AgentGraphClientSnapshot>): void {
while (encodedBytes(snapshot) > AGENT_GRAPH_RESULT_MAX_BYTES) {
if (snapshot.terminalHistory.records.length > 1) {
snapshot.terminalHistory.records.pop();
updateTerminalCursor(snapshot);
continue;
}
if (snapshot.recentActivity.shift()) {
snapshot.omitted.recentActivity += 1;
continue;
}
if (snapshot.recentControlDecisions.shift()) {
snapshot.omitted.controlDecisions += 1;
continue;
}
if (snapshot.stoppedTargets.shift()) {
snapshot.omitted.stoppedTargets += 1;
continue;
}
if (snapshot.reconciliationFailures.shift()) {
snapshot.omitted.reconciliationFailures += 1;
continue;
}
if (snapshot.claims.shift()) {
snapshot.omitted.claims += 1;
continue;
}
const terminalWorkIndex = snapshot.work.findIndex((entry) => entry.status !== 'requested');
const removedWork = snapshot.work.splice(
terminalWorkIndex < 0 ? snapshot.work.length - 1 : terminalWorkIndex,
1,
)[0];
if (removedWork) {
snapshot.omitted.work += 1;
continue;
}
if (snapshot.edges.pop()) {
snapshot.omitted.edges += 1;
continue;
}
const terminalOperatorIndex = snapshot.operators.findIndex((entry) =>
['completed', 'failed', 'aborted', 'cancelled'].includes(entry.status),
);
const operator = snapshot.operators.splice(
terminalOperatorIndex < 0 ? snapshot.operators.length - 1 : terminalOperatorIndex,
1,
)[0];
if (operator) {
snapshot.omitted.operators += 1;
const retainedEdges = snapshot.edges.filter(
(edge) =>
edge.fromOperatorId !== operator.operatorId && edge.toOperatorId !== operator.operatorId,
);
snapshot.omitted.edges += snapshot.edges.length - retainedEdges.length;
snapshot.edges = retainedEdges;
continue;
}
throw new Error('Agent graph snapshot cannot fit the Runtime Host wire limit');
}
}
function fitInspection(inspection: Mutable<AgentGraphOperatorInspection>): void {
while (encodedBytes(inspection) > AGENT_GRAPH_RESULT_MAX_BYTES) {
if (inspection.recentRecords.shift()) {
inspection.omitted.records += 1;
continue;
}
if (inspection.activations.shift()) {
inspection.omitted.activations += 1;
continue;
}
if (inspection.claims.shift()) {
inspection.omitted.claims += 1;
continue;
}
if (inspection.work.shift()) {
inspection.omitted.work += 1;
continue;
}
if (inspection.inboundEdges.shift()) {
inspection.omitted.inboundEdges += 1;
continue;
}
if (inspection.outboundEdges.shift()) {
inspection.omitted.outboundEdges += 1;
continue;
}
throw new Error('Agent graph operator inspection cannot fit the Runtime Host wire limit');
}
}
function updateTerminalCursor(snapshot: Mutable<AgentGraphClientSnapshot>): void {
const last = snapshot.terminalHistory.records.at(-1);
if (last) {
snapshot.terminalHistory.nextCursor = encodeAgentGraphTerminalCursor(snapshot.graphId, last);
}
}
function projectChangedEvent(event: AgentGraphClientChangedEvent): {
rootSessionId: string;
graphId: string;
reason: AgentGraphClientChangedEvent['reason'];
} {
return {
rootSessionId: event.rootSessionId,
graphId: event.graphId,
reason: event.reason,
};
}
function graphQueryFailure(error: unknown): {
ok: false;
error: { code: GraphQueryFailureCode; message: string };
} {
if (error instanceof AgentGraphClientOperationError) {
return error.code === 'session_archived'
? failure('operation_conflict', error.message)
: failure(error.code, error.message);
}
if (isSessionNotFoundError(error)) return failure('not_found', 'Session was not found');
return failure('persistence_failed', 'Agent graph projection is unavailable');
}
function failure<Code extends RootOperationFailureCode | 'persistence_failed' | 'internal_failure'>(
code: Code,
message: string,
): { ok: false; error: { code: Code; message: string } } {
return { ok: false, error: { code, message } };
}
function requireFingerprint(value: string): `sha256:${string}` {
if (!/^sha256:[a-f0-9]{64}$/.test(value)) {
throw new Error('Agent graph projection contains an invalid fingerprint');
}
return value as `sha256:${string}`;
}
function encodedBytes(value: unknown): number {
return Buffer.byteLength(JSON.stringify(value), 'utf8');
}