feat: refactor sgclaw around zeroclaw compat runtime
This commit is contained in:
156
src/compat/browser_tool_adapter.rs
Normal file
156
src/compat/browser_tool_adapter.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use async_trait::async_trait;
|
||||
use serde_json::{json, Map, Value};
|
||||
use zeroclaw::tools::{Tool, ToolResult};
|
||||
|
||||
use crate::pipe::{Action, BrowserPipeTool, Transport};
|
||||
|
||||
pub const BROWSER_ACTION_TOOL_NAME: &str = "browser_action";
|
||||
|
||||
pub struct ZeroClawBrowserTool<T: Transport> {
|
||||
browser_tool: BrowserPipeTool<T>,
|
||||
}
|
||||
|
||||
impl<T: Transport> ZeroClawBrowserTool<T> {
|
||||
pub fn new(browser_tool: BrowserPipeTool<T>) -> Self {
|
||||
Self { browser_tool }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Transport + 'static> Tool for ZeroClawBrowserTool<T> {
|
||||
fn name(&self) -> &str {
|
||||
BROWSER_ACTION_TOOL_NAME
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Execute browser actions in SuperRPA through the existing sgClaw pipe protocol."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"required": ["action", "expected_domain"],
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["click", "type", "navigate", "getText"]
|
||||
},
|
||||
"expected_domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"selector": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"clear_first": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
|
||||
let request = match parse_browser_action_request(args) {
|
||||
Ok(request) => request,
|
||||
Err(err) => return Ok(failed_tool_result(err.to_string())),
|
||||
};
|
||||
|
||||
let result = match self.browser_tool.invoke(
|
||||
request.action,
|
||||
request.params,
|
||||
&request.expected_domain,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(err) => return Ok(failed_tool_result(err.to_string())),
|
||||
};
|
||||
|
||||
let output = serde_json::to_string(&json!({
|
||||
"seq": result.seq,
|
||||
"success": result.success,
|
||||
"data": result.data,
|
||||
"aom_snapshot": result.aom_snapshot,
|
||||
"timing": result.timing
|
||||
}))?;
|
||||
|
||||
Ok(ToolResult {
|
||||
success: result.success,
|
||||
output,
|
||||
error: (!result.success).then(|| "browser action returned success=false".to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct BrowserActionRequest {
|
||||
action: Action,
|
||||
expected_domain: String,
|
||||
params: Value,
|
||||
}
|
||||
|
||||
fn parse_browser_action_request(args: Value) -> Result<BrowserActionRequest, BrowserActionAdapterError> {
|
||||
let mut args = match args {
|
||||
Value::Object(args) => args,
|
||||
other => {
|
||||
return Err(BrowserActionAdapterError::InvalidArguments(format!(
|
||||
"expected object arguments, got {other}"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let action_name = take_required_string(&mut args, "action")?;
|
||||
let expected_domain = take_required_string(&mut args, "expected_domain")?;
|
||||
let action = parse_action(&action_name)?;
|
||||
|
||||
Ok(BrowserActionRequest {
|
||||
action,
|
||||
expected_domain,
|
||||
params: Value::Object(args),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_action(action_name: &str) -> Result<Action, BrowserActionAdapterError> {
|
||||
match action_name {
|
||||
"click" => Ok(Action::Click),
|
||||
"type" => Ok(Action::Type),
|
||||
"navigate" => Ok(Action::Navigate),
|
||||
"getText" => Ok(Action::GetText),
|
||||
other => Err(BrowserActionAdapterError::UnsupportedAction(
|
||||
other.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_required_string(
|
||||
args: &mut Map<String, Value>,
|
||||
key: &'static str,
|
||||
) -> Result<String, BrowserActionAdapterError> {
|
||||
match args.remove(key) {
|
||||
Some(Value::String(value)) if !value.trim().is_empty() => Ok(value),
|
||||
Some(other) => Err(BrowserActionAdapterError::InvalidArguments(format!(
|
||||
"{key} must be a non-empty string, got {other}"
|
||||
))),
|
||||
None => Err(BrowserActionAdapterError::MissingField(key)),
|
||||
}
|
||||
}
|
||||
|
||||
fn failed_tool_result(error: String) -> ToolResult {
|
||||
ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum BrowserActionAdapterError {
|
||||
#[error("unsupported action: {0}")]
|
||||
UnsupportedAction(String),
|
||||
#[error("missing required field: {0}")]
|
||||
MissingField(&'static str),
|
||||
#[error("invalid tool arguments: {0}")]
|
||||
InvalidArguments(String),
|
||||
}
|
||||
Reference in New Issue
Block a user