| 1 | //! Single portable formatter for the authoritative context source map and prompt schema. |
| 2 | //! Pressure and route-source verification are already classified by the host |
| 3 | //! and skipped by serde, so JSON retains its original public schema. |
| 4 | |
| 5 | use codewhale_command_contract::facets::{DebugPromptContext, DebugPromptSourceMap}; |
| 6 | use std::fmt::Write as _; |
| 7 | |
| 8 | pub fn format_context_report(report: &DebugPromptSourceMap) -> String { |
| 9 | let mut out = String::new(); |
| 10 | let _ = writeln!(out, "Context Source Map"); |
| 11 | let _ = writeln!( |
| 12 | out, |
| 13 | "Estimated active context: {} tokens", |
| 14 | report.active_context_estimated_tokens |
| 15 | ); |
| 16 | write_overflow_guard_line(&mut out, report); |
| 17 | match (report.context_window_tokens, report.budget_used_percent) { |
| 18 | (Some(window), Some(percent)) => { |
| 19 | let source = report |
| 20 | .context_window_source |
| 21 | .as_deref() |
| 22 | .unwrap_or("fallback"); |
| 23 | // An unverified rung is a guess about the window printed on this |
| 24 | // same line; it must not claim a fixed 128K default the capability |
| 25 | // matrix may not hold. A label from no known rung is no evidence |
| 26 | // either, so it reads the same way. |
| 27 | let source_label = if report.context_window_verified { |
| 28 | source.to_string() |
| 29 | } else { |
| 30 | format!( |
| 31 | "{source} (unverified — nothing describes this model, so this window is a guess)" |
| 32 | ) |
| 33 | }; |
| 34 | let _ = writeln!( |
| 35 | out, |
| 36 | "Window: {window} tokens ({percent:.1}% used, {}; source: {})", |
| 37 | report.pressure_label.as_str(), |
| 38 | source_label |
| 39 | ); |
| 40 | } |
| 41 | _ => { |
| 42 | let _ = writeln!(out, "Window: unknown"); |
| 43 | } |
| 44 | } |
| 45 | // #5134: the source label says where the window came from but not how to |
| 46 | // change it. Name the key here so the report answers the question it |
| 47 | // provokes. |
| 48 | let _ = writeln!( |
| 49 | out, |
| 50 | "Change the window: set `context_window` on the active `[providers.<name>]` table in config.toml (docs/CONFIGURATION.md, \"Context length\")." |
| 51 | ); |
| 52 | let _ = writeln!( |
| 53 | out, |
| 54 | "Source-entry total: {} tokens", |
| 55 | report.total_estimated_tokens |
| 56 | ); |
| 57 | let _ = writeln!( |
| 58 | out, |
| 59 | "Manage standing law: /constitution (status/preview), /constitution repo (repo-local law), /setup report (readiness)." |
| 60 | ); |
| 61 | let _ = writeln!(out); |
| 62 | let _ = writeln!(out, "Sources:"); |
| 63 | for entry in &report.entries { |
| 64 | let path = entry |
| 65 | .source_path |
| 66 | .as_deref() |
| 67 | .map(|path| format!(" [{path}]")) |
| 68 | .unwrap_or_default(); |
| 69 | let tier = entry |
| 70 | .authority_tier |
| 71 | .map(|tier| format!(", tier {tier}")) |
| 72 | .unwrap_or_default(); |
| 73 | let omitted = entry |
| 74 | .truncation_reason |
| 75 | .as_deref() |
| 76 | .map(|reason| format!(" - {reason}")) |
| 77 | .unwrap_or_default(); |
| 78 | let _ = writeln!( |
| 79 | out, |
| 80 | "- {:?}: {}{} - {} tokens ({:?}{}){}", |
| 81 | entry.source_kind, |
| 82 | entry.label, |
| 83 | path, |
| 84 | entry.estimated_tokens, |
| 85 | entry.counting_confidence, |
| 86 | tier, |
| 87 | omitted |
| 88 | ); |
| 89 | } |
| 90 | let _ = writeln!(out); |
| 91 | let _ = write!(out, "{}", report.note); |
| 92 | out |
| 93 | } |
| 94 | |
| 95 | /// Secondary line: the inflated overflow-guard figure, labeled so nobody |
| 96 | /// reads it as the pressure the meter and the compaction gate act on. |
| 97 | fn write_overflow_guard_line(out: &mut String, report: &DebugPromptSourceMap) { |
| 98 | if let Some(guard) = report.overflow_guard_estimated_tokens { |
| 99 | let _ = writeln!( |
| 100 | out, |
| 101 | "Overflow guard: {guard} tokens (conservative 1.5x estimate that blocks oversized requests; not the pressure the meter and auto-compaction read)" |
| 102 | ); |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | pub fn format_context_summary(report: &DebugPromptSourceMap) -> String { |
| 107 | let mut entries = report.entries.clone(); |
| 108 | entries.sort_by_key(|entry| std::cmp::Reverse(entry.estimated_tokens)); |
| 109 | let top = entries |
| 110 | .iter() |
| 111 | .take(5) |
| 112 | .map(|entry| format!("{} ({})", entry.label, entry.estimated_tokens)) |
| 113 | .collect::<Vec<_>>() |
| 114 | .join(", "); |
| 115 | |
| 116 | let mut out = String::new(); |
| 117 | let _ = writeln!(out, "Context Summary"); |
| 118 | let _ = writeln!(out, "Pressure: {}", report.pressure_label.as_str()); |
| 119 | let _ = writeln!( |
| 120 | out, |
| 121 | "Estimated active context: {} tokens", |
| 122 | report.active_context_estimated_tokens |
| 123 | ); |
| 124 | if let Some(percent) = report.budget_used_percent { |
| 125 | let _ = writeln!(out, "Budget used: {percent:.1}%"); |
| 126 | } |
| 127 | write_overflow_guard_line(&mut out, report); |
| 128 | let _ = write!(out, "Top sources: {top}"); |
| 129 | out |
| 130 | } |
| 131 | |
| 132 | pub fn context_report_json(report: &DebugPromptSourceMap) -> String { |
| 133 | serde_json::to_string_pretty(report).unwrap_or_else(|err| { |
| 134 | format!("{{\"error\":\"failed to serialize context report: {err}\"}}") |
| 135 | }) |
| 136 | } |
| 137 | |
| 138 | #[must_use] |
| 139 | pub fn prompt_context_json(context: &DebugPromptContext) -> String { |
| 140 | serde_json::to_string_pretty(context).unwrap_or_else(|error| { |
| 141 | format!(r#"{{"error":"failed to serialize prompt context: {error}"}}"#) |
| 142 | }) |
| 143 | } |
| 144 |