use serde::{Deserialize, Serialize};
use super::{ApiSurfaceConfig, BackendKind, Config, SemanticConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ContextManager {
#[default]
Standard,
AppendOnly,
Progressive,
Distributed,
}
impl ContextManager {
pub fn from_keyword(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"standard" => Some(Self::Standard),
"append-only" | "append_only" | "appendonly" | "append" => Some(Self::AppendOnly),
"progressive" => Some(Self::Progressive),
"distributed" => Some(Self::Distributed),
_ => None,
}
}
pub fn keyword(self) -> &'static str {
match self {
Self::Standard => "standard",
Self::AppendOnly => "append-only",
Self::Progressive => "progressive",
Self::Distributed => "distributed",
}
}
pub fn available(self) -> bool {
matches!(self, Self::Standard | Self::AppendOnly)
}
pub fn rewrites_history(self) -> bool {
!matches!(self, Self::AppendOnly)
}
pub fn base_features(self) -> ContextFeatureSet {
ContextFeatureSet::default()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum CompactionTriggerPolicy {
#[default]
HeadroomAware,
MessageCount,
}
impl CompactionTriggerPolicy {
pub fn from_keyword(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"headroom_aware" => Some(Self::HeadroomAware),
"message_count" => Some(Self::MessageCount),
_ => None,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::HeadroomAware => "headroom_aware",
Self::MessageCount => "message_count",
}
}
pub const fn keyword(self) -> &'static str {
self.as_str()
}
}
#[must_use]
pub fn session_compaction_trigger_policy(
report: &mut dyn FnMut(crate::tty::Notice<'static>),
) -> CompactionTriggerPolicy {
if let Some(policy) = std::env::var("NEWT_COMPACTION_TRIGGER")
.ok()
.as_deref()
.and_then(CompactionTriggerPolicy::from_keyword)
{
return policy;
}
Config::resolve_unpublished(report)
.ok()
.and_then(|c| c.context)
.map(|c| c.compaction_trigger_policy)
.unwrap_or_default()
}
#[must_use]
pub fn compaction_trigger_is_session_pinned() -> bool {
std::env::var("NEWT_COMPACTION_TRIGGER")
.ok()
.as_deref()
.and_then(CompactionTriggerPolicy::from_keyword)
.is_some()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextFeature {
ToolOffload,
Scratchpad,
Semantic,
Provenance,
Experiential,
Scheduled,
}
impl ContextFeature {
pub const ALL: [Self; 6] = [
Self::ToolOffload,
Self::Scratchpad,
Self::Semantic,
Self::Provenance,
Self::Experiential,
Self::Scheduled,
];
pub fn from_keyword(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().replace('-', "_").as_str() {
"tool_offload" | "tooloffload" | "offload" => Some(Self::ToolOffload),
"scratchpad" | "state" => Some(Self::Scratchpad),
"semantic" | "retrieval" => Some(Self::Semantic),
"provenance" | "handles" => Some(Self::Provenance),
"experiential" | "experience" => Some(Self::Experiential),
"scheduled" | "compiled" => Some(Self::Scheduled),
_ => None,
}
}
pub fn keyword(self) -> &'static str {
match self {
Self::ToolOffload => "tool_offload",
Self::Scratchpad => "scratchpad",
Self::Semantic => "semantic",
Self::Provenance => "provenance",
Self::Experiential => "experiential",
Self::Scheduled => "scheduled",
}
}
pub fn available(self) -> bool {
match self {
Self::ToolOffload
| Self::Scratchpad
| Self::Semantic
| Self::Experiential
| Self::Scheduled => true,
Self::Provenance => false,
}
}
pub fn issue(self) -> u32 {
match self {
Self::ToolOffload | Self::Provenance => 584,
Self::Scratchpad => 583,
Self::Semantic => 582,
Self::Experiential => 585,
Self::Scheduled => 586,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ContextFeatureSet {
pub tool_offload: bool,
pub scratchpad: bool,
pub semantic: bool,
pub provenance: bool,
pub experiential: bool,
pub scheduled: bool,
}
impl ContextFeatureSet {
pub fn get(&self, f: ContextFeature) -> bool {
match f {
ContextFeature::ToolOffload => self.tool_offload,
ContextFeature::Scratchpad => self.scratchpad,
ContextFeature::Semantic => self.semantic,
ContextFeature::Provenance => self.provenance,
ContextFeature::Experiential => self.experiential,
ContextFeature::Scheduled => self.scheduled,
}
}
pub fn set(&mut self, f: ContextFeature, on: bool) {
match f {
ContextFeature::ToolOffload => self.tool_offload = on,
ContextFeature::Scratchpad => self.scratchpad = on,
ContextFeature::Semantic => self.semantic = on,
ContextFeature::Provenance => self.provenance = on,
ContextFeature::Experiential => self.experiential = on,
ContextFeature::Scheduled => self.scheduled = on,
}
}
pub fn enabled(self) -> Vec<ContextFeature> {
ContextFeature::ALL
.into_iter()
.filter(|&f| self.get(f))
.collect()
}
pub fn base_for(manager: ContextManager, _kind: BackendKind) -> Self {
let mut base = manager.base_features();
for f in ContextFeature::ALL {
if f != ContextFeature::Provenance && f.available() {
base.set(f, true);
}
}
base
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContextFeatures {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_offload: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scratchpad: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub semantic: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub experiential: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scheduled: Option<bool>,
}
impl ContextFeatures {
pub fn get(&self, f: ContextFeature) -> Option<bool> {
match f {
ContextFeature::ToolOffload => self.tool_offload,
ContextFeature::Scratchpad => self.scratchpad,
ContextFeature::Semantic => self.semantic,
ContextFeature::Provenance => self.provenance,
ContextFeature::Experiential => self.experiential,
ContextFeature::Scheduled => self.scheduled,
}
}
pub fn set(&mut self, f: ContextFeature, v: Option<bool>) {
match f {
ContextFeature::ToolOffload => self.tool_offload = v,
ContextFeature::Scratchpad => self.scratchpad = v,
ContextFeature::Semantic => self.semantic = v,
ContextFeature::Provenance => self.provenance = v,
ContextFeature::Experiential => self.experiential = v,
ContextFeature::Scheduled => self.scheduled = v,
}
}
pub fn apply_to(&self, mut base: ContextFeatureSet) -> ContextFeatureSet {
for f in ContextFeature::ALL {
if let Some(v) = self.get(f) {
base.set(f, v);
}
}
base
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContextConfig {
#[serde(default)]
pub manager: ContextManager,
#[serde(default)]
pub compaction_trigger_policy: CompactionTriggerPolicy,
#[serde(default)]
pub features: ContextFeatures,
#[serde(default)]
pub semantic: SemanticConfig,
#[serde(default)]
pub estimation: crate::tokens::TokenEstimation,
#[serde(default = "default_summary_input_cap_floor_chars")]
pub summary_input_cap_floor_chars: usize,
#[serde(default)]
pub api_surface: ApiSurfaceConfig,
#[serde(
default = "default_input_ceiling_pct",
deserialize_with = "deserialize_input_ceiling_pct"
)]
pub input_ceiling_pct: u32,
#[serde(default = "default_low_budget_pct")]
pub low_budget_pct: usize,
}
fn default_summary_input_cap_floor_chars() -> usize {
8_192
}
pub(super) fn default_input_ceiling_pct() -> u32 {
80
}
#[must_use]
pub fn normalize_input_ceiling_pct(value: u32) -> u32 {
if (1..=99).contains(&value) {
value
} else {
default_input_ceiling_pct()
}
}
#[must_use]
pub fn input_percentage_ceiling(context_window: u32, input_ceiling_pct: u32) -> u32 {
let pct = normalize_input_ceiling_pct(input_ceiling_pct);
(u64::from(context_window) * u64::from(pct) / 100) as u32
}
fn deserialize_input_ceiling_pct<'de, D>(deserializer: D) -> std::result::Result<u32, D::Error>
where
D: serde::Deserializer<'de>,
{
u32::deserialize(deserializer).map(normalize_input_ceiling_pct)
}
fn default_low_budget_pct() -> usize {
15
}
impl Default for ContextConfig {
fn default() -> Self {
Self {
manager: ContextManager::default(),
compaction_trigger_policy: CompactionTriggerPolicy::default(),
features: ContextFeatures::default(),
semantic: SemanticConfig::default(),
estimation: crate::tokens::TokenEstimation::default(),
summary_input_cap_floor_chars: default_summary_input_cap_floor_chars(),
api_surface: ApiSurfaceConfig::default(),
input_ceiling_pct: default_input_ceiling_pct(),
low_budget_pct: default_low_budget_pct(),
}
}
}