You are viewing: API Specification - Complete technical referenceSee also: User Guide | SDK Reference
- Component: Agent UI Server - FastAPI backend for the desktop chat application
- Module:
gaia.ui - Import:
from gaia.ui.server import create_app - Source:
src/gaia/ui/
Overview
The Agent UI Server is a FastAPI-based backend that powers the GAIA Chat desktop application. It provides REST API endpoints for session management, real-time chat with SSE streaming, document library management, and system health monitoring. Key Design Decisions:- SQLite for zero-configuration persistence (WAL mode for concurrent reads)
- FastAPI for automatic OpenAPI docs and Pydantic validation
- SSE (Server-Sent Events) for streaming chat responses
- Hybrid document model โ global library with per-session attachment
- Shared database between CLI and desktop app
Module Structure
Server (server.py)
App Factory
- CORS middleware (allows all origins for local use)
- Database initialization
- All API endpoint registration
- Static file serving (for web frontend, when
dist/build exists)
4200
Endpoint Registration
All endpoints are registered as closures withincreate_app(), sharing the db instance via closure scope. The database is also stored on app.state.db for test access.
Streaming Implementation
Chat streaming uses a thread-based producer/consumer pattern:- A background thread calls
AgentSDK.send_stream()synchronously - Chunks are placed into a
queue.Queue - The async generator polls the queue and yields SSE events
- On completion, the full response is saved to the database
Helper Functions
| Function | Description |
|---|---|
_session_to_response(session) | Convert DB dict to SessionResponse |
_message_to_response(msg) | Convert DB dict to MessageResponse (parses JSON rag_sources) |
_doc_to_response(doc) | Convert DB dict to DocumentResponse |
_sanitize_document_path(user_path) | Validate and sanitize user-provided file paths (null bytes, extension allowlist, path resolution) |
_sanitize_static_path(base_dir, user_path) | Validate URL paths for static file serving stay within base directory |
_validate_file_path(filepath) | Check path is absolute, no null bytes, and has allowed extension |
_compute_file_hash(filepath) | SHA-256 hash for document deduplication |
_index_document(filepath) | Index file via RAG SDK, returns chunk count |
_get_chat_response(db, session, request) | Non-streaming LLM response |
_stream_chat_response(db, session, request) | SSE streaming generator |
Database (database.py)
Schema
Four tables with foreign key relationships:Configuration
- Foreign keys: Enabled (
PRAGMA foreign_keys = ON) - Journal mode: WAL (
PRAGMA journal_mode = WAL) - Thread safety:
check_same_thread=False(FastAPI runs across threads) - Row factory:
sqlite3.Rowfor dict-like access
Transaction Management
Document Deduplication
Documents are deduplicated by SHA-256 file hash. If a document with the same hash already exists, the existing record is returned with an updatedlast_accessed_at timestamp.
Document Upload Paths
The server exposes two document upload endpoints, each for a different source of truth:| Endpoint | Input | Used By | Storage |
|---|---|---|---|
POST /api/documents/upload-path | JSON {filepath} โ absolute server-side path | File browser (user picks an existing file) | Indexes in place; server does NOT own the file |
POST /api/documents/upload | multipart/form-data file blob | Drag-and-drop in browser mode / modern Electron | File is persisted under ~/.gaia/documents/<uuid>.<ext> and owned by the server |
File objects never expose a filesystem path, and since Electron 32 File.path has been removed as well (see issue #728). It streams the upload with a running size check (capped at 20 MB), computes a SHA-256 hash, short-circuits on dedup by hash, and only then promotes the file into the managed directory via an atomic os.replace. On failure, partial and orphan files are cleaned up.
When a server-owned document is deleted (DELETE /api/documents/{id}), the on-disk file under ~/.gaia/documents/ is also unlinked as a best-effort cleanup. User-owned files from upload-path are left untouched.
Cascade Deletes
- Deleting a session cascades to its messages and session_documents entries
- Deleting a document cascades to session_documents entries
Models (models.py)
All 14 Pydantic models:
| Model | Type | Used By |
|---|---|---|
SystemStatus | Response | GET /api/system/status |
CreateSessionRequest | Request | POST /api/sessions |
UpdateSessionRequest | Request | PUT /api/sessions/{session_id} |
SessionResponse | Response | Session endpoints |
SessionListResponse | Response | GET /api/sessions |
ChatRequest | Request | POST /api/chat/send |
ChatResponse | Response | POST /api/chat/send (non-streaming) |
SourceInfo | Nested | RAG source citations |
MessageResponse | Response | GET /api/sessions/{session_id}/messages |
MessageListResponse | Response | GET /api/sessions/{session_id}/messages |
DocumentResponse | Response | Document endpoints |
DocumentListResponse | Response | GET /api/documents |
DocumentUploadRequest | Request | POST /api/documents/upload-path |
UploadFile (multipart) | Request | POST /api/documents/upload (blob) |
AttachDocumentRequest | Request | POST /api/sessions/{session_id}/documents |
Dependencies
Required
| Package | Purpose |
|---|---|
fastapi | Web framework |
uvicorn | ASGI server |
pydantic | Request/response validation |
httpx | Async HTTP client (Lemonade health checks) |
Optional
| Package | Purpose |
|---|---|
psutil | Memory availability check in system status |
gaia.chat.sdk | LLM communication (AgentSDK) |
gaia.rag.sdk | Document indexing (RAGSDK) |
Error Handling
| Error | HTTP Code | Behavior |
|---|---|---|
| Session not found | 404 | "Session not found" |
| Document not found | 404 | "Document not found" or "File not found: {path}" |
| Path is not a file | 400 | "Path is not a file" |
| Unsupported export format | 400 | "Unsupported format: {format}" |
| LLM error (non-streaming) | 200 | Error message in response text |
| LLM error (streaming) | 200 | SSE error event: {"type": "error", "content": "..."} |
| Database locked | 500 | SQLite WAL mode minimizes this |
Security Considerations
- CORS: Uses an allowlist of localhost origins plus ngrok regex. The standalone server (
python -m gaia.ui.server) binds tolocalhostby default. Note:gaia chat --uibinds to0.0.0.0for Electron/browser access - No authentication: Designed for single-user local use. Do not expose to untrusted networks
- File path validation: The
upload-pathendpoint sanitizes all user-provided paths:- Null byte rejection (prevents path injection)
- Path resolution to absolute canonical paths (eliminates
..traversal) - File extension allowlist (only document/code file types accepted)
- See
_sanitize_document_path()inserver.py
- Static file serving: URL paths are validated to stay within the
dist/directory via_sanitize_static_path(), preventing directory traversal - SQL injection: Prevented by parameterized queries throughout
- No telemetry: No data is sent externally