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    AgentFrameRun, AssembledTurn, AssistantOutput, AwaitEventKey, AwaitEventResolver,
330    AwaitEventWaitIdentity, CausalRef, Clock, CodeOutputRecord, DefaultProcessCancelAbility,
331    DeliveryPolicy, DirectCompletionClient, DurableProcessWorker, DurableProcessWorkerConfig,
332    DurableStoreFacet, EffectHost, EmbeddedRuntimeBuilder, EmbeddedRuntimeHost, EventSink,
333    ExecutionScope, ExecutionSummary, ExternalCompletionError, InMemoryLiveReplayStore,
334    InMemoryLiveReplayStoreConfig, InMemoryProcessExecutionEnvStore, InMemorySessionStore,
335    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, ProcessEngine, ProcessEngineRegistry,
345    ProcessEngineRunContext, ProcessEngineRunGuard, ProcessEngineRuntimeContext,
346    ProcessEngineValidationContext, ProcessEvent, ProcessEventAppendPlan,
347    ProcessEventAppendRequest, ProcessEventAppendResult, ProcessEventSink, ProcessEventType,
348    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, 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    Residency, Resolution, ResolveOutcome, RuntimeEnvironment, RuntimeEnvironmentBuilder,
362    RuntimeError, RuntimeErrorCode, RuntimeHandle, RuntimeHostConfig, RuntimeObservation,
363    ScopedEffectController, SessionCommand, SessionCommandReceipt, SessionCursor,
364    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 use session::{
405    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
406    ToolInvocationReply,
407};
408pub use session_graph::{
409    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
410    SessionNodePayload, SessionNodeRecord,
411};
412pub use session_model::context::PreparedContext;
413pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
414pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
415pub use store::{
416    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
417    LeaseOwnerIdentity, LeaseOwnerLiveness, LeaseTimings, LeaseTimingsError, QueuedWorkStore,
418    RuntimePersistence, SessionCommitStore, SessionExecutionLease,
419    SessionExecutionLeaseClaimOutcome, SessionExecutionLeaseCompletion, SessionExecutionLeaseFence,
420    SessionExecutionLeaseStore, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError,
421    StoreMaintenance, TurnInputStore, VacuumReport,
422};
423#[allow(unused_imports)]
424pub(crate) use store::{
425    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
426    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
427    load_persisted_session_state_active_path,
428};
429pub use store::{
430    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
431    refresh_persisted_session_state,
432};
433pub use tool_provider::{
434    PreparedToolBatch, PreparedToolBatchCall, PreparedToolCall, ProgressSender, SandboxMessage,
435    ToolCall, ToolChildExecutionTraceHook, ToolChildProcessStarted, ToolContext,
436    ToolDurableEffects, ToolExecutionGrant, ToolPrepareCall, ToolPrepareContext, ToolProvider,
437    ToolSessionAdmin, ToolSessionModel, ToolTriggerClient,
438};
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
446        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
447            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION
448        }))
449        .expect("deserialize options");
450
451        assert!(options.is_empty());
452        assert_eq!(options.schema_version, PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION);
453        assert_eq!(options.payload, serde_json::json!({}));
454    }
455
456    #[test]
457    fn protocol_turn_options_explicit_null_is_not_empty() {
458        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
459            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
460            "payload": null
461        }))
462        .expect("deserialize options");
463
464        assert!(!options.is_empty());
465        assert_eq!(options.payload, serde_json::Value::Null);
466    }
467
468    #[test]
469    fn protocol_turn_options_missing_schema_version_rejects_preversioned_state() {
470        let err =
471            serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({ "payload": {} }))
472                .expect_err("pre-versioned options should fail");
473
474        assert!(
475            err.to_string().contains(
476                "missing schema_version and were written by unsupported pre-versioned state"
477            ),
478            "{err}"
479        );
480    }
481
482    #[test]
483    fn protocol_turn_options_unsupported_schema_version_rejects_state() {
484        let err = serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({
485            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION + 1,
486            "payload": {}
487        }))
488        .expect_err("unsupported options version should fail");
489
490        assert!(
491            err.to_string().contains("is not supported by this binary"),
492            "{err}"
493        );
494    }
495
496    #[test]
497    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
498        let source = include_str!("lib.rs");
499        let removed_envelope = ["SessionState", "Envelope"].concat();
500        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
501        let removed_history_rewriter = ["History", "Rewriter"].concat();
502        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
503        let removed_rewrite_context = ["Rewrite", "Context"].concat();
504        let removed_history_state = ["History", "State"].concat();
505        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
506
507        assert!(!source.contains(&removed_envelope));
508        assert!(!source.contains(&removed_persisted));
509        assert!(!source.contains(&removed_history_rewriter));
510        assert!(!source.contains(&removed_rewrite_trigger));
511        assert!(!source.contains(&removed_rewrite_context));
512        assert!(!source.contains(&removed_history_state));
513        assert!(!source.contains(&removed_history_metadata));
514    }
515
516    fn public_reexport_block(source: &str, module: &str) -> String {
517        let start = format!("pub use {module}::{{");
518        let mut block = String::new();
519        let mut collecting = false;
520        for line in source.lines() {
521            if line.trim_start().starts_with(&start) {
522                collecting = true;
523            }
524            if collecting {
525                block.push_str(line);
526                block.push('\n');
527                if line.trim_end() == "};" {
528                    break;
529                }
530            }
531        }
532        assert!(!block.is_empty(), "missing public {module} re-export block");
533        block
534    }
535
536    #[test]
537    fn root_runtime_exports_exclude_internal_runtime_records() {
538        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
539        for removed in [
540            "RuntimeEffectCommand",
541            "RuntimeEffectEnvelope",
542            "RuntimeEffectKind",
543            "RuntimeEffectOutcome",
544            "RuntimeInvocation",
545            "RuntimeScope",
546            "RuntimeSessionState",
547            "QueuedWorkBatch",
548            "QueuedWorkBatchDraft",
549            "QueuedWorkPayload",
550            "prepare_process_registration",
551            "process_wake_batch_draft",
552            "require_event_replay",
553        ] {
554            assert!(
555                !runtime_exports.contains(removed),
556                "runtime root export leaked {removed}"
557            );
558        }
559    }
560
561    #[test]
562    fn root_store_exports_exclude_wire_records() {
563        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
564        for removed in [
565            "SessionHead",
566            "SessionCheckpoint",
567            "RuntimeCommit",
568            "HydratedSessionCheckpoint",
569            "PersistedSessionRead",
570            "GraphCommitDelta",
571        ] {
572            assert!(
573                !store_exports.contains(removed),
574                "store root export leaked {removed}"
575            );
576        }
577    }
578
579    #[test]
580    fn removed_manager_and_host_trait_names_stay_removed() {
581        let removed_manager = ["Runtime", "Session", "Manager"].concat();
582        let removed_host = ["Runtime", "Session", "Host"].concat();
583        let sources = [
584            include_str!("runtime/session_manager/mod.rs"),
585            include_str!("plugin/runtime_host.rs"),
586            include_str!("tool_dispatch/context.rs"),
587            include_str!("tool_provider.rs"),
588        ];
589
590        for source in sources {
591            assert!(!source.contains(&removed_manager));
592            assert!(!source.contains(&removed_host));
593        }
594    }
595}