fix: harvest safe bug fixes from PR #2880
Harvests 7 safe fixes from PR #2880 by @HUQIANTAO: tool-name hex-digit guard, token-usage u32 clamp, read-file line usize::try_from, grep context-lines cap, UTF-8 PDF trim, run_skill dedup, and Volcengine/SiliconflowCn reasoning_content support. Excludes the DeepSeek stream-stop change and the unwired prompt_persist module (deferred for separate review). Co-Authored-By: HUQIANTAO <58421104+HUQIANTAO@users.noreply.github.com>
This commit is contained in:
@@ -61,7 +61,10 @@ pub(super) fn from_api_tool_name(name: &str) -> String {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Ok(code) = u32::from_str_radix(&hex, 16)
|
||||
// Only decode if we got exactly 6 hex digits (matching encoder output).
|
||||
// Fewer digits means a truncated/malformed sequence — pass through as-is.
|
||||
if hex.len() == 6
|
||||
&& let Ok(code) = u32::from_str_radix(&hex, 16)
|
||||
&& let Some(decoded) = std::char::from_u32(code)
|
||||
{
|
||||
if let Some('-') = iter.peek().copied() {
|
||||
@@ -1395,8 +1398,8 @@ pub(super) fn parse_usage(usage: Option<&Value>) -> Usage {
|
||||
});
|
||||
|
||||
Usage {
|
||||
input_tokens: input_tokens as u32,
|
||||
output_tokens: output_tokens as u32,
|
||||
input_tokens: input_tokens.min(u64::from(u32::MAX)) as u32,
|
||||
output_tokens: output_tokens.min(u64::from(u32::MAX)) as u32,
|
||||
prompt_cache_hit_tokens,
|
||||
prompt_cache_miss_tokens,
|
||||
reasoning_tokens,
|
||||
|
||||
@@ -1990,6 +1990,8 @@ fn provider_accepts_reasoning_content(provider: ApiProvider) -> bool {
|
||||
| ApiProvider::Novita
|
||||
| ApiProvider::Fireworks
|
||||
| ApiProvider::Siliconflow
|
||||
| ApiProvider::SiliconflowCn
|
||||
| ApiProvider::Volcengine
|
||||
| ApiProvider::Arcee
|
||||
| ApiProvider::Sglang
|
||||
)
|
||||
|
||||
@@ -682,8 +682,8 @@ pub fn execute(cmd: &str, app: &mut App) -> CommandResult {
|
||||
_ => {
|
||||
// Third source: skills (lowest precedence after native and user-config).
|
||||
// Try to run a skill whose name matches the command.
|
||||
if skills::run_skill_by_name(app, command, arg).is_some() {
|
||||
return skills::run_skill_by_name(app, command, arg).unwrap();
|
||||
if let Some(result) = skills::run_skill_by_name(app, command, arg) {
|
||||
return result;
|
||||
}
|
||||
let suggestions = suggest_command_names(command, 3);
|
||||
if suggestions.is_empty() {
|
||||
|
||||
@@ -114,7 +114,11 @@ impl ToolSpec for ReadFileTool {
|
||||
"start_line must be 1-based and greater than 0".to_string(),
|
||||
));
|
||||
}
|
||||
Some(v) => v as usize,
|
||||
Some(v) => usize::try_from(v).map_err(|_| {
|
||||
ToolError::invalid_input(
|
||||
"start_line exceeds platform addressable range".to_string(),
|
||||
)
|
||||
})?,
|
||||
None => 1,
|
||||
};
|
||||
|
||||
@@ -124,7 +128,14 @@ impl ToolSpec for ReadFileTool {
|
||||
"max_lines must be greater than 0".to_string(),
|
||||
));
|
||||
}
|
||||
Some(v) => std::cmp::min(v as usize, HARD_MAX_READ_LINES),
|
||||
Some(v) => {
|
||||
let converted = usize::try_from(v).map_err(|_| {
|
||||
ToolError::invalid_input(
|
||||
"max_lines exceeds platform addressable range".to_string(),
|
||||
)
|
||||
})?;
|
||||
std::cmp::min(converted, HARD_MAX_READ_LINES)
|
||||
}
|
||||
None => DEFAULT_READ_LINES,
|
||||
};
|
||||
|
||||
@@ -292,7 +303,9 @@ fn clean_pdf_text(raw: &str) -> String {
|
||||
if any_content {
|
||||
let start = out.find(|c: char| c != '\n').unwrap_or(0);
|
||||
// Walk back from end to find the last non-newline character.
|
||||
let end = out.rfind(|c: char| c != '\n').map_or(out.len(), |i| i + 1);
|
||||
let end = out.rfind(|c: char| c != '\n').map_or(out.len(), |i| {
|
||||
i + out[i..].chars().next().map_or(1, |c| c.len_utf8())
|
||||
});
|
||||
out[start..end].to_string()
|
||||
} else {
|
||||
String::new()
|
||||
|
||||
@@ -100,8 +100,9 @@ impl ToolSpec for GrepFilesTool {
|
||||
async fn execute(&self, input: Value, context: &ToolContext) -> Result<ToolResult, ToolError> {
|
||||
let pattern_str = required_str(&input, "pattern")?;
|
||||
let path_str = optional_str(&input, "path").unwrap_or(".");
|
||||
let context_lines =
|
||||
usize::try_from(optional_u64(&input, "context_lines", 2)).unwrap_or(usize::MAX);
|
||||
let context_lines = usize::try_from(optional_u64(&input, "context_lines", 2))
|
||||
.unwrap_or(usize::MAX)
|
||||
.min(1000);
|
||||
let case_insensitive = optional_bool(&input, "case_insensitive", false);
|
||||
let max_results = usize::try_from(optional_u64(&input, "max_results", MAX_RESULTS as u64))
|
||||
.unwrap_or(MAX_RESULTS);
|
||||
|
||||
Reference in New Issue
Block a user