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