use anyhow::Result;
use std::sync::Arc;
use tokio::sync::mpsc;
use crate::models::AgentRuntimeInfo;
use crate::models::a2a::Artifact;
use crate::services::SkillService;
use crate::services::a2a_server::processing::artifact::ArtifactBuilder;
use crate::services::a2a_server::processing::message::StreamEvent;
use systemprompt_models::{AiMessage, AiProvider, RequestContext};
pub fn build_artifacts_from_results(
tool_results: &[systemprompt_models::CallToolResult],
tool_calls: &[systemprompt_models::ToolCall],
tools: &[systemprompt_models::McpTool],
context_id_str: &str,
task_id_str: &str,
) -> Result<Vec<Artifact>> {
if tool_results.is_empty() {
tracing::info!("No tool_results - no artifacts expected");
return Ok(Vec::new());
}
let has_structured_content = tool_results.iter().any(|r| r.structured_content.is_some());
if !has_structured_content {
tracing::info!(
"No structured_content - ephemeral tool calls, skipping A2A artifact building"
);
return Ok(Vec::new());
}
tracing::info!(
"Tool results contain structured_content - building A2A artifacts from agentic MCP calls"
);
let artifact_builder = ArtifactBuilder::new(
tool_calls.to_vec(),
tool_results.to_vec(),
tools.to_vec(),
context_id_str.to_string(),
task_id_str.to_string(),
);
artifact_builder.build_artifacts()
}
pub struct SynthesizeFinalResponseParams<'a> {
pub tool_calls: &'a [systemprompt_models::ToolCall],
pub tool_results: &'a [systemprompt_models::CallToolResult],
pub artifacts: &'a [Artifact],
pub accumulated_text: &'a str,
pub ai_service: Arc<dyn AiProvider>,
pub agent_runtime: &'a AgentRuntimeInfo,
pub ai_messages_for_synthesis: Vec<AiMessage>,
pub tx: mpsc::UnboundedSender<StreamEvent>,
pub request_ctx: RequestContext,
pub skill_service: Arc<SkillService>,
}
pub async fn synthesize_final_response(params: SynthesizeFinalResponseParams<'_>) -> String {
use crate::services::a2a_server::processing::ai_executor::{
SynthesizeToolResultsParams, synthesize_tool_results_with_artifacts,
};
let SynthesizeFinalResponseParams {
tool_calls,
tool_results,
artifacts,
accumulated_text,
ai_service,
agent_runtime,
ai_messages_for_synthesis,
tx,
request_ctx,
skill_service,
} = params;
if !tool_calls.is_empty() && !tool_results.is_empty() {
tracing::info!(
tool_call_count = tool_calls.len(),
artifact_count = artifacts.len(),
"Synthesizing results from tool calls"
);
synthesize_tool_results_with_artifacts(SynthesizeToolResultsParams {
ai_service,
agent_runtime,
original_messages: ai_messages_for_synthesis,
initial_response: accumulated_text,
tool_calls,
tool_results,
artifacts,
tx,
request_context: request_ctx,
skill_service,
})
.await
.unwrap_or_else(|()| {
tracing::warn!("Synthesis failed, using initial response");
accumulated_text.to_string()
})
} else {
if tool_calls.is_empty() && !accumulated_text.is_empty() {
tracing::warn!(
response_len = accumulated_text.len(),
"Synthesis skipped: Agent produced text without tool calls"
);
}
accumulated_text.to_string()
}
}