1pub 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
49pub 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};
391pub 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}