Skip to main content

lash_core/
lib.rs

1pub mod attachments;
2pub mod chronological;
3pub mod direct;
4pub mod host_events;
5pub mod lashlang_bridge;
6pub mod llm;
7mod model;
8pub mod plugin;
9mod plugin_stack;
10mod protocol_build;
11pub mod provider;
12pub mod runtime;
13pub mod search;
14pub mod session;
15pub mod session_graph;
16pub mod session_model;
17mod stable_hash;
18pub mod store;
19#[cfg(any(test, feature = "testing"))]
20pub mod testing;
21pub mod tool_dispatch;
22mod tool_provider;
23pub mod tool_registry;
24mod tool_result;
25mod trace;
26
27pub use lash_sansio::sansio;
28
29pub const VERSION: &str = env!("CARGO_PKG_VERSION");
30pub const SANSIO_VERSION: &str = lash_sansio::VERSION;
31
32// Re-exports
33pub use attachments::{
34    AttachmentStore, AttachmentStoreError, AttachmentStorePersistence, FileAttachmentStore,
35    InMemoryAttachmentStore, SessionScopedAttachmentStore, StoredAttachment,
36};
37// The Lashlang artifact store is a host-owned durability dependency of
38// `RuntimeHostConfig`; re-export it so the `lash` facade can name it without a
39// direct `lashlang` dependency.
40pub use chronological::{
41    BorrowedChronologicalEntry, BorrowedChronologicalMessage, BorrowedChronologicalPayload,
42    ChronologicalEntry, ChronologicalPayload, ChronologicalProjection, visit_turn_view,
43};
44pub use direct::{
45    DirectJsonSchema, DirectLlmClient, DirectLlmError, DirectMessage, DirectOutputSpec, DirectPart,
46    DirectRequest, DirectRole,
47};
48pub use host_events::{
49    HostEvent, HostEventCatalog, HostEventEmitReport, HostEventKey, HostEventOccurrenceRecord,
50    HostEventOccurrenceRequest, HostEventRouter, HostEventStore, InMemoryHostEventStore,
51    TriggerDeliveryReservation, TriggerRegistration, TriggerSourceType, TriggerSubscriptionDraft,
52    TriggerSubscriptionFilter, TriggerSubscriptionRecord, TriggerTargetSummary,
53    default_host_event_source_key, deterministic_delivery_process_id, deterministic_occurrence_id,
54    empty_host_event_source_key, host_event_occurrence_request_hash, host_event_source_type,
55    validate_host_event_occurrence_request,
56};
57pub use lash_sansio::llm::types::{
58    GenerationOptions, LlmOutputPart, LlmRequest, LlmResponse, LlmTerminalReason,
59};
60pub use lash_sansio::{
61    AcceptedInjectedTurnInput, AttachmentCreateMeta, AttachmentId, AttachmentMeta, AttachmentRef,
62    BaseRenderCache, CheckpointDelivery, CheckpointKind, CompactToolContract, EffectId,
63    ErrorEnvelope, ExecImage, ExecResponse, ImageMediaType, LashSchema, LlmCallError, MediaType,
64    Message, MessageOrigin, MessageRole, MessageSequence, ModelToolReturn, ModelToolReturnPart,
65    Part, PartKind, PluginMessage, PluginRuntimeEvent, PreparedPrompt, PromptBuildInput,
66    PromptBuiltin, PromptContext, PromptContribution, PromptContributionGate,
67    PromptContributionSet, PromptFingerprint, PromptLayer, PromptSlot, PromptSlotLayer,
68    PromptTemplate, PromptTemplateEntry, PromptTemplateSection, PruneState, RenderedPrompt,
69    ResolvedPromptLayer, Response, SchemaProjectionOverride, SessionEvent, TextProjectionMetadata,
70    TokenUsage, ToolActivation, ToolAgentExecutableSurface, ToolAgentSurface,
71    ToolArgumentProjectionPolicy, ToolAvailability, ToolAvailabilityConfig, ToolCallOutcome,
72    ToolCallOutput, ToolCallRecord, ToolCallStatus, ToolCancellation, ToolContract, ToolControl,
73    ToolDefinition, ToolFailure, ToolFailureClass, ToolFailureSource, ToolId, ToolManifest,
74    ToolOutputContract, ToolRetryDisposition, ToolRetryPolicy, ToolScheduling, ToolSurface,
75    ToolSurfaceBuildInput, ToolSurfaceEntry, ToolSurfaceOverride, ToolValue, TurnCause, TurnFinish,
76    TurnLimitFinalMessage, TurnOutcome, TurnStop, append_assistant_text_part, build_prompt,
77    build_tool_surface, build_turn, default_prompt_template, head_tail_truncate,
78    messages_are_prompt_resume_safe, normalized_response_parts, prompt_template_fingerprint,
79    prompt_text_fingerprint, prompt_tool_names_fingerprint, reasoning_part,
80    render_turn_causes_prompt, resolve_prompt_layers, shared_parts, validate_tool_input,
81};
82pub use lashlang::{DurabilityTier, InMemoryLashlangArtifactStore, LashlangArtifactStore};
83pub use protocol_build::ProtocolBuildInput;
84pub use tool_registry::{
85    ReconfigureError, ToolRegistry, ToolRestoreReport, ToolSourceHandle, ToolState, ToolStateEntry,
86};
87pub use tool_result::ToolResult;
88#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
89pub struct ProtocolTurnOptions {
90    #[serde(default = "empty_protocol_turn_payload")]
91    pub payload: serde_json::Value,
92}
93
94fn empty_protocol_turn_payload() -> serde_json::Value {
95    serde_json::Value::Object(serde_json::Map::new())
96}
97
98impl Default for ProtocolTurnOptions {
99    fn default() -> Self {
100        Self::empty()
101    }
102}
103
104impl ProtocolTurnOptions {
105    pub fn empty() -> Self {
106        Self {
107            payload: serde_json::Value::Object(serde_json::Map::new()),
108        }
109    }
110
111    pub fn is_empty(&self) -> bool {
112        match &self.payload {
113            serde_json::Value::Object(map) => map.is_empty(),
114            _ => false,
115        }
116    }
117
118    pub fn merged_with_override(&self, override_options: &Self) -> Self {
119        match (&self.payload, &override_options.payload) {
120            (serde_json::Value::Object(base), serde_json::Value::Object(overrides)) => {
121                let mut payload = base.clone();
122                payload.extend(overrides.clone());
123                Self {
124                    payload: serde_json::Value::Object(payload),
125                }
126            }
127            _ => override_options.clone(),
128        }
129    }
130
131    pub fn typed<T>(value: T) -> Result<Self, serde_json::Error>
132    where
133        T: serde::Serialize,
134    {
135        Ok(Self {
136            payload: serde_json::to_value(value)?,
137        })
138    }
139
140    pub fn decode<T>(&self) -> Result<T, serde_json::Error>
141    where
142        T: serde::de::DeserializeOwned,
143    {
144        serde_json::from_value(self.payload.clone())
145    }
146}
147
148#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
149pub struct ProtocolDriverState {
150    pub plugin_id: String,
151    pub payload: serde_json::Value,
152}
153
154impl ProtocolDriverState {
155    pub fn new(plugin_id: impl Into<String>, payload: serde_json::Value) -> Self {
156        Self {
157            plugin_id: plugin_id.into(),
158            payload,
159        }
160    }
161}
162
163#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
164pub struct HostTurnProtocol;
165
166impl lash_sansio::TurnProtocol for HostTurnProtocol {
167    type Event = crate::session_model::ProtocolEvent;
168    type Termination = ProtocolTurnOptions;
169    type DriverState = ProtocolDriverState;
170}
171
172pub type Effect = lash_sansio::Effect<HostTurnProtocol>;
173pub type DriverAction = lash_sansio::DriverAction<HostTurnProtocol>;
174pub type DriverContextView<'a> = lash_sansio::DriverContextView<'a, HostTurnProtocol>;
175pub type TurnDriverConfig = lash_sansio::TurnDriverConfig<HostTurnProtocol>;
176pub type TurnDriverPreamble = lash_sansio::TurnDriverPreamble<HostTurnProtocol>;
177pub type ProjectorContext<'a> = lash_sansio::ProjectorContext<'a, HostTurnProtocol>;
178pub type PreparedTurnMachine = lash_sansio::PreparedTurnMachine<HostTurnProtocol>;
179pub type SansIoTurnInput = lash_sansio::SansIoTurnInput<HostTurnProtocol>;
180pub type TurnMachine = lash_sansio::TurnMachine<HostTurnProtocol>;
181pub type TurnMachineConfig = lash_sansio::TurnMachineConfig<HostTurnProtocol>;
182#[cfg(feature = "otel-trace")]
183pub use lash_trace::otel::{OtelTraceOptions, OtelTraceSink};
184pub use lash_trace::{
185    JsonlTraceSink, TraceAttachment, TraceBranchSelection, TraceContentBlock, TraceContext,
186    TraceError, TraceEvent, TraceLabelMetadata, TraceLashlangChildExecution,
187    TraceLashlangEdgeSelection, TraceLashlangExecutionEvent, TraceLashlangExecutionIdentity,
188    TraceLashlangGraph, TraceLashlangGraphChildLink, TraceLashlangGraphEdge,
189    TraceLashlangGraphNode, TraceLashlangGraphStore, TraceLashlangMap, TraceLashlangMapEdge,
190    TraceLashlangMapNode, TraceLashlangNodeStatus, TraceLashlangStatus, TraceLevel,
191    TraceLlmMessage, TraceLlmRequest, TraceLlmResponse, TracePromptComponent,
192    TraceProviderStreamEvent, TraceRecord, TraceRuntimeScope, TraceRuntimeStreamEvent,
193    TraceRuntimeSubject, TraceSink, TraceSinkError, TraceTokenUsage, TraceToolSpec,
194};
195pub use llm::transport::{LlmTransportError, ProviderFailure, ProviderFailureKind};
196pub use model::{ModelLimits, ModelSpec};
197pub use plugin::{
198    AgentFrameAssignment, AgentFrameId, AgentFrameReason, AgentFrameRecord, AgentFrameStatus,
199    AppendSessionNodesRequest, AppendSessionNodesResult, AssistantResponseHookContext,
200    AssistantResponseTransform, AssistantStreamHookContext, AssistantStreamTransform,
201    CheckpointHookContext, CompactionContext, ContextCompaction, ContextCompactor, ContextError,
202    ContextRegistrations, DirectCompletion, DirectLlmCompletion, HostEventRegistrations,
203    OpenAgentFrameRequest, OpenAgentFrameResult, PersistentRuntimeServices, PluginAction,
204    PluginActionContext, PluginActionDef, PluginActionFailure, PluginActionInvokeError,
205    PluginActionKind, PluginDirective, PluginError, PluginFactory, PluginHost,
206    PluginLifecycleEvent, PluginLifecycleEventHook, PluginOptions, PluginOwned, PluginRegistrar,
207    PluginSession, PluginSessionContext, PluginSessionSnapshot, PluginSnapshotArtifact,
208    PluginSnapshotEntry, PluginSnapshotMeta, PluginSpec, PluginSpecFactory, PromptHookContext,
209    ProtocolBeforeLlmCallContext, ProtocolLlmCallAction, RuntimeServices, SessionAppendNode,
210    SessionConfigChangedContext, SessionContextSurface, SessionCreateRequest, SessionGraphService,
211    SessionHandle, SessionLifecycleService, SessionParam, SessionPlugin, SessionPluginSource,
212    SessionReadView, SessionRelation, SessionSnapshot, SessionStartPoint,
213    SessionStateChangedContext, SessionStateService, SessionToolAccess, SessionTurnInput,
214    SessionTurnRequest, SnapshotReader, SnapshotWriter, SubagentSessionContext,
215    ToolDiscoveryContext, ToolDiscoveryContribution, ToolDiscoveryContributor,
216    ToolDiscoveryToolContribution, ToolResultProjectionContext, ToolResultProjector,
217    ToolSurfaceContribution, TurnContextTransform, TurnHookContext, TurnResultHookContext,
218    TurnResultSummary, TurnTransformContext, plugin_action_def,
219};
220pub use plugin_stack::PluginStack;
221pub use provider::{
222    CacheRetention, EmptyProviderResolver, LlmTimeouts, MapProviderResolver, Provider,
223    ProviderBinding, ProviderComponents, ProviderFactory, ProviderHandle, ProviderModelPolicy,
224    ProviderOptions, ProviderResolutionError, ProviderSpec, RequestTimeout,
225    RuntimeProviderResolver, SingleProviderResolver, StaticModelPolicy,
226};
227#[cfg(any(test, feature = "testing"))]
228pub use runtime::TestLocalProcessRegistry;
229pub use runtime::{
230    AgentFrameRun, AssembledTurn, AssistantOutput, CausalRef, CodeOutputRecord,
231    DefaultProcessCancelAbility, DeliveryPolicy, DirectCompletionClient, DurableProcessWorker,
232    DurableProcessWorkerConfig, DurableStoreFacet, EffectHost, EffectScope, EmbeddedRuntimeBuilder,
233    EmbeddedRuntimeHost, EventSink, ExecutionSummary, InMemoryLiveReplayStore,
234    InMemoryLiveReplayStoreConfig, InMemorySessionStore, InMemorySessionStoreFactory,
235    InlineEffectHost, InlineProcessRunHandle, InlineRuntimeEffectController, InputItem,
236    LashRuntime, LiveReplayGap, LiveReplayGapReason, LiveReplayResult, LiveReplayStore,
237    LiveReplayStoreError, LiveReplaySubscribeResult, LiveReplaySubscription, MergeKey,
238    NoopEventSink, NoopTurnActivitySink, ObservedProcess, ObservedProcessEvent, ObservedWorkItem,
239    OutputState, PROCESS_LEASE_SCHEMA_VERSION, ParkedSession, ProcessAwaitOutput,
240    ProcessCancelAbility, ProcessCancelAllRequest, ProcessCancelRequest, ProcessCancelSource,
241    ProcessCancelSummary, ProcessDefinitionSelector, ProcessDefinitionSummary, ProcessEvent,
242    ProcessEventAppendRequest, ProcessEventAppendResult, ProcessEventType, ProcessExecutionContext,
243    ProcessExecutionEnvRef, ProcessExecutionEnvSpec, ProcessExternalRef, ProcessHandleDescriptor,
244    ProcessHandleGrant, ProcessHandleSummary, ProcessId, ProcessInput, ProcessLease,
245    ProcessLeaseCompletion, ProcessLifecycleStatus, ProcessListFilter, ProcessListMode,
246    ProcessOpScope, ProcessOriginator, ProcessProvenance, ProcessRecord, ProcessRegistration,
247    ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost, ProcessService,
248    ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant, ProcessStartOptions,
249    ProcessStartRequest,
250    ProcessStatus, ProcessStatusFilter, ProcessTerminalSemantics, ProcessTerminalSpec,
251    ProcessTerminalState, ProcessValueSelector, ProcessWake, ProcessWakeDedupeKey,
252    ProcessWakeDelivery, ProcessWakeSpec, ProcessWorkDriver, ProcessWorkObserver, ProcessWorkPoke,
253    ProcessWorkRunner, ProcessWorkSnapshot, PromptUsage, ProtocolSessionExtension,
254    ProtocolSessionExtensionHandle, ProtocolTurnExtension, ProtocolTurnExtensionHandle,
255    QueuedWorkPoke, QueuedWorkRunHandle, QueuedWorkRunOutcome, QueuedWorkRunRequest,
256    QueuedWorkRunner, Residency, RuntimeEnvironment, RuntimeEnvironmentBuilder, RuntimeError,
257    RuntimeErrorCode, RuntimeHandle, RuntimeHostConfig, RuntimeObservation, ScopedEffectController,
258    SessionCommand, SessionCommandReceipt, SessionCursor, SessionCursorError, SessionObservation,
259    SessionObservationEvent, SessionObservationEventPayload, SessionObservationSubscription,
260    SessionProcessEventKind, SessionQueueEventKind, SessionResume, SessionRevision, SessionScope,
261    SessionScopeId, SessionStoreCreateRequest, SessionStoreFactory, SessionUsageReport, SlotPolicy,
262    TerminationPolicy, TokenLedgerEntry, TurnActivity, TurnActivityId, TurnActivitySink,
263    TurnContext, TurnEvent, TurnInput, TurnIssue, TurnOptions, UnavailableProcessService,
264    UsageReportRow, UsageTotals, WaitKind, WaitState, current_epoch_ms, diff_token_ledger,
265    diff_usage_reports, ensure_durable_effect_input, epoch_ms_from_system_time,
266    lashlang_process_event_types, lashlang_process_signal_event_types, process_signal_event_type,
267    process_signal_name_from_event_type, process_signal_wait_key, system_time_from_epoch_ms,
268    validate_process_signal_name,
269};
270#[allow(unused_imports)]
271pub(crate) use runtime::{
272    LlmAttachmentSpec, PreparedProcessEventAppend, ProcessEventSemantics, QUEUED_WORK_CLAIM_TTL_MS,
273    QueuedCheckpointWork, QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
274    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
275    RuntimeReplay, RuntimeScope, RuntimeSubject, load_process_execution_env,
276    materialize_process_event_semantics, persist_process_execution_env,
277    prepare_process_event_append, prepare_process_registration, process_event_invocation,
278    process_event_payload_hash, process_wake_batch_draft, process_wake_delivery,
279    process_wake_input_from_event_payload, process_wake_turn_cause, process_wake_turn_text,
280    require_event_replay,
281};
282// Effect / process-control types consumed by external effect hosts (e.g.
283// lash-restate's workflows) and their integration tests. Kept on the public
284// surface; the rest of the runtime block above stays crate-internal.
285pub use runtime::{
286    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
287    RuntimeEffectCommand, RuntimeEffectController, RuntimeEffectControllerError,
288    RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome,
289    RuntimeInvocation, RuntimeSessionState,
290};
291pub use schemars::JsonSchema;
292pub use session::{
293    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
294    ToolInvocationReply,
295};
296pub use session_graph::{
297    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
298    SessionNodePayload, SessionNodeRecord,
299};
300pub use session_model::context::PreparedContext;
301pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
302pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
303pub use store::{
304    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
305    RuntimePersistence, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError, VacuumReport,
306};
307#[allow(unused_imports)]
308pub(crate) use store::{
309    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
310    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
311    load_persisted_session_state_active_path,
312};
313pub use store::{
314    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
315    refresh_persisted_session_state,
316};
317pub use tool_provider::{
318    PreparedToolCall, ProgressSender, SandboxMessage, ToolCall, ToolContext, ToolHostEventControl,
319    ToolLashlangExecutionCallSite, ToolPrepareCall, ToolPrepareContext, ToolProvider,
320    ToolSessionControl, ToolSessionModel,
321};
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
329        let options: ProtocolTurnOptions =
330            serde_json::from_value(serde_json::json!({})).expect("deserialize options");
331
332        assert!(options.is_empty());
333        assert_eq!(options.payload, serde_json::json!({}));
334    }
335
336    #[test]
337    fn protocol_turn_options_explicit_null_is_not_empty() {
338        let options: ProtocolTurnOptions =
339            serde_json::from_value(serde_json::json!({ "payload": null }))
340                .expect("deserialize options");
341
342        assert!(!options.is_empty());
343        assert_eq!(options.payload, serde_json::Value::Null);
344    }
345
346    #[test]
347    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
348        let source = include_str!("lib.rs");
349        let removed_envelope = ["SessionState", "Envelope"].concat();
350        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
351        let removed_history_rewriter = ["History", "Rewriter"].concat();
352        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
353        let removed_rewrite_context = ["Rewrite", "Context"].concat();
354        let removed_history_state = ["History", "State"].concat();
355        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
356
357        assert!(!source.contains(&removed_envelope));
358        assert!(!source.contains(&removed_persisted));
359        assert!(!source.contains(&removed_history_rewriter));
360        assert!(!source.contains(&removed_rewrite_trigger));
361        assert!(!source.contains(&removed_rewrite_context));
362        assert!(!source.contains(&removed_history_state));
363        assert!(!source.contains(&removed_history_metadata));
364    }
365
366    fn public_reexport_block(source: &str, module: &str) -> String {
367        let start = format!("pub use {module}::{{");
368        let mut block = String::new();
369        let mut collecting = false;
370        for line in source.lines() {
371            if line.trim_start().starts_with(&start) {
372                collecting = true;
373            }
374            if collecting {
375                block.push_str(line);
376                block.push('\n');
377                if line.trim_end() == "};" {
378                    break;
379                }
380            }
381        }
382        assert!(!block.is_empty(), "missing public {module} re-export block");
383        block
384    }
385
386    #[test]
387    fn root_runtime_exports_exclude_internal_runtime_records() {
388        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
389        for removed in [
390            "RuntimeEffectCommand",
391            "RuntimeEffectEnvelope",
392            "RuntimeEffectKind",
393            "RuntimeEffectOutcome",
394            "RuntimeInvocation",
395            "RuntimeScope",
396            "RuntimeSessionState",
397            "QueuedWorkBatch",
398            "QueuedWorkBatchDraft",
399            "QueuedWorkPayload",
400            "prepare_process_registration",
401            "process_wake_batch_draft",
402            "require_event_replay",
403        ] {
404            assert!(
405                !runtime_exports.contains(removed),
406                "runtime root export leaked {removed}"
407            );
408        }
409    }
410
411    #[test]
412    fn root_store_exports_exclude_wire_records() {
413        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
414        for removed in [
415            "SessionHead",
416            "SessionCheckpoint",
417            "RuntimeCommit",
418            "HydratedSessionCheckpoint",
419            "PersistedSessionRead",
420            "GraphCommitDelta",
421        ] {
422            assert!(
423                !store_exports.contains(removed),
424                "store root export leaked {removed}"
425            );
426        }
427    }
428
429    #[test]
430    fn removed_manager_and_host_trait_names_stay_removed() {
431        let removed_manager = ["Runtime", "Session", "Manager"].concat();
432        let removed_host = ["Runtime", "Session", "Host"].concat();
433        let sources = [
434            include_str!("runtime/session_manager/mod.rs"),
435            include_str!("plugin/runtime_host.rs"),
436            include_str!("tool_dispatch/context.rs"),
437            include_str!("tool_provider.rs"),
438        ];
439
440        for source in sources {
441            assert!(!source.contains(&removed_manager));
442            assert!(!source.contains(&removed_host));
443        }
444    }
445}