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