"""FastAPI route definitions for Bridge V1.2 with UTF-8 support.""" from __future__ import annotations import sys from pathlib import Path from fastapi import APIRouter, HTTPException, Request import adapters as adapter_registry from core.run_manager import RunManager from models.schemas import CreateRunRequest, HealthResponse, RunResponse, RunStatus from policy.guard import check as policy_check def safe_print(text): """Print with UTF-8 encoding fallback for Windows CP1252.""" try: print(text) except UnicodeEncodeError: sys.stdout.buffer.write(text.encode('utf-8', errors='replace')) sys.stdout.buffer.write(b'\n') RUNS_DIR = Path(__file__).resolve().parents[1] / "runs" _manager = RunManager(RUNS_DIR) router = APIRouter() @router.get("/health", response_model=HealthResponse) def health(): """Returns server status and per-adapter mode (real/stub).""" import os from adapters import openai_adapter from controller.decision_model import DECISION_MODE print(f"[ENCODING DEBUG] sys.getdefaultencoding() = {sys.getdefaultencoding()}") print(f"[ENCODING DEBUG] sys.getfilesystemencoding() = {sys.getfilesystemencoding()}") print(f"[ENCODING DEBUG] LANG = {os.environ.get('LANG', 'NOT SET')}") print(f"[ENCODING DEBUG] PYTHONIOENCODING = {os.environ.get('PYTHONIOENCODING', 'NOT SET')}") return HealthResponse( status="ok", adapters=adapter_registry.available(), openai_mode=openai_adapter.mode(), decision_backend=DECISION_MODE, ollama_model=os.environ.get("OLLAMA_MODEL", "qwen2.5:7b"), ) @router.post("/runs", response_model=RunResponse, status_code=201) def create_run(body: CreateRunRequest): """One-shot run: create -> instruct -> execute -> persist -> done|forced. The response is the final completed state — status is never 'running' when this endpoint returns. FORCE AGENT ROUTING: All requests are forced to use 'agent' adapter. No fallback, no alternative adapters, no direct LLM calls. """ safe_print("=== RUNS ENDPOINT CALLED ===") safe_print("=== FORCE AGENT ROUTING ENABLED ===") # Force agent adapter - ignore request body adapter forced_adapter = "agent" safe_print(f"=== USING ADAPTER: {forced_adapter} ===") # Handle greetings directly - no need to run agent goal_lower = body.goal.strip().lower() greetings = { "привет": "Привет! Я Agent Maxim. Напиши, что хочешь найти - я помогу!", "hi": "Hi! I'm Agent Maxim. What would you like to find?", "hello": "Hello! I'm Agent Maxim. What would you like to find?", "здравствуй": "Здравствуй! Я Agent Maxim. Напиши, что хочешь найти - я помогу!", "приветик": "Приветик! Чем могу помочь?", "йо": "Йо! Чем могу помочь?", "прив": "Прив! Что ищем?", "здорово": "Здорово! Чем могу помочь?", "йоу": "Йоу! Что хочешь найти?", } if goal_lower in greetings: from datetime import datetime, timezone now = datetime.now(timezone.utc).isoformat() safe_print(f"=== GREETING DETECTED: {goal_lower} - returning quick response ===") return RunResponse( run_id=body.run_id or "greeting", goal=body.goal, status="done", adapter=forced_adapter, adapter_mode="greeting", instruction=body.goal, response=greetings[goal_lower], error=None, exit_code=0, created_at=now, updated_at=now, ) # DEBUG: Log encoding details safe_print(f"[ENCODING DEBUG] Raw goal: {body.goal}") safe_print(f"[ENCODING DEBUG] Repr goal: {repr(body.goal)}") safe_print(f"[ENCODING DEBUG] Goal type: {type(body.goal)}") safe_print(f"[ENCODING DEBUG] Goal bytes: {body.goal.encode('utf-8')}") # DEBUG: Log adapter info safe_print(f"[FORCE ROUTING] Requested adapter: {body.adapter}") safe_print(f"[FORCE ROUTING] Forced adapter: {forced_adapter}") safe_print(f"[FORCE ROUTING] No fallback, no alternatives - agent only") adapter_mod = adapter_registry.get(forced_adapter) if adapter_mod is None: safe_print("=== RETURNING RESPONSE (ERROR) ===") raise HTTPException( status_code=500, detail=f"Agent adapter not available - this should never happen", ) try: state = _manager.execute_run( goal=body.goal, adapter_name=forced_adapter, adapter_fn=adapter_mod.execute, # type: ignore[attr-defined] guard_fn=policy_check, run_id=body.run_id, ) except ValueError as exc: safe_print("=== RETURNING RESPONSE (ERROR) ===") raise HTTPException(status_code=409, detail=str(exc)) from exc safe_print("=== RETURNING RESPONSE (SUCCESS) ===") return RunResponse(**state) @router.get("/runs/{run_id}", response_model=RunResponse) def get_run(run_id: str): """Retrieve persisted run state.""" state = _manager.get_run(run_id) if state is None: raise HTTPException(status_code=404, detail=f"run '{run_id}' not found") return RunResponse(**state) @router.post("/runs/{run_id}/cancel") def cancel_run(run_id: str): """Cancel a running or pending run.""" state = _manager.get_run(run_id) if state is None: raise HTTPException(status_code=404, detail=f"run '{run_id}' not found") _manager.cancel_run(run_id) return {"message": f"run '{run_id}' cancellation requested"}