fix: load DeepSeek config from browser runtime

This commit is contained in:
zyl
2026-03-27 00:34:14 +08:00
parent 11c0b0fc70
commit 0e3af5a391
8 changed files with 531 additions and 52 deletions

View File

@@ -1,5 +1,6 @@
mod common;
use std::fs;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::path::PathBuf;
@@ -9,8 +10,13 @@ use std::time::Duration;
use common::MockTransport;
use serde_json::{json, Value};
use sgclaw::agent::handle_browser_message;
use sgclaw::agent::{
handle_browser_message,
handle_browser_message_with_context,
AgentRuntimeContext,
};
use sgclaw::compat::runtime::execute_task;
use sgclaw::config::DeepSeekSettings;
use sgclaw::pipe::{Action, AgentMessage, BrowserMessage, BrowserPipeTool, Timing};
use sgclaw::security::MacPolicy;
use uuid::Uuid;
@@ -40,6 +46,21 @@ fn temp_workspace_root() -> PathBuf {
root
}
fn write_deepseek_config(root: &PathBuf, api_key: &str, base_url: &str, model: &str) -> PathBuf {
let config_path = root.join("sgclaw_config.json");
fs::write(
&config_path,
serde_json::to_string_pretty(&json!({
"apiKey": api_key,
"baseUrl": base_url,
"model": model,
}))
.unwrap(),
)
.unwrap();
config_path
}
fn start_fake_deepseek_server(
responses: Vec<Value>,
) -> (String, Arc<Mutex<Vec<Value>>>, thread::JoinHandle<()>) {
@@ -177,6 +198,7 @@ fn compat_runtime_uses_zeroclaw_provider_path_and_executes_browser_actions() {
std::env::set_var("DEEPSEEK_MODEL", "deepseek-chat");
let workspace_root = temp_workspace_root();
let settings = DeepSeekSettings::from_env().unwrap();
let transport = Arc::new(MockTransport::new(vec![
BrowserMessage::Response {
seq: 1,
@@ -211,6 +233,7 @@ fn compat_runtime_uses_zeroclaw_provider_path_and_executes_browser_actions() {
browser_tool,
"打开百度搜索天气",
&workspace_root,
&settings,
)
.unwrap();
server_handle.join().unwrap();
@@ -255,7 +278,151 @@ fn compat_runtime_uses_zeroclaw_provider_path_and_executes_browser_actions() {
}
#[test]
fn handle_browser_message_uses_compat_runtime_summary_when_deepseek_env_is_set() {
fn handle_browser_message_prefers_compat_runtime_for_supported_instruction_when_deepseek_is_configured() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let first_response = json!({
"choices": [{
"message": {
"content": "",
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {
"name": "browser_action",
"arguments": serde_json::to_string(&json!({
"action": "navigate",
"expected_domain": "www.baidu.com",
"url": "https://www.baidu.com"
})).unwrap()
}
},
{
"id": "call_2",
"type": "function",
"function": {
"name": "browser_action",
"arguments": serde_json::to_string(&json!({
"action": "type",
"expected_domain": "www.baidu.com",
"selector": "#kw",
"text": "天气",
"clear_first": true
})).unwrap()
}
},
{
"id": "call_3",
"type": "function",
"function": {
"name": "browser_action",
"arguments": serde_json::to_string(&json!({
"action": "click",
"expected_domain": "www.baidu.com",
"selector": "#su"
})).unwrap()
}
}
]
}
}]
});
let second_response = json!({
"choices": [{
"message": {
"content": "已通过 DeepSeek 执行任务: 打开百度搜索天气"
}
}]
});
let (base_url, requests, server_handle) =
start_fake_deepseek_server(vec![first_response, second_response]);
std::env::remove_var("DEEPSEEK_API_KEY");
std::env::remove_var("DEEPSEEK_BASE_URL");
std::env::remove_var("DEEPSEEK_MODEL");
let workspace_root = temp_workspace_root();
let config_path = write_deepseek_config(
&workspace_root,
"deepseek-test-key",
&base_url,
"deepseek-chat",
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![
BrowserMessage::Response {
seq: 1,
success: true,
data: json!({ "navigated": true }),
aom_snapshot: vec![],
timing: Timing {
queue_ms: 1,
exec_ms: 10,
},
},
BrowserMessage::Response {
seq: 2,
success: true,
data: json!({ "typed": true }),
aom_snapshot: vec![],
timing: Timing {
queue_ms: 1,
exec_ms: 10,
},
},
BrowserMessage::Response {
seq: 3,
success: true,
data: json!({ "clicked": 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));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "打开百度搜索天气".to_string(),
},
)
.unwrap();
server_handle.join().unwrap();
let sent = transport.sent_messages();
let request_bodies = requests.lock().unwrap().clone();
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary == "已通过 DeepSeek 执行任务: 打开百度搜索天气"
)
}));
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::LogEntry { level, message }
if level == "mode" && message == "compat_llm_primary"
)
}));
assert_eq!(request_bodies.len(), 2);
}
#[test]
fn handle_browser_message_falls_back_to_compat_runtime_for_unsupported_instruction() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let first_response = json!({
@@ -284,7 +451,8 @@ fn handle_browser_message_uses_compat_runtime_summary_when_deepseek_env_is_set()
}
}]
});
let (base_url, _, server_handle) = start_fake_deepseek_server(vec![first_response, second_response]);
let (base_url, requests, server_handle) =
start_fake_deepseek_server(vec![first_response, second_response]);
std::env::set_var("DEEPSEEK_API_KEY", "deepseek-test-key");
std::env::set_var("DEEPSEEK_BASE_URL", base_url);
@@ -315,7 +483,7 @@ fn handle_browser_message_uses_compat_runtime_summary_when_deepseek_env_is_set()
transport.as_ref(),
&browser_tool,
BrowserMessage::SubmitTask {
instruction: "打开百度搜索天气".to_string(),
instruction: "帮我打开百度首页".to_string(),
},
)
.unwrap();
@@ -323,6 +491,7 @@ fn handle_browser_message_uses_compat_runtime_summary_when_deepseek_env_is_set()
std::env::set_current_dir(original_dir).unwrap();
let sent = transport.sent_messages();
let request_bodies = requests.lock().unwrap().clone();
assert!(sent.iter().any(|message| {
matches!(
@@ -331,4 +500,12 @@ fn handle_browser_message_uses_compat_runtime_summary_when_deepseek_env_is_set()
if *success && summary == "来自 ZeroClaw runtime"
)
}));
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::LogEntry { level, message }
if level == "mode" && message == "compat_llm_primary"
)
}));
assert_eq!(request_bodies.len(), 2);
}