mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
fix(usage): 流式终端 usage 以更完整值为准,Codex CLI 显式选择 response 解析器
- 新增 StandardizedUsage::signal_score/is_more_complete_than/choose_more_complete,流式合并与终端落库均按信号完整度择优 - OpenAI Chat/CLI 解析器支持仅 usage 的终结 chunk 与 response.completed usage - Codex provider 注入 provider_stream_event_api_format=openai:cli,解析器选择改由 report_context 显式决定 - usage_mapper 扩展嵌套 response/message/item 兼容 Claude message_start/message_delta 及 Gemini stream chunks - usage SQL upsert 在终态(completed/failed/cancelled)时按 GREATEST 写入 token/费用镜像列
This commit is contained in:
@@ -83,6 +83,41 @@ impl StandardizedUsage {
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn signal_score(&self) -> usize {
|
||||
[
|
||||
self.input_tokens,
|
||||
self.output_tokens,
|
||||
self.cache_creation_tokens,
|
||||
self.cache_creation_ephemeral_5m_tokens,
|
||||
self.cache_creation_ephemeral_1h_tokens,
|
||||
self.cache_read_tokens,
|
||||
self.reasoning_tokens,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|value| *value > 0)
|
||||
.count()
|
||||
+ self.dimensions.len()
|
||||
}
|
||||
|
||||
pub fn has_token_signal(&self) -> bool {
|
||||
self.signal_score() > 0
|
||||
}
|
||||
|
||||
pub fn is_more_complete_than(&self, other: &Self) -> bool {
|
||||
self.signal_score() > other.signal_score()
|
||||
}
|
||||
|
||||
pub fn choose_more_complete(primary: Option<Self>, candidate: Option<Self>) -> Option<Self> {
|
||||
match (primary, candidate) {
|
||||
(Some(primary), Some(candidate)) if candidate.is_more_complete_than(&primary) => {
|
||||
Some(candidate)
|
||||
}
|
||||
(Some(primary), _) => Some(primary),
|
||||
(None, Some(candidate)) => Some(candidate),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
@@ -116,6 +151,36 @@ fn as_f64(value: &serde_json::Value, default: f64) -> f64 {
|
||||
mod tests {
|
||||
use super::StandardizedUsage;
|
||||
|
||||
#[test]
|
||||
fn standardized_usage_prefers_more_complete_candidate() {
|
||||
let mut output_only = StandardizedUsage::new();
|
||||
output_only.output_tokens = 131;
|
||||
let mut complete = StandardizedUsage::new();
|
||||
complete.input_tokens = 26;
|
||||
complete.output_tokens = 131;
|
||||
|
||||
let selected = StandardizedUsage::choose_more_complete(Some(output_only), Some(complete))
|
||||
.expect("usage should be selected");
|
||||
|
||||
assert_eq!(selected.input_tokens, 26);
|
||||
assert_eq!(selected.output_tokens, 131);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standardized_usage_keeps_primary_when_candidate_is_not_more_complete() {
|
||||
let mut primary = StandardizedUsage::new();
|
||||
primary.input_tokens = 26;
|
||||
primary.output_tokens = 131;
|
||||
let mut output_only = StandardizedUsage::new();
|
||||
output_only.output_tokens = 131;
|
||||
|
||||
let selected = StandardizedUsage::choose_more_complete(Some(primary), Some(output_only))
|
||||
.expect("usage should be selected");
|
||||
|
||||
assert_eq!(selected.input_tokens, 26);
|
||||
assert_eq!(selected.output_tokens, 131);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standardized_usage_reads_and_writes_known_and_extra_fields() {
|
||||
let mut usage = StandardizedUsage::new();
|
||||
|
||||
Reference in New Issue
Block a user