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, InMemoryAttachmentStore,
35    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, 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, ProviderThinkingPolicy, 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    ProcessExternalRef, ProcessHandleDescriptor, ProcessHandleGrant, ProcessHandleSummary,
244    ProcessId, ProcessInput, ProcessLease, ProcessLeaseCompletion, ProcessLifecycleStatus,
245    ProcessListFilter, ProcessListMode, ProcessOpScope, ProcessProvenance, ProcessRecord,
246    ProcessRegistration, ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost, ProcessScope,
247    ProcessScopeId, ProcessService, ProcessSessionDeleteReport, ProcessStartGrant,
248    ProcessStartOptions, ProcessStartRequest, ProcessStatus, ProcessStatusFilter,
249    ProcessTerminalSemantics, ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector,
250    ProcessWake, ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeSpec, ProcessWorkDriver,
251    ProcessWorkObserver, ProcessWorkPoke, ProcessWorkRunner, ProcessWorkSnapshot, PromptUsage,
252    ProtocolSessionExtension, ProtocolSessionExtensionHandle, ProtocolTurnExtension,
253    ProtocolTurnExtensionHandle, QueuedWorkPoke, QueuedWorkRunHandle, QueuedWorkRunOutcome,
254    QueuedWorkRunRequest, QueuedWorkRunner, Residency, RuntimeEnvironment,
255    RuntimeEnvironmentBuilder, RuntimeError, RuntimeErrorCode, RuntimeHandle, RuntimeHostConfig,
256    RuntimeObservation, ScopedEffectController, SessionCommand, SessionCommandReceipt,
257    SessionCursor, SessionCursorError, SessionObservation, SessionObservationEvent,
258    SessionObservationEventPayload, SessionObservationSubscription, SessionProcessEventKind,
259    SessionQueueEventKind, SessionResume, SessionRevision, SessionStoreCreateRequest,
260    SessionStoreFactory, SessionUsageReport, SlotPolicy, TerminationPolicy, TokenLedgerEntry,
261    TurnActivity, TurnActivityId, TurnActivitySink, TurnContext, TurnEvent, TurnInput, TurnIssue,
262    TurnOptions, UnavailableProcessService, UsageReportRow, UsageTotals, current_epoch_ms,
263    diff_token_ledger, diff_usage_reports, ensure_durable_effect_input, epoch_ms_from_system_time,
264    lashlang_process_event_types, system_time_from_epoch_ms,
265};
266#[allow(unused_imports)]
267pub(crate) use runtime::{
268    LlmAttachmentSpec, PreparedProcessEventAppend, ProcessEventSemantics, QUEUED_WORK_CLAIM_TTL_MS,
269    QueuedCheckpointWork, QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
270    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
271    RuntimeReplay, RuntimeScope, RuntimeSubject, materialize_process_event_semantics,
272    prepare_process_event_append, prepare_process_registration, process_event_invocation,
273    process_event_payload_hash, process_wake_batch_draft, process_wake_delivery,
274    process_wake_input_from_event_payload, process_wake_turn_cause, process_wake_turn_text,
275    require_event_replay,
276};
277// Effect / process-control types consumed by external effect hosts (e.g.
278// lash-restate's workflows) and their integration tests. Kept on the public
279// surface; the rest of the runtime block above stays crate-internal.
280pub use runtime::{
281    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
282    RuntimeEffectCommand, RuntimeEffectController, RuntimeEffectControllerError,
283    RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome,
284    RuntimeInvocation, RuntimeSessionState,
285};
286pub use schemars::JsonSchema;
287pub use session::{
288    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
289    ToolInvocationReply,
290};
291pub use session_graph::{
292    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
293    SessionNodePayload, SessionNodeRecord,
294};
295pub use session_model::context::PreparedContext;
296pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
297pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
298pub use store::{
299    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
300    RuntimePersistence, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError, VacuumReport,
301};
302#[allow(unused_imports)]
303pub(crate) use store::{
304    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
305    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
306    load_persisted_session_state_active_path,
307};
308pub use store::{
309    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
310    refresh_persisted_session_state,
311};
312pub use tool_provider::{
313    PreparedToolCall, ProgressSender, SandboxMessage, ToolCall, ToolContext, ToolHostEventControl,
314    ToolLashlangExecutionCallSite, ToolPrepareCall, ToolPrepareContext, ToolProvider,
315    ToolSessionControl, ToolSessionModel,
316};
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    #[test]
323    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
324        let options: ProtocolTurnOptions =
325            serde_json::from_value(serde_json::json!({})).expect("deserialize options");
326
327        assert!(options.is_empty());
328        assert_eq!(options.payload, serde_json::json!({}));
329    }
330
331    #[test]
332    fn protocol_turn_options_explicit_null_is_not_empty() {
333        let options: ProtocolTurnOptions =
334            serde_json::from_value(serde_json::json!({ "payload": null }))
335                .expect("deserialize options");
336
337        assert!(!options.is_empty());
338        assert_eq!(options.payload, serde_json::Value::Null);
339    }
340
341    #[test]
342    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
343        let source = include_str!("lib.rs");
344        let removed_envelope = ["SessionState", "Envelope"].concat();
345        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
346        let removed_history_rewriter = ["History", "Rewriter"].concat();
347        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
348        let removed_rewrite_context = ["Rewrite", "Context"].concat();
349        let removed_history_state = ["History", "State"].concat();
350        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
351
352        assert!(!source.contains(&removed_envelope));
353        assert!(!source.contains(&removed_persisted));
354        assert!(!source.contains(&removed_history_rewriter));
355        assert!(!source.contains(&removed_rewrite_trigger));
356        assert!(!source.contains(&removed_rewrite_context));
357        assert!(!source.contains(&removed_history_state));
358        assert!(!source.contains(&removed_history_metadata));
359    }
360
361    fn public_reexport_block(source: &str, module: &str) -> String {
362        let start = format!("pub use {module}::{{");
363        let mut block = String::new();
364        let mut collecting = false;
365        for line in source.lines() {
366            if line.trim_start().starts_with(&start) {
367                collecting = true;
368            }
369            if collecting {
370                block.push_str(line);
371                block.push('\n');
372                if line.trim_end() == "};" {
373                    break;
374                }
375            }
376        }
377        assert!(!block.is_empty(), "missing public {module} re-export block");
378        block
379    }
380
381    #[test]
382    fn root_runtime_exports_exclude_internal_runtime_records() {
383        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
384        for removed in [
385            "RuntimeEffectCommand",
386            "RuntimeEffectEnvelope",
387            "RuntimeEffectKind",
388            "RuntimeEffectOutcome",
389            "RuntimeInvocation",
390            "RuntimeScope",
391            "RuntimeSessionState",
392            "QueuedWorkBatch",
393            "QueuedWorkBatchDraft",
394            "QueuedWorkPayload",
395            "prepare_process_registration",
396            "process_wake_batch_draft",
397            "require_event_replay",
398        ] {
399            assert!(
400                !runtime_exports.contains(removed),
401                "runtime root export leaked {removed}"
402            );
403        }
404    }
405
406    #[test]
407    fn root_store_exports_exclude_wire_records() {
408        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
409        for removed in [
410            "SessionHead",
411            "SessionCheckpoint",
412            "RuntimeCommit",
413            "HydratedSessionCheckpoint",
414            "PersistedSessionRead",
415            "GraphCommitDelta",
416        ] {
417            assert!(
418                !store_exports.contains(removed),
419                "store root export leaked {removed}"
420            );
421        }
422    }
423
424    #[test]
425    fn removed_manager_and_host_trait_names_stay_removed() {
426        let removed_manager = ["Runtime", "Session", "Manager"].concat();
427        let removed_host = ["Runtime", "Session", "Host"].concat();
428        let sources = [
429            include_str!("runtime/session_manager/mod.rs"),
430            include_str!("plugin/runtime_host.rs"),
431            include_str!("tool_dispatch/context.rs"),
432            include_str!("tool_provider.rs"),
433        ];
434
435        for source in sources {
436            assert!(!source.contains(&removed_manager));
437            assert!(!source.contains(&removed_host));
438        }
439    }
440}