fix: sync all bug fixes to combined branch
- 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 <crush@charm.land>
This commit is contained in:
parent
763179877a
commit
0d0055a39e
|
|
@ -892,7 +892,7 @@ async fn expect_success(response: reqwest::Response) -> Result<reqwest::Response
|
||||||
let request_id = request_id_from_headers(&headers);
|
let request_id = request_id_from_headers(&headers);
|
||||||
let body = response.text().await.unwrap_or_else(|_| String::new());
|
let body = response.text().await.unwrap_or_else(|_| String::new());
|
||||||
let parsed_error = serde_json::from_str::<AnthropicErrorEnvelope>(&body).ok();
|
let parsed_error = serde_json::from_str::<AnthropicErrorEnvelope>(&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 retry_after = parse_retry_after(&headers, status);
|
||||||
|
|
||||||
Err(ApiError::Api {
|
Err(ApiError::Api {
|
||||||
|
|
@ -943,10 +943,15 @@ fn is_retryable_400(status: reqwest::StatusCode, body: &str) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let lowered = body.to_ascii_lowercase();
|
let lowered = body.to_ascii_lowercase();
|
||||||
|
// Gateway/proxy flakes that return 400 with transient error bodies
|
||||||
lowered.contains("no parseable body")
|
lowered.contains("no parseable body")
|
||||||
|| lowered.contains("connection reset")
|
|| lowered.contains("connection reset")
|
||||||
|| lowered.contains("broken pipe")
|
|| lowered.contains("broken pipe")
|
||||||
|| lowered.contains("empty reply from server")
|
|| 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
|
/// Anthropic API keys (`sk-ant-*`) are accepted over the `x-api-key` header
|
||||||
|
|
|
||||||
|
|
@ -1743,7 +1743,7 @@ async fn expect_success(response: reqwest::Response) -> Result<reqwest::Response
|
||||||
let request_id = request_id_from_headers(&headers);
|
let request_id = request_id_from_headers(&headers);
|
||||||
let body = response.text().await.unwrap_or_default();
|
let body = response.text().await.unwrap_or_default();
|
||||||
let parsed_error = serde_json::from_str::<ErrorEnvelope>(&body).ok();
|
let parsed_error = serde_json::from_str::<ErrorEnvelope>(&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 retry_after = parse_retry_after(&headers, status);
|
||||||
|
|
||||||
let suggested_action = suggested_action_for_status(status);
|
let suggested_action = suggested_action_for_status(status);
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ pub fn compact_session(session: &Session, config: CompactionConfig) -> Compactio
|
||||||
.first()
|
.first()
|
||||||
.and_then(extract_existing_compacted_summary);
|
.and_then(extract_existing_compacted_summary);
|
||||||
let compacted_prefix_len = usize::from(existing_summary.is_some());
|
let compacted_prefix_len = usize::from(existing_summary.is_some());
|
||||||
|
<<<<<<< HEAD
|
||||||
// When preserve_recent_messages is 0, the caller wants maximum compaction
|
// When preserve_recent_messages is 0, the caller wants maximum compaction
|
||||||
// (no recent messages preserved). Without this guard, saturating_sub(0)
|
// (no recent messages preserved). Without this guard, saturating_sub(0)
|
||||||
// returns messages.len(), which later indexes past the end of the array
|
// 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
|
.messages
|
||||||
.len()
|
.len()
|
||||||
.saturating_sub(config.preserve_recent_messages)
|
.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
|
// 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
|
// boundary. If the first preserved message is a user message whose first
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,17 @@ pub struct RuntimePluginConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
<<<<<<< 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<String>,
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch)
|
||||||
/// API timeout and retry configuration.
|
/// API timeout and retry configuration.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ApiTimeoutConfig {
|
pub struct ApiTimeoutConfig {
|
||||||
|
|
@ -195,8 +206,10 @@ pub struct RuntimeFeatureConfig {
|
||||||
rules_import: RulesImportConfig,
|
rules_import: RulesImportConfig,
|
||||||
provider: RuntimeProviderConfig,
|
provider: RuntimeProviderConfig,
|
||||||
lsp: BTreeMap<String, LspServerConfig>,
|
lsp: BTreeMap<String, LspServerConfig>,
|
||||||
|
api_timeout: ApiTimeoutConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
/// Controls which external AI coding framework rules are imported into the system prompt.
|
/// Controls which external AI coding framework rules are imported into the system prompt.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub enum RulesImportConfig {
|
pub enum RulesImportConfig {
|
||||||
|
|
@ -218,6 +231,26 @@ impl RulesImportConfig {
|
||||||
Self::List(frameworks) => frameworks
|
Self::List(frameworks) => frameworks
|
||||||
.iter()
|
.iter()
|
||||||
.any(|candidate| candidate.eq_ignore_ascii_case(framework)),
|
.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> {
|
pub fn model(&self) -> Option<&str> {
|
||||||
self.model.as_deref()
|
self.model.as_deref()
|
||||||
}
|
}
|
||||||
api_timeout: ApiTimeoutConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ordered chain of fallback model identifiers used when the primary
|
/// Ordered chain of fallback model identifiers used when the primary
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ pub use compact::{
|
||||||
get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult,
|
get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult,
|
||||||
};
|
};
|
||||||
pub use config::{
|
pub use config::{
|
||||||
|
<<<<<<< HEAD
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
clear_user_provider_settings, default_config_home, save_user_provider_settings,
|
clear_user_provider_settings, default_config_home, save_user_provider_settings,
|
||||||
suppress_config_warnings_for_json_mode, ApiTimeoutConfig, ConfigEntry, ConfigError,
|
suppress_config_warnings_for_json_mode, ApiTimeoutConfig, ConfigEntry, ConfigError,
|
||||||
|
|
@ -85,6 +86,10 @@ pub use config::{
|
||||||
McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpServerConfig,
|
McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpServerConfig,
|
||||||
McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
|
McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
|
||||||
ApiTimeoutConfig, ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpConfigCollection,
|
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,
|
McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig,
|
||||||
McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
|
McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
|
||||||
ProviderFallbackConfig, ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig,
|
ProviderFallbackConfig, ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig,
|
||||||
|
|
|
||||||
|
|
@ -7865,6 +7865,7 @@ impl LiveCli {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
let error_str = error.to_string();
|
let error_str = error.to_string();
|
||||||
|
<<<<<<< HEAD
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
// Detect context window overflow. Some providers (e.g. OpenAI-compat backends)
|
// Detect context window overflow. Some providers (e.g. OpenAI-compat backends)
|
||||||
// return 400 with "no parseable body" instead of a proper context_length_exceeded
|
// return 400 with "no parseable body" instead of a proper context_length_exceeded
|
||||||
|
|
@ -7934,31 +7935,51 @@ impl LiveCli {
|
||||||
break;
|
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");
|
||||||
|
=======
|
||||||
|
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 {
|
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
|
for round in 0..max_compact_rounds {
|
||||||
// Run the Trident compaction pipeline (supersede + collapse + cluster)
|
let preserve = preserve_schedule[round];
|
||||||
// then apply summary-based compaction for maximum context reduction
|
println!(
|
||||||
let result = runtime::trident::trident_compact_session(
|
" Auto-compacting session (round {}/{}, preserving {} recent messages)...",
|
||||||
runtime.session(),
|
round + 1,
|
||||||
CompactionConfig {
|
max_compact_rounds,
|
||||||
max_estimated_tokens: 0,
|
preserve
|
||||||
..CompactionConfig::default()
|
);
|
||||||
},
|
|
||||||
&runtime::trident::TridentConfig::default(),
|
// Run Trident pipeline then summary-based compaction
|
||||||
);
|
let result = runtime::trident::trident_compact_session(
|
||||||
let removed = result.removed_message_count;
|
runtime.session(),
|
||||||
|
CompactionConfig {
|
||||||
// Only proceed if compaction actually happened (messages were removed)
|
preserve_recent_messages: preserve,
|
||||||
// or there's still a session to work with
|
max_estimated_tokens: 0,
|
||||||
if removed > 0 || result.compacted_session.messages.len() > 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 {
|
if removed > 0 {
|
||||||
// Report compaction results to user
|
|
||||||
println!("{}", format_compact_report(removed, result.compacted_session.messages.len(), false));
|
println!("{}", format_compact_report(removed, result.compacted_session.messages.len(), false));
|
||||||
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
if removed > 0 {
|
if removed > 0 {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -7981,16 +8002,31 @@ impl LiveCli {
|
||||||
self.prepare_turn_runtime(true)?;
|
self.prepare_turn_runtime(true)?;
|
||||||
drop(hook_abort_monitor);
|
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);
|
let mut rp = CliPermissionPrompter::new(self.permission_mode);
|
||||||
match new_runtime.run_turn(input, Some(&mut rp)) {
|
match new_runtime.run_turn(input, Some(&mut rp)) {
|
||||||
Ok(summary) => {
|
Ok(summary) => {
|
||||||
self.replace_runtime(new_runtime)?;
|
self.replace_runtime(new_runtime)?;
|
||||||
spinner.finish(
|
spinner.finish(
|
||||||
|
<<<<<<< HEAD
|
||||||
if round == 0 {
|
if round == 0 {
|
||||||
"✨ Done (after auto-compact)"
|
"✨ Done (after auto-compact)"
|
||||||
} else {
|
} else {
|
||||||
"✨ Done (after aggressive auto-compact)"
|
"✨ 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(),
|
TerminalRenderer::new().color_theme(),
|
||||||
&mut stdout,
|
&mut stdout,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -8008,6 +8044,7 @@ impl LiveCli {
|
||||||
let retry_str = retry_error.to_string();
|
let retry_str = retry_error.to_string();
|
||||||
let still_context_window = retry_str.contains("context_window")
|
let still_context_window = retry_str.contains("context_window")
|
||||||
|| retry_str.contains("Context window")
|
|| retry_str.contains("Context window")
|
||||||
|
<<<<<<< HEAD
|
||||||
|| retry_str.contains("no parseable body")
|
|| retry_str.contains("no parseable body")
|
||||||
|| retry_str.contains("exceed_context_size")
|
|| retry_str.contains("exceed_context_size")
|
||||||
|| retry_str.contains("exceeds the available 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.
|
// The compacted session was still too large for the model's context.
|
||||||
// Shut down the old runtime, adopt the partially-compacted one,
|
// Shut down the old runtime, adopt the partially-compacted one,
|
||||||
// and loop — the next round will compact more aggressively.
|
// 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.shutdown_plugins()?;
|
||||||
runtime = new_runtime;
|
runtime = new_runtime;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
|
=======
|
||||||
|
|
||||||
|
>>>>>>> 1ff5617c (fix: sync all bug fixes to combined branch)
|
||||||
// Not a context window error, or out of rounds
|
// Not a context window error, or out of rounds
|
||||||
return Err(Box::new(retry_error));
|
return Err(Box::new(retry_error));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue