From 969fcab66eda10cafeab5e8d44928712569ef7fa Mon Sep 17 00:00:00 2001 From: jcarver989 <117696+jcarver989@users.noreply.github.com> Date: Mon, 12 Oct 2026 04:49:08 +0000 Subject: [PATCH] chore: scheduled code-cleanup --- crates/aether-cli/Cargo.toml | 1 + crates/aether-cli/src/acp/testing.rs | 84 ++++--------------- crates/aether-core/src/events/agent_event.rs | 28 +++++-- crates/aether-core/tests/agent/agent_tests.rs | 8 +- crates/aether-evals/src/agents/transcript.rs | 34 ++------ crates/aether-evals/src/assertions.rs | 25 +----- crates/aether-evals/src/judge.rs | 37 ++------ crates/aether-sessions/src/testing.rs | 17 ++-- crates/aether-sessions/tests/model.rs | 2 +- crates/aether-sessions/tests/transcript.rs | 6 +- .../aether-telemetry/tests/observer_tests.rs | 15 +--- .../tests/otlp_collector_tests.rs | 12 +-- crates/llm/src/tools.rs | 12 +++ .../tests/integration/coding_mcp_tools.rs | 2 +- .../tests/integration/plugins_mcp_agents.rs | 6 +- 15 files changed, 89 insertions(+), 200 deletions(-) diff --git a/crates/aether-cli/Cargo.toml b/crates/aether-cli/Cargo.toml index 785201fb3..be709f687 100644 --- a/crates/aether-cli/Cargo.toml +++ b/crates/aether-cli/Cargo.toml @@ -76,5 +76,6 @@ libc = { workspace = true } [dev-dependencies] aether-agent-cli = { path = ".", features = ["testing"] } +aether-sessions = { workspace = true, features = ["testing"] } tokio = { workspace = true, features = ["test-util"] } acp_utils = { package = "aether-acp-utils", path = "../acp-utils", features = ["testing"], version = "0.5.13" } diff --git a/crates/aether-cli/src/acp/testing.rs b/crates/aether-cli/src/acp/testing.rs index 6981d0094..c8d624b6a 100644 --- a/crates/aether-cli/src/acp/testing.rs +++ b/crates/aether-cli/src/acp/testing.rs @@ -19,20 +19,23 @@ use acp_utils::testing::{TestPeer, initialize_request}; use aether_auth::OAuthCredentialStorage; use aether_core::agent_spec::{AgentSpec, AgentSpecExposure}; use aether_core::core::{AgentBuilder, AgentHandle, Prompt}; -use aether_core::events::{AgentEvent, Command, MessageEvent, ToolEvent, TurnEvent, TurnOutcome, UserCommand}; +use aether_core::events::{AgentEvent, Command, TurnOutcome, UserCommand}; use aether_core::mcp::{McpBuilder, McpSession, ServerFactory, mcp}; use aether_project::AgentCatalog; use aether_sessions::SessionStore; -use aether_sessions::{SessionControlEvent, SessionEvent, SessionMeta, UserEvent, last_agent_from_events}; +use aether_sessions::testing::{ + agent_switched, assistant_text, tool_call, tool_result, turn_ended, user_message, user_message_with, +}; +use aether_sessions::{SessionEvent, SessionMeta, last_agent_from_events}; use agent_client_protocol::schema::v2::{ AbsolutePath, InitializeResponse, ReplayFrom, ReplayFromStart, ResumeSessionRequest, SessionId, SessionUpdate, StateUpdate, StopReason, }; use agent_client_protocol::{Agent, Channel, Client, ConnectionTo, on_receive_notification}; use futures::FutureExt; -use llm::testing::FakeLlmProvider; -use llm::{ChatMessage, Context, LlmModel, LlmResponse, SessionUsageEvent, StreamingModelProvider}; -use llm::{MessageId, ProviderConnectionOverrides, ToolCallRequest, ToolCallResult}; +use llm::ProviderConnectionOverrides; +use llm::testing::{FakeLlmProvider, llm_response}; +use llm::{ChatMessage, Context, LlmModel, SessionUsageEvent, StreamingModelProvider}; use mcp_utils::client::{InMemoryServerSpec, McpServer, McpTransport, ToolExposure}; use mcp_utils::testing::{FakeMcpServer, FakeTool, FakeToolResponse, completed_task_payload}; use rmcp::RoleServer; @@ -387,13 +390,7 @@ impl AcpTestHarness { } pub fn append_agent_switch(&self, session_id: &str, from: Option<&str>, to: Option<&str>) { - self.append_stored_event( - session_id, - &SessionEvent::Control(SessionControlEvent::AgentSwitched { - from: from.map(str::to_string), - to: to.map(str::to_string), - }), - ); + self.append_stored_event(session_id, &agent_switched(from, to)); } /// Register a stub session built from a hand-spawned @@ -455,72 +452,28 @@ impl AcpTestHarness { } pub fn append_stored_prompt(&self, session_id: &str, prompt: &str) { - self.append_stored_event( - session_id, - &SessionEvent::User(UserEvent::Message { - message_id: llm::MessageId::new(), - content: vec![llm::ContentBlock::text(prompt)], - display_content: None, - }), - ); + self.append_stored_event(session_id, &user_message(prompt)); } pub fn append_stored_user_blocks(&self, session_id: &str, blocks: Vec) { - self.append_stored_event( - session_id, - &SessionEvent::User(UserEvent::Message { - message_id: llm::MessageId::new(), - content: blocks, - display_content: None, - }), - ); + self.append_stored_event(session_id, &user_message_with(blocks)); } pub fn append_stored_agent_turn(&self, session_id: &str, text: &str) { self.append_stored_agent_text(session_id, text); - self.append_stored_event( - session_id, - &SessionEvent::Agent(AgentEvent::Turn(TurnEvent::Ended { outcome: TurnOutcome::Completed })), - ); + self.append_stored_event(session_id, &turn_ended(TurnOutcome::Completed)); } pub fn append_stored_agent_text(&self, session_id: &str, text: &str) { - self.append_stored_event( - session_id, - &SessionEvent::Agent(AgentEvent::Message(MessageEvent::Text { - message_id: MessageId::new(), - chunk: text.to_string(), - is_complete: true, - })), - ); + self.append_stored_event(session_id, &assistant_text(llm::MessageId::new().as_str(), text)); } pub fn append_stored_tool_call(&self, session_id: &str, id: &str, name: &str, arguments: &str) { - self.append_stored_event( - session_id, - &SessionEvent::Agent(AgentEvent::Tool(ToolEvent::Call { - request: ToolCallRequest { - id: id.to_string(), - name: name.to_string(), - arguments: arguments.to_string(), - }, - })), - ); + self.append_stored_event(session_id, &tool_call(id, name, arguments)); } pub fn append_stored_tool_result(&self, session_id: &str, id: &str, name: &str, result: &str) { - self.append_stored_event( - session_id, - &SessionEvent::Agent(AgentEvent::Tool(ToolEvent::Result { - result: ToolCallResult { - id: id.to_string(), - name: name.to_string(), - arguments: "{}".to_string(), - result: result.to_string(), - }, - result_meta: None, - })), - ); + self.append_stored_event(session_id, &tool_result(id, name, result)); } async fn insert_switching_session( @@ -905,11 +858,8 @@ fn background_task(status: TaskStatus) -> Task { fn fake_agent(name: &str, server_name: &str, prompt_name: &str, reply: &str) -> (FakeAgentDef, FakeAcpAgent) { const TURNS_BEFORE_AND_AFTER_REATTACH: usize = 2; - let provider = FakeLlmProvider::new(vec![ - vec![LlmResponse::Start, LlmResponse::text(reply), LlmResponse::done()]; - TURNS_BEFORE_AND_AFTER_REATTACH - ]) - .with_display_name(name); + let provider = FakeLlmProvider::new(vec![llm_response().text(&[reply]).build(); TURNS_BEFORE_AND_AFTER_REATTACH]) + .with_display_name(name); let captured_contexts = provider.captured_contexts(); let def = FakeAgentDef { spec: fake_agent_spec(name), diff --git a/crates/aether-core/src/events/agent_event.rs b/crates/aether-core/src/events/agent_event.rs index 1176757f0..0456f52e4 100644 --- a/crates/aether-core/src/events/agent_event.rs +++ b/crates/aether-core/src/events/agent_event.rs @@ -1,3 +1,4 @@ +use llm::{ToolCallError, ToolCallRequest, ToolCallResult}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -37,6 +38,25 @@ impl AgentEvent { Self::Turn(TurnEvent::Ended { outcome }) } + pub fn tool_call(id: &str, name: &str, arguments: &str) -> Self { + Self::Tool(ToolEvent::Call { request: ToolCallRequest::new(id, name, arguments) }) + } + + pub fn tool_result(id: &str, name: &str, arguments: &str, result: &str) -> Self { + Self::Tool(ToolEvent::Result { result: ToolCallResult::new(id, name, arguments, result), result_meta: None }) + } + + pub fn tool_error(id: &str, name: &str, arguments: &str, error: &str) -> Self { + Self::Tool(ToolEvent::Error { + error: ToolCallError { + id: id.into(), + name: name.into(), + arguments: Some(arguments.into()), + error: error.into(), + }, + }) + } + /// Human-readable text content of this event, if any. pub fn content(&self) -> Option { match self { @@ -111,11 +131,11 @@ mod tests { AgentEvent::Model(ModelEvent::Switched { previous: "a".into(), new: "b".into() }), AgentEvent::SessionUsage(llm::testing::session_usage_event(1, llm::TokenUsage::new(1, 2))), AgentEvent::Tool(ToolEvent::DisplayUpdate { - request: llm::ToolCallRequest { id: "call".into(), name: "read".into(), arguments: "{}".into() }, + request: llm::ToolCallRequest::new("call", "read", "{}"), meta: ToolDisplayMeta::new("Read file", "main.rs").into(), }), AgentEvent::Tool(ToolEvent::SubAgentProgress { - request: llm::ToolCallRequest { id: "call".into(), name: "spawn".into(), arguments: "{}".into() }, + request: llm::ToolCallRequest::new("call", "spawn", "{}"), payload: Box::new(crate::events::SubAgentProgressPayload { task_id: "task_0".into(), agent_name: "explorer".into(), @@ -218,9 +238,7 @@ mod tests { AgentEvent::text("m", "partial", StreamState::Complete), AgentEvent::thought("m", "partial", StreamState::Complete), AgentEvent::Tool(ToolEvent::InputStarted { id: "call".into(), name: "read".into() }), - AgentEvent::Tool(ToolEvent::Call { - request: llm::ToolCallRequest { id: "call".into(), name: "read".into(), arguments: "{}".into() }, - }), + AgentEvent::tool_call("call", "read", "{}"), AgentEvent::turn_ended(TurnOutcome::Completed), ]; assert!(deltas.iter().all(AgentEvent::is_stream_delta)); diff --git a/crates/aether-core/tests/agent/agent_tests.rs b/crates/aether-core/tests/agent/agent_tests.rs index 6604a1080..c3bac9507 100644 --- a/crates/aether-core/tests/agent/agent_tests.rs +++ b/crates/aether-core/tests/agent/agent_tests.rs @@ -151,13 +151,7 @@ async fn streamed_tool_input_is_followed_by_the_complete_call() -> Result<(), Bo &ToolEvent::InputStarted { id: "call_1".into(), name: "test__add_numbers".into() }, &ToolEvent::InputDelta { id: "call_1".into(), chunk: arg_chunk_1.into() }, &ToolEvent::InputDelta { id: "call_1".into(), chunk: arg_chunk_2.into() }, - &ToolEvent::Call { - request: llm::ToolCallRequest { - id: "call_1".into(), - name: "test__add_numbers".into(), - arguments: request_json.clone(), - }, - }, + &ToolEvent::Call { request: llm::ToolCallRequest::new("call_1", "test__add_numbers", &request_json) }, ] ); diff --git a/crates/aether-evals/src/agents/transcript.rs b/crates/aether-evals/src/agents/transcript.rs index 309ea8d3b..409e13b1e 100644 --- a/crates/aether-evals/src/agents/transcript.rs +++ b/crates/aether-evals/src/agents/transcript.rs @@ -139,8 +139,8 @@ mod tests { use super::*; use crate::{Agent, FakeAgent, Task}; use aether_core::events::TurnEvent; + use llm::TokenUsage; use llm::testing::session_usage_event; - use llm::{TokenUsage, ToolCallRequest, ToolCallResult}; #[tokio::test] async fn transcript_from_stream() { @@ -154,7 +154,11 @@ mod tests { #[test] fn tool_call_count_counts_matching_tool_calls() { - let transcript = transcript_with_events(vec![tool_call("bash"), tool_call("read"), tool_result("bash")]); + let transcript = Transcript::new(vec![ + AgentEvent::tool_call("call_1", "bash", "{}"), + AgentEvent::tool_call("call_2", "read", "{}"), + AgentEvent::tool_result("call_3", "bash", "{}", "ok"), + ]); assert!(transcript.tool_called("bash")); assert!(!transcript.tool_called("read")); @@ -179,7 +183,7 @@ mod tests { #[test] fn usage_returns_zeroed_totals_when_no_usage_was_recorded() { - let transcript = transcript_with_events(vec![tool_call("bash")]); + let transcript = Transcript::new(vec![AgentEvent::tool_call("call_1", "bash", "{}")]); assert_eq!(transcript.usage(), SessionUsageTotals::default()); } @@ -188,7 +192,7 @@ mod tests { let mut last = session_usage_event(2, TokenUsage::new(2000, 500)); last.totals.tokens = TokenUsage::new(3000, 600); last.totals.unpriced_calls = 2; - let transcript = transcript_with_events(vec![ + let transcript = Transcript::new(vec![ AgentEvent::SessionUsage(session_usage_event(1, TokenUsage::new(1000, 100))), AgentEvent::SessionUsage(last), ]); @@ -200,26 +204,4 @@ mod tests { assert_eq!(usage.unpriced_calls, 2); assert!(!usage.is_fully_priced()); } - - fn transcript_with_events(events: Vec) -> Transcript { - Transcript::new(events) - } - - fn tool_call(name: &str) -> AgentEvent { - AgentEvent::Tool(ToolEvent::Call { - request: ToolCallRequest { id: name.to_string(), name: name.to_string(), arguments: "{}".to_string() }, - }) - } - - fn tool_result(name: &str) -> AgentEvent { - AgentEvent::Tool(ToolEvent::Result { - result: ToolCallResult { - id: name.to_string(), - name: name.to_string(), - arguments: "{}".to_string(), - result: "ok".to_string(), - }, - result_meta: None, - }) - } } diff --git a/crates/aether-evals/src/assertions.rs b/crates/aether-evals/src/assertions.rs index b462426e8..7f9803163 100644 --- a/crates/aether-evals/src/assertions.rs +++ b/crates/aether-evals/src/assertions.rs @@ -44,12 +44,11 @@ pub fn assert_tool_call_with_args(transcript: &Transcript, name: &str, expected: #[cfg(test)] mod tests { use super::*; - use aether_core::events::{AgentEvent, ToolEvent}; - use llm::ToolCallResult; + use aether_core::events::AgentEvent; #[test] fn assert_tool_call_with_args_accepts_matching_json_arguments() { - let trace = transcript_with_messages(vec![tool_result("bash", r#"{"command":"pwd"}"#)]); + let trace = Transcript::new(vec![AgentEvent::tool_result("call_1", "bash", r#"{"command":"pwd"}"#, "ok")]); assert_tool_call_with_args(&trace, "bash", &serde_json::json!({ "command": "pwd" })); } @@ -57,7 +56,7 @@ mod tests { #[test] #[should_panic(expected = "Non-JSON arguments seen for this tool")] fn assert_tool_call_with_args_surfaces_parse_failures() { - let trace = transcript_with_messages(vec![tool_result("bash", "not json")]); + let trace = Transcript::new(vec![AgentEvent::tool_result("call_1", "bash", "not json", "ok")]); assert_tool_call_with_args(&trace, "bash", &serde_json::json!({ "command": "pwd" })); } @@ -65,24 +64,8 @@ mod tests { #[test] #[should_panic(expected = "expected tool `missing` to be called")] fn assert_tool_called_panics_when_tool_was_not_called() { - let trace = transcript_with_messages(vec![]); + let trace = Transcript::new(vec![]); assert_tool_called(&trace, "missing"); } - - fn transcript_with_messages(messages: Vec) -> Transcript { - Transcript::new(messages) - } - - fn tool_result(name: &str, arguments: &str) -> AgentEvent { - AgentEvent::Tool(ToolEvent::Result { - result: ToolCallResult { - id: name.to_string(), - name: name.to_string(), - arguments: arguments.to_string(), - result: "ok".to_string(), - }, - result_meta: None, - }) - } } diff --git a/crates/aether-evals/src/judge.rs b/crates/aether-evals/src/judge.rs index bec96a3f0..7e993982c 100644 --- a/crates/aether-evals/src/judge.rs +++ b/crates/aether-evals/src/judge.rs @@ -466,20 +466,14 @@ fn truncate_chars(value: &str, max_chars: usize) -> String { mod tests { use super::*; use aether_core::events::{AgentEvent, StreamState, TurnOutcome}; - use llm::testing::FakeLlmProvider; - use llm::{LlmError, ProviderError, ToolCallRequest, ToolCallResult}; + use llm::ProviderError; + use llm::testing::{FakeLlmProvider, failed_call, llm_response}; const VALID_RESPONSE: &str = r#"{"criteria":[{"id":"behavior","score":1.0,"reason":"correct"},{"id":"clarity","score":0.5,"reason":"brief"}],"overall_reason":"good"}"#; #[test] fn transcript_lines_label_each_message_kind() { - let call = AgentEvent::Tool(ToolEvent::Call { - request: ToolCallRequest { - id: "call_1".to_string(), - name: "bash".to_string(), - arguments: "{}".to_string(), - }, - }); + let call = AgentEvent::tool_call("call_1", "bash", "{}"); assert_eq!( get_transcript_line(&AgentEvent::text("msg_1", "hi", StreamState::Complete), 100).unwrap(), @@ -498,15 +492,7 @@ mod tests { #[test] fn tool_result_transcript_uses_result_arguments() { - let message = AgentEvent::Tool(ToolEvent::Result { - result: ToolCallResult { - id: "call_1".to_string(), - name: "coding__read_file".to_string(), - arguments: r#"["Cargo.toml"]"#.to_string(), - result: "file contents".to_string(), - }, - result_meta: None, - }); + let message = AgentEvent::tool_result("call_1", "coding__read_file", r#"["Cargo.toml"]"#, "file contents"); assert_eq!(get_transcript_line(&message, 100).unwrap(), "[tool-result] coding__read_file: file contents"); } @@ -541,13 +527,7 @@ mod tests { #[test] fn judge_builder_renders_transcript_context() { let messages = vec![ - AgentEvent::Tool(ToolEvent::Call { - request: ToolCallRequest { - id: "call_1".to_string(), - name: "bash".to_string(), - arguments: "{}".to_string(), - }, - }), + AgentEvent::tool_call("call_1", "bash", "{}"), AgentEvent::text("msg_1", "all done", StreamState::Complete), ]; @@ -665,7 +645,7 @@ mod tests { #[tokio::test] async fn judge_run_extracts_json_object_from_surrounding_prose() { let response = format!("Here is my assessment:\n{VALID_RESPONSE}"); - let judge_llm = FakeLlmProvider::with_single_response(vec![LlmResponse::text(&response)]); + let judge_llm = FakeLlmProvider::with_single_response(llm_response().text(&[response.as_str()]).build()); let judge = judge().task("prompt").criteria(default_criteria()).build().unwrap(); let summary = judge.run(&judge_llm).await.unwrap(); @@ -675,7 +655,7 @@ mod tests { #[tokio::test] async fn judge_run_returns_invalid_json_error_with_raw_response() { - let judge_llm = FakeLlmProvider::with_single_response(vec![LlmResponse::text("not json")]); + let judge_llm = FakeLlmProvider::with_single_response(llm_response().text(&["not json"]).build()); let judge = judge().task("prompt").criteria(default_criteria()).build().unwrap(); let error = judge.run(&judge_llm).await.unwrap_err(); @@ -688,8 +668,7 @@ mod tests { #[tokio::test] async fn judge_run_returns_stream_error_on_llm_failure() { - let judge_llm = - FakeLlmProvider::from_results(vec![vec![Err(LlmError::from(ProviderError::api("boom".to_string())))]]); + let judge_llm = FakeLlmProvider::from_results(vec![failed_call(ProviderError::api("boom".to_string()))]); let judge = judge().task("prompt").criteria(default_criteria()).build().unwrap(); let error = judge.run(&judge_llm).await.unwrap_err(); diff --git a/crates/aether-sessions/src/testing.rs b/crates/aether-sessions/src/testing.rs index 6874e3b8c..40a70d006 100644 --- a/crates/aether-sessions/src/testing.rs +++ b/crates/aether-sessions/src/testing.rs @@ -6,8 +6,8 @@ use std::fs; use std::path::Path; -use aether_core::events::{AgentEvent, ContextEvent, MessageEvent, StreamState, ToolEvent, TurnEvent, TurnOutcome}; -use llm::{ContentBlock, MessageId, ToolCallError, ToolCallRequest, ToolCallResult}; +use aether_core::events::{AgentEvent, ContextEvent, MessageEvent, StreamState, TurnEvent, TurnOutcome}; +use llm::{ContentBlock, MessageId}; use tempfile::TempDir; use crate::model::{SessionControlEvent, SessionEvent, SessionMeta, UserEvent}; @@ -53,24 +53,17 @@ pub fn partial_text(message_id: &str, chunk: &str) -> SessionEvent { /// A tool call issued by the agent. pub fn tool_call(id: &str, name: &str, arguments: &str) -> SessionEvent { - SessionEvent::Agent(AgentEvent::Tool(ToolEvent::Call { - request: ToolCallRequest { id: id.into(), name: name.into(), arguments: arguments.into() }, - })) + SessionEvent::Agent(AgentEvent::tool_call(id, name, arguments)) } /// A successful tool result for `id`. pub fn tool_result(id: &str, name: &str, result: &str) -> SessionEvent { - SessionEvent::Agent(AgentEvent::Tool(ToolEvent::Result { - result: ToolCallResult { id: id.into(), name: name.into(), arguments: "{}".into(), result: result.into() }, - result_meta: None, - })) + SessionEvent::Agent(AgentEvent::tool_result(id, name, "{}", result)) } /// A failed tool execution for `id`. pub fn tool_error(id: &str, name: &str, error: &str) -> SessionEvent { - SessionEvent::Agent(AgentEvent::Tool(ToolEvent::Error { - error: ToolCallError { id: id.into(), name: name.into(), arguments: Some("{}".into()), error: error.into() }, - })) + SessionEvent::Agent(AgentEvent::tool_error(id, name, "{}", error)) } /// The terminal event of a turn. diff --git a/crates/aether-sessions/tests/model.rs b/crates/aether-sessions/tests/model.rs index 4f05c5038..06ef6096a 100644 --- a/crates/aether-sessions/tests/model.rs +++ b/crates/aether-sessions/tests/model.rs @@ -43,7 +43,7 @@ fn persisted_event_policy_covers_representative_variants() { let partial = SessionEvent::Agent(AgentEvent::text("message", "partial", StreamState::Partial)); let usage = SessionEvent::Agent(AgentEvent::SessionUsage(session_usage(1, 5))); let sub_agent_progress = SessionEvent::Agent(AgentEvent::Tool(ToolEvent::SubAgentProgress { - request: llm::ToolCallRequest { id: "call".into(), name: "spawn".into(), arguments: "{}".into() }, + request: llm::ToolCallRequest::new("call", "spawn", "{}"), payload: Box::new(SubAgentProgressPayload { task_id: "task_0".into(), agent_name: "explorer".into(), diff --git a/crates/aether-sessions/tests/transcript.rs b/crates/aether-sessions/tests/transcript.rs index a981ba8bb..9258b54a9 100644 --- a/crates/aether-sessions/tests/transcript.rs +++ b/crates/aether-sessions/tests/transcript.rs @@ -45,11 +45,7 @@ fn reconstructs_conversation_and_ignores_control_events() { #[test] fn task_completion_during_iteration_preserves_pending_tools() { let task = aether_core::events::ToolEvent::TaskCancelled { - request: llm::ToolCallRequest { - id: "background-call".into(), - name: "background".into(), - arguments: "{}".into(), - }, + request: llm::ToolCallRequest::new("background-call", "background", "{}"), task_id: "task".into(), }; let context = context_from_events(&[ diff --git a/crates/aether-telemetry/tests/observer_tests.rs b/crates/aether-telemetry/tests/observer_tests.rs index 5df84d712..b0aa3571d 100644 --- a/crates/aether-telemetry/tests/observer_tests.rs +++ b/crates/aether-telemetry/tests/observer_tests.rs @@ -3,7 +3,7 @@ use std::error::Error; use std::time::Duration; use aether_core::core::RetryConfig; -use aether_core::events::{AgentEvent, AgentObserver, LlmCallOutcome, StreamState, ToolEvent, TurnEvent, TurnOutcome}; +use aether_core::events::{AgentEvent, AgentObserver, LlmCallOutcome, StreamState, TurnEvent, TurnOutcome}; use aether_core::testing::{AgentTrace, TestScenario, test_agent}; use aether_telemetry::ContentCaptureSettings; use aether_telemetry::{ @@ -15,10 +15,7 @@ use common::{SYSTEM_INSTRUCTIONS_JSON, SYSTEM_PROMPT, SYSTEM_PROMPT_SHA256, all_ mod common; use llm::testing::llm_response; -use llm::{ - LlmCallPurpose, LlmError, LlmResponse, ModelIdentity, ModelPricing, ProviderError, StopReason, TokenUsage, - ToolCallRequest, -}; +use llm::{LlmCallPurpose, LlmError, LlmResponse, ModelIdentity, ModelPricing, ProviderError, StopReason, TokenUsage}; use opentelemetry::metrics::MeterProvider as _; use opentelemetry::trace::{SpanKind, Status, TracerProvider as _}; use opentelemetry::{Array, Value}; @@ -32,13 +29,7 @@ async fn tool_span_exposes_w3c_parent_context() -> Result<(), Box> { let mut harness = otel_test().redacting().build(); let observer = harness.observer.as_mut().expect("observer still alive"); observer.on_event(&AgentEvent::Turn(TurnEvent::Started { content: vec![] })); - observer.on_event(&AgentEvent::Tool(ToolEvent::Call { - request: ToolCallRequest { - id: "call_1".to_string(), - name: "subagents__spawn_subagent".to_string(), - arguments: "{}".to_string(), - }, - })); + observer.on_event(&AgentEvent::tool_call("call_1", "subagents__spawn_subagent", "{}")); let context = observer.tool_trace_context("call_1").expect("tool span exposes trace context"); harness.drop_observer(); let spans = harness.spans(); diff --git a/crates/aether-telemetry/tests/otlp_collector_tests.rs b/crates/aether-telemetry/tests/otlp_collector_tests.rs index cecb1fbbc..d11a28c73 100644 --- a/crates/aether-telemetry/tests/otlp_collector_tests.rs +++ b/crates/aether-telemetry/tests/otlp_collector_tests.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use aether_core::events::{AgentEvent, LlmCallOutcome, ToolEvent, TraceContext, TurnEvent, TurnOutcome}; +use aether_core::events::{AgentEvent, LlmCallOutcome, TraceContext, TurnEvent, TurnOutcome}; use aether_telemetry::{ AETHER_SYSTEM_INSTRUCTIONS_SHA256, AgentTraceContext, ContentCaptureSettings, GEN_AI_SYSTEM_INSTRUCTIONS, TelemetryConfig, TelemetryRuntime, @@ -14,7 +14,7 @@ use axum::body::Bytes; use axum::extract::State; use axum::http::{HeaderMap, StatusCode}; use axum::routing::post; -use llm::{LlmCallPurpose, ModelIdentity, ModelPricing, TokenUsage, ToolCallRequest}; +use llm::{LlmCallPurpose, ModelIdentity, ModelPricing, TokenUsage}; use opentelemetry_proto::tonic::collector::metrics::v1::ExportMetricsServiceRequest; use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest; use opentelemetry_proto::tonic::trace::v1::Span; @@ -152,13 +152,7 @@ async fn propagated_mcp_context_connects_parent_tool_server_and_child_agent_span let factory = runtime.observer_factory(); let mut parent = factory.agent(Some("PatchWaveFix"), None); parent.on_event(&AgentEvent::Turn(TurnEvent::Started { content: vec![] })); - parent.on_event(&AgentEvent::Tool(ToolEvent::Call { - request: ToolCallRequest { - id: "call_1".to_string(), - name: "subagents__spawn_subagent".to_string(), - arguments: "{}".to_string(), - }, - })); + parent.on_event(&AgentEvent::tool_call("call_1", "subagents__spawn_subagent", "{}")); let outbound = parent.tool_trace_context("call_1").expect("outbound tool span provides propagation context"); let server = factory.tool_call_request("spawn_subagent", Some(&outbound)); diff --git a/crates/llm/src/tools.rs b/crates/llm/src/tools.rs index 0a96c563f..f0900936a 100644 --- a/crates/llm/src/tools.rs +++ b/crates/llm/src/tools.rs @@ -52,6 +52,12 @@ pub struct ToolCallRequest { pub arguments: String, } +impl ToolCallRequest { + pub fn new(id: &str, name: &str, arguments: &str) -> Self { + Self { id: id.into(), name: name.into(), arguments: arguments.into() } + } +} + /// Successful result of a tool call #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct ToolCallResult { @@ -61,6 +67,12 @@ pub struct ToolCallResult { pub result: String, } +impl ToolCallResult { + pub fn new(id: &str, name: &str, arguments: &str, result: &str) -> Self { + Self { id: id.into(), name: name.into(), arguments: arguments.into(), result: result.into() } + } +} + /// Error result of a tool call #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct ToolCallError { diff --git a/crates/mcp-servers/tests/integration/coding_mcp_tools.rs b/crates/mcp-servers/tests/integration/coding_mcp_tools.rs index 1ac4e9b34..4e13242b4 100644 --- a/crates/mcp-servers/tests/integration/coding_mcp_tools.rs +++ b/crates/mcp-servers/tests/integration/coding_mcp_tools.rs @@ -45,7 +45,7 @@ async fn full_read_file_pages_reach_the_llm_whole() -> TestResult { .call_raw("read_file", ReadFileArgs { file_path: path.to_string_lossy().into(), ..Default::default() }) .await?; - let request = ToolCallRequest { id: "read".into(), name: "coding__read_file".into(), arguments: "{}".into() }; + let request = ToolCallRequest::new("read", "coding__read_file", "{}"); let (result, _) = convert_tool_result(&request, Ok(page), &TempDir::new()).map_err(|error| test_error(error.error))?; diff --git a/crates/mcp-servers/tests/integration/plugins_mcp_agents.rs b/crates/mcp-servers/tests/integration/plugins_mcp_agents.rs index 233d6dc0d..19b6b8c23 100644 --- a/crates/mcp-servers/tests/integration/plugins_mcp_agents.rs +++ b/crates/mcp-servers/tests/integration/plugins_mcp_agents.rs @@ -366,11 +366,7 @@ async fn call_subagent_through_manager( .into_iter() .find(|tool| tool.name.ends_with("spawn_subagent")) .ok_or_else(|| test_error("spawn_subagent tool missing"))?; - let request = llm::ToolCallRequest { - id: "runtime-registry-test".to_string(), - name: tool.name.clone(), - arguments: serde_json::to_string(&input)?, - }; + let request = llm::ToolCallRequest::new("runtime-registry-test", &tool.name, &serde_json::to_string(&input)?); let options = CallToolOptions { timeout: Duration::MAX, meta: None, cancel: CancellationToken::new() }; let mut events = spawn.handle().call_model_visible(request.name.clone(), &request.arguments, options);