fix: anchor compaction to provider input usage

This commit is contained in:
leookun
2026-08-26 01:49:12 +08:00
parent 081e1f50e2
commit 78ff002aad
4 changed files with 335 additions and 9 deletions
+9 -1
View File
@@ -1,6 +1,6 @@
use serde::Serialize;
use super::ProviderType;
use super::{ProviderType, Usage};
#[derive(Clone, Debug)]
pub struct NewLlmCall {
@@ -22,6 +22,14 @@ pub struct NewLlmCall {
pub detailed: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct LlmCallUsageAnchor {
pub request_type: ProviderType,
pub usage: Usage,
pub message_count: usize,
pub tool_count: usize,
}
#[derive(Clone, Debug, Serialize)]
pub struct LlmCallSummary {
pub call_id: String,
+50
View File
@@ -2,6 +2,8 @@ use std::ops::AddAssign;
use serde::{Deserialize, Serialize};
use super::ProviderType;
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Usage {
pub input_tokens: Option<u64>,
@@ -12,6 +14,19 @@ pub struct Usage {
pub reasoning_tokens: Option<u64>,
}
impl Usage {
/// Returns the provider-visible input context without counting cached tokens twice.
pub(crate) fn context_input_tokens(self, provider: ProviderType) -> Option<u64> {
let input = self.input_tokens?;
match provider {
ProviderType::OpenAiChat | ProviderType::OpenAiResponses => Some(input),
ProviderType::Anthropic => input
.checked_add(self.cache_read_tokens.unwrap_or_default())?
.checked_add(self.cache_write_tokens.unwrap_or_default()),
}
}
}
impl AddAssign for Usage {
fn add_assign(&mut self, rhs: Self) {
self.input_tokens = sum(self.input_tokens, rhs.input_tokens);
@@ -30,6 +45,41 @@ fn sum(left: Option<u64>, right: Option<u64>) -> Option<u64> {
#[cfg(test)]
mod tests {
use super::Usage;
use crate::model::ProviderType;
#[test]
fn openai_context_input_does_not_double_count_cached_tokens() {
let usage = Usage {
input_tokens: Some(140_649),
cache_read_tokens: Some(120_000),
cache_write_tokens: Some(10_000),
..Usage::default()
};
assert_eq!(
usage.context_input_tokens(ProviderType::OpenAiResponses),
Some(140_649)
);
assert_eq!(
usage.context_input_tokens(ProviderType::OpenAiChat),
Some(140_649)
);
}
#[test]
fn anthropic_context_input_includes_disjoint_cache_tokens() {
let usage = Usage {
input_tokens: Some(10_649),
cache_read_tokens: Some(120_000),
cache_write_tokens: Some(10_000),
..Usage::default()
};
assert_eq!(
usage.context_input_tokens(ProviderType::Anthropic),
Some(140_649)
);
}
#[test]
fn turn_total_only_reports_fields_known_for_every_cycle() {