File size: 6,465 Bytes
1ba74bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45fe286
 
 
 
 
 
 
 
 
 
 
 
 
1ba74bb
 
 
 
 
 
 
 
 
 
 
 
45fe286
 
 
1ba74bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45fe286
 
 
1ba74bb
 
 
 
 
 
c20e35d
 
 
 
 
 
 
 
 
 
 
1ba74bb
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
"""
FastAPI server for the AI Agent.

Exposes REST endpoints for agent interaction with A2UI streaming support.
"""

import os
import json
from typing import Optional
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import uvicorn

from agent import get_agent, A2UIMessage

# ============================================================================
# Configuration
# ============================================================================

# Get configuration from environment
FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:3000")
API_PORT = int(os.getenv("API_PORT", 8000))
API_HOST = os.getenv("API_HOST", "0.0.0.0")
DEBUG = os.getenv("DEBUG", "False").lower() == "true"

# ============================================================================
# FastAPI App Setup
# ============================================================================

app = FastAPI(
    title="AI Agent API",
    description="API for AI Agent with A2UI streaming support",
    version="1.0.0"
)

# ============================================================================
# CORS Configuration (for frontend-backend communication)
# ============================================================================

cors_origins = [
    "http://localhost:3000",
    "http://localhost:5173",  # Vite default
    FRONTEND_URL,
]

if DEBUG:
    cors_origins.append("*")

app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ============================================================================
# Serve Static Frontend Files
# ============================================================================

frontend_dist = Path(__file__).parent.parent / "frontend" / "dist"
if frontend_dist.exists():
    app.mount("/static", StaticFiles(directory=str(frontend_dist), html=False), name="static")
    static_available = True
else:
    static_available = False

# ============================================================================
# Request/Response Models
# ============================================================================

class ChatRequest(BaseModel):
    """Chat request model."""
    message: str


class HealthResponse(BaseModel):
    """Health check response."""
    status: str
    message: str


# ============================================================================
# API Routes
# ============================================================================

@app.get("/health", response_model=HealthResponse)
async def health_check():
    """
    Health check endpoint.
    
    Returns:
        HealthResponse: Status of the API
    """
    return {
        "status": "healthy",
        "message": "AI Agent API is running"
    }


@app.post("/chat")
async def chat(request: ChatRequest):
    """
    Chat endpoint with A2UI streaming.
    
    Processes user message through the AI Agent and streams A2UI events.
    
    Args:
        request: ChatRequest containing user message
        
    Returns:
        StreamingResponse: Server-Sent Events stream of A2UI messages
    """
    if not request.message or not request.message.strip():
        raise HTTPException(status_code=400, detail="Message cannot be empty")
    
    try:
        agent = get_agent()
        
        def event_generator():
            """Generate A2UI events from agent processing."""
            try:
                for a2ui_message in agent.process_message(request.message.strip()):
                    # Convert A2UIMessage to dict
                    event_data = a2ui_message.to_dict()
                    
                    # Format as JSON and send as Server-Sent Event
                    json_data = json.dumps(event_data)
                    yield f"data: {json_data}\n\n"
            except Exception as e:
                import traceback
                error_msg = traceback.format_exc()
                print(f"ERROR in event_generator: {error_msg}")
                raise
        
        return StreamingResponse(
            event_generator(),
            media_type="text/event-stream",
            headers={
                "Cache-Control": "no-cache",
                "Connection": "keep-alive",
                "X-Accel-Buffering": "no",
            }
        )
    
    except Exception as e:
        import traceback
        error_msg = traceback.format_exc()
        print(f"ERROR in /chat: {error_msg}")
        raise HTTPException(
            status_code=500,
            detail=f"Error processing message: {str(e)}"
        )


@app.delete("/chat/history")
async def clear_history():
    """
    Clear chat history.
    
    Returns:
        dict: Confirmation message
    """
    try:
        agent = get_agent()
        agent.clear_history()
        return {
            "status": "success",
            "message": "Chat history cleared"
        }
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Error clearing history: {str(e)}"
        )


@app.get("/tools")
async def get_tools():
    """
    Get available tools.
    
    Returns:
        dict: List of available tools
    """
    try:
        agent = get_agent()
        return {
            "tools": agent.available_tools
        }
    except Exception as e:
        import traceback
        error_msg = traceback.format_exc()
        print(f"ERROR in /tools: {error_msg}")
        raise HTTPException(
            status_code=500,
            detail=f"Error getting tools: {str(e)}"
        )


# ============================================================================
# Serve Static Frontend Files (mount at root with lower precedence)
# ============================================================================

if frontend_dist.exists():
    app.mount("/", StaticFiles(directory=str(frontend_dist), html=True), name="static")
    static_available = True
else:
    static_available = False


# ============================================================================
# Main
# ============================================================================

if __name__ == "__main__":
    uvicorn.run(
        app,
        host=API_HOST,
        port=API_PORT,
        reload=DEBUG
    )