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