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, ProcessEvent, ProcessEventAppendRequest, ProcessEventAppendResult,
243    ProcessEventType, ProcessExecutionContext, ProcessExecutionEnvRef, ProcessExecutionEnvSpec,
244    ProcessExternalRef, ProcessHandleDescriptor, ProcessHandleGrant, ProcessHandleSummary,
245    ProcessId, ProcessInput, ProcessLease, ProcessLeaseCompletion, ProcessLifecycleStatus,
246    ProcessListFilter, ProcessListMode, ProcessOpScope, ProcessOriginator, ProcessProvenance,
247    ProcessRecord, ProcessRegistration, ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost,
248    ProcessService, ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant,
249    ProcessStartOptions, ProcessStartRequest, ProcessStatus, ProcessStatusFilter,
250    ProcessTerminalSemantics, ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector,
251    ProcessWake, ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeSpec, ProcessWorkDriver,
252    ProcessWorkObserver, ProcessWorkPoke, ProcessWorkRunner, ProcessWorkSnapshot, PromptUsage,
253    ProtocolSessionExtension, ProtocolSessionExtensionHandle, ProtocolTurnExtension,
254    ProtocolTurnExtensionHandle, QueuedWorkPoke, QueuedWorkRunHandle, QueuedWorkRunOutcome,
255    QueuedWorkRunRequest, QueuedWorkRunner, Residency, Resolution, ResolveOutcome,
256    RuntimeEnvironment, RuntimeEnvironmentBuilder, RuntimeError, RuntimeErrorCode, RuntimeHandle,
257    RuntimeHostConfig, RuntimeObservation, ScopedEffectController, SessionCommand,
258    SessionCommandReceipt, SessionCursor, SessionCursorError, SessionObservation,
259    SessionObservationEvent, SessionObservationEventPayload, SessionObservationSubscription,
260    SessionProcessEventKind, SessionQueueEventKind, SessionResume, SessionRevision, SessionScope,
261    SessionScopeId, SessionStoreCreateRequest, SessionStoreFactory, SessionUsageReport, SlotPolicy,
262    TerminationPolicy, TokenLedgerEntry, ToolCallLaunch, TurnActivity, TurnActivityId,
263    TurnActivitySink, TurnContext, TurnEvent, TurnInput, TurnIssue, TurnOptions,
264    UnavailableProcessService, UsageReportRow, UsageTotals, WaitKind, WaitState, current_epoch_ms,
265    diff_token_ledger, 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, ToolDurableEffects,
319    ToolLashlangExecutionCallSite, ToolPrepareCall, ToolPrepareContext, ToolProvider,
320    ToolSessionAdmin, ToolSessionModel, ToolTriggerClient,
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}