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 body = response.text().await.unwrap_or_else(|_| String::new());
|
||||
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);
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1743,7 +1743,7 @@ async fn expect_success(response: reqwest::Response) -> Result<reqwest::Response
|
|||
let request_id = request_id_from_headers(&headers);
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
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 suggested_action = suggested_action_for_status(status);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
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<String, LspServerConfig>,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue