skill_web/api/
types.rs

1//! API request and response types
2//!
3//! These types mirror the skill-http API types for serialization.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8// ============================================================================
9// Pagination
10// ============================================================================
11
12/// Pagination query parameters
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14pub struct PaginationParams {
15    /// Page number (1-indexed)
16    #[serde(default = "default_page")]
17    pub page: usize,
18    /// Items per page
19    #[serde(default = "default_per_page")]
20    pub per_page: usize,
21}
22
23fn default_page() -> usize {
24    1
25}
26
27fn default_per_page() -> usize {
28    20
29}
30
31impl PaginationParams {
32    pub fn new(page: usize, per_page: usize) -> Self {
33        Self { page, per_page }
34    }
35}
36
37/// Paginated response wrapper
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct PaginatedResponse<T> {
40    /// Items for this page
41    pub items: Vec<T>,
42    /// Total number of items
43    pub total: usize,
44    /// Current page
45    pub page: usize,
46    /// Items per page
47    pub per_page: usize,
48    /// Total pages
49    pub total_pages: usize,
50}
51
52// ============================================================================
53// Skills
54// ============================================================================
55
56/// Host service requirement for a skill
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub struct SkillServiceRequirement {
59    pub name: String,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub description: Option<String>,
62    pub optional: bool,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub default_port: Option<u16>,
65    pub status: ServiceStatus,
66}
67
68/// Skill summary from API
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct SkillSummary {
71    pub name: String,
72    pub version: String,
73    pub description: String,
74    pub source: String,
75    pub runtime: String,
76    pub tools_count: usize,
77    pub instances_count: usize,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub last_used: Option<String>,
80    pub execution_count: u64,
81    #[serde(default, skip_serializing_if = "Vec::is_empty")]
82    pub required_services: Vec<SkillServiceRequirement>,
83}
84
85/// Skill detail from API
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87pub struct SkillDetail {
88    #[serde(flatten)]
89    pub summary: SkillSummary,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub full_description: Option<String>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub author: Option<String>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub repository: Option<String>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub license: Option<String>,
98    pub tools: Vec<ToolInfo>,
99    pub instances: Vec<InstanceInfo>,
100}
101
102/// Tool information
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104pub struct ToolInfo {
105    pub name: String,
106    pub description: String,
107    pub parameters: Vec<ParameterInfo>,
108    pub streaming: bool,
109}
110
111/// Parameter information
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub struct ParameterInfo {
114    pub name: String,
115    #[serde(rename = "type")]
116    pub param_type: String,
117    pub description: String,
118    pub required: bool,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub default_value: Option<String>,
121}
122
123/// Instance information
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct InstanceInfo {
126    pub name: String,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub description: Option<String>,
129    pub is_default: bool,
130    pub config_keys: Vec<String>,
131}
132
133/// Install skill request
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct InstallSkillRequest {
136    pub source: String,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub name: Option<String>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub git_ref: Option<String>,
141    #[serde(default)]
142    pub force: bool,
143}
144
145/// Install skill response
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct InstallSkillResponse {
148    pub success: bool,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub name: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub version: Option<String>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub error: Option<String>,
155    pub tools_count: usize,
156}
157
158// ============================================================================
159// Execution
160// ============================================================================
161
162/// Execution request
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct ExecutionRequest {
165    pub skill: String,
166    pub tool: String,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub instance: Option<String>,
169    #[serde(default)]
170    pub args: HashMap<String, serde_json::Value>,
171    #[serde(default)]
172    pub stream: bool,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub timeout_secs: Option<u64>,
175}
176
177/// Execution response
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179pub struct ExecutionResponse {
180    pub id: String,
181    pub status: ExecutionStatus,
182    pub output: String,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub error: Option<String>,
185    pub duration_ms: u64,
186    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
187    pub metadata: HashMap<String, String>,
188}
189
190/// Execution status
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
192#[serde(rename_all = "lowercase")]
193pub enum ExecutionStatus {
194    Pending,
195    Running,
196    Success,
197    Failed,
198    Timeout,
199    Cancelled,
200}
201
202/// Execution history entry
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct ExecutionHistoryEntry {
205    pub id: String,
206    pub skill: String,
207    pub tool: String,
208    pub instance: String,
209    pub status: ExecutionStatus,
210    pub duration_ms: u64,
211    pub started_at: String,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub error: Option<String>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub output: Option<String>,
216}
217
218// ============================================================================
219// Search
220// ============================================================================
221
222/// Search request
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct SearchRequest {
225    pub query: String,
226    #[serde(default = "default_top_k")]
227    pub top_k: usize,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub skill_filter: Option<String>,
230    #[serde(default)]
231    pub include_examples: bool,
232}
233
234fn default_top_k() -> usize {
235    5
236}
237
238/// Search result
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct SearchResult {
241    pub id: String,
242    pub skill: String,
243    pub tool: String,
244    pub content: String,
245    pub score: f32,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub rerank_score: Option<f32>,
248}
249
250/// Search response
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct SearchResponse {
253    pub results: Vec<SearchResult>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub query_info: Option<QueryInfo>,
256    pub duration_ms: u64,
257}
258
259/// Query processing information
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct QueryInfo {
262    pub normalized: String,
263    pub intent: String,
264    pub confidence: f32,
265}
266
267// ============================================================================
268// Configuration
269// ============================================================================
270
271/// Search configuration
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct SearchConfigResponse {
274    pub embedding_provider: String,
275    pub embedding_model: String,
276    pub dimensions: usize,
277    pub vector_backend: String,
278    pub hybrid_search_enabled: bool,
279    pub reranking_enabled: bool,
280    pub indexed_documents: usize,
281}
282
283/// Update search config request
284#[derive(Debug, Clone, Default, Serialize, Deserialize)]
285pub struct UpdateSearchConfigRequest {
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub embedding_provider: Option<String>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub embedding_model: Option<String>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub vector_backend: Option<String>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub enable_hybrid: Option<bool>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub enable_reranking: Option<bool>,
296}
297
298/// Response from indexing operation
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct IndexResponse {
301    pub success: bool,
302    pub documents_indexed: usize,
303    pub duration_ms: u64,
304    pub message: String,
305    pub stats: IndexStats,
306}
307
308/// Indexing statistics
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct IndexStats {
311    pub documents_added: usize,
312    pub documents_updated: usize,
313    pub total_documents: usize,
314    pub index_size_bytes: Option<usize>,
315}
316
317/// Application configuration
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct AppConfig {
320    pub default_timeout_secs: u64,
321    pub max_concurrent_executions: usize,
322    pub enable_history: bool,
323    pub max_history_entries: usize,
324    pub search: SearchConfigResponse,
325}
326
327/// Update app config request
328#[derive(Debug, Clone, Default, Serialize, Deserialize)]
329pub struct UpdateAppConfigRequest {
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub default_timeout_secs: Option<u64>,
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub max_concurrent_executions: Option<usize>,
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub enable_history: Option<bool>,
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub max_history_entries: Option<usize>,
338}
339
340// ============================================================================
341// Health & Version
342// ============================================================================
343
344/// Health check response
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct HealthResponse {
347    pub status: String,
348    pub healthy: bool,
349    pub components: HashMap<String, ComponentHealth>,
350    pub version: String,
351    pub uptime_secs: u64,
352}
353
354/// Component health
355#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct ComponentHealth {
357    pub name: String,
358    pub healthy: bool,
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub message: Option<String>,
361}
362
363/// Version response
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct VersionResponse {
366    pub version: String,
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub build: Option<String>,
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub commit: Option<String>,
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub rust_version: Option<String>,
373    pub wasmtime_version: String,
374}
375
376// =============================================================================
377// Manifest Import Types
378// =============================================================================
379
380/// Request to import a manifest configuration
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct ImportManifestRequest {
383    pub content: String,
384    #[serde(default)]
385    pub merge: bool,
386    #[serde(default)]
387    pub install: bool,
388}
389
390/// Parsed skill from manifest
391#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
392pub struct ParsedSkill {
393    pub name: String,
394    pub source: String,
395    pub runtime: String,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub description: Option<String>,
398    pub instances: Vec<ParsedInstance>,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub docker_config: Option<DockerConfig>,
401}
402
403/// Parsed instance from manifest
404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405pub struct ParsedInstance {
406    pub name: String,
407    pub config_keys: Vec<String>,
408    pub env_keys: Vec<String>,
409    pub is_default: bool,
410}
411
412/// Docker runtime configuration
413#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
414pub struct DockerConfig {
415    pub image: String,
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub entrypoint: Option<String>,
418    #[serde(default)]
419    pub volumes: Vec<String>,
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub working_dir: Option<String>,
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub memory: Option<String>,
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub cpus: Option<String>,
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub network: Option<String>,
428}
429
430/// Response from importing a manifest
431#[derive(Debug, Clone, Serialize, Deserialize)]
432pub struct ImportManifestResponse {
433    pub success: bool,
434    pub skills: Vec<ParsedSkill>,
435    pub skills_count: usize,
436    pub installed_count: usize,
437    #[serde(default)]
438    pub warnings: Vec<String>,
439    #[serde(default)]
440    pub errors: Vec<String>,
441}
442
443/// Request to validate manifest content
444#[derive(Debug, Clone, Serialize, Deserialize)]
445pub struct ValidateManifestRequest {
446    pub content: String,
447}
448
449/// Response from validating a manifest
450#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct ValidateManifestResponse {
452    pub valid: bool,
453    pub skills: Vec<ParsedSkill>,
454    #[serde(default)]
455    pub errors: Vec<String>,
456    #[serde(default)]
457    pub warnings: Vec<String>,
458}
459
460// ============================================================================
461// System Services
462// ============================================================================
463
464/// Status of a system service
465#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
466pub struct ServiceStatus {
467    pub name: String,
468    pub running: bool,
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub pid: Option<u32>,
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub port: Option<u16>,
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub url: Option<String>,
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub error: Option<String>,
477}
478
479/// Response listing all system services
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct ServicesStatusResponse {
482    pub services: Vec<ServiceStatus>,
483}
484
485/// Request to start a service
486#[derive(Debug, Clone, Serialize, Deserialize)]
487pub struct StartServiceRequest {
488    pub service: String,
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub port: Option<u16>,
491}
492
493/// Response from starting a service
494#[derive(Debug, Clone, Serialize, Deserialize)]
495pub struct StartServiceResponse {
496    pub success: bool,
497    pub status: ServiceStatus,
498    pub message: String,
499}
500
501/// Request to stop a service
502#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct StopServiceRequest {
504    pub service: String,
505}
506
507// ============================================================================
508// Vector DB Testing Types
509// ============================================================================
510
511/// Request to test search connection
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct TestConnectionRequest {
514    pub embedding_provider: String,
515    pub embedding_model: String,
516    pub vector_backend: String,
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub qdrant_url: Option<String>,
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub ollama_url: Option<String>,
521}
522
523/// Response from testing search connection
524#[derive(Debug, Clone, Serialize, Deserialize)]
525pub struct TestConnectionResponse {
526    pub success: bool,
527    pub embedding_provider_status: ComponentHealth,
528    pub vector_backend_status: ComponentHealth,
529    pub duration_ms: u128,
530    pub message: String,
531}
532
533/// Request to test full search pipeline
534#[derive(Debug, Clone, Serialize, Deserialize)]
535pub struct TestPipelineRequest {
536    pub embedding_provider: String,
537    pub embedding_model: String,
538    pub vector_backend: String,
539    pub enable_hybrid: bool,
540    pub enable_reranking: bool,
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub qdrant_url: Option<String>,
543}
544
545/// Response from testing search pipeline
546#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct TestPipelineResponse {
548    pub success: bool,
549    pub index_stats: PipelineIndexStats,
550    pub search_results: Vec<PipelineSearchResult>,
551    pub duration_ms: u128,
552    pub message: String,
553}
554
555/// Pipeline indexing statistics
556#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct PipelineIndexStats {
558    pub documents_indexed: usize,
559    pub indexing_duration_ms: u64,
560    pub embedding_duration_ms: u64,
561}
562
563/// Search result from pipeline
564#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
565pub struct PipelineSearchResult {
566    pub id: String,
567    pub content: String,
568    pub score: f32,
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub rerank_score: Option<f32>,
571    pub metadata: DocumentMetadata,
572}
573
574/// Document metadata
575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
576pub struct DocumentMetadata {
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub skill_name: Option<String>,
579    #[serde(skip_serializing_if = "Option::is_none")]
580    pub tool_name: Option<String>,
581    #[serde(default)]
582    pub tags: Vec<String>,
583}
584
585// ============================================================================
586// Agent Configuration Types
587// ============================================================================
588
589/// Agent runtime configuration
590#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct AgentConfig {
592    pub runtime: AgentRuntime,
593    pub model_config: AgentModelConfig,
594    pub timeout_secs: u64,
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub claude_code_path: Option<String>,
597}
598
599impl Default for AgentConfig {
600    fn default() -> Self {
601        Self {
602            runtime: AgentRuntime::ClaudeCode,
603            model_config: AgentModelConfig::default(),
604            timeout_secs: 300,
605            claude_code_path: None,
606        }
607    }
608}
609
610/// Agent runtime type
611#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
612#[serde(rename_all = "kebab-case")]
613pub enum AgentRuntime {
614    ClaudeCode,
615    Gemini,
616    #[serde(rename = "openai")]
617    OpenAI,
618    Custom,
619}
620
621/// Model configuration for agent
622#[derive(Debug, Clone, Serialize, Deserialize)]
623pub struct AgentModelConfig {
624    pub provider: String,
625    pub model: String,
626    pub temperature: f32,
627    pub max_tokens: usize,
628}
629
630impl Default for AgentModelConfig {
631    fn default() -> Self {
632        Self {
633            provider: "anthropic".to_string(),
634            model: "claude-sonnet-4".to_string(),
635            temperature: 0.7,
636            max_tokens: 4096,
637        }
638    }
639}
640
641/// Response with agent configuration
642#[derive(Debug, Clone, Serialize, Deserialize)]
643pub struct GetAgentConfigResponse {
644    pub config: AgentConfig,
645    pub available_runtimes: Vec<RuntimeInfo>,
646    pub available_models: HashMap<String, Vec<ModelInfo>>,
647    pub claude_code_detected: bool,
648    #[serde(skip_serializing_if = "Option::is_none")]
649    pub claude_code_version: Option<String>,
650}
651
652/// Information about an agent runtime
653#[derive(Debug, Clone, Serialize, Deserialize)]
654pub struct RuntimeInfo {
655    pub runtime: AgentRuntime,
656    pub name: String,
657    pub description: String,
658    pub supported_providers: Vec<String>,
659    pub available: bool,
660}
661
662/// Information about an LLM model
663#[derive(Debug, Clone, Serialize, Deserialize)]
664pub struct ModelInfo {
665    pub id: String,
666    pub name: String,
667    pub max_tokens: usize,
668    pub supports_tools: bool,
669}
670
671/// Request to update agent configuration
672#[derive(Debug, Clone, Default, Serialize, Deserialize)]
673pub struct UpdateAgentConfigRequest {
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub runtime: Option<AgentRuntime>,
676    #[serde(skip_serializing_if = "Option::is_none")]
677    pub model_config: Option<AgentModelConfig>,
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub timeout_secs: Option<u64>,
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub claude_code_path: Option<String>,
682}