1use crate::dsl::agent_composition::{check_comm_policy, log_comm_message};
8use crate::dsl::evaluator::DslValue;
9use crate::error::{ReplError, Result};
10use std::collections::HashMap;
11use std::sync::Arc;
12use std::time::Duration;
13use symbi_runtime::communication::policy_gate::CommunicationPolicyGate;
14use symbi_runtime::communication::CommunicationBus;
15use symbi_runtime::reasoning::agent_registry::AgentRegistry;
16use symbi_runtime::reasoning::inference::InferenceProvider;
17use symbi_runtime::types::{AgentId, MessageType, RequestId};
18
19#[derive(Clone, Default)]
21pub struct ReasoningBuiltinContext {
22 pub provider: Option<Arc<dyn InferenceProvider>>,
24 pub agent_registry: Option<Arc<AgentRegistry>>,
26 pub sender_agent_id: Option<AgentId>,
28 pub comm_bus: Option<Arc<dyn CommunicationBus + Send + Sync>>,
30 pub comm_policy: Option<Arc<CommunicationPolicyGate>>,
32}
33
34pub async fn builtin_reason(args: &[DslValue], ctx: &ReasoningBuiltinContext) -> Result<DslValue> {
44 let provider = ctx
45 .provider
46 .as_ref()
47 .ok_or_else(|| ReplError::Execution("No inference provider configured".into()))?;
48
49 let (system, user, max_iterations, max_tokens) = parse_reason_args(args)?;
50
51 use symbi_runtime::reasoning::circuit_breaker::CircuitBreakerRegistry;
52 use symbi_runtime::reasoning::context_manager::DefaultContextManager;
53 use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
54 use symbi_runtime::reasoning::executor::DefaultActionExecutor;
55 use symbi_runtime::reasoning::loop_types::{BufferedJournal, LoopConfig};
56 use symbi_runtime::reasoning::policy_bridge::DefaultPolicyGate;
57 use symbi_runtime::reasoning::reasoning_loop::ReasoningLoopRunner;
58
59 let runner = ReasoningLoopRunner {
60 provider: Arc::clone(provider),
61 policy_gate: Arc::new(DefaultPolicyGate::permissive()),
62 executor: Arc::new(DefaultActionExecutor::default()),
63 context_manager: Arc::new(DefaultContextManager::default()),
64 circuit_breakers: Arc::new(CircuitBreakerRegistry::default()),
65 journal: Arc::new(BufferedJournal::new(1000)),
66 knowledge_bridge: None,
67 };
68
69 let mut conv = Conversation::with_system(&system);
70 conv.push(ConversationMessage::user(&user));
71
72 let config = LoopConfig {
73 max_iterations,
74 max_total_tokens: max_tokens,
75 ..Default::default()
76 };
77
78 let result = runner.run(AgentId::new(), conv, config).await;
79
80 let mut map = HashMap::new();
81 map.insert("response".to_string(), DslValue::String(result.output));
82 map.insert(
83 "iterations".to_string(),
84 DslValue::Integer(result.iterations as i64),
85 );
86 map.insert(
87 "total_tokens".to_string(),
88 DslValue::Integer(result.total_usage.total_tokens as i64),
89 );
90 map.insert(
91 "termination_reason".to_string(),
92 DslValue::String(format!("{:?}", result.termination_reason)),
93 );
94
95 Ok(DslValue::Map(map))
96}
97
98pub async fn builtin_llm_call(
108 args: &[DslValue],
109 ctx: &ReasoningBuiltinContext,
110) -> Result<DslValue> {
111 let provider = ctx
112 .provider
113 .as_ref()
114 .ok_or_else(|| ReplError::Execution("No inference provider configured".into()))?;
115
116 let prompt = match args.first() {
117 Some(DslValue::String(s)) => s.clone(),
118 Some(DslValue::Map(map)) => map
119 .get("prompt")
120 .and_then(|v| match v {
121 DslValue::String(s) => Some(s.clone()),
122 _ => None,
123 })
124 .ok_or_else(|| ReplError::Execution("llm_call requires 'prompt' argument".into()))?,
125 _ => {
126 return Err(ReplError::Execution(
127 "llm_call requires a string prompt".into(),
128 ))
129 }
130 };
131
132 use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
133 use symbi_runtime::reasoning::inference::InferenceOptions;
134
135 let mut conv = Conversation::new();
136 conv.push(ConversationMessage::user(&prompt));
137
138 let options = InferenceOptions::default();
139 let response = provider
140 .complete(&conv, &options)
141 .await
142 .map_err(|e| ReplError::Execution(format!("LLM call failed: {}", e)))?;
143
144 Ok(DslValue::String(response.content))
145}
146
147pub fn builtin_parse_json(args: &[DslValue]) -> Result<DslValue> {
154 let text = match args.first() {
155 Some(DslValue::String(s)) => s,
156 _ => {
157 return Err(ReplError::Execution(
158 "parse_json requires a string argument".into(),
159 ))
160 }
161 };
162
163 let value: serde_json::Value = serde_json::from_str(text)
164 .map_err(|e| ReplError::Execution(format!("JSON parse error: {}", e)))?;
165
166 Ok(json_to_dsl_value(&value))
167}
168
169pub async fn builtin_tool_call(
177 args: &[DslValue],
178 _ctx: &ReasoningBuiltinContext,
179) -> Result<DslValue> {
180 let (name, arguments) = match args {
181 [DslValue::String(name), DslValue::Map(args_map)] => {
182 let json_args: serde_json::Map<String, serde_json::Value> = args_map
183 .iter()
184 .map(|(k, v)| (k.clone(), v.to_json()))
185 .collect();
186 (
187 name.clone(),
188 serde_json::Value::Object(json_args).to_string(),
189 )
190 }
191 [DslValue::String(name), DslValue::String(args_str)] => (name.clone(), args_str.clone()),
192 [DslValue::String(name)] => (name.clone(), "{}".to_string()),
193 _ => {
194 return Err(ReplError::Execution(
195 "tool_call requires (name: string, args?: map|string)".into(),
196 ))
197 }
198 };
199
200 let mut result = HashMap::new();
203 result.insert("tool".to_string(), DslValue::String(name));
204 result.insert("arguments".to_string(), DslValue::String(arguments));
205 result.insert(
206 "status".to_string(),
207 DslValue::String("executed".to_string()),
208 );
209
210 Ok(DslValue::Map(result))
211}
212
213pub async fn builtin_delegate(
222 args: &[DslValue],
223 ctx: &ReasoningBuiltinContext,
224) -> Result<DslValue> {
225 let (agent_name, message) = match args {
226 [DslValue::String(agent), DslValue::String(msg)] => (agent.clone(), msg.clone()),
227 [DslValue::Map(map)] => {
228 let agent = map
229 .get("agent")
230 .and_then(|v| match v {
231 DslValue::String(s) => Some(s.clone()),
232 _ => None,
233 })
234 .ok_or_else(|| ReplError::Execution("delegate requires 'agent' argument".into()))?;
235 let msg = map
236 .get("message")
237 .and_then(|v| match v {
238 DslValue::String(s) => Some(s.clone()),
239 _ => None,
240 })
241 .ok_or_else(|| {
242 ReplError::Execution("delegate requires 'message' argument".into())
243 })?;
244 (agent, msg)
245 }
246 _ => {
247 return Err(ReplError::Execution(
248 "delegate requires (agent: string, message: string)".into(),
249 ))
250 }
251 };
252
253 let recipient_id = if let Some(registry) = &ctx.agent_registry {
255 registry
256 .get_agent(&agent_name)
257 .await
258 .map(|a| a.agent_id)
259 .unwrap_or_default()
260 } else {
261 AgentId::new()
262 };
263 let sender_id = ctx.sender_agent_id.unwrap_or_default();
264 let request_id = RequestId::new();
265
266 check_comm_policy(
267 ctx,
268 sender_id,
269 recipient_id,
270 MessageType::Request(request_id),
271 )?;
272 log_comm_message(
273 ctx,
274 sender_id,
275 recipient_id,
276 &message,
277 MessageType::Request(request_id),
278 Duration::from_secs(30),
279 )
280 .await;
281
282 let provider = ctx
284 .provider
285 .as_ref()
286 .ok_or_else(|| ReplError::Execution("No inference provider configured".into()))?;
287
288 use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
289 use symbi_runtime::reasoning::inference::InferenceOptions;
290
291 let mut conv = Conversation::with_system(format!(
292 "You are agent '{}'. Respond to the delegated task.",
293 agent_name
294 ));
295 conv.push(ConversationMessage::user(&message));
296
297 let response = provider
298 .complete(&conv, &InferenceOptions::default())
299 .await
300 .map_err(|e| {
301 ReplError::Execution(format!("Delegation to '{}' failed: {}", agent_name, e))
302 })?;
303
304 log_comm_message(
305 ctx,
306 recipient_id,
307 sender_id,
308 &response.content,
309 MessageType::Response(request_id),
310 Duration::from_secs(30),
311 )
312 .await;
313
314 Ok(DslValue::String(response.content))
315}
316
317fn parse_reason_args(args: &[DslValue]) -> Result<(String, String, u32, u32)> {
320 match args {
321 [DslValue::Map(map)] => {
323 let system = map
324 .get("system")
325 .and_then(|v| match v {
326 DslValue::String(s) => Some(s.clone()),
327 _ => None,
328 })
329 .ok_or_else(|| ReplError::Execution("reason requires 'system' argument".into()))?;
330 let user = map
331 .get("user")
332 .and_then(|v| match v {
333 DslValue::String(s) => Some(s.clone()),
334 _ => None,
335 })
336 .ok_or_else(|| ReplError::Execution("reason requires 'user' argument".into()))?;
337 let max_iterations = map
338 .get("max_iterations")
339 .and_then(|v| match v {
340 DslValue::Integer(i) => Some(*i as u32),
341 DslValue::Number(n) => Some(*n as u32),
342 _ => None,
343 })
344 .unwrap_or(10);
345 let max_tokens = map
346 .get("max_tokens")
347 .and_then(|v| match v {
348 DslValue::Integer(i) => Some(*i as u32),
349 DslValue::Number(n) => Some(*n as u32),
350 _ => None,
351 })
352 .unwrap_or(100_000);
353 Ok((system, user, max_iterations, max_tokens))
354 }
355 [DslValue::String(system), DslValue::String(user)] => {
357 Ok((system.clone(), user.clone(), 10, 100_000))
358 }
359 [DslValue::String(system), DslValue::String(user), DslValue::Integer(max_iter)] => {
361 Ok((system.clone(), user.clone(), *max_iter as u32, 100_000))
362 }
363 _ => Err(ReplError::Execution(
364 "reason requires (system: string, user: string, [max_iterations?, max_tokens?])".into(),
365 )),
366 }
367}
368
369pub fn json_to_dsl_value(value: &serde_json::Value) -> DslValue {
371 match value {
372 serde_json::Value::Null => DslValue::Null,
373 serde_json::Value::Bool(b) => DslValue::Boolean(*b),
374 serde_json::Value::Number(n) => {
375 if let Some(i) = n.as_i64() {
376 DslValue::Integer(i)
377 } else if let Some(f) = n.as_f64() {
378 DslValue::Number(f)
379 } else {
380 DslValue::Number(0.0)
381 }
382 }
383 serde_json::Value::String(s) => DslValue::String(s.clone()),
384 serde_json::Value::Array(arr) => {
385 DslValue::List(arr.iter().map(json_to_dsl_value).collect())
386 }
387 serde_json::Value::Object(obj) => {
388 let map: HashMap<String, DslValue> = obj
389 .iter()
390 .map(|(k, v)| (k.clone(), json_to_dsl_value(v)))
391 .collect();
392 DslValue::Map(map)
393 }
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_parse_json_valid() {
403 let result =
404 builtin_parse_json(&[DslValue::String(r#"{"key": "value", "num": 42}"#.into())])
405 .unwrap();
406 match result {
407 DslValue::Map(map) => {
408 assert_eq!(map.get("key"), Some(&DslValue::String("value".into())));
409 assert_eq!(map.get("num"), Some(&DslValue::Integer(42)));
410 }
411 _ => panic!("Expected Map"),
412 }
413 }
414
415 #[test]
416 fn test_parse_json_array() {
417 let result = builtin_parse_json(&[DslValue::String("[1, 2, 3]".into())]).unwrap();
418 match result {
419 DslValue::List(items) => {
420 assert_eq!(items.len(), 3);
421 assert_eq!(items[0], DslValue::Integer(1));
422 }
423 _ => panic!("Expected List"),
424 }
425 }
426
427 #[test]
428 fn test_parse_json_invalid() {
429 let result = builtin_parse_json(&[DslValue::String("not json".into())]);
430 assert!(result.is_err());
431 }
432
433 #[test]
434 fn test_parse_json_nested() {
435 let json = r#"{"tasks": [{"id": 1, "done": false}], "count": 1}"#;
436 let result = builtin_parse_json(&[DslValue::String(json.into())]).unwrap();
437 match result {
438 DslValue::Map(map) => match map.get("tasks") {
439 Some(DslValue::List(tasks)) => {
440 assert_eq!(tasks.len(), 1);
441 match &tasks[0] {
442 DslValue::Map(task) => {
443 assert_eq!(task.get("id"), Some(&DslValue::Integer(1)));
444 assert_eq!(task.get("done"), Some(&DslValue::Boolean(false)));
445 }
446 _ => panic!("Expected Map in list"),
447 }
448 }
449 _ => panic!("Expected List for tasks"),
450 },
451 _ => panic!("Expected Map"),
452 }
453 }
454
455 #[test]
456 fn test_json_to_dsl_value_all_types() {
457 let json = serde_json::json!({
458 "str": "hello",
459 "int": 42,
460 "float": 3.14,
461 "bool": true,
462 "null": null,
463 "arr": [1, 2],
464 "obj": {"nested": "value"}
465 });
466
467 let dsl = json_to_dsl_value(&json);
468 match dsl {
469 DslValue::Map(map) => {
470 assert_eq!(map.get("str"), Some(&DslValue::String("hello".into())));
471 assert_eq!(map.get("int"), Some(&DslValue::Integer(42)));
472 assert_eq!(map.get("bool"), Some(&DslValue::Boolean(true)));
473 assert_eq!(map.get("null"), Some(&DslValue::Null));
474 }
475 _ => panic!("Expected Map"),
476 }
477 }
478
479 #[test]
480 fn test_parse_reason_args_positional() {
481 let args = vec![
482 DslValue::String("system prompt".into()),
483 DslValue::String("user message".into()),
484 ];
485 let (system, user, max_iter, max_tokens) = parse_reason_args(&args).unwrap();
486 assert_eq!(system, "system prompt");
487 assert_eq!(user, "user message");
488 assert_eq!(max_iter, 10);
489 assert_eq!(max_tokens, 100_000);
490 }
491
492 #[test]
493 fn test_parse_reason_args_named() {
494 let mut map = HashMap::new();
495 map.insert("system".into(), DslValue::String("sys".into()));
496 map.insert("user".into(), DslValue::String("usr".into()));
497 map.insert("max_iterations".into(), DslValue::Integer(5));
498
499 let args = vec![DslValue::Map(map)];
500 let (system, user, max_iter, max_tokens) = parse_reason_args(&args).unwrap();
501 assert_eq!(system, "sys");
502 assert_eq!(user, "usr");
503 assert_eq!(max_iter, 5);
504 assert_eq!(max_tokens, 100_000);
505 }
506
507 #[test]
508 fn test_parse_reason_args_missing_required() {
509 let mut map = HashMap::new();
510 map.insert("system".into(), DslValue::String("sys".into()));
511 let args = vec![DslValue::Map(map)];
514 assert!(parse_reason_args(&args).is_err());
515 }
516}