diff --git a/Cargo.toml b/Cargo.toml index 6c312ee8c8ba..d88b69afed98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ description = "An AI agent" [workspace.lints.clippy] uninlined_format_args = "allow" +string_slice = "warn" [workspace.dependencies] rmcp = { version = "0.8.3", features = ["schemars", "auth"] } diff --git a/crates/goose-cli/src/session/completion.rs b/crates/goose-cli/src/session/completion.rs index 67021047458c..6c32529a9aba 100644 --- a/crates/goose-cli/src/session/completion.rs +++ b/crates/goose-cli/src/session/completion.rs @@ -26,7 +26,7 @@ impl GooseCompleter { /// Complete prompt names for the /prompt command fn complete_prompt_names(&self, line: &str) -> Result<(usize, Vec)> { // Get the prefix of the prompt name being typed - let prefix = if line.len() > 8 { &line[8..] } else { "" }; + let prefix = line.get(8..).unwrap_or(""); // Get available prompts from cache let cache = self.completion_cache.read().unwrap(); @@ -156,7 +156,7 @@ impl GooseCompleter { /// Complete argument keys for a specific prompt fn complete_argument_keys(&self, line: &str) -> Result<(usize, Vec)> { - let parts: Vec<&str> = line[8..].split_whitespace().collect(); + let parts: Vec<&str> = line.get(8..).unwrap_or("").split_whitespace().collect(); // We need at least the prompt name if parts.is_empty() { diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index 081be75f9862..a1d44b877fda 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -169,15 +169,17 @@ fn handle_slash_command(input: &str) -> Option { } } s if s.starts_with(CMD_EXTENSION) => Some(InputResult::AddExtension( - s[CMD_EXTENSION.len()..].to_string(), + s.get(CMD_EXTENSION.len()..).unwrap_or("").to_string(), )), - s if s.starts_with(CMD_BUILTIN) => { - Some(InputResult::AddBuiltin(s[CMD_BUILTIN.len()..].to_string())) - } - s if s.starts_with(CMD_MODE) => { - Some(InputResult::GooseMode(s[CMD_MODE.len()..].to_string())) + s if s.starts_with(CMD_BUILTIN) => Some(InputResult::AddBuiltin( + s.get(CMD_BUILTIN.len()..).unwrap_or("").to_string(), + )), + s if s.starts_with(CMD_MODE) => Some(InputResult::GooseMode( + s.get(CMD_MODE.len()..).unwrap_or("").to_string(), + )), + s if s.starts_with(CMD_PLAN) => { + parse_plan_command(s.get(CMD_PLAN.len()..).unwrap_or("").trim().to_string()) } - s if s.starts_with(CMD_PLAN) => parse_plan_command(s[CMD_PLAN.len()..].trim().to_string()), s if s == CMD_ENDPLAN => Some(InputResult::EndPlan), s if s == CMD_CLEAR => Some(InputResult::Clear), s if s.starts_with(CMD_RECIPE) => parse_recipe_command(s), @@ -199,7 +201,7 @@ fn parse_recipe_command(s: &str) -> Option { } // Extract the filepath from the command - let filepath = s[CMD_RECIPE.len()..].trim(); + let filepath = s.get(CMD_RECIPE.len()..).unwrap_or("").trim(); if filepath.is_empty() { return Some(InputResult::Recipe(None)); diff --git a/crates/goose-mcp/src/developer/analyze/formatter.rs b/crates/goose-mcp/src/developer/analyze/formatter.rs index c05011647820..49bb76cc4509 100644 --- a/crates/goose-mcp/src/developer/analyze/formatter.rs +++ b/crates/goose-mcp/src/developer/analyze/formatter.rs @@ -1,11 +1,11 @@ -use rmcp::model::{Content, Role}; -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; - use crate::developer::analyze::types::{ AnalysisMode, AnalysisResult, CallChain, EntryType, FocusedAnalysisData, }; use crate::developer::lang; +use goose::utils::safe_truncate; +use rmcp::model::{Content, Role}; +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; pub struct Formatter; @@ -164,13 +164,7 @@ impl Formatter { if imports.len() > 1 { format!("{}({})", group, imports.len()) } else { - // For single imports, show more detail - let imp = &imports[0]; - if imp.len() > 40 { - format!("{}...", &imp[..37]) - } else { - imp.clone() - } + safe_truncate(&imports[0], 40) } }) .collect(); @@ -727,7 +721,7 @@ impl Formatter { if let Some(header_line) = output .lines() .rev() - .find(|l| l.starts_with("##") && line.contains(&l[3..])) + .find(|l| l.starts_with("##") && l.get(3..).is_some_and(|s| line.contains(s))) { if !filtered.contains(header_line) { filtered.push_str(header_line); diff --git a/crates/goose-mcp/src/developer/analyze/languages/go.rs b/crates/goose-mcp/src/developer/analyze/languages/go.rs index 71c7116e7aee..9c1ec28376e0 100644 --- a/crates/goose-mcp/src/developer/analyze/languages/go.rs +++ b/crates/goose-mcp/src/developer/analyze/languages/go.rs @@ -87,7 +87,7 @@ pub fn find_method_for_receiver( for i in 0..parent.child_count() { if let Some(child) = parent.child(i) { if child.kind() == "field_identifier" { - return Some(source[child.byte_range()].to_string()); + return source.get(child.byte_range()).map(|s| s.to_string()); } } } diff --git a/crates/goose-mcp/src/developer/analyze/languages/ruby.rs b/crates/goose-mcp/src/developer/analyze/languages/ruby.rs index 6214e6313596..df9f7496fa7c 100644 --- a/crates/goose-mcp/src/developer/analyze/languages/ruby.rs +++ b/crates/goose-mcp/src/developer/analyze/languages/ruby.rs @@ -140,7 +140,7 @@ fn find_method_in_body_with_depth( for j in 0..child.child_count() { if let Some(name_node) = child.child(j) { if name_node.kind() == "identifier" { - return Some(source[name_node.byte_range()].to_string()); + return source.get(name_node.byte_range()).map(|s| s.to_string()); } } } diff --git a/crates/goose-mcp/src/developer/analyze/languages/rust.rs b/crates/goose-mcp/src/developer/analyze/languages/rust.rs index bc1a980db1cf..bf9a6f983e47 100644 --- a/crates/goose-mcp/src/developer/analyze/languages/rust.rs +++ b/crates/goose-mcp/src/developer/analyze/languages/rust.rs @@ -83,7 +83,9 @@ pub fn extract_function_name_for_kind( for i in 0..node.child_count() { if let Some(child) = node.child(i) { if child.kind() == "type_identifier" { - return Some(format!("impl {}", &source[child.byte_range()])); + return source + .get(child.byte_range()) + .map(|s| format!("impl {}", s)); } } } @@ -109,7 +111,7 @@ pub fn find_method_for_receiver( for i in 0..parent.child_count() { if let Some(child) = parent.child(i) { if child.kind() == "identifier" { - return Some(source[child.byte_range()].to_string()); + return source.get(child.byte_range()).map(|s| s.to_string()); } } } @@ -133,7 +135,7 @@ pub fn find_receiver_type(node: &tree_sitter::Node, source: &str) -> Option Option { - Self::find_child_by_kind(node, kinds).map(|child| source[child.byte_range()].to_string()) + Self::find_child_by_kind(node, kinds) + .and_then(|child| source.get(child.byte_range()).map(|s| s.to_string())) } pub fn extract_with_depth( @@ -216,8 +217,13 @@ impl ElementExtractor { for match_ in matches.by_ref() { for capture in match_.captures { let node = capture.node; - let text = &source[node.byte_range()]; - let line = source[..node.start_byte()].lines().count() + 1; + let Some(text) = source.get(node.byte_range()) else { + continue; + }; + let line = source + .get(..node.start_byte()) + .map(|s| s.lines().count() + 1) + .unwrap_or(1); match query.capture_names()[capture.index as usize] { "func" | "const" => { @@ -284,18 +290,25 @@ impl ElementExtractor { for match_ in matches.by_ref() { for capture in match_.captures { let node = capture.node; - let text = &source[node.byte_range()]; + let Some(text) = source.get(node.byte_range()) else { + continue; + }; let start_pos = node.start_position(); - let line_start = source[..node.start_byte()] - .rfind('\n') + let line_start = source + .get(..node.start_byte()) + .and_then(|s| s.rfind('\n')) .map(|i| i + 1) .unwrap_or(0); - let line_end = source[node.end_byte()..] - .find('\n') + let line_end = source + .get(node.end_byte()..) + .and_then(|s| s.find('\n')) .map(|i| node.end_byte() + i) .unwrap_or(source.len()); - let context = source[line_start..line_end].trim().to_string(); + let context = source + .get(line_start..line_end) + .map(|s| s.trim().to_string()) + .unwrap_or_default(); let caller_name = Self::find_containing_function(&node, source, language); @@ -356,18 +369,25 @@ impl ElementExtractor { for match_ in matches.by_ref() { for capture in match_.captures { let node = capture.node; - let text = &source[node.byte_range()]; + let Some(text) = source.get(node.byte_range()) else { + continue; + }; let start_pos = node.start_position(); - let line_start = source[..node.start_byte()] - .rfind('\n') + let line_start = source + .get(..node.start_byte()) + .and_then(|s| s.rfind('\n')) .map(|i| i + 1) .unwrap_or(0); - let line_end = source[node.end_byte()..] - .find('\n') + let line_end = source + .get(node.end_byte()..) + .and_then(|s| s.find('\n')) .map(|i| node.end_byte() + i) .unwrap_or(source.len()); - let context = source[line_start..line_end].trim().to_string(); + let context = source + .get(line_start..line_end) + .map(|s| s.trim().to_string()) + .unwrap_or_default(); let capture_name = query.capture_names()[capture.index as usize]; diff --git a/crates/goose-mcp/src/developer/editor_models/morphllm_editor.rs b/crates/goose-mcp/src/developer/editor_models/morphllm_editor.rs index 57a33859636b..44170a48cf81 100644 --- a/crates/goose-mcp/src/developer/editor_models/morphllm_editor.rs +++ b/crates/goose-mcp/src/developer/editor_models/morphllm_editor.rs @@ -28,8 +28,9 @@ impl MorphLLMEditor { if let (Some(start_pos), Some(end_pos)) = (text.find(&start_tag), text.find(&end_tag)) { if start_pos < end_pos { let content_start = start_pos + start_tag.len(); - let content = &text[content_start..end_pos]; - return Some(content.trim().to_string()); + if let Some(content) = text.get(content_start..end_pos) { + return Some(content.trim().to_string()); + } } } None diff --git a/crates/goose-mcp/src/developer/rmcp_developer.rs b/crates/goose-mcp/src/developer/rmcp_developer.rs index bbd91d96ae25..c2257c2876e0 100644 --- a/crates/goose-mcp/src/developer/rmcp_developer.rs +++ b/crates/goose-mcp/src/developer/rmcp_developer.rs @@ -1335,20 +1335,22 @@ impl DeveloperServer { // Find the last space before AM/PM and replace it with U+202F let space_pos = filename.rfind(meridian) - .map(|pos| filename[..pos].trim_end().len()) + .and_then(|pos| filename.get(..pos).map(|s| s.trim_end().len())) .unwrap_or(0); if space_pos > 0 { let parent = path.parent().unwrap_or(Path::new("")); - let new_filename = format!( - "{}{}{}", - &filename[..space_pos], - '\u{202F}', - &filename[space_pos+1..] - ); - let new_path = parent.join(new_filename); - - return new_path; + if let (Some(before), Some(after)) = (filename.get(..space_pos), filename.get(space_pos+1..)) { + let new_filename = format!( + "{}{}{}", + before, + '\u{202F}', + after + ); + let new_path = parent.join(new_filename); + + return new_path; + } } } } @@ -3208,7 +3210,10 @@ mod tests { ) { let start_idx = start + start_tag.len(); if start_idx < end { - let path = assistant_content.text[start_idx..end].trim(); + let Some(path) = assistant_content.text.get(start_idx..end).map(|s| s.trim()) + else { + panic!("Failed to extract path from assistant content"); + }; println!("Extracted path: {}", path); let file_contents = diff --git a/crates/goose-test/src/mcp/stdio/playback.rs b/crates/goose-test/src/mcp/stdio/playback.rs index 82f3897ecf1e..49c3e4567e20 100644 --- a/crates/goose-test/src/mcp/stdio/playback.rs +++ b/crates/goose-test/src/mcp/stdio/playback.rs @@ -20,7 +20,7 @@ struct LogEntry { fn parse_log_line(line: &str) -> Option { line.find(": ").and_then(|pos| { let (prefix, content) = line.split_at(pos); - let content = &content[2..]; // Skip ": " + let content = content.get(2..)?; // Skip ": " let stream_type = match prefix { "STDIN" => StreamType::Stdin, diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 9e80a08f8160..61dfa73acd1e 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -1365,17 +1365,9 @@ impl Agent { .unwrap_or(&content) .trim() .to_string(); - tracing::debug!( - "Cleaned content for parsing: {}", - &clean_content[..std::cmp::min(200, clean_content.len())] - ); - // try to parse json response from the LLM - tracing::debug!("Attempting to parse recipe content as JSON"); let (instructions, activities) = if let Ok(json_content) = serde_json::from_str::(&clean_content) { - tracing::debug!("Successfully parsed JSON content"); - let instructions = json_content .get("instructions") .ok_or_else(|| anyhow!("Missing 'instructions' in json response"))? diff --git a/crates/goose/src/agents/subagent_execution_tool/tasks.rs b/crates/goose/src/agents/subagent_execution_tool/tasks.rs index e978030231f0..f8a46ae7ace8 100644 --- a/crates/goose/src/agents/subagent_execution_tool/tasks.rs +++ b/crates/goose/src/agents/subagent_execution_tool/tasks.rs @@ -249,7 +249,7 @@ fn extract_json_from_line(line: &str) -> Option { return None; } - let potential_json = &line[start..=end]; + let potential_json = line.get(start..=end)?; if serde_json::from_str::(potential_json).is_ok() { Some(potential_json.to_string()) } else { diff --git a/crates/goose/src/providers/claude_code.rs b/crates/goose/src/providers/claude_code.rs index e64a212bd2d9..a05e64c08d73 100644 --- a/crates/goose/src/providers/claude_code.rs +++ b/crates/goose/src/providers/claude_code.rs @@ -9,7 +9,7 @@ use tokio::process::Command; use super::base::{ConfigKey, Provider, ProviderMetadata, ProviderUsage, Usage}; use super::errors::ProviderError; -use super::utils::RequestLog; +use super::utils::{filter_extensions_from_system_prompt, RequestLog}; use crate::config::{Config, GooseMode}; use crate::conversation::message::{Message, MessageContent}; use crate::model::ModelConfig; @@ -103,28 +103,6 @@ impl ClaudeCodeProvider { None } - /// Filter out the Extensions section from the system prompt - fn filter_extensions_from_system_prompt(&self, system: &str) -> String { - // Find the Extensions section and remove it - if let Some(extensions_start) = system.find("# Extensions") { - // Look for the next major section that starts with # - let after_extensions = &system[extensions_start..]; - if let Some(next_section_pos) = after_extensions[1..].find("\n# ") { - // Found next section, keep everything before Extensions and after the next section - let before_extensions = &system[..extensions_start]; - let next_section_start = extensions_start + next_section_pos + 1; - let after_next_section = &system[next_section_start..]; - format!("{}{}", before_extensions.trim_end(), after_next_section) - } else { - // No next section found, just remove everything from Extensions onward - system[..extensions_start].trim_end().to_string() - } - } else { - // No Extensions section found, return original - system.to_string() - } - } - /// Convert goose messages to the format expected by claude CLI fn messages_to_claude_format(&self, _system: &str, messages: &[Message]) -> Result { let mut claude_messages = Vec::new(); @@ -303,8 +281,7 @@ impl ClaudeCodeProvider { ProviderError::RequestFailed(format!("Failed to format messages: {}", e)) })?; - // Create a filtered system prompt without Extensions section - let filtered_system = self.filter_extensions_from_system_prompt(system); + let filtered_system = filter_extensions_from_system_prompt(system); if std::env::var("GOOSE_CLAUDE_CODE_DEBUG").is_ok() { println!("=== CLAUDE CODE PROVIDER DEBUG ==="); diff --git a/crates/goose/src/providers/cursor_agent.rs b/crates/goose/src/providers/cursor_agent.rs index 225d09e0265d..66469c83c2ae 100644 --- a/crates/goose/src/providers/cursor_agent.rs +++ b/crates/goose/src/providers/cursor_agent.rs @@ -9,7 +9,7 @@ use tokio::process::Command; use super::base::{ConfigKey, Provider, ProviderMetadata, ProviderUsage, Usage}; use super::errors::ProviderError; -use super::utils::RequestLog; +use super::utils::{filter_extensions_from_system_prompt, RequestLog}; use crate::conversation::message::{Message, MessageContent}; use crate::model::ModelConfig; use rmcp::model::Tool; @@ -112,34 +112,11 @@ impl CursorAgentProvider { None } - /// Filter out the Extensions section from the system prompt - fn filter_extensions_from_system_prompt(&self, system: &str) -> String { - // Find the Extensions section and remove it - if let Some(extensions_start) = system.find("# Extensions") { - // Look for the next major section that starts with # - let after_extensions = &system[extensions_start..]; - if let Some(next_section_pos) = after_extensions[1..].find("\n# ") { - // Found next section, keep everything before Extensions and after the next section - let before_extensions = &system[..extensions_start]; - let next_section_start = extensions_start + next_section_pos + 1; - let after_next_section = &system[next_section_start..]; - format!("{}{}", before_extensions.trim_end(), after_next_section) - } else { - // No next section found, just remove everything from Extensions onward - system[..extensions_start].trim_end().to_string() - } - } else { - // No Extensions section found, return original - system.to_string() - } - } - /// Convert goose messages to a simple prompt format for cursor-agent CLI fn messages_to_cursor_agent_format(&self, system: &str, messages: &[Message]) -> String { let mut full_prompt = String::new(); - // Add system prompt - let filtered_system = self.filter_extensions_from_system_prompt(system); + let filtered_system = filter_extensions_from_system_prompt(system); full_prompt.push_str(&filtered_system); full_prompt.push_str("\n\n"); @@ -267,7 +244,7 @@ impl CursorAgentProvider { println!("Original system prompt length: {} chars", system.len()); println!( "Filtered system prompt length: {} chars", - self.filter_extensions_from_system_prompt(system).len() + filter_extensions_from_system_prompt(system).len() ); println!("Full prompt: {}", prompt); println!("Model: {}", self.model.model_name); diff --git a/crates/goose/src/providers/formats/snowflake.rs b/crates/goose/src/providers/formats/snowflake.rs index 0f355612fe2b..2f8d492ad3ec 100644 --- a/crates/goose/src/providers/formats/snowflake.rs +++ b/crates/goose/src/providers/formats/snowflake.rs @@ -133,7 +133,9 @@ pub fn parse_streaming_response(sse_data: &str) -> Result { continue; } - let json_str = &line[6..]; // Remove "data: " prefix + let Some(json_str) = line.get(6..) else { + continue; + }; // Remove "data: " prefix if json_str.trim().is_empty() || json_str.trim() == "[DONE]" { continue; } diff --git a/crates/goose/src/providers/gemini_cli.rs b/crates/goose/src/providers/gemini_cli.rs index e13610e7dd09..af0016cac5ad 100644 --- a/crates/goose/src/providers/gemini_cli.rs +++ b/crates/goose/src/providers/gemini_cli.rs @@ -8,7 +8,7 @@ use tokio::process::Command; use super::base::{Provider, ProviderMetadata, ProviderUsage, Usage}; use super::errors::ProviderError; -use super::utils::RequestLog; +use super::utils::{filter_extensions_from_system_prompt, RequestLog}; use crate::conversation::message::{Message, MessageContent}; use crate::model::ModelConfig; @@ -103,28 +103,6 @@ impl GeminiCliProvider { None } - /// Filter out the Extensions section from the system prompt - fn filter_extensions_from_system_prompt(&self, system: &str) -> String { - // Find the Extensions section and remove it - if let Some(extensions_start) = system.find("# Extensions") { - // Look for the next major section that starts with # - let after_extensions = &system[extensions_start..]; - if let Some(next_section_pos) = after_extensions[1..].find("\n# ") { - // Found next section, keep everything before Extensions and after the next section - let before_extensions = &system[..extensions_start]; - let next_section_start = extensions_start + next_section_pos + 1; - let after_next_section = &system[next_section_start..]; - format!("{}{}", before_extensions.trim_end(), after_next_section) - } else { - // No next section found, just remove everything from Extensions onward - system[..extensions_start].trim_end().to_string() - } - } else { - // No Extensions section found, return original - system.to_string() - } - } - /// Execute gemini CLI command with simple text prompt async fn execute_command( &self, @@ -135,8 +113,7 @@ impl GeminiCliProvider { // Create a simple prompt combining system + conversation let mut full_prompt = String::new(); - // Add system prompt - let filtered_system = self.filter_extensions_from_system_prompt(system); + let filtered_system = filter_extensions_from_system_prompt(system); full_prompt.push_str(&filtered_system); full_prompt.push_str("\n\n"); diff --git a/crates/goose/src/providers/githubcopilot.rs b/crates/goose/src/providers/githubcopilot.rs index 1b503aa50a38..a4478ac1d56d 100644 --- a/crates/goose/src/providers/githubcopilot.rs +++ b/crates/goose/src/providers/githubcopilot.rs @@ -169,7 +169,9 @@ impl GithubCopilotProvider { if !tline.starts_with("data: ") { continue; } - let payload = &tline[6..]; + let Some(payload) = tline.get(6..) else { + continue; + }; if payload == "[DONE]" { break; } diff --git a/crates/goose/src/providers/sagemaker_tgi.rs b/crates/goose/src/providers/sagemaker_tgi.rs index a5210b5b91fa..74dbff3fa683 100644 --- a/crates/goose/src/providers/sagemaker_tgi.rs +++ b/crates/goose/src/providers/sagemaker_tgi.rs @@ -246,7 +246,7 @@ impl SageMakerTgiProvider { // Remove any remaining HTML-like tags using a simple pattern // This is a basic implementation - for production use, consider using a proper HTML parser while let Some(start) = result.find('<') { - if let Some(end) = result[start..].find('>') { + if let Some(end) = result.get(start..).and_then(|s| s.find('>')) { result.replace_range(start..start + end + 1, ""); } else { break; diff --git a/crates/goose/src/providers/utils.rs b/crates/goose/src/providers/utils.rs index def0c4e544d7..5da1946a623d 100644 --- a/crates/goose/src/providers/utils.rs +++ b/crates/goose/src/providers/utils.rs @@ -47,6 +47,31 @@ pub fn convert_image(image: &ImageContent, image_format: &ImageFormat) -> Value } } +pub fn filter_extensions_from_system_prompt(system: &str) -> String { + let Some(extensions_start) = system.find("# Extensions") else { + return system.to_string(); + }; + + let Some(after_extensions) = system.get(extensions_start + 1..) else { + return system.to_string(); + }; + + if let Some(next_section_pos) = after_extensions.find("\n# ") { + let Some(before) = system.get(..extensions_start) else { + return system.to_string(); + }; + let Some(after) = system.get(extensions_start + next_section_pos + 1..) else { + return system.to_string(); + }; + format!("{}{}", before.trim_end(), after) + } else { + system + .get(..extensions_start) + .map(|s| s.trim_end().to_string()) + .unwrap_or_else(|| system.to_string()) + } +} + fn check_context_length_exceeded(text: &str) -> bool { let check_phrases = [ "too long", diff --git a/crates/goose/src/providers/utils_universal_openai_stream.rs b/crates/goose/src/providers/utils_universal_openai_stream.rs index 1025e0bf7cd3..38f1aab35e25 100644 --- a/crates/goose/src/providers/utils_universal_openai_stream.rs +++ b/crates/goose/src/providers/utils_universal_openai_stream.rs @@ -275,7 +275,9 @@ data: [DONE] if !line.starts_with("data: ") { continue; } - let payload = &line[6..]; + let Some(payload) = line.get(6..) else { + continue; + }; if payload == "[DONE]" { break; } @@ -325,7 +327,9 @@ data: [DONE] if !line.starts_with("data: ") { continue; } - let payload = &line[6..]; + let Some(payload) = line.get(6..) else { + continue; + }; if payload == "[DONE]" { break; } @@ -377,7 +381,9 @@ data: [DONE] if !line.starts_with("data: ") { continue; } - let payload = &line[6..]; + let Some(payload) = line.get(6..) else { + continue; + }; if payload == "[DONE]" { break; } diff --git a/scripts/test_providers.sh b/scripts/test_providers.sh index ae31e40ebac7..c4261d75c14c 100755 --- a/scripts/test_providers.sh +++ b/scripts/test_providers.sh @@ -51,7 +51,7 @@ for provider_config in "${PROVIDERS[@]}"; do echo "Model: ${MODEL}" echo "" TMPFILE=$(mktemp) - (cd "$TESTDIR" && "$SCRIPT_DIR/target/release/goose" run --text "please list files in the current directory" --with-builtin developer,autovisualiser,computercontroller,tutorial 2>&1) | tee "$TMPFILE" + (cd "$TESTDIR" && "$SCRIPT_DIR/target/release/goose" run --text "please list files in the current directory" --with-builtin developer,autovisualiser,computercontroller,tutorial,todo,extensionmanager 2>&1) | tee "$TMPFILE" echo "" if grep -q "shell | developer" "$TMPFILE"; then echo "✓ SUCCESS: Test passed - developer tool called"