Skip to main content

magi_code/context/
mod.rs

1#[cfg(test)]
2mod cache;
3mod replay;
4mod replay_cache;
5mod tokens;
6
7#[cfg(test)]
8pub(crate) use cache::conversation_cache_material;
9#[cfg(test)]
10pub(crate) use replay::{
11    ConversationReplay, REPLAY_TOOL_RESULT_OUTPUT_CHAR_LIMIT, build_conversation_replay,
12    build_conversation_replay_with_limits, compact_historical_tool_output,
13};
14pub(crate) use replay::{
15    ConversationReplayLimits, REPLAY_JSONL_MAX_BYTES, REPLAY_JSONL_MAX_LINES,
16    build_conversation_replay_from_events, build_frontend_conversation_from_events,
17};
18pub(crate) use replay_cache::ConversationReplayCache;
19#[cfg(test)]
20pub(crate) use replay_cache::ReplayCacheMetrics;
21#[cfg(test)]
22pub(crate) use tokens::estimate_provider_request_input_tokens;
23#[cfg(test)]
24pub(crate) use tokens::estimate_text_tokens;
25pub use tokens::{
26    ContextBudget, ContextBudgetOverride, estimate_messages_tokens, usage_input_tokens,
27};
28pub(crate) use tokens::{
29    ContextTokenCount, count_supported_provider_conversation_item_tokens,
30    project_provider_conversation_items_tokens, project_provider_request_input_tokens,
31    project_text_tokens, token_encoding_family_for_model,
32};
33
34#[cfg(test)]
35pub(crate) use crate::providers::{ChatMessage, ProviderConversationItem, ProviderToolResult};
36#[cfg(test)]
37use crate::sessions::{
38    Session, SessionDiagnosticOperation, SessionReadDiagnostic, report_session_diagnostic,
39};
40use crate::sessions::{SessionEvent, SessionEventKind};
41use serde_json::Value;
42#[cfg(test)]
43pub(crate) use serde_json::json;
44
45#[cfg(test)]
46const RECENT_EVENTS_CONTEXT_MAX_EVENTS: usize = 10_000;
47#[cfg(test)]
48const RECENT_EVENTS_CONTEXT_MAX_BYTES: usize = 2 * 1024 * 1024;
49// Tail read cap matches retained-byte budget: one bounded read can fill recent-context window.
50#[cfg(test)]
51const RECENT_EVENTS_CONTEXT_MAX_READ_BYTES: usize = RECENT_EVENTS_CONTEXT_MAX_BYTES;
52
53#[cfg(test)]
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub(crate) struct RecentEventsContext {
56    pub text: String,
57    pub diagnostics: Vec<SessionReadDiagnostic>,
58}
59
60#[cfg(test)]
61pub(crate) fn recent_events_context(
62    session: &Session,
63    keep_recent_tokens: usize,
64) -> anyhow::Result<String> {
65    Ok(recent_events_context_report(session, keep_recent_tokens)?.text)
66}
67
68#[cfg(test)]
69pub(crate) fn recent_events_context_report(
70    session: &Session,
71    keep_recent_tokens: usize,
72) -> anyhow::Result<RecentEventsContext> {
73    let mut selected = Vec::new();
74    let mut tokens = 0;
75    let recent = session
76        .read_recent_events_tolerant_tail(
77            RECENT_EVENTS_CONTEXT_MAX_EVENTS,
78            RECENT_EVENTS_CONTEXT_MAX_BYTES,
79            RECENT_EVENTS_CONTEXT_MAX_READ_BYTES,
80        )
81        .inspect_err(|error| {
82            report_session_diagnostic(
83                SessionDiagnosticOperation::RecentContext,
84                session.path(),
85                error,
86            );
87        })?;
88    for event in recent.events.into_iter().rev() {
89        if !is_provider_visible_session_event(&event) {
90            continue;
91        }
92        let line = event_line(&event);
93        let line_tokens = estimate_text_tokens(&line);
94        if !selected.is_empty() && tokens + line_tokens > keep_recent_tokens {
95            break;
96        }
97        tokens += line_tokens;
98        selected.push(line);
99    }
100    selected.reverse();
101    Ok(RecentEventsContext {
102        text: selected.join("\n"),
103        diagnostics: recent.diagnostics,
104    })
105}
106
107#[cfg(test)]
108fn is_provider_visible_session_event(event: &SessionEvent) -> bool {
109    !is_local_only_session_event(event) && event.kind() != Some(SessionEventKind::ReasoningSummary)
110}
111
112pub(crate) fn is_local_only_session_event(event: &SessionEvent) -> bool {
113    event.kind().is_some_and(SessionEventKind::is_local_only)
114}
115
116pub(crate) fn event_line(event: &SessionEvent) -> String {
117    let compact_payload = if event.kind() == Some(SessionEventKind::Compaction) {
118        event
119            .payload
120            .get("summary")
121            .and_then(Value::as_str)
122            .unwrap_or("[invalid compaction summary]")
123            .to_string()
124    } else if let Some(text) = event.payload.get("text").and_then(Value::as_str) {
125        text.to_string()
126    } else if let Some(message) = event.payload.get("message").and_then(Value::as_str) {
127        message.to_string()
128    } else {
129        event.payload.to_string()
130    };
131    format!(
132        "{}: {}",
133        event.event_type,
134        compact_payload.replace('\n', "\\n")
135    )
136}
137
138pub(crate) fn canonical_json(value: &Value) -> String {
139    serde_json::to_string(value).unwrap_or_else(|_| "null".to_string())
140}
141
142#[cfg(test)]
143mod tests;