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