feat(phase4): complete zclaw-skills, zclaw-hands, zclaw-channels, zclaw-protocols 模块实现
This commit is contained in:
152
crates/zclaw-skills/src/runner.rs
Normal file
152
crates/zclaw-skills/src/runner.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
//! Skill runners for different execution modes
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
use zclaw_types::Result;
|
||||
|
||||
use super::{Skill, SkillContext, SkillManifest, SkillResult};
|
||||
|
||||
/// Prompt-only skill execution
|
||||
pub struct PromptOnlySkill {
|
||||
manifest: SkillManifest,
|
||||
prompt_template: String,
|
||||
}
|
||||
|
||||
impl PromptOnlySkill {
|
||||
pub fn new(manifest: SkillManifest, prompt_template: String) -> Self {
|
||||
Self { manifest, prompt_template }
|
||||
}
|
||||
|
||||
fn format_prompt(&self, input: &Value) -> String {
|
||||
let mut prompt = self.prompt_template.clone();
|
||||
|
||||
if let Value::String(s) = input {
|
||||
prompt = prompt.replace("{{input}}", s);
|
||||
} else {
|
||||
prompt = prompt.replace("{{input}}", &serde_json::to_string_pretty(input).unwrap_or_default());
|
||||
}
|
||||
|
||||
prompt
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Skill for PromptOnlySkill {
|
||||
fn manifest(&self) -> &SkillManifest {
|
||||
&self.manifest
|
||||
}
|
||||
|
||||
async fn execute(&self, _context: &SkillContext, input: Value) -> Result<SkillResult> {
|
||||
let prompt = self.format_prompt(&input);
|
||||
Ok(SkillResult::success(Value::String(prompt)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Python script skill execution
|
||||
pub struct PythonSkill {
|
||||
manifest: SkillManifest,
|
||||
script_path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl PythonSkill {
|
||||
pub fn new(manifest: SkillManifest, script_path: std::path::PathBuf) -> Self {
|
||||
Self { manifest, script_path }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Skill for PythonSkill {
|
||||
fn manifest(&self) -> &SkillManifest {
|
||||
&self.manifest
|
||||
}
|
||||
|
||||
async fn execute(&self, context: &SkillContext, input: Value) -> Result<SkillResult> {
|
||||
let start = Instant::now();
|
||||
let input_json = serde_json::to_string(&input).unwrap_or_default();
|
||||
|
||||
let output = Command::new("python3")
|
||||
.arg(&self.script_path)
|
||||
.env("SKILL_INPUT", &input_json)
|
||||
.env("AGENT_ID", &context.agent_id)
|
||||
.env("SESSION_ID", &context.session_id)
|
||||
.output()
|
||||
.map_err(|e| zclaw_types::ZclawError::ToolError(format!("Failed to execute Python: {}", e)))?;
|
||||
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let result = serde_json::from_str(&stdout)
|
||||
.map(|v| SkillResult {
|
||||
success: true,
|
||||
output: v,
|
||||
error: None,
|
||||
duration_ms: Some(duration_ms),
|
||||
tokens_used: None,
|
||||
})
|
||||
.unwrap_or_else(|_| SkillResult::success(Value::String(stdout.to_string())));
|
||||
Ok(result)
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
Ok(SkillResult::error(stderr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shell command skill execution
|
||||
pub struct ShellSkill {
|
||||
manifest: SkillManifest,
|
||||
command: String,
|
||||
}
|
||||
|
||||
impl ShellSkill {
|
||||
pub fn new(manifest: SkillManifest, command: String) -> Self {
|
||||
Self { manifest, command }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Skill for ShellSkill {
|
||||
fn manifest(&self) -> &SkillManifest {
|
||||
&self.manifest
|
||||
}
|
||||
|
||||
async fn execute(&self, context: &SkillContext, input: Value) -> Result<SkillResult> {
|
||||
let start = Instant::now();
|
||||
|
||||
let mut cmd = self.command.clone();
|
||||
if let Value::String(s) = input {
|
||||
cmd = cmd.replace("{{input}}", &s);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let output = {
|
||||
Command::new("cmd")
|
||||
.args(["/C", &cmd])
|
||||
.current_dir(context.working_dir.as_ref().unwrap_or(&std::path::PathBuf::from(".")))
|
||||
.output()
|
||||
.map_err(|e| zclaw_types::ZclawError::ToolError(format!("Failed to execute shell: {}", e)))?
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let output = {
|
||||
Command::new("sh")
|
||||
.args(["-c", &cmd])
|
||||
.current_dir(context.working_dir.as_ref().unwrap_or(&std::path::PathBuf::from(".")))
|
||||
.output()
|
||||
.map_err(|e| zclaw_types::ZclawError::ToolError(format!("Failed to execute shell: {}", e)))?
|
||||
};
|
||||
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
Ok(SkillResult::success(Value::String(stdout.to_string())))
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
Ok(SkillResult::error(stderr))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user