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