cargo fmt

This commit is contained in:
jaberjaber23
2026-05-12 20:57:57 +03:00
parent 4aa1508f54
commit 7185ea8808
19 changed files with 416 additions and 274 deletions
+13 -3
View File
@@ -3856,7 +3856,10 @@ pub async fn audit_append(
};
let outcome = req.outcome.clone().unwrap_or_else(|| "ok".to_string());
let hash = state.kernel.audit_log.record(agent_id, action, detail, outcome);
let hash = state
.kernel
.audit_log
.record(agent_id, action, detail, outcome);
let seq = state.kernel.audit_log.len().saturating_sub(1) as u64;
(
@@ -10063,7 +10066,9 @@ pub async fn clone_agent(
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": format!("Failed to serialize manifest: {e}")})),
Json(
serde_json::json!({"error": format!("Failed to serialize manifest: {e}")}),
),
);
}
};
@@ -10240,7 +10245,12 @@ pub async fn list_agent_files(
// Identity files live in the agent's private state directory (see #1097).
// Fall back to the legacy workspace location for agents created before the
// split so existing on-disk files remain reachable.
let workspace = match entry.manifest.state_dir.as_ref().or(entry.manifest.workspace.as_ref()) {
let workspace = match entry
.manifest
.state_dir
.as_ref()
.or(entry.manifest.workspace.as_ref())
{
Some(ws) => ws.clone(),
None => {
return (
+1 -2
View File
@@ -167,8 +167,7 @@ mod tests {
fn skill_install_request_defaults_back_compat() {
// Existing callers send `{"name": "..."}` only. New optional fields
// must default cleanly (issue #1170).
let req: SkillInstallRequest =
serde_json::from_str(r#"{"name":"github-helper"}"#).unwrap();
let req: SkillInstallRequest = serde_json::from_str(r#"{"name":"github-helper"}"#).unwrap();
assert_eq!(req.name, "github-helper");
assert!(!req.require_signed);
assert!(req.allowed_signer_keys.is_empty());
+7 -6
View File
@@ -1048,7 +1048,9 @@ async fn handle_command(
"command": cmd,
"message": format!("Hand '{}' deactivated.", instance.hand_id),
}),
Err(e) => serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")}),
Err(e) => {
serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")})
}
}
} else {
match state.kernel.stop_agent_run(agent_id) {
@@ -1058,7 +1060,9 @@ async fn handle_command(
Ok(false) => {
serde_json::json!({"type": "command_result", "command": cmd, "message": "No active run to cancel."})
}
Err(e) => serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")}),
Err(e) => {
serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")})
}
}
}
}
@@ -1812,10 +1816,7 @@ mod tests {
// Cookie signed with the wrong secret must fail.
let bad = crate::session_auth::create_session_token("alice", "other-secret", 1);
let mut headers = axum::http::HeaderMap::new();
headers.insert(
"cookie",
format!("openfang_session={bad}").parse().unwrap(),
);
headers.insert("cookie", format!("openfang_session={bad}").parse().unwrap());
let uri = empty_uri();
let ctx = WsAuthCtx {
api_key: "secret",
@@ -1660,10 +1660,7 @@ async fn test_clone_agent_happy_path() {
assert_eq!(manifest["description"], "Cloned for user 1");
assert_eq!(
manifest["tags"].as_array().unwrap(),
&vec![
serde_json::json!("clone"),
serde_json::json!("user-1"),
]
&vec![serde_json::json!("clone"), serde_json::json!("user-1"),]
);
// Inherited from template — the system_prompt should match.
assert_eq!(
@@ -1678,10 +1675,7 @@ async fn test_clone_agent_happy_path() {
.await
.unwrap();
let agents: Vec<serde_json::Value> = resp.json().await.unwrap();
let names: Vec<&str> = agents
.iter()
.map(|a| a["name"].as_str().unwrap())
.collect();
let names: Vec<&str> = agents.iter().map(|a| a["name"].as_str().unwrap()).collect();
assert!(names.contains(&"test-agent"));
assert!(names.contains(&"cloned-user-1"));
}
@@ -1731,10 +1725,7 @@ async fn test_clone_agent_name_collision() {
"duplicate name must return 409 Conflict"
);
let body: serde_json::Value = resp.json().await.unwrap();
assert!(body["error"]
.as_str()
.unwrap()
.contains("already exists"));
assert!(body["error"].as_str().unwrap().contains("already exists"));
// Cloning into the template's own name must also be rejected.
let resp = client
+15 -20
View File
@@ -144,8 +144,7 @@ impl MatrixAdapter {
&& status == reqwest::StatusCode::UNAUTHORIZED
&& is_unknown_token_body(&body_text)
{
match try_refresh_tokens(&self.client, &self.homeserver_url, &self.tokens)
.await
match try_refresh_tokens(&self.client, &self.homeserver_url, &self.tokens).await
{
Ok(()) => {
info!("Matrix: access token refreshed via MSC2918, retrying send");
@@ -717,7 +716,8 @@ mod tests {
#[test]
fn test_is_unknown_token_body() {
// Real matrix.org body for M_UNKNOWN_TOKEN under MAS.
let body = r#"{"errcode":"M_UNKNOWN_TOKEN","error":"Token is not active","soft_logout":true}"#;
let body =
r#"{"errcode":"M_UNKNOWN_TOKEN","error":"Token is not active","soft_logout":true}"#;
assert!(is_unknown_token_body(body));
assert!(!is_hard_logout(body));
@@ -740,19 +740,17 @@ mod tests {
// access and refresh tokens and returns the new pair.
use axum::{routing::post, Json, Router};
async fn refresh_handler(
Json(body): Json<serde_json::Value>,
) -> Json<serde_json::Value> {
let incoming = body
.get("refresh_token")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(incoming, "old_refresh");
Json(serde_json::json!({
"access_token": "new_access",
"refresh_token": "new_refresh",
"expires_in_ms": 3_600_000u64,
}))
async fn refresh_handler(Json(body): Json<serde_json::Value>) -> Json<serde_json::Value> {
let incoming = body
.get("refresh_token")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(incoming, "old_refresh");
Json(serde_json::json!({
"access_token": "new_access",
"refresh_token": "new_refresh",
"expires_in_ms": 3_600_000u64,
}))
}
let app = Router::new().route("/_matrix/client/v3/refresh", post(refresh_handler));
@@ -779,10 +777,7 @@ mod tests {
drop(guard);
// Refresh with no refresh token configured must fail cleanly.
let no_refresh: TokenPair = Arc::new(RwLock::new((
Zeroizing::new("a".to_string()),
None,
)));
let no_refresh: TokenPair = Arc::new(RwLock::new((Zeroizing::new("a".to_string()), None)));
let err = try_refresh_tokens(&client, &homeserver, &no_refresh)
.await
.unwrap_err();
+2 -1
View File
@@ -722,7 +722,8 @@ mod tests {
assert_eq!(resolved, None);
// Missing channel_id on the wire — no match (the binding is restrictive).
let resolved = router.resolve_with_channel_id(&ChannelType::Discord, "any-user", None, None);
let resolved =
router.resolve_with_channel_id(&ChannelType::Discord, "any-user", None, None);
assert_eq!(resolved, None);
}
+1 -3
View File
@@ -329,9 +329,7 @@ impl ChannelAdapter for SlackAdapter {
// connection during the rotation overlap. Ack on
// both, but only forward to the agent once.
if is_duplicate_envelope(&seen_envelopes, envelope_id) {
debug!(
"Slack: skipping duplicate envelope_id {envelope_id}"
);
debug!("Slack: skipping duplicate envelope_id {envelope_id}");
continue;
}
+229 -61
View File
@@ -1234,9 +1234,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.channel, ChannelType::Telegram);
assert_eq!(msg.sender.display_name, "Alice Smith");
assert_eq!(msg.sender.platform_id, "111222333");
@@ -1267,9 +1275,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
// The chat_id (used for replies) stays on sender.platform_id.
assert_eq!(msg.sender.platform_id, "-1009876543210");
@@ -1308,9 +1324,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
let tg_id = msg
.metadata
@@ -1345,9 +1369,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Command { name, args } => {
assert_eq!(name, "agent");
@@ -1379,8 +1411,16 @@ mod tests {
let client = test_client();
// Empty allowed_users = allow all
let msg =
parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new()).await;
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await;
assert!(msg.is_some());
// Non-matching allowed_users = filter out
@@ -1434,9 +1474,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.channel, ChannelType::Telegram);
assert_eq!(msg.sender.display_name, "Alice Smith");
assert!(matches!(msg.content, ChannelContent::Text(ref t) if t == "Edited message!"));
@@ -1472,9 +1520,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Command { name, args } => {
assert_eq!(name, "agents");
@@ -1498,9 +1554,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert!(matches!(msg.content, ChannelContent::Location { .. }));
}
@@ -1524,9 +1588,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
// With a fake token, getFile will fail, so we get a text fallback
match &msg.content {
ChannelContent::Text(t) => {
@@ -1561,9 +1633,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.contains("Document received"));
@@ -1594,9 +1674,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.contains("Voice message"));
@@ -1627,9 +1715,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.thread_id, Some("42".to_string()));
assert!(msg.is_group);
}
@@ -1649,9 +1745,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.thread_id, None);
assert!(!msg.is_group);
}
@@ -1673,9 +1777,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.thread_id, Some("99".to_string()));
}
@@ -1871,9 +1983,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.sender.display_name, "My Channel");
assert_eq!(msg.sender.platform_id, "-1001234567890");
assert!(
@@ -1895,8 +2015,16 @@ mod tests {
});
let client = test_client();
let msg =
parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new()).await;
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await;
assert!(msg.is_none());
}
@@ -2162,9 +2290,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.starts_with("[Replying to Bob: We should use Rust]\n\n"));
@@ -2204,9 +2340,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.starts_with("[Replying to Carol: Sunset view]\n\n"));
@@ -2245,9 +2389,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert_eq!(t, "What was that?");
@@ -2283,9 +2435,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.starts_with("[Replying to Unknown: Anonymous message]\n\n"));
@@ -2310,9 +2470,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert_eq!(t, "Just a normal message");
+28 -30
View File
@@ -1086,11 +1086,10 @@ impl OpenFangKernel {
openfang_runtime::media_understanding::MediaEngine::new(config.media.clone());
// Closes #1051: thread MediaConfig URL overrides into the TTS engine
// so local OpenAI/ElevenLabs-compatible services can be targeted.
let tts_engine = openfang_runtime::tts::TtsEngine::new(config.tts.clone())
.with_base_urls(
config.media.tts_openai_base_url.clone(),
config.media.tts_elevenlabs_base_url.clone(),
);
let tts_engine = openfang_runtime::tts::TtsEngine::new(config.tts.clone()).with_base_urls(
config.media.tts_openai_base_url.clone(),
config.media.tts_elevenlabs_base_url.clone(),
);
let mut pairing = crate::pairing::PairingManager::new(config.pairing.clone());
// Load paired devices from database and set up persistence callback
@@ -1246,16 +1245,16 @@ impl OpenFangKernel {
}
let audit_log_for_cb = Arc::clone(&kernel.audit_log);
kernel.hand_registry.set_audit_callback(Arc::new(
move |hand_id: &str, hash: &str| {
kernel
.hand_registry
.set_audit_callback(Arc::new(move |hand_id: &str, hash: &str| {
audit_log_for_cb.record(
"kernel",
openfang_runtime::audit::AuditAction::ConfigChange,
format!("HAND.toml reload hand={hand_id} sha256={hash}"),
"ok",
);
},
));
}));
}
// Restore persisted agents from SQLite
@@ -1541,9 +1540,7 @@ impl OpenFangKernel {
}
}
if auto_spawned > 0 {
info!(
"Auto-spawned {auto_spawned} agent(s) from ~/.openfang/agents"
);
info!("Auto-spawned {auto_spawned} agent(s) from ~/.openfang/agents");
}
}
}
@@ -1705,7 +1702,10 @@ impl OpenFangKernel {
// does not specify one. When the user sets `workspace = "/path"` in
// agent.toml we leave that path alone — only data/, output/, skills/
// get created lazily so private state never pollutes the target dir.
let workspace_dir = manifest.workspace.clone().unwrap_or_else(|| state_dir.clone());
let workspace_dir = manifest
.workspace
.clone()
.unwrap_or_else(|| state_dir.clone());
ensure_state_dir(&state_dir, &workspace_dir)?;
ensure_workspace(&workspace_dir)?;
if manifest.generate_identity_files {
@@ -2127,7 +2127,10 @@ impl OpenFangKernel {
// to the same path unless the manifest already pinned one. See #1097.
if manifest.state_dir.is_none() {
let state_dir = self.config.effective_workspaces_dir().join(&manifest.name);
let workspace_dir = manifest.workspace.clone().unwrap_or_else(|| state_dir.clone());
let workspace_dir = manifest
.workspace
.clone()
.unwrap_or_else(|| state_dir.clone());
if let Err(e) = ensure_state_dir(&state_dir, &workspace_dir) {
warn!(agent_id = %agent_id, "Failed to backfill state_dir (streaming): {e}");
}
@@ -2699,7 +2702,10 @@ impl OpenFangKernel {
// state_dir). See issue #1097.
if manifest.state_dir.is_none() {
let state_dir = self.config.effective_workspaces_dir().join(&manifest.name);
let workspace_dir = manifest.workspace.clone().unwrap_or_else(|| state_dir.clone());
let workspace_dir = manifest
.workspace
.clone()
.unwrap_or_else(|| state_dir.clone());
if let Err(e) = ensure_state_dir(&state_dir, &workspace_dir) {
warn!(agent_id = %agent_id, "Failed to backfill state_dir: {e}");
}
@@ -5511,9 +5517,7 @@ impl OpenFangKernel {
}
for var in skill.manifest.config.values() {
if let Some(env_name) = var.env.as_deref() {
if let Some(providers) =
env_to_provider.get(&env_name.to_ascii_uppercase())
{
if let Some(providers) = env_to_provider.get(&env_name.to_ascii_uppercase()) {
for provider in providers {
set.insert(provider.clone());
}
@@ -5528,9 +5532,7 @@ impl OpenFangKernel {
// wired that provider into their MCP server.
for server in &self.config.mcp_servers {
for env_name in &server.env {
if let Some(providers) =
env_to_provider.get(&env_name.to_ascii_uppercase())
{
if let Some(providers) = env_to_provider.get(&env_name.to_ascii_uppercase()) {
for provider in providers {
set.insert(provider.clone());
}
@@ -8022,8 +8024,7 @@ mod tests {
assert_eq!(merged.description, "new", "TOML edits must apply");
assert_eq!(
merged.workspace,
entry.workspace,
merged.workspace, entry.workspace,
"kernel-assigned workspace must survive a TOML edit that omits it"
);
assert!(
@@ -8438,9 +8439,7 @@ mod tests {
let first_instance_id = instance.instance_id;
// Sanity: hand is Active and re-activation is rejected.
assert!(kernel
.activate_hand("lead", HashMap::new(), None)
.is_err());
assert!(kernel.activate_hand("lead", HashMap::new(), None).is_err());
// Simulate what POST /api/agents/{id}/stop now does for a hand-owned
// agent: look up the instance and deactivate the hand (which also
@@ -8880,7 +8879,8 @@ mod tests {
"fallback timeout edits must be hot-reloadable"
);
assert!(
plan.hot_actions.contains(&HotAction::ReloadFallbackProviders),
plan.hot_actions
.contains(&HotAction::ReloadFallbackProviders),
"ReloadFallbackProviders must be present in the plan"
);
@@ -9160,9 +9160,7 @@ mod tests {
#[test]
fn test_1188_referenced_providers_walks_mcp_env() {
use openfang_types::config::{
DefaultModelConfig, McpServerConfigEntry, McpTransportEntry,
};
use openfang_types::config::{DefaultModelConfig, McpServerConfigEntry, McpTransportEntry};
let tmp = tempfile::tempdir().unwrap();
let home_dir = tmp.path().join("openfang-1188-mcp");
+11 -7
View File
@@ -69,8 +69,7 @@ fn env_timeout_secs(var: &str) -> Option<u64> {
fn tool_timeout_for(tool_name: &str) -> Option<Duration> {
let secs = match tool_name {
"agent_send" | "agent_spawn" => {
env_timeout_secs("OPENFANG_AGENT_TOOL_TIMEOUT_SECS")
.unwrap_or(AGENT_TOOL_TIMEOUT_SECS)
env_timeout_secs("OPENFANG_AGENT_TOOL_TIMEOUT_SECS").unwrap_or(AGENT_TOOL_TIMEOUT_SECS)
}
_ => env_timeout_secs("OPENFANG_TOOL_TIMEOUT_SECS").unwrap_or(TOOL_TIMEOUT_SECS),
};
@@ -3271,7 +3270,9 @@ mod tests {
assert_eq!(blocks.len(), 2, "must preserve thinking + text");
match &blocks[0] {
ContentBlock::Thinking {
thinking, signature, ..
thinking,
signature,
..
} => {
assert_eq!(thinking, "Let me reason carefully...");
assert_eq!(signature.as_deref(), Some("sig_anthropic_xyz"));
@@ -3367,7 +3368,10 @@ mod tests {
let has_redacted = blocks
.iter()
.any(|b| matches!(b, ContentBlock::RedactedThinking { .. }));
assert!(has_thinking, "Thinking block must be preserved on MaxTokens");
assert!(
has_thinking,
"Thinking block must be preserved on MaxTokens"
);
assert!(
has_redacted,
"RedactedThinking block must be preserved on MaxTokens"
@@ -3409,9 +3413,9 @@ mod tests {
MessageContent::Blocks(b) => b,
other => panic!("expected Blocks content for redacted-only turn, got {other:?}"),
};
let has_redacted = blocks
.iter()
.any(|b| matches!(b, ContentBlock::RedactedThinking { data } if data == "encrypted_only"));
let has_redacted = blocks.iter().any(
|b| matches!(b, ContentBlock::RedactedThinking { data } if data == "encrypted_only"),
);
assert!(
has_redacted,
"RedactedThinking-only turn must be preserved as Blocks"
@@ -189,7 +189,9 @@ enum ContentBlockAccum {
/// Redacted (encrypted) thinking block streamed from Anthropic.
/// The opaque `data` blob arrives on `content_block_start` and must be
/// persisted so the next turn can echo it back verbatim.
RedactedThinking { data: String },
RedactedThinking {
data: String,
},
}
#[async_trait]
@@ -454,10 +456,8 @@ impl LlmDriver for AnthropicDriver {
"thinking" => {
// Some API versions ship the signature on
// content_block_start instead of as a delta.
let initial_sig = block["signature"]
.as_str()
.unwrap_or("")
.to_string();
let initial_sig =
block["signature"].as_str().unwrap_or("").to_string();
blocks.push(ContentBlockAccum::Thinking {
thinking: String::new(),
signature: initial_sig,
@@ -470,10 +470,7 @@ impl LlmDriver for AnthropicDriver {
// verbatim so we can echo it back on the
// next request — API rejects history
// that strips redacted_thinking blocks.
let data = block["data"]
.as_str()
.unwrap_or("")
.to_string();
let data = block["data"].as_str().unwrap_or("").to_string();
blocks.push(ContentBlockAccum::RedactedThinking { data });
}
_ => {}
@@ -751,9 +748,7 @@ fn convert_message(msg: &Message) -> ApiMessage {
if data.is_empty() {
None
} else {
Some(ApiContentBlock::RedactedThinking {
data: data.clone(),
})
Some(ApiContentBlock::RedactedThinking { data: data.clone() })
}
}
ContentBlock::Unknown => None,
@@ -328,9 +328,7 @@ fn convert_content_block(block: &ContentBlock) -> Option<BedrockContentBlock> {
}
}
// Image, Thinking, and Unknown are not supported — silently drop
ContentBlock::Image { .. }
| ContentBlock::Thinking { .. }
| ContentBlock::Unknown => None,
ContentBlock::Image { .. } | ContentBlock::Thinking { .. } | ContentBlock::Unknown => None,
}
}
+3 -3
View File
@@ -21,9 +21,9 @@ use openfang_types::model_catalog::{
HUGGINGFACE_BASE_URL, KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL,
MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, NOVITA_BASE_URL, NVIDIA_NIM_BASE_URL,
OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL,
QWEN_BASE_URL, REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL,
VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
QWEN_BASE_URL, REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL,
VENICE_BASE_URL, VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL,
ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
};
use std::sync::Arc;
+40 -44
View File
@@ -460,8 +460,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
// Convert messages
@@ -474,8 +474,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -484,8 +484,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::Assistant, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -494,8 +494,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Blocks(blocks)) => {
// Handle tool results and images in user messages
@@ -519,8 +519,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: Some(tool_use_id.clone()),
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
ContentBlock::Text { text, .. } => {
parts.push(OaiContentPart::Text { text: text.clone() });
@@ -543,8 +543,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
}
(Role::Assistant, MessageContent::Blocks(blocks)) => {
@@ -915,8 +915,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
for msg in &request.messages {
@@ -928,8 +928,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -938,8 +938,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::Assistant, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -948,8 +948,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Blocks(blocks)) => {
for block in blocks {
@@ -969,8 +969,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: Some(tool_use_id.clone()),
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
}
}
@@ -1492,7 +1492,9 @@ impl LlmDriver for OpenAIDriver {
// non-zero output_tokens so the agent loop doesn't misclassify
// this as a "silent failure" and loop unnecessarily.
if !content.is_empty() && usage.input_tokens == 0 && usage.output_tokens == 0 {
debug!("Stream has content but no usage stats — setting synthetic output_tokens=1");
debug!(
"Stream has content but no usage stats — setting synthetic output_tokens=1"
);
usage.output_tokens = 1;
}
@@ -1956,10 +1958,7 @@ mod tests {
/// the upstream server is pre- or post-vLLM 0.19.
#[test]
fn test_assemble_emits_both_reasoning_fields_for_vllm_compat() {
let driver = OpenAIDriver::new(
"test".to_string(),
"http://localhost:8000/v1".to_string(),
);
let driver = OpenAIDriver::new("test".to_string(), "http://localhost:8000/v1".to_string());
let blocks = vec![
ContentBlock::Thinking {
thinking: "MARKER-vllm-019".to_string(),
@@ -1994,8 +1993,7 @@ mod tests {
/// change in #1157.
#[test]
fn test_assemble_no_reasoning_fields_for_plain_model() {
let driver =
OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let driver = OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let blocks = vec![ContentBlock::Text {
text: "hi".to_string(),
provider_metadata: None,
@@ -2014,18 +2012,14 @@ mod tests {
/// understand the new name.
#[test]
fn test_assemble_moonshot_keeps_legacy_field_only() {
let driver = OpenAIDriver::new(
"test".to_string(),
"https://api.moonshot.cn/v1".to_string(),
);
let blocks = vec![
ContentBlock::ToolUse {
id: "call_1".to_string(),
name: "search".to_string(),
input: serde_json::json!({"q": "x"}),
provider_metadata: None,
},
];
let driver =
OpenAIDriver::new("test".to_string(), "https://api.moonshot.cn/v1".to_string());
let blocks = vec![ContentBlock::ToolUse {
id: "call_1".to_string(),
name: "search".to_string(),
input: serde_json::json!({"q": "x"}),
provider_metadata: None,
}];
let msg = assemble_assistant_message(&blocks, "kimi-k2", &driver);
assert_eq!(
msg.reasoning_content.as_deref(),
@@ -2174,7 +2168,10 @@ mod tests {
Some(OaiMessageContent::Text(t)) => t,
_ => panic!("expected text content"),
};
assert_eq!(content, "answer", "visible content must not include <think>");
assert_eq!(
content, "answer",
"visible content must not include <think>"
);
assert_eq!(
msg.reasoning_content.as_deref(),
Some("internal chain-of-thought"),
@@ -2186,8 +2183,7 @@ mod tests {
/// assistant message — preserve the legacy shape.
#[test]
fn test_assemble_assistant_no_thinking_is_plain() {
let driver =
OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let driver = OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let blocks = vec![ContentBlock::Text {
text: "Hi.".to_string(),
provider_metadata: None,
@@ -258,7 +258,6 @@ fn host_net_fetch(state: &GuestState, params: &serde_json::Value) -> serde_json:
})
}
// ---------------------------------------------------------------------------
// Shell (capability-checked)
// ---------------------------------------------------------------------------
@@ -561,7 +560,10 @@ mod tests {
assert!(web_fetch::check_ssrf("http://127.0.0.1:8080/secret", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://localhost:3000/api", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://169.254.169.254/metadata", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://metadata.google.internal/v1/instance", &no_allow).is_err());
assert!(
web_fetch::check_ssrf("http://metadata.google.internal/v1/instance", &no_allow)
.is_err()
);
// These were previously missing from host_functions — now covered:
assert!(web_fetch::check_ssrf("http://[::1]:8080/secret", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://100.100.100.200/metadata", &no_allow).is_err());
+7 -12
View File
@@ -10,8 +10,8 @@ use openfang_types::model_catalog::{
HUGGINGFACE_BASE_URL, KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL,
MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, NVIDIA_NIM_BASE_URL, OLLAMA_BASE_URL,
OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL,
REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL,
VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL,
VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
};
use std::collections::HashMap;
@@ -1052,10 +1052,7 @@ fn builtin_aliases() -> HashMap<String, String> {
),
("free", "openrouter/meta-llama/llama-3.3-70b-instruct:free"),
("free-reasoning", "openrouter/deepseek/deepseek-r1:free"),
(
"openrouter/free-coder",
"openrouter/qwen/qwen3-coder:free",
),
("openrouter/free-coder", "openrouter/qwen/qwen3-coder:free"),
(
"openrouter/free-large",
"openrouter/openai/gpt-oss-120b:free",
@@ -4715,9 +4712,9 @@ mod tests {
#[test]
fn test_openrouter_free_alias_supports_tools() {
let catalog = ModelCatalog::new();
let entry = catalog.find_model("openrouter/free").expect(
"openrouter/free alias must resolve to a known model",
);
let entry = catalog
.find_model("openrouter/free")
.expect("openrouter/free alias must resolve to a known model");
assert_eq!(entry.provider, "openrouter");
assert!(
entry.supports_tools,
@@ -4730,9 +4727,7 @@ mod tests {
#[test]
fn test_openrouter_free_short_alias_supports_tools() {
let catalog = ModelCatalog::new();
let entry = catalog
.find_model("free")
.expect("free alias must resolve");
let entry = catalog.find_model("free").expect("free alias must resolve");
assert_eq!(entry.provider, "openrouter");
assert!(
entry.supports_tools,
+33 -38
View File
@@ -3119,8 +3119,7 @@ async fn tool_image_generate(
// Closes #1051: route to a local OpenAI-compatible image generation
// service when `media.image_gen_base_url` is set.
let base_url_override = media_engine
.and_then(|e| e.config().image_gen_base_url.as_deref());
let base_url_override = media_engine.and_then(|e| e.config().image_gen_base_url.as_deref());
let result = crate::image_gen::generate_image(&request, base_url_override).await?;
// Save images to workspace if available
@@ -3636,9 +3635,9 @@ fn tool_skill_describe(
.ok_or("Missing 'name' parameter")?
.trim();
let registry = skill_registry.ok_or("No skill registry available")?;
let skill = registry
.get(name)
.ok_or_else(|| format!("Skill '{name}' not found. Use skill_list to see installed skills."))?;
let skill = registry.get(name).ok_or_else(|| {
format!("Skill '{name}' not found. Use skill_list to see installed skills.")
})?;
let body = skill
.manifest
.prompt_context
@@ -3672,9 +3671,9 @@ async fn tool_skill_execute(
.ok_or("Missing 'skill' parameter")?
.trim();
let registry = skill_registry.ok_or("No skill registry available")?;
let skill = registry
.get(skill_name)
.ok_or_else(|| format!("Skill '{skill_name}' not found. Use skill_list to see installed skills."))?;
let skill = registry.get(skill_name).ok_or_else(|| {
format!("Skill '{skill_name}' not found. Use skill_list to see installed skills.")
})?;
// If no tool name was given, default behavior depends on runtime.
// For prompt-only skills, return the SKILL.md body (most useful response
@@ -4017,14 +4016,14 @@ mod tests {
async fn test_create_directory_creates_nested() {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
let result = tool_create_directory(
&serde_json::json!({"path": "a/b/c"}),
Some(root),
)
.await;
let result = tool_create_directory(&serde_json::json!({"path": "a/b/c"}), Some(root)).await;
assert!(result.is_ok(), "Expected Ok, got: {:?}", result);
let expected = root.join("a").join("b").join("c");
assert!(expected.is_dir(), "Expected directory to exist: {}", expected.display());
assert!(
expected.is_dir(),
"Expected directory to exist: {}",
expected.display()
);
}
#[tokio::test]
@@ -4032,18 +4031,10 @@ mod tests {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
// First create
let r1 = tool_create_directory(
&serde_json::json!({"path": "data/logs"}),
Some(root),
)
.await;
let r1 = tool_create_directory(&serde_json::json!({"path": "data/logs"}), Some(root)).await;
assert!(r1.is_ok());
// Second create on existing dir should also succeed
let r2 = tool_create_directory(
&serde_json::json!({"path": "data/logs"}),
Some(root),
)
.await;
let r2 = tool_create_directory(&serde_json::json!({"path": "data/logs"}), Some(root)).await;
assert!(r2.is_ok(), "Expected idempotent success, got: {:?}", r2);
}
@@ -4063,23 +4054,27 @@ mod tests {
"test-id",
"create_directory",
&serde_json::json!({"path": "nested/folder"}),
None, // kernel
None, // allowed_tools
None, // caller_agent_id
None, // skill_registry
None, // mcp_connections
None, // web_ctx
None, // browser_ctx
None, // allowed_env_vars
None, // kernel
None, // allowed_tools
None, // caller_agent_id
None, // skill_registry
None, // mcp_connections
None, // web_ctx
None, // browser_ctx
None, // allowed_env_vars
Some(root.as_path()), // workspace_root
None, // media_engine
None, // exec_policy
None, // tts_engine
None, // docker_config
None, // process_manager
None, // media_engine
None, // exec_policy
None, // tts_engine
None, // docker_config
None, // process_manager
)
.await;
assert!(!result.is_error, "Expected success, got: {}", result.content);
assert!(
!result.is_error,
"Expected success, got: {}",
result.content
);
assert!(root.join("nested").join("folder").is_dir());
}
+4 -11
View File
@@ -60,11 +60,8 @@ impl InstallOptions {
/// Well-known filenames the installer searches for a detached signature
/// envelope, in priority order.
const SIGNATURE_CANDIDATES: &[&str] = &[
"signature.json",
"skill.toml.sig.json",
"SKILL.md.sig.json",
];
const SIGNATURE_CANDIDATES: &[&str] =
&["signature.json", "skill.toml.sig.json", "SKILL.md.sig.json"];
/// Normalize a manifest text for content-binding comparison.
///
@@ -167,10 +164,7 @@ pub fn load_signature(skill_dir: &Path) -> Result<Option<SignedManifest>, SkillE
/// Returns `SkillError::SecurityBlocked` when enforcement is on and the
/// skill fails any of those checks. On failure the caller is expected to
/// remove `skill_dir` to keep the skills directory clean.
pub fn enforce_require_signed(
skill_dir: &Path,
opts: &InstallOptions,
) -> Result<(), SkillError> {
pub fn enforce_require_signed(skill_dir: &Path, opts: &InstallOptions) -> Result<(), SkillError> {
if !opts.require_signed {
return Ok(());
}
@@ -214,8 +208,7 @@ pub fn enforce_require_signed(
// CRLF→LF normalization (applied symmetrically to both sides).
// `package.json` is included because openclaw_compat treats it as a
// valid SKILL manifest source.
const MANIFEST_CANDIDATES: &[&str] =
&["skill.toml", "SKILL.md", "skill.md", "package.json"];
const MANIFEST_CANDIDATES: &[&str] = &["skill.toml", "SKILL.md", "skill.md", "package.json"];
let normalized_envelope = normalize_manifest_text(&envelope.manifest);
let mut bound = false;
for name in MANIFEST_CANDIDATES {
+7 -4
View File
@@ -434,7 +434,9 @@ mod tests {
let restored: ContentBlock = serde_json::from_str(&serialized).unwrap();
match restored {
ContentBlock::Thinking {
thinking, signature, ..
thinking,
signature,
..
} => {
assert_eq!(thinking, "Let me reason about this carefully...");
assert_eq!(
@@ -517,7 +519,9 @@ mod tests {
assert_eq!(blocks.len(), 2);
match &blocks[0] {
ContentBlock::Thinking {
thinking, signature, ..
thinking,
signature,
..
} => {
assert_eq!(thinking, "Internal reasoning");
assert_eq!(signature.as_deref(), Some("sig_xyz"));
@@ -547,8 +551,7 @@ mod tests {
fn test_provider_msg_id_is_recorded_but_not_primary() {
// The LLM-supplied identifier is preserved for debugging only — the
// server-generated `msg_id` is unchanged and remains the primary key.
let msg =
Message::assistant("hi").with_provider_msg_id("msg_abc123_anthropic_collidable");
let msg = Message::assistant("hi").with_provider_msg_id("msg_abc123_anthropic_collidable");
assert_eq!(
msg.provider_msg_id.as_deref(),
Some("msg_abc123_anthropic_collidable")