Files
orca/src/shared/agent-session-rewind.ts
T
Brennan BensonandMerge Sim ce4a3a4186 feat(chat): add structured session rewind backend (#19235)
* feat(chat): add structured session rewind backend

* fix(chat): make interrupted session rewinds recover safely

* fix(native-chat): negotiate rewind runtime capability

* fix(native-chat): consolidate remaining adapter imports

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-07 12:20:24 -07:00

61 lines
2.1 KiB
TypeScript

import { z } from 'zod'
import { AgentJournalItemBodySchema } from './agent-session-journal-schemas'
import { parseAgentJournalItemKey } from './agent-session-journal-item-key'
import type { AgentSessionMutationEnvelope } from './agent-session-wire'
export const AGENT_SESSION_REWIND_REASONS = [
'unsupported',
'history-not-paginated',
'busy',
'stale-epoch',
'invalid-target',
'history-limit',
'provider-refused',
'proof-mismatch',
'outcome-unknown'
] as const
export type AgentSessionRewindReason = (typeof AGENT_SESSION_REWIND_REASONS)[number]
export type AgentSessionRewindSupport =
| { supported: true }
| { supported: false; reason: AgentSessionRewindReason }
export type AgentSessionRewindParams = {
envelope: AgentSessionMutationEnvelope
itemId: string
expectedEpoch: string
}
export type AgentSessionRewindResult = { itemId: string; epoch: string }
const Key = z.string().min(1).max(4096)
export const AgentSessionRewindRecordSchema = z.object({
operationId: Key,
callerKey: Key,
itemId: Key,
providerItemId: Key.optional(),
expectedEpoch: Key,
phase: z.enum(['prepared', 'provider-succeeded', 'completed', 'refused']),
epoch: Key.optional(),
hydrationVerified: z.boolean().optional(),
providerApplied: z.boolean().optional(),
reason: z.string().min(1).max(512).optional(),
retained: z
.array(
z.object({
itemId: Key.refine((key) => parseAgentJournalItemKey(key) !== null),
body: AgentJournalItemBodySchema,
observedAt: z.number().finite()
})
)
.max(10_000)
})
export type AgentSessionRewindRecord = z.infer<typeof AgentSessionRewindRecordSchema>
export const isAgentSessionRewindRecord = (value: unknown): value is AgentSessionRewindRecord =>
AgentSessionRewindRecordSchema.safeParse(value).success
export function isAgentSessionRewindResult(value: unknown): value is AgentSessionRewindResult {
if (!value || typeof value !== 'object') {
return false
}
const result = value as Partial<AgentSessionRewindResult>
return typeof result.itemId === 'string' && typeof result.epoch === 'string'
}