Skip to main content

lash_core/
lib.rs

1//! Runtime kernel for Lash.
2//!
3//! The process kernel intentionally understands `ToolCall`, `SessionTurn`, and
4//! `External` because those inputs carry runtime mechanisms core must enforce:
5//! tool orchestration, child-session turns, and externally completed work. New
6//! process runtimes should use `ProcessInput::Engine { kind, payload }` unless
7//! core must understand their semantics to enforce a kernel mechanism.
8//!
9//! Protocols follow the same boundary: core owns the `HostTurnProtocol` state
10//! shape and the `ProtocolDriverPlugin` slot, while external protocol crates
11//! provide the driver implementation.
12
13pub mod attachments;
14pub mod chronological;
15pub mod direct;
16pub mod llm;
17mod model;
18pub mod plugin;
19mod plugin_stack;
20mod protocol_build;
21pub mod provider;
22pub mod runtime;
23pub mod search;
24pub mod session;
25pub mod session_graph;
26pub mod session_model;
27mod stable_hash;
28pub mod store;
29#[cfg(any(test, feature = "testing"))]
30pub mod testing;
31pub mod tool_dispatch;
32mod tool_provider;
33pub mod tool_registry;
34mod tool_result;
35mod trace;
36pub mod triggers;
37
38pub use lash_sansio::sansio;
39
40pub const VERSION: &str = env!("CARGO_PKG_VERSION");
41pub const SANSIO_VERSION: &str = lash_sansio::VERSION;
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum DurabilityTier {
45    Inline,
46    Durable,
47}
48
49// Re-exports
50pub use attachments::{
51    AttachmentReclamationReport, AttachmentStore, AttachmentStoreError, AttachmentStorePersistence,
52    FileAttachmentStore, InMemoryAttachmentStore, SessionScopedAttachmentStore, StoredAttachment,
53    reclaim_orphaned_attachments,
54};
55pub use chronological::{
56    BorrowedChronologicalEntry, BorrowedChronologicalMessage, BorrowedChronologicalPayload,
57    ChronologicalEntry, ChronologicalPayload, ChronologicalProjection, visit_turn_view,
58};
59pub use direct::{
60    DirectJsonSchema, DirectLlmClient, DirectLlmError, DirectMessage, DirectOutputSpec, DirectPart,
61    DirectRequest, DirectRole,
62};
63pub use lash_sansio::llm::types::{
64    GenerationOptions, LlmOutputPart, LlmRequest, LlmRequestScope, LlmResponse, LlmTerminalReason,
65};
66pub use lash_sansio::{
67    AcceptedInjectedTurnInput, AttachmentCreateMeta, AttachmentId, AttachmentMeta, AttachmentRef,
68    BaseRenderCache, CheckpointDelivery, CheckpointKind, CompactToolContract, EffectId,
69    ErrorEnvelope, ExecImage, ExecResponse, ImageMediaType, LashSchema, LlmCallError, MediaType,
70    Message, MessageOrigin, MessageRole, MessageSequence, ModelToolReturn, ModelToolReturnPart,
71    Part, PartKind, PluginMessage, PluginRuntimeEvent, PreparedPrompt, ProjectionMode,
72    PromptBuildInput, PromptBuiltin, PromptContext, PromptContribution, PromptContributionGate,
73    PromptContributionSet, PromptFingerprint, PromptLayer, PromptSlot, PromptSlotLayer,
74    PromptTemplate, PromptTemplateEntry, PromptTemplateSection, ProviderSchemaCapabilities,
75    PruneState, RenderedPrompt, ResolvedPromptLayer, ResolvedSchema, Response, SchemaContract,
76    SchemaDialect, SchemaProjectionOverride, SchemaProjectionPolicy, SchemaPurpose,
77    SchemaResolutionError, SchemaResolutionRequest, SessionEvent, TextProjectionMetadata,
78    TokenUsage, ToolActivation, ToolArgumentProjectionPolicy, ToolCallOutcome, ToolCallOutput,
79    ToolCallRecord, ToolCallStatus, ToolCancellation, ToolCatalog, ToolCatalogBuildInput,
80    ToolCatalogEntry, ToolContract, ToolControl, ToolDefinition, ToolFailure, ToolFailureClass,
81    ToolFailureSource, ToolId, ToolManifest, ToolOutputContract, ToolRetryDisposition,
82    ToolRetryPolicy, ToolScheduling, ToolValue, TurnCause, TurnFinish, TurnLimitFinalMessage,
83    TurnOutcome, TurnStop, append_assistant_text_part, build_prompt, build_tool_catalog,
84    build_turn, default_prompt_template, head_tail_truncate, messages_are_prompt_resume_safe,
85    normalized_response_parts, project_anthropic_bedrock_schema, project_for_dialect,
86    prompt_template_fingerprint, prompt_text_fingerprint, prompt_tool_names_fingerprint,
87    reasoning_part, render_turn_causes_prompt, resolve_prompt_layers, resolve_schema, shared_parts,
88    validate_tool_input, visible_response_parts, visible_response_text_from_parts,
89};
90pub use protocol_build::ProtocolBuildInput;
91pub use tool_registry::{
92    PLUGIN_TOOL_SOURCE_ID, ReconfigureError, ToolRegistry, ToolRestoreReport, ToolSourceHandle,
93    ToolState, ToolStateEntry,
94};
95pub use tool_result::{CancelHint, PendingCompletion, TimeoutBehavior, ToolResult};
96pub use triggers::{
97    InMemoryTriggerStore, TriggerDeliveryReservation, TriggerEmitReport, TriggerEvent,
98    TriggerEventCatalog, TriggerEventKey, TriggerEventType, TriggerInputBinding,
99    TriggerOccurrenceRecord, TriggerOccurrenceRequest, TriggerRegistration, TriggerRouter,
100    TriggerStore, TriggerSubscriptionDraft, TriggerSubscriptionFilter, TriggerSubscriptionRecord,
101    TriggerTargetSummary, default_trigger_source_key, deterministic_delivery_process_id,
102    deterministic_occurrence_id, empty_trigger_source_key, trigger_event_type,
103    trigger_occurrence_request_hash, validate_trigger_occurrence_request,
104};
105pub const PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION: u32 = 1;
106
107#[derive(Clone, Debug, serde::Serialize)]
108pub struct ProtocolTurnOptions {
109    pub schema_version: u32,
110    pub payload: serde_json::Value,
111}
112
113fn empty_protocol_turn_payload() -> serde_json::Value {
114    serde_json::Value::Object(serde_json::Map::new())
115}
116
117#[derive(Debug, thiserror::Error)]
118pub enum ProtocolTurnOptionsError {
119    #[error(
120        "protocol turn options are missing schema_version and were written by unsupported pre-versioned state (expected {expected})"
121    )]
122    MissingSchemaVersion { expected: u32 },
123    #[error(
124        "protocol turn options schema_version {actual} is not supported by this binary (expected {expected})"
125    )]
126    UnsupportedSchemaVersion { actual: u32, expected: u32 },
127    #[error(
128        "protocol turn options schema_version {actual} is invalid (expected integer {expected})"
129    )]
130    InvalidSchemaVersion { actual: String, expected: u32 },
131    #[error("failed to decode protocol turn options payload: {0}")]
132    Decode(#[source] serde_json::Error),
133}
134
135fn parse_protocol_turn_options_schema_version(
136    value: Option<serde_json::Value>,
137) -> Result<u32, ProtocolTurnOptionsError> {
138    let expected = PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION;
139    let Some(value) = value else {
140        return Err(ProtocolTurnOptionsError::MissingSchemaVersion { expected });
141    };
142    let Some(actual) = value
143        .as_u64()
144        .and_then(|version| u32::try_from(version).ok())
145    else {
146        return Err(ProtocolTurnOptionsError::InvalidSchemaVersion {
147            actual: value.to_string(),
148            expected,
149        });
150    };
151    ensure_protocol_turn_options_schema_version(actual)?;
152    Ok(actual)
153}
154
155fn ensure_protocol_turn_options_schema_version(
156    actual: u32,
157) -> Result<(), ProtocolTurnOptionsError> {
158    let expected = PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION;
159    if actual == expected {
160        Ok(())
161    } else {
162        Err(ProtocolTurnOptionsError::UnsupportedSchemaVersion { actual, expected })
163    }
164}
165
166impl<'de> serde::Deserialize<'de> for ProtocolTurnOptions {
167    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
168    where
169        D: serde::Deserializer<'de>,
170    {
171        #[derive(serde::Deserialize)]
172        struct ProtocolTurnOptionsWire {
173            schema_version: Option<serde_json::Value>,
174            #[serde(default = "empty_protocol_turn_payload")]
175            payload: serde_json::Value,
176        }
177
178        let wire = ProtocolTurnOptionsWire::deserialize(deserializer)?;
179        let schema_version = parse_protocol_turn_options_schema_version(wire.schema_version)
180            .map_err(serde::de::Error::custom)?;
181        Ok(Self {
182            schema_version,
183            payload: wire.payload,
184        })
185    }
186}
187
188impl Default for ProtocolTurnOptions {
189    fn default() -> Self {
190        Self::empty()
191    }
192}
193
194impl ProtocolTurnOptions {
195    pub fn empty() -> Self {
196        Self {
197            schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
198            payload: serde_json::Value::Object(serde_json::Map::new()),
199        }
200    }
201
202    pub fn from_payload(payload: serde_json::Value) -> Self {
203        Self {
204            schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
205            payload,
206        }
207    }
208
209    pub fn is_empty(&self) -> bool {
210        match &self.payload {
211            serde_json::Value::Object(map) => map.is_empty(),
212            _ => false,
213        }
214    }
215
216    pub fn merged_with_override(&self, override_options: &Self) -> Self {
217        match (&self.payload, &override_options.payload) {
218            (serde_json::Value::Object(base), serde_json::Value::Object(overrides)) => {
219                let mut payload = base.clone();
220                payload.extend(overrides.clone());
221                Self {
222                    schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
223                    payload: serde_json::Value::Object(payload),
224                }
225            }
226            _ => override_options.clone(),
227        }
228    }
229
230    pub fn typed<T>(value: T) -> Result<Self, serde_json::Error>
231    where
232        T: serde::Serialize,
233    {
234        Ok(Self {
235            schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
236            payload: serde_json::to_value(value)?,
237        })
238    }
239
240    pub fn decode<T>(&self) -> Result<T, ProtocolTurnOptionsError>
241    where
242        T: serde::de::DeserializeOwned,
243    {
244        ensure_protocol_turn_options_schema_version(self.schema_version)?;
245        serde_json::from_value(self.payload.clone()).map_err(ProtocolTurnOptionsError::Decode)
246    }
247}
248
249#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
250pub struct ProtocolDriverState {
251    pub plugin_id: String,
252    pub payload: serde_json::Value,
253}
254
255impl ProtocolDriverState {
256    pub fn new(plugin_id: impl Into<String>, payload: serde_json::Value) -> Self {
257        Self {
258            plugin_id: plugin_id.into(),
259            payload,
260        }
261    }
262}
263
264#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
265pub struct HostTurnProtocol;
266
267impl lash_sansio::TurnProtocol for HostTurnProtocol {
268    type Event = crate::session_model::ProtocolEvent;
269    type Termination = ProtocolTurnOptions;
270    type DriverState = ProtocolDriverState;
271}
272
273pub type Effect = lash_sansio::Effect<HostTurnProtocol>;
274pub type DriverAction = lash_sansio::DriverAction<HostTurnProtocol>;
275pub type DriverContextView<'a> = lash_sansio::DriverContextView<'a, HostTurnProtocol>;
276pub type TurnDriverConfig = lash_sansio::TurnDriverConfig<HostTurnProtocol>;
277pub type TurnDriverPreamble = lash_sansio::TurnDriverPreamble<HostTurnProtocol>;
278pub type ProjectorContext<'a> = lash_sansio::ProjectorContext<'a, HostTurnProtocol>;
279pub type PreparedTurnMachine = lash_sansio::PreparedTurnMachine<HostTurnProtocol>;
280pub type SansIoTurnInput = lash_sansio::SansIoTurnInput<HostTurnProtocol>;
281pub type TurnMachine = lash_sansio::TurnMachine<HostTurnProtocol>;
282pub type TurnMachineConfig = lash_sansio::TurnMachineConfig<HostTurnProtocol>;
283#[cfg(feature = "otel-trace")]
284pub use lash_trace::otel::{OtelTraceOptions, OtelTraceSink};
285pub use lash_trace::{
286    JsonlTraceSink, TraceAttachment, TraceBranchSelection, TraceContentBlock, TraceContext,
287    TraceError, TraceEvent, TraceLabelMetadata, TraceLevel, TraceLlmMessage, TraceLlmRequest,
288    TraceLlmResponse, TracePromptComponent, TraceProviderStreamEvent, TraceRecord,
289    TraceRuntimeScope, TraceRuntimeStreamEvent, TraceRuntimeSubject, TraceSink, TraceSinkError,
290    TraceTokenUsage, TraceToolSpec,
291};
292pub use llm::transport::{LlmTransportError, ProviderFailure, ProviderFailureKind};
293pub use model::{ModelLimits, ModelSpec};
294pub use plugin::{
295    AgentFrameAssignment, AgentFrameId, AgentFrameReason, AgentFrameRecord, AgentFrameStatus,
296    AppendSessionNodesRequest, AppendSessionNodesResult, AssistantResponseHookContext,
297    AssistantResponseTransform, AssistantStreamHookContext, AssistantStreamTransform,
298    CheckpointHookContext, CompactionContext, ContextCompaction, ContextCompactor, ContextError,
299    ContextRegistrations, DirectCompletion, DirectLlmCompletion, OpenAgentFrameRequest,
300    OpenAgentFrameResult, PersistentRuntimeServices, PluginCommand, PluginCommandContext,
301    PluginCommandOutcome, PluginCommandReceipt, PluginDirective, PluginError,
302    PluginExtensionContribution, PluginExtensions, PluginFactory, PluginHost, PluginLifecycleEvent,
303    PluginLifecycleEventHook, PluginOperation, PluginOperationDef, PluginOperationFailure,
304    PluginOperationInvokeError, PluginOperationKind, PluginOptions, PluginOwned, PluginQuery,
305    PluginQueryContext, PluginRegistrar, PluginRuntimeDirective, PluginSession,
306    PluginSessionContext, PluginSessionSnapshot, PluginSnapshotArtifact, PluginSnapshotEntry,
307    PluginSnapshotMeta, PluginSpec, PluginSpecFactory, PluginTask, PluginTaskContext,
308    PluginTaskOutcome, PluginTaskReceipt, ProcessEngineContributionContext, PromptHookContext,
309    ProtocolBeforeLlmCallContext, ProtocolLlmCallAction, RuntimeServices, SessionAppendNode,
310    SessionConfigChangedContext, SessionContextOverlay, SessionCreateRequest, SessionGraphService,
311    SessionHandle, SessionLifecycleService, SessionParam, SessionPlugin, SessionPluginSource,
312    SessionReadView, SessionRelation, SessionSnapshot, SessionStartPoint,
313    SessionStateChangedContext, SessionStateService, SessionToolAccess, SessionTurnInput,
314    SessionTurnRequest, SnapshotReader, SnapshotWriter, SubagentSessionContext,
315    ToolCatalogContribution, ToolResultProjectionContext, ToolResultProjector,
316    TriggerEventRegistrations, TurnContextTransform, TurnHookContext, TurnResultHookContext,
317    TurnResultSummary, TurnTransformContext, plugin_operation_def,
318};
319pub use plugin_stack::PluginStack;
320pub use provider::{
321    CacheRetention, EmptyProviderResolver, LlmTimeouts, MapProviderResolver, Provider,
322    ProviderBinding, ProviderComponents, ProviderFactory, ProviderHandle, ProviderModelPolicy,
323    ProviderOptions, ProviderResolutionError, ProviderSpec, RequestTimeout,
324    RuntimeProviderResolver, SingleProviderResolver, StaticModelPolicy,
325};
326#[cfg(any(test, feature = "testing"))]
327pub use runtime::TestLocalProcessRegistry;
328pub use runtime::{
329    AbandonEvidence, AbandonRequest, AbandonWriter, AgentFrameRun, AssembledTurn, AssistantOutput,
330    AwaitEventKey, AwaitEventResolver, AwaitEventWaitIdentity, CausalRef, Clock, CodeOutputRecord,
331    DefaultProcessCancelAbility, DeliveryPolicy, DirectCompletionClient, DurableProcessWorker,
332    DurableProcessWorkerConfig, DurableStoreFacet, EffectHost, EmbeddedRuntimeBuilder,
333    EmbeddedRuntimeHost, EventSink, ExecutionScope, ExecutionSummary, ExternalCompletionError,
334    InMemoryLiveReplayStore, InMemoryLiveReplayStoreConfig, InMemoryProcessExecutionEnvStore,
335    InMemorySessionStore, InMemorySessionStoreFactory, InlineEffectHost, InlineProcessRunHandle,
336    InlineRuntimeEffectController, InputItem, LashRuntime, LiveReplayGap, LiveReplayGapReason,
337    LiveReplayResult, LiveReplayStore, LiveReplayStoreError, LiveReplaySubscribeResult,
338    LiveReplaySubscription, MergeKey, NoopEventSink, NoopTurnActivitySink, ObservedProcess,
339    ObservedProcessEvent, ObservedWorkItem, OutputState, PROCESS_LEASE_SCHEMA_VERSION,
340    ParkedSession, PendingTurnInput, PendingTurnInputCancelOutcome, PendingTurnInputCancelResult,
341    PendingTurnInputCancelTarget, PendingTurnInputClaimDiagnostics, PendingTurnInputDraft,
342    PendingTurnInputSuffixCancelOutcome, ProcessAttach, ProcessAwaitOutput, ProcessAwaiter,
343    ProcessCancelAbility, ProcessCancelAllRequest, ProcessCancelRequest, ProcessCancelSource,
344    ProcessCancelSummary, ProcessChangeHub, ProcessDrainReport, ProcessEngine,
345    ProcessEngineRegistry, ProcessEngineRunContext, ProcessEngineRunGuard,
346    ProcessEngineRuntimeContext, ProcessEngineValidationContext, ProcessEvent,
347    ProcessEventAppendPlan, ProcessEventAppendRequest, ProcessEventAppendResult, ProcessEventSink,
348    ProcessEventType, ProcessExecutionContext, ProcessExecutionEnvRef, ProcessExecutionEnvSpec,
349    ProcessExecutionEnvStore, ProcessExternalRef, ProcessHandleDescriptor, ProcessHandleGrant,
350    ProcessHandleSummary, ProcessId, ProcessIdentity, ProcessInput, ProcessLease,
351    ProcessLeaseClaimOutcome, ProcessLeaseCompletion, ProcessLifecycleStatus, ProcessListFilter,
352    ProcessListMode, ProcessOpScope, ProcessOriginator, ProcessProvenance, ProcessPruneReport,
353    ProcessRecord, ProcessRegistration, ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost,
354    ProcessService, ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant,
355    ProcessStartOptions, ProcessStartRequest, ProcessStarted, ProcessStatus, ProcessStatusFilter,
356    ProcessTerminalSemantics, ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector,
357    ProcessWake, ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeDeliveryRequest,
358    ProcessWakeSpec, ProcessWorkDriver, ProcessWorkObserver, ProcessWorkSnapshot, PromptUsage,
359    ProtocolSessionExtension, ProtocolSessionExtensionHandle, ProtocolTurnExtension,
360    ProtocolTurnExtensionHandle, QueuedWorkDriver, QueuedWorkRunHandle, QueuedWorkRunRequest,
361    RecoveryDisposition, Residency, Resolution, ResolveOutcome, RuntimeEnvironment,
362    RuntimeEnvironmentBuilder, RuntimeError, RuntimeErrorCode, RuntimeHandle, RuntimeHostConfig,
363    RuntimeObservation, ScopedEffectController, SessionCommand, SessionCommandReceipt,
364    SessionCursor, SessionCursorError, SessionObservation, SessionObservationEvent,
365    SessionObservationEventPayload, SessionObservationSubscription, SessionProcessEventKind,
366    SessionQueueEventKind, SessionResume, SessionRevision, SessionScope, SessionScopeId,
367    SessionStoreCreateRequest, SessionStoreFactory, SessionUsageReport, SlotPolicy, SystemClock,
368    TerminationPolicy, TokenLedgerEntry, ToolCallLaunch, TurnActivity, TurnActivityId,
369    TurnActivitySink, TurnContext, TurnEvent, TurnInput, TurnInputCheckpointBoundary,
370    TurnInputClaim, TurnInputClaimMode, TurnInputCompletion, TurnInputIngress, TurnInputState,
371    TurnIssue, TurnOptions, UnavailableProcessService, UsageReportRow, UsageTotals, WaitKind,
372    WaitState, apply_process_status_projection, current_epoch_ms, diff_token_ledger,
373    diff_usage_reports, ensure_durable_effect_input, epoch_ms_from_system_time,
374    process_signal_event_type, process_signal_name_from_event_type, process_signal_wait_key,
375    process_wake_delivery, system_time_from_epoch_ms, validate_process_signal_name,
376    watch_process_registry, watch_process_registry_with_sink,
377};
378#[allow(unused_imports)]
379pub(crate) use runtime::{
380    LlmAttachmentSpec, ProcessEventSemantics, QueuedCheckpointTurnInput, QueuedCheckpointWork,
381    QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
382    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
383    RuntimeReplay, RuntimeScope, RuntimeSubject, load_process_execution_env,
384    materialize_process_event_semantics, persist_process_execution_env,
385    prepare_process_event_append, prepare_process_registration, process_event_invocation,
386    process_event_payload_hash, process_wake_batch_draft, process_wake_input_from_event_payload,
387    process_wake_turn_cause, process_wake_turn_text, require_event_replay,
388};
389pub use session_model::{
390    PLUGIN_RUNTIME_PROTOCOL_PLUGIN_ID, PersistedPluginRuntimeEvent,
391    plugin_runtime_event_from_protocol, plugin_runtime_protocol_event,
392};
393// Effect / process-control types consumed by external effect hosts (e.g.
394// lash-restate's workflows) and their integration tests. Kept on the public
395// surface; the rest of the runtime block above stays crate-internal.
396pub use runtime::{
397    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
398    RuntimeEffectCommand, RuntimeEffectController, RuntimeEffectControllerError,
399    RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome,
400    RuntimeInvocation, RuntimeSessionState, ToolAttemptEffectOutcome, ToolAttemptLaunch,
401    ToolBatchEffectOutcome,
402};
403pub use schemars::JsonSchema;
404pub(crate) use session::RuntimeExecutionTracing;
405pub use session::{
406    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
407    ToolInvocationReply,
408};
409pub use session_graph::{
410    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
411    SessionNodePayload, SessionNodeRecord,
412};
413pub use session_model::context::PreparedContext;
414pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
415pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
416pub use store::{
417    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
418    LeaseOwnerIdentity, LeaseOwnerLiveness, LeaseTimings, LeaseTimingsError, QueuedWorkStore,
419    RuntimePersistence, SessionCommitStore, SessionExecutionLease,
420    SessionExecutionLeaseClaimOutcome, SessionExecutionLeaseCompletion, SessionExecutionLeaseFence,
421    SessionExecutionLeaseStore, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError,
422    StoreMaintenance, TurnInputStore, VacuumReport,
423};
424#[allow(unused_imports)]
425pub(crate) use store::{
426    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
427    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
428    load_persisted_session_state_active_path,
429};
430pub use store::{
431    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
432    refresh_persisted_session_state,
433};
434pub use tool_provider::{
435    PreparedToolBatch, PreparedToolBatchCall, PreparedToolCall, ProgressSender, SandboxMessage,
436    ToolCall, ToolChildExecutionTraceHook, ToolChildProcessStarted, ToolContext,
437    ToolDurableEffects, ToolExecutionGrant, ToolPrepareCall, ToolPrepareContext, ToolProvider,
438    ToolSessionAdmin, ToolSessionModel, ToolSessionProcessAdmin, ToolTriggerClient,
439};
440
441#[cfg(test)]
442mod tests {
443    use super::*;
444
445    #[test]
446    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
447        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
448            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION
449        }))
450        .expect("deserialize options");
451
452        assert!(options.is_empty());
453        assert_eq!(options.schema_version, PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION);
454        assert_eq!(options.payload, serde_json::json!({}));
455    }
456
457    #[test]
458    fn protocol_turn_options_explicit_null_is_not_empty() {
459        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
460            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
461            "payload": null
462        }))
463        .expect("deserialize options");
464
465        assert!(!options.is_empty());
466        assert_eq!(options.payload, serde_json::Value::Null);
467    }
468
469    #[test]
470    fn protocol_turn_options_missing_schema_version_rejects_preversioned_state() {
471        let err =
472            serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({ "payload": {} }))
473                .expect_err("pre-versioned options should fail");
474
475        assert!(
476            err.to_string().contains(
477                "missing schema_version and were written by unsupported pre-versioned state"
478            ),
479            "{err}"
480        );
481    }
482
483    #[test]
484    fn protocol_turn_options_unsupported_schema_version_rejects_state() {
485        let err = serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({
486            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION + 1,
487            "payload": {}
488        }))
489        .expect_err("unsupported options version should fail");
490
491        assert!(
492            err.to_string().contains("is not supported by this binary"),
493            "{err}"
494        );
495    }
496
497    #[test]
498    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
499        let source = include_str!("lib.rs");
500        let removed_envelope = ["SessionState", "Envelope"].concat();
501        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
502        let removed_history_rewriter = ["History", "Rewriter"].concat();
503        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
504        let removed_rewrite_context = ["Rewrite", "Context"].concat();
505        let removed_history_state = ["History", "State"].concat();
506        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
507
508        assert!(!source.contains(&removed_envelope));
509        assert!(!source.contains(&removed_persisted));
510        assert!(!source.contains(&removed_history_rewriter));
511        assert!(!source.contains(&removed_rewrite_trigger));
512        assert!(!source.contains(&removed_rewrite_context));
513        assert!(!source.contains(&removed_history_state));
514        assert!(!source.contains(&removed_history_metadata));
515    }
516
517    fn public_reexport_block(source: &str, module: &str) -> String {
518        let start = format!("pub use {module}::{{");
519        let mut block = String::new();
520        let mut collecting = false;
521        for line in source.lines() {
522            if line.trim_start().starts_with(&start) {
523                collecting = true;
524            }
525            if collecting {
526                block.push_str(line);
527                block.push('\n');
528                if line.trim_end() == "};" {
529                    break;
530                }
531            }
532        }
533        assert!(!block.is_empty(), "missing public {module} re-export block");
534        block
535    }
536
537    #[test]
538    fn root_runtime_exports_exclude_internal_runtime_records() {
539        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
540        for removed in [
541            "RuntimeEffectCommand",
542            "RuntimeEffectEnvelope",
543            "RuntimeEffectKind",
544            "RuntimeEffectOutcome",
545            "RuntimeInvocation",
546            "RuntimeScope",
547            "RuntimeSessionState",
548            "QueuedWorkBatch",
549            "QueuedWorkBatchDraft",
550            "QueuedWorkPayload",
551            "prepare_process_registration",
552            "process_wake_batch_draft",
553            "require_event_replay",
554        ] {
555            assert!(
556                !runtime_exports.contains(removed),
557                "runtime root export leaked {removed}"
558            );
559        }
560    }
561
562    #[test]
563    fn root_store_exports_exclude_wire_records() {
564        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
565        for removed in [
566            "SessionHead",
567            "SessionCheckpoint",
568            "RuntimeCommit",
569            "HydratedSessionCheckpoint",
570            "PersistedSessionRead",
571            "GraphCommitDelta",
572        ] {
573            assert!(
574                !store_exports.contains(removed),
575                "store root export leaked {removed}"
576            );
577        }
578    }
579
580    #[test]
581    fn removed_manager_and_host_trait_names_stay_removed() {
582        let removed_manager = ["Runtime", "Session", "Manager"].concat();
583        let removed_host = ["Runtime", "Session", "Host"].concat();
584        let sources = [
585            include_str!("runtime/session_manager/mod.rs"),
586            include_str!("plugin/runtime_host.rs"),
587            include_str!("tool_dispatch/context.rs"),
588            include_str!("tool_provider.rs"),
589        ];
590
591        for source in sources {
592            assert!(!source.contains(&removed_manager));
593            assert!(!source.contains(&removed_host));
594        }
595    }
596}