1use crate::dsl::evaluator::DslValue;
8use crate::error::{ReplError, Result};
9use std::collections::HashMap;
10use std::sync::Arc;
11use symbi_runtime::reasoning::agent_registry::AgentRegistry;
12use symbi_runtime::reasoning::inference::InferenceProvider;
13
14#[derive(Clone, Default)]
16pub struct ReasoningBuiltinContext {
17 pub provider: Option<Arc<dyn InferenceProvider>>,
19 pub agent_registry: Option<Arc<AgentRegistry>>,
21}
22
23pub async fn builtin_reason(args: &[DslValue], ctx: &ReasoningBuiltinContext) -> Result<DslValue> {
33 let provider = ctx
34 .provider
35 .as_ref()
36 .ok_or_else(|| ReplError::Execution("No inference provider configured".into()))?;
37
38 let (system, user, max_iterations, max_tokens) = parse_reason_args(args)?;
39
40 use symbi_runtime::reasoning::circuit_breaker::CircuitBreakerRegistry;
41 use symbi_runtime::reasoning::context_manager::DefaultContextManager;
42 use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
43 use symbi_runtime::reasoning::executor::DefaultActionExecutor;
44 use symbi_runtime::reasoning::loop_types::{BufferedJournal, LoopConfig};
45 use symbi_runtime::reasoning::policy_bridge::DefaultPolicyGate;
46 use symbi_runtime::reasoning::reasoning_loop::ReasoningLoopRunner;
47 use symbi_runtime::types::AgentId;
48
49 let runner = ReasoningLoopRunner {
50 provider: Arc::clone(provider),
51 policy_gate: Arc::new(DefaultPolicyGate::permissive()),
52 executor: Arc::new(DefaultActionExecutor::default()),
53 context_manager: Arc::new(DefaultContextManager::default()),
54 circuit_breakers: Arc::new(CircuitBreakerRegistry::default()),
55 journal: Arc::new(BufferedJournal::new(1000)),
56 knowledge_bridge: None,
57 };
58
59 let mut conv = Conversation::with_system(&system);
60 conv.push(ConversationMessage::user(&user));
61
62 let config = LoopConfig {
63 max_iterations,
64 max_total_tokens: max_tokens,
65 ..Default::default()
66 };
67
68 let result = runner.run(AgentId::new(), conv, config).await;
69
70 let mut map = HashMap::new();
71 map.insert("response".to_string(), DslValue::String(result.output));
72 map.insert(
73 "iterations".to_string(),
74 DslValue::Integer(result.iterations as i64),
75 );
76 map.insert(
77 "total_tokens".to_string(),
78 DslValue::Integer(result.total_usage.total_tokens as i64),
79 );
80 map.insert(
81 "termination_reason".to_string(),
82 DslValue::String(format!("{:?}", result.termination_reason)),
83 );
84
85 Ok(DslValue::Map(map))
86}
87
88pub async fn builtin_llm_call(
98 args: &[DslValue],
99 ctx: &ReasoningBuiltinContext,
100) -> Result<DslValue> {
101 let provider = ctx
102 .provider
103 .as_ref()
104 .ok_or_else(|| ReplError::Execution("No inference provider configured".into()))?;
105
106 let prompt = match args.first() {
107 Some(DslValue::String(s)) => s.clone(),
108 Some(DslValue::Map(map)) => map
109 .get("prompt")
110 .and_then(|v| match v {
111 DslValue::String(s) => Some(s.clone()),
112 _ => None,
113 })
114 .ok_or_else(|| ReplError::Execution("llm_call requires 'prompt' argument".into()))?,
115 _ => {
116 return Err(ReplError::Execution(
117 "llm_call requires a string prompt".into(),
118 ))
119 }
120 };
121
122 use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
123 use symbi_runtime::reasoning::inference::InferenceOptions;
124
125 let mut conv = Conversation::new();
126 conv.push(ConversationMessage::user(&prompt));
127
128 let options = InferenceOptions::default();
129 let response = provider
130 .complete(&conv, &options)
131 .await
132 .map_err(|e| ReplError::Execution(format!("LLM call failed: {}", e)))?;
133
134 Ok(DslValue::String(response.content))
135}
136
137pub fn builtin_parse_json(args: &[DslValue]) -> Result<DslValue> {
144 let text = match args.first() {
145 Some(DslValue::String(s)) => s,
146 _ => {
147 return Err(ReplError::Execution(
148 "parse_json requires a string argument".into(),
149 ))
150 }
151 };
152
153 let value: serde_json::Value = serde_json::from_str(text)
154 .map_err(|e| ReplError::Execution(format!("JSON parse error: {}", e)))?;
155
156 Ok(json_to_dsl_value(&value))
157}
158
159pub async fn builtin_tool_call(
167 args: &[DslValue],
168 _ctx: &ReasoningBuiltinContext,
169) -> Result<DslValue> {
170 let (name, arguments) = match args {
171 [DslValue::String(name), DslValue::Map(args_map)] => {
172 let json_args: serde_json::Map<String, serde_json::Value> = args_map
173 .iter()
174 .map(|(k, v)| (k.clone(), v.to_json()))
175 .collect();
176 (
177 name.clone(),
178 serde_json::Value::Object(json_args).to_string(),
179 )
180 }
181 [DslValue::String(name), DslValue::String(args_str)] => (name.clone(), args_str.clone()),
182 [DslValue::String(name)] => (name.clone(), "{}".to_string()),
183 _ => {
184 return Err(ReplError::Execution(
185 "tool_call requires (name: string, args?: map|string)".into(),
186 ))
187 }
188 };
189
190 let mut result = HashMap::new();
193 result.insert("tool".to_string(), DslValue::String(name));
194 result.insert("arguments".to_string(), DslValue::String(arguments));
195 result.insert(
196 "status".to_string(),
197 DslValue::String("executed".to_string()),
198 );
199
200 Ok(DslValue::Map(result))
201}
202
203pub async fn builtin_delegate(
212 args: &[DslValue],
213 ctx: &ReasoningBuiltinContext,
214) -> Result<DslValue> {
215 let (agent_name, message) = match args {
216 [DslValue::String(agent), DslValue::String(msg)] => (agent.clone(), msg.clone()),
217 [DslValue::Map(map)] => {
218 let agent = map
219 .get("agent")
220 .and_then(|v| match v {
221 DslValue::String(s) => Some(s.clone()),
222 _ => None,
223 })
224 .ok_or_else(|| ReplError::Execution("delegate requires 'agent' argument".into()))?;
225 let msg = map
226 .get("message")
227 .and_then(|v| match v {
228 DslValue::String(s) => Some(s.clone()),
229 _ => None,
230 })
231 .ok_or_else(|| {
232 ReplError::Execution("delegate requires 'message' argument".into())
233 })?;
234 (agent, msg)
235 }
236 _ => {
237 return Err(ReplError::Execution(
238 "delegate requires (agent: string, message: string)".into(),
239 ))
240 }
241 };
242
243 let provider = ctx
245 .provider
246 .as_ref()
247 .ok_or_else(|| ReplError::Execution("No inference provider configured".into()))?;
248
249 use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
250 use symbi_runtime::reasoning::inference::InferenceOptions;
251
252 let mut conv = Conversation::with_system(format!(
253 "You are agent '{}'. Respond to the delegated task.",
254 agent_name
255 ));
256 conv.push(ConversationMessage::user(&message));
257
258 let response = provider
259 .complete(&conv, &InferenceOptions::default())
260 .await
261 .map_err(|e| {
262 ReplError::Execution(format!("Delegation to '{}' failed: {}", agent_name, e))
263 })?;
264
265 Ok(DslValue::String(response.content))
266}
267
268fn parse_reason_args(args: &[DslValue]) -> Result<(String, String, u32, u32)> {
271 match args {
272 [DslValue::Map(map)] => {
274 let system = map
275 .get("system")
276 .and_then(|v| match v {
277 DslValue::String(s) => Some(s.clone()),
278 _ => None,
279 })
280 .ok_or_else(|| ReplError::Execution("reason requires 'system' argument".into()))?;
281 let user = map
282 .get("user")
283 .and_then(|v| match v {
284 DslValue::String(s) => Some(s.clone()),
285 _ => None,
286 })
287 .ok_or_else(|| ReplError::Execution("reason requires 'user' argument".into()))?;
288 let max_iterations = map
289 .get("max_iterations")
290 .and_then(|v| match v {
291 DslValue::Integer(i) => Some(*i as u32),
292 DslValue::Number(n) => Some(*n as u32),
293 _ => None,
294 })
295 .unwrap_or(10);
296 let max_tokens = map
297 .get("max_tokens")
298 .and_then(|v| match v {
299 DslValue::Integer(i) => Some(*i as u32),
300 DslValue::Number(n) => Some(*n as u32),
301 _ => None,
302 })
303 .unwrap_or(100_000);
304 Ok((system, user, max_iterations, max_tokens))
305 }
306 [DslValue::String(system), DslValue::String(user)] => {
308 Ok((system.clone(), user.clone(), 10, 100_000))
309 }
310 [DslValue::String(system), DslValue::String(user), DslValue::Integer(max_iter)] => {
312 Ok((system.clone(), user.clone(), *max_iter as u32, 100_000))
313 }
314 _ => Err(ReplError::Execution(
315 "reason requires (system: string, user: string, [max_iterations?, max_tokens?])".into(),
316 )),
317 }
318}
319
320pub fn json_to_dsl_value(value: &serde_json::Value) -> DslValue {
322 match value {
323 serde_json::Value::Null => DslValue::Null,
324 serde_json::Value::Bool(b) => DslValue::Boolean(*b),
325 serde_json::Value::Number(n) => {
326 if let Some(i) = n.as_i64() {
327 DslValue::Integer(i)
328 } else if let Some(f) = n.as_f64() {
329 DslValue::Number(f)
330 } else {
331 DslValue::Number(0.0)
332 }
333 }
334 serde_json::Value::String(s) => DslValue::String(s.clone()),
335 serde_json::Value::Array(arr) => {
336 DslValue::List(arr.iter().map(json_to_dsl_value).collect())
337 }
338 serde_json::Value::Object(obj) => {
339 let map: HashMap<String, DslValue> = obj
340 .iter()
341 .map(|(k, v)| (k.clone(), json_to_dsl_value(v)))
342 .collect();
343 DslValue::Map(map)
344 }
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn test_parse_json_valid() {
354 let result =
355 builtin_parse_json(&[DslValue::String(r#"{"key": "value", "num": 42}"#.into())])
356 .unwrap();
357 match result {
358 DslValue::Map(map) => {
359 assert_eq!(map.get("key"), Some(&DslValue::String("value".into())));
360 assert_eq!(map.get("num"), Some(&DslValue::Integer(42)));
361 }
362 _ => panic!("Expected Map"),
363 }
364 }
365
366 #[test]
367 fn test_parse_json_array() {
368 let result = builtin_parse_json(&[DslValue::String("[1, 2, 3]".into())]).unwrap();
369 match result {
370 DslValue::List(items) => {
371 assert_eq!(items.len(), 3);
372 assert_eq!(items[0], DslValue::Integer(1));
373 }
374 _ => panic!("Expected List"),
375 }
376 }
377
378 #[test]
379 fn test_parse_json_invalid() {
380 let result = builtin_parse_json(&[DslValue::String("not json".into())]);
381 assert!(result.is_err());
382 }
383
384 #[test]
385 fn test_parse_json_nested() {
386 let json = r#"{"tasks": [{"id": 1, "done": false}], "count": 1}"#;
387 let result = builtin_parse_json(&[DslValue::String(json.into())]).unwrap();
388 match result {
389 DslValue::Map(map) => match map.get("tasks") {
390 Some(DslValue::List(tasks)) => {
391 assert_eq!(tasks.len(), 1);
392 match &tasks[0] {
393 DslValue::Map(task) => {
394 assert_eq!(task.get("id"), Some(&DslValue::Integer(1)));
395 assert_eq!(task.get("done"), Some(&DslValue::Boolean(false)));
396 }
397 _ => panic!("Expected Map in list"),
398 }
399 }
400 _ => panic!("Expected List for tasks"),
401 },
402 _ => panic!("Expected Map"),
403 }
404 }
405
406 #[test]
407 fn test_json_to_dsl_value_all_types() {
408 let json = serde_json::json!({
409 "str": "hello",
410 "int": 42,
411 "float": 3.14,
412 "bool": true,
413 "null": null,
414 "arr": [1, 2],
415 "obj": {"nested": "value"}
416 });
417
418 let dsl = json_to_dsl_value(&json);
419 match dsl {
420 DslValue::Map(map) => {
421 assert_eq!(map.get("str"), Some(&DslValue::String("hello".into())));
422 assert_eq!(map.get("int"), Some(&DslValue::Integer(42)));
423 assert_eq!(map.get("bool"), Some(&DslValue::Boolean(true)));
424 assert_eq!(map.get("null"), Some(&DslValue::Null));
425 }
426 _ => panic!("Expected Map"),
427 }
428 }
429
430 #[test]
431 fn test_parse_reason_args_positional() {
432 let args = vec![
433 DslValue::String("system prompt".into()),
434 DslValue::String("user message".into()),
435 ];
436 let (system, user, max_iter, max_tokens) = parse_reason_args(&args).unwrap();
437 assert_eq!(system, "system prompt");
438 assert_eq!(user, "user message");
439 assert_eq!(max_iter, 10);
440 assert_eq!(max_tokens, 100_000);
441 }
442
443 #[test]
444 fn test_parse_reason_args_named() {
445 let mut map = HashMap::new();
446 map.insert("system".into(), DslValue::String("sys".into()));
447 map.insert("user".into(), DslValue::String("usr".into()));
448 map.insert("max_iterations".into(), DslValue::Integer(5));
449
450 let args = vec![DslValue::Map(map)];
451 let (system, user, max_iter, max_tokens) = parse_reason_args(&args).unwrap();
452 assert_eq!(system, "sys");
453 assert_eq!(user, "usr");
454 assert_eq!(max_iter, 5);
455 assert_eq!(max_tokens, 100_000);
456 }
457
458 #[test]
459 fn test_parse_reason_args_missing_required() {
460 let mut map = HashMap::new();
461 map.insert("system".into(), DslValue::String("sys".into()));
462 let args = vec![DslValue::Map(map)];
465 assert!(parse_reason_args(&args).is_err());
466 }
467}