use serde::{Deserialize, Serialize};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
use crate::localization::localized_tool_display_name;
use crate::typed_id::TurnId;
use super::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub enum CapabilityUsageKind {
Configured,
Resolved,
Exposed,
Invoked,
EffectRan,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct CapabilityUsageRecord {
pub capability_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability_name: Option<String>,
pub usage_kind: CapabilityUsageKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage_count: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duration_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct CapabilityUsageData {
pub records: Vec<CapabilityUsageRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolCallSummary {
pub id: String,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub narration: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_narration: Option<String>,
}
impl From<&ToolCall> for ToolCallSummary {
fn from(tc: &ToolCall) -> Self {
Self {
id: tc.id.clone(),
name: tc.name.clone(),
display_name: None,
narration: None,
completed_narration: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolDefinitionSummary {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability_name: Option<String>,
pub description: String,
}
impl From<&crate::tool_types::ToolDefinition> for ToolDefinitionSummary {
fn from(tool: &crate::tool_types::ToolDefinition) -> Self {
let capability_attribution = tool.capability_attribution();
Self {
name: tool.name().to_string(),
display_name: tool.display_name().map(|s| s.to_string()),
category: tool.category().map(|s| s.to_string()),
capability_id: capability_attribution.map(|(id, _)| id.to_string()),
capability_name: capability_attribution.and_then(|(_, name)| name.map(str::to_string)),
description: tool.description().to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ActStartedData {
pub tool_calls: Vec<ToolCallSummary>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub headline: Option<String>,
}
impl ActStartedData {
pub fn new(tool_calls: &[ToolCall]) -> Self {
Self::new_with_locale(tool_calls, None)
}
pub fn new_with_locale(tool_calls: &[ToolCall], locale: Option<&str>) -> Self {
Self {
tool_calls: tool_calls.iter().map(ToolCallSummary::from).collect(),
headline: render_group_headline_with_locale(
tool_calls,
&[],
ToolNarrationPhase::Started,
locale,
),
}
}
pub fn with_definitions(
tool_calls: &[ToolCall],
tool_defs: &[crate::tool_types::ToolDefinition],
) -> Self {
Self::with_definitions_and_locale(tool_calls, tool_defs, None)
}
pub fn with_definitions_and_locale(
tool_calls: &[ToolCall],
tool_defs: &[crate::tool_types::ToolDefinition],
locale: Option<&str>,
) -> Self {
let def_map: std::collections::HashMap<&str, &crate::tool_types::ToolDefinition> =
tool_defs.iter().map(|d| (d.name(), d)).collect();
Self {
tool_calls: tool_calls
.iter()
.map(|tc| {
let tool_def = def_map.get(tc.name.as_str()).copied();
let display_name = localized_tool_display_name(
&tc.name,
tool_def.and_then(|d| d.display_name()),
locale,
);
ToolCallSummary {
id: tc.id.clone(),
name: tc.name.clone(),
display_name,
narration: Some(render_tool_narration_with_locale(
tool_def,
tc,
ToolNarrationPhase::Started,
locale,
)),
completed_narration: Some(render_tool_narration_with_locale(
tool_def,
tc,
ToolNarrationPhase::Completed,
locale,
)),
}
})
.collect(),
headline: render_group_headline_with_locale(
tool_calls,
tool_defs,
ToolNarrationPhase::Started,
locale,
),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ActCompletedData {
pub completed: bool,
pub success_count: u32,
pub error_count: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub headline: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolStartedData {
pub tool_call: ToolCall,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_fingerprint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub narration: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolCompletedData {
pub tool_call_id: String,
pub tool_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_fingerprint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_result_fingerprint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
pub success: bool,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Vec<ContentPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub narration: Option<String>,
}
impl ToolCompletedData {
pub fn success(
tool_call_id: String,
tool_name: String,
result: Vec<ContentPart>,
duration_ms: Option<u64>,
) -> Self {
Self {
tool_call_id,
tool_name,
tool_call_fingerprint: None,
tool_result_fingerprint: None,
display_name: None,
success: true,
status: "success".to_string(),
result: Some(result),
error: None,
duration_ms,
capability_id: None,
capability_name: None,
narration: None,
}
}
pub fn failure(
tool_call_id: String,
tool_name: String,
status: String,
error: String,
duration_ms: Option<u64>,
) -> Self {
Self {
tool_call_id,
tool_name,
tool_call_fingerprint: None,
tool_result_fingerprint: None,
display_name: None,
success: false,
status,
result: None,
error: Some(error),
duration_ms,
capability_id: None,
capability_name: None,
narration: None,
}
}
pub fn with_display_name(mut self, display_name: Option<String>) -> Self {
self.display_name = display_name;
self
}
pub fn with_fingerprints(
mut self,
tool_call_fingerprint: String,
tool_result_fingerprint: String,
) -> Self {
self.tool_call_fingerprint = Some(tool_call_fingerprint);
self.tool_result_fingerprint = Some(tool_result_fingerprint);
self
}
pub fn with_narration(mut self, narration: Option<String>) -> Self {
self.narration = narration;
self
}
pub fn with_capability_attribution(
mut self,
capability_id: Option<String>,
capability_name: Option<String>,
) -> Self {
self.capability_id = capability_id;
self.capability_name = capability_name;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolProgressData {
pub tool_call_id: String,
pub tool_name: String,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolOutputDeltaData {
pub tool_call_id: String,
pub tool_name: String,
pub delta: String,
pub stream: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum TranscriptRepairAction {
Replay,
Synthesize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct TranscriptRepairedData {
pub tool_call_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
pub action: TranscriptRepairAction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolCallRepairedData {
#[cfg_attr(feature = "openapi", schema(value_type = String, example = "turn_01933b5a00007000800000000000001"))]
pub turn_id: TurnId,
pub tool_call_id: String,
pub tool_name: String,
pub outcome: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ToolCallRequestedData {
pub tool_calls: Vec<ToolCall>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_summaries: Vec<ToolCallSummary>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub headline: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_headline: Option<String>,
}
impl ToolCallRequestedData {
pub fn with_definitions(
tool_calls: &[ToolCall],
tool_defs: &[crate::tool_types::ToolDefinition],
) -> Self {
Self::with_definitions_and_locale(tool_calls, tool_defs, None)
}
pub fn with_definitions_and_locale(
tool_calls: &[ToolCall],
tool_defs: &[crate::tool_types::ToolDefinition],
locale: Option<&str>,
) -> Self {
let def_map: std::collections::HashMap<&str, &crate::tool_types::ToolDefinition> =
tool_defs.iter().map(|d| (d.name(), d)).collect();
let tool_summaries = tool_calls
.iter()
.map(|tool_call| {
let tool_def = def_map.get(tool_call.name.as_str()).copied();
ToolCallSummary {
id: tool_call.id.clone(),
name: tool_call.name.clone(),
display_name: localized_tool_display_name(
&tool_call.name,
tool_def.and_then(|def| def.display_name()),
locale,
),
narration: Some(render_tool_narration_with_locale(
tool_def,
tool_call,
ToolNarrationPhase::Waiting,
locale,
)),
completed_narration: Some(render_tool_narration_with_locale(
tool_def,
tool_call,
ToolNarrationPhase::Completed,
locale,
)),
}
})
.collect();
Self {
tool_calls: tool_calls.to_vec(),
tool_summaries,
headline: render_group_headline_with_locale(
tool_calls,
tool_defs,
ToolNarrationPhase::Waiting,
locale,
),
completed_headline: render_group_headline_with_locale(
tool_calls,
tool_defs,
ToolNarrationPhase::Completed,
locale,
),
}
}
}