huskies: merge 1186 story compact chat command: distill session context deterministically, then reset with a seed
This commit is contained in:
@@ -6,6 +6,7 @@ mod stream;
|
||||
mod tests;
|
||||
|
||||
use super::parse::{parse_assistant_message, parse_tool_results};
|
||||
use crate::agents::TokenUsage;
|
||||
use crate::llm::types::Message;
|
||||
use crate::slog;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -16,6 +17,7 @@ use stream::handle_stream_event;
|
||||
/// Routes the event to the appropriate handler based on `type`, emitting tokens,
|
||||
/// thinking output, activity signals, and parsed messages to their respective channels.
|
||||
/// Returns `true` only for `"result"` events, which signal that the CLI turn is complete.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn process_json_event(
|
||||
json: &serde_json::Value,
|
||||
token_tx: &tokio::sync::mpsc::UnboundedSender<String>,
|
||||
@@ -23,6 +25,7 @@ pub(super) fn process_json_event(
|
||||
activity_tx: &tokio::sync::mpsc::UnboundedSender<String>,
|
||||
msg_tx: &std::sync::mpsc::Sender<Message>,
|
||||
sid_tx: &mut Option<tokio::sync::oneshot::Sender<String>>,
|
||||
usage_tx: &mut Option<tokio::sync::oneshot::Sender<TokenUsage>>,
|
||||
auth_failed: &AtomicBool,
|
||||
) -> bool {
|
||||
let event_type = match json.get("type").and_then(|t| t.as_str()) {
|
||||
@@ -80,7 +83,14 @@ pub(super) fn process_json_event(
|
||||
}
|
||||
false
|
||||
}
|
||||
"result" => true,
|
||||
"result" => {
|
||||
if let Some(tx) = usage_tx.take()
|
||||
&& let Some(usage) = TokenUsage::from_result_event(json)
|
||||
{
|
||||
let _ = tx.send(usage);
|
||||
}
|
||||
true
|
||||
}
|
||||
// system, rate_limit_event, and unknown types are no-ops
|
||||
_ => false,
|
||||
}
|
||||
|
||||
@@ -202,10 +202,68 @@ fn process_json_event_result_returns_true() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx_opt,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_json_event_result_captures_usage() {
|
||||
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
|
||||
let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
|
||||
let (usage_tx, mut usage_rx) = tokio::sync::oneshot::channel::<crate::agents::TokenUsage>();
|
||||
let mut usage_tx_opt = Some(usage_tx);
|
||||
let json = json!({
|
||||
"type": "result",
|
||||
"subtype": "success",
|
||||
"total_cost_usd": 0.42,
|
||||
"usage": {
|
||||
"input_tokens": 10,
|
||||
"output_tokens": 20,
|
||||
"cache_creation_input_tokens": 100,
|
||||
"cache_read_input_tokens": 60000
|
||||
}
|
||||
});
|
||||
assert!(process_json_event(
|
||||
&json,
|
||||
&tok_tx,
|
||||
&thi_tx,
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut usage_tx_opt,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
assert!(usage_tx_opt.is_none(), "usage_tx should be consumed");
|
||||
let usage = usage_rx.try_recv().unwrap();
|
||||
assert_eq!(usage.cache_read_input_tokens, 60000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_json_event_result_without_usage_sends_nothing() {
|
||||
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
|
||||
let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
|
||||
let (usage_tx, mut usage_rx) = tokio::sync::oneshot::channel::<crate::agents::TokenUsage>();
|
||||
let mut usage_tx_opt = Some(usage_tx);
|
||||
let json = json!({"type": "result", "subtype": "success"});
|
||||
assert!(process_json_event(
|
||||
&json,
|
||||
&tok_tx,
|
||||
&thi_tx,
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut usage_tx_opt,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
// The result event carried no "usage" field, so the sender is dropped
|
||||
// without sending — the receiver observes a closed channel, not a value.
|
||||
assert!(
|
||||
usage_rx.try_recv().is_err(),
|
||||
"no usage field means nothing sent"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_json_event_system_returns_false() {
|
||||
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
|
||||
@@ -218,6 +276,7 @@ fn process_json_event_system_returns_false() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
}
|
||||
@@ -234,6 +293,7 @@ fn process_json_event_rate_limit_returns_false() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
}
|
||||
@@ -250,6 +310,7 @@ fn process_json_event_unknown_type_returns_false() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
}
|
||||
@@ -266,6 +327,7 @@ fn process_json_event_no_type_returns_false() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
}
|
||||
@@ -283,6 +345,7 @@ fn process_json_event_captures_session_id() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx_opt,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
);
|
||||
// sid_tx should have been consumed
|
||||
@@ -304,6 +367,7 @@ fn process_json_event_preserves_sid_tx_if_no_session_id() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx_opt,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
);
|
||||
// sid_tx should still be present since no session_id in event
|
||||
@@ -329,6 +393,7 @@ fn process_json_event_stream_event_forwards_token() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(tok_tx);
|
||||
@@ -364,6 +429,7 @@ fn process_json_event_stream_event_tool_use_fires_activity() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(act_tx);
|
||||
@@ -397,6 +463,7 @@ fn process_json_event_assistant_with_tool_use_fires_activity() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(act_tx);
|
||||
@@ -430,6 +497,7 @@ fn process_json_event_assistant_with_multiple_tool_uses_fires_all_activities() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(act_tx);
|
||||
@@ -460,6 +528,7 @@ fn process_json_event_assistant_text_only_no_activity() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(act_tx);
|
||||
@@ -490,6 +559,7 @@ fn process_json_event_assistant_event_parses_message() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(msg_tx);
|
||||
@@ -515,6 +585,7 @@ fn process_json_event_user_event_parses_tool_results() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(msg_tx);
|
||||
@@ -539,6 +610,7 @@ fn process_json_event_assistant_without_content_array_is_noop() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(msg_tx);
|
||||
@@ -558,6 +630,7 @@ fn process_json_event_user_without_content_array_is_noop() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&AtomicBool::new(false),
|
||||
));
|
||||
drop(msg_tx);
|
||||
@@ -584,6 +657,7 @@ fn process_json_event_detects_authentication_failed() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&auth_failed,
|
||||
));
|
||||
assert!(auth_failed.load(Ordering::Relaxed));
|
||||
@@ -607,6 +681,7 @@ fn process_json_event_no_auth_failed_for_normal_events() {
|
||||
&act_tx,
|
||||
&msg_tx,
|
||||
&mut sid_tx,
|
||||
&mut None,
|
||||
&auth_failed,
|
||||
));
|
||||
assert!(!auth_failed.load(Ordering::Relaxed));
|
||||
|
||||
Reference in New Issue
Block a user