Skip to main content

lash_core/
lib.rs

1pub mod attachments;
2pub mod chronological;
3pub mod direct;
4pub mod lashlang_bridge;
5pub mod llm;
6mod model;
7pub mod plugin;
8mod plugin_stack;
9mod protocol_build;
10pub mod provider;
11pub mod runtime;
12pub mod search;
13pub mod session;
14pub mod session_graph;
15pub mod session_model;
16mod stable_hash;
17pub mod store;
18#[cfg(any(test, feature = "testing"))]
19pub mod testing;
20pub mod tool_dispatch;
21mod tool_provider;
22pub mod tool_registry;
23mod tool_result;
24mod trace;
25pub mod triggers;
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 lash_sansio::llm::types::{
49    GenerationOptions, LlmOutputPart, LlmRequest, LlmResponse, LlmTerminalReason,
50};
51pub use lash_sansio::{
52    AcceptedInjectedTurnInput, AttachmentCreateMeta, AttachmentId, AttachmentMeta, AttachmentRef,
53    BaseRenderCache, CheckpointDelivery, CheckpointKind, CompactToolContract, EffectId,
54    ErrorEnvelope, ExecImage, ExecResponse, ImageMediaType, LashSchema, LashlangToolBinding,
55    LlmCallError, MediaType, Message, MessageOrigin, MessageRole, MessageSequence, ModelToolReturn,
56    ModelToolReturnPart, Part, PartKind, PluginMessage, PluginRuntimeEvent, PreparedPrompt,
57    PromptBuildInput, PromptBuiltin, PromptContext, PromptContribution, PromptContributionGate,
58    PromptContributionSet, PromptFingerprint, PromptLayer, PromptSlot, PromptSlotLayer,
59    PromptTemplate, PromptTemplateEntry, PromptTemplateSection, PruneState, RenderedPrompt,
60    ResolvedLashlangToolBinding, ResolvedPromptLayer, Response, SchemaProjectionOverride,
61    SessionEvent, TextProjectionMetadata, TokenUsage, ToolActivation, ToolArgumentProjectionPolicy,
62    ToolAvailability, ToolAvailabilityConfig, ToolCallOutcome, ToolCallOutput, ToolCallRecord,
63    ToolCallStatus, ToolCancellation, ToolCatalog, ToolCatalogBuildInput, ToolCatalogEntry,
64    ToolCatalogOverride, ToolContract, ToolControl, ToolDefinition, ToolFailure, ToolFailureClass,
65    ToolFailureSource, ToolId, ToolManifest, ToolOutputContract, ToolRetryDisposition,
66    ToolRetryPolicy, ToolScheduling, ToolValue, TurnCause, TurnFinish, TurnLimitFinalMessage,
67    TurnOutcome, TurnStop, append_assistant_text_part, build_prompt, build_tool_catalog,
68    build_turn, default_prompt_template, head_tail_truncate, messages_are_prompt_resume_safe,
69    normalized_response_parts, prompt_template_fingerprint, prompt_text_fingerprint,
70    prompt_tool_names_fingerprint, reasoning_part, render_turn_causes_prompt,
71    resolve_prompt_layers, shared_parts, validate_tool_input,
72};
73pub use lashlang::{DurabilityTier, InMemoryLashlangArtifactStore, LashlangArtifactStore};
74pub use protocol_build::ProtocolBuildInput;
75pub use tool_registry::{
76    ReconfigureError, ToolRegistry, ToolRestoreReport, ToolSourceHandle, ToolState, ToolStateEntry,
77};
78pub use tool_result::{CancelHint, PendingCompletion, TimeoutBehavior, ToolResult};
79pub use triggers::{
80    InMemoryTriggerStore, TriggerDeliveryReservation, TriggerEmitReport, TriggerEvent,
81    TriggerEventCatalog, TriggerEventKey, TriggerEventType, TriggerOccurrenceRecord,
82    TriggerOccurrenceRequest, TriggerRegistration, TriggerRouter, TriggerStore,
83    TriggerSubscriptionDraft, TriggerSubscriptionFilter, TriggerSubscriptionRecord,
84    TriggerTargetSummary, default_trigger_source_key, deterministic_delivery_process_id,
85    deterministic_occurrence_id, empty_trigger_source_key, trigger_event_type,
86    trigger_occurrence_request_hash, validate_payload, validate_trigger_occurrence_request,
87};
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, OpenAgentFrameRequest,
203    OpenAgentFrameResult, PersistentRuntimeServices, PluginAction, PluginActionContext,
204    PluginActionDef, PluginActionFailure, PluginActionInvokeError, PluginActionKind,
205    PluginDirective, PluginError, PluginFactory, PluginHost, PluginLifecycleEvent,
206    PluginLifecycleEventHook, PluginOptions, PluginOwned, PluginRegistrar, PluginSession,
207    PluginSessionContext, PluginSessionSnapshot, PluginSnapshotArtifact, PluginSnapshotEntry,
208    PluginSnapshotMeta, PluginSpec, PluginSpecFactory, PromptHookContext,
209    ProtocolBeforeLlmCallContext, ProtocolLlmCallAction, RuntimeServices, SessionAppendNode,
210    SessionConfigChangedContext, SessionContextOverlay, SessionCreateRequest, SessionGraphService,
211    SessionHandle, SessionLifecycleService, SessionParam, SessionPlugin, SessionPluginSource,
212    SessionReadView, SessionRelation, SessionSnapshot, SessionStartPoint,
213    SessionStateChangedContext, SessionStateService, SessionToolAccess, SessionTurnInput,
214    SessionTurnRequest, SnapshotReader, SnapshotWriter, SubagentSessionContext,
215    ToolCatalogContribution, ToolDiscoveryContext, ToolDiscoveryContribution,
216    ToolDiscoveryContributor, ToolDiscoveryToolContribution, ToolResultProjectionContext,
217    ToolResultProjector, TriggerEventRegistrations, TurnContextTransform, TurnHookContext,
218    TurnResultHookContext, 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, AwaitEventKey, AwaitEventWaitIdentity,
231    CausalRef, CodeOutputRecord, DefaultProcessCancelAbility, DeliveryPolicy,
232    DirectCompletionClient, DurableProcessWorker, DurableProcessWorkerConfig, DurableStoreFacet,
233    EffectHost, EmbeddedRuntimeBuilder, EmbeddedRuntimeHost, EventSink, ExecutionScope,
234    ExecutionSummary, ExternalCompletionError, InMemoryLiveReplayStore,
235    InMemoryLiveReplayStoreConfig, InMemorySessionStore, InMemorySessionStoreFactory,
236    InlineEffectHost, InlineProcessRunHandle, InlineRuntimeEffectController, InputItem,
237    LashRuntime, LiveReplayGap, LiveReplayGapReason, LiveReplayResult, LiveReplayStore,
238    LiveReplayStoreError, LiveReplaySubscribeResult, LiveReplaySubscription, MergeKey,
239    NoopEventSink, NoopTurnActivitySink, ObservedProcess, ObservedProcessEvent, ObservedWorkItem,
240    OutputState, PROCESS_LEASE_SCHEMA_VERSION, ParkedSession, ProcessAwaitOutput,
241    ProcessCancelAbility, ProcessCancelAllRequest, ProcessCancelRequest, ProcessCancelSource,
242    ProcessCancelSummary, ProcessDefinitionSelector, ProcessDefinitionSummary, ProcessEvent,
243    ProcessEventAppendRequest, ProcessEventAppendResult, ProcessEventType, ProcessExecutionContext,
244    ProcessExecutionEnvRef, ProcessExecutionEnvSpec, ProcessExternalRef, ProcessHandleDescriptor,
245    ProcessHandleGrant, ProcessHandleSummary, ProcessId, ProcessInput, ProcessLease,
246    ProcessLeaseCompletion, ProcessLifecycleStatus, ProcessListFilter, ProcessListMode,
247    ProcessOpScope, ProcessOriginator, ProcessProvenance, ProcessRecord, ProcessRegistration,
248    ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost, ProcessService,
249    ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant, ProcessStartOptions,
250    ProcessStartRequest, ProcessStatus, ProcessStatusFilter, ProcessTerminalSemantics,
251    ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector, ProcessWake,
252    ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeSpec, ProcessWorkDriver,
253    ProcessWorkObserver, ProcessWorkPoke, ProcessWorkRunner, ProcessWorkSnapshot, PromptUsage,
254    ProtocolSessionExtension, ProtocolSessionExtensionHandle, ProtocolTurnExtension,
255    ProtocolTurnExtensionHandle, QueuedWorkPoke, QueuedWorkRunHandle, QueuedWorkRunOutcome,
256    QueuedWorkRunRequest, QueuedWorkRunner, Residency, Resolution, ResolveOutcome,
257    RuntimeEnvironment, RuntimeEnvironmentBuilder, RuntimeError, RuntimeErrorCode, RuntimeHandle,
258    RuntimeHostConfig, RuntimeObservation, ScopedEffectController, SessionCommand,
259    SessionCommandReceipt, SessionCursor, SessionCursorError, SessionObservation,
260    SessionObservationEvent, SessionObservationEventPayload, SessionObservationSubscription,
261    SessionProcessEventKind, SessionQueueEventKind, SessionResume, SessionRevision, SessionScope,
262    SessionScopeId, SessionStoreCreateRequest, SessionStoreFactory, SessionUsageReport, SlotPolicy,
263    TerminationPolicy, TokenLedgerEntry, ToolCallLaunch, TurnActivity, TurnActivityId,
264    TurnActivitySink, TurnContext, TurnEvent, TurnInput, TurnIssue, TurnOptions,
265    UnavailableProcessService, UsageReportRow, UsageTotals, WaitKind, WaitState, current_epoch_ms,
266    diff_token_ledger, diff_usage_reports, ensure_durable_effect_input, epoch_ms_from_system_time,
267    lashlang_process_event_types, lashlang_process_signal_event_types, process_signal_event_type,
268    process_signal_name_from_event_type, process_signal_wait_key, system_time_from_epoch_ms,
269    validate_process_signal_name,
270};
271#[allow(unused_imports)]
272pub(crate) use runtime::{
273    LlmAttachmentSpec, PreparedProcessEventAppend, ProcessEventSemantics, QUEUED_WORK_CLAIM_TTL_MS,
274    QueuedCheckpointWork, QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
275    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
276    RuntimeReplay, RuntimeScope, RuntimeSubject, load_process_execution_env,
277    materialize_process_event_semantics, persist_process_execution_env,
278    prepare_process_event_append, prepare_process_registration, process_event_invocation,
279    process_event_payload_hash, process_wake_batch_draft, process_wake_delivery,
280    process_wake_input_from_event_payload, process_wake_turn_cause, process_wake_turn_text,
281    require_event_replay,
282};
283// Effect / process-control types consumed by external effect hosts (e.g.
284// lash-restate's workflows) and their integration tests. Kept on the public
285// surface; the rest of the runtime block above stays crate-internal.
286pub use runtime::{
287    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
288    RuntimeEffectCommand, RuntimeEffectController, RuntimeEffectControllerError,
289    RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome,
290    RuntimeInvocation, RuntimeSessionState,
291};
292pub use schemars::JsonSchema;
293pub use session::{
294    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
295    ToolInvocationReply,
296};
297pub use session_graph::{
298    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
299    SessionNodePayload, SessionNodeRecord,
300};
301pub use session_model::context::PreparedContext;
302pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
303pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
304pub use store::{
305    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
306    RuntimePersistence, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError, VacuumReport,
307};
308#[allow(unused_imports)]
309pub(crate) use store::{
310    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
311    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
312    load_persisted_session_state_active_path,
313};
314pub use store::{
315    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
316    refresh_persisted_session_state,
317};
318pub use tool_provider::{
319    PreparedToolCall, ProgressSender, SandboxMessage, ToolCall, ToolContext,
320    ToolLashlangExecutionCallSite, ToolPrepareCall, ToolPrepareContext, ToolProvider,
321    ToolSessionAdmin, ToolSessionModel, ToolTriggerClient,
322};
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
330        let options: ProtocolTurnOptions =
331            serde_json::from_value(serde_json::json!({})).expect("deserialize options");
332
333        assert!(options.is_empty());
334        assert_eq!(options.payload, serde_json::json!({}));
335    }
336
337    #[test]
338    fn protocol_turn_options_explicit_null_is_not_empty() {
339        let options: ProtocolTurnOptions =
340            serde_json::from_value(serde_json::json!({ "payload": null }))
341                .expect("deserialize options");
342
343        assert!(!options.is_empty());
344        assert_eq!(options.payload, serde_json::Value::Null);
345    }
346
347    #[test]
348    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
349        let source = include_str!("lib.rs");
350        let removed_envelope = ["SessionState", "Envelope"].concat();
351        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
352        let removed_history_rewriter = ["History", "Rewriter"].concat();
353        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
354        let removed_rewrite_context = ["Rewrite", "Context"].concat();
355        let removed_history_state = ["History", "State"].concat();
356        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
357
358        assert!(!source.contains(&removed_envelope));
359        assert!(!source.contains(&removed_persisted));
360        assert!(!source.contains(&removed_history_rewriter));
361        assert!(!source.contains(&removed_rewrite_trigger));
362        assert!(!source.contains(&removed_rewrite_context));
363        assert!(!source.contains(&removed_history_state));
364        assert!(!source.contains(&removed_history_metadata));
365    }
366
367    fn public_reexport_block(source: &str, module: &str) -> String {
368        let start = format!("pub use {module}::{{");
369        let mut block = String::new();
370        let mut collecting = false;
371        for line in source.lines() {
372            if line.trim_start().starts_with(&start) {
373                collecting = true;
374            }
375            if collecting {
376                block.push_str(line);
377                block.push('\n');
378                if line.trim_end() == "};" {
379                    break;
380                }
381            }
382        }
383        assert!(!block.is_empty(), "missing public {module} re-export block");
384        block
385    }
386
387    #[test]
388    fn root_runtime_exports_exclude_internal_runtime_records() {
389        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
390        for removed in [
391            "RuntimeEffectCommand",
392            "RuntimeEffectEnvelope",
393            "RuntimeEffectKind",
394            "RuntimeEffectOutcome",
395            "RuntimeInvocation",
396            "RuntimeScope",
397            "RuntimeSessionState",
398            "QueuedWorkBatch",
399            "QueuedWorkBatchDraft",
400            "QueuedWorkPayload",
401            "prepare_process_registration",
402            "process_wake_batch_draft",
403            "require_event_replay",
404        ] {
405            assert!(
406                !runtime_exports.contains(removed),
407                "runtime root export leaked {removed}"
408            );
409        }
410    }
411
412    #[test]
413    fn root_store_exports_exclude_wire_records() {
414        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
415        for removed in [
416            "SessionHead",
417            "SessionCheckpoint",
418            "RuntimeCommit",
419            "HydratedSessionCheckpoint",
420            "PersistedSessionRead",
421            "GraphCommitDelta",
422        ] {
423            assert!(
424                !store_exports.contains(removed),
425                "store root export leaked {removed}"
426            );
427        }
428    }
429
430    #[test]
431    fn removed_manager_and_host_trait_names_stay_removed() {
432        let removed_manager = ["Runtime", "Session", "Manager"].concat();
433        let removed_host = ["Runtime", "Session", "Host"].concat();
434        let sources = [
435            include_str!("runtime/session_manager/mod.rs"),
436            include_str!("plugin/runtime_host.rs"),
437            include_str!("tool_dispatch/context.rs"),
438            include_str!("tool_provider.rs"),
439        ];
440
441        for source in sources {
442            assert!(!source.contains(&removed_manager));
443            assert!(!source.contains(&removed_host));
444        }
445    }
446}