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::{CacheConfig, CachePolicy, EntrypointConfig, RunnableConfig, TaskConfig};
217pub use edge::{END, Edge, PathMap, RouteResult, Router, START, TriggerTable};
218pub use error::{ErrorCode, InvalidUpdateError, JunctureError, NodeTimeoutError};
219pub use func::{Runtime as FuncRuntime, compile_entrypoint, compile_entrypoint_with_config};
220pub use graph::{
221    CompiledGraph, DrawableEdge, DrawableGraph, DrawableNode, ErrorHandlerNode, GraphOutput,
222    GraphOutputMetadata, InterruptInfo, NodeMetadata, RetryPolicy, RetryingNode, StateFilter,
223    StateGraph, StateUpdate, StreamHandle, SubgraphInfo, TopologyError,
224};
225pub use interrupt::{
226    HIDDEN_TAG, InterruptContext, InterruptSignal, ResumeValue, Scratchpad, generate_interrupt_id,
227    should_interrupt,
228};
229pub use llm::{
230    CallOptions, ChatModel, JsonSchema, LlmError, MessageChunk, StructuredOutputModel, ToolChoice,
231    ToolDefinition,
232};
233pub use node::{IntoNode, Node, NodeError};
234pub use observability::{CacheKeyInput, GraphLifecycleCallback, MetricsCollector};
235pub use prebuilt::{PromptSource, ReactAgentConfig};
236pub use pregel::{
237    BubbleUp, BudgetConfig, BudgetExceededAction, BudgetExceededReason, BudgetTracker, BudgetUsage,
238    Durability, ExecutionConfig, ExecutionContext, FieldVersionTracker, GraphDrained,
239    GraphInterrupt, LoopStatus, PendingTask, PregelLoop, PregelProtocol,
240    StreamEvent as PregelStreamEvent, SuperstepResult, SyncAsyncFuture, TaskOutput, TaskTrigger,
241    TimeoutPolicy, TriggerToNodes, apply_writes, compute_next_tasks, execute_superstep,
242};
243pub use runtime::{
244    ExecutionInfo, Heartbeat, HeartbeatWatcher, ManagedValues, RunControl, Runtime,
245    StreamWriterTrait,
246};
247pub use send::Send;
248pub use state::{
249    AnyValueReducer, AppendReducer, Channel, Content, ContentPart, CowState, DeltaBlob,
250    DeltaChannel, EphemeralChannel, FieldsChanged, FromState, ImageData, ImageSource, IntoState,
251    LastValueAfterFinishChannel, LastWriteWinsReducer, Message, MessagesState, MessagesStateUpdate,
252    Overwrite, REMOVE_ALL_MESSAGES, Reducer, RemoveMessage, ReplaceReducer, Role, State,
253    TokenUsage, ToolCall, UntrackedChannel, messages_reducer,
254};
255pub use store::{
256    EmbeddingFunc, FilterExpr, IndexConfig, Item, MemoryStore, SearchItem, SearchQuery,
257    SearchResult, Store, StoreError, StoreOp, StoreResult, TTLConfig,
258};
259pub use stream::{
260    BatchTransformer, DebugEvent, EventEmitter, FilterFieldsTransformer, JsonParseTransformer,
261    MessageBatchConfig, StreamConfig, StreamEvent, StreamMode, StreamPart, StreamResumption,
262    StreamTransformer, StreamWriter, TaskEventType, ToolsEvent, call_llm_streaming,
263};
264pub use subgraph::{
265    StateSubset, SubgraphConfig, SubgraphMount, SubgraphNode, SubgraphPersistence,
266    SubgraphTransformer,
267};
268pub use tools::{
269    NopToolInterceptor, StatefulTool, Tool, ToolCallTransformer, ToolError, ToolExecutionTrace,
270    ToolInterceptor, ToolNode, ToolNodeConfig, ToolRuntime, tools_condition,
271};
272
273// Rust guideline compliant 2026-05-19