use std::{path::PathBuf, sync::Arc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{ContentBlock, Error};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ToolCall {
#[serde(rename = "toolCallId")]
pub id: ToolCallId,
pub title: String,
#[serde(default, skip_serializing_if = "ToolKind::is_default")]
pub kind: ToolKind,
#[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
pub status: ToolCallStatus,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub content: Vec<ToolCallContent>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub locations: Vec<ToolCallLocation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw_input: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw_output: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<serde_json::Value>,
}
impl ToolCall {
pub fn update(&mut self, fields: ToolCallUpdateFields) {
if let Some(title) = fields.title {
self.title = title;
}
if let Some(kind) = fields.kind {
self.kind = kind;
}
if let Some(status) = fields.status {
self.status = status;
}
if let Some(content) = fields.content {
self.content = content;
}
if let Some(locations) = fields.locations {
self.locations = locations;
}
if let Some(raw_input) = fields.raw_input {
self.raw_input = Some(raw_input);
}
if let Some(raw_output) = fields.raw_output {
self.raw_output = Some(raw_output);
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ToolCallUpdate {
#[serde(rename = "toolCallId")]
pub id: ToolCallId,
#[serde(flatten)]
pub fields: ToolCallUpdateFields,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<serde_json::Value>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ToolCallUpdateFields {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<ToolKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<ToolCallStatus>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<Vec<ToolCallContent>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locations: Option<Vec<ToolCallLocation>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw_input: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw_output: Option<serde_json::Value>,
}
impl TryFrom<ToolCallUpdate> for ToolCall {
type Error = Error;
fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
let ToolCallUpdate {
id,
fields:
ToolCallUpdateFields {
kind,
status,
title,
content,
locations,
raw_input,
raw_output,
},
meta: _,
} = update;
Ok(Self {
id,
title: title.ok_or_else(|| {
Error::invalid_params()
.with_data(serde_json::json!("title is required for a tool call"))
})?,
kind: kind.unwrap_or_default(),
status: status.unwrap_or_default(),
content: content.unwrap_or_default(),
locations: locations.unwrap_or_default(),
raw_input,
raw_output,
meta: None,
})
}
}
impl From<ToolCall> for ToolCallUpdate {
fn from(value: ToolCall) -> Self {
let ToolCall {
id,
title,
kind,
status,
content,
locations,
raw_input,
raw_output,
meta: _,
} = value;
Self {
id,
fields: ToolCallUpdateFields {
kind: Some(kind),
status: Some(status),
title: Some(title),
content: Some(content),
locations: Some(locations),
raw_input,
raw_output,
},
meta: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct ToolCallId(pub Arc<str>);
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ToolKind {
Read,
Edit,
Delete,
Move,
Search,
Execute,
Think,
Fetch,
SwitchMode,
#[default]
#[serde(other)]
Other,
}
impl ToolKind {
fn is_default(&self) -> bool {
matches!(self, ToolKind::Other)
}
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ToolCallStatus {
#[default]
Pending,
InProgress,
Completed,
Failed,
}
impl ToolCallStatus {
fn is_default(&self) -> bool {
matches!(self, ToolCallStatus::Pending)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ToolCallContent {
Content {
content: ContentBlock,
},
Diff {
#[serde(flatten)]
diff: Diff,
},
#[serde(rename_all = "camelCase")]
Terminal { terminal_id: crate::TerminalId },
}
impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
fn from(content: T) -> Self {
ToolCallContent::Content {
content: content.into(),
}
}
}
impl From<Diff> for ToolCallContent {
fn from(diff: Diff) -> Self {
ToolCallContent::Diff { diff }
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Diff {
pub path: PathBuf,
pub old_text: Option<String>,
pub new_text: String,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", rename_all = "camelCase")]
pub struct ToolCallLocation {
pub path: PathBuf,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<serde_json::Value>,
}