use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use utoipa::ToSchema;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SkillServiceRequirement {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub optional: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_port: Option<u16>,
pub status: ServiceStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SkillSummary {
pub name: String,
pub version: String,
pub description: String,
pub source: String,
pub runtime: String,
pub tools_count: usize,
pub instances_count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_used: Option<DateTime<Utc>>,
pub execution_count: u64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_services: Vec<SkillServiceRequirement>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SkillDetail {
#[serde(flatten)]
pub summary: SkillSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub full_description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
pub tools: Vec<ToolInfo>,
pub instances: Vec<InstanceInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ToolInfo {
pub name: String,
pub description: String,
pub parameters: Vec<ParameterInfo>,
pub streaming: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ParameterInfo {
pub name: String,
#[serde(rename = "type")]
pub param_type: String,
pub description: String,
pub required: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_value: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct InstanceInfo {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub is_default: bool,
pub config_keys: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct InstallSkillRequest {
pub source: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git_ref: Option<String>,
#[serde(default)]
pub force: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct InstallSkillResponse {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub tools_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ExecutionRequest {
pub skill: String,
pub tool: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instance: Option<String>,
#[serde(default)]
pub args: HashMap<String, serde_json::Value>,
#[serde(default)]
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_secs: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ExecutionResponse {
pub id: String,
pub status: ExecutionStatus,
pub output: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub duration_ms: u64,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ExecutionStatus {
Pending,
Running,
Success,
Failed,
Timeout,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ExecutionHistoryEntry {
pub id: String,
pub skill: String,
pub tool: String,
pub instance: String,
pub status: ExecutionStatus,
pub duration_ms: u64,
pub started_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SearchRequest {
pub query: String,
#[serde(default = "default_top_k")]
pub top_k: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub skill_filter: Option<String>,
#[serde(default)]
pub include_examples: bool,
}
fn default_top_k() -> usize {
5
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SearchResult {
pub id: String,
pub skill: String,
pub tool: String,
pub content: String,
pub score: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub rerank_score: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SearchResponse {
pub results: Vec<SearchResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_info: Option<QueryInfo>,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct QueryInfo {
pub normalized: String,
pub intent: String,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SearchConfigResponse {
pub embedding_provider: String,
pub embedding_model: String,
pub dimensions: usize,
pub vector_backend: String,
pub hybrid_search_enabled: bool,
pub reranking_enabled: bool,
pub indexed_documents: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdateSearchConfigRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vector_backend: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_hybrid: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_reranking: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AppConfig {
pub default_timeout_secs: u64,
pub max_concurrent_executions: usize,
pub enable_history: bool,
pub max_history_entries: usize,
pub search: SearchConfigResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdateAppConfigRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub default_timeout_secs: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_concurrent_executions: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_history: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_history_entries: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct HealthResponse {
pub status: String,
pub healthy: bool,
pub components: HashMap<String, ComponentHealth>,
pub version: String,
pub uptime_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ComponentHealth {
pub name: String,
pub healthy: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct VersionResponse {
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub build: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub commit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rust_version: Option<String>,
pub wasmtime_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ApiError {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
impl ApiError {
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
code: code.into(),
message: message.into(),
details: None,
}
}
pub fn with_details(mut self, details: serde_json::Value) -> Self {
self.details = Some(details);
self
}
pub fn not_found(resource: &str) -> Self {
Self::new("NOT_FOUND", format!("{} not found", resource))
}
pub fn bad_request(message: impl Into<String>) -> Self {
Self::new("BAD_REQUEST", message)
}
pub fn internal(message: impl Into<String>) -> Self {
Self::new("INTERNAL_ERROR", message)
}
pub fn validation(message: impl Into<String>) -> Self {
Self::new("VALIDATION_ERROR", message)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct PaginationParams {
#[serde(default = "default_page")]
pub page: usize,
#[serde(default = "default_per_page")]
pub per_page: usize,
}
fn default_page() -> usize {
1
}
fn default_per_page() -> usize {
20
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct PaginatedResponse<T> {
pub items: Vec<T>,
pub total: usize,
pub page: usize,
pub per_page: usize,
pub total_pages: usize,
}
impl<T> PaginatedResponse<T> {
pub fn new(items: Vec<T>, total: usize, page: usize, per_page: usize) -> Self {
let total_pages = (total + per_page - 1) / per_page;
Self {
items,
total,
page,
per_page,
total_pages,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ImportManifestRequest {
pub content: String,
#[serde(default)]
pub merge: bool,
#[serde(default)]
pub install: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ParsedSkill {
pub name: String,
pub source: String,
pub runtime: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub instances: Vec<ParsedInstance>,
#[serde(skip_serializing_if = "Option::is_none")]
pub docker_config: Option<DockerConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ParsedInstance {
pub name: String,
pub config_keys: Vec<String>,
pub env_keys: Vec<String>,
pub is_default: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct DockerConfig {
pub image: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub entrypoint: Option<String>,
#[serde(default)]
pub volumes: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub working_dir: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cpus: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub network: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ImportManifestResponse {
pub success: bool,
pub skills: Vec<ParsedSkill>,
pub skills_count: usize,
pub installed_count: usize,
#[serde(default)]
pub warnings: Vec<String>,
#[serde(default)]
pub errors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ValidateManifestRequest {
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ValidateManifestResponse {
pub valid: bool,
pub skills: Vec<ParsedSkill>,
#[serde(default)]
pub errors: Vec<String>,
#[serde(default)]
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ExportManifestRequest {
#[serde(default = "default_export_format")]
pub format: String,
#[serde(default)]
pub include_secrets: bool,
}
fn default_export_format() -> String {
"toml".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ExportManifestResponse {
pub content: String,
pub format: String,
pub skills_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ServiceStatus {
pub name: String,
pub running: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ServicesStatusResponse {
pub services: Vec<ServiceStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct StartServiceRequest {
pub service: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct StartServiceResponse {
pub success: bool,
pub status: ServiceStatus,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct StopServiceRequest {
pub service: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct TestConnectionRequest {
pub embedding_provider: String,
pub embedding_model: String,
pub vector_backend: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub qdrant_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ollama_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct TestConnectionResponse {
pub success: bool,
pub embedding_provider_status: ComponentHealth,
pub vector_backend_status: ComponentHealth,
pub duration_ms: u128,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct TestPipelineRequest {
pub embedding_provider: String,
pub embedding_model: String,
pub vector_backend: String,
pub enable_hybrid: bool,
pub enable_reranking: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub qdrant_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct TestPipelineResponse {
pub success: bool,
pub index_stats: PipelineIndexStats,
pub search_results: Vec<PipelineSearchResult>,
pub duration_ms: u128,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct PipelineIndexStats {
pub documents_indexed: usize,
pub indexing_duration_ms: u64,
pub embedding_duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct PipelineSearchResult {
pub id: String,
pub content: String,
pub score: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub rerank_score: Option<f32>,
pub metadata: DocumentMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct DocumentMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub skill_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AgentConfig {
pub runtime: AgentRuntime,
pub model_config: AgentModelConfig,
pub timeout_secs: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub claude_code_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
#[serde(rename_all = "kebab-case")]
pub enum AgentRuntime {
ClaudeCode,
Gemini,
OpenAI,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AgentModelConfig {
pub provider: String,
pub model: String,
pub temperature: f32,
pub max_tokens: usize,
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
runtime: AgentRuntime::ClaudeCode,
model_config: AgentModelConfig {
provider: "anthropic".to_string(),
model: "claude-sonnet-4".to_string(),
temperature: 0.7,
max_tokens: 4096,
},
timeout_secs: 300,
claude_code_path: None, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct GetAgentConfigResponse {
pub config: AgentConfig,
pub available_runtimes: Vec<RuntimeInfo>,
pub available_models: HashMap<String, Vec<ModelInfo>>,
pub claude_code_detected: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub claude_code_version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RuntimeInfo {
pub runtime: AgentRuntime,
pub name: String,
pub description: String,
pub supported_providers: Vec<String>,
pub available: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ModelInfo {
pub id: String,
pub name: String,
pub max_tokens: usize,
pub supports_tools: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdateAgentConfigRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub runtime: Option<AgentRuntime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_config: Option<AgentModelConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_secs: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub claude_code_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct IndexResponse {
pub success: bool,
pub documents_indexed: usize,
pub duration_ms: u64,
pub message: String,
pub stats: IndexStats,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct IndexStats {
pub documents_added: usize,
pub documents_updated: usize,
pub total_documents: usize,
pub index_size_bytes: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SubmitFeedbackRequest {
pub query: String,
pub result_id: String,
pub score: f32,
pub rank: usize,
pub feedback_type: String, #[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(default = "default_client_type")]
pub client_type: String,
}
fn default_client_type() -> String {
"http".to_string()
}
#[derive(Debug, Clone, Serialize)]
pub struct SubmitFeedbackResponse {
pub success: bool,
pub feedback_id: String,
pub message: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct GetFeedbackRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feedback_type: Option<String>,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default)]
pub offset: usize,
}
fn default_limit() -> usize {
100
}
#[derive(Debug, Clone, Serialize)]
pub struct GetFeedbackResponse {
pub feedback: Vec<FeedbackEntry>,
pub total_count: usize,
pub limit: usize,
pub offset: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct FeedbackEntry {
pub id: String,
pub query: String,
pub result_id: String,
pub score: f32,
pub rank: usize,
pub feedback_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
pub client_type: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnalyticsOverviewResponse {
pub total_searches: usize,
pub total_feedback: usize,
pub positive_feedback: usize,
pub negative_feedback: usize,
pub avg_latency_ms: f64,
pub avg_results: f64,
pub recent_searches: Vec<SearchHistorySummary>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SearchHistorySummary {
pub query: String,
pub results_count: usize,
pub duration_ms: u64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize)]
pub struct TopQueriesResponse {
pub queries: Vec<QueryStats>,
}
#[derive(Debug, Clone, Serialize)]
pub struct QueryStats {
pub query: String,
pub count: usize,
pub avg_results: f64,
pub avg_latency_ms: f64,
pub positive_feedback: usize,
pub negative_feedback: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct FeedbackStatsResponse {
pub by_type: Vec<FeedbackTypeCount>,
pub top_positive: Vec<ResultFeedbackSummary>,
pub top_negative: Vec<ResultFeedbackSummary>,
}
#[derive(Debug, Clone, Serialize)]
pub struct FeedbackTypeCount {
pub feedback_type: String,
pub count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct ResultFeedbackSummary {
pub result_id: String,
pub positive_count: usize,
pub negative_count: usize,
pub total_count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct SearchTimelineResponse {
pub timeline: Vec<TimelineDataPoint>,
}
#[derive(Debug, Clone, Serialize)]
pub struct TimelineDataPoint {
pub timestamp: DateTime<Utc>,
pub search_count: usize,
pub avg_latency_ms: f64,
}