aether_core/
script.rs

1//! # Aether Script
2//! 
3//! Aether Script (.ae) is a specialized DSL built on top of Rhai,
4//! optimized for AI-agentic workflows. It introduces first-class 
5//! AI directives and data-flow operators.
6
7use crate::{Result, AetherError, AiProvider, InjectionEngine};
8use rhai::{Engine, Dynamic, Scope};
9use std::sync::Arc;
10use tracing::debug;
11
12/// Pre-processor for Aether Script syntax.
13pub struct AetherScript;
14
15impl AetherScript {
16    /// Compiles Aether Script syntax into valid Rhai code.
17    /// 
18    /// Supported Directives:
19    /// - `@ai("prompt")` -> Shortcut for AI generation.
20    /// - `@json { ... }` -> Auto-decoding JSON responses.
21    pub fn preprocess(script: &str) -> String {
22        let mut processed = script.to_string();
23
24        // Placeholder for regex-based transformation
25        // In a production system, this would be a proper lexer/parser
26        
27        // Example: Transform @ai("prompt") -> __aether_ask("prompt")
28        // We use a simple replacement for now to demonstrate the concept
29        processed = processed.replace("@ai", "__aether_ask");
30        
31        processed
32    }
33}
34
35/// Aether-enhanced runtime that supports agentic functions.
36pub struct AetherAgenticRuntime<P: AiProvider> {
37    engine: Engine,
38    _provider: Arc<P>,
39}
40
41impl<P: AiProvider + 'static> AetherAgenticRuntime<P> {
42    /// Create a new agentic runtime with the given AI provider.
43    pub fn new(provider: P) -> Self {
44        let mut engine = Engine::new();
45        let provider = Arc::new(provider);
46        let p_clone = Arc::clone(&provider);
47
48        // Register __aether_ask function
49        // This allows scripts to call AI directly!
50        engine.register_fn("__aether_ask", move |prompt: String| -> Dynamic {
51            let p = Arc::clone(&p_clone);
52            
53            // SAFETY: Rhai is synchronous, but InjectionEngine is asynchronous.
54            // Calling block_on directly within a Tokio runtime causes a panic.
55            // We spawn a separate thread and a dedicated single-threaded runtime 
56            // to safely bridge the sync/async gap.
57            let result = std::thread::spawn(move || {
58                let rt = tokio::runtime::Builder::new_current_thread()
59                    .enable_all()
60                    .build()
61                    .map_err(|e| AetherError::InjectionError(e.to_string()))?;
62                
63                rt.block_on(async move {
64                    let engine = InjectionEngine::new_raw(p);
65                    engine.inject_raw(&prompt).await
66                })
67            }).join();
68
69            match result {
70                Ok(Ok(code)) => Dynamic::from(code),
71                Ok(Err(e)) => Dynamic::from(format!("Error: {}", e)),
72                Err(_) => Dynamic::from("Error: AI thread panicked".to_string()),
73            }
74        });
75
76        Self { engine, _provider: provider }
77    }
78
79    /// Execute an Aether Script.
80    pub fn execute(&self, script: &str, scope: &mut Scope) -> Result<Dynamic> {
81        let processed = AetherScript::preprocess(script);
82        debug!("Executing preprocessed script: {}", processed);
83        
84        self.engine.eval_with_scope(scope, &processed)
85            .map_err(|e| AetherError::ConfigError(format!("Script execution failed: {}", e)))
86    }
87}