提议
希望给 aion_types::Message 加一个可选的 usage: Option<TokenUsage> 字段, 让 session.json 持久化时 assistant Message 能带上本 turn 的 token 用量, 而不只是 Session.total_usage 这个全量终值。
动机
aionrs 在 engine.rs turn loop 里已经有完整的 turn_usage 变量 (engine.rs:515), 在 LlmEvent::Done 时被填充, 然后只用于:
- 累加到
total_usage (engine.rs:555-558)
- 触发 compact 水位检测 (engine.rs:565-576)
之后 turn_usage 就被丢弃, 没有挂到对应的 assistant Message 上。下游想做时间维度的 token 分析 (按日/周趋势、跨 model 切换的成本归因、cache miss 序列分析) 时, 拿到的 session.json 里只有终值, 无法重建 per-turn 流水。
具体使用场景:
- 我在为 AionUi 做使用统计仪表盘 (iOfficeAI/AionUi#2946), 第三个数据源是 aionrs。当前 Claude Code CLI 和 Codex CLI 都以 per-event JSONL 提供 token 流水, aionrs 只有 session 级聚合, 导致 aionrs 部分的趋势图精度退化 (跨午夜的长会话全部计入结束日期等)。
- 其它 host 产品 (Cline / Continue 等) 接 aionrs 时也会遇到同样问题。
数据本来就在 engine 内存里, 缺的只是"挂到 Message 上"这一步。
简要设计
// crates/aion-types/src/message.rs
pub struct Message {
pub role: Role,
pub content: Vec<ContentBlock>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timestamp: Option<DateTime<Utc>>,
/// Token usage attributed to this turn. None for user/tool-result
/// messages, legacy sessions, and messages constructed outside engine.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<TokenUsage>,
}
impl Message {
pub fn with_usage(mut self, usage: TokenUsage) -> Self {
self.usage = Some(usage);
self
}
}
engine.rs 唯一改动 (大约 621 行附近):
self.messages.push(
Message::now(Role::Assistant, assistant_content).with_usage(turn_usage.clone()),
);
关键属性
- 向后兼容:
#[serde(default)] + skip_serializing_if → 旧 session.json 反序列化为 None, 新 session.json 被旧 binary 加载会 ignore 未知字段
- 不变量:
Σ messages[i].usage (assistant turn) == session.total_usage
- 零额外计算: 复用现有
turn_usage
- 改动量: ~80 行 (含序列化兼容性测试 + 不变量测试)
- 非破坏: 用 builder 模式不动现有
Message::new / Message::now 调用点
备选方案 (供讨论)
如果倾向不动 Message struct, 备选是在 Session 上加一个平行的 turns: Vec<TurnSummary> 字段, 但需要维护 index 一致性、查询不如直接挂自然。详见提案文档。
详细提案
完整设计文档 (含改动清单、测试矩阵、兼容性场景、备选方案):
https://github.com/Jassy930/AionUi/blob/e8d546690/docs/prds/settings/agent_usage/aionrs-pr-proposal.md
如果方向 OK 我来提 PR, 想先听一下:
- 字段直接挂
Message 还是用备选 Session.turns?
- 字段名
usage 是否合适 (会不会和未来其它 usage 概念冲突)?
- 是否需要同时让
Message.timestamp 在 engine 生成的 assistant Message 上从 Option 变成 always-set?
- 还有别的 host 需求我没考虑到的吗?
谢谢!
提议
希望给
aion_types::Message加一个可选的usage: Option<TokenUsage>字段, 让 session.json 持久化时 assistant Message 能带上本 turn 的 token 用量, 而不只是Session.total_usage这个全量终值。动机
aionrs 在
engine.rsturn loop 里已经有完整的turn_usage变量 (engine.rs:515), 在LlmEvent::Done时被填充, 然后只用于:total_usage(engine.rs:555-558)之后 turn_usage 就被丢弃, 没有挂到对应的 assistant Message 上。下游想做时间维度的 token 分析 (按日/周趋势、跨 model 切换的成本归因、cache miss 序列分析) 时, 拿到的 session.json 里只有终值, 无法重建 per-turn 流水。
具体使用场景:
数据本来就在 engine 内存里, 缺的只是"挂到 Message 上"这一步。
简要设计
engine.rs 唯一改动 (大约 621 行附近):
关键属性
#[serde(default)]+skip_serializing_if→ 旧 session.json 反序列化为None, 新 session.json 被旧 binary 加载会 ignore 未知字段Σ messages[i].usage(assistant turn)== session.total_usageturn_usageMessage::new/Message::now调用点备选方案 (供讨论)
如果倾向不动
Messagestruct, 备选是在Session上加一个平行的turns: Vec<TurnSummary>字段, 但需要维护 index 一致性、查询不如直接挂自然。详见提案文档。详细提案
完整设计文档 (含改动清单、测试矩阵、兼容性场景、备选方案):
https://github.com/Jassy930/AionUi/blob/e8d546690/docs/prds/settings/agent_usage/aionrs-pr-proposal.md
如果方向 OK 我来提 PR, 想先听一下:
Message还是用备选Session.turns?usage是否合适 (会不会和未来其它 usage 概念冲突)?Message.timestamp在 engine 生成的 assistant Message 上从Option变成 always-set?谢谢!