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 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 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)]
#[non_exhaustive]
pub struct CreateAgentRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub name: 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", 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(),
system_prompt: system_prompt.into(),
description: None,
default_model_id: None,
tags: vec![],
capabilities: vec![],
initial_files: vec![],
}
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.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 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::getrandom(&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::getrandom(&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 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
}
}
#[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 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", 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,
agent_id: None,
title: None,
locale: None,
model_id: None,
tags: vec![],
capabilities: 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 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 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)]
#[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<'_>> {
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 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)]
#[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);
}
}