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, 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, PromptHookContext, ProtocolBeforeLlmCallContext,
308    ProtocolLlmCallAction, RuntimeServices, SessionAppendNode, SessionConfigChangedContext,
309    SessionContextOverlay, SessionCreateRequest, SessionGraphService, SessionHandle,
310    SessionLifecycleService, SessionParam, SessionPlugin, SessionPluginSource, SessionReadView,
311    SessionRelation, SessionSnapshot, SessionStartPoint, SessionStateChangedContext,
312    SessionStateService, SessionToolAccess, SessionTurnInput, SessionTurnRequest, SnapshotReader,
313    SnapshotWriter, SubagentSessionContext, ToolCatalogContribution, ToolResultProjectionContext,
314    ToolResultProjector, TriggerEventRegistrations, TurnContextTransform, TurnHookContext,
315    TurnResultHookContext, TurnResultSummary, TurnTransformContext, plugin_operation_def,
316};
317pub use plugin_stack::PluginStack;
318pub use provider::{
319    CacheRetention, EmptyProviderResolver, LlmTimeouts, MapProviderResolver, Provider,
320    ProviderBinding, ProviderComponents, ProviderFactory, ProviderHandle, ProviderModelPolicy,
321    ProviderOptions, ProviderResolutionError, ProviderSpec, RequestTimeout,
322    RuntimeProviderResolver, SingleProviderResolver, StaticModelPolicy,
323};
324#[cfg(any(test, feature = "testing"))]
325pub use runtime::TestLocalProcessRegistry;
326pub use runtime::{
327    AgentFrameRun, AssembledTurn, AssistantOutput, AwaitEventKey, AwaitEventWaitIdentity,
328    CausalRef, Clock, CodeOutputRecord, DefaultProcessCancelAbility, DeliveryPolicy,
329    DirectCompletionClient, DurableProcessWorker, DurableProcessWorkerConfig, DurableStoreFacet,
330    EffectHost, EmbeddedRuntimeBuilder, EmbeddedRuntimeHost, EventSink, ExecutionScope,
331    ExecutionSummary, ExternalCompletionError, InMemoryLiveReplayStore,
332    InMemoryLiveReplayStoreConfig, InMemoryProcessExecutionEnvStore, InMemorySessionStore,
333    InMemorySessionStoreFactory, InlineEffectHost, InlineProcessRunHandle,
334    InlineRuntimeEffectController, InputItem, LashRuntime, LiveReplayGap, LiveReplayGapReason,
335    LiveReplayResult, LiveReplayStore, LiveReplayStoreError, LiveReplaySubscribeResult,
336    LiveReplaySubscription, MergeKey, NoopEventSink, NoopTurnActivitySink, ObservedProcess,
337    ObservedProcessEvent, ObservedWorkItem, OutputState, PROCESS_LEASE_SCHEMA_VERSION,
338    ParkedSession, PendingTurnInput, PendingTurnInputCancelOutcome, PendingTurnInputCancelResult,
339    PendingTurnInputCancelTarget, PendingTurnInputClaimDiagnostics, PendingTurnInputDraft,
340    PendingTurnInputSuffixCancelOutcome, ProcessAwaitOutput, ProcessCancelAbility,
341    ProcessCancelAllRequest, ProcessCancelRequest, ProcessCancelSource, ProcessCancelSummary,
342    ProcessEngine, ProcessEngineRegistry, ProcessEngineRunContext, ProcessEngineRunGuard,
343    ProcessEngineRuntimeContext, ProcessEngineValidationContext, ProcessEvent,
344    ProcessEventAppendPlan, ProcessEventAppendRequest, ProcessEventAppendResult, ProcessEventType,
345    ProcessExecutionContext, ProcessExecutionEnvRef, ProcessExecutionEnvSpec,
346    ProcessExecutionEnvStore, ProcessExternalRef, ProcessHandleDescriptor, ProcessHandleGrant,
347    ProcessHandleSummary, ProcessId, ProcessIdentity, ProcessInput, ProcessLease,
348    ProcessLeaseCompletion, ProcessLifecycleStatus, ProcessListFilter, ProcessListMode,
349    ProcessOpScope, ProcessOriginator, ProcessProvenance, ProcessRecord, ProcessRegistration,
350    ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost, ProcessService,
351    ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant, ProcessStartOptions,
352    ProcessStartRequest, ProcessStatus, ProcessStatusFilter, ProcessTerminalSemantics,
353    ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector, ProcessWake,
354    ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeDeliveryRequest, ProcessWakeSpec,
355    ProcessWorkDriver, ProcessWorkObserver, ProcessWorkSnapshot, PromptUsage,
356    ProtocolSessionExtension, ProtocolSessionExtensionHandle, ProtocolTurnExtension,
357    ProtocolTurnExtensionHandle, QueuedWorkDriver, QueuedWorkRunHandle, QueuedWorkRunRequest,
358    Residency, Resolution, ResolveOutcome, RuntimeEnvironment, RuntimeEnvironmentBuilder,
359    RuntimeError, RuntimeErrorCode, RuntimeHandle, RuntimeHostConfig, RuntimeObservation,
360    ScopedEffectController, SessionCommand, SessionCommandReceipt, SessionCursor,
361    SessionCursorError, SessionObservation, SessionObservationEvent,
362    SessionObservationEventPayload, SessionObservationSubscription, SessionProcessEventKind,
363    SessionQueueEventKind, SessionResume, SessionRevision, SessionScope, SessionScopeId,
364    SessionStoreCreateRequest, SessionStoreFactory, SessionUsageReport, SlotPolicy, SystemClock,
365    TerminationPolicy, TokenLedgerEntry, ToolCallLaunch, TurnActivity, TurnActivityId,
366    TurnActivitySink, TurnContext, TurnEvent, TurnInput, TurnInputCheckpointBoundary,
367    TurnInputClaim, TurnInputClaimMode, TurnInputCompletion, TurnInputIngress, TurnInputState,
368    TurnIssue, TurnOptions, UnavailableProcessService, UsageReportRow, UsageTotals, WaitKind,
369    WaitState, apply_process_status_projection, current_epoch_ms, diff_token_ledger,
370    diff_usage_reports, ensure_durable_effect_input, epoch_ms_from_system_time,
371    process_signal_event_type, process_signal_name_from_event_type, process_signal_wait_key,
372    process_wake_delivery, system_time_from_epoch_ms, validate_process_signal_name,
373};
374#[allow(unused_imports)]
375pub(crate) use runtime::{
376    LlmAttachmentSpec, ProcessEventSemantics, QUEUED_WORK_CLAIM_TTL_MS, QueuedCheckpointTurnInput,
377    QueuedCheckpointWork, QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
378    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
379    RuntimeReplay, RuntimeScope, RuntimeSubject, TURN_INPUT_CLAIM_TTL_MS,
380    load_process_execution_env, materialize_process_event_semantics, persist_process_execution_env,
381    prepare_process_event_append, prepare_process_registration, process_event_invocation,
382    process_event_payload_hash, process_wake_batch_draft, process_wake_input_from_event_payload,
383    process_wake_turn_cause, process_wake_turn_text, require_event_replay,
384};
385pub use session_model::{
386    PLUGIN_RUNTIME_PROTOCOL_PLUGIN_ID, PersistedPluginRuntimeEvent,
387    plugin_runtime_event_from_protocol, plugin_runtime_protocol_event,
388};
389// Effect / process-control types consumed by external effect hosts (e.g.
390// lash-restate's workflows) and their integration tests. Kept on the public
391// surface; the rest of the runtime block above stays crate-internal.
392pub use runtime::{
393    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
394    RuntimeEffectCommand, RuntimeEffectController, RuntimeEffectControllerError,
395    RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome,
396    RuntimeInvocation, RuntimeSessionState, ToolAttemptEffectOutcome, ToolAttemptLaunch,
397    ToolBatchEffectOutcome,
398};
399pub use schemars::JsonSchema;
400pub use session::{
401    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
402    ToolInvocationReply,
403};
404pub use session_graph::{
405    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
406    SessionNodePayload, SessionNodeRecord,
407};
408pub use session_model::context::PreparedContext;
409pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
410pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
411pub use store::{
412    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
413    LeaseOwnerIdentity, LeaseOwnerLiveness, RuntimePersistence, SessionExecutionLease,
414    SessionExecutionLeaseClaimOutcome, SessionExecutionLeaseCompletion, SessionExecutionLeaseFence,
415    SessionMeta, SessionPickerInfo, SessionReadScope, StoreError, VacuumReport,
416};
417#[allow(unused_imports)]
418pub(crate) use store::{
419    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
420    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
421    load_persisted_session_state_active_path,
422};
423pub use store::{
424    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
425    refresh_persisted_session_state,
426};
427pub use tool_provider::{
428    PreparedToolBatch, PreparedToolBatchCall, PreparedToolCall, ProgressSender, SandboxMessage,
429    ToolCall, ToolChildExecutionTraceHook, ToolChildProcessStarted, ToolContext,
430    ToolDurableEffects, ToolExecutionGrant, ToolPrepareCall, ToolPrepareContext, ToolProvider,
431    ToolSessionAdmin, ToolSessionModel, ToolTriggerClient,
432};
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
440        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
441            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION
442        }))
443        .expect("deserialize options");
444
445        assert!(options.is_empty());
446        assert_eq!(options.schema_version, PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION);
447        assert_eq!(options.payload, serde_json::json!({}));
448    }
449
450    #[test]
451    fn protocol_turn_options_explicit_null_is_not_empty() {
452        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
453            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
454            "payload": null
455        }))
456        .expect("deserialize options");
457
458        assert!(!options.is_empty());
459        assert_eq!(options.payload, serde_json::Value::Null);
460    }
461
462    #[test]
463    fn protocol_turn_options_missing_schema_version_rejects_preversioned_state() {
464        let err =
465            serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({ "payload": {} }))
466                .expect_err("pre-versioned options should fail");
467
468        assert!(
469            err.to_string().contains(
470                "missing schema_version and were written by unsupported pre-versioned state"
471            ),
472            "{err}"
473        );
474    }
475
476    #[test]
477    fn protocol_turn_options_unsupported_schema_version_rejects_state() {
478        let err = serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({
479            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION + 1,
480            "payload": {}
481        }))
482        .expect_err("unsupported options version should fail");
483
484        assert!(
485            err.to_string().contains("is not supported by this binary"),
486            "{err}"
487        );
488    }
489
490    #[test]
491    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
492        let source = include_str!("lib.rs");
493        let removed_envelope = ["SessionState", "Envelope"].concat();
494        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
495        let removed_history_rewriter = ["History", "Rewriter"].concat();
496        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
497        let removed_rewrite_context = ["Rewrite", "Context"].concat();
498        let removed_history_state = ["History", "State"].concat();
499        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
500
501        assert!(!source.contains(&removed_envelope));
502        assert!(!source.contains(&removed_persisted));
503        assert!(!source.contains(&removed_history_rewriter));
504        assert!(!source.contains(&removed_rewrite_trigger));
505        assert!(!source.contains(&removed_rewrite_context));
506        assert!(!source.contains(&removed_history_state));
507        assert!(!source.contains(&removed_history_metadata));
508    }
509
510    fn public_reexport_block(source: &str, module: &str) -> String {
511        let start = format!("pub use {module}::{{");
512        let mut block = String::new();
513        let mut collecting = false;
514        for line in source.lines() {
515            if line.trim_start().starts_with(&start) {
516                collecting = true;
517            }
518            if collecting {
519                block.push_str(line);
520                block.push('\n');
521                if line.trim_end() == "};" {
522                    break;
523                }
524            }
525        }
526        assert!(!block.is_empty(), "missing public {module} re-export block");
527        block
528    }
529
530    #[test]
531    fn root_runtime_exports_exclude_internal_runtime_records() {
532        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
533        for removed in [
534            "RuntimeEffectCommand",
535            "RuntimeEffectEnvelope",
536            "RuntimeEffectKind",
537            "RuntimeEffectOutcome",
538            "RuntimeInvocation",
539            "RuntimeScope",
540            "RuntimeSessionState",
541            "QueuedWorkBatch",
542            "QueuedWorkBatchDraft",
543            "QueuedWorkPayload",
544            "prepare_process_registration",
545            "process_wake_batch_draft",
546            "require_event_replay",
547        ] {
548            assert!(
549                !runtime_exports.contains(removed),
550                "runtime root export leaked {removed}"
551            );
552        }
553    }
554
555    #[test]
556    fn root_store_exports_exclude_wire_records() {
557        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
558        for removed in [
559            "SessionHead",
560            "SessionCheckpoint",
561            "RuntimeCommit",
562            "HydratedSessionCheckpoint",
563            "PersistedSessionRead",
564            "GraphCommitDelta",
565        ] {
566            assert!(
567                !store_exports.contains(removed),
568                "store root export leaked {removed}"
569            );
570        }
571    }
572
573    #[test]
574    fn removed_manager_and_host_trait_names_stay_removed() {
575        let removed_manager = ["Runtime", "Session", "Manager"].concat();
576        let removed_host = ["Runtime", "Session", "Host"].concat();
577        let sources = [
578            include_str!("runtime/session_manager/mod.rs"),
579            include_str!("plugin/runtime_host.rs"),
580            include_str!("tool_dispatch/context.rs"),
581            include_str!("tool_provider.rs"),
582        ];
583
584        for source in sources {
585            assert!(!source.contains(&removed_manager));
586            assert!(!source.contains(&removed_host));
587        }
588    }
589}