velesdb_memory/context/model_windows.rs
1//! A static, committed model → context-window table (V2a-3 quick win).
2//!
3//! No notion of "which model am I compiling for, and how big is its
4//! context window" exists anywhere in the crate — an agent calling
5//! `compile_context` has to guess `token_budget` from scratch. This module
6//! gives it a documented starting point instead: a small table, compiled
7//! into the binary, dated "as of" so staleness is visible at a glance.
8//! **Never a network call** — extending or refreshing the table is a code
9//! change (a new release), not a runtime lookup.
10
11use schemars::JsonSchema;
12use serde::Serialize;
13
14/// The as-of date of [`MODEL_WINDOWS`] — bump this (and add a CHANGELOG
15/// entry) whenever the table gains or corrects an entry.
16const MODEL_WINDOWS_AS_OF: &str = "2026-07";
17
18/// `(model name, context window in tokens)`. Matched case-insensitively on
19/// the whole string — no fuzzy or prefix matching, a caller must name their
20/// model precisely (provider docs are the source of truth; this table is a
21/// convenience snapshot, not an oracle). Extend as new models ship; an
22/// entry that goes stale (a provider changes a window) is corrected here in
23/// a normal code change, never fetched.
24const MODEL_WINDOWS: &[(&str, u64)] = &[
25 ("claude-opus-4-5", 200_000),
26 ("claude-sonnet-4-5", 200_000),
27 ("claude-haiku-4-5", 200_000),
28 ("claude-sonnet-4", 200_000),
29 ("claude-3-7-sonnet", 200_000),
30 ("gpt-5", 400_000),
31 ("gpt-5-mini", 400_000),
32 ("gpt-4.1", 1_000_000),
33 ("gpt-4o", 128_000),
34 ("o3", 200_000),
35 ("gemini-2.5-pro", 1_000_000),
36 ("gemini-2.0-flash", 1_000_000),
37];
38
39/// The context window of `model`, in tokens — `None` when it is not in the
40/// static table (never a guess).
41#[must_use]
42pub fn model_window(model: &str) -> Option<u64> {
43 MODEL_WINDOWS
44 .iter()
45 .find(|(name, _)| name.eq_ignore_ascii_case(model))
46 .map(|(_, window)| *window)
47}
48
49/// Output of the `suggest_budget` MCP tool: a starting `token_budget` for a
50/// target model, derived from the static table alone.
51#[derive(Debug, Clone, Serialize, JsonSchema)]
52pub struct SuggestedBudget {
53 /// The model's context window, in tokens — `None` when `target_model`
54 /// is not in the static table.
55 pub window: Option<u64>,
56 /// `window - reserve_tokens` (saturating at 0) — `None` when `window`
57 /// is `None`. Mirrors the role of
58 /// [`CompilePolicy::response_reserve_tokens`](super::CompilePolicy::response_reserve_tokens)
59 /// on `compile_context`'s own budget.
60 pub suggested_budget: Option<u64>,
61 /// Always the static table's provenance, dated — never "measured" or
62 /// "fetched": this is a committed snapshot, not a live lookup.
63 pub source: String,
64}
65
66/// Look up `target_model`'s window and suggest a budget that reserves
67/// `reserve_tokens` for the response. Never touches the network. An unknown
68/// model reports both fields `None` — an honest "I don't know", never a
69/// guessed default.
70#[must_use]
71pub fn suggest_token_budget(target_model: &str, reserve_tokens: u64) -> SuggestedBudget {
72 let window = model_window(target_model);
73 SuggestedBudget {
74 window,
75 suggested_budget: window.map(|w| w.saturating_sub(reserve_tokens)),
76 source: format!("static table as of {MODEL_WINDOWS_AS_OF}"),
77 }
78}
79
80#[cfg(test)]
81#[path = "model_windows_tests.rs"]
82mod tests;