use crate::repl;
use crate::tool_definition;
use crate::utils;
use serde_json::json;
pub struct LuaRepl<'a> {
repl: &'a repl::Repl,
options: LuaReplOptions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LuaReplOptions {
pub max_output_len: usize,
}
impl<'a> LuaRepl<'a> {
const DEFAULT_OPTIONS: LuaReplOptions = LuaReplOptions {
max_output_len: 50_000,
};
pub fn new(repl: &'a repl::Repl) -> Self {
Self::new_with(repl, Self::DEFAULT_OPTIONS)
}
pub fn new_with(repl: &'a repl::Repl, options: LuaReplOptions) -> Self {
Self { repl, options }
}
pub fn definition(&self) -> genai::chat::Tool {
genai::chat::Tool::new(tool_definition::NAME)
.with_description(tool_definition::DESCRIPTION)
.with_schema(tool_definition::json_schema())
}
pub fn call(&self, tool_call: &genai::chat::ToolCall) -> genai::chat::ToolResponse {
if tool_call.fn_name != tool_definition::NAME {
return Self::build_error_response(
tool_call.call_id.clone(),
format!(
"Unknown tool: {}. Expected: {}",
tool_call.fn_name,
tool_definition::NAME
),
);
}
let source_code = match &tool_call.fn_arguments[tool_definition::PARAM_SOURCE_CODE] {
serde_json::Value::String(s) => s,
_ => {
return Self::build_error_response(
tool_call.call_id.clone(),
format!(
"Missing or invalid parameter: {}",
tool_definition::PARAM_SOURCE_CODE
),
);
}
};
let eval_outcome = match self.repl.eval(source_code) {
Ok(outcome) => outcome,
Err(err) => {
return Self::build_error_response(
tool_call.call_id.clone(),
format!("REPL evaluation failed: {}", err),
);
}
};
let truncated_output =
utils::truncate_output(&eval_outcome.output.join("\n"), self.options.max_output_len);
let full_result = match eval_outcome.result {
Ok(values) => values.join("\n"),
Err(err) => format!("error: {}", err),
};
let truncated_result = utils::truncate_output(&full_result, self.options.max_output_len);
genai::chat::ToolResponse::new(
tool_call.call_id.clone(),
json!({
"output": truncated_output,
"result": truncated_result
})
.to_string(),
)
}
fn build_error_response(call_id: String, error_message: String) -> genai::chat::ToolResponse {
genai::chat::ToolResponse::new(call_id, json!({ "error": error_message }).to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Repl;
#[test]
fn test_output_truncation_with_long_output() {
let repl = Repl::new().expect("Failed to create REPL");
let options = LuaReplOptions {
max_output_len: 100,
};
let lua_repl = LuaRepl::new_with(&repl, options);
let long_output_code = r#"
print(string.rep("A", 500))
return "result"
"#;
let tool_call = genai::chat::ToolCall {
call_id: "test-123".to_string(),
fn_name: "lua_repl".to_string(),
fn_arguments: serde_json::json!({
"source_code": long_output_code
}),
thought_signatures: None,
};
let response = lua_repl.call(&tool_call);
let response_json: serde_json::Value =
serde_json::from_str(&response.content).expect("Invalid JSON");
let output = response_json["output"].as_str().unwrap();
assert_eq!(output.chars().count(), 100);
assert!(output.ends_with("...\n(output truncated)"));
}
}