pub mod clarity;
pub mod depth;
pub mod focus;
pub mod harmonic;
pub mod mode;
pub mod narrow;
pub mod refract;
pub mod wide;
use forge::budget::SignalBudget;
#[derive(Debug, Clone)]
pub struct LensContext {
pub task_hint: Option<String>,
pub budget: SignalBudget,
pub ecosystem_hint: Option<String>,
pub exit_code: i32,
pub subcmd: String,
}
impl LensContext {
pub fn new(limit: usize) -> Self {
Self {
task_hint: None,
budget: SignalBudget::new(limit),
ecosystem_hint: None,
exit_code: 0,
subcmd: String::new(),
}
}
pub fn with_task(mut self, task: impl Into<String>) -> Self {
self.task_hint = Some(task.into());
self
}
pub fn with_exit_code(mut self, code: i32) -> Self {
self.exit_code = code;
self
}
pub fn with_subcmd(mut self, subcmd: impl Into<String>) -> Self {
self.subcmd = subcmd.into();
self
}
}
#[derive(Debug, Clone)]
pub struct LensOutput {
pub content: String,
pub tokens_before: usize,
pub tokens_after: usize,
pub applied: Vec<String>,
}
impl LensOutput {
pub fn passthrough(content: impl Into<String>) -> Self {
let content = content.into();
let t = forge::budget::estimator::TokenEstimator::count_nonblocking(&content);
Self {
content,
tokens_before: t,
tokens_after: t,
applied: vec![],
}
}
pub fn savings_pct(&self) -> f64 {
forge::budget::estimator::TokenEstimator::savings_pct(self.tokens_before, self.tokens_after)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LensId {
Clarity,
Depth,
Focus,
Narrow,
Wide,
Refract,
Harmonic,
}
impl LensId {
pub fn name(&self) -> &'static str {
match self {
Self::Clarity => "clarity",
Self::Depth => "depth",
Self::Focus => "focus",
Self::Narrow => "narrow",
Self::Wide => "wide",
Self::Refract => "refract",
Self::Harmonic => "harmonic",
}
}
}
pub trait Lens: Send + Sync {
fn id(&self) -> LensId;
fn apply(&self, input: &str, ctx: &LensContext) -> LensOutput;
fn token_budget_hint(&self) -> Option<usize> {
None
}
}
pub fn apply_stack(lenses: &[Box<dyn Lens>], input: &str, ctx: &LensContext) -> LensOutput {
if lenses.is_empty() {
return LensOutput::passthrough(input);
}
let tokens_before = forge::budget::estimator::TokenEstimator::count_nonblocking(input);
let mut current = input.to_string();
let mut applied = Vec::new();
for lens in lenses {
let out = lens.apply(¤t, ctx);
applied.push(lens.id().name().to_string());
current = out.content;
}
let tokens_after = forge::budget::estimator::TokenEstimator::count_nonblocking(¤t);
if tokens_after >= tokens_before {
return LensOutput {
content: input.to_string(),
tokens_before,
tokens_after: tokens_before,
applied,
};
}
LensOutput {
content: current,
tokens_before,
tokens_after,
applied,
}
}
#[cfg(test)]
mod tests {
use super::*;
struct ExpandingLens;
impl Lens for ExpandingLens {
fn id(&self) -> LensId {
LensId::Clarity
}
fn apply(&self, input: &str, _ctx: &LensContext) -> LensOutput {
LensOutput::passthrough(format!("{input} {input} {input} extra padding words here"))
}
}
#[test]
fn apply_stack_never_expands_output() {
let lenses: Vec<Box<dyn Lens>> = vec![Box::new(ExpandingLens)];
let ctx = LensContext::new(2000);
let out = apply_stack(&lenses, "short input", &ctx);
assert!(
out.tokens_after <= out.tokens_before,
"compressed output must never exceed the original"
);
assert_eq!(
out.content, "short input",
"must fall back to the raw input when the lens stack would expand it"
);
}
}