story-kit: merge 174_story_constrain_thinking_traces_in_chat_panel

This commit is contained in:
Dave
2026-02-25 09:32:48 +00:00
parent 4b161d7c87
commit 42c40209d2
5 changed files with 204 additions and 68 deletions

View File

@@ -180,18 +180,20 @@ pub fn set_anthropic_api_key(store: &dyn StoreOps, api_key: String) -> Result<()
}
#[allow(clippy::too_many_arguments)]
pub async fn chat<F, U, A>(
pub async fn chat<F, U, T, A>(
messages: Vec<Message>,
config: ProviderConfig,
state: &SessionState,
store: &dyn StoreOps,
mut on_update: F,
mut on_token: U,
mut on_thinking: T,
mut on_activity: A,
) -> Result<ChatResult, String>
where
F: FnMut(&[Message]) + Send,
U: FnMut(&str) + Send,
T: FnMut(&str) + Send,
A: FnMut(&str) + Send,
{
use crate::llm::providers::anthropic::AnthropicProvider;
@@ -244,6 +246,7 @@ where
config.session_id.as_deref(),
&mut cancel_rx,
|token| on_token(token),
|thinking| on_thinking(thinking),
|tool_name| on_activity(tool_name),
)
.await
@@ -799,6 +802,7 @@ mod tests {
|_| {},
|_| {},
|_| {},
|_| {},
)
.await;
@@ -840,6 +844,7 @@ mod tests {
|_| {},
|_| {},
|_| {},
|_| {},
)
.await;
@@ -879,6 +884,7 @@ mod tests {
|_| {},
|_| {},
|_| {},
|_| {},
)
.await;

View File

@@ -37,17 +37,19 @@ impl ClaudeCodeProvider {
}
#[allow(clippy::too_many_arguments)]
pub async fn chat_stream<F, A>(
pub async fn chat_stream<F, T, A>(
&self,
user_message: &str,
project_root: &str,
session_id: Option<&str>,
cancel_rx: &mut watch::Receiver<bool>,
mut on_token: F,
mut on_thinking: T,
mut on_activity: A,
) -> Result<ClaudeCodeResult, String>
where
F: FnMut(&str) + Send,
T: FnMut(&str) + Send,
A: FnMut(&str) + Send,
{
let message = user_message.to_string();
@@ -67,6 +69,7 @@ impl ClaudeCodeProvider {
});
let (token_tx, mut token_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (thinking_tx, mut thinking_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (activity_tx, mut activity_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (msg_tx, msg_rx) = std::sync::mpsc::channel::<Message>();
let (sid_tx, sid_rx) = tokio::sync::oneshot::channel::<String>();
@@ -78,6 +81,7 @@ impl ClaudeCodeProvider {
resume_id.as_deref(),
cancelled,
token_tx,
thinking_tx,
activity_tx,
msg_tx,
sid_tx,
@@ -90,12 +94,20 @@ impl ClaudeCodeProvider {
Some(t) => on_token(&t),
None => break,
},
msg = thinking_rx.recv() => if let Some(t) = msg {
on_thinking(&t);
},
msg = activity_rx.recv() => if let Some(name) = msg {
on_activity(&name);
},
}
}
// Drain any remaining activity/thinking messages that were buffered when
// the token channel closed.
while let Ok(t) = thinking_rx.try_recv() {
on_thinking(&t);
}
// Drain any remaining activity messages that were buffered when the
// token channel closed. The select! loop breaks on token_rx → None,
// but activity_rx may still hold signals sent in the same instant.
@@ -136,6 +148,7 @@ fn run_pty_session(
resume_session_id: Option<&str>,
cancelled: Arc<AtomicBool>,
token_tx: tokio::sync::mpsc::UnboundedSender<String>,
thinking_tx: tokio::sync::mpsc::UnboundedSender<String>,
activity_tx: tokio::sync::mpsc::UnboundedSender<String>,
msg_tx: std::sync::mpsc::Sender<Message>,
sid_tx: tokio::sync::oneshot::Sender<String>,
@@ -254,7 +267,7 @@ fn run_pty_session(
// Try to parse as JSON
if let Ok(json) = serde_json::from_str::<serde_json::Value>(trimmed)
&& process_json_event(&json, &token_tx, &activity_tx, &msg_tx, &mut sid_tx)
&& process_json_event(&json, &token_tx, &thinking_tx, &activity_tx, &msg_tx, &mut sid_tx)
{
got_result = true;
}
@@ -276,6 +289,7 @@ fn run_pty_session(
process_json_event(
&json,
&token_tx,
&thinking_tx,
&activity_tx,
&msg_tx,
&mut sid_tx,
@@ -319,6 +333,7 @@ fn run_pty_session(
fn process_json_event(
json: &serde_json::Value,
token_tx: &tokio::sync::mpsc::UnboundedSender<String>,
thinking_tx: &tokio::sync::mpsc::UnboundedSender<String>,
activity_tx: &tokio::sync::mpsc::UnboundedSender<String>,
msg_tx: &std::sync::mpsc::Sender<Message>,
sid_tx: &mut Option<tokio::sync::oneshot::Sender<String>>,
@@ -340,7 +355,7 @@ fn process_json_event(
match event_type {
"stream_event" => {
if let Some(event) = json.get("event") {
handle_stream_event(event, token_tx, activity_tx);
handle_stream_event(event, token_tx, thinking_tx, activity_tx);
}
false
}
@@ -489,6 +504,7 @@ fn parse_tool_results(
fn handle_stream_event(
event: &serde_json::Value,
token_tx: &tokio::sync::mpsc::UnboundedSender<String>,
thinking_tx: &tokio::sync::mpsc::UnboundedSender<String>,
activity_tx: &tokio::sync::mpsc::UnboundedSender<String>,
) {
let event_type = event.get("type").and_then(|t| t.as_str()).unwrap_or("");
@@ -516,7 +532,7 @@ fn handle_stream_event(
if let Some(thinking) =
delta.get("thinking").and_then(|t| t.as_str())
{
let _ = token_tx.send(format!("[thinking] {thinking}"));
let _ = thinking_tx.send(thinking.to_string());
}
}
_ => {}
@@ -671,12 +687,13 @@ mod tests {
#[test]
fn handle_stream_event_text_delta_sends_token() {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
let event = json!({
"type": "content_block_delta",
"delta": {"type": "text_delta", "text": "hello "}
});
handle_stream_event(&event, &tx, &atx);
handle_stream_event(&event, &tx, &ttx, &atx);
drop(tx);
let tokens: Vec<_> = {
let mut v = vec![];
@@ -692,12 +709,13 @@ mod tests {
fn handle_stream_event_input_json_delta_not_sent() {
// Tool argument JSON deltas should NOT be sent as text tokens
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
let event = json!({
"type": "content_block_delta",
"delta": {"type": "input_json_delta", "partial_json": "{\"path\":"}
});
handle_stream_event(&event, &tx, &atx);
handle_stream_event(&event, &tx, &ttx, &atx);
drop(tx);
let tokens: Vec<String> = {
let mut v = vec![];
@@ -710,15 +728,18 @@ mod tests {
}
#[test]
fn handle_stream_event_thinking_delta_sends_prefixed_token() {
fn handle_stream_event_thinking_delta_routes_to_thinking_channel() {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel();
let (ttx, mut trx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
let event = json!({
"type": "content_block_delta",
"delta": {"type": "thinking_delta", "thinking": "I should check the file"}
});
handle_stream_event(&event, &tx, &atx);
handle_stream_event(&event, &tx, &ttx, &atx);
drop(tx);
drop(ttx);
// thinking token must NOT appear in the regular token channel
let tokens: Vec<String> = {
let mut v = vec![];
while let Ok(t) = rx.try_recv() {
@@ -726,18 +747,28 @@ mod tests {
}
v
};
assert_eq!(tokens, vec!["[thinking] I should check the file"]);
assert!(tokens.is_empty(), "thinking token leaked into token channel");
// thinking token must appear in the dedicated thinking channel, without prefix
let thinking: Vec<String> = {
let mut v = vec![];
while let Ok(t) = trx.try_recv() {
v.push(t);
}
v
};
assert_eq!(thinking, vec!["I should check the file"]);
}
#[test]
fn handle_stream_event_error_sends_error_token() {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel();
let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
let event = json!({
"type": "error",
"error": {"type": "overloaded_error", "message": "Overloaded"}
});
handle_stream_event(&event, &tx, &atx);
handle_stream_event(&event, &tx, &ttx, &atx);
drop(tx);
let tokens: Vec<String> = {
let mut v = vec![];
@@ -752,9 +783,10 @@ mod tests {
#[test]
fn handle_stream_event_unknown_type_is_noop() {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel();
let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
let event = json!({"type": "ping"});
handle_stream_event(&event, &tx, &atx);
handle_stream_event(&event, &tx, &ttx, &atx);
drop(tx);
let tokens: Vec<String> = {
let mut v = vec![];
@@ -846,65 +878,68 @@ mod tests {
tokio::sync::mpsc::UnboundedReceiver<String>,
tokio::sync::mpsc::UnboundedSender<String>,
tokio::sync::mpsc::UnboundedReceiver<String>,
tokio::sync::mpsc::UnboundedSender<String>,
tokio::sync::mpsc::UnboundedReceiver<String>,
std::sync::mpsc::Sender<Message>,
std::sync::mpsc::Receiver<Message>,
);
fn make_channels() -> Channels {
let (tok_tx, tok_rx) = tokio::sync::mpsc::unbounded_channel();
let (thi_tx, thi_rx) = tokio::sync::mpsc::unbounded_channel();
let (act_tx, act_rx) = tokio::sync::mpsc::unbounded_channel();
let (msg_tx, msg_rx) = std::sync::mpsc::channel();
(tok_tx, tok_rx, act_tx, act_rx, msg_tx, msg_rx)
(tok_tx, tok_rx, thi_tx, thi_rx, act_tx, act_rx, msg_tx, msg_rx)
}
#[test]
fn process_json_event_result_returns_true() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
let (sid_tx, _sid_rx) = tokio::sync::oneshot::channel::<String>();
let mut sid_tx_opt = Some(sid_tx);
let json = json!({"type": "result", "subtype": "success"});
assert!(process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx_opt));
assert!(process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx_opt));
}
#[test]
fn process_json_event_system_returns_false() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
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 json = json!({"type": "system", "subtype": "init", "apiKeySource": "env"});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
}
#[test]
fn process_json_event_rate_limit_returns_false() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
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 json = json!({"type": "rate_limit_event"});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
}
#[test]
fn process_json_event_unknown_type_returns_false() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
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 json = json!({"type": "some_future_event"});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
}
#[test]
fn process_json_event_no_type_returns_false() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
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 json = json!({"content": "no type field"});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
}
#[test]
fn process_json_event_captures_session_id() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
let (sid_tx, mut sid_rx) = tokio::sync::oneshot::channel::<String>();
let mut sid_tx_opt = Some(sid_tx);
let json = json!({"type": "system", "session_id": "sess-abc-123"});
process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx_opt);
process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx_opt);
// sid_tx should have been consumed
assert!(sid_tx_opt.is_none());
let received = sid_rx.try_recv().unwrap();
@@ -913,18 +948,18 @@ mod tests {
#[test]
fn process_json_event_preserves_sid_tx_if_no_session_id() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
let (sid_tx, _sid_rx) = tokio::sync::oneshot::channel::<String>();
let mut sid_tx_opt = Some(sid_tx);
let json = json!({"type": "system"});
process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx_opt);
process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx_opt);
// sid_tx should still be present since no session_id in event
assert!(sid_tx_opt.is_some());
}
#[test]
fn process_json_event_stream_event_forwards_token() {
let (tok_tx, mut tok_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, mut 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 json = json!({
"type": "stream_event",
@@ -934,7 +969,7 @@ mod tests {
"delta": {"type": "text_delta", "text": "word"}
}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(tok_tx);
let tokens: Vec<String> = {
let mut v = vec![];
@@ -950,7 +985,7 @@ mod tests {
fn process_json_event_stream_event_tool_use_fires_activity() {
// This is the primary activity path: stream_event wrapping content_block_start
// with a tool_use block. Requires --include-partial-messages to be enabled.
let (tok_tx, _tok_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
let json = json!({
"type": "stream_event",
@@ -961,7 +996,7 @@ mod tests {
"content_block": {"type": "tool_use", "id": "toolu_abc", "name": "Bash", "input": {}}
}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(act_tx);
let activities: Vec<String> = {
let mut v = vec![];
@@ -975,7 +1010,7 @@ mod tests {
#[test]
fn process_json_event_assistant_with_tool_use_fires_activity() {
let (tok_tx, _tok_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
let json = json!({
"type": "assistant",
@@ -986,7 +1021,7 @@ mod tests {
]
}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(act_tx);
let activities: Vec<String> = {
let mut v = vec![];
@@ -1000,7 +1035,7 @@ mod tests {
#[test]
fn process_json_event_assistant_with_multiple_tool_uses_fires_all_activities() {
let (tok_tx, _tok_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
let json = json!({
"type": "assistant",
@@ -1011,7 +1046,7 @@ mod tests {
]
}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(act_tx);
let activities: Vec<String> = {
let mut v = vec![];
@@ -1025,7 +1060,7 @@ mod tests {
#[test]
fn process_json_event_assistant_text_only_no_activity() {
let (tok_tx, _tok_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
let json = json!({
"type": "assistant",
@@ -1033,7 +1068,7 @@ mod tests {
"content": [{"type": "text", "text": "Just text, no tools."}]
}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(act_tx);
let activities: Vec<String> = {
let mut v = vec![];
@@ -1047,7 +1082,7 @@ mod tests {
#[test]
fn process_json_event_assistant_event_parses_message() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
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 json = json!({
"type": "assistant",
@@ -1055,7 +1090,7 @@ mod tests {
"content": [{"type": "text", "text": "Hi!"}]
}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(msg_tx);
let msgs: Vec<Message> = msg_rx.try_iter().collect();
assert_eq!(msgs.len(), 1);
@@ -1064,7 +1099,7 @@ mod tests {
#[test]
fn process_json_event_user_event_parses_tool_results() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
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 json = json!({
"type": "user",
@@ -1072,7 +1107,7 @@ mod tests {
"content": [{"type": "tool_result", "tool_use_id": "tid1", "content": "done"}]
}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(msg_tx);
let msgs: Vec<Message> = msg_rx.try_iter().collect();
assert_eq!(msgs.len(), 1);
@@ -1082,13 +1117,13 @@ mod tests {
#[test]
fn process_json_event_assistant_without_content_array_is_noop() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
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 json = json!({
"type": "assistant",
"message": {"content": "not an array"}
});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(msg_tx);
let msgs: Vec<Message> = msg_rx.try_iter().collect();
assert!(msgs.is_empty());
@@ -1096,10 +1131,10 @@ mod tests {
#[test]
fn process_json_event_user_without_content_array_is_noop() {
let (tok_tx, _tok_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
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 json = json!({"type": "user", "message": {"content": null}});
assert!(!process_json_event(&json, &tok_tx, &act_tx, &msg_tx, &mut sid_tx));
assert!(!process_json_event(&json, &tok_tx, &thi_tx, &act_tx, &msg_tx, &mut sid_tx));
drop(msg_tx);
let msgs: Vec<Message> = msg_rx.try_iter().collect();
assert!(msgs.is_empty());
@@ -1113,13 +1148,14 @@ mod tests {
#[test]
fn handle_stream_event_tool_use_start_sends_activity() {
let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (atx, mut arx) = tokio::sync::mpsc::unbounded_channel::<String>();
let event = json!({
"type": "content_block_start",
"index": 1,
"content_block": {"type": "tool_use", "id": "toolu_1", "name": "Read", "input": {}}
});
handle_stream_event(&event, &tx, &atx);
handle_stream_event(&event, &tx, &ttx, &atx);
drop(atx);
let activities: Vec<String> = {
let mut v = vec![];
@@ -1134,13 +1170,14 @@ mod tests {
#[test]
fn handle_stream_event_text_block_start_no_activity() {
let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (atx, mut arx) = tokio::sync::mpsc::unbounded_channel::<String>();
let event = json!({
"type": "content_block_start",
"index": 0,
"content_block": {"type": "text", "text": ""}
});
handle_stream_event(&event, &tx, &atx);
handle_stream_event(&event, &tx, &ttx, &atx);
drop(atx);
let activities: Vec<String> = {
let mut v = vec![];