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