use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use starweaver_core::Metadata;
use starweaver_model::{ModelMessage, ModelRequestPart, ModelResponsePart};
use uuid::Uuid;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AgentInfo {
pub agent_id: String,
pub agent_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_agent_id: Option<String>,
}
impl AgentInfo {
#[must_use]
pub fn new(agent_id: impl Into<String>, agent_name: impl Into<String>) -> Self {
Self {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
parent_agent_id: None,
}
}
#[must_use]
pub fn with_parent_agent_id(mut self, parent_agent_id: impl Into<String>) -> Self {
self.parent_agent_id = Some(parent_agent_id.into());
self
}
}
pub type DeferredToolMetadata = Metadata;
pub type WrapperMetadata = Metadata;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ContextLifecycleState {
#[serde(default)]
pub entered: bool,
#[serde(default)]
pub stream_queue_enabled: bool,
#[serde(default)]
pub compact_depth: u32,
}
impl ContextLifecycleState {
#[must_use]
pub fn is_default(&self) -> bool {
self == &Self::default()
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ToolIdWrapper {
#[serde(default = "default_tool_id_prefix")]
pub prefix: String,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub tool_call_maps: BTreeMap<String, String>,
}
impl Default for ToolIdWrapper {
fn default() -> Self {
Self {
prefix: default_tool_id_prefix(),
tool_call_maps: BTreeMap::new(),
}
}
}
impl ToolIdWrapper {
pub fn clear(&mut self) {
self.tool_call_maps.clear();
}
pub fn upsert_tool_call_id(&mut self, tool_call_id: &str) -> String {
if tool_call_id.starts_with(&self.prefix) {
return tool_call_id.to_string();
}
if let Some(existing) = self.tool_call_maps.get(tool_call_id) {
return existing.clone();
}
let wrapped = format!("{}{}", self.prefix, Uuid::new_v4().simple());
self.tool_call_maps
.insert(tool_call_id.to_string(), wrapped.clone());
wrapped
}
pub fn wrap_messages(&mut self, message_history: &mut [ModelMessage]) {
for message in message_history {
self.wrap_message(message);
}
}
pub fn wrap_message(&mut self, message: &mut ModelMessage) {
match message {
ModelMessage::Request(request) => {
for part in &mut request.parts {
match part {
ModelRequestPart::ToolReturn(tool_return) => {
tool_return.tool_call_id =
self.upsert_tool_call_id(&tool_return.tool_call_id);
}
ModelRequestPart::RetryPrompt { tool_call_id, .. } => {
if let Some(id) = tool_call_id {
*id = self.upsert_tool_call_id(id);
}
}
ModelRequestPart::SystemPrompt { .. }
| ModelRequestPart::UserPrompt { .. }
| ModelRequestPart::Instruction { .. } => {}
}
}
}
ModelMessage::Response(response) => {
for part in &mut response.parts {
self.wrap_response_part(part);
}
}
}
}
pub fn wrap_response_part(&mut self, part: &mut ModelResponsePart) {
match part {
ModelResponsePart::ToolCall(call)
| ModelResponsePart::ProviderToolCall { call, .. } => {
call.id = self.upsert_tool_call_id(&call.id);
}
ModelResponsePart::NativeToolCall { payload, .. }
| ModelResponsePart::NativeToolReturn { payload, .. }
| ModelResponsePart::ProviderOpaque { payload, .. } => {
self.wrap_provider_payload(payload);
}
ModelResponsePart::Text { .. }
| ModelResponsePart::ProviderText { .. }
| ModelResponsePart::Thinking { .. }
| ModelResponsePart::ProviderThinking { .. }
| ModelResponsePart::File { .. }
| ModelResponsePart::Compaction { .. } => {}
}
}
pub fn wrap_provider_payload(&mut self, payload: &mut Value) {
let Some(object) = payload.as_object_mut() else {
return;
};
for key in ["tool_call_id", "toolUseId", "tool_use_id", "call_id", "id"] {
let Some(value) = object.get_mut(key) else {
continue;
};
let Some(id) = value.as_str().map(ToString::to_string) else {
continue;
};
*value = Value::String(self.upsert_tool_call_id(&id));
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tool_call_maps.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AgentStreamQueueRegistry {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub queues: Vec<String>,
}
impl AgentStreamQueueRegistry {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.queues.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ToolSearchState {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub loaded_tools: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub loaded_namespaces: Vec<String>,
}
impl ToolSearchState {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.loaded_tools.is_empty() && self.loaded_namespaces.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ToolSearchInvalidation {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub removed_tools: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub removed_namespaces: Vec<String>,
}
impl ToolSearchInvalidation {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.removed_tools.is_empty() && self.removed_namespaces.is_empty()
}
}
pub type ModelWrapperMetadata = Value;
fn default_tool_id_prefix() -> String {
"sw-tool-".to_string()
}