179 lines
5.2 KiB
Rust
179 lines
5.2 KiB
Rust
mod common;
|
|
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use common::MockTransport;
|
|
use sgclaw::agent::handle_browser_message;
|
|
use sgclaw::agent::runtime::{browser_action_tool_definition, execute_task_with_provider};
|
|
use sgclaw::llm::{ChatMessage, LlmError, LlmProvider, ToolDefinition, ToolFunctionCall};
|
|
use sgclaw::pipe::{Action, AgentMessage, BrowserMessage, BrowserPipeTool, Timing};
|
|
use sgclaw::security::MacPolicy;
|
|
|
|
struct FakeProvider {
|
|
calls: Vec<ToolFunctionCall>,
|
|
}
|
|
|
|
impl LlmProvider for FakeProvider {
|
|
fn chat(
|
|
&self,
|
|
_messages: &[ChatMessage],
|
|
_tools: &[ToolDefinition],
|
|
) -> Result<Vec<ToolFunctionCall>, LlmError> {
|
|
Ok(self.calls.clone())
|
|
}
|
|
}
|
|
|
|
fn test_policy() -> MacPolicy {
|
|
MacPolicy::from_json_str(
|
|
r#"{
|
|
"version": "1.0",
|
|
"domains": { "allowed": ["www.baidu.com"] },
|
|
"pipe_actions": {
|
|
"allowed": ["click", "type", "navigate", "getText"],
|
|
"blocked": []
|
|
}
|
|
}"#,
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn browser_action_tool_definition_uses_expected_name() {
|
|
let tool = browser_action_tool_definition();
|
|
|
|
assert_eq!(tool.name, "browser_action");
|
|
assert_eq!(tool.parameters["required"][0], "action");
|
|
assert_eq!(tool.parameters["required"][1], "expected_domain");
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_executes_provider_tool_calls_and_returns_summary() {
|
|
let transport = Arc::new(MockTransport::new(vec![
|
|
BrowserMessage::Response {
|
|
seq: 1,
|
|
success: true,
|
|
data: serde_json::json!({ "navigated": true }),
|
|
aom_snapshot: vec![],
|
|
timing: Timing {
|
|
queue_ms: 1,
|
|
exec_ms: 10,
|
|
},
|
|
},
|
|
BrowserMessage::Response {
|
|
seq: 2,
|
|
success: true,
|
|
data: serde_json::json!({ "typed": true }),
|
|
aom_snapshot: vec![],
|
|
timing: Timing {
|
|
queue_ms: 1,
|
|
exec_ms: 10,
|
|
},
|
|
},
|
|
]));
|
|
let browser_tool = BrowserPipeTool::new(
|
|
transport.clone(),
|
|
test_policy(),
|
|
vec![1, 2, 3, 4, 5, 6, 7, 8],
|
|
)
|
|
.with_response_timeout(Duration::from_secs(1));
|
|
let provider = FakeProvider {
|
|
calls: vec![
|
|
ToolFunctionCall {
|
|
id: "call-1".to_string(),
|
|
name: "browser_action".to_string(),
|
|
arguments: serde_json::json!({
|
|
"action": "navigate",
|
|
"expected_domain": "www.baidu.com",
|
|
"url": "https://www.baidu.com"
|
|
}),
|
|
},
|
|
ToolFunctionCall {
|
|
id: "call-2".to_string(),
|
|
name: "browser_action".to_string(),
|
|
arguments: serde_json::json!({
|
|
"action": "type",
|
|
"expected_domain": "www.baidu.com",
|
|
"selector": "#kw",
|
|
"text": "天气",
|
|
"clear_first": true
|
|
}),
|
|
},
|
|
],
|
|
};
|
|
|
|
let summary = execute_task_with_provider(
|
|
transport.as_ref(),
|
|
&browser_tool,
|
|
&provider,
|
|
"打开百度搜索天气",
|
|
)
|
|
.unwrap();
|
|
let sent = transport.sent_messages();
|
|
|
|
assert_eq!(summary, "已通过 Agent 执行任务: 打开百度搜索天气");
|
|
assert!(matches!(
|
|
&sent[0],
|
|
AgentMessage::LogEntry { level, message }
|
|
if level == "info" && message == "navigate www.baidu.com"
|
|
));
|
|
assert!(matches!(
|
|
&sent[1],
|
|
AgentMessage::Command { seq, action, .. }
|
|
if *seq == 1 && action == &Action::Navigate
|
|
));
|
|
assert!(matches!(
|
|
&sent[2],
|
|
AgentMessage::LogEntry { level, message }
|
|
if level == "info" && message == "type www.baidu.com"
|
|
));
|
|
assert!(matches!(
|
|
&sent[3],
|
|
AgentMessage::Command { seq, action, .. }
|
|
if *seq == 2 && action == &Action::Type
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn legacy_agent_runtime_is_explicitly_dev_only() {
|
|
assert!(sgclaw::agent::runtime::LEGACY_DEV_ONLY);
|
|
}
|
|
|
|
#[test]
|
|
fn production_submit_task_does_not_route_into_legacy_runtime_without_llm_config() {
|
|
std::env::remove_var("DEEPSEEK_API_KEY");
|
|
std::env::remove_var("DEEPSEEK_BASE_URL");
|
|
std::env::remove_var("DEEPSEEK_MODEL");
|
|
|
|
let transport = Arc::new(MockTransport::new(vec![]));
|
|
let browser_tool = BrowserPipeTool::new(
|
|
transport.clone(),
|
|
test_policy(),
|
|
vec![1, 2, 3, 4, 5, 6, 7, 8],
|
|
)
|
|
.with_response_timeout(Duration::from_secs(1));
|
|
|
|
handle_browser_message(
|
|
transport.as_ref(),
|
|
&browser_tool,
|
|
BrowserMessage::SubmitTask {
|
|
instruction: "打开百度".to_string(),
|
|
conversation_id: String::new(),
|
|
messages: vec![],
|
|
page_url: String::new(),
|
|
page_title: String::new(),
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
let sent = transport.sent_messages();
|
|
assert!(matches!(
|
|
sent.last(),
|
|
Some(AgentMessage::TaskComplete { success, summary })
|
|
if !success && summary.contains("未配置大语言模型")
|
|
));
|
|
assert!(!sent
|
|
.iter()
|
|
.any(|message| { matches!(message, AgentMessage::Command { .. }) }));
|
|
}
|