use super::*;
const ANTHROPIC_COMPACT_TRIGGER_FLOOR: u64 = 50_000;
const ANTHROPIC_CLEAR_TOOL_USES_TRIGGER_TOKENS: u64 = 100_000;
const ANTHROPIC_CLEAR_TOOL_USES_KEEP: u64 = 3;
const ANTHROPIC_CLEAR_THINKING_KEEP_TURNS: u64 = 2;
pub(crate) fn anthropic_inline_compaction_edits(instructions: Option<&str>, include_context_edits: bool) -> Vec<Value> {
let mut edits = Vec::with_capacity(3);
if include_context_edits {
edits.push(json!({
"type": "clear_thinking_20251015",
"keep": { "type": "thinking_turns", "value": ANTHROPIC_CLEAR_THINKING_KEEP_TURNS },
}));
edits.push(json!({
"type": "clear_tool_uses_20250919",
"trigger": { "type": "input_tokens", "value": ANTHROPIC_CLEAR_TOOL_USES_TRIGGER_TOKENS },
"keep": { "type": "tool_uses", "value": ANTHROPIC_CLEAR_TOOL_USES_KEEP },
}));
}
let mut compact_edit = serde_json::Map::new();
compact_edit.insert("type".to_string(), json!("compact_20260112"));
compact_edit
.insert("trigger".to_string(), json!({ "type": "input_tokens", "value": ANTHROPIC_COMPACT_TRIGGER_FLOOR }));
compact_edit.insert("pause_after_compaction".to_string(), json!(true));
if let Some(instructions) = instructions.map(str::trim).filter(|instructions| !instructions.is_empty()) {
compact_edit.insert("instructions".to_string(), json!(instructions));
}
edits.push(Value::Object(compact_edit));
edits
}
pub(crate) async fn compact_history_native_inline(
provider: &dyn LLMProvider,
model: &str,
history: &[Message],
config: &CompactionConfig,
options: &ManualCompactionOptions,
context_budget: Option<usize>,
parent: Option<&CompactionParentContext>,
) -> Result<(Vec<Message>, CompactionMode)> {
let edits =
anthropic_inline_compaction_edits(options.instructions.as_deref(), provider.supports_context_edits(model));
let inline_source = bound_history_for_native_compaction(
history,
options.instructions.as_deref().unwrap_or(""),
compaction_history_budget(provider, model, context_budget),
);
let request = LLMRequest {
messages: Arc::new(inline_source),
model: model.to_string(),
system_prompt: parent.and_then(|parent| parent.system_prompt.clone()),
tools: parent.and_then(|parent| parent.tools.clone()).filter(|tools| !tools.is_empty()),
tool_choice: Some(ToolChoice::none()),
context_management: Some(json!({ "edits": edits })),
max_tokens: options.max_output_tokens,
reasoning_effort: options.reasoning_effort,
verbosity: options.verbosity,
..Default::default()
};
let response = match collect_single_response(provider, request).await {
Ok(response) => response,
Err(error) => {
tracing::warn!(
error = ?error,
"provider-native inline compaction request failed; \
falling back to local summarization"
);
let compacted =
summarize_locally(provider, model, history, config, options, context_budget, parent).await?;
return Ok((compacted, CompactionMode::Local));
}
};
if response.finish_reason == FinishReason::Pause
&& let Some(summary) = response
.compaction
.as_ref()
.map(|summary| summary.trim())
.filter(|summary| !summary.is_empty())
{
let effective_config = context_bounded_compaction_config(provider, model, history, config, context_budget);
let tail_target = route_tail_target_tokens(provider, model, context_budget);
if let Some(detail) = response_compaction_detail(&response) {
return Ok((
build_provider_compacted_history(history, detail, &effective_config, true, tail_target),
CompactionMode::Provider,
));
}
let compacted = bound_compacted_history_to_context(
build_summary_compacted_history(history, summary, &effective_config, true, tail_target),
provider,
model,
context_budget,
);
return Ok((compacted, CompactionMode::Local));
}
let compacted = summarize_locally(provider, model, history, config, options, context_budget, parent).await?;
Ok((compacted, CompactionMode::Local))
}