Addressed clippy lint issues
This commit is contained in:
parent
08d6959e0d
commit
7ef9d8a7b5
@ -387,7 +387,7 @@ fn parse_tool_call_value(value: &serde_json::Value) -> Option<ParsedToolCall> {
|
||||
return Some(ParsedToolCall {
|
||||
name,
|
||||
arguments,
|
||||
tool_call_id: tool_call_id,
|
||||
tool_call_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -409,7 +409,7 @@ fn parse_tool_call_value(value: &serde_json::Value) -> Option<ParsedToolCall> {
|
||||
Some(ParsedToolCall {
|
||||
name,
|
||||
arguments,
|
||||
tool_call_id: tool_call_id,
|
||||
tool_call_id,
|
||||
})
|
||||
}
|
||||
|
||||
@ -2625,15 +2625,13 @@ pub(crate) async fn run_tool_call_loop(
|
||||
ordered_results[*idx] = Some((call.name.clone(), call.tool_call_id.clone(), outcome));
|
||||
}
|
||||
|
||||
for entry in ordered_results {
|
||||
if let Some((tool_name, tool_call_id, outcome)) = entry {
|
||||
individual_results.push((tool_call_id, outcome.output.clone()));
|
||||
let _ = writeln!(
|
||||
tool_results,
|
||||
"<tool_result name=\"{}\">\n{}\n</tool_result>",
|
||||
tool_name, outcome.output
|
||||
);
|
||||
}
|
||||
for (tool_name, tool_call_id, outcome) in ordered_results.into_iter().flatten() {
|
||||
individual_results.push((tool_call_id, outcome.output.clone()));
|
||||
let _ = writeln!(
|
||||
tool_results,
|
||||
"<tool_result name=\"{}\">\n{}\n</tool_result>",
|
||||
tool_name, outcome.output
|
||||
);
|
||||
}
|
||||
|
||||
// Add assistant message with tool calls + tool results to history.
|
||||
|
||||
@ -5,6 +5,7 @@ use parking_lot::Mutex;
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use uuid::Uuid;
|
||||
@ -370,6 +371,7 @@ fn pick_uniform_index(len: usize) -> usize {
|
||||
loop {
|
||||
let value = rand::random::<u64>();
|
||||
if value < reject_threshold {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
return (value % upper) as usize;
|
||||
}
|
||||
}
|
||||
@ -391,7 +393,7 @@ fn encode_emoji_for_discord(emoji: &str) -> String {
|
||||
|
||||
let mut encoded = String::new();
|
||||
for byte in emoji.as_bytes() {
|
||||
encoded.push_str(&format!("%{byte:02X}"));
|
||||
let _ = write!(encoded, "%{byte:02X}");
|
||||
}
|
||||
encoded
|
||||
}
|
||||
@ -1368,7 +1370,10 @@ mod tests {
|
||||
#[test]
|
||||
fn split_message_many_short_lines() {
|
||||
// Many short lines should be batched into chunks under the limit
|
||||
let msg: String = (0..500).map(|i| format!("line {i}\n")).collect();
|
||||
let msg: String = (0..500).fold(String::new(), |mut acc, i| {
|
||||
let _ = writeln!(acc, "line {i}");
|
||||
acc
|
||||
});
|
||||
let parts = split_message_for_discord(&msg);
|
||||
for part in &parts {
|
||||
assert!(
|
||||
|
||||
@ -1295,9 +1295,7 @@ fn sanitize_tool_json_value(
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(object) = value.as_object() else {
|
||||
return None;
|
||||
};
|
||||
let object = value.as_object()?;
|
||||
|
||||
if let Some(tool_calls) = object.get("tool_calls").and_then(|value| value.as_array()) {
|
||||
if !tool_calls.is_empty()
|
||||
@ -1337,7 +1335,7 @@ fn strip_isolated_tool_json_artifacts(message: &str, known_tool_names: &HashSet<
|
||||
let mut saw_tool_call_payload = false;
|
||||
|
||||
while cursor < message.len() {
|
||||
let Some(rel_start) = message[cursor..].find(|ch: char| ch == '{' || ch == '[') else {
|
||||
let Some(rel_start) = message[cursor..].find(['{', '[']) else {
|
||||
cleaned.push_str(&message[cursor..]);
|
||||
break;
|
||||
};
|
||||
@ -2682,7 +2680,7 @@ struct ConfiguredChannel {
|
||||
|
||||
fn collect_configured_channels(
|
||||
config: &Config,
|
||||
_matrix_skip_context: &str,
|
||||
matrix_skip_context: &str,
|
||||
) -> Vec<ConfiguredChannel> {
|
||||
let mut channels = Vec::new();
|
||||
|
||||
@ -2767,7 +2765,7 @@ fn collect_configured_channels(
|
||||
if config.channels_config.matrix.is_some() {
|
||||
tracing::warn!(
|
||||
"Matrix channel is configured but this build was compiled without `channel-matrix`; skipping Matrix {}.",
|
||||
_matrix_skip_context
|
||||
matrix_skip_context
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -429,7 +429,7 @@ mod tests {
|
||||
"message": {
|
||||
"actorType": "users",
|
||||
"actorId": "user_a",
|
||||
"timestamp": 1735701200123u64,
|
||||
"timestamp": 1_735_701_200_123_u64,
|
||||
"message": "hello"
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,6 +6,7 @@ use async_trait::async_trait;
|
||||
use directories::UserDirs;
|
||||
use parking_lot::Mutex;
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use std::fmt::Write as _;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
@ -100,6 +101,7 @@ fn pick_uniform_index(len: usize) -> usize {
|
||||
loop {
|
||||
let value = rand::random::<u64>();
|
||||
if value < reject_threshold {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
return (value % upper) as usize;
|
||||
}
|
||||
}
|
||||
@ -1248,7 +1250,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
if self.mention_only && is_group {
|
||||
let bot_username = self.bot_username.lock();
|
||||
if let Some(ref bot_username) = *bot_username {
|
||||
if !Self::contains_bot_mention(&text, bot_username) {
|
||||
if !Self::contains_bot_mention(text, bot_username) {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
@ -1283,7 +1285,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
let content = if self.mention_only && is_group {
|
||||
let bot_username = self.bot_username.lock();
|
||||
let bot_username = bot_username.as_ref()?;
|
||||
Self::normalize_incoming_content(&text, bot_username)?
|
||||
Self::normalize_incoming_content(text, bot_username)?
|
||||
} else {
|
||||
text.to_string()
|
||||
};
|
||||
@ -1391,7 +1393,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'*' {
|
||||
if let Some(end) = line[i + 2..].find("**") {
|
||||
let inner = Self::escape_html(&line[i + 2..i + 2 + end]);
|
||||
line_out.push_str(&format!("<b>{inner}</b>"));
|
||||
let _ = write!(line_out, "<b>{inner}</b>");
|
||||
i += 4 + end;
|
||||
continue;
|
||||
}
|
||||
@ -1399,7 +1401,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
if i + 1 < len && bytes[i] == b'_' && bytes[i + 1] == b'_' {
|
||||
if let Some(end) = line[i + 2..].find("__") {
|
||||
let inner = Self::escape_html(&line[i + 2..i + 2 + end]);
|
||||
line_out.push_str(&format!("<b>{inner}</b>"));
|
||||
let _ = write!(line_out, "<b>{inner}</b>");
|
||||
i += 4 + end;
|
||||
continue;
|
||||
}
|
||||
@ -1409,7 +1411,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
if let Some(end) = line[i + 1..].find('*') {
|
||||
if end > 0 {
|
||||
let inner = Self::escape_html(&line[i + 1..i + 1 + end]);
|
||||
line_out.push_str(&format!("<i>{inner}</i>"));
|
||||
let _ = write!(line_out, "<i>{inner}</i>");
|
||||
i += 2 + end;
|
||||
continue;
|
||||
}
|
||||
@ -1419,7 +1421,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
if bytes[i] == b'`' && (i == 0 || bytes[i - 1] != b'`') {
|
||||
if let Some(end) = line[i + 1..].find('`') {
|
||||
let inner = Self::escape_html(&line[i + 1..i + 1 + end]);
|
||||
line_out.push_str(&format!("<code>{inner}</code>"));
|
||||
let _ = write!(line_out, "<code>{inner}</code>");
|
||||
i += 2 + end;
|
||||
continue;
|
||||
}
|
||||
@ -1435,9 +1437,8 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
if url.starts_with("http://") || url.starts_with("https://") {
|
||||
let text_html = Self::escape_html(text_part);
|
||||
let url_html = Self::escape_html(url);
|
||||
line_out.push_str(&format!(
|
||||
"<a href=\"{url_html}\">{text_html}</a>"
|
||||
));
|
||||
let _ =
|
||||
write!(line_out, "<a href=\"{url_html}\">{text_html}</a>");
|
||||
i = after_bracket + 1 + paren_end + 1;
|
||||
continue;
|
||||
}
|
||||
@ -1449,7 +1450,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
if i + 1 < len && bytes[i] == b'~' && bytes[i + 1] == b'~' {
|
||||
if let Some(end) = line[i + 2..].find("~~") {
|
||||
let inner = Self::escape_html(&line[i + 2..i + 2 + end]);
|
||||
line_out.push_str(&format!("<s>{inner}</s>"));
|
||||
let _ = write!(line_out, "<s>{inner}</s>");
|
||||
i += 4 + end;
|
||||
continue;
|
||||
}
|
||||
@ -1478,14 +1479,14 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
for line in joined.split('\n') {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("```") {
|
||||
if !in_code_block {
|
||||
in_code_block = true;
|
||||
code_buf.clear();
|
||||
} else {
|
||||
if in_code_block {
|
||||
in_code_block = false;
|
||||
let escaped = code_buf.trim_end_matches('\n');
|
||||
// Telegram HTML parse mode supports <pre> and <code>, but not class attributes.
|
||||
final_out.push_str(&format!("<pre><code>{escaped}</code></pre>\n"));
|
||||
let _ = writeln!(final_out, "<pre><code>{escaped}</code></pre>");
|
||||
code_buf.clear();
|
||||
} else {
|
||||
in_code_block = true;
|
||||
code_buf.clear();
|
||||
}
|
||||
} else if in_code_block {
|
||||
@ -1497,10 +1498,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||
}
|
||||
}
|
||||
if in_code_block && !code_buf.is_empty() {
|
||||
final_out.push_str(&format!(
|
||||
"<pre><code>{}</code></pre>\n",
|
||||
code_buf.trim_end()
|
||||
));
|
||||
let _ = writeln!(final_out, "<pre><code>{}</code></pre>", code_buf.trim_end());
|
||||
}
|
||||
|
||||
final_out.trim_end_matches('\n').to_string()
|
||||
@ -2731,7 +2729,7 @@ mod tests {
|
||||
"update_id": 1,
|
||||
"message": {
|
||||
"message_id": 99,
|
||||
"chat": { "id": -100123456 }
|
||||
"chat": { "id": -100_123_456 }
|
||||
}
|
||||
});
|
||||
|
||||
@ -3824,7 +3822,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn telegram_split_many_short_lines() {
|
||||
let msg: String = (0..1000).map(|i| format!("line {i}\n")).collect();
|
||||
let msg: String = (0..1000).fold(String::new(), |mut acc, i| {
|
||||
let _ = writeln!(acc, "line {i}");
|
||||
acc
|
||||
});
|
||||
let parts = split_message_for_telegram(&msg);
|
||||
for part in &parts {
|
||||
assert!(
|
||||
@ -4130,7 +4131,7 @@ mod tests {
|
||||
/// Skipped automatically when `GROQ_API_KEY` is not set.
|
||||
/// Run: `GROQ_API_KEY=<key> cargo test --lib -- telegram::tests::e2e_live_voice_transcription_and_reply_cache --ignored`
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
#[ignore = "requires GROQ_API_KEY environment variable"]
|
||||
async fn e2e_live_voice_transcription_and_reply_cache() {
|
||||
if std::env::var("GROQ_API_KEY").is_err() {
|
||||
eprintln!("GROQ_API_KEY not set — skipping live voice transcription test");
|
||||
|
||||
@ -335,7 +335,7 @@ mod tests {
|
||||
"text": "Hello from WATI!",
|
||||
"waId": "1234567890",
|
||||
"fromMe": false,
|
||||
"timestamp": 1705320000u64
|
||||
"timestamp": 1_705_320_000_u64
|
||||
});
|
||||
|
||||
let msgs = ch.parse_webhook_payload(&payload);
|
||||
@ -344,7 +344,7 @@ mod tests {
|
||||
assert_eq!(msgs[0].content, "Hello from WATI!");
|
||||
assert_eq!(msgs[0].channel, "wati");
|
||||
assert_eq!(msgs[0].reply_target, "+1234567890");
|
||||
assert_eq!(msgs[0].timestamp, 1705320000);
|
||||
assert_eq!(msgs[0].timestamp, 1_705_320_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -381,7 +381,7 @@ mod tests {
|
||||
"message": { "body": "Alt field test" },
|
||||
"wa_id": "1234567890",
|
||||
"from_me": false,
|
||||
"timestamp": 1705320000u64
|
||||
"timestamp": 1_705_320_000_u64
|
||||
});
|
||||
|
||||
let msgs = ch.parse_webhook_payload(&payload);
|
||||
@ -396,11 +396,11 @@ mod tests {
|
||||
let payload = serde_json::json!({
|
||||
"text": "Test",
|
||||
"waId": "1234567890",
|
||||
"timestamp": 1705320000u64
|
||||
"timestamp": 1_705_320_000_u64
|
||||
});
|
||||
|
||||
let msgs = ch.parse_webhook_payload(&payload);
|
||||
assert_eq!(msgs[0].timestamp, 1705320000);
|
||||
assert_eq!(msgs[0].timestamp, 1_705_320_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -409,11 +409,11 @@ mod tests {
|
||||
let payload = serde_json::json!({
|
||||
"text": "Test",
|
||||
"waId": "1234567890",
|
||||
"timestamp": 1705320000000u64
|
||||
"timestamp": 1_705_320_000_000_u64
|
||||
});
|
||||
|
||||
let msgs = ch.parse_webhook_payload(&payload);
|
||||
assert_eq!(msgs[0].timestamp, 1705320000);
|
||||
assert_eq!(msgs[0].timestamp, 1_705_320_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -426,7 +426,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let msgs = ch.parse_webhook_payload(&payload);
|
||||
assert_eq!(msgs[0].timestamp, 1736942400);
|
||||
assert_eq!(msgs[0].timestamp, 1_736_942_400);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -19,7 +19,7 @@ pub use schema::{
|
||||
WebFetchConfig, WebSearchConfig, WebhookConfig,
|
||||
};
|
||||
|
||||
pub fn name_and_presence<T: traits::ChannelConfig>(channel: &Option<T>) -> (&'static str, bool) {
|
||||
pub fn name_and_presence<T: traits::ChannelConfig>(channel: Option<&T>) -> (&'static str, bool) {
|
||||
(T::name(), channel.is_some())
|
||||
}
|
||||
|
||||
|
||||
@ -457,7 +457,7 @@ fn parse_skills_prompt_injection_mode(raw: &str) -> Option<SkillsPromptInjection
|
||||
}
|
||||
|
||||
/// Skills loading configuration (`[skills]` section).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
|
||||
pub struct SkillsConfig {
|
||||
/// Enable loading and syncing the community open-skills repository.
|
||||
/// Default: `false` (opt-in).
|
||||
@ -473,16 +473,6 @@ pub struct SkillsConfig {
|
||||
pub prompt_injection_mode: SkillsPromptInjectionMode,
|
||||
}
|
||||
|
||||
impl Default for SkillsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open_skills_enabled: false,
|
||||
open_skills_dir: None,
|
||||
prompt_injection_mode: SkillsPromptInjectionMode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Multimodal (image) handling configuration (`[multimodal]` section).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct MultimodalConfig {
|
||||
@ -1940,20 +1930,12 @@ impl Default for HooksConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
|
||||
pub struct BuiltinHooksConfig {
|
||||
/// Enable the command-logger hook (logs tool calls for auditing).
|
||||
pub command_logger: bool,
|
||||
}
|
||||
|
||||
impl Default for BuiltinHooksConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
command_logger: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Autonomy / Security ──────────────────────────────────────────
|
||||
|
||||
/// Autonomy and security policy configuration (`[autonomy]` section).
|
||||
@ -2528,7 +2510,7 @@ pub struct CustomTunnelConfig {
|
||||
struct ConfigWrapper<T: ChannelConfig>(std::marker::PhantomData<T>);
|
||||
|
||||
impl<T: ChannelConfig> ConfigWrapper<T> {
|
||||
fn new(_: &Option<T>) -> Self {
|
||||
fn new(_: Option<&T>) -> Self {
|
||||
Self(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
@ -2604,79 +2586,79 @@ impl ChannelsConfig {
|
||||
pub fn channels_except_webhook(&self) -> Vec<(Box<dyn super::traits::ConfigHandle>, bool)> {
|
||||
vec![
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.telegram)),
|
||||
Box::new(ConfigWrapper::new(self.telegram.as_ref())),
|
||||
self.telegram.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.discord)),
|
||||
Box::new(ConfigWrapper::new(self.discord.as_ref())),
|
||||
self.discord.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.slack)),
|
||||
Box::new(ConfigWrapper::new(self.slack.as_ref())),
|
||||
self.slack.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.mattermost)),
|
||||
Box::new(ConfigWrapper::new(self.mattermost.as_ref())),
|
||||
self.mattermost.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.imessage)),
|
||||
Box::new(ConfigWrapper::new(self.imessage.as_ref())),
|
||||
self.imessage.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.matrix)),
|
||||
Box::new(ConfigWrapper::new(self.matrix.as_ref())),
|
||||
self.matrix.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.signal)),
|
||||
Box::new(ConfigWrapper::new(self.signal.as_ref())),
|
||||
self.signal.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.whatsapp)),
|
||||
Box::new(ConfigWrapper::new(self.whatsapp.as_ref())),
|
||||
self.whatsapp.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.linq)),
|
||||
Box::new(ConfigWrapper::new(self.linq.as_ref())),
|
||||
self.linq.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.wati)),
|
||||
Box::new(ConfigWrapper::new(self.wati.as_ref())),
|
||||
self.wati.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.nextcloud_talk)),
|
||||
Box::new(ConfigWrapper::new(self.nextcloud_talk.as_ref())),
|
||||
self.nextcloud_talk.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.email)),
|
||||
Box::new(ConfigWrapper::new(self.email.as_ref())),
|
||||
self.email.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.irc)),
|
||||
Box::new(ConfigWrapper::new(self.irc.as_ref())),
|
||||
self.irc.is_some()
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.lark)),
|
||||
Box::new(ConfigWrapper::new(self.lark.as_ref())),
|
||||
self.lark.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.feishu)),
|
||||
Box::new(ConfigWrapper::new(self.feishu.as_ref())),
|
||||
self.feishu.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.dingtalk)),
|
||||
Box::new(ConfigWrapper::new(self.dingtalk.as_ref())),
|
||||
self.dingtalk.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.qq)),
|
||||
Box::new(ConfigWrapper::new(self.qq.as_ref())),
|
||||
self.qq.is_some()
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.nostr)),
|
||||
Box::new(ConfigWrapper::new(self.nostr.as_ref())),
|
||||
self.nostr.is_some(),
|
||||
),
|
||||
(
|
||||
Box::new(ConfigWrapper::new(&self.clawdtalk)),
|
||||
Box::new(ConfigWrapper::new(self.clawdtalk.as_ref())),
|
||||
self.clawdtalk.is_some(),
|
||||
),
|
||||
]
|
||||
@ -2685,7 +2667,7 @@ impl ChannelsConfig {
|
||||
pub fn channels(&self) -> Vec<(Box<dyn super::traits::ConfigHandle>, bool)> {
|
||||
let mut ret = self.channels_except_webhook();
|
||||
ret.push((
|
||||
Box::new(ConfigWrapper::new(&self.webhook)),
|
||||
Box::new(ConfigWrapper::new(self.webhook.as_ref())),
|
||||
self.webhook.is_some(),
|
||||
));
|
||||
ret
|
||||
@ -4823,7 +4805,7 @@ async fn sync_directory(path: &Path) -> Result<()> {
|
||||
dir.sync_all()
|
||||
.await
|
||||
.with_context(|| format!("Failed to fsync directory metadata: {}", path.display()))?;
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
|
||||
@ -53,8 +53,7 @@ async fn handle_socket(socket: WebSocket, state: AppState) {
|
||||
while let Some(msg) = receiver.next().await {
|
||||
let msg = match msg {
|
||||
Ok(Message::Text(text)) => text,
|
||||
Ok(Message::Close(_)) => break,
|
||||
Err(_) => break,
|
||||
Ok(Message::Close(_)) | Err(_) => break,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
|
||||
@ -109,7 +109,7 @@ pub fn handle_command(cmd: crate::HardwareCommands, _config: &Config) -> Result<
|
||||
let _ = &cmd;
|
||||
println!("Hardware discovery requires the 'hardware' feature.");
|
||||
println!("Build with: cargo build --features hardware");
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
|
||||
@ -766,8 +766,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
match cli.command {
|
||||
Commands::Onboard { .. } => unreachable!(),
|
||||
Commands::Completions { .. } => unreachable!(),
|
||||
Commands::Onboard { .. } | Commands::Completions { .. } => unreachable!(),
|
||||
|
||||
Commands::Agent {
|
||||
message,
|
||||
|
||||
@ -328,7 +328,14 @@ fn date_prefix(filename: &str) -> Option<NaiveDate> {
|
||||
if filename.len() < 10 {
|
||||
return None;
|
||||
}
|
||||
NaiveDate::parse_from_str(&filename[..filename.floor_char_boundary(10)], "%Y-%m-%d").ok()
|
||||
let boundary = {
|
||||
let mut i = 10.min(filename.len());
|
||||
while i > 0 && !filename.is_char_boundary(i) {
|
||||
i -= 1;
|
||||
}
|
||||
i
|
||||
};
|
||||
NaiveDate::parse_from_str(&filename[..boundary], "%Y-%m-%d").ok()
|
||||
}
|
||||
|
||||
fn is_older_than(path: &Path, cutoff: SystemTime) -> bool {
|
||||
|
||||
@ -22,7 +22,6 @@ pub enum RuntimeTraceStorageMode {
|
||||
impl RuntimeTraceStorageMode {
|
||||
fn from_raw(raw: &str) -> Self {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
"none" => Self::None,
|
||||
"rolling" => Self::Rolling,
|
||||
"full" => Self::Full,
|
||||
_ => Self::None,
|
||||
|
||||
@ -2004,7 +2004,7 @@ fn resolve_interactive_onboarding_mode(
|
||||
" Existing config found at {}. Select setup mode",
|
||||
config_path.display()
|
||||
))
|
||||
.items(&options)
|
||||
.items(options)
|
||||
.default(1)
|
||||
.interact()?;
|
||||
|
||||
@ -5814,7 +5814,7 @@ mod tests {
|
||||
apply_provider_update(
|
||||
&mut config,
|
||||
"anthropic".to_string(),
|
||||
"".to_string(),
|
||||
String::new(),
|
||||
"claude-sonnet-4-5-20250929".to_string(),
|
||||
None,
|
||||
);
|
||||
|
||||
@ -229,6 +229,7 @@ pub async fn create_peripheral_tools(config: &PeripheralsConfig) -> Result<Vec<B
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "hardware"))]
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn create_peripheral_tools(_config: &PeripheralsConfig) -> Result<Vec<Box<dyn Tool>>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
@ -697,7 +697,6 @@ impl BedrockProvider {
|
||||
let after_semi = &rest[semi + 1..];
|
||||
if let Some(b64) = after_semi.strip_prefix("base64,") {
|
||||
let format = match mime {
|
||||
"image/jpeg" | "image/jpg" => "jpeg",
|
||||
"image/png" => "png",
|
||||
"image/gif" => "gif",
|
||||
"image/webp" => "webp",
|
||||
|
||||
@ -281,7 +281,6 @@ fn clamp_reasoning_effort(model: &str, effort: &str) -> String {
|
||||
return match effort {
|
||||
"low" | "medium" | "high" => effort.to_string(),
|
||||
"minimal" => "low".to_string(),
|
||||
"xhigh" => "high".to_string(),
|
||||
_ => "high".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -311,7 +311,7 @@ mod tests {
|
||||
assert!(patterns.iter().any(|p| p.contains("Stripe")));
|
||||
assert!(redacted.contains("[REDACTED"));
|
||||
}
|
||||
_ => panic!("Should detect Stripe key"),
|
||||
LeakResult::Clean => panic!("Should detect Stripe key"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,7 +324,7 @@ mod tests {
|
||||
LeakResult::Detected { patterns, .. } => {
|
||||
assert!(patterns.iter().any(|p| p.contains("AWS")));
|
||||
}
|
||||
_ => panic!("Should detect AWS key"),
|
||||
LeakResult::Clean => panic!("Should detect AWS key"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,7 +342,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq...
|
||||
assert!(patterns.iter().any(|p| p.contains("private key")));
|
||||
assert!(redacted.contains("[REDACTED_PRIVATE_KEY]"));
|
||||
}
|
||||
_ => panic!("Should detect private key"),
|
||||
LeakResult::Clean => panic!("Should detect private key"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,7 +356,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq...
|
||||
assert!(patterns.iter().any(|p| p.contains("JWT")));
|
||||
assert!(redacted.contains("[REDACTED_JWT]"));
|
||||
}
|
||||
_ => panic!("Should detect JWT"),
|
||||
LeakResult::Clean => panic!("Should detect JWT"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,7 +369,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq...
|
||||
LeakResult::Detected { patterns, .. } => {
|
||||
assert!(patterns.iter().any(|p| p.contains("PostgreSQL")));
|
||||
}
|
||||
_ => panic!("Should detect database URL"),
|
||||
LeakResult::Clean => panic!("Should detect database URL"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -200,12 +200,12 @@ fn audit_manifest_file(root: &Path, path: &Path, report: &mut SkillAuditReport)
|
||||
.push(format!("{rel}: tools[{idx}] is missing a command field."));
|
||||
}
|
||||
|
||||
if kind.eq_ignore_ascii_case("script") || kind.eq_ignore_ascii_case("shell") {
|
||||
if command.is_some_and(|value| value.trim().is_empty()) {
|
||||
report
|
||||
.findings
|
||||
.push(format!("{rel}: tools[{idx}] has an empty {kind} command."));
|
||||
}
|
||||
if (kind.eq_ignore_ascii_case("script") || kind.eq_ignore_ascii_case("shell"))
|
||||
&& command.is_some_and(|value| value.trim().is_empty())
|
||||
{
|
||||
report
|
||||
.findings
|
||||
.push(format!("{rel}: tools[{idx}] has an empty {kind} command."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +238,7 @@ fn open_skills_enabled_from_sources(
|
||||
env_override: Option<&str>,
|
||||
) -> bool {
|
||||
if let Some(raw) = env_override {
|
||||
if let Some(enabled) = parse_open_skills_enabled(&raw) {
|
||||
if let Some(enabled) = parse_open_skills_enabled(raw) {
|
||||
return enabled;
|
||||
}
|
||||
if !raw.trim().is_empty() {
|
||||
|
||||
@ -173,7 +173,11 @@ impl ScreenshotTool {
|
||||
let size = bytes.len();
|
||||
let mut encoded = base64::engine::general_purpose::STANDARD.encode(&bytes);
|
||||
let truncated = if encoded.len() > MAX_BASE64_BYTES {
|
||||
encoded.truncate(encoded.floor_char_boundary(MAX_BASE64_BYTES));
|
||||
let mut boundary = MAX_BASE64_BYTES.min(encoded.len());
|
||||
while boundary > 0 && !encoded.is_char_boundary(boundary) {
|
||||
boundary -= 1;
|
||||
}
|
||||
encoded.truncate(boundary);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
||||
@ -164,11 +164,19 @@ impl Tool for ShellTool {
|
||||
|
||||
// Truncate output to prevent OOM
|
||||
if stdout.len() > MAX_OUTPUT_BYTES {
|
||||
stdout.truncate(stdout.floor_char_boundary(MAX_OUTPUT_BYTES));
|
||||
let mut b = MAX_OUTPUT_BYTES.min(stdout.len());
|
||||
while b > 0 && !stdout.is_char_boundary(b) {
|
||||
b -= 1;
|
||||
}
|
||||
stdout.truncate(b);
|
||||
stdout.push_str("\n... [output truncated at 1MB]");
|
||||
}
|
||||
if stderr.len() > MAX_OUTPUT_BYTES {
|
||||
stderr.truncate(stderr.floor_char_boundary(MAX_OUTPUT_BYTES));
|
||||
let mut b = MAX_OUTPUT_BYTES.min(stderr.len());
|
||||
while b > 0 && !stderr.is_char_boundary(b) {
|
||||
b -= 1;
|
||||
}
|
||||
stderr.truncate(b);
|
||||
stderr.push_str("\n... [stderr truncated at 1MB]");
|
||||
}
|
||||
|
||||
|
||||
@ -119,10 +119,12 @@ fn memory_config_default_vector_keyword_weights_sum_to_one() {
|
||||
|
||||
#[test]
|
||||
fn config_toml_roundtrip_preserves_provider() {
|
||||
let mut config = Config::default();
|
||||
config.default_provider = Some("deepseek".into());
|
||||
config.default_model = Some("deepseek-chat".into());
|
||||
config.default_temperature = 0.5;
|
||||
let config = Config {
|
||||
default_provider: Some("deepseek".into()),
|
||||
default_model: Some("deepseek-chat".into()),
|
||||
default_temperature: 0.5,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let toml_str = toml::to_string(&config).expect("config should serialize to TOML");
|
||||
let parsed: Config = toml::from_str(&toml_str).expect("TOML should deserialize back");
|
||||
@ -173,9 +175,11 @@ fn config_file_write_read_roundtrip() {
|
||||
let tmp = tempfile::TempDir::new().expect("tempdir creation should succeed");
|
||||
let config_path = tmp.path().join("config.toml");
|
||||
|
||||
let mut config = Config::default();
|
||||
config.default_provider = Some("mistral".into());
|
||||
config.default_model = Some("mistral-large".into());
|
||||
let mut config = Config {
|
||||
default_provider: Some("mistral".into()),
|
||||
default_model: Some("mistral-large".into()),
|
||||
..Default::default()
|
||||
};
|
||||
config.agent.max_tool_iterations = 15;
|
||||
|
||||
let toml_str = toml::to_string(&config).expect("config should serialize");
|
||||
|
||||
@ -99,11 +99,13 @@ fn gateway_config_idempotency_defaults() {
|
||||
|
||||
#[test]
|
||||
fn gateway_config_toml_roundtrip() {
|
||||
let mut gw = GatewayConfig::default();
|
||||
gw.port = 8080;
|
||||
gw.host = "0.0.0.0".into();
|
||||
gw.require_pairing = false;
|
||||
gw.pair_rate_limit_per_minute = 5;
|
||||
let gw = GatewayConfig {
|
||||
port: 8080,
|
||||
host: "0.0.0.0".into(),
|
||||
require_pairing: false,
|
||||
pair_rate_limit_per_minute: 5,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let toml_str = toml::to_string(&gw).expect("gateway config should serialize");
|
||||
let parsed: GatewayConfig = toml::from_str(&toml_str).expect("should deserialize back");
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
//! 1. Primary provider (OpenAI Codex) fails
|
||||
//! 2. Fallback to Gemini is triggered
|
||||
//! 3. Gemini OAuth tokens are expired (we manually expire them)
|
||||
//!
|
||||
//! Then:
|
||||
//! - Gemini provider's warmup() automatically refreshes the tokens
|
||||
//! - The fallback request succeeds
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
//! Run manually: `cargo test provider_vision -- --ignored --nocapture`
|
||||
|
||||
use anyhow::Result;
|
||||
use zeroclaw::providers::{ChatMessage, ChatRequest, Provider, ProviderRuntimeOptions};
|
||||
use zeroclaw::providers::{ChatMessage, ChatRequest, ProviderRuntimeOptions};
|
||||
|
||||
/// Tests that provider supports vision input.
|
||||
///
|
||||
|
||||
@ -292,7 +292,7 @@ fn provider_construction_with_different_auth_styles() {
|
||||
|
||||
#[test]
|
||||
fn chat_messages_maintain_role_sequence() {
|
||||
let history = vec![
|
||||
let history = [
|
||||
ChatMessage::system("You are helpful"),
|
||||
ChatMessage::user("What is Rust?"),
|
||||
ChatMessage::assistant("Rust is a systems programming language"),
|
||||
@ -309,7 +309,7 @@ fn chat_messages_maintain_role_sequence() {
|
||||
|
||||
#[test]
|
||||
fn chat_messages_with_tool_calls_maintain_sequence() {
|
||||
let history = vec![
|
||||
let history = [
|
||||
ChatMessage::system("You are helpful"),
|
||||
ChatMessage::user("Search for Rust"),
|
||||
ChatMessage::assistant("I'll search for that"),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user