From 371e083ef0fcfd81fd3980af0abd98915849254b Mon Sep 17 00:00:00 2001 From: David Katz Date: Wed, 3 Sep 2025 19:36:05 -0400 Subject: [PATCH 1/6] parse the error --- crates/goose/src/providers/databricks.rs | 51 +++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index 8a09b522f263..e809dc60fe5c 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -326,10 +326,57 @@ impl Provider for DatabricksProvider { .with_retry(|| async { let resp = self.api_client.response_post(&path, &payload).await?; if !resp.status().is_success() { + let status = resp.status(); + let error_text = resp.text().await.unwrap_or_default(); + + // Try to parse as JSON to get structured error + if let Ok(error_json) = serde_json::from_str::(&error_text) { + // Check for context length errors in various formats + if let Some(error_obj) = error_json.get("error") { + let error_msg = error_obj + .get("message") + .and_then(|m| m.as_str()) + .unwrap_or(&error_text); + + // Check for context length exceeded patterns + if error_msg.to_lowercase().contains("context length") + || error_msg.to_lowercase().contains("too long") + || error_msg.to_lowercase().contains("exceeds") + || error_msg.to_lowercase().contains("token") + { + return Err(ProviderError::ContextLengthExceeded( + error_msg.to_string(), + )); + } + } + + // Also check top-level message field + if let Some(msg) = error_json.get("message").and_then(|m| m.as_str()) { + if msg.to_lowercase().contains("context length") + || msg.to_lowercase().contains("too long") + || msg.to_lowercase().contains("exceeds") + || msg.to_lowercase().contains("token") + { + return Err(ProviderError::ContextLengthExceeded(msg.to_string())); + } + } + } + + // Check raw text for context length patterns + let error_lower = error_text.to_lowercase(); + if error_lower.contains("context length") + || error_lower.contains("too long") + || error_lower.contains("exceeds") + || error_lower.contains("maximum context") + || error_lower.contains("token limit") + { + return Err(ProviderError::ContextLengthExceeded(error_text)); + } + + // Default to generic error return Err(ProviderError::RequestFailed(format!( "HTTP {}: {}", - resp.status(), - resp.text().await.unwrap_or_default() + status, error_text ))); } Ok(resp) From 07e23cfdd7fcc242ad91ee60dcf141634368bf86 Mon Sep 17 00:00:00 2001 From: David Katz Date: Wed, 3 Sep 2025 20:09:05 -0400 Subject: [PATCH 2/6] emit a message instead of an error --- crates/goose/src/providers/databricks.rs | 49 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index e809dc60fe5c..e9012c608d1b 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -322,7 +322,9 @@ impl Provider for DatabricksProvider { .insert("stream".to_string(), Value::Bool(true)); let path = self.get_endpoint_path(&model_config.model_name, false); - let response = self + + // Try to make the request with retry logic + let response_result = self .with_retry(|| async { let resp = self.api_client.response_post(&path, &payload).await?; if !resp.status().is_success() { @@ -381,23 +383,36 @@ impl Provider for DatabricksProvider { } Ok(resp) }) - .await?; - - let stream = response.bytes_stream().map_err(io::Error::other); - - let model = self.model.clone(); - Ok(Box::pin(try_stream! { - let stream_reader = StreamReader::new(stream); - let framed = FramedRead::new(stream_reader, LinesCodec::new()).map_err(anyhow::Error::from); - - let message_stream = response_to_streaming_message(framed); - pin!(message_stream); - while let Some(message) = message_stream.next().await { - let (message, usage) = message.map_err(|e| ProviderError::RequestFailed(format!("Stream decode error: {}", e)))?; - super::utils::emit_debug_trace(&model, &payload, &message, &usage.as_ref().map(|f| f.usage).unwrap_or_default()); - yield (message, usage); + .await; + + // Handle context length exceeded by returning a stream with the error message + match response_result { + Err(ProviderError::ContextLengthExceeded(msg)) => { + // Return a stream that yields a single message with ContextLengthExceeded content + Ok(Box::pin(try_stream! { + let error_message = Message::assistant().with_context_length_exceeded(msg); + yield (Some(error_message), None); + })) + } + Err(e) => Err(e), + Ok(response) => { + let stream = response.bytes_stream().map_err(io::Error::other); + + let model = self.model.clone(); + Ok(Box::pin(try_stream! { + let stream_reader = StreamReader::new(stream); + let framed = FramedRead::new(stream_reader, LinesCodec::new()).map_err(anyhow::Error::from); + + let message_stream = response_to_streaming_message(framed); + pin!(message_stream); + while let Some(message) = message_stream.next().await { + let (message, usage) = message.map_err(|e| ProviderError::RequestFailed(format!("Stream decode error: {}", e)))?; + super::utils::emit_debug_trace(&model, &payload, &message, &usage.as_ref().map(|f| f.usage).unwrap_or_default()); + yield (message, usage); + } + })) } - })) + } } fn supports_streaming(&self) -> bool { From 7a9b477f8b7615b6e0ba8b2e0a7a26672f5db491 Mon Sep 17 00:00:00 2001 From: David Katz Date: Wed, 3 Sep 2025 20:39:12 -0400 Subject: [PATCH 3/6] go back to error --- crates/goose-cli/src/session/mod.rs | 26 ++++++++++--- crates/goose/src/providers/databricks.rs | 49 ++++++++---------------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 7e9ec62b5d9f..30de1d76407e 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -1276,12 +1276,26 @@ impl Session { if let Err(e) = self.handle_interrupted_messages(false).await { eprintln!("Error handling interruption: {}", e); } - output::render_error( - "The error above was an exception we were not able to handle.\n\ - These errors are often related to connection or authentication\n\ - We've removed the conversation up to the most recent user message\n\ - - depending on the error you may be able to continue", - ); + + // Check if it's a ProviderError::ContextLengthExceeded + if e.downcast_ref::() + .map(|provider_error| matches!(provider_error, goose::providers::errors::ProviderError::ContextLengthExceeded(_))) + .unwrap_or(false) { + output::render_error( + "Context length exceeded error.\n\ + The conversation is too long for the model's context window.\n\ + Consider using /summarize to condense the conversation history\n\ + or /clear to start fresh.\n\ + We've removed the conversation up to the most recent user message.", + ); + } else { + output::render_error( + "The error above was an exception we were not able to handle.\n\ + These errors are often related to connection or authentication\n\ + We've removed the conversation up to the most recent user message\n\ + - depending on the error you may be able to continue", + ); + } break; } None => break, diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index e9012c608d1b..e809dc60fe5c 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -322,9 +322,7 @@ impl Provider for DatabricksProvider { .insert("stream".to_string(), Value::Bool(true)); let path = self.get_endpoint_path(&model_config.model_name, false); - - // Try to make the request with retry logic - let response_result = self + let response = self .with_retry(|| async { let resp = self.api_client.response_post(&path, &payload).await?; if !resp.status().is_success() { @@ -383,36 +381,23 @@ impl Provider for DatabricksProvider { } Ok(resp) }) - .await; - - // Handle context length exceeded by returning a stream with the error message - match response_result { - Err(ProviderError::ContextLengthExceeded(msg)) => { - // Return a stream that yields a single message with ContextLengthExceeded content - Ok(Box::pin(try_stream! { - let error_message = Message::assistant().with_context_length_exceeded(msg); - yield (Some(error_message), None); - })) - } - Err(e) => Err(e), - Ok(response) => { - let stream = response.bytes_stream().map_err(io::Error::other); - - let model = self.model.clone(); - Ok(Box::pin(try_stream! { - let stream_reader = StreamReader::new(stream); - let framed = FramedRead::new(stream_reader, LinesCodec::new()).map_err(anyhow::Error::from); - - let message_stream = response_to_streaming_message(framed); - pin!(message_stream); - while let Some(message) = message_stream.next().await { - let (message, usage) = message.map_err(|e| ProviderError::RequestFailed(format!("Stream decode error: {}", e)))?; - super::utils::emit_debug_trace(&model, &payload, &message, &usage.as_ref().map(|f| f.usage).unwrap_or_default()); - yield (message, usage); - } - })) + .await?; + + let stream = response.bytes_stream().map_err(io::Error::other); + + let model = self.model.clone(); + Ok(Box::pin(try_stream! { + let stream_reader = StreamReader::new(stream); + let framed = FramedRead::new(stream_reader, LinesCodec::new()).map_err(anyhow::Error::from); + + let message_stream = response_to_streaming_message(framed); + pin!(message_stream); + while let Some(message) = message_stream.next().await { + let (message, usage) = message.map_err(|e| ProviderError::RequestFailed(format!("Stream decode error: {}", e)))?; + super::utils::emit_debug_trace(&model, &payload, &message, &usage.as_ref().map(|f| f.usage).unwrap_or_default()); + yield (message, usage); } - } + })) } fn supports_streaming(&self) -> bool { From bd514366a2c336749ae676e686609e9082fccdb2 Mon Sep 17 00:00:00 2001 From: David Katz Date: Wed, 3 Sep 2025 20:47:03 -0400 Subject: [PATCH 4/6] simplify error check --- crates/goose/src/providers/databricks.rs | 51 +++++------------------- 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index e809dc60fe5c..b39ea86cd389 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -225,6 +225,14 @@ impl DatabricksProvider { }) } + fn check_context_length_exceeded(error_text: &str) -> bool { + let lower = error_text.to_lowercase(); + lower.contains("context limit") + || lower.contains("exceed") + || lower.contains("too long") + || lower.contains("input is too long") + } + fn get_endpoint_path(&self, model_name: &str, is_embedding: bool) -> String { if is_embedding { "serving-endpoints/text-embedding-3-small/invocations".to_string() @@ -329,47 +337,8 @@ impl Provider for DatabricksProvider { let status = resp.status(); let error_text = resp.text().await.unwrap_or_default(); - // Try to parse as JSON to get structured error - if let Ok(error_json) = serde_json::from_str::(&error_text) { - // Check for context length errors in various formats - if let Some(error_obj) = error_json.get("error") { - let error_msg = error_obj - .get("message") - .and_then(|m| m.as_str()) - .unwrap_or(&error_text); - - // Check for context length exceeded patterns - if error_msg.to_lowercase().contains("context length") - || error_msg.to_lowercase().contains("too long") - || error_msg.to_lowercase().contains("exceeds") - || error_msg.to_lowercase().contains("token") - { - return Err(ProviderError::ContextLengthExceeded( - error_msg.to_string(), - )); - } - } - - // Also check top-level message field - if let Some(msg) = error_json.get("message").and_then(|m| m.as_str()) { - if msg.to_lowercase().contains("context length") - || msg.to_lowercase().contains("too long") - || msg.to_lowercase().contains("exceeds") - || msg.to_lowercase().contains("token") - { - return Err(ProviderError::ContextLengthExceeded(msg.to_string())); - } - } - } - - // Check raw text for context length patterns - let error_lower = error_text.to_lowercase(); - if error_lower.contains("context length") - || error_lower.contains("too long") - || error_lower.contains("exceeds") - || error_lower.contains("maximum context") - || error_lower.contains("token limit") - { + // Check if it's a context length error + if Self::check_context_length_exceeded(&error_text) { return Err(ProviderError::ContextLengthExceeded(error_text)); } From c925560f11a9441a95f52be534705ac59f15cfd4 Mon Sep 17 00:00:00 2001 From: David Katz Date: Wed, 3 Sep 2025 20:51:10 -0400 Subject: [PATCH 5/6] rm dupe fn --- crates/goose/src/providers/databricks.rs | 14 ++++---------- crates/goose/src/providers/utils.rs | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index b39ea86cd389..a07471f8ab1c 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -16,7 +16,9 @@ use super::errors::ProviderError; use super::formats::databricks::{create_request, response_to_message}; use super::oauth; use super::retry::ProviderRetry; -use super::utils::{get_model, handle_response_openai_compat, ImageFormat}; +use super::utils::{ + check_context_length_exceeded, get_model, handle_response_openai_compat, ImageFormat, +}; use crate::config::ConfigError; use crate::conversation::message::Message; use crate::impl_provider_default; @@ -225,14 +227,6 @@ impl DatabricksProvider { }) } - fn check_context_length_exceeded(error_text: &str) -> bool { - let lower = error_text.to_lowercase(); - lower.contains("context limit") - || lower.contains("exceed") - || lower.contains("too long") - || lower.contains("input is too long") - } - fn get_endpoint_path(&self, model_name: &str, is_embedding: bool) -> String { if is_embedding { "serving-endpoints/text-embedding-3-small/invocations".to_string() @@ -338,7 +332,7 @@ impl Provider for DatabricksProvider { let error_text = resp.text().await.unwrap_or_default(); // Check if it's a context length error - if Self::check_context_length_exceeded(&error_text) { + if check_context_length_exceeded(&error_text) { return Err(ProviderError::ContextLengthExceeded(error_text)); } diff --git a/crates/goose/src/providers/utils.rs b/crates/goose/src/providers/utils.rs index e5dec1b72a47..8b040152583b 100644 --- a/crates/goose/src/providers/utils.rs +++ b/crates/goose/src/providers/utils.rs @@ -44,7 +44,7 @@ pub fn convert_image(image: &ImageContent, image_format: &ImageFormat) -> Value } } -fn check_context_length_exceeded(text: &str) -> bool { +pub fn check_context_length_exceeded(text: &str) -> bool { let check_phrases = [ "too long", "context length", From 3a297fc94441e4cbf3d88f10d2f5ab8b4a2a570c Mon Sep 17 00:00:00 2001 From: David Katz Date: Wed, 3 Sep 2025 20:57:50 -0400 Subject: [PATCH 6/6] use existing fn --- crates/goose/src/providers/databricks.rs | 15 ++++----------- crates/goose/src/providers/utils.rs | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index a07471f8ab1c..3792b10ab64a 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -17,7 +17,7 @@ use super::formats::databricks::{create_request, response_to_message}; use super::oauth; use super::retry::ProviderRetry; use super::utils::{ - check_context_length_exceeded, get_model, handle_response_openai_compat, ImageFormat, + get_model, handle_response_openai_compat, map_http_error_to_provider_error, ImageFormat, }; use crate::config::ConfigError; use crate::conversation::message::Message; @@ -331,16 +331,9 @@ impl Provider for DatabricksProvider { let status = resp.status(); let error_text = resp.text().await.unwrap_or_default(); - // Check if it's a context length error - if check_context_length_exceeded(&error_text) { - return Err(ProviderError::ContextLengthExceeded(error_text)); - } - - // Default to generic error - return Err(ProviderError::RequestFailed(format!( - "HTTP {}: {}", - status, error_text - ))); + // Parse as JSON if possible to pass to map_http_error_to_provider_error + let json_payload = serde_json::from_str::(&error_text).ok(); + return Err(map_http_error_to_provider_error(status, json_payload)); } Ok(resp) }) diff --git a/crates/goose/src/providers/utils.rs b/crates/goose/src/providers/utils.rs index 8b040152583b..e5dec1b72a47 100644 --- a/crates/goose/src/providers/utils.rs +++ b/crates/goose/src/providers/utils.rs @@ -44,7 +44,7 @@ pub fn convert_image(image: &ImageContent, image_format: &ImageFormat) -> Value } } -pub fn check_context_length_exceeded(text: &str) -> bool { +fn check_context_length_exceeded(text: &str) -> bool { let check_phrases = [ "too long", "context length",