use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use starweaver_model::ToolDefinition;
use crate::run::AgentRunState;
use super::{validation::parse_output, OutputSchema, OutputValidationResult, OutputValue};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct OutputFunctionDefinition {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub parameters: Value,
}
impl OutputFunctionDefinition {
#[must_use]
pub fn new(name: impl Into<String>, parameters: Value) -> Self {
Self {
name: name.into(),
description: None,
parameters,
}
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn from_output_schema(schema: &OutputSchema) -> Self {
let mut definition = Self::new(schema.name.clone(), schema.schema.clone());
definition.description.clone_from(&schema.description);
definition
}
#[must_use]
pub fn tool_definition(&self) -> ToolDefinition {
ToolDefinition {
name: self.name.clone(),
description: self.description.clone(),
parameters: self.parameters.clone(),
return_schema: None,
strict: None,
sequential: None,
metadata: serde_json::Map::new(),
}
}
}
#[derive(Clone)]
pub struct SchemaOutputFunction {
schema: OutputSchema,
}
impl SchemaOutputFunction {
#[must_use]
pub const fn new(schema: OutputSchema) -> Self {
Self { schema }
}
}
#[async_trait]
impl OutputFunction for SchemaOutputFunction {
fn definition(&self) -> OutputFunctionDefinition {
OutputFunctionDefinition::from_output_schema(&self.schema)
}
async fn call(
&self,
_context: OutputFunctionContext,
arguments: Value,
) -> OutputValidationResult<OutputValue> {
parse_output(&arguments.to_string(), Some(&self.schema))
}
}
#[derive(Clone, Debug)]
pub struct OutputFunctionContext {
pub state: AgentRunState,
}
#[async_trait]
pub trait OutputFunction: Send + Sync {
fn definition(&self) -> OutputFunctionDefinition;
async fn call(
&self,
context: OutputFunctionContext,
arguments: Value,
) -> OutputValidationResult<OutputValue>;
}
pub struct FunctionOutputFunction<F> {
definition: OutputFunctionDefinition,
function: F,
}
impl<F> FunctionOutputFunction<F> {
#[must_use]
pub const fn new(definition: OutputFunctionDefinition, function: F) -> Self {
Self {
definition,
function,
}
}
}
#[async_trait]
impl<F, Fut> OutputFunction for FunctionOutputFunction<F>
where
F: Send + Sync + Fn(OutputFunctionContext, Value) -> Fut,
Fut: Send + std::future::Future<Output = OutputValidationResult<OutputValue>>,
{
fn definition(&self) -> OutputFunctionDefinition {
self.definition.clone()
}
async fn call(
&self,
context: OutputFunctionContext,
arguments: Value,
) -> OutputValidationResult<OutputValue> {
(self.function)(context, arguments).await
}
}