GT AI OS Community Edition v2.0.33
Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
154
apps/tenant-backend/app/schemas/agent.py
Normal file
154
apps/tenant-backend/app/schemas/agent.py
Normal file
@@ -0,0 +1,154 @@
|
||||
"""
|
||||
Agent schemas for GT 2.0 Tenant Backend
|
||||
|
||||
Pydantic models for agent-related API request/response validation.
|
||||
Implements comprehensive agent management per CLAUDE.md specifications.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
class AgentTemplate(BaseModel):
|
||||
"""Agent template information"""
|
||||
id: str = Field(..., description="Template identifier")
|
||||
name: str = Field(..., description="Template display name")
|
||||
description: str = Field(..., description="Template description")
|
||||
icon: str = Field(..., description="Template icon emoji or URL")
|
||||
category: str = Field(..., description="Template category")
|
||||
prompt: str = Field(..., description="System prompt template")
|
||||
default_capabilities: List[str] = Field(default_factory=list, description="Default capability grants")
|
||||
personality_config: Dict[str, Any] = Field(default_factory=dict, description="Personality configuration")
|
||||
resource_preferences: Dict[str, Any] = Field(default_factory=dict, description="Resource preferences")
|
||||
|
||||
|
||||
class AgentTemplateListResponse(BaseModel):
|
||||
"""Response for listing agent templates"""
|
||||
templates: List[AgentTemplate]
|
||||
categories: List[str] = Field(default_factory=list, description="Available categories")
|
||||
total: int
|
||||
|
||||
|
||||
class AgentCreate(BaseModel):
|
||||
"""Request to create a new agent"""
|
||||
name: str = Field(..., description="Agent name")
|
||||
description: Optional[str] = Field(None, description="Agent description")
|
||||
template_id: Optional[str] = Field(None, description="Template ID to use")
|
||||
category: Optional[str] = Field(None, description="Agent category")
|
||||
prompt_template: Optional[str] = Field(None, description="System prompt template")
|
||||
model: Optional[str] = Field(None, description="AI model identifier")
|
||||
model_id: Optional[str] = Field(None, description="AI model identifier (alias for model)")
|
||||
temperature: Optional[float] = Field(None, description="Model temperature parameter")
|
||||
# max_tokens removed - now determined by model configuration
|
||||
visibility: Optional[str] = Field(None, description="Agent visibility setting")
|
||||
dataset_connection: Optional[str] = Field(None, description="RAG dataset connection type")
|
||||
selected_dataset_ids: Optional[List[str]] = Field(None, description="Selected dataset IDs for RAG")
|
||||
personality_config: Optional[Dict[str, Any]] = Field(None, description="Personality configuration")
|
||||
resource_preferences: Optional[Dict[str, Any]] = Field(None, description="Resource preferences")
|
||||
tags: Optional[List[str]] = Field(None, description="Agent tags")
|
||||
disclaimer: Optional[str] = Field(None, max_length=500, description="Disclaimer text shown in chat")
|
||||
easy_prompts: Optional[List[str]] = Field(None, description="Quick-access preset prompts (max 10)")
|
||||
team_shares: Optional[List[Dict[str, Any]]] = Field(None, description="Team sharing configuration with per-user permissions")
|
||||
|
||||
model_config = ConfigDict(protected_namespaces=())
|
||||
|
||||
|
||||
class AgentUpdate(BaseModel):
|
||||
"""Request to update an agent"""
|
||||
name: Optional[str] = Field(None, description="New agent name")
|
||||
description: Optional[str] = Field(None, description="New agent description")
|
||||
category: Optional[str] = Field(None, description="Agent category")
|
||||
prompt_template: Optional[str] = Field(None, description="System prompt template")
|
||||
model: Optional[str] = Field(None, description="AI model identifier")
|
||||
temperature: Optional[float] = Field(None, description="Model temperature parameter")
|
||||
# max_tokens removed - now determined by model configuration
|
||||
visibility: Optional[str] = Field(None, description="Agent visibility setting")
|
||||
dataset_connection: Optional[str] = Field(None, description="RAG dataset connection type")
|
||||
selected_dataset_ids: Optional[List[str]] = Field(None, description="Selected dataset IDs for RAG")
|
||||
personality_config: Optional[Dict[str, Any]] = Field(None, description="Updated personality config")
|
||||
resource_preferences: Optional[Dict[str, Any]] = Field(None, description="Updated resource preferences")
|
||||
tags: Optional[List[str]] = Field(None, description="Updated tags")
|
||||
is_favorite: Optional[bool] = Field(None, description="Favorite status")
|
||||
disclaimer: Optional[str] = Field(None, max_length=500, description="Disclaimer text shown in chat")
|
||||
easy_prompts: Optional[List[str]] = Field(None, description="Quick-access preset prompts (max 10)")
|
||||
team_shares: Optional[List[Dict[str, Any]]] = Field(None, description="Update team sharing configuration")
|
||||
|
||||
|
||||
class AgentResponse(BaseModel):
|
||||
"""Response for agent operations"""
|
||||
id: str = Field(..., description="Agent UUID")
|
||||
name: str = Field(..., description="Agent name")
|
||||
description: Optional[str] = Field(None, description="Agent description")
|
||||
template_id: Optional[str] = Field(None, description="Template ID if created from template")
|
||||
category: Optional[str] = Field(None, description="Agent category")
|
||||
prompt_template: Optional[str] = Field(None, description="System prompt template")
|
||||
model: Optional[str] = Field(None, description="AI model identifier")
|
||||
temperature: Optional[float] = Field(None, description="Model temperature parameter")
|
||||
max_tokens: Optional[int] = Field(None, description="Maximum tokens for generation")
|
||||
visibility: Optional[str] = Field(None, description="Agent visibility setting")
|
||||
dataset_connection: Optional[str] = Field(None, description="RAG dataset connection type")
|
||||
selected_dataset_ids: Optional[List[str]] = Field(None, description="Selected dataset IDs for RAG")
|
||||
personality_config: Dict[str, Any] = Field(default_factory=dict, description="Personality configuration")
|
||||
resource_preferences: Dict[str, Any] = Field(default_factory=dict, description="Resource preferences")
|
||||
tags: List[str] = Field(default_factory=list, description="Agent tags")
|
||||
is_favorite: bool = Field(False, description="Favorite status")
|
||||
disclaimer: Optional[str] = Field(None, description="Disclaimer text shown in chat")
|
||||
easy_prompts: List[str] = Field(default_factory=list, description="Quick-access preset prompts")
|
||||
conversation_count: int = Field(0, description="Number of conversations")
|
||||
usage_count: int = Field(0, description="Number of conversations (alias for frontend compatibility)")
|
||||
total_cost_cents: int = Field(0, description="Total cost in cents")
|
||||
created_at: datetime = Field(..., description="Creation timestamp")
|
||||
updated_at: datetime = Field(..., description="Last update timestamp")
|
||||
# Creator information
|
||||
created_by_name: Optional[str] = Field(None, description="Full name of the user who created this agent")
|
||||
# Permission flags for frontend
|
||||
can_edit: bool = Field(False, description="Whether current user can edit this agent")
|
||||
can_delete: bool = Field(False, description="Whether current user can delete this agent")
|
||||
is_owner: bool = Field(False, description="Whether current user owns this agent")
|
||||
# Team sharing configuration
|
||||
team_shares: Optional[List[Dict[str, Any]]] = Field(None, description="Team sharing configuration with per-user permissions")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class AgentListResponse(BaseModel):
|
||||
"""Response for listing agents"""
|
||||
data: List[AgentResponse] = Field(..., description="List of agents")
|
||||
total: int = Field(..., description="Total number of agents")
|
||||
limit: int = Field(..., description="Query limit")
|
||||
offset: int = Field(..., description="Query offset")
|
||||
|
||||
|
||||
class AgentCapabilities(BaseModel):
|
||||
"""Agent capabilities and resource access"""
|
||||
agent_id: str = Field(..., description="Agent UUID")
|
||||
capabilities: List[Dict[str, Any]] = Field(default_factory=list, description="Granted capabilities")
|
||||
resource_preferences: Dict[str, Any] = Field(default_factory=dict, description="Resource preferences")
|
||||
allowed_tools: List[str] = Field(default_factory=list, description="Allowed tool integrations")
|
||||
total: int = Field(..., description="Total capability count")
|
||||
|
||||
|
||||
class AgentStatistics(BaseModel):
|
||||
"""Agent usage statistics"""
|
||||
agent_id: str = Field(..., description="Agent UUID")
|
||||
name: str = Field(..., description="Agent name")
|
||||
created_at: datetime = Field(..., description="Creation timestamp")
|
||||
last_used_at: Optional[datetime] = Field(None, description="Last usage timestamp")
|
||||
conversation_count: int = Field(0, description="Total conversations")
|
||||
total_messages: int = Field(0, description="Total messages processed")
|
||||
total_tokens_used: int = Field(0, description="Total tokens consumed")
|
||||
total_cost_cents: int = Field(0, description="Total cost in cents")
|
||||
total_cost_dollars: float = Field(0.0, description="Total cost in dollars")
|
||||
average_tokens_per_message: float = Field(0.0, description="Average tokens per message")
|
||||
is_favorite: bool = Field(False, description="Favorite status")
|
||||
tags: List[str] = Field(default_factory=list, description="Agent tags")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class AgentCloneRequest(BaseModel):
|
||||
"""Request to clone an agent"""
|
||||
new_name: str = Field(..., description="Name for the cloned agent")
|
||||
modifications: Optional[Dict[str, Any]] = Field(None, description="Modifications to apply")
|
||||
71
apps/tenant-backend/app/schemas/category.py
Normal file
71
apps/tenant-backend/app/schemas/category.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Category schemas for GT 2.0 Tenant Backend
|
||||
|
||||
Pydantic models for agent category API request/response validation.
|
||||
Supports tenant-scoped editable/deletable categories per Issue #215.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
|
||||
class CategoryCreate(BaseModel):
|
||||
"""Request to create a new category"""
|
||||
name: str = Field(..., min_length=1, max_length=100, description="Category display name")
|
||||
description: Optional[str] = Field(None, max_length=500, description="Category description")
|
||||
icon: Optional[str] = Field(None, max_length=10, description="Category icon (emoji)")
|
||||
|
||||
@field_validator('name')
|
||||
@classmethod
|
||||
def validate_name(cls, v: str) -> str:
|
||||
v = v.strip()
|
||||
if not v:
|
||||
raise ValueError('Category name cannot be empty')
|
||||
# Check for invalid characters (allow alphanumeric, spaces, hyphens, underscores)
|
||||
if not re.match(r'^[\w\s\-]+$', v):
|
||||
raise ValueError('Category name can only contain letters, numbers, spaces, hyphens, and underscores')
|
||||
return v
|
||||
|
||||
|
||||
class CategoryUpdate(BaseModel):
|
||||
"""Request to update a category"""
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100, description="New category name")
|
||||
description: Optional[str] = Field(None, max_length=500, description="New category description")
|
||||
icon: Optional[str] = Field(None, max_length=10, description="New category icon")
|
||||
|
||||
@field_validator('name')
|
||||
@classmethod
|
||||
def validate_name(cls, v: Optional[str]) -> Optional[str]:
|
||||
if v is None:
|
||||
return v
|
||||
v = v.strip()
|
||||
if not v:
|
||||
raise ValueError('Category name cannot be empty')
|
||||
if not re.match(r'^[\w\s\-]+$', v):
|
||||
raise ValueError('Category name can only contain letters, numbers, spaces, hyphens, and underscores')
|
||||
return v
|
||||
|
||||
|
||||
class CategoryResponse(BaseModel):
|
||||
"""Response for category operations"""
|
||||
id: str = Field(..., description="Category UUID")
|
||||
name: str = Field(..., description="Category display name")
|
||||
slug: str = Field(..., description="URL-safe category identifier")
|
||||
description: Optional[str] = Field(None, description="Category description")
|
||||
icon: Optional[str] = Field(None, description="Category icon (emoji)")
|
||||
is_default: bool = Field(..., description="Whether this is a system default category")
|
||||
created_by: Optional[str] = Field(None, description="UUID of user who created the category")
|
||||
created_by_name: Optional[str] = Field(None, description="Name of user who created the category")
|
||||
can_edit: bool = Field(..., description="Whether current user can edit this category")
|
||||
can_delete: bool = Field(..., description="Whether current user can delete this category")
|
||||
sort_order: int = Field(..., description="Display sort order")
|
||||
created_at: datetime = Field(..., description="Creation timestamp")
|
||||
updated_at: datetime = Field(..., description="Last update timestamp")
|
||||
|
||||
|
||||
class CategoryListResponse(BaseModel):
|
||||
"""Response for listing categories"""
|
||||
categories: List[CategoryResponse] = Field(default_factory=list, description="List of categories")
|
||||
total: int = Field(..., description="Total number of categories")
|
||||
81
apps/tenant-backend/app/schemas/conversation.py
Normal file
81
apps/tenant-backend/app/schemas/conversation.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Conversation schemas for GT 2.0 Tenant Backend
|
||||
|
||||
Pydantic models for conversation-related API request/response validation.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class ConversationCreate(BaseModel):
|
||||
"""Request to create a new conversation"""
|
||||
agent_id: str = Field(..., description="Agent UUID to chat with")
|
||||
title: Optional[str] = Field(None, description="Conversation title")
|
||||
initial_message: Optional[str] = Field(None, description="First message to send")
|
||||
|
||||
|
||||
class ConversationUpdate(BaseModel):
|
||||
"""Request to update a conversation"""
|
||||
title: Optional[str] = Field(None, description="New conversation title")
|
||||
system_prompt: Optional[str] = Field(None, description="Updated system prompt")
|
||||
|
||||
|
||||
class MessageCreate(BaseModel):
|
||||
"""Request to send a message"""
|
||||
content: str = Field(..., description="Message content")
|
||||
context_sources: Optional[List[str]] = Field(None, description="Context source IDs")
|
||||
metadata: Optional[Dict[str, Any]] = Field(None, description="Additional message metadata")
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
"""Message response"""
|
||||
id: Optional[str] = Field(None, description="Message ID")
|
||||
message_id: Optional[str] = Field(None, description="Message ID (alternative)")
|
||||
content: Optional[str] = Field(None, description="Message content")
|
||||
role: Optional[str] = Field(None, description="Message role")
|
||||
tokens_used: Optional[int] = Field(None, description="Tokens consumed")
|
||||
model_used: Optional[str] = Field(None, description="Model used for generation")
|
||||
context_sources: Optional[List[str]] = Field(None, description="RAG context source documents")
|
||||
created_at: Optional[datetime] = Field(None, description="Creation timestamp")
|
||||
stream: Optional[bool] = Field(None, description="Whether response is streamed")
|
||||
stream_endpoint: Optional[str] = Field(None, description="Stream endpoint URL")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
|
||||
|
||||
|
||||
class MessageListResponse(BaseModel):
|
||||
"""Response for listing messages"""
|
||||
messages: List[MessageResponse]
|
||||
conversation_id: int
|
||||
total: int
|
||||
|
||||
|
||||
class ConversationResponse(BaseModel):
|
||||
"""Conversation response"""
|
||||
id: int = Field(..., description="Conversation ID")
|
||||
title: str = Field(..., description="Conversation title")
|
||||
agent_id: str = Field(..., description="Agent ID")
|
||||
model_id: str = Field(..., description="Model identifier")
|
||||
system_prompt: Optional[str] = Field(None, description="System prompt")
|
||||
message_count: int = Field(0, description="Total message count")
|
||||
total_tokens: int = Field(0, description="Total tokens used")
|
||||
created_at: datetime = Field(..., description="Creation timestamp")
|
||||
updated_at: datetime = Field(..., description="Last update timestamp")
|
||||
messages: Optional[List[MessageResponse]] = Field(None, description="Conversation messages")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
|
||||
|
||||
|
||||
class ConversationWithUnread(ConversationResponse):
|
||||
"""Conversation response with unread message count"""
|
||||
unread_count: int = Field(0, description="Number of unread messages")
|
||||
|
||||
|
||||
class ConversationListResponse(BaseModel):
|
||||
"""Response for listing conversations"""
|
||||
conversations: List[ConversationResponse]
|
||||
total: int
|
||||
limit: int
|
||||
offset: int
|
||||
160
apps/tenant-backend/app/schemas/document.py
Normal file
160
apps/tenant-backend/app/schemas/document.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
Document Pydantic schemas for GT 2.0 Tenant Backend
|
||||
|
||||
Defines request/response schemas for document and RAG operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
from pydantic import BaseModel, Field, validator
|
||||
|
||||
|
||||
class DocumentResponse(BaseModel):
|
||||
"""Document response schema"""
|
||||
id: int
|
||||
uuid: str
|
||||
filename: str
|
||||
original_filename: str
|
||||
file_type: str
|
||||
file_extension: str
|
||||
file_size_bytes: int
|
||||
processing_status: str
|
||||
chunk_count: int
|
||||
content_summary: Optional[str] = None
|
||||
detected_language: Optional[str] = None
|
||||
content_type: Optional[str] = None
|
||||
keywords: List[str] = Field(default_factory=list)
|
||||
uploaded_by: str
|
||||
tags: List[str] = Field(default_factory=list)
|
||||
category: Optional[str] = None
|
||||
access_count: int = 0
|
||||
is_active: bool = True
|
||||
is_searchable: bool = True
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
processed_at: Optional[datetime] = None
|
||||
last_accessed_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class RAGDatasetCreate(BaseModel):
|
||||
"""Schema for creating a RAG dataset"""
|
||||
dataset_name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = Field(None, max_length=1000)
|
||||
chunking_strategy: str = Field(default="hybrid", pattern="^(fixed|semantic|hierarchical|hybrid)$")
|
||||
chunk_size: int = Field(default=512, ge=128, le=2048)
|
||||
chunk_overlap: int = Field(default=128, ge=0, le=512)
|
||||
embedding_model: str = Field(default="BAAI/bge-m3")
|
||||
|
||||
@validator('chunk_overlap')
|
||||
def validate_chunk_overlap(cls, v, values):
|
||||
if 'chunk_size' in values and v >= values['chunk_size']:
|
||||
raise ValueError('chunk_overlap must be less than chunk_size')
|
||||
return v
|
||||
|
||||
|
||||
class RAGDatasetResponse(BaseModel):
|
||||
"""RAG dataset response schema"""
|
||||
id: str
|
||||
user_id: str
|
||||
dataset_name: str
|
||||
description: Optional[str] = None
|
||||
chunking_strategy: str
|
||||
embedding_model: str
|
||||
chunk_size: int
|
||||
chunk_overlap: int
|
||||
document_count: int = 0
|
||||
chunk_count: int = 0
|
||||
vector_count: int = 0
|
||||
total_size_bytes: int = 0
|
||||
status: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class DocumentChunkResponse(BaseModel):
|
||||
"""Document chunk response schema"""
|
||||
id: str
|
||||
chunk_index: int
|
||||
chunk_metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
embedding_id: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class SearchRequest(BaseModel):
|
||||
"""Document search request schema"""
|
||||
query: str = Field(..., min_length=1, max_length=1000)
|
||||
dataset_ids: Optional[List[str]] = None
|
||||
top_k: int = Field(default=5, ge=1, le=20)
|
||||
similarity_threshold: float = Field(default=0.7, ge=0.0, le=1.0)
|
||||
|
||||
|
||||
class SearchResult(BaseModel):
|
||||
"""Document search result schema"""
|
||||
document_id: Optional[int] = None
|
||||
dataset_id: Optional[str] = None
|
||||
dataset_name: Optional[str] = None
|
||||
text: str
|
||||
similarity: float
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
filename: Optional[str] = None
|
||||
chunk_index: Optional[int] = None
|
||||
|
||||
|
||||
class SearchResponse(BaseModel):
|
||||
"""Document search response schema"""
|
||||
query: str
|
||||
results: List[SearchResult]
|
||||
total_results: int
|
||||
search_time_ms: Optional[float] = None
|
||||
|
||||
|
||||
class DocumentContextResponse(BaseModel):
|
||||
"""Document context response schema"""
|
||||
document_id: int
|
||||
document_name: str
|
||||
query: str
|
||||
relevant_chunks: List[SearchResult]
|
||||
context_text: str
|
||||
|
||||
|
||||
class RAGStatistics(BaseModel):
|
||||
"""RAG usage statistics schema"""
|
||||
user_id: str
|
||||
document_count: int
|
||||
dataset_count: int
|
||||
total_size_bytes: int
|
||||
total_size_mb: float
|
||||
total_chunks: int
|
||||
processed_documents: int
|
||||
pending_documents: int
|
||||
failed_documents: int
|
||||
|
||||
|
||||
class ProcessDocumentRequest(BaseModel):
|
||||
"""Document processing request schema"""
|
||||
chunking_strategy: Optional[str] = Field(default="hybrid", pattern="^(fixed|semantic|hierarchical|hybrid)$")
|
||||
|
||||
|
||||
class ProcessDocumentResponse(BaseModel):
|
||||
"""Document processing response schema"""
|
||||
status: str
|
||||
document_id: int
|
||||
chunk_count: int
|
||||
vector_store_ids: List[str]
|
||||
processing_time_ms: Optional[float] = None
|
||||
|
||||
|
||||
class UploadDocumentResponse(BaseModel):
|
||||
"""Document upload response schema"""
|
||||
document: DocumentResponse
|
||||
processing_initiated: bool = False
|
||||
message: str = "Document uploaded successfully"
|
||||
269
apps/tenant-backend/app/schemas/event.py
Normal file
269
apps/tenant-backend/app/schemas/event.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
Event Pydantic schemas for GT 2.0 Tenant Backend
|
||||
|
||||
Defines request/response schemas for event automation operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
from pydantic import BaseModel, Field, validator
|
||||
|
||||
|
||||
class EventActionCreate(BaseModel):
|
||||
"""Schema for creating an event action"""
|
||||
action_type: str = Field(..., description="Type of action to execute")
|
||||
config: Dict[str, Any] = Field(default_factory=dict, description="Action configuration")
|
||||
delay_seconds: int = Field(default=0, ge=0, le=3600, description="Delay before execution")
|
||||
retry_count: int = Field(default=3, ge=0, le=10, description="Number of retries on failure")
|
||||
retry_delay: int = Field(default=60, ge=1, le=3600, description="Delay between retries")
|
||||
condition: Optional[str] = Field(None, max_length=1000, description="Python expression for conditional execution")
|
||||
execution_order: int = Field(default=0, ge=0, description="Order of execution within subscription")
|
||||
|
||||
@validator('action_type')
|
||||
def validate_action_type(cls, v):
|
||||
valid_types = [
|
||||
'process_document', 'send_notification', 'update_statistics',
|
||||
'trigger_rag_indexing', 'log_analytics', 'execute_webhook',
|
||||
'create_assistant', 'schedule_task'
|
||||
]
|
||||
if v not in valid_types:
|
||||
raise ValueError(f'action_type must be one of: {", ".join(valid_types)}')
|
||||
return v
|
||||
|
||||
|
||||
class EventActionResponse(BaseModel):
|
||||
"""Event action response schema"""
|
||||
id: str
|
||||
action_type: str
|
||||
subscription_id: str
|
||||
config: Dict[str, Any]
|
||||
condition: Optional[str] = None
|
||||
delay_seconds: int
|
||||
retry_count: int
|
||||
retry_delay: int
|
||||
execution_order: int
|
||||
is_active: bool
|
||||
execution_count: int
|
||||
success_count: int
|
||||
failure_count: int
|
||||
last_executed_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EventSubscriptionCreate(BaseModel):
|
||||
"""Schema for creating an event subscription"""
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = Field(None, max_length=1000)
|
||||
event_type: str = Field(..., description="Type of event to subscribe to")
|
||||
actions: List[EventActionCreate] = Field(..., min_items=1, description="Actions to execute")
|
||||
filter_conditions: Dict[str, Any] = Field(default_factory=dict, description="Conditions for subscription activation")
|
||||
|
||||
@validator('event_type')
|
||||
def validate_event_type(cls, v):
|
||||
valid_types = [
|
||||
'document.uploaded', 'document.processed', 'document.failed',
|
||||
'conversation.started', 'message.sent', 'agent.created',
|
||||
'rag.search_performed', 'user.login', 'user.activity',
|
||||
'system.health_check'
|
||||
]
|
||||
if v not in valid_types:
|
||||
raise ValueError(f'event_type must be one of: {", ".join(valid_types)}')
|
||||
return v
|
||||
|
||||
|
||||
class EventSubscriptionResponse(BaseModel):
|
||||
"""Event subscription response schema"""
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
event_type: str
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
trigger_id: Optional[str] = None
|
||||
filter_conditions: Dict[str, Any]
|
||||
is_active: bool
|
||||
trigger_count: int
|
||||
last_triggered_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
actions: List[EventActionResponse] = Field(default_factory=list)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EventResponse(BaseModel):
|
||||
"""Event response schema"""
|
||||
id: int
|
||||
event_id: str
|
||||
event_type: str
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
payload: Dict[str, Any]
|
||||
metadata: Dict[str, Any]
|
||||
status: str
|
||||
error_message: Optional[str] = None
|
||||
retry_count: int
|
||||
created_at: datetime
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EventStatistics(BaseModel):
|
||||
"""Event statistics response schema"""
|
||||
total_events: int
|
||||
events_by_type: Dict[str, int]
|
||||
events_by_status: Dict[str, int]
|
||||
average_events_per_day: float
|
||||
|
||||
|
||||
class EventTriggerCreate(BaseModel):
|
||||
"""Schema for creating an event trigger"""
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = Field(None, max_length=1000)
|
||||
trigger_type: str = Field(..., description="Type of trigger")
|
||||
config: Dict[str, Any] = Field(default_factory=dict, description="Trigger configuration")
|
||||
conditions: Dict[str, Any] = Field(default_factory=dict, description="Trigger conditions")
|
||||
|
||||
@validator('trigger_type')
|
||||
def validate_trigger_type(cls, v):
|
||||
valid_types = [
|
||||
'schedule', 'webhook', 'file_watch', 'database_change',
|
||||
'api_call', 'user_action', 'system_event'
|
||||
]
|
||||
if v not in valid_types:
|
||||
raise ValueError(f'trigger_type must be one of: {", ".join(valid_types)}')
|
||||
return v
|
||||
|
||||
|
||||
class EventTriggerResponse(BaseModel):
|
||||
"""Event trigger response schema"""
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
trigger_type: str
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
config: Dict[str, Any]
|
||||
conditions: Dict[str, Any]
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
last_triggered_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ScheduledTaskResponse(BaseModel):
|
||||
"""Scheduled task response schema"""
|
||||
id: str
|
||||
task_type: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
scheduled_at: datetime
|
||||
executed_at: Optional[datetime] = None
|
||||
config: Dict[str, Any]
|
||||
context: Dict[str, Any]
|
||||
status: str
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
error_message: Optional[str] = None
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
retry_count: int
|
||||
max_retries: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EventLogResponse(BaseModel):
|
||||
"""Event log response schema"""
|
||||
id: int
|
||||
event_id: str
|
||||
log_level: str
|
||||
message: str
|
||||
details: Dict[str, Any]
|
||||
action_id: Optional[str] = None
|
||||
subscription_id: Optional[str] = None
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EmitEventRequest(BaseModel):
|
||||
"""Request schema for manually emitting events"""
|
||||
event_type: str = Field(..., description="Type of event to emit")
|
||||
data: Dict[str, Any] = Field(..., description="Event data payload")
|
||||
metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional metadata")
|
||||
|
||||
@validator('event_type')
|
||||
def validate_event_type(cls, v):
|
||||
valid_types = [
|
||||
'document.uploaded', 'document.processed', 'document.failed',
|
||||
'conversation.started', 'message.sent', 'agent.created',
|
||||
'rag.search_performed', 'user.login', 'user.activity',
|
||||
'system.health_check'
|
||||
]
|
||||
if v not in valid_types:
|
||||
raise ValueError(f'event_type must be one of: {", ".join(valid_types)}')
|
||||
return v
|
||||
|
||||
|
||||
class WebhookConfig(BaseModel):
|
||||
"""Configuration for webhook actions"""
|
||||
url: str = Field(..., description="Webhook URL")
|
||||
method: str = Field(default="POST", pattern="^(GET|POST|PUT|PATCH|DELETE)$")
|
||||
headers: Dict[str, str] = Field(default_factory=dict)
|
||||
timeout: int = Field(default=30, ge=1, le=300)
|
||||
retry_on_failure: bool = Field(default=True)
|
||||
|
||||
|
||||
class NotificationConfig(BaseModel):
|
||||
"""Configuration for notification actions"""
|
||||
type: str = Field(default="system", description="Notification type")
|
||||
message: str = Field(..., min_length=1, max_length=1000, description="Notification message")
|
||||
priority: str = Field(default="normal", pattern="^(low|normal|high|urgent)$")
|
||||
channels: List[str] = Field(default_factory=list, description="Notification channels")
|
||||
|
||||
|
||||
class DocumentProcessingConfig(BaseModel):
|
||||
"""Configuration for document processing actions"""
|
||||
chunking_strategy: str = Field(default="hybrid", pattern="^(fixed|semantic|hierarchical|hybrid)$")
|
||||
chunk_size: int = Field(default=512, ge=128, le=2048)
|
||||
chunk_overlap: int = Field(default=128, ge=0, le=512)
|
||||
auto_index: bool = Field(default=True, description="Automatically index in RAG system")
|
||||
|
||||
|
||||
class StatisticsUpdateConfig(BaseModel):
|
||||
"""Configuration for statistics update actions"""
|
||||
type: str = Field(..., description="Type of statistic to update")
|
||||
increment: int = Field(default=1, description="Amount to increment")
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
||||
|
||||
|
||||
class AssistantCreationConfig(BaseModel):
|
||||
"""Configuration for agent creation actions"""
|
||||
template_id: str = Field(default="general_assistant", description="Agent template ID")
|
||||
name: str = Field(..., min_length=1, max_length=255, description="Agent name")
|
||||
config_overrides: Dict[str, Any] = Field(default_factory=dict, description="Configuration overrides")
|
||||
|
||||
|
||||
class TaskSchedulingConfig(BaseModel):
|
||||
"""Configuration for task scheduling actions"""
|
||||
task_type: str = Field(..., description="Type of task to schedule")
|
||||
delay_minutes: int = Field(default=0, ge=0, description="Delay before execution")
|
||||
task_config: Dict[str, Any] = Field(default_factory=dict, description="Task configuration")
|
||||
max_retries: int = Field(default=3, ge=0, le=10, description="Maximum retry attempts")
|
||||
64
apps/tenant-backend/app/schemas/user.py
Normal file
64
apps/tenant-backend/app/schemas/user.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
User schemas for GT 2.0 Tenant Backend
|
||||
|
||||
Pydantic models for user-related API request/response validation.
|
||||
Implements user preferences management per GT 2.0 specifications.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class CustomCategory(BaseModel):
|
||||
"""User-defined custom category with metadata"""
|
||||
name: str = Field(..., description="Category name (lowercase, unique per user)")
|
||||
description: str = Field(..., description="Category description")
|
||||
created_at: Optional[str] = Field(None, description="ISO timestamp when category was created")
|
||||
|
||||
|
||||
class UserPreferences(BaseModel):
|
||||
"""User preferences stored in JSONB"""
|
||||
favorite_agent_ids: Optional[List[str]] = Field(default_factory=list, description="List of favorited agent UUIDs")
|
||||
custom_categories: Optional[List[CustomCategory]] = Field(default_factory=list, description="User's custom agent categories")
|
||||
# Future preferences can be added here
|
||||
|
||||
|
||||
class UserPreferencesResponse(BaseModel):
|
||||
"""Response for getting user preferences"""
|
||||
preferences: Dict[str, Any] = Field(..., description="User preferences dictionary")
|
||||
|
||||
|
||||
class UpdateUserPreferencesRequest(BaseModel):
|
||||
"""Request to update user preferences (merges with existing)"""
|
||||
preferences: Dict[str, Any] = Field(..., description="Preferences to merge with existing")
|
||||
|
||||
|
||||
class FavoriteAgentsResponse(BaseModel):
|
||||
"""Response for getting favorite agent IDs"""
|
||||
favorite_agent_ids: List[str] = Field(..., description="List of favorited agent UUIDs")
|
||||
|
||||
|
||||
class UpdateFavoriteAgentsRequest(BaseModel):
|
||||
"""Request to update favorite agent IDs (replaces existing list)"""
|
||||
agent_ids: List[str] = Field(..., description="List of agent UUIDs to set as favorites")
|
||||
|
||||
|
||||
class AddFavoriteAgentRequest(BaseModel):
|
||||
"""Request to add a single agent to favorites"""
|
||||
agent_id: str = Field(..., description="Agent UUID to add to favorites")
|
||||
|
||||
|
||||
class RemoveFavoriteAgentRequest(BaseModel):
|
||||
"""Request to remove a single agent from favorites"""
|
||||
agent_id: str = Field(..., description="Agent UUID to remove from favorites")
|
||||
|
||||
|
||||
class CustomCategoriesResponse(BaseModel):
|
||||
"""Response for getting custom categories"""
|
||||
categories: List[CustomCategory] = Field(..., description="List of user's custom categories")
|
||||
|
||||
|
||||
class UpdateCustomCategoriesRequest(BaseModel):
|
||||
"""Request to update custom categories (replaces entire list)"""
|
||||
categories: List[CustomCategory] = Field(..., description="Complete list of custom categories")
|
||||
Reference in New Issue
Block a user