use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use super::support::ratio_of;
pub const TOKEN_BYTES_DIVISOR: usize = 3;
pub const TOKEN_ITEM_OVERHEAD: usize = 16;
pub const TARGET_BUDGET_RATIO: f64 = 0.80;
pub const AUTO_COMPACTION_RATIO: f64 = 0.92;
pub const PROVIDER_OVERHEAD_TOKENS: u64 = 1_024;
pub const FALLBACK_CONTEXT_WINDOW: u64 = 32_768;
pub const FALLBACK_MAX_COMPLETION_TOKENS: u64 = 4_096;
pub const FALLBACK_RECOMMENDED_COMPLETION_TOKENS: u64 = 4_096;
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContextItemKind {
Harness,
ProjectInstruction,
PinnedFile,
Skill,
Transcript,
Summary,
ToolArchive,
}
impl ContextItemKind {
pub fn label(&self) -> &'static str {
match self {
ContextItemKind::Harness => "harness",
ContextItemKind::ProjectInstruction => "project_instruction",
ContextItemKind::PinnedFile => "pinned_file",
ContextItemKind::Skill => "skill",
ContextItemKind::Transcript => "transcript",
ContextItemKind::Summary => "summary",
ContextItemKind::ToolArchive => "tool_archive",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContextVisibility {
Visible,
Pinned,
SummaryOnly,
Archived,
Candidate,
Dropped,
Blocked,
}
impl ContextVisibility {
pub fn is_rendered(&self) -> bool {
matches!(self, ContextVisibility::Visible | ContextVisibility::Pinned)
}
pub fn label(&self) -> &'static str {
match self {
ContextVisibility::Visible => "visible",
ContextVisibility::Pinned => "pinned",
ContextVisibility::SummaryOnly => "summary_only",
ContextVisibility::Archived => "archived",
ContextVisibility::Candidate => "candidate",
ContextVisibility::Dropped => "dropped",
ContextVisibility::Blocked => "blocked",
}
}
pub fn reason(&self, base: &str) -> String {
match self {
ContextVisibility::Visible => base.to_string(),
ContextVisibility::Pinned => format!("{base}: pinned"),
ContextVisibility::SummaryOnly => format!("{base}: summary-only"),
ContextVisibility::Archived => format!("{base}: archived"),
ContextVisibility::Candidate => format!("{base}: candidate (not selected)"),
ContextVisibility::Dropped => format!("{base}: dropped"),
ContextVisibility::Blocked => format!("{base}: blocked (oversized)"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModelLimitSource {
Fallback = 0,
Static = 1,
LiveMetadata = 2,
UserOverride = 3,
}
impl ModelLimitSource {
pub fn label(&self) -> &'static str {
match self {
ModelLimitSource::Fallback => "fallback",
ModelLimitSource::Static => "static",
ModelLimitSource::LiveMetadata => "live-metadata",
ModelLimitSource::UserOverride => "user-override",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModelLimitConfidence {
Conservative,
ProviderReported,
Exact,
UserSupplied,
}
impl ModelLimitConfidence {
pub fn label(&self) -> &'static str {
match self {
ModelLimitConfidence::Conservative => "conservative",
ModelLimitConfidence::ProviderReported => "provider-reported",
ModelLimitConfidence::Exact => "exact",
ModelLimitConfidence::UserSupplied => "user-supplied",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DiagnosticSeverity {
Info,
Warning,
Error,
}
impl DiagnosticSeverity {
pub fn label(&self) -> &'static str {
match self {
DiagnosticSeverity::Info => "info",
DiagnosticSeverity::Warning => "warning",
DiagnosticSeverity::Error => "error",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ModelContextLimits {
pub provider: String,
pub model: String,
pub context_window: u64,
pub max_completion_tokens: u64,
pub recommended_completion_tokens: u64,
pub source: ModelLimitSource,
pub confidence: ModelLimitConfidence,
}
impl ModelContextLimits {
pub fn available_input_budget(&self) -> u64 {
let reserved_completion = self.recommended_completion_tokens.max(self.max_completion_tokens);
self.context_window
.saturating_sub(reserved_completion)
.saturating_sub(PROVIDER_OVERHEAD_TOKENS)
}
pub fn target_budget(&self) -> u64 {
ratio_of(self.available_input_budget(), TARGET_BUDGET_RATIO)
}
pub fn auto_compaction_threshold(&self) -> u64 {
ratio_of(self.available_input_budget(), AUTO_COMPACTION_RATIO)
}
pub fn resolve(
provider: &str, model: &str, override_entry: Option<ModelLimitOverride>, live: Option<&LiveModelMetadata>,
) -> (ModelContextLimits, Vec<ContextDiagnostic>) {
let mut diagnostics = Vec::new();
if let Some(entry) = override_entry {
match entry.validate() {
Ok(()) => {
return (
ModelContextLimits {
provider: provider.to_string(),
model: model.to_string(),
context_window: entry.context_window,
max_completion_tokens: entry.max_completion_tokens,
recommended_completion_tokens: entry.recommended_completion_tokens,
source: ModelLimitSource::UserOverride,
confidence: ModelLimitConfidence::UserSupplied,
},
diagnostics,
);
}
Err(reason) => {
diagnostics.push(ContextDiagnostic::invalid_model_override(provider, model, &reason));
}
}
}
if let Some(live) = live
&& let Some(limits) = live.to_limits(provider, model)
{
return (limits, diagnostics);
}
if let Some(static_limits) = static_provider_limits(provider, model) {
return (static_limits, diagnostics);
}
diagnostics.push(ContextDiagnostic::fallback_model_limits(provider, model));
(
ModelContextLimits {
provider: provider.to_string(),
model: model.to_string(),
context_window: FALLBACK_CONTEXT_WINDOW,
max_completion_tokens: FALLBACK_MAX_COMPLETION_TOKENS,
recommended_completion_tokens: FALLBACK_RECOMMENDED_COMPLETION_TOKENS,
source: ModelLimitSource::Fallback,
confidence: ModelLimitConfidence::Conservative,
},
diagnostics,
)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LiveModelMetadata {
pub context_window: Option<u64>,
pub max_completion_tokens: Option<u64>,
pub recommended_completion_tokens: Option<u64>,
}
impl LiveModelMetadata {
pub fn new(context_window: u64, max_completion_tokens: u64, recommended_completion_tokens: u64) -> Self {
Self {
context_window: Some(context_window),
max_completion_tokens: Some(max_completion_tokens),
recommended_completion_tokens: Some(recommended_completion_tokens),
}
}
fn to_limits(&self, provider: &str, model: &str) -> Option<ModelContextLimits> {
let context_window = self.context_window?;
let max_completion_tokens = self.max_completion_tokens?;
let recommended = self
.recommended_completion_tokens
.unwrap_or_else(|| max_completion_tokens.min(context_window / 2));
if context_window == 0 || max_completion_tokens == 0 {
return None;
}
Some(ModelContextLimits {
provider: provider.to_string(),
model: model.to_string(),
context_window,
max_completion_tokens,
recommended_completion_tokens: recommended,
source: ModelLimitSource::LiveMetadata,
confidence: ModelLimitConfidence::Exact,
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct ModelLimitOverride {
pub context_window: u64,
pub max_completion_tokens: u64,
pub recommended_completion_tokens: u64,
}
impl ModelLimitOverride {
pub fn validate(&self) -> Result<(), String> {
if self.context_window == 0 {
return Err("context_window must be a positive integer".to_string());
}
if self.max_completion_tokens == 0 {
return Err("max_completion_tokens must be a positive integer".to_string());
}
if self.recommended_completion_tokens == 0 {
return Err("recommended_completion_tokens must be a positive integer".to_string());
}
if self.recommended_completion_tokens > self.max_completion_tokens {
return Err(format!(
"recommended_completion_tokens ({}) must not exceed max_completion_tokens ({})",
self.recommended_completion_tokens, self.max_completion_tokens
));
}
if self.max_completion_tokens >= self.context_window {
return Err(format!(
"max_completion_tokens ({}) must be less than context_window ({})",
self.max_completion_tokens, self.context_window
));
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContextItem {
pub id: String,
pub kind: ContextItemKind,
pub label: String,
pub source_path: Option<PathBuf>,
pub scope: String,
pub content_hash: Option<u64>,
pub artifact_handle: Option<String>,
pub byte_count: usize,
pub content: Option<String>,
pub token_estimate: usize,
pub visibility: ContextVisibility,
pub reason_code: String,
pub reason: String,
}
impl ContextItem {
pub fn summary(&self) -> String {
format!(
"{} {} {} {} est. tokens [{}]",
self.id,
self.kind.label(),
self.visibility.label(),
self.token_estimate,
self.label,
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContextBudget {
pub limits: ModelContextLimits,
pub available_input: u64,
pub target: u64,
pub auto_compaction_threshold: u64,
pub used: u64,
}
impl ContextBudget {
pub fn from_limits(limits: ModelContextLimits, items: &[ContextItem]) -> Self {
let available_input = limits.available_input_budget();
let target = limits.target_budget();
let auto_compaction_threshold = limits.auto_compaction_threshold();
let used = items
.iter()
.filter(|item| item.visibility.is_rendered())
.map(|item| item.token_estimate as u64)
.sum();
ContextBudget { limits, available_input, target, auto_compaction_threshold, used }
}
pub fn exceeds_target(&self) -> bool {
self.used > self.target
}
pub fn exceeds_auto_compaction(&self) -> bool {
self.used > self.auto_compaction_threshold
}
pub fn remaining_to_target(&self) -> u64 {
self.target.saturating_sub(self.used)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContextDiagnostic {
pub severity: DiagnosticSeverity,
pub code: String,
pub message: String,
}
impl ContextDiagnostic {
pub fn fallback_model_limits(provider: &str, model: &str) -> Self {
ContextDiagnostic {
severity: DiagnosticSeverity::Warning,
code: "fallback_model_limits".to_string(),
message: format!("no model metadata for {provider}/{model}; using conservative fallback context window"),
}
}
pub fn invalid_model_override(provider: &str, model: &str, reason: &str) -> Self {
ContextDiagnostic {
severity: DiagnosticSeverity::Error,
code: "invalid_model_override".to_string(),
message: format!("model_limits override for {provider}/{model} rejected: {reason}"),
}
}
pub fn summary(&self) -> String {
format!("{} {} {}", self.severity.label(), self.code, self.message)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContextLedger {
pub items: Vec<ContextItem>,
pub budget: ContextBudget,
pub diagnostics: Vec<ContextDiagnostic>,
}
impl ContextLedger {
pub fn rendered(&self) -> Vec<&ContextItem> {
self.items.iter().filter(|item| item.visibility.is_rendered()).collect()
}
pub fn counts(&self) -> ContextCounts {
let mut counts = ContextCounts::default();
for item in &self.items {
match item.visibility {
ContextVisibility::Visible => counts.visible += 1,
ContextVisibility::Pinned => counts.pinned += 1,
ContextVisibility::SummaryOnly => counts.summary_only += 1,
ContextVisibility::Archived => counts.archived += 1,
ContextVisibility::Candidate => counts.candidate += 1,
ContextVisibility::Dropped => counts.dropped += 1,
ContextVisibility::Blocked => counts.blocked += 1,
}
}
counts
}
pub fn find(&self, id: &str) -> Option<&ContextItem> {
self.items.iter().find(|item| item.id == id)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ContextCounts {
pub visible: usize,
pub pinned: usize,
pub summary_only: usize,
pub archived: usize,
pub candidate: usize,
pub dropped: usize,
pub blocked: usize,
}
pub fn estimate_tokens(bytes: usize) -> usize {
bytes.div_ceil(TOKEN_BYTES_DIVISOR) + TOKEN_ITEM_OVERHEAD
}
pub fn item_id_for_path(kind: &ContextItemKind, path: &Path) -> String {
let mut hasher = DefaultHasher::new();
kind.label().hash(&mut hasher);
path.hash(&mut hasher);
format!("ctx_{}_{:016x}", kind.label(), hasher.finish())
}
pub fn item_id_for_session_range(kind: &ContextItemKind, session_id: &str, start: u64, end: u64) -> String {
let mut hasher = DefaultHasher::new();
kind.label().hash(&mut hasher);
session_id.hash(&mut hasher);
start.hash(&mut hasher);
end.hash(&mut hasher);
format!("ctx_{}_{:016x}", kind.label(), hasher.finish())
}
pub fn render_ledger_summary(ledger: &ContextLedger) -> String {
let counts = ledger.counts();
let mut parts = vec![format!("{} visible", counts.visible)];
if counts.pinned > 0 {
parts.push(format!("{} pinned", counts.pinned));
}
if counts.archived > 0 {
parts.push(format!("{} archived", counts.archived));
}
if counts.summary_only > 0 {
parts.push(format!("{} summary", counts.summary_only));
}
if counts.blocked > 0 {
parts.push(format!("{} blocked", counts.blocked));
}
parts.push(format!("{} est. tokens", compact_token_count(ledger.budget.used)));
format!("context {}", parts.join(" · "))
}
pub fn render_model_dashboard(ledger: &ContextLedger) -> String {
let mut out = String::new();
out.push_str("<context_dashboard>\n");
out.push_str(" <budget>\n");
element(
&mut out,
4,
"available_input",
&compact_token_count(ledger.budget.available_input),
);
element(&mut out, 4, "target", &compact_token_count(ledger.budget.target));
element(
&mut out,
4,
"auto_compaction_threshold",
&compact_token_count(ledger.budget.auto_compaction_threshold),
);
element(&mut out, 4, "used", &compact_token_count(ledger.budget.used));
element(
&mut out,
4,
"exceeds_target",
&ledger.budget.exceeds_target().to_string(),
);
element(
&mut out,
4,
"exceeds_auto_compaction",
&ledger.budget.exceeds_auto_compaction().to_string(),
);
element(&mut out, 4, "limit_source", ledger.budget.limits.source.label());
element(&mut out, 4, "limit_confidence", ledger.budget.limits.confidence.label());
out.push_str(" </budget>\n");
out.push_str(" <items>\n");
for item in &ledger.items {
out.push_str(" <item>\n");
element(&mut out, 6, "id", &item.id);
element(&mut out, 6, "kind", item.kind.label());
element(&mut out, 6, "visibility", item.visibility.label());
element(&mut out, 6, "tokens", &item.token_estimate.to_string());
element(&mut out, 6, "label", &item.label);
if let Some(handle) = &item.artifact_handle {
element(&mut out, 6, "recovery_handle", handle);
}
out.push_str(" </item>\n");
}
out.push_str(" </items>\n");
if !ledger.diagnostics.is_empty() {
out.push_str(" <diagnostics>\n");
for diagnostic in &ledger.diagnostics {
element(&mut out, 4, "diagnostic", &diagnostic.summary());
}
out.push_str(" </diagnostics>\n");
}
out.push_str("</context_dashboard>");
out
}
fn static_provider_limits(provider: &str, model: &str) -> Option<ModelContextLimits> {
let (context_window, max_completion, recommended) = match provider {
"umans" => (200_000, 32_768, 8_192),
"opencode-go" => (200_000, 32_768, 8_192),
"opencode-zen" => (200_000, 32_768, 8_192),
"chatgpt-codex" => (200_000, 32_768, 8_192),
_ => return None,
};
Some(ModelContextLimits {
provider: provider.to_string(),
model: model.to_string(),
context_window,
max_completion_tokens: max_completion,
recommended_completion_tokens: recommended,
source: ModelLimitSource::Static,
confidence: ModelLimitConfidence::ProviderReported,
})
}
fn compact_token_count(tokens: u64) -> String {
const K: u64 = 1_000;
const M: u64 = K * 1_000;
if tokens >= M && tokens.is_multiple_of(M) {
format!("{}M", tokens / M)
} else if tokens >= K && tokens.is_multiple_of(K) {
format!("{}k", tokens / K)
} else {
tokens.to_string()
}
}
fn element(out: &mut String, indent: usize, name: &str, value: &str) {
let pad = " ".repeat(indent);
out.push_str(&format!("{pad}<{name}>{value}</{name}>\n"));
}