use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use futures::StreamExt;
use serde_json::json;
use cognis::agents::AgentExecutor;
use cognis_core::language_models::fake::FakeMessagesListChatModel;
use cognis_core::messages::tool_types::ToolCall;
use cognis_core::messages::{AIMessage, HumanMessage, Message};
use cognis_core::tools::{BaseTool, ToolInput, ToolOutput};
use cognis_core::tracers::EventType;
struct JsonTool;
#[async_trait]
impl BaseTool for JsonTool {
fn name(&self) -> &str {
"json_tool"
}
fn description(&self) -> &str {
"returns structured JSON"
}
async fn _run(&self, _input: ToolInput) -> cognis_core::error::Result<ToolOutput> {
Ok(ToolOutput::Content(
json!({"answer": 42, "confidence": 0.9}),
))
}
}
#[tokio::test]
async fn astream_events_preserves_tool_output_shape() {
let tool_call = ToolCall {
name: "json_tool".to_string(),
args: HashMap::new(),
id: Some("call_1".to_string()),
};
let first = Message::Ai(AIMessage::new("calling tool").with_tool_calls(vec![tool_call]));
let second = Message::Ai(AIMessage::new("done"));
let model = Arc::new(FakeMessagesListChatModel::new(vec![first, second]));
let tool: Arc<dyn BaseTool> = Arc::new(JsonTool);
let executor = AgentExecutor::builder()
.model(model)
.tool(tool)
.max_iterations(3)
.build();
let messages = vec![Message::Human(HumanMessage::new("go"))];
let mut stream = executor.astream_events(messages).await.unwrap();
let mut saw_typed_output = false;
while let Some(ev) = stream.next().await {
let ev = ev.unwrap();
if ev.event == EventType::OnToolEnd {
let out = ev.data.output.expect("output should be set on OnToolEnd");
assert_eq!(
out,
json!({"answer": 42, "confidence": 0.9}),
"OnToolEnd.data.output must be the raw structured value (no stringification)"
);
saw_typed_output = true;
}
}
assert!(
saw_typed_output,
"expected at least one OnToolEnd event carrying the typed tool output"
);
}