mockforge_http/handlers/
ai_studio.rs

1//! AI Studio HTTP handlers
2//!
3//! This module provides HTTP endpoints for AI Studio features:
4//! - API Architecture Critique
5//! - System Generation
6//! - Behavioral Simulation
7
8use axum::{
9    extract::{Path, State},
10    http::StatusCode,
11    response::Json,
12    routing::post,
13    Router,
14};
15use mockforge_core::{
16    ai_studio::{
17        api_critique::{ApiCritiqueEngine, CritiqueRequest},
18        artifact_freezer::{ArtifactFreezer, FreezeMetadata, FreezeRequest},
19        behavioral_simulator::{BehavioralSimulator, CreateAgentRequest, SimulateBehaviorRequest},
20        config::DeterministicModeConfig,
21        system_generator::{SystemGenerationRequest, SystemGenerator},
22    },
23    intelligent_behavior::IntelligentBehaviorConfig,
24};
25use serde::{Deserialize, Serialize};
26use std::collections::HashMap;
27use std::sync::Arc;
28use tokio::sync::{Mutex, RwLock};
29use tracing::{error, info, warn};
30
31/// State for AI Studio handlers
32#[derive(Clone)]
33pub struct AiStudioState {
34    /// API critique engine
35    pub critique_engine: Arc<ApiCritiqueEngine>,
36    /// System generator engine
37    pub system_generator: Arc<SystemGenerator>,
38    /// Behavioral simulator engine (wrapped in Mutex for mutability)
39    pub behavioral_simulator: Arc<Mutex<BehavioralSimulator>>,
40    /// Artifact freezer for storing critiques and systems
41    pub artifact_freezer: Arc<ArtifactFreezer>,
42    /// AI configuration (for metadata)
43    pub config: IntelligentBehaviorConfig,
44    /// Deterministic mode configuration (optional, from workspace)
45    pub deterministic_config: Option<DeterministicModeConfig>,
46    /// Workspace ID (optional, can be set per request)
47    pub workspace_id: Option<String>,
48    /// In-memory storage for generated systems (system_id -> GeneratedSystem)
49    pub system_storage: Arc<RwLock<HashMap<String, mockforge_core::ai_studio::system_generator::GeneratedSystem>>>,
50}
51
52impl AiStudioState {
53    /// Create new AI Studio state
54    pub fn new(config: IntelligentBehaviorConfig) -> Self {
55        let critique_engine = Arc::new(ApiCritiqueEngine::new(config.clone()));
56        let system_generator = Arc::new(SystemGenerator::new(config.clone()));
57        let behavioral_simulator = Arc::new(Mutex::new(BehavioralSimulator::new(config.clone())));
58        let artifact_freezer = Arc::new(ArtifactFreezer::new());
59        Self {
60            critique_engine,
61            system_generator,
62            behavioral_simulator,
63            artifact_freezer,
64            config,
65            deterministic_config: None,
66            workspace_id: None,
67            system_storage: Arc::new(RwLock::new(HashMap::new())),
68        }
69    }
70
71    /// Create with custom workspace ID and deterministic config
72    pub fn with_workspace(
73        mut self,
74        workspace_id: String,
75        deterministic_config: Option<DeterministicModeConfig>,
76    ) -> Self {
77        self.workspace_id = Some(workspace_id);
78        self.deterministic_config = deterministic_config;
79        self
80    }
81}
82
83/// Request body for API critique endpoint
84#[derive(Debug, Deserialize, Serialize)]
85pub struct ApiCritiqueRequest {
86    /// API schema (OpenAPI JSON, GraphQL SDL, or Protobuf)
87    pub schema: serde_json::Value,
88
89    /// Schema type: "openapi", "graphql", or "protobuf"
90    pub schema_type: String,
91
92    /// Optional focus areas for analysis
93    /// Valid values: "anti-patterns", "redundancy", "naming", "tone", "restructuring"
94    #[serde(default)]
95    pub focus_areas: Vec<String>,
96
97    /// Optional workspace ID
98    pub workspace_id: Option<String>,
99}
100
101/// Response for API critique endpoint
102#[derive(Debug, Serialize)]
103pub struct ApiCritiqueResponse {
104    /// The critique result
105    pub critique: mockforge_core::ai_studio::api_critique::ApiCritique,
106
107    /// Artifact ID if stored
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub artifact_id: Option<String>,
110
111    /// Whether the critique was frozen as an artifact
112    pub frozen: bool,
113}
114
115/// Analyze an API schema and generate critique
116///
117/// POST /api/v1/ai-studio/api-critique
118pub async fn api_critique_handler(
119    State(state): State<AiStudioState>,
120    Json(request): Json<ApiCritiqueRequest>,
121) -> std::result::Result<Json<ApiCritiqueResponse>, StatusCode> {
122    info!("API critique request received for schema type: {}", request.schema_type);
123
124    // Build critique request
125    let critique_request = CritiqueRequest {
126        schema: request.schema,
127        schema_type: request.schema_type,
128        focus_areas: request.focus_areas,
129        workspace_id: request.workspace_id.or_else(|| state.workspace_id.clone()),
130    };
131
132    // Generate critique
133    let critique = match state.critique_engine.analyze(&critique_request).await {
134        Ok(c) => c,
135        Err(e) => {
136            error!("Failed to generate API critique: {}", e);
137            return Err(StatusCode::INTERNAL_SERVER_ERROR);
138        }
139    };
140
141    // Store as artifact
142    let critique_json = match serde_json::to_value(&critique) {
143        Ok(v) => v,
144        Err(e) => {
145            error!("Failed to serialize critique: {}", e);
146            return Err(StatusCode::INTERNAL_SERVER_ERROR);
147        }
148    };
149
150    // Create metadata for artifact freezing
151    let freeze_request = FreezeRequest {
152        artifact_type: "api_critique".to_string(),
153        content: critique_json,
154        format: "json".to_string(),
155        path: None,
156        metadata: Some(FreezeMetadata {
157            llm_provider: Some(state.config.behavior_model.llm_provider.clone()),
158            llm_model: Some(state.config.behavior_model.model.clone()),
159            llm_version: None,
160            prompt_hash: None,
161            output_hash: None,
162            original_prompt: None,
163        }),
164    };
165
166    let frozen_artifact = match state.artifact_freezer.freeze(&freeze_request).await {
167        Ok(a) => a,
168        Err(e) => {
169            error!("Failed to freeze critique artifact: {}", e);
170            return Err(StatusCode::INTERNAL_SERVER_ERROR);
171        }
172    };
173
174    info!(
175        "API critique completed. Artifact path: {}, Tokens used: {:?}, Cost: ${:.4}",
176        frozen_artifact.path,
177        critique.tokens_used,
178        critique.cost_usd.unwrap_or(0.0)
179    );
180
181    Ok(Json(ApiCritiqueResponse {
182        critique,
183        artifact_id: Some(frozen_artifact.path),
184        frozen: true,
185    }))
186}
187
188/// Request body for system generation endpoint
189#[derive(Debug, Deserialize)]
190pub struct SystemGenerationHttpRequest {
191    /// Natural language description
192    pub description: String,
193
194    /// Output formats to generate
195    #[serde(default)]
196    pub output_formats: Vec<String>,
197
198    /// Optional workspace ID
199    pub workspace_id: Option<String>,
200
201    /// Optional system ID (for versioning)
202    pub system_id: Option<String>,
203}
204
205/// Response for system generation endpoint
206#[derive(Debug, Serialize)]
207pub struct SystemGenerationResponse {
208    /// Generated system
209    pub system: mockforge_core::ai_studio::system_generator::GeneratedSystem,
210}
211
212/// Request body for apply system design endpoint
213#[derive(Debug, Deserialize)]
214pub struct ApplySystemRequest {
215    /// Optional artifact IDs to apply (if None, applies all)
216    #[serde(default)]
217    pub artifact_ids: Option<Vec<String>>,
218}
219
220/// Response for apply system design endpoint
221#[derive(Debug, Serialize)]
222pub struct ApplySystemResponse {
223    /// Applied system result
224    pub applied: mockforge_core::ai_studio::system_generator::AppliedSystem,
225}
226
227/// Request body for freeze artifacts endpoint
228#[derive(Debug, Deserialize)]
229pub struct FreezeArtifactsRequest {
230    /// Artifact IDs to freeze
231    pub artifact_ids: Vec<String>,
232}
233
234/// Response for freeze artifacts endpoint
235#[derive(Debug, Serialize)]
236pub struct FreezeArtifactsResponse {
237    /// Frozen artifact paths
238    pub frozen_paths: Vec<String>,
239}
240
241/// Generate a complete system from natural language description
242///
243/// POST /api/v1/ai-studio/generate-system
244pub async fn generate_system_handler(
245    State(state): State<AiStudioState>,
246    Json(request): Json<SystemGenerationHttpRequest>,
247) -> std::result::Result<Json<SystemGenerationResponse>, StatusCode> {
248    info!("System generation request received");
249
250    let generation_request = SystemGenerationRequest {
251        description: request.description,
252        output_formats: request.output_formats,
253        workspace_id: request.workspace_id.or_else(|| state.workspace_id.clone()),
254        system_id: request.system_id,
255    };
256
257    let system = match state
258        .system_generator
259        .generate(&generation_request, state.deterministic_config.as_ref())
260        .await
261    {
262        Ok(s) => s,
263        Err(e) => {
264            error!("Failed to generate system: {}", e);
265            return Err(StatusCode::INTERNAL_SERVER_ERROR);
266        }
267    };
268
269    // Store the generated system in storage
270    {
271        let mut storage = state.system_storage.write().await;
272        storage.insert(system.system_id.clone(), system.clone());
273    }
274
275    info!(
276        "System generation completed. System ID: {}, Version: {}, Status: {}, Tokens: {:?}, Cost: ${:.4}",
277        system.system_id,
278        system.version,
279        system.status,
280        system.tokens_used,
281        system.cost_usd.unwrap_or(0.0)
282    );
283
284    Ok(Json(SystemGenerationResponse { system }))
285}
286
287/// Apply system design (freeze artifacts if deterministic mode requires it)
288///
289/// POST /api/v1/ai-studio/system/{system_id}/apply
290pub async fn apply_system_handler(
291    State(state): State<AiStudioState>,
292    Path(system_id): Path<String>,
293    Json(request): Json<ApplySystemRequest>,
294) -> std::result::Result<Json<ApplySystemResponse>, StatusCode> {
295    info!("Apply system request received for system: {}", system_id);
296
297    // Load the system from storage
298    let system = {
299        let storage = state.system_storage.read().await;
300        storage.get(&system_id).cloned()
301    };
302
303    let system = match system {
304        Some(s) => s,
305        None => {
306            warn!("System not found: {}", system_id);
307            return Err(StatusCode::NOT_FOUND);
308        }
309    };
310
311    // Apply the system design
312    let applied = match state
313        .system_generator
314        .apply_system_design(
315            &system,
316            state.deterministic_config.as_ref(),
317            request.artifact_ids.clone(),
318        )
319        .await
320    {
321        Ok(a) => a,
322        Err(e) => {
323            error!("Failed to apply system design: {}", e);
324            return Err(StatusCode::INTERNAL_SERVER_ERROR);
325        }
326    };
327
328    // Update the system status in storage if it was frozen
329    if applied.frozen {
330        let mut storage = state.system_storage.write().await;
331        if let Some(stored_system) = storage.get_mut(&system_id) {
332            stored_system.status = "frozen".to_string();
333        }
334    }
335
336    info!(
337        "System design applied. System ID: {}, Applied artifacts: {}, Frozen: {}",
338        applied.system_id,
339        applied.applied_artifacts.len(),
340        applied.frozen
341    );
342
343    Ok(Json(ApplySystemResponse { applied }))
344}
345
346/// Freeze specific artifacts manually
347///
348/// POST /api/v1/ai-studio/system/{system_id}/freeze
349pub async fn freeze_artifacts_handler(
350    State(state): State<AiStudioState>,
351    Path(system_id): Path<String>,
352    Json(request): Json<FreezeArtifactsRequest>,
353) -> std::result::Result<Json<FreezeArtifactsResponse>, StatusCode> {
354    info!(
355        "Freeze artifacts request received for system: {}, artifacts: {:?}",
356        system_id, request.artifact_ids
357    );
358
359    // Load the system from storage
360    let system = {
361        let storage = state.system_storage.read().await;
362        storage.get(&system_id).cloned()
363    };
364
365    let system = match system {
366        Some(s) => s,
367        None => {
368            warn!("System not found: {}", system_id);
369            return Err(StatusCode::NOT_FOUND);
370        }
371    };
372
373    // Freeze the specified artifacts
374    let frozen_paths = match state
375        .system_generator
376        .freeze_artifacts(&system, request.artifact_ids.clone())
377        .await
378    {
379        Ok(paths) => paths,
380        Err(e) => {
381            error!("Failed to freeze artifacts: {}", e);
382            return Err(StatusCode::INTERNAL_SERVER_ERROR);
383        }
384    };
385
386    // Update the system status in storage if all artifacts are frozen
387    if !frozen_paths.is_empty() {
388        let mut storage = state.system_storage.write().await;
389        if let Some(stored_system) = storage.get_mut(&system_id) {
390            // Check if all artifacts are now frozen
391            let all_frozen = stored_system
392                .artifacts
393                .values()
394                .all(|artifact| request.artifact_ids.contains(&artifact.artifact_id));
395            if all_frozen {
396                stored_system.status = "frozen".to_string();
397            }
398        }
399    }
400
401    info!(
402        "Artifacts frozen. System ID: {}, Frozen paths: {}",
403        system_id,
404        frozen_paths.len()
405    );
406
407    Ok(Json(FreezeArtifactsResponse {
408        frozen_paths,
409    }))
410}
411
412/// Request body for create agent endpoint
413#[derive(Debug, Deserialize)]
414pub struct CreateAgentHttpRequest {
415    /// Optional: Attach to existing persona ID
416    pub persona_id: Option<String>,
417
418    /// Optional: Behavior policy type
419    pub behavior_policy: Option<String>,
420
421    /// If true, generate new persona if needed
422    pub generate_persona: bool,
423
424    /// Optional workspace ID
425    pub workspace_id: Option<String>,
426}
427
428/// Response for create agent endpoint
429#[derive(Debug, Serialize)]
430pub struct CreateAgentResponse {
431    /// Created agent
432    pub agent: mockforge_core::ai_studio::behavioral_simulator::NarrativeAgent,
433}
434
435/// Request body for simulate behavior endpoint
436#[derive(Debug, Deserialize)]
437pub struct SimulateBehaviorHttpRequest {
438    /// Optional: Use existing agent ID
439    pub agent_id: Option<String>,
440
441    /// Optional: Attach to existing persona
442    pub persona_id: Option<String>,
443
444    /// Current app state
445    pub current_state: mockforge_core::ai_studio::behavioral_simulator::AppState,
446
447    /// Trigger event
448    pub trigger_event: Option<String>,
449
450    /// Optional workspace ID
451    pub workspace_id: Option<String>,
452}
453
454/// Create a new narrative agent
455///
456/// POST /api/v1/ai-studio/simulate-behavior/create-agent
457#[axum::debug_handler]
458pub async fn create_agent_handler(
459    State(state): State<AiStudioState>,
460    Json(request): Json<CreateAgentHttpRequest>,
461) -> std::result::Result<Json<CreateAgentResponse>, StatusCode> {
462    info!("Create agent request received");
463
464    let create_request = CreateAgentRequest {
465        persona_id: request.persona_id,
466        behavior_policy: request.behavior_policy,
467        generate_persona: request.generate_persona,
468        workspace_id: request.workspace_id.or_else(|| state.workspace_id.clone()),
469    };
470
471    let mut simulator = state.behavioral_simulator.lock().await;
472    let agent = match simulator.create_agent(&create_request).await {
473        Ok(a) => a,
474        Err(e) => {
475            error!("Failed to create agent: {}", e);
476            return Err(StatusCode::INTERNAL_SERVER_ERROR);
477        }
478    };
479
480    info!("Agent created: {}", agent.agent_id);
481
482    Ok(Json(CreateAgentResponse { agent }))
483}
484
485/// Simulate behavior based on current state
486///
487/// POST /api/v1/ai-studio/simulate-behavior
488#[axum::debug_handler]
489pub async fn simulate_behavior_handler(
490    State(state): State<AiStudioState>,
491    Json(request): Json<SimulateBehaviorHttpRequest>,
492) -> std::result::Result<
493    Json<mockforge_core::ai_studio::behavioral_simulator::SimulateBehaviorResponse>,
494    StatusCode,
495> {
496    info!("Simulate behavior request received");
497
498    let simulate_request = SimulateBehaviorRequest {
499        agent_id: request.agent_id,
500        persona_id: request.persona_id,
501        current_state: request.current_state,
502        trigger_event: request.trigger_event,
503        workspace_id: request.workspace_id.or_else(|| state.workspace_id.clone()),
504    };
505
506    let mut simulator = state.behavioral_simulator.lock().await;
507    let response = match simulator.simulate_behavior(&simulate_request).await {
508        Ok(r) => r,
509        Err(e) => {
510            error!("Failed to simulate behavior: {}", e);
511            return Err(StatusCode::INTERNAL_SERVER_ERROR);
512        }
513    };
514
515    info!(
516        "Behavior simulation completed. Intention: {:?}, Tokens: {:?}, Cost: ${:.4}",
517        response.intention,
518        response.tokens_used,
519        response.cost_usd.unwrap_or(0.0)
520    );
521
522    Ok(Json(response))
523}
524
525/// Build AI Studio router
526pub fn ai_studio_router(state: AiStudioState) -> Router {
527    let mut router = Router::new()
528        .route("/api-critique", post(api_critique_handler))
529        .route("/generate-system", post(generate_system_handler))
530        .route("/system/{system_id}/apply", post(apply_system_handler))
531        .route("/system/{system_id}/freeze", post(freeze_artifacts_handler))
532        .route("/simulate-behavior/create-agent", post(create_agent_handler))
533        .route("/simulate-behavior", post(simulate_behavior_handler));
534    router.with_state(state)
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540    use mockforge_core::intelligent_behavior::config::BehaviorModelConfig;
541
542    fn create_test_state() -> AiStudioState {
543        let config = IntelligentBehaviorConfig {
544            behavior_model: BehaviorModelConfig {
545                llm_provider: "ollama".to_string(),
546                model: "llama2".to_string(),
547                api_endpoint: Some("http://localhost:11434/api/chat".to_string()),
548                api_key: None,
549                temperature: 0.7,
550                max_tokens: 2000,
551                rules: mockforge_core::intelligent_behavior::types::BehaviorRules::default(),
552            },
553            ..Default::default()
554        };
555        AiStudioState::new(config)
556    }
557
558    #[test]
559    fn test_ai_studio_state_creation() {
560        let state = create_test_state();
561        // State should be created successfully
562        assert!(true);
563    }
564
565    #[test]
566    fn test_api_critique_request_serialization() {
567        let request = ApiCritiqueRequest {
568            schema: serde_json::json!({"openapi": "3.0.0"}),
569            schema_type: "openapi".to_string(),
570            focus_areas: vec!["anti-patterns".to_string()],
571            workspace_id: None,
572        };
573
574        let json = serde_json::to_string(&request).unwrap();
575        assert!(json.contains("openapi"));
576        assert!(json.contains("anti-patterns"));
577    }
578}