Skip to main content

busbar_sf_agentscript/
ast.rs

1//! Abstract Syntax Tree types for AgentScript.
2//!
3//! This module defines all types representing a parsed AgentScript file.
4//! Every node in the AST is wrapped in [`Spanned`] to track its source location,
5//! enabling precise error reporting and IDE features like go-to-definition.
6//!
7//! For a comprehensive guide to the AST structure, node relationships, and UX development patterns,
8//! see the **[AST Reference](https://github.com/composable-delivery/sf-agentscript/blob/main/AST_REFERENCE.md)**.
9//!
10//! # AST Structure
11//!
12//! The root type is [`AgentFile`], which contains optional blocks:
13//!
14//! ```text
15//! AgentFile
16//! ├── config: ConfigBlock (agent metadata)
17//! ├── system: SystemBlock (global instructions/messages)
18//! ├── variables: VariablesBlock (state management)
19//! ├── connections: ConnectionsBlock (escalation routing)
20//! ├── language: LanguageBlock (locale settings)
21//! ├── start_agent: StartAgentBlock (entry point)
22//! └── topics: Vec<TopicBlock> (conversation topics)
23//! ```
24//!
25//! # Span Tracking
26//!
27//! All nodes use [`Spanned<T>`] to preserve source locations:
28//!
29//! ```rust
30//! use busbar_sf_agentscript::Spanned;
31//!
32//! // A spanned string with byte offsets 10..20
33//! let name = Spanned::new("MyAgent".to_string(), 10..20);
34//! assert_eq!(name.node, "MyAgent");
35//! assert_eq!(name.span, 10..20);
36//! ```
37//!
38//! # Serialization
39//!
40//! All types implement `Serialize` and `Deserialize` for JSON interop:
41//!
42//! ```rust
43//! # use busbar_sf_agentscript::parse;
44//! let source = "config:\n   agent_name: \"Test\"\n";
45//! let agent = parse(source).unwrap();
46//! let json = serde_json::to_string(&agent).unwrap();
47//! ```
48
49use indexmap::IndexMap;
50use serde::{Deserialize, Serialize};
51use std::ops::Range;
52
53/// A span in the source code represented as byte offsets.
54///
55/// Used to track the exact location of every AST node in the original source,
56/// enabling precise error messages and IDE features.
57///
58/// # Example
59///
60/// ```rust
61/// use busbar_sf_agentscript::ast::Span;
62///
63/// let span: Span = 0..10; // Bytes 0 through 9
64/// assert_eq!(span.start, 0);
65/// assert_eq!(span.end, 10);
66/// ```
67pub type Span = Range<usize>;
68
69/// A value with an associated source span.
70///
71/// This wrapper type preserves source location information throughout the AST,
72/// enabling features like:
73/// - Precise error messages pointing to exact source locations
74/// - IDE go-to-definition and hover information
75/// - Source maps for debugging
76///
77/// # Example
78///
79/// ```rust
80/// use busbar_sf_agentscript::Spanned;
81///
82/// let spanned = Spanned::new("hello".to_string(), 5..10);
83/// assert_eq!(spanned.node, "hello");
84/// assert_eq!(spanned.span.start, 5);
85///
86/// // Transform the inner value while preserving span
87/// let upper = spanned.map(|s| s.to_uppercase());
88/// assert_eq!(upper.node, "HELLO");
89/// assert_eq!(upper.span.start, 5);
90/// ```
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct Spanned<T> {
93    /// The wrapped value.
94    pub node: T,
95    /// Source location as byte offsets.
96    pub span: Span,
97}
98
99impl<T> Spanned<T> {
100    /// Create a new spanned value.
101    ///
102    /// # Example
103    ///
104    /// ```rust
105    /// use busbar_sf_agentscript::Spanned;
106    ///
107    /// let s = Spanned::new(42, 0..2);
108    /// assert_eq!(s.node, 42);
109    /// ```
110    pub fn new(node: T, span: Span) -> Self {
111        Self { node, span }
112    }
113
114    /// Transform the inner value while preserving the span.
115    ///
116    /// # Example
117    ///
118    /// ```rust
119    /// use busbar_sf_agentscript::Spanned;
120    ///
121    /// let s = Spanned::new(5, 0..1);
122    /// let doubled = s.map(|n| n * 2);
123    /// assert_eq!(doubled.node, 10);
124    /// assert_eq!(doubled.span, 0..1);
125    /// ```
126    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
127        Spanned {
128            node: f(self.node),
129            span: self.span,
130        }
131    }
132}
133
134// ============================================================================
135// Top-Level File Structure
136// ============================================================================
137
138/// A complete parsed AgentScript file.
139///
140/// This is the root type returned by [`crate::parse()`]. It contains all the
141/// top-level blocks that make up an AgentScript definition.
142///
143/// # Structure
144///
145/// An AgentScript file typically contains:
146/// - `config:` - Required agent metadata
147/// - `system:` - Global instructions and messages
148/// - `variables:` - State variables
149/// - `start_agent:` - Entry point for the agent
150/// - One or more `topic:` blocks - Conversation topics
151///
152/// # Example
153///
154/// ```rust
155/// use busbar_sf_agentscript::parse;
156///
157/// let source = r#"
158/// config:
159///    agent_name: "MyAgent"
160///
161/// topic main:
162///    description: "Main topic"
163/// "#;
164///
165/// let agent = parse(source).unwrap();
166/// assert!(agent.config.is_some());
167/// assert_eq!(agent.topics.len(), 1);
168/// ```
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
170pub struct AgentFile {
171    /// Agent configuration block (`config:` section).
172    ///
173    /// Contains agent metadata like name, label, and description.
174    /// Should be the first block in the file.
175    pub config: Option<Spanned<ConfigBlock>>,
176
177    /// Variable declarations (`variables:` section).
178    ///
179    /// Defines state variables that can be `mutable` (read-write)
180    /// or `linked` (read-only from external context).
181    pub variables: Option<Spanned<VariablesBlock>>,
182
183    /// System configuration (`system:` section).
184    ///
185    /// Contains global instructions and system messages like
186    /// welcome and error messages.
187    pub system: Option<Spanned<SystemBlock>>,
188
189    /// Connection configurations (`connection <name>:` blocks).
190    ///
191    /// Each connection block defines escalation routing for a specific channel.
192    /// Multiple connection blocks can exist at the top level.
193    pub connections: Vec<Spanned<ConnectionBlock>>,
194
195    /// Knowledge base configuration (`knowledge:` section).
196    ///
197    /// Configures access to external knowledge sources.
198    pub knowledge: Option<Spanned<KnowledgeBlock>>,
199
200    /// Language/locale settings (`language:` section).
201    ///
202    /// Configures internationalization settings.
203    pub language: Option<Spanned<LanguageBlock>>,
204
205    /// Entry point (`start_agent:` section).
206    ///
207    /// The initial topic selector that routes conversations
208    /// to appropriate topics.
209    pub start_agent: Option<Spanned<StartAgentBlock>>,
210
211    /// Conversation topics (`topic:` sections).
212    ///
213    /// Each topic defines a conversational context with its own
214    /// reasoning instructions and available actions.
215    pub topics: Vec<Spanned<TopicBlock>>,
216}
217
218impl AgentFile {
219    /// Create a new empty AgentFile.
220    ///
221    /// # Example
222    ///
223    /// ```rust
224    /// use busbar_sf_agentscript::AgentFile;
225    ///
226    /// let agent = AgentFile::new();
227    /// assert!(agent.config.is_none());
228    /// assert!(agent.topics.is_empty());
229    /// ```
230    pub fn new() -> Self {
231        Self::default()
232    }
233}
234
235// ============================================================================
236// Config Block
237// ============================================================================
238
239/// Agent configuration block containing metadata.
240///
241/// This block should appear first in the file and defines identifying
242/// information about the agent.
243///
244/// # AgentScript Syntax
245///
246/// ```text
247/// config:
248///    agent_name: "CustomerSupport"
249///    agent_label: "Customer Support Agent"
250///    description: "Handles customer support inquiries"
251///    agent_type: "AgentforceServiceAgent"
252/// ```
253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
254pub struct ConfigBlock {
255    /// The agent's unique identifier name (required).
256    ///
257    /// This is used internally to reference the agent.
258    pub agent_name: Spanned<String>,
259
260    /// Human-readable display label (optional).
261    ///
262    /// Shown in UIs when the agent_name is too technical.
263    pub agent_label: Option<Spanned<String>>,
264
265    /// Description of the agent's purpose.
266    pub description: Option<Spanned<String>>,
267
268    /// Agent type classification (e.g., "AgentforceServiceAgent").
269    pub agent_type: Option<Spanned<String>>,
270
271    /// Default user email for agent operations.
272    pub default_agent_user: Option<Spanned<String>>,
273}
274
275// ============================================================================
276// Variables Block
277// ============================================================================
278
279/// Variables block containing state variable declarations.
280///
281/// Variables in AgentScript can be either `mutable` (read-write) or `linked`
282/// (read-only from external context).
283///
284/// # AgentScript Syntax
285///
286/// ```text
287/// variables:
288///    customer_name: mutable string = ""
289///       description: "Customer's name"
290///    order_total: linked number
291///       source: @context.order.total
292///       description: "Total order amount"
293/// ```
294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
295pub struct VariablesBlock {
296    /// List of variable declarations.
297    pub variables: Vec<Spanned<VariableDecl>>,
298}
299
300/// A single variable declaration.
301///
302/// Variables have a name, kind (mutable/linked), type, and optional metadata.
303/// Mutable variables require a default value; linked variables require a source.
304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305pub struct VariableDecl {
306    /// Variable name (used in references like `@variables.name`).
307    pub name: Spanned<String>,
308
309    /// Whether the variable is mutable or linked.
310    pub kind: VariableKind,
311
312    /// The variable's data type.
313    pub ty: Spanned<Type>,
314
315    /// Default value (required for mutable variables).
316    ///
317    /// Example: `mutable string = "default"`
318    pub default: Option<Spanned<Expr>>,
319
320    /// Human-readable description.
321    pub description: Option<Spanned<String>>,
322
323    /// Source reference for linked variables.
324    ///
325    /// Example: `source: @context.user.email`
326    pub source: Option<Spanned<Reference>>,
327}
328
329/// Variable mutability kind.
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
331pub enum VariableKind {
332    /// Mutable variable - agent can read and write.
333    ///
334    /// Requires a default value in the declaration.
335    Mutable,
336
337    /// Linked variable - read-only from external context.
338    ///
339    /// Requires a source reference pointing to external data.
340    Linked,
341}
342
343/// Data type for variables and action parameters.
344///
345/// AgentScript supports a fixed set of primitive types plus `List` for arrays.
346///
347/// # Example Types
348///
349/// | Type | Description |
350/// |------|-------------|
351/// | `string` | Text data |
352/// | `number` | Floating-point number |
353/// | `boolean` | True/False |
354/// | `integer` | Whole number |
355/// | `id` | Salesforce record ID |
356/// | `list<string>` | Array of strings |
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
358pub enum Type {
359    /// Text data (`string`).
360    String,
361    /// Floating-point number (`number`).
362    Number,
363    /// Boolean value (`boolean`).
364    Boolean,
365    /// Generic object (`object`).
366    Object,
367    /// Date without time (`date`).
368    Date,
369    /// Unix timestamp (`timestamp`).
370    Timestamp,
371    /// Currency value (`currency`).
372    Currency,
373    /// Salesforce record ID (`id`).
374    Id,
375    /// Date with time (`datetime`).
376    Datetime,
377    /// Time without date (`time`).
378    Time,
379    /// Whole number (`integer`).
380    Integer,
381    /// Large integer (`long`).
382    Long,
383    /// Array type (`list<T>`).
384    List(Box<Type>),
385}
386
387impl Type {
388    /// Parse a type from its string representation.
389    ///
390    /// # Example
391    ///
392    /// ```rust
393    /// use busbar_sf_agentscript::Type;
394    ///
395    /// assert_eq!(Type::parse_type("string"), Some(Type::String));
396    /// assert_eq!(Type::parse_type("number"), Some(Type::Number));
397    /// assert_eq!(Type::parse_type("unknown"), None);
398    /// ```
399    pub fn parse_type(s: &str) -> Option<Self> {
400        match s {
401            "string" => Some(Type::String),
402            "number" => Some(Type::Number),
403            "boolean" => Some(Type::Boolean),
404            "object" => Some(Type::Object),
405            "date" => Some(Type::Date),
406            "timestamp" => Some(Type::Timestamp),
407            "currency" => Some(Type::Currency),
408            "id" => Some(Type::Id),
409            "datetime" => Some(Type::Datetime),
410            "time" => Some(Type::Time),
411            "integer" => Some(Type::Integer),
412            "long" => Some(Type::Long),
413            _ => None,
414        }
415    }
416}
417
418// ============================================================================
419// System Block
420// ============================================================================
421
422/// The system block defines global agent settings.
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
424pub struct SystemBlock {
425    /// System messages.
426    pub messages: Option<Spanned<SystemMessages>>,
427    /// System instructions.
428    pub instructions: Option<Spanned<Instructions>>,
429}
430
431/// System messages (welcome, error).
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433pub struct SystemMessages {
434    pub welcome: Option<Spanned<String>>,
435    pub error: Option<Spanned<String>>,
436}
437
438// ============================================================================
439// Connection Block
440// ============================================================================
441
442/// A connection block defines escalation routing for a specific channel.
443///
444/// # AgentScript Syntax
445///
446/// ```text
447/// connection messaging:
448///    escalation_message: "I'm connecting you with a specialist."
449///    outbound_route_type: "OmniChannelFlow"
450///    outbound_route_name: "SpecialistQueue"
451/// ```
452#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
453pub struct ConnectionBlock {
454    /// Connection name (e.g., "messaging").
455    pub name: Spanned<String>,
456    /// Connection configuration entries.
457    pub entries: Vec<Spanned<ConnectionEntry>>,
458}
459
460/// A key-value entry in a connection block.
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462pub struct ConnectionEntry {
463    pub name: Spanned<String>,
464    pub value: Spanned<String>,
465}
466
467// Legacy type alias for backwards compatibility
468// TODO: Remove in next major version
469/// @deprecated Use ConnectionBlock instead - this type exists only for backwards compatibility
470pub type ConnectionsBlock = ConnectionBlock;
471
472// ============================================================================
473// Knowledge Block
474// ============================================================================
475
476/// The knowledge block configures knowledge base access.
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478pub struct KnowledgeBlock {
479    /// Knowledge configuration entries.
480    pub entries: Vec<Spanned<KnowledgeEntry>>,
481}
482
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
484pub struct KnowledgeEntry {
485    pub name: Spanned<String>,
486    pub value: Spanned<Expr>,
487}
488
489// ============================================================================
490// Language Block
491// ============================================================================
492
493/// The language block configures locale settings.
494#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
495pub struct LanguageBlock {
496    /// Language setting entries.
497    pub entries: Vec<Spanned<LanguageEntry>>,
498}
499
500#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
501pub struct LanguageEntry {
502    pub name: Spanned<String>,
503    pub value: Spanned<Expr>,
504}
505
506// ============================================================================
507// Start Agent Block
508// ============================================================================
509
510/// The start_agent block is the entry point for the agent.
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
512pub struct StartAgentBlock {
513    /// The name after start_agent (typically "topic_selector").
514    pub name: Spanned<String>,
515    /// Description of the start agent.
516    pub description: Option<Spanned<String>>,
517    /// Optional system override.
518    pub system: Option<Spanned<TopicSystemOverride>>,
519    /// Optional action definitions.
520    pub actions: Option<Spanned<ActionsBlock>>,
521    /// Optional before_reasoning block.
522    pub before_reasoning: Option<Spanned<DirectiveBlock>>,
523    /// The reasoning block (required).
524    pub reasoning: Option<Spanned<ReasoningBlock>>,
525    /// Optional after_reasoning block.
526    pub after_reasoning: Option<Spanned<DirectiveBlock>>,
527}
528
529// ============================================================================
530// Topic Block
531// ============================================================================
532
533/// A topic block defines a conversation topic.
534#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
535pub struct TopicBlock {
536    /// Topic name.
537    pub name: Spanned<String>,
538    /// Description of the topic.
539    pub description: Option<Spanned<String>>,
540    /// Optional system override.
541    pub system: Option<Spanned<TopicSystemOverride>>,
542    /// Optional action definitions.
543    pub actions: Option<Spanned<ActionsBlock>>,
544    /// Optional before_reasoning block.
545    pub before_reasoning: Option<Spanned<DirectiveBlock>>,
546    /// The reasoning block (required).
547    pub reasoning: Option<Spanned<ReasoningBlock>>,
548    /// Optional after_reasoning block.
549    pub after_reasoning: Option<Spanned<DirectiveBlock>>,
550}
551
552/// System instruction override for a topic.
553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
554pub struct TopicSystemOverride {
555    pub instructions: Option<Spanned<Instructions>>,
556}
557
558// ============================================================================
559// Actions Block
560// ============================================================================
561
562/// A block of action definitions.
563#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
564pub struct ActionsBlock {
565    pub actions: Vec<Spanned<ActionDef>>,
566}
567
568/// An action definition.
569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
570pub struct ActionDef {
571    /// Action name.
572    pub name: Spanned<String>,
573    /// Description.
574    pub description: Option<Spanned<String>>,
575    /// Display label.
576    pub label: Option<Spanned<String>>,
577    /// Whether user confirmation is required.
578    pub require_user_confirmation: Option<Spanned<bool>>,
579    /// Whether to show progress indicator.
580    pub include_in_progress_indicator: Option<Spanned<bool>>,
581    /// Progress indicator message.
582    pub progress_indicator_message: Option<Spanned<String>>,
583    /// Input parameters.
584    pub inputs: Option<Spanned<Vec<Spanned<ParamDef>>>>,
585    /// Output parameters.
586    pub outputs: Option<Spanned<Vec<Spanned<ParamDef>>>>,
587    /// Target (e.g., "flow://FlowName").
588    pub target: Option<Spanned<String>>,
589}
590
591/// A parameter definition (for action inputs/outputs).
592#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
593pub struct ParamDef {
594    /// Parameter name.
595    pub name: Spanned<String>,
596    /// Parameter type.
597    pub ty: Spanned<Type>,
598    /// Description.
599    pub description: Option<Spanned<String>>,
600    /// Display label.
601    pub label: Option<Spanned<String>>,
602    /// Whether required (for inputs).
603    pub is_required: Option<Spanned<bool>>,
604    /// Whether to filter from agent.
605    pub filter_from_agent: Option<Spanned<bool>>,
606    /// Whether displayable.
607    pub is_displayable: Option<Spanned<bool>>,
608    /// Complex data type name.
609    pub complex_data_type_name: Option<Spanned<String>>,
610}
611
612// ============================================================================
613// Directive Blocks (before_reasoning, after_reasoning)
614// ============================================================================
615
616/// A directive block contains imperative statements.
617#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
618pub struct DirectiveBlock {
619    pub statements: Vec<Spanned<Stmt>>,
620}
621
622/// A statement in a directive block.
623#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
624pub enum Stmt {
625    /// Variable assignment: `set @variables.x = expr`
626    Set {
627        target: Spanned<Reference>,
628        value: Spanned<Expr>,
629    },
630    /// Action invocation: `run @actions.foo`
631    Run {
632        action: Spanned<Reference>,
633        with_clauses: Vec<Spanned<WithClause>>,
634        set_clauses: Vec<Spanned<SetClause>>,
635    },
636    /// Conditional: `if expr:`
637    If {
638        condition: Spanned<Expr>,
639        then_block: Vec<Spanned<Stmt>>,
640        else_block: Option<Vec<Spanned<Stmt>>>,
641    },
642    /// Transition: `transition to @topic.name`
643    Transition { target: Spanned<Reference> },
644}
645
646/// A with clause binds an action input.
647#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
648pub struct WithClause {
649    pub param: Spanned<String>,
650    pub value: Spanned<WithValue>,
651}
652
653/// The value of a with clause.
654#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
655pub enum WithValue {
656    /// A concrete expression.
657    Expr(Expr),
658}
659
660/// A set clause assigns a value to a variable.
661#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
662pub struct SetClause {
663    pub target: Spanned<Reference>,
664    /// The value to assign (can be reference, literal, or expression).
665    pub source: Spanned<Expr>,
666}
667
668/// Reference to an action output.
669#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
670pub struct OutputRef {
671    /// The output name (e.g., "result" from "@outputs.result").
672    pub name: String,
673}
674
675// ============================================================================
676// Reasoning Block
677// ============================================================================
678
679/// The reasoning block configures LLM reasoning.
680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
681pub struct ReasoningBlock {
682    /// Instructions for the LLM.
683    pub instructions: Option<Spanned<Instructions>>,
684    /// Actions available to the LLM.
685    pub actions: Option<Spanned<Vec<Spanned<ReasoningAction>>>>,
686}
687
688/// An action available during reasoning.
689#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
690pub struct ReasoningAction {
691    /// Action alias name.
692    pub name: Spanned<String>,
693    /// Target reference (e.g., `@actions.foo` or `@utils.transition to @topic.x`).
694    pub target: Spanned<ReasoningActionTarget>,
695    /// Optional description override.
696    pub description: Option<Spanned<String>>,
697    /// Availability condition.
698    pub available_when: Option<Spanned<Expr>>,
699    /// Input bindings.
700    pub with_clauses: Vec<Spanned<WithClause>>,
701    /// Output captures.
702    pub set_clauses: Vec<Spanned<SetClause>>,
703    /// Chained run statements.
704    pub run_clauses: Vec<Spanned<RunClause>>,
705    /// Post-action if conditions.
706    pub if_clauses: Vec<Spanned<IfClause>>,
707    /// Post-action transition.
708    pub transition: Option<Spanned<Reference>>,
709}
710
711/// Target of a reasoning action.
712#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
713pub enum ReasoningActionTarget {
714    /// Reference to an action: `@actions.foo`
715    Action(Reference),
716    /// Transition utility: `@utils.transition to @topic.x`
717    TransitionTo(Reference),
718    /// Escalate utility: `@utils.escalate`
719    Escalate,
720    /// Set variables utility: `@utils.setVariables`
721    SetVariables,
722    /// Topic delegation: `@topic.x`
723    TopicDelegate(Reference),
724}
725
726/// A chained run clause in reasoning actions.
727#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
728pub struct RunClause {
729    pub action: Spanned<Reference>,
730    pub with_clauses: Vec<Spanned<WithClause>>,
731    pub set_clauses: Vec<Spanned<SetClause>>,
732}
733
734/// A conditional clause in reasoning actions.
735#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
736pub struct IfClause {
737    pub condition: Spanned<Expr>,
738    pub transition: Option<Spanned<Reference>>,
739}
740
741// ============================================================================
742// Instructions
743// ============================================================================
744
745/// Instructions can be static or dynamic.
746#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
747pub enum Instructions {
748    /// Simple string instructions.
749    Simple(String),
750    /// Static multiline (`:| ...`).
751    Static(Vec<Spanned<String>>),
752    /// Dynamic with conditionals (`:-> ...`).
753    Dynamic(Vec<Spanned<InstructionPart>>),
754}
755
756/// A part of dynamic instructions.
757#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
758pub enum InstructionPart {
759    /// Literal text line (prefixed with `|`).
760    Text(String),
761    /// Template interpolation `{!expr}`.
762    Interpolation(Expr),
763    /// Conditional section.
764    Conditional {
765        condition: Spanned<Expr>,
766        then_parts: Vec<Spanned<InstructionPart>>,
767        else_parts: Option<Vec<Spanned<InstructionPart>>>,
768    },
769}
770
771// ============================================================================
772// Expressions
773// ============================================================================
774
775/// An expression in AgentScript.
776///
777/// Expressions can appear in conditions, variable assignments, action bindings,
778/// and dynamic instruction interpolation.
779///
780/// # Variants
781///
782/// | Variant | Example | Description |
783/// |---------|---------|-------------|
784/// | `Reference` | `@variables.name` | Reference to a namespaced resource |
785/// | `String` | `"hello"` | String literal |
786/// | `Number` | `42`, `3.14` | Numeric literal |
787/// | `Bool` | `True`, `False` | Boolean literal |
788/// | `None` | `None` | Null value |
789/// | `List` | `[1, 2, 3]` | Array literal |
790/// | `Object` | `{key: value}` | Object literal |
791/// | `BinOp` | `a == b` | Binary operation |
792/// | `UnaryOp` | `not x` | Unary operation |
793/// | `Ternary` | `x if cond else y` | Conditional expression |
794/// | `Property` | `obj.field` | Property access |
795/// | `Index` | `arr[0]` | Index access |
796#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
797pub enum Expr {
798    /// Reference to a namespaced resource: `@namespace.path`.
799    ///
800    /// Common namespaces: `variables`, `actions`, `outputs`, `topic`, `utils`.
801    Reference(Reference),
802
803    /// String literal: `"text"`.
804    String(String),
805
806    /// Numeric literal: `42` or `3.14`.
807    Number(f64),
808
809    /// Boolean literal: `True` or `False`.
810    Bool(bool),
811
812    /// Null value: `None`.
813    None,
814
815    /// Slot-fill token: `...`
816    ///
817    /// Instructs the LLM to infer the value. Used in `with` clauses:
818    /// `with order_id = ...`
819    SlotFill,
820
821    /// Array literal: `[item1, item2, ...]`.
822    List(Vec<Spanned<Expr>>),
823
824    /// Object literal: `{key1: value1, key2: value2}`.
825    Object(IndexMap<String, Spanned<Expr>>),
826
827    /// Binary operation: `left op right`.
828    BinOp {
829        /// Left operand.
830        left: Box<Spanned<Expr>>,
831        /// Operator.
832        op: BinOp,
833        /// Right operand.
834        right: Box<Spanned<Expr>>,
835    },
836
837    /// Unary operation: `op operand`.
838    UnaryOp {
839        /// Operator (e.g., `not`, `-`).
840        op: UnaryOp,
841        /// Operand.
842        operand: Box<Spanned<Expr>>,
843    },
844
845    /// Ternary conditional: `then_expr if condition else else_expr`.
846    ///
847    /// Note: Python-style ordering (value first, then condition).
848    Ternary {
849        /// The condition to evaluate.
850        condition: Box<Spanned<Expr>>,
851        /// Value if condition is true.
852        then_expr: Box<Spanned<Expr>>,
853        /// Value if condition is false.
854        else_expr: Box<Spanned<Expr>>,
855    },
856
857    /// Property access: `object.field`.
858    Property {
859        /// The object to access.
860        object: Box<Spanned<Expr>>,
861        /// The field name.
862        field: Spanned<String>,
863    },
864
865    /// Index access: `object[index]`.
866    Index {
867        /// The array/object to index.
868        object: Box<Spanned<Expr>>,
869        /// The index expression.
870        index: Box<Spanned<Expr>>,
871    },
872}
873
874/// A reference to a namespaced resource.
875///
876/// References use the `@namespace.path` syntax to access variables, actions,
877/// topics, and utilities.
878///
879/// # Common Namespaces
880///
881/// | Namespace | Purpose | Example |
882/// |-----------|---------|---------|
883/// | `variables` | State variables | `@variables.customer_id` |
884/// | `actions` | Action definitions | `@actions.lookup_order` |
885/// | `outputs` | Action outputs | `@outputs.result` |
886/// | `topic` | Topic references | `@topic.support` |
887/// | `utils` | Built-in utilities | `@utils.transition` |
888/// | `context` | External context | `@context.user.email` |
889///
890/// # Example
891///
892/// ```rust
893/// use busbar_sf_agentscript::Reference;
894///
895/// let ref1 = Reference::new("variables", vec!["customer_id".to_string()]);
896/// assert_eq!(ref1.full_path(), "@variables.customer_id");
897///
898/// let ref2 = Reference::new("utils", vec!["transition".to_string()]);
899/// assert_eq!(ref2.full_path(), "@utils.transition");
900/// ```
901#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
902pub struct Reference {
903    /// The namespace (e.g., "variables", "actions", "outputs", "utils", "topic").
904    pub namespace: String,
905
906    /// Path components after the namespace.
907    ///
908    /// For `@variables.user.name`, this would be `["user", "name"]`.
909    pub path: Vec<String>,
910}
911
912impl Reference {
913    /// Create a new reference.
914    ///
915    /// # Example
916    ///
917    /// ```rust
918    /// use busbar_sf_agentscript::Reference;
919    ///
920    /// let r = Reference::new("variables", vec!["name".to_string()]);
921    /// assert_eq!(r.namespace, "variables");
922    /// ```
923    pub fn new(namespace: impl Into<String>, path: Vec<String>) -> Self {
924        Self {
925            namespace: namespace.into(),
926            path,
927        }
928    }
929
930    /// Get the full reference path as a string.
931    ///
932    /// # Example
933    ///
934    /// ```rust
935    /// use busbar_sf_agentscript::Reference;
936    ///
937    /// let r = Reference::new("actions", vec!["send_email".to_string()]);
938    /// assert_eq!(r.full_path(), "@actions.send_email");
939    /// ```
940    pub fn full_path(&self) -> String {
941        if self.path.is_empty() {
942            format!("@{}", self.namespace)
943        } else {
944            format!("@{}.{}", self.namespace, self.path.join("."))
945        }
946    }
947}
948
949/// Binary operators.
950#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
951pub enum BinOp {
952    // Comparison
953    Eq,    // ==
954    Ne,    // !=
955    Lt,    // <
956    Gt,    // >
957    Le,    // <=
958    Ge,    // >=
959    Is,    // is
960    IsNot, // is not
961
962    // Logical
963    And, // and
964    Or,  // or
965
966    // Arithmetic
967    Add, // +
968    Sub, // -
969}
970
971impl BinOp {
972    /// Get the precedence of this operator (higher binds tighter).
973    pub fn precedence(&self) -> u8 {
974        match self {
975            BinOp::Or => 1,
976            BinOp::And => 2,
977            BinOp::Is | BinOp::IsNot => 3,
978            BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => 4,
979            BinOp::Add | BinOp::Sub => 5,
980        }
981    }
982}
983
984/// Unary operators.
985#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
986pub enum UnaryOp {
987    Not, // not
988    Neg, // - (negative)
989}
990
991// ============================================================================
992// Comments
993// ============================================================================
994
995/// A comment in the source.
996#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
997pub struct Comment {
998    pub text: String,
999    pub span: Span,
1000}