Skip to main content

juncture_core/
lib.rs

1#[cfg(feature = "chat")]
2pub mod chat;
3pub mod checkpoint;
4#[cfg(all(feature = "chat", not(target_family = "wasm")))]
5pub mod client;
6pub mod command;
7pub mod config;
8pub mod edge;
9pub mod error;
10pub mod func;
11pub mod graph;
12pub mod interrupt;
13pub mod llm;
14pub mod node;
15pub mod observability;
16pub mod prebuilt;
17pub mod pregel;
18pub mod runtime;
19pub mod send;
20pub mod state;
21pub mod store;
22pub mod stream;
23pub mod subgraph;
24pub mod time;
25pub mod tools;
26pub mod tracing_wasm;
27pub mod wasm_send;
28
29/// Interrupt macro for human-in-the-loop interactions (task-local version)
30///
31/// When called, execution either returns a resume value (if resuming)
32/// or sends an interrupt signal and returns an error.
33///
34/// This macro uses task-local storage to access the interrupt context,
35/// so it doesn't need to be passed explicitly. The task-local must be
36/// set by the Pregel engine before spawning node tasks.
37///
38/// # Syntax
39///
40/// ```ignore
41/// // Anonymous interrupt (auto-generated ID from node name + index):
42/// interrupt!(payload)
43///
44/// // Named interrupt (user-specified ID for targeted resume):
45/// interrupt!(id, payload)
46/// ```
47///
48/// # Examples
49///
50/// ```ignore
51/// use juncture_core::interrupt;
52/// use serde_json::json;
53///
54/// async fn my_node(state: MyState) -> Result<MyStateUpdate, JunctureError> {
55///     // Anonymous interrupt -- ID is auto-generated from node + index
56///     let decision: serde_json::Value = interrupt!(
57///         json!({"question": "Continue?", "options": ["yes", "no"]})
58///     )?;
59///
60///     // Named interrupt -- caller can resume by ID
61///     let approval: serde_json::Value = interrupt!(
62///         "approve_step",
63///         json!({"question": "Approve this action?", "action": "delete"})
64///     )?;
65///
66///     Ok(MyStateUpdate::default())
67/// }
68/// ```
69#[macro_export]
70macro_rules! interrupt {
71    // Named interrupt: interrupt!("my_id", json!({...}))
72    ($id:expr, $payload:expr) => {{
73        $crate::interrupt::INTERRUPT_CONTEXT
74            .try_with(|ctx| {
75                Box::pin($crate::interrupt::__interrupt_impl(
76                    &**ctx,
77                    ::serde_json::to_value(&$payload)
78                        .expect("interrupt payload must be serializable"),
79                    Some($id),
80                ))
81                .await
82            })
83            .unwrap_or_else(|_| {
84                Err($crate::JunctureError::execution(
85                    "interrupt context not set in task-local",
86                ))
87            })
88    }};
89    // Anonymous interrupt: interrupt!(json!({...}))
90    ($payload:expr) => {{
91        $crate::interrupt::INTERRUPT_CONTEXT
92            .try_with(|ctx| {
93                Box::pin($crate::interrupt::__interrupt_impl(
94                    &**ctx,
95                    ::serde_json::to_value(&$payload)
96                        .expect("interrupt payload must be serializable"),
97                    None,
98                ))
99                .await
100            })
101            .unwrap_or_else(|_| {
102                Err($crate::JunctureError::execution(
103                    "interrupt context not set in task-local",
104                ))
105            })
106    }};
107}
108
109/// Interrupt macro for human-in-the-loop interactions (explicit context)
110///
111/// When called, execution either returns a resume value (if resuming)
112/// or sends an interrupt signal and returns an error.
113///
114/// This macro requires the context to be passed explicitly. Use the
115/// `interrupt!` macro for the task-local version.
116///
117/// # Syntax
118///
119/// ```ignore
120/// // Anonymous interrupt (auto-generated ID from node name + index):
121/// interrupt_with_ctx!(context, payload)
122///
123/// // Named interrupt (user-specified ID for targeted resume):
124/// interrupt_with_ctx!(context, id, payload)
125/// ```
126///
127/// # Examples
128///
129/// ```ignore
130/// use juncture_core::interrupt;
131/// use serde_json::json;
132///
133/// async fn my_node(state: MyState, ctx: &InterruptContext) -> Result<MyStateUpdate, JunctureError> {
134///     // Anonymous interrupt -- ID is auto-generated from node + index
135///     let decision: serde_json::Value = interrupt_with_ctx!(
136///         ctx,
137///         json!({"question": "Continue?", "options": ["yes", "no"]})
138///     )?;
139///
140///     // Named interrupt -- caller can resume by ID
141///     let approval: serde_json::Value = interrupt_with_ctx!(
142///         ctx,
143///         "approve_step",
144///         json!({"question": "Approve this action?", "action": "delete"})
145///     )?;
146///
147///     Ok(MyStateUpdate::default())
148/// }
149/// ```
150#[macro_export]
151macro_rules! interrupt_with_ctx {
152    // Named interrupt: interrupt_with_ctx!(ctx, "my_id", json!({...}))
153    ($ctx:expr, $id:expr, $payload:expr) => {{
154        $crate::interrupt::__interrupt_impl(
155            $ctx,
156            ::serde_json::to_value(&$payload).expect("interrupt payload must be serializable"),
157            Some($id),
158        )
159        .await
160    }};
161    // Anonymous interrupt: interrupt_with_ctx!(ctx, json!({...}))
162    ($ctx:expr, $payload:expr) => {{
163        $crate::interrupt::__interrupt_impl(
164            $ctx,
165            ::serde_json::to_value(&$payload).expect("interrupt payload must be serializable"),
166            None,
167        )
168        .await
169    }};
170}
171
172/// Parent command macro for subgraph-to-parent routing
173///
174/// Allows a node inside a subgraph to request routing to a specific node
175/// in the parent graph. This works as an exception mechanism: the macro
176/// returns a `JunctureError::parent_command(target)` which the
177/// `SubgraphNode` wrapper catches and converts to `Command::goto(target)`.
178///
179/// # Syntax
180///
181/// ```ignore
182/// parent_command!("target_node_name")
183/// ```
184///
185/// # Examples
186///
187/// ```ignore
188/// use juncture_core::parent_command;
189///
190/// async fn my_subgraph_node(state: SubState) -> Result<SubStateUpdate, JunctureError> {
191///     if should_exit() {
192///         // Route directly to "publish" node in the parent graph
193///         parent_command!("publish");
194///     }
195///     Ok(SubStateUpdate::default())
196/// }
197/// ```
198#[macro_export]
199macro_rules! parent_command {
200    ($target:expr) => {
201        return Err($crate::JunctureError::parent_command($target))
202    };
203}
204
205#[cfg(all(feature = "chat", not(target_family = "wasm")))]
206pub use chat::{ChatAnthropic, ChatOllama, ChatOpenAI};
207pub use checkpoint::{
208    CHECKPOINT_NS_SEPARATOR, CheckpointNamespace, CheckpointSaver, DeltaCounters, NamespaceSegment,
209    generate_checkpoint_id,
210};
211#[cfg(all(feature = "chat", not(target_family = "wasm")))]
212pub use client::{
213    AuthConfig, ClientError, GraphClient, InvokeConfig, JunctureClient, StateSnapshot, Thread,
214};
215pub use command::{Command, CommandGoto, Final, Goto, GraphTarget, ParentCommand, SendTarget};
216pub use config::{
217    CacheConfig, CachePolicy, EntrypointConfig, ResourceLimits, RunnableConfig, TaskConfig,
218};
219pub use edge::{END, Edge, PathMap, RouteResult, Router, START, TriggerTable};
220pub use error::{ErrorCode, InvalidUpdateError, JunctureError, NodeTimeoutError};
221pub use func::{Runtime as FuncRuntime, compile_entrypoint, compile_entrypoint_with_config};
222pub use graph::{
223    CircuitBreakerConfig, CircuitBreakerState, CircuitState, CompiledGraph, DrawableEdge,
224    DrawableGraph, DrawableNode, ErrorHandlerNode, GraphOutput, GraphOutputMetadata, InterruptInfo,
225    NodeMetadata, RetryPolicy, RetryingNode, StateFilter, StateGraph, StateUpdate, StreamHandle,
226    SubgraphInfo, TopologyError,
227};
228pub use interrupt::{
229    HIDDEN_TAG, InterruptContext, InterruptSignal, ResumeValue, Scratchpad, generate_interrupt_id,
230    should_interrupt,
231};
232pub use llm::{
233    CallOptions, ChatModel, JsonSchema, LlmError, MessageChunk, StructuredOutputModel, ToolChoice,
234    ToolDefinition,
235};
236pub use node::{IntoNode, Node, NodeError};
237pub use observability::{CacheKeyInput, GraphLifecycleCallback, MetricsCollector};
238pub use prebuilt::{PromptSource, ReactAgentConfig};
239pub use pregel::{
240    BubbleUp, BudgetConfig, BudgetExceededAction, BudgetExceededReason, BudgetTracker, BudgetUsage,
241    Durability, ExecutionConfig, ExecutionContext, FieldVersionTracker, GraphDrained,
242    GraphInterrupt, HealthStatus, LoopStatus, NodeHealth, NodeHealthState, PendingTask, PregelLoop,
243    PregelProtocol, StreamEvent as PregelStreamEvent, SuperstepResult, SyncAsyncFuture, TaskOutput,
244    TaskTrigger, TimeoutPolicy, TriggerToNodes, apply_writes, compute_next_tasks,
245    execute_superstep,
246};
247pub use runtime::{
248    ExecutionInfo, Heartbeat, HeartbeatWatcher, ManagedValues, RunControl, Runtime,
249    StreamWriterTrait,
250};
251pub use send::Send;
252pub use state::{
253    AnyValueReducer, AppendReducer, Channel, Content, ContentPart, CowState, DeltaBlob,
254    DeltaChannel, EphemeralChannel, FieldsChanged, FromState, ImageData, ImageSource, IntoState,
255    LastValueAfterFinishChannel, LastWriteWinsReducer, Message, MessagesState, MessagesStateUpdate,
256    Overwrite, REMOVE_ALL_MESSAGES, Reducer, RemoveMessage, ReplaceReducer, RingBufferChannel,
257    Role, State, TokenUsage, ToolCall, UntrackedChannel, messages_reducer,
258};
259pub use store::{
260    EmbeddingFunc, FilterExpr, IndexConfig, Item, MemoryStore, SearchItem, SearchQuery,
261    SearchResult, Store, StoreError, StoreOp, StoreResult, TTLConfig,
262};
263pub use stream::{
264    BatchTransformer, DebugEvent, EventEmitter, FilterFieldsTransformer, JsonParseTransformer,
265    MessageBatchConfig, StreamConfig, StreamEvent, StreamMode, StreamPart, StreamResumption,
266    StreamTransformer, StreamWriter, TaskEventType, ToolsEvent, call_llm_streaming,
267};
268pub use subgraph::{
269    StateSubset, SubgraphConfig, SubgraphMount, SubgraphNode, SubgraphPersistence,
270    SubgraphTransformer,
271};
272pub use tools::{
273    NopToolInterceptor, StatefulTool, Tool, ToolCallTransformer, ToolError, ToolExecutionTrace,
274    ToolInterceptor, ToolNode, ToolNodeConfig, ToolRuntime, tools_condition,
275};
276
277// Rust guideline compliant 2026-05-19