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}