Show a compaction happening, and what it recovered
The Compacting status had been declared, rendered in four places, and never once emitted: no driver produced it, and the app had no control to ask for a compaction in the first place. Pressing nothing for two minutes and then quietly having less context was the whole experience. The CLI turns out to announce all of it, which was worth measuring rather than guessing at. Driven through /compact against 2.1.237 it emits a `status: "compacting"` line at the start, a `status: null` carrying `compact_result` at the end -- `"failed"` with a sentence saying why, when it does -- and then a `compact_boundary` with the token counts. The same records appear in the CLI's own transcript file with camelCase keys, which is the obvious place to read the shape off and gets every field name wrong. So none of it is inferred here. The driver writes the line and says nothing; the translator reports what the CLI reports. A failed compaction surfaces the CLI's own sentence, which is specific enough to act on. The counts are the part worth keeping afterwards, so they land in the transcript rather than only in a status that vanishes: a session that went from 128,402 tokens to 9,617 has just been given its context back. They are optional throughout, because a compaction whose size nobody reported has to be able to say so -- a zero would read as "recovered nothing". Also here, all found on the way: - `rename_all` renames variants; fields need `rename_all_fields`. Every field in Event was a single word until `pre_tokens`, which went out as snake_case, was not found by the app, and rendered as the "no counts reported" case -- a state it is allowed to be in, so nothing looked wrong. There is now a test on the wire names. - The unparseable-line warning sliced bytes, not chars, on output that is full of em dashes. A panic there kills the task reading the session's stdout, and the session goes deaf with nothing on screen. The other three truncations in the tree already did this correctly. - Echo compacts too, with invented numbers and a real shape, so this screen can be looked at without spending two minutes of somebody's account to reach the state.
This commit is contained in:
1 parent
42131c75d6
commit
5396da76c7
8 files changed
+408
-38
No files matched your search
@@ -21,7 +21,19 @@ pub type ImageRef = String;
|
||||
/// reconnecting is just "events after seq N" -- no separate history path
|
||||
/// to drift from the live one.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
// `rename_all` renames the variants; `rename_all_fields` renames what is
|
||||
// inside them. Both are needed and only the first is obvious: every field
|
||||
// here was a single lowercase word until `pre_tokens` arrived, so a
|
||||
// multi-word field went out as snake_case, the app looked for camelCase and
|
||||
// found nothing, and the event still rendered -- as the "no counts were
|
||||
// reported" case, which is a state it is allowed to be in. A wire mismatch
|
||||
// that lands on a plausible state is invisible; anything added below with a
|
||||
// two-word field would have hit the same thing.
|
||||
#[serde(
|
||||
tag = "type",
|
||||
rename_all = "camelCase",
|
||||
rename_all_fields = "camelCase"
|
||||
)]
|
||||
pub enum Event {
|
||||
/// What the user sent, written into the transcript by the manager (not
|
||||
/// by drivers) so every device renders the full conversation from the
|
||||
@@ -106,6 +118,29 @@ pub enum Event {
|
||||
UsageDelta {
|
||||
tokens: u64,
|
||||
},
|
||||
/// A compaction that finished, and how much context it recovered.
|
||||
///
|
||||
/// The counts are the point, and a spinner is not: what a reader wants
|
||||
/// afterwards is that the session went from a million tokens to ten
|
||||
/// thousand, which is measured rather than estimated. They are
|
||||
/// optional because the record has shipped without them, and "the
|
||||
/// compaction happened, we don't know by how much" is a state this
|
||||
/// has to be able to say -- filling in a plausible number would make
|
||||
/// it indistinguishable from one that was counted.
|
||||
Compacted {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pre_tokens: Option<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
post_tokens: Option<u64>,
|
||||
/// What asked for it, in the dialect's own word -- `auto` when the
|
||||
/// session compacted on its own. Carried rather than reduced to a
|
||||
/// bool so an unrecognised trigger stays unrecognised: an
|
||||
/// automatic compaction is the one worth naming, because it
|
||||
/// explains a wait nobody asked for, and defaulting the unknown
|
||||
/// case to "you asked for this" would explain it away.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
trigger: Option<String>,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
@@ -180,3 +215,32 @@ pub trait Driver: Send + Sync {
|
||||
/// [`detach`]: Driver::detach
|
||||
fn stop(&self);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A tripwire for the wire format, not for serde.
|
||||
///
|
||||
/// The app reads these names, and getting one wrong does not fail
|
||||
/// loudly: a field the app cannot find reads as a field the server
|
||||
/// chose not to send, which several of them are allowed to be.
|
||||
#[test]
|
||||
fn multi_word_fields_go_out_in_camel_case() {
|
||||
let json = serde_json::to_value(Event::Compacted {
|
||||
pre_tokens: Some(28719),
|
||||
post_tokens: Some(1125),
|
||||
trigger: Some("manual".to_string()),
|
||||
})
|
||||
.expect("serialize");
|
||||
assert_eq!(
|
||||
json,
|
||||
serde_json::json!({
|
||||
"type": "compacted",
|
||||
"preTokens": 28719,
|
||||
"postTokens": 1125,
|
||||
"trigger": "manual",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user