use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AgentCapabilityConfig {
#[serde(rename = "ref")]
pub capability_ref: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub config: Option<serde_json::Value>,
}
impl AgentCapabilityConfig {
pub fn new(capability_ref: impl Into<String>) -> Self {
Self {
capability_ref: capability_ref.into(),
config: None,
}
}
pub fn config(mut self, config: serde_json::Value) -> Self {
self.config = Some(config);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClientSideTool {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deferrable: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct BuiltinTool {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deferrable: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub policy: Option<String>,
}
impl ClientSideTool {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
display_name: None,
category: None,
hints: None,
deferrable: None,
}
}
pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
self.display_name = Some(display_name.into());
self
}
}
impl BuiltinTool {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
display_name: None,
category: None,
hints: None,
deferrable: None,
policy: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ToolDefinition {
ClientSide(ClientSideTool),
Builtin(BuiltinTool),
}
impl ToolDefinition {
pub fn client_side(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self::ClientSide(ClientSideTool::new(name, description, parameters))
}
pub fn builtin(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self::Builtin(BuiltinTool::new(name, description, parameters))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CapabilityInfo {
pub id: String,
pub name: String,
pub description: String,
pub status: String,
#[serde(default)]
pub category: Option<String>,
#[serde(default)]
pub dependencies: Vec<String>,
#[serde(default)]
pub icon: Option<String>,
#[serde(default)]
pub is_mcp: bool,
#[serde(default)]
pub display_name: Option<String>,
#[serde(default)]
pub features: Vec<String>,
#[serde(default)]
pub is_skill: bool,
#[serde(default)]
pub risk_level: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Agent {
pub id: String,
pub name: String,
#[serde(default)]
pub display_name: Option<String>,
#[serde(default)]
pub description: Option<String>,
pub system_prompt: String,
#[serde(default)]
pub default_model_id: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub capabilities: Vec<AgentCapabilityConfig>,
#[serde(default)]
pub initial_files: Vec<InitialFile>,
pub status: AgentStatus,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentStatus {
Active,
Archived,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AgentVersionChangeKind {
Manual,
Patch,
Minor,
Major,
Import,
Rollback,
Fork,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AgentVersion {
pub id: String,
pub agent_id: String,
pub version_number: i32,
pub semver_major: i32,
pub semver_minor: i32,
pub semver_patch: i32,
pub version: String,
pub change_kind: AgentVersionChangeKind,
pub config_hash: String,
pub authored_config: serde_json::Value,
pub resolved_config: serde_json::Value,
pub created_at: String,
#[serde(default)]
pub created_by_principal_id: Option<String>,
#[serde(default)]
pub parent_version_id: Option<String>,
#[serde(default)]
pub source_version_id: Option<String>,
#[serde(default)]
pub summary: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AgentVersionDiffResponse {
pub from_version_id: String,
pub to_version_id: String,
pub authored_diff: serde_json::Value,
pub resolved_diff: serde_json::Value,
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct CreateAgentVersionRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub change_kind: Option<AgentVersionChangeKind>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
impl CreateAgentVersionRequest {
pub fn new() -> Self {
Self::default()
}
pub fn change_kind(mut self, change_kind: AgentVersionChangeKind) -> Self {
self.change_kind = Some(change_kind);
self
}
pub fn summary(mut self, summary: impl Into<String>) -> Self {
self.summary = Some(summary.into());
self
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct SetDefaultAgentVersionRequest {
pub version_id: String,
}
impl SetDefaultAgentVersionRequest {
pub fn new(version_id: impl Into<String>) -> Self {
Self {
version_id: version_id.into(),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct ForkAgentVersionRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl ForkAgentVersionRequest {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
display_name: None,
description: None,
}
}
pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
self.display_name = Some(display_name.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct RollbackAgentVersionRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub save_version: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
impl RollbackAgentVersionRequest {
pub fn new() -> Self {
Self::default()
}
pub fn save_version(mut self, save_version: bool) -> Self {
self.save_version = Some(save_version);
self
}
pub fn summary(mut self, summary: impl Into<String>) -> Self {
self.summary = Some(summary.into());
self
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateAgentRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
pub system_prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_model_id: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<AgentCapabilityConfig>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolDefinition>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub initial_files: Vec<InitialFile>,
}
impl CreateAgentRequest {
pub fn new(name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
Self {
id: None,
name: name.into(),
display_name: None,
system_prompt: system_prompt.into(),
description: None,
default_model_id: None,
tags: vec![],
capabilities: vec![],
tools: vec![],
initial_files: vec![],
}
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
self.display_name = Some(display_name.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn default_model_id(mut self, model_id: impl Into<String>) -> Self {
self.default_model_id = Some(model_id.into());
self
}
pub fn tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn capabilities(mut self, capabilities: Vec<AgentCapabilityConfig>) -> Self {
self.capabilities = capabilities;
self
}
pub fn tools(mut self, tools: Vec<ToolDefinition>) -> Self {
self.tools = tools;
self
}
pub fn initial_files(mut self, initial_files: Vec<InitialFile>) -> Self {
self.initial_files = initial_files;
self
}
}
pub fn generate_agent_id() -> String {
let mut bytes = [0u8; 16];
getrandom::fill(&mut bytes).expect("failed to generate random bytes");
let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
format!("agent_{}", hex)
}
pub fn generate_harness_id() -> String {
let mut bytes = [0u8; 16];
getrandom::fill(&mut bytes).expect("failed to generate random bytes");
let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
format!("harness_{}", hex)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Session {
pub id: String,
pub organization_id: String,
pub harness_id: String,
#[serde(default)]
pub agent_id: Option<String>,
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub locale: Option<String>,
#[serde(default)]
pub model_id: Option<String>,
#[serde(default)]
pub capabilities: Vec<AgentCapabilityConfig>,
pub status: SessionStatus,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub usage: Option<TokenUsage>,
#[serde(default)]
pub active_schedule_count: Option<i32>,
#[serde(default)]
pub features: Vec<String>,
#[serde(default)]
pub is_pinned: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SessionStatus {
Started,
Active,
Idle,
#[serde(rename = "waitingfortoolresults")]
WaitingForToolResults,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TokenUsage {
#[serde(default)]
pub input_tokens: u64,
#[serde(default)]
pub output_tokens: u64,
#[serde(default)]
pub cache_read_tokens: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ResourceStats {
pub session_count: u64,
pub active_session_count: u64,
pub idle_session_count: u64,
pub started_session_count: u64,
pub waiting_for_tool_results_session_count: u64,
pub execution_count: u64,
pub total_session_duration_ms: u64,
#[serde(default)]
pub avg_session_duration_ms: Option<u64>,
pub total_input_tokens: u64,
pub total_output_tokens: u64,
pub total_cache_read_tokens: u64,
pub total_cache_creation_tokens: u64,
#[serde(default)]
pub first_session_at: Option<String>,
#[serde(default)]
pub last_session_at: Option<String>,
#[serde(default)]
pub last_execution_at: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HealthCheckStatus {
Pending,
Running,
Completed,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HealthCheckSummary {
pub total: i32,
pub passed: i32,
pub failed: i32,
pub errored: i32,
pub pass_rate: f64,
pub avg_score: f64,
pub avg_turns: f64,
pub total_input_tokens: u64,
pub total_output_tokens: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HealthCheckCaseResult {
pub name: String,
pub user_message: String,
pub rubric: String,
pub passed: bool,
pub score: f64,
pub judge_reason: String,
pub deterministic_reason: String,
pub turns: i32,
pub latency_ms: u64,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub session_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HealthCheckRun {
pub id: String,
pub config_hash: String,
pub status: HealthCheckStatus,
pub created_at: String,
#[serde(default)]
pub agent_id: Option<String>,
#[serde(default)]
pub model_id: Option<String>,
#[serde(default)]
pub completed_at: Option<String>,
#[serde(default)]
pub error_message: Option<String>,
#[serde(default)]
pub summary: Option<HealthCheckSummary>,
#[serde(default)]
pub results: Option<Vec<HealthCheckCaseResult>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InitialFile {
pub path: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoding: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_readonly: Option<bool>,
}
impl InitialFile {
pub fn new(path: impl Into<String>, content: impl Into<String>) -> Self {
Self {
path: path.into(),
content: content.into(),
encoding: None,
is_readonly: None,
}
}
pub fn encoding(mut self, encoding: impl Into<String>) -> Self {
self.encoding = Some(encoding.into());
self
}
pub fn is_readonly(mut self, is_readonly: bool) -> Self {
self.is_readonly = Some(is_readonly);
self
}
}
fn validate_addressable_name(name: &str, label: &str) -> crate::error::Result<()> {
const MAX_LEN: usize = 64;
if name.len() > MAX_LEN {
return Err(crate::error::Error::Validation(format!(
"{} must be at most {} characters, got {}",
label,
MAX_LEN,
name.len()
)));
}
let valid = !name.is_empty()
&& name.split('-').all(|seg| {
!seg.is_empty()
&& seg
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
});
if !valid {
return Err(crate::error::Error::Validation(format!(
"{} must match pattern [a-z0-9]+(-[a-z0-9]+)*, got {:?}",
label, name
)));
}
Ok(())
}
pub fn validate_harness_name(name: &str) -> crate::error::Result<()> {
validate_addressable_name(name, "harness_name")
}
pub fn validate_agent_name(name: &str) -> crate::error::Result<()> {
validate_addressable_name(name, "agent_name")
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateSessionRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub harness_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub harness_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_id: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<AgentCapabilityConfig>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolDefinition>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub initial_files: Vec<InitialFile>,
}
impl Default for CreateSessionRequest {
fn default() -> Self {
Self::new()
}
}
impl CreateSessionRequest {
pub fn new() -> Self {
Self {
harness_id: None,
harness_name: None,
agent_id: None,
title: None,
locale: None,
model_id: None,
tags: vec![],
capabilities: vec![],
tools: vec![],
initial_files: vec![],
}
}
pub fn harness_id(mut self, harness_id: impl Into<String>) -> Self {
self.harness_id = Some(harness_id.into());
self
}
pub fn harness_name(mut self, harness_name: impl Into<String>) -> Self {
self.harness_name = Some(harness_name.into());
self
}
pub fn agent_id(mut self, agent_id: impl Into<String>) -> Self {
self.agent_id = Some(agent_id.into());
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn locale(mut self, locale: impl Into<String>) -> Self {
self.locale = Some(locale.into());
self
}
pub fn model_id(mut self, model_id: impl Into<String>) -> Self {
self.model_id = Some(model_id.into());
self
}
pub fn tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn capabilities(mut self, capabilities: Vec<AgentCapabilityConfig>) -> Self {
self.capabilities = capabilities;
self
}
pub fn tools(mut self, tools: Vec<ToolDefinition>) -> Self {
self.tools = tools;
self
}
pub fn initial_files(mut self, initial_files: Vec<InitialFile>) -> Self {
self.initial_files = initial_files;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ExternalActor {
pub actor_id: String,
pub source: String,
#[serde(default)]
pub actor_name: Option<String>,
#[serde(default)]
pub metadata: Option<std::collections::HashMap<String, String>>,
}
impl ExternalActor {
pub fn new(actor_id: impl Into<String>, source: impl Into<String>) -> Self {
Self {
actor_id: actor_id.into(),
source: source.into(),
actor_name: None,
metadata: None,
}
}
pub fn actor_name(mut self, name: impl Into<String>) -> Self {
self.actor_name = Some(name.into());
self
}
pub fn metadata(mut self, metadata: std::collections::HashMap<String, String>) -> Self {
self.metadata = Some(metadata);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Message {
pub id: String,
pub session_id: String,
pub sequence: u64,
pub role: MessageRole,
pub content: Vec<ContentPart>,
#[serde(default)]
pub thinking: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
pub created_at: String,
#[serde(default)]
pub external_actor: Option<ExternalActor>,
#[serde(default)]
pub phase: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MessageRole {
User,
Agent,
ToolResult,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
Text {
text: String,
},
Image {
url: Option<String>,
base64: Option<String>,
},
ImageFile {
image_id: String,
},
ToolCall {
id: String,
name: String,
arguments: serde_json::Value,
},
ToolResult {
tool_call_id: String,
result: Option<serde_json::Value>,
error: Option<String>,
},
}
impl ContentPart {
pub fn text(text: impl Into<String>) -> Self {
Self::Text { text: text.into() }
}
pub fn tool_result(tool_call_id: impl Into<String>, result: serde_json::Value) -> Self {
Self::ToolResult {
tool_call_id: tool_call_id.into(),
result: Some(result),
error: None,
}
}
pub fn tool_error(tool_call_id: impl Into<String>, error: impl Into<String>) -> Self {
Self::ToolResult {
tool_call_id: tool_call_id.into(),
result: None,
error: Some(error.into()),
}
}
pub fn is_tool_call(&self) -> bool {
matches!(self, Self::ToolCall { .. })
}
pub fn as_tool_call(&self) -> Option<ToolCallInfo<'_>> {
match self {
Self::ToolCall {
id,
name,
arguments,
} => Some(ToolCallInfo {
id,
name,
arguments,
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct ToolCallInfo<'a> {
pub id: &'a str,
pub name: &'a str,
pub arguments: &'a serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClientToolResult {
pub tool_call_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct SubmitToolResultsRequest {
pub tool_results: Vec<ClientToolResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SubmitToolResultsResponse {
pub accepted: u64,
pub status: String,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateMessageRequest {
pub message: MessageInput,
#[serde(skip_serializing_if = "Option::is_none")]
pub controls: Option<Controls>,
#[serde(skip_serializing_if = "Option::is_none")]
pub external_actor: Option<ExternalActor>,
}
impl CreateMessageRequest {
pub fn new(message: MessageInput) -> Self {
Self {
message,
controls: None,
external_actor: None,
}
}
pub fn user_text(text: impl Into<String>) -> Self {
Self::new(MessageInput::user_text(text))
}
pub fn tool_results(results: Vec<ContentPart>) -> Self {
Self::new(MessageInput::tool_results(results))
}
pub fn controls(mut self, controls: Controls) -> Self {
self.controls = Some(controls);
self
}
pub fn external_actor(mut self, actor: ExternalActor) -> Self {
self.external_actor = Some(actor);
self
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct MessageInput {
pub role: MessageRole,
pub content: Vec<ContentPart>,
}
impl MessageInput {
pub fn new(role: MessageRole, content: Vec<ContentPart>) -> Self {
Self { role, content }
}
pub fn user_text(text: impl Into<String>) -> Self {
Self::new(
MessageRole::User,
vec![ContentPart::Text { text: text.into() }],
)
}
pub fn tool_results(results: Vec<ContentPart>) -> Self {
Self::new(MessageRole::ToolResult, results)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Controls {
#[serde(skip_serializing_if = "Option::is_none")]
pub model_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
}
impl Default for Controls {
fn default() -> Self {
Self::new()
}
}
impl Controls {
pub fn new() -> Self {
Self {
model_id: None,
max_tokens: None,
temperature: None,
}
}
pub fn model_id(mut self, model_id: impl Into<String>) -> Self {
self.model_id = Some(model_id.into());
self
}
pub fn max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ListResponse<T> {
pub data: Vec<T>,
#[serde(default)]
pub total: u64,
#[serde(default)]
pub offset: u64,
#[serde(default)]
pub limit: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Event {
pub id: String,
#[serde(rename = "type")]
pub event_type: String,
pub ts: String,
pub session_id: String,
pub data: serde_json::Value,
#[serde(default)]
pub context: EventContext,
}
impl Event {
pub fn tool_calls(&self) -> Vec<ToolCallInfo<'_>> {
extract_tool_calls(&self.data)
}
}
pub fn extract_tool_calls(data: &serde_json::Value) -> Vec<ToolCallInfo<'_>> {
if let Some(tool_calls) = data.get("tool_calls").and_then(|c| c.as_array()) {
return tool_calls
.iter()
.filter_map(|part| {
Some(ToolCallInfo {
id: part.get("id")?.as_str()?,
name: part.get("name")?.as_str()?,
arguments: part.get("arguments")?,
})
})
.collect();
}
let Some(content) = data
.get("message")
.and_then(|m| m.get("content"))
.and_then(|c| c.as_array())
else {
return vec![];
};
content
.iter()
.filter_map(|part| {
if part.get("type")?.as_str()? != "tool_call" {
return None;
}
Some(ToolCallInfo {
id: part.get("id")?.as_str()?,
name: part.get("name")?.as_str()?,
arguments: part.get("arguments")?,
})
})
.collect()
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[non_exhaustive]
pub struct EventContext {
#[serde(default)]
pub turn_id: Option<String>,
#[serde(default)]
pub input_message_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Workspace {
pub id: String,
pub name: String,
pub status: String,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub archived_at: Option<String>,
#[serde(default)]
pub deleted_at: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateWorkspaceRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl CreateWorkspaceRequest {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct UpdateWorkspaceRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
}
impl UpdateWorkspaceRequest {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FileInfo {
pub id: String,
pub session_id: String,
pub path: String,
pub name: String,
pub is_directory: bool,
pub is_readonly: bool,
pub size_bytes: i64,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SessionFile {
pub id: String,
pub session_id: String,
pub path: String,
pub name: String,
pub is_directory: bool,
pub is_readonly: bool,
pub size_bytes: i64,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub content: Option<String>,
#[serde(default)]
pub encoding: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FileStat {
pub path: String,
pub name: String,
pub is_directory: bool,
pub is_readonly: bool,
pub size_bytes: i64,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateFileRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoding: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_directory: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_readonly: Option<bool>,
}
impl CreateFileRequest {
pub fn file(content: impl Into<String>) -> Self {
Self {
content: Some(content.into()),
encoding: None,
is_directory: None,
is_readonly: None,
}
}
pub fn directory() -> Self {
Self {
content: None,
encoding: None,
is_directory: Some(true),
is_readonly: None,
}
}
pub fn encoding(mut self, encoding: impl Into<String>) -> Self {
self.encoding = Some(encoding.into());
self
}
pub fn is_readonly(mut self, is_readonly: bool) -> Self {
self.is_readonly = Some(is_readonly);
self
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct UpdateFileRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoding: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_readonly: Option<bool>,
}
impl UpdateFileRequest {
pub fn content(content: impl Into<String>) -> Self {
Self {
content: Some(content.into()),
encoding: None,
is_readonly: None,
}
}
pub fn encoding(mut self, encoding: impl Into<String>) -> Self {
self.encoding = Some(encoding.into());
self
}
pub fn is_readonly(mut self, is_readonly: bool) -> Self {
self.is_readonly = Some(is_readonly);
self
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CopyFileRequest {
pub src_path: String,
pub dst_path: String,
}
impl CopyFileRequest {
pub fn new(src_path: impl Into<String>, dst_path: impl Into<String>) -> Self {
Self {
src_path: src_path.into(),
dst_path: dst_path.into(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct MoveFileRequest {
pub src_path: String,
pub dst_path: String,
}
impl MoveFileRequest {
pub fn new(src_path: impl Into<String>, dst_path: impl Into<String>) -> Self {
Self {
src_path: src_path.into(),
dst_path: dst_path.into(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct GrepRequest {
pub pattern: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_pattern: Option<String>,
}
impl GrepRequest {
pub fn new(pattern: impl Into<String>) -> Self {
Self {
pattern: pattern.into(),
path_pattern: None,
}
}
pub fn path_pattern(mut self, path_pattern: impl Into<String>) -> Self {
self.path_pattern = Some(path_pattern.into());
self
}
}
#[derive(Debug, Clone, Serialize)]
pub struct StatRequest {
pub path: String,
}
impl StatRequest {
pub fn new(path: impl Into<String>) -> Self {
Self { path: path.into() }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GrepMatch {
pub path: String,
pub line_number: u64,
pub line: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GrepResult {
pub path: String,
pub matches: Vec<GrepMatch>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteResponse {
pub deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Memory {
pub id: String,
pub name: String,
pub source_type: String,
pub source: serde_json::Value,
pub is_readonly: bool,
pub sync_status: String,
pub status: String,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub last_sync_error: Option<String>,
#[serde(default)]
pub last_synced_at: Option<String>,
#[serde(default)]
pub archived_at: Option<String>,
#[serde(default)]
pub deleted_at: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateMemoryRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<serde_json::Value>,
}
impl CreateMemoryRequest {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
source: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn source(mut self, source: serde_json::Value) -> Self {
self.source = Some(source);
self
}
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct UpdateMemoryRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<serde_json::Value>,
}
impl UpdateMemoryRequest {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn source(mut self, source: serde_json::Value) -> Self {
self.source = Some(source);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MemoryFileInfo {
pub path: String,
pub is_directory: bool,
pub size_bytes: i64,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub content_hash: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MemoryFile {
pub path: String,
pub content: String,
pub encoding: String,
pub size_bytes: i64,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub content_hash: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateMemoryFileRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoding: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_directory: Option<bool>,
}
impl CreateMemoryFileRequest {
pub fn file(content: impl Into<String>) -> Self {
Self {
content: Some(content.into()),
encoding: None,
is_directory: None,
}
}
pub fn directory() -> Self {
Self {
content: None,
encoding: None,
is_directory: Some(true),
}
}
pub fn encoding(mut self, encoding: impl Into<String>) -> Self {
self.encoding = Some(encoding.into());
self
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct UpdateMemoryFileRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoding: Option<String>,
}
impl UpdateMemoryFileRequest {
pub fn content(content: impl Into<String>) -> Self {
Self {
content: Some(content.into()),
encoding: None,
}
}
pub fn encoding(mut self, encoding: impl Into<String>) -> Self {
self.encoding = Some(encoding.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MemoryGrepResult {
pub path: String,
pub size_bytes: i64,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct AnalyzeAgentRequest {
pub system_prompt: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<AgentCapabilityConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<serde_json::Value>,
#[serde(rename = "mcpServers", skip_serializing_if = "Option::is_none")]
pub mcp_servers: Option<serde_json::Value>,
}
impl AnalyzeAgentRequest {
pub fn new(system_prompt: impl Into<String>) -> Self {
Self {
system_prompt: system_prompt.into(),
capabilities: Vec::new(),
tools: Vec::new(),
mcp_servers: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AgentAnalysisResponse {
pub findings: Vec<Finding>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Finding {
pub rule_id: String,
pub severity: String,
pub category: String,
pub source: String,
pub message: String,
#[serde(default)]
pub location: Option<FindingLocation>,
#[serde(default)]
pub fix: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FindingLocation {
pub field: String,
#[serde(default)]
pub start: Option<u64>,
#[serde(default)]
pub end: Option<u64>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct GuardrailsDryRunRequest {
pub config: serde_json::Value,
pub stage: String,
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
}
impl GuardrailsDryRunRequest {
pub fn new(
config: serde_json::Value,
stage: impl Into<String>,
text: impl Into<String>,
) -> Self {
Self {
config,
stage: stage.into(),
text: text.into(),
tool_name: None,
}
}
pub fn tool_name(mut self, tool_name: impl Into<String>) -> Self {
self.tool_name = Some(tool_name.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GuardrailsDryRunResponse {
pub hits: Vec<GuardrailsDryRunHit>,
pub blocked: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GuardrailsDryRunHit {
pub check_index: i32,
pub check_id: String,
pub stage: String,
pub rule_type: String,
pub action: String,
pub reason_code: String,
#[serde(default)]
pub matched: Option<String>,
#[serde(default)]
pub replacement: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GuardrailExamplesResponse {
pub examples: Vec<GuardrailExample>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GuardrailExample {
pub name: String,
pub display_name: String,
pub description: String,
pub tags: Vec<String>,
pub check_types: Vec<String>,
pub stages: Vec<String>,
pub data_egress: String,
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BudgetStatus {
Active,
Paused,
Exhausted,
Disabled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BudgetPeriod {
Rolling { window: String },
Calendar { unit: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Budget {
pub id: String,
pub organization_id: String,
pub subject_type: String,
pub subject_id: String,
pub currency: String,
pub limit: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub soft_limit: Option<f64>,
pub balance: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub period: Option<BudgetPeriod>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
pub status: BudgetStatus,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct CreateBudgetRequest {
pub subject_type: String,
pub subject_id: String,
pub currency: String,
pub limit: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub soft_limit: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub period: Option<BudgetPeriod>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
impl CreateBudgetRequest {
pub fn new(
subject_type: impl Into<String>,
subject_id: impl Into<String>,
currency: impl Into<String>,
limit: f64,
) -> Self {
Self {
subject_type: subject_type.into(),
subject_id: subject_id.into(),
currency: currency.into(),
limit,
soft_limit: None,
period: None,
metadata: None,
}
}
pub fn soft_limit(mut self, soft_limit: f64) -> Self {
self.soft_limit = Some(soft_limit);
self
}
pub fn period(mut self, period: BudgetPeriod) -> Self {
self.period = Some(period);
self
}
pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct UpdateBudgetRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub soft_limit: Option<Option<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
impl UpdateBudgetRequest {
pub fn new() -> Self {
Self {
limit: None,
soft_limit: None,
status: None,
metadata: None,
}
}
pub fn limit(mut self, limit: f64) -> Self {
self.limit = Some(limit);
self
}
pub fn soft_limit(mut self, soft_limit: Option<f64>) -> Self {
self.soft_limit = Some(soft_limit);
self
}
pub fn status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
}
impl Default for UpdateBudgetRequest {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize)]
pub struct TopUpRequest {
pub amount: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl TopUpRequest {
pub fn new(amount: f64) -> Self {
Self {
amount,
description: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LedgerEntry {
pub id: String,
pub budget_id: String,
pub amount: f64,
pub meter_source: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ref_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ref_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct BudgetCheckResult {
pub action: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub balance: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResumeSessionResponse {
pub resumed_budgets: i32,
pub session_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Connection {
pub provider: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct SetConnectionRequest {
pub api_key: String,
}
impl SetConnectionRequest {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SetSecretsRequest {
pub secrets: std::collections::HashMap<String, String>,
}
impl SetSecretsRequest {
pub fn new(secrets: std::collections::HashMap<String, String>) -> Self {
Self { secrets }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_response_deserializes_without_pagination_fields() {
let json = r#"{"data": [1, 2, 3]}"#;
let resp: ListResponse<i32> = serde_json::from_str(json).unwrap();
assert_eq!(resp.data, vec![1, 2, 3]);
assert_eq!(resp.total, 0);
assert_eq!(resp.offset, 0);
assert_eq!(resp.limit, 0);
}
#[test]
fn list_response_deserializes_with_pagination_fields() {
let json = r#"{"data": ["a"], "total": 10, "offset": 5, "limit": 25}"#;
let resp: ListResponse<String> = serde_json::from_str(json).unwrap();
assert_eq!(resp.data, vec!["a"]);
assert_eq!(resp.total, 10);
assert_eq!(resp.offset, 5);
assert_eq!(resp.limit, 25);
}
}