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