use std::collections::HashMap;
use std::sync::Arc;
use anyhow::anyhow;
use async_trait::async_trait;
use rs_utcp::plugins::codemode::{
CodeModeArgs, CodeModeResult, CodeModeUtcp, CodemodeOrchestrator, LlmModel,
};
use serde_json::Value;
use crate::error::AgentError;
use crate::models::LLM;
use crate::tools::Tool;
use crate::types::{Message, Role, ToolRequest, ToolResponse, ToolSpec};
pub struct CodeModeTool {
engine: Arc<CodeModeUtcp>,
}
impl CodeModeTool {
pub fn new(engine: Arc<CodeModeUtcp>) -> Self {
Self { engine }
}
fn spec_from_engine(&self) -> ToolSpec {
let schema = self.engine.tool();
let input_schema = serde_json::to_value(&schema.inputs)
.unwrap_or_else(|_| serde_json::json!({"type": "object"}));
ToolSpec {
name: schema.name,
description: schema.description,
input_schema,
examples: None,
}
}
}
#[async_trait]
impl Tool for CodeModeTool {
fn spec(&self) -> ToolSpec {
self.spec_from_engine()
}
async fn invoke(&self, req: ToolRequest) -> crate::Result<ToolResponse> {
let code = req
.arguments
.get("code")
.and_then(|v| v.as_str())
.ok_or_else(|| AgentError::ToolError("codemode.run_code requires `code`".into()))?;
let timeout = req.arguments.get("timeout").and_then(|v| v.as_u64());
let result = self
.engine
.execute(CodeModeArgs {
code: code.to_string(),
timeout,
})
.await
.map_err(|e| AgentError::ToolError(e.to_string()))?;
let content = serialize_result(&result);
Ok(ToolResponse {
content,
metadata: Some(HashMap::from([(
"provider".to_string(),
"codemode".to_string(),
)])),
})
}
}
pub struct CodemodeLlmAdapter {
llm: Arc<dyn LLM>,
}
impl CodemodeLlmAdapter {
pub fn new(llm: Arc<dyn LLM>) -> Self {
Self { llm }
}
}
#[async_trait]
impl LlmModel for CodemodeLlmAdapter {
async fn complete(&self, prompt: &str) -> anyhow::Result<Value> {
let messages = vec![Message {
role: Role::User,
content: prompt.to_string(),
metadata: None,
}];
let result = self
.llm
.generate(messages, None)
.await
.map_err(|e| anyhow!(e.to_string()))?;
let cleaned = strip_code_fence(&result.content);
Ok(Value::String(cleaned))
}
}
pub fn build_orchestrator(engine: Arc<CodeModeUtcp>, llm: Arc<dyn LLM>) -> CodemodeOrchestrator {
let adapter = CodemodeLlmAdapter::new(llm);
CodemodeOrchestrator::new(engine, Arc::new(adapter))
}
pub fn format_codemode_value(value: &Value) -> String {
if let Some(s) = value.as_str() {
return s.to_string();
}
serde_json::to_string(value).unwrap_or_else(|_| format!("{value:?}"))
}
fn serialize_result(result: &CodeModeResult) -> String {
serde_json::to_string(result).unwrap_or_else(|_| format!("{result:?}"))
}
fn strip_code_fence(s: &str) -> String {
let trimmed = s.trim();
if !trimmed.starts_with("```") {
return trimmed.to_string();
}
let mut inner = trimmed.trim_start_matches("```");
if let Some(pos) = inner.find('\n') {
inner = &inner[pos + 1..];
}
if let Some(end) = inner.rfind("```") {
inner = &inner[..end];
}
inner.trim().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strip_code_fence_removes_markdown_fences() {
assert_eq!(strip_code_fence("```rust\nlet x = 5;\n```"), "let x = 5;");
assert_eq!(strip_code_fence("```\ncode\n```"), "code");
assert_eq!(strip_code_fence("plain text"), "plain text");
}
#[test]
fn format_codemode_value_handles_strings_and_json() {
assert_eq!(format_codemode_value(&Value::String("test".into())), "test");
assert_eq!(
format_codemode_value(&Value::Number(42.into())),
"42"
);
}
}