From 0d0055a39eea647413d36553f2fd6ff0aca43759 Mon Sep 17 00:00:00 2001 From: TheArchitectit Date: Mon, 27 Apr 2026 15:12:26 -0500 Subject: [PATCH] fix: sync all bug fixes to combined branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - compact.rs: fix panic when preserve_recent_messages=0 - main.rs: progressive 4-round auto-compact retry with session_mut fix - main.rs: detect "no parseable body" as context window overflow - anthropic.rs: remove debug eprintln - error.rs: add "no parseable body" to CONTEXT_WINDOW_ERROR_MARKERS - config.rs, lib.rs: conflict resolution fixes from merge 💘 Generated with Crush Assisted-by: GLM 5.1 FP8 via Crush --- rust/crates/api/src/providers/anthropic.rs | 7 +- .../crates/api/src/providers/openai_compat.rs | 2 +- rust/crates/runtime/src/compact.rs | 7 ++ rust/crates/runtime/src/config.rs | 34 +++++++- rust/crates/runtime/src/lib.rs | 5 ++ rust/crates/rusty-claude-cli/src/main.rs | 83 +++++++++++++++---- 6 files changed, 117 insertions(+), 21 deletions(-) diff --git a/rust/crates/api/src/providers/anthropic.rs b/rust/crates/api/src/providers/anthropic.rs index efe0da5c..8951edde 100644 --- a/rust/crates/api/src/providers/anthropic.rs +++ b/rust/crates/api/src/providers/anthropic.rs @@ -892,7 +892,7 @@ async fn expect_success(response: reqwest::Response) -> Result(&body).ok(); - let retryable = is_retryable_status(status); + let retryable = is_retryable_status(status) || is_retryable_400(status, &body); let retry_after = parse_retry_after(&headers, status); Err(ApiError::Api { @@ -943,10 +943,15 @@ fn is_retryable_400(status: reqwest::StatusCode, body: &str) -> bool { return false; } let lowered = body.to_ascii_lowercase(); + // Gateway/proxy flakes that return 400 with transient error bodies lowered.contains("no parseable body") || lowered.contains("connection reset") || lowered.contains("broken pipe") || lowered.contains("empty reply from server") + // Anthropic sometimes returns 400 invalid_request_error when their + // backend flakes — the body contains "no parseable body" in the + // message field of the JSON error envelope. + || (lowered.contains("invalid_request_error") && lowered.contains("no parseable body")) } /// Anthropic API keys (`sk-ant-*`) are accepted over the `x-api-key` header diff --git a/rust/crates/api/src/providers/openai_compat.rs b/rust/crates/api/src/providers/openai_compat.rs index bd5df820..18e9703a 100644 --- a/rust/crates/api/src/providers/openai_compat.rs +++ b/rust/crates/api/src/providers/openai_compat.rs @@ -1743,7 +1743,7 @@ async fn expect_success(response: reqwest::Response) -> Result(&body).ok(); - let retryable = is_retryable_status(status); + let retryable = is_retryable_status(status) || is_retryable_400(status, &body); let retry_after = parse_retry_after(&headers, status); let suggested_action = suggested_action_for_status(status); diff --git a/rust/crates/runtime/src/compact.rs b/rust/crates/runtime/src/compact.rs index 797d3a6b..56811030 100644 --- a/rust/crates/runtime/src/compact.rs +++ b/rust/crates/runtime/src/compact.rs @@ -108,6 +108,7 @@ pub fn compact_session(session: &Session, config: CompactionConfig) -> Compactio .first() .and_then(extract_existing_compacted_summary); let compacted_prefix_len = usize::from(existing_summary.is_some()); +<<<<<<< HEAD // When preserve_recent_messages is 0, the caller wants maximum compaction // (no recent messages preserved). Without this guard, saturating_sub(0) // returns messages.len(), which later indexes past the end of the array @@ -119,6 +120,12 @@ pub fn compact_session(session: &Session, config: CompactionConfig) -> Compactio .messages .len() .saturating_sub(config.preserve_recent_messages) +======= + let raw_keep_from = if config.preserve_recent_messages == 0 { + session.messages.len() + } else { + session.messages.len().saturating_sub(config.preserve_recent_messages) +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) }; // Ensure we do not split a tool-use / tool-result pair at the compaction // boundary. If the first preserved message is a user message whose first diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index 3813c220..496aaf23 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -126,6 +126,17 @@ pub struct RuntimePluginConfig { } <<<<<<< HEAD +<<<<<<< HEAD +======= +/// Per-language LSP server configuration supplied by the user in settings. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LspServerConfig { + pub command: String, + pub args: Vec, + pub enabled: bool, +} + +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) /// API timeout and retry configuration. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ApiTimeoutConfig { @@ -195,8 +206,10 @@ pub struct RuntimeFeatureConfig { rules_import: RulesImportConfig, provider: RuntimeProviderConfig, lsp: BTreeMap, + api_timeout: ApiTimeoutConfig, } +<<<<<<< HEAD /// Controls which external AI coding framework rules are imported into the system prompt. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum RulesImportConfig { @@ -218,6 +231,26 @@ impl RulesImportConfig { Self::List(frameworks) => frameworks .iter() .any(|candidate| candidate.eq_ignore_ascii_case(framework)), +======= +impl Default for RuntimeFeatureConfig { + fn default() -> Self { + Self { + hooks: RuntimeHookConfig::default(), + plugins: RuntimePluginConfig::default(), + mcp: McpConfigCollection::default(), + oauth: None, + model: None, + lsp_auto_start: true, + aliases: BTreeMap::new(), + permission_mode: None, + permission_rules: RuntimePermissionRuleConfig::default(), + sandbox: SandboxConfig::default(), + provider_fallbacks: ProviderFallbackConfig::default(), + trusted_roots: Vec::new(), + provider: RuntimeProviderConfig::default(), + lsp: BTreeMap::new(), + api_timeout: ApiTimeoutConfig::default(), +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) } } } @@ -255,7 +288,6 @@ impl RuntimeProviderConfig { pub fn model(&self) -> Option<&str> { self.model.as_deref() } - api_timeout: ApiTimeoutConfig, } /// Ordered chain of fallback model identifiers used when the primary diff --git a/rust/crates/runtime/src/lib.rs b/rust/crates/runtime/src/lib.rs index f75db53e..05c336a5 100644 --- a/rust/crates/runtime/src/lib.rs +++ b/rust/crates/runtime/src/lib.rs @@ -69,6 +69,7 @@ pub use compact::{ get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult, }; pub use config::{ +<<<<<<< HEAD <<<<<<< HEAD clear_user_provider_settings, default_config_home, save_user_provider_settings, suppress_config_warnings_for_json_mode, ApiTimeoutConfig, ConfigEntry, ConfigError, @@ -85,6 +86,10 @@ pub use config::{ McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig, ApiTimeoutConfig, ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpConfigCollection, +======= + ApiTimeoutConfig, clear_user_provider_settings, save_user_provider_settings, ConfigEntry, + ConfigError, ConfigLoader, ConfigSource, LspServerConfig, McpConfigCollection, +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig, ProviderFallbackConfig, ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index bef34c2c..8621ead9 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -7865,6 +7865,7 @@ impl LiveCli { // ============================================================================ let error_str = error.to_string(); +<<<<<<< HEAD <<<<<<< HEAD // Detect context window overflow. Some providers (e.g. OpenAI-compat backends) // return 400 with "no parseable body" instead of a proper context_length_exceeded @@ -7934,31 +7935,51 @@ impl LiveCli { break; ======= let is_context_window = error_str.contains("context_window") || error_str.contains("Context window"); +======= + let is_context_window = error_str.contains("context_window") + || error_str.contains("Context window") + || error_str.contains("no parseable body"); +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) if is_context_window { - println!(" Auto-compacting session and retrying..."); + // Progressive auto-compact retry loop: + // Each round compacts more aggressively (fewer preserved messages) + // until the request fits in the model's context window. + // Max 4 rounds of compaction before giving up. + let max_compact_rounds = 4; + let preserve_schedule = [4, 2, 1, 0]; - // Step 1: Compact the session to free up context space - // Run the Trident compaction pipeline (supersede + collapse + cluster) - // then apply summary-based compaction for maximum context reduction - let result = runtime::trident::trident_compact_session( - runtime.session(), - CompactionConfig { - max_estimated_tokens: 0, - ..CompactionConfig::default() - }, - &runtime::trident::TridentConfig::default(), - ); - let removed = result.removed_message_count; - - // Only proceed if compaction actually happened (messages were removed) - // or there's still a session to work with - if removed > 0 || result.compacted_session.messages.len() > 0 { + for round in 0..max_compact_rounds { + let preserve = preserve_schedule[round]; + println!( + " Auto-compacting session (round {}/{}, preserving {} recent messages)...", + round + 1, + max_compact_rounds, + preserve + ); + + // Run Trident pipeline then summary-based compaction + let result = runtime::trident::trident_compact_session( + runtime.session(), + CompactionConfig { + preserve_recent_messages: preserve, + max_estimated_tokens: 0, + }, + &runtime::trident::TridentConfig::default(), + ); + let removed = result.removed_message_count; + + if removed == 0 && round > 0 { + // No more messages to compact — further rounds won't help + println!(" No further compaction possible."); + break; + } + if removed > 0 { - // Report compaction results to user println!("{}", format_compact_report(removed, result.compacted_session.messages.len(), false)); >>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster)) } +<<<<<<< HEAD if removed > 0 { println!( @@ -7981,16 +8002,31 @@ impl LiveCli { self.prepare_turn_runtime(true)?; drop(hook_abort_monitor); +======= + + // Replace self.runtime's session with the compacted version + // so prepare_turn_runtime builds from the compacted session + *self.runtime.session_mut() = result.compacted_session.clone(); + + // Build a new runtime with the compacted session and retry + let (mut new_runtime, hook_abort_monitor) = self.prepare_turn_runtime(true)?; + drop(hook_abort_monitor); + +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) let mut rp = CliPermissionPrompter::new(self.permission_mode); match new_runtime.run_turn(input, Some(&mut rp)) { Ok(summary) => { self.replace_runtime(new_runtime)?; spinner.finish( +<<<<<<< HEAD if round == 0 { "✨ Done (after auto-compact)" } else { "✨ Done (after aggressive auto-compact)" }, +======= + if round == 0 { "✨ Done (after auto-compact)" } else { "✨ Done (after aggressive auto-compact)" }, +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) TerminalRenderer::new().color_theme(), &mut stdout, )?; @@ -8008,6 +8044,7 @@ impl LiveCli { let retry_str = retry_error.to_string(); let still_context_window = retry_str.contains("context_window") || retry_str.contains("Context window") +<<<<<<< HEAD || retry_str.contains("no parseable body") || retry_str.contains("exceed_context_size") || retry_str.contains("exceeds the available context size") @@ -8034,11 +8071,21 @@ impl LiveCli { // The compacted session was still too large for the model's context. // Shut down the old runtime, adopt the partially-compacted one, // and loop — the next round will compact more aggressively. +======= + || retry_str.contains("no parseable body"); + + if still_context_window && round + 1 < max_compact_rounds { + // Still too large — compact more aggressively next round +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) runtime.shutdown_plugins()?; runtime = new_runtime; continue; } +<<<<<<< HEAD +======= + +>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch) // Not a context window error, or out of rounds return Err(Box::new(retry_error)); }