kairo-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. image-service/main.py +178 -0
  2. infra/chat/app/main.py +84 -0
  3. kairo/backend/__init__.py +0 -0
  4. kairo/backend/api/__init__.py +0 -0
  5. kairo/backend/api/admin/__init__.py +23 -0
  6. kairo/backend/api/admin/audit.py +54 -0
  7. kairo/backend/api/admin/content.py +142 -0
  8. kairo/backend/api/admin/incidents.py +148 -0
  9. kairo/backend/api/admin/stats.py +125 -0
  10. kairo/backend/api/admin/system.py +87 -0
  11. kairo/backend/api/admin/users.py +279 -0
  12. kairo/backend/api/agents.py +94 -0
  13. kairo/backend/api/api_keys.py +85 -0
  14. kairo/backend/api/auth.py +116 -0
  15. kairo/backend/api/billing.py +41 -0
  16. kairo/backend/api/chat.py +72 -0
  17. kairo/backend/api/conversations.py +125 -0
  18. kairo/backend/api/device_auth.py +100 -0
  19. kairo/backend/api/files.py +83 -0
  20. kairo/backend/api/health.py +36 -0
  21. kairo/backend/api/images.py +80 -0
  22. kairo/backend/api/openai_compat.py +225 -0
  23. kairo/backend/api/projects.py +102 -0
  24. kairo/backend/api/usage.py +32 -0
  25. kairo/backend/api/webhooks.py +79 -0
  26. kairo/backend/app.py +297 -0
  27. kairo/backend/config.py +179 -0
  28. kairo/backend/core/__init__.py +0 -0
  29. kairo/backend/core/admin_auth.py +24 -0
  30. kairo/backend/core/api_key_auth.py +55 -0
  31. kairo/backend/core/database.py +28 -0
  32. kairo/backend/core/dependencies.py +70 -0
  33. kairo/backend/core/logging.py +23 -0
  34. kairo/backend/core/rate_limit.py +73 -0
  35. kairo/backend/core/security.py +29 -0
  36. kairo/backend/models/__init__.py +19 -0
  37. kairo/backend/models/agent.py +30 -0
  38. kairo/backend/models/api_key.py +25 -0
  39. kairo/backend/models/api_usage.py +29 -0
  40. kairo/backend/models/audit_log.py +26 -0
  41. kairo/backend/models/conversation.py +48 -0
  42. kairo/backend/models/device_code.py +30 -0
  43. kairo/backend/models/feature_flag.py +21 -0
  44. kairo/backend/models/image_generation.py +24 -0
  45. kairo/backend/models/incident.py +28 -0
  46. kairo/backend/models/project.py +28 -0
  47. kairo/backend/models/uptime_record.py +24 -0
  48. kairo/backend/models/usage.py +24 -0
  49. kairo/backend/models/user.py +49 -0
  50. kairo/backend/schemas/__init__.py +0 -0
  51. kairo/backend/schemas/admin/__init__.py +0 -0
  52. kairo/backend/schemas/admin/audit.py +28 -0
  53. kairo/backend/schemas/admin/content.py +53 -0
  54. kairo/backend/schemas/admin/stats.py +77 -0
  55. kairo/backend/schemas/admin/system.py +44 -0
  56. kairo/backend/schemas/admin/users.py +48 -0
  57. kairo/backend/schemas/agent.py +42 -0
  58. kairo/backend/schemas/api_key.py +30 -0
  59. kairo/backend/schemas/auth.py +57 -0
  60. kairo/backend/schemas/chat.py +26 -0
  61. kairo/backend/schemas/conversation.py +39 -0
  62. kairo/backend/schemas/device_auth.py +40 -0
  63. kairo/backend/schemas/image.py +15 -0
  64. kairo/backend/schemas/openai_compat.py +76 -0
  65. kairo/backend/schemas/project.py +21 -0
  66. kairo/backend/schemas/status.py +81 -0
  67. kairo/backend/schemas/usage.py +15 -0
  68. kairo/backend/services/__init__.py +0 -0
  69. kairo/backend/services/admin/__init__.py +0 -0
  70. kairo/backend/services/admin/audit_service.py +78 -0
  71. kairo/backend/services/admin/content_service.py +119 -0
  72. kairo/backend/services/admin/incident_service.py +94 -0
  73. kairo/backend/services/admin/stats_service.py +281 -0
  74. kairo/backend/services/admin/system_service.py +126 -0
  75. kairo/backend/services/admin/user_service.py +157 -0
  76. kairo/backend/services/agent_service.py +107 -0
  77. kairo/backend/services/api_key_service.py +66 -0
  78. kairo/backend/services/api_usage_service.py +126 -0
  79. kairo/backend/services/auth_service.py +101 -0
  80. kairo/backend/services/chat_service.py +501 -0
  81. kairo/backend/services/conversation_service.py +264 -0
  82. kairo/backend/services/device_auth_service.py +193 -0
  83. kairo/backend/services/email_service.py +55 -0
  84. kairo/backend/services/image_service.py +181 -0
  85. kairo/backend/services/llm_service.py +186 -0
  86. kairo/backend/services/project_service.py +109 -0
  87. kairo/backend/services/status_service.py +167 -0
  88. kairo/backend/services/stripe_service.py +78 -0
  89. kairo/backend/services/usage_service.py +150 -0
  90. kairo/backend/services/web_search_service.py +96 -0
  91. kairo/migrations/env.py +60 -0
  92. kairo/migrations/versions/001_initial.py +55 -0
  93. kairo/migrations/versions/002_usage_tracking_and_indexes.py +66 -0
  94. kairo/migrations/versions/003_username_to_email.py +21 -0
  95. kairo/migrations/versions/004_add_plans_and_verification.py +67 -0
  96. kairo/migrations/versions/005_add_projects.py +52 -0
  97. kairo/migrations/versions/006_add_image_generation.py +63 -0
  98. kairo/migrations/versions/007_add_admin_portal.py +107 -0
  99. kairo/migrations/versions/008_add_device_code_auth.py +76 -0
  100. kairo/migrations/versions/009_add_status_page.py +65 -0
  101. kairo/tools/extract_claude_data.py +465 -0
  102. kairo/tools/filter_claude_data.py +303 -0
  103. kairo/tools/generate_curated_data.py +157 -0
  104. kairo/tools/mix_training_data.py +295 -0
  105. kairo_code/__init__.py +3 -0
  106. kairo_code/agents/__init__.py +25 -0
  107. kairo_code/agents/architect.py +98 -0
  108. kairo_code/agents/audit.py +100 -0
  109. kairo_code/agents/base.py +463 -0
  110. kairo_code/agents/coder.py +155 -0
  111. kairo_code/agents/database.py +77 -0
  112. kairo_code/agents/docs.py +88 -0
  113. kairo_code/agents/explorer.py +62 -0
  114. kairo_code/agents/guardian.py +80 -0
  115. kairo_code/agents/planner.py +66 -0
  116. kairo_code/agents/reviewer.py +91 -0
  117. kairo_code/agents/security.py +94 -0
  118. kairo_code/agents/terraform.py +88 -0
  119. kairo_code/agents/testing.py +97 -0
  120. kairo_code/agents/uiux.py +88 -0
  121. kairo_code/auth.py +232 -0
  122. kairo_code/config.py +172 -0
  123. kairo_code/conversation.py +173 -0
  124. kairo_code/heartbeat.py +63 -0
  125. kairo_code/llm.py +291 -0
  126. kairo_code/logging_config.py +156 -0
  127. kairo_code/main.py +818 -0
  128. kairo_code/router.py +217 -0
  129. kairo_code/sandbox.py +248 -0
  130. kairo_code/settings.py +183 -0
  131. kairo_code/tools/__init__.py +51 -0
  132. kairo_code/tools/analysis.py +509 -0
  133. kairo_code/tools/base.py +417 -0
  134. kairo_code/tools/code.py +58 -0
  135. kairo_code/tools/definitions.py +617 -0
  136. kairo_code/tools/files.py +315 -0
  137. kairo_code/tools/review.py +390 -0
  138. kairo_code/tools/search.py +185 -0
  139. kairo_code/ui.py +418 -0
  140. kairo_code-0.1.0.dist-info/METADATA +13 -0
  141. kairo_code-0.1.0.dist-info/RECORD +144 -0
  142. kairo_code-0.1.0.dist-info/WHEEL +5 -0
  143. kairo_code-0.1.0.dist-info/entry_points.txt +2 -0
  144. kairo_code-0.1.0.dist-info/top_level.txt +4 -0
@@ -0,0 +1,77 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class OverviewStats(BaseModel):
5
+ total_users: int
6
+ active_today: int
7
+ active_7d: int
8
+ active_30d: int
9
+ pro_subscribers: int
10
+ max_subscribers: int
11
+ mrr: int
12
+ conversations_today: int
13
+ images_today: int
14
+
15
+
16
+ class UserGrowthEntry(BaseModel):
17
+ date: str
18
+ signups: int
19
+
20
+
21
+ class UsageStatsEntry(BaseModel):
22
+ date: str
23
+ total_tokens: int
24
+
25
+
26
+ class RevenueStats(BaseModel):
27
+ free_count: int
28
+ pro_count: int
29
+ max_count: int
30
+
31
+
32
+ class TopUserEntry(BaseModel):
33
+ user_id: str
34
+ email: str
35
+ total_tokens: int
36
+
37
+
38
+ # --- Analytics endpoint schemas ---
39
+
40
+
41
+ class DailyCountEntry(BaseModel):
42
+ date: str
43
+ count: int
44
+
45
+
46
+ class DailyCountResponse(BaseModel):
47
+ data: list[DailyCountEntry]
48
+
49
+
50
+ class DailyTokensEntry(BaseModel):
51
+ date: str
52
+ tokens: int
53
+
54
+
55
+ class DailyTokensResponse(BaseModel):
56
+ data: list[DailyTokensEntry]
57
+
58
+
59
+ class PlanDistributionEntry(BaseModel):
60
+ plan: str
61
+ count: int
62
+
63
+
64
+ class PlanDistributionResponse(BaseModel):
65
+ data: list[PlanDistributionEntry]
66
+
67
+
68
+ class TopUserAnalyticsEntry(BaseModel):
69
+ email: str
70
+ plan: str
71
+ total_tokens: int
72
+ conversations: int
73
+ images: int
74
+
75
+
76
+ class TopUserAnalyticsResponse(BaseModel):
77
+ data: list[TopUserAnalyticsEntry]
@@ -0,0 +1,44 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class ServiceHealth(BaseModel):
7
+ name: str
8
+ status: str # "healthy" | "degraded" | "down"
9
+ latency_ms: int | None = None
10
+ message: str | None = None
11
+
12
+
13
+ class HealthStatus(BaseModel):
14
+ overall: str # "healthy" | "degraded" | "down"
15
+ services: list[ServiceHealth]
16
+ checked_at: str
17
+
18
+
19
+ class FeatureFlagItem(BaseModel):
20
+ key: str
21
+ enabled: bool
22
+ updated_by: str | None = None
23
+ updated_at: str | None = None
24
+
25
+ model_config = {"from_attributes": True}
26
+
27
+
28
+ class FeatureFlagResponse(BaseModel):
29
+ flags: list[FeatureFlagItem]
30
+
31
+
32
+ class FeatureFlagUpdate(BaseModel):
33
+ key: str
34
+ enabled: bool
35
+ reason: str = ""
36
+
37
+
38
+ class ConfigEntry(BaseModel):
39
+ key: str
40
+ value: str
41
+
42
+
43
+ class ConfigResponse(BaseModel):
44
+ config: list[ConfigEntry]
@@ -0,0 +1,48 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class AdminUserListItem(BaseModel):
7
+ id: str
8
+ email: str
9
+ role: str
10
+ status: str
11
+ plan: str
12
+ email_verified: bool
13
+ created_at: datetime
14
+
15
+ model_config = {"from_attributes": True}
16
+
17
+
18
+ class AdminUserDetailResponse(BaseModel):
19
+ """Allowlisted fields only — never expose hashed_password, tokens, etc."""
20
+
21
+ id: str
22
+ email: str
23
+ role: str
24
+ status: str
25
+ plan: str
26
+ created_at: datetime
27
+ email_verified: bool
28
+ stripe_customer_id: str | None = None
29
+ stripe_subscription_id: str | None = None
30
+ daily_token_limit: int
31
+ monthly_token_limit: int
32
+
33
+ model_config = {"from_attributes": True}
34
+
35
+
36
+ class PlanChangeRequest(BaseModel):
37
+ plan: str = Field(..., description="New plan: free, pro, or max")
38
+ reason: str = Field(..., min_length=1, max_length=1000)
39
+
40
+
41
+ class StatusChangeRequest(BaseModel):
42
+ status: str = Field(..., description="New status: active, suspended, or banned")
43
+ reason: str = Field(..., min_length=1, max_length=1000)
44
+
45
+
46
+ class RoleChangeRequest(BaseModel):
47
+ role: str = Field(..., description="New role: user, moderator, admin, or superadmin")
48
+ reason: str = Field(..., min_length=1, max_length=1000)
@@ -0,0 +1,42 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class RegisterAgentRequest(BaseModel):
7
+ name: str = Field(..., min_length=1, max_length=100)
8
+ description: str | None = Field(None, max_length=500)
9
+ system_prompt: str | None = Field(None, max_length=10000)
10
+ model_preference: str = "nyx"
11
+ tools: list[str] | None = None
12
+
13
+
14
+ class UpdateAgentRequest(BaseModel):
15
+ name: str | None = Field(None, min_length=1, max_length=100)
16
+ description: str | None = None
17
+ system_prompt: str | None = Field(None, max_length=10000)
18
+ model_preference: str | None = None
19
+ tools: list[str] | None = None
20
+
21
+
22
+ class AgentResponse(BaseModel):
23
+ id: str
24
+ name: str
25
+ description: str | None = None
26
+ model_preference: str
27
+ status: str
28
+ last_heartbeat_at: datetime | None = None
29
+ created_at: datetime
30
+ updated_at: datetime
31
+
32
+ model_config = {"from_attributes": True}
33
+
34
+
35
+ class AgentHeartbeatRequest(BaseModel):
36
+ agent_id: str
37
+ status: str = Field("online", pattern="^(online|busy|idle)$")
38
+
39
+
40
+ class AgentHeartbeatResponse(BaseModel):
41
+ acknowledged: bool
42
+ server_time: datetime
@@ -0,0 +1,30 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class CreateApiKeyRequest(BaseModel):
7
+ name: str = Field(..., min_length=1, max_length=100)
8
+ expires_in_days: int | None = Field(None, ge=1, le=365)
9
+
10
+
11
+ class ApiKeyResponse(BaseModel):
12
+ id: str
13
+ name: str
14
+ key_prefix: str
15
+ is_active: bool
16
+ last_used_at: datetime | None = None
17
+ created_at: datetime
18
+ expires_at: datetime | None = None
19
+
20
+ model_config = {"from_attributes": True}
21
+
22
+
23
+ class ApiKeyCreatedResponse(ApiKeyResponse):
24
+ """Returned only once on creation — includes the full key."""
25
+ full_key: str
26
+
27
+
28
+ class RevokeApiKeyResponse(BaseModel):
29
+ id: str
30
+ revoked: bool
@@ -0,0 +1,57 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, EmailStr, Field
4
+
5
+
6
+ class RegisterRequest(BaseModel):
7
+ email: EmailStr = Field(..., max_length=254)
8
+ password: str = Field(..., min_length=8, max_length=128)
9
+
10
+
11
+ class LoginRequest(BaseModel):
12
+ email: EmailStr = Field(..., max_length=254)
13
+ password: str = Field(..., max_length=128)
14
+
15
+
16
+ class VerifyEmailRequest(BaseModel):
17
+ token: str = Field(..., max_length=128)
18
+
19
+
20
+ class ForgotPasswordRequest(BaseModel):
21
+ email: EmailStr = Field(..., max_length=254)
22
+
23
+
24
+ class ResetPasswordRequest(BaseModel):
25
+ token: str = Field(..., max_length=128)
26
+ password: str = Field(..., min_length=8, max_length=128)
27
+
28
+
29
+ class UserResponse(BaseModel):
30
+ id: str
31
+ email: str
32
+ created_at: datetime
33
+ plan: str = "free"
34
+ email_verified: bool = False
35
+ role: str = "user"
36
+
37
+ model_config = {"from_attributes": True}
38
+
39
+
40
+ class TokenResponse(BaseModel):
41
+ access_token: str
42
+ token_type: str = "bearer"
43
+ user: UserResponse
44
+
45
+
46
+ class CheckoutResponse(BaseModel):
47
+ checkout_url: str
48
+
49
+
50
+ class BillingPortalResponse(BaseModel):
51
+ portal_url: str
52
+
53
+
54
+ class BillingStatusResponse(BaseModel):
55
+ plan: str
56
+ stripe_customer_id: str | None = None
57
+ stripe_subscription_id: str | None = None
@@ -0,0 +1,26 @@
1
+ from pydantic import BaseModel, Field, field_validator
2
+
3
+ from backend.config import settings
4
+
5
+
6
+ class ChatRequest(BaseModel):
7
+ conversation_id: str | None = None
8
+ message: str = Field(..., min_length=1, max_length=32000)
9
+ model: str = "nyx"
10
+ temperature: float = Field(default=0.7, ge=0.0, le=2.0)
11
+ max_tokens: int = Field(default=2048, ge=1, le=16384)
12
+ project_id: str | None = None
13
+
14
+ @field_validator("model")
15
+ @classmethod
16
+ def model_must_be_allowed(cls, v: str) -> str:
17
+ allowed = set(settings.MODEL_MAP.keys())
18
+ if v not in allowed:
19
+ raise ValueError(f"model must be one of: {', '.join(sorted(allowed))}")
20
+ return v
21
+
22
+
23
+ class StreamChunk(BaseModel):
24
+ type: str # "meta", "content", "error"
25
+ conversation_id: str | None = None
26
+ content: str | None = None
@@ -0,0 +1,39 @@
1
+ from datetime import datetime
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class MessageSchema(BaseModel):
6
+ id: str
7
+ role: str
8
+ content: str
9
+ created_at: datetime
10
+ image_url: str | None = None
11
+
12
+ model_config = {"from_attributes": True}
13
+
14
+
15
+ class ConversationSummary(BaseModel):
16
+ id: str
17
+ title: str
18
+ model: str
19
+ updated_at: datetime
20
+ message_count: int
21
+ project_id: str | None = None
22
+
23
+ model_config = {"from_attributes": True}
24
+
25
+
26
+ class ConversationDetail(BaseModel):
27
+ id: str
28
+ title: str
29
+ model: str
30
+ created_at: datetime
31
+ updated_at: datetime
32
+ project_id: str | None = None
33
+ messages: list[MessageSchema]
34
+
35
+ model_config = {"from_attributes": True}
36
+
37
+
38
+ class ConversationRename(BaseModel):
39
+ title: str = Field(..., min_length=1, max_length=200)
@@ -0,0 +1,40 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class DeviceCodeRequest(BaseModel):
5
+ client_name: str = Field("kairo-code", max_length=100)
6
+
7
+
8
+ class DeviceCodeResponse(BaseModel):
9
+ device_code: str
10
+ user_code: str
11
+ verification_uri: str
12
+ verification_uri_complete: str
13
+ expires_in: int
14
+ interval: int
15
+
16
+
17
+ class DeviceTokenRequest(BaseModel):
18
+ device_code: str
19
+ grant_type: str = "urn:ietf:params:oauth:grant-type:device_code"
20
+
21
+
22
+ class DeviceTokenResponse(BaseModel):
23
+ access_token: str
24
+ token_type: str = "bearer"
25
+ plan: str
26
+ email: str
27
+
28
+
29
+ class DeviceTokenErrorResponse(BaseModel):
30
+ error: str # authorization_pending, slow_down, expired_token, access_denied
31
+ error_description: str
32
+
33
+
34
+ class DeviceApproveRequest(BaseModel):
35
+ user_code: str
36
+
37
+
38
+ class DeviceApproveResponse(BaseModel):
39
+ approved: bool
40
+ message: str
@@ -0,0 +1,15 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class ImageGenerateRequest(BaseModel):
5
+ prompt: str = Field(..., min_length=1, max_length=2000)
6
+ conversation_id: str | None = None
7
+ width: int = Field(default=1024, ge=256, le=2048)
8
+ height: int = Field(default=1024, ge=256, le=2048)
9
+
10
+
11
+ class ImageGenerateResponse(BaseModel):
12
+ image_url: str
13
+ message_id: str
14
+ conversation_id: str
15
+ prompt: str
@@ -0,0 +1,76 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ChatMessage(BaseModel):
7
+ role: str
8
+ content: str | None = None
9
+ name: str | None = None
10
+ tool_calls: list[dict[str, Any]] | None = None
11
+ tool_call_id: str | None = None
12
+
13
+
14
+ class ChatCompletionRequest(BaseModel):
15
+ model: str
16
+ messages: list[ChatMessage]
17
+ temperature: float = Field(0.7, ge=0.0, le=2.0)
18
+ max_tokens: int | None = Field(None, ge=1)
19
+ stream: bool = False
20
+ top_p: float | None = Field(None, ge=0.0, le=1.0)
21
+ stop: str | list[str] | None = None
22
+ # Tool calling
23
+ tools: list[dict[str, Any]] | None = None
24
+ tool_choice: str | dict[str, Any] | None = None
25
+ parallel_tool_calls: bool | None = None
26
+ # Additional OpenAI-compatible fields
27
+ response_format: dict[str, Any] | None = None
28
+ frequency_penalty: float | None = Field(None, ge=-2.0, le=2.0)
29
+ presence_penalty: float | None = Field(None, ge=-2.0, le=2.0)
30
+ seed: int | None = None
31
+ n: int | None = Field(None, ge=1, le=8)
32
+ logprobs: bool | None = None
33
+ top_logprobs: int | None = Field(None, ge=0, le=20)
34
+ user: str | None = None
35
+
36
+
37
+ class ChoiceDelta(BaseModel):
38
+ role: str | None = None
39
+ content: str | None = None
40
+ tool_calls: list[dict[str, Any]] | None = None
41
+
42
+
43
+ class ChatCompletionChoice(BaseModel):
44
+ index: int
45
+ message: ChatMessage
46
+ finish_reason: str | None = None
47
+
48
+
49
+ class StreamChoice(BaseModel):
50
+ index: int
51
+ delta: ChoiceDelta
52
+ finish_reason: str | None = None
53
+
54
+
55
+ class UsageInfo(BaseModel):
56
+ prompt_tokens: int
57
+ completion_tokens: int
58
+ total_tokens: int
59
+
60
+
61
+ class ChatCompletionResponse(BaseModel):
62
+ id: str
63
+ object: str = "chat.completion"
64
+ created: int
65
+ model: str
66
+ choices: list[ChatCompletionChoice]
67
+ usage: UsageInfo | None = None
68
+
69
+
70
+ class ChatCompletionChunk(BaseModel):
71
+ id: str
72
+ object: str = "chat.completion.chunk"
73
+ created: int
74
+ model: str
75
+ choices: list[StreamChoice]
76
+ usage: UsageInfo | None = None
@@ -0,0 +1,21 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ProjectCreate(BaseModel):
7
+ name: str = Field(..., min_length=1, max_length=200)
8
+
9
+
10
+ class ProjectUpdate(BaseModel):
11
+ name: str | None = Field(default=None, min_length=1, max_length=200)
12
+ instructions: str | None = None
13
+
14
+
15
+ class ProjectResponse(BaseModel):
16
+ id: str
17
+ name: str
18
+ instructions: str | None
19
+ conversation_count: int
20
+ created_at: datetime
21
+ updated_at: datetime
@@ -0,0 +1,81 @@
1
+ from pydantic import BaseModel, field_validator
2
+
3
+
4
+ class ComponentStatus(BaseModel):
5
+ name: str
6
+ status: str # operational, degraded, down
7
+ latency_ms: int | None = None
8
+ uptime_24h: float | None = None
9
+
10
+
11
+ class IncidentSummary(BaseModel):
12
+ id: str
13
+ title: str
14
+ description: str | None = None
15
+ severity: str
16
+ component: str
17
+ status: str
18
+ started_at: str
19
+ resolved_at: str | None = None
20
+
21
+
22
+ class UptimeEntry(BaseModel):
23
+ date: str
24
+ uptime_percent: float
25
+
26
+
27
+ class StatusPageResponse(BaseModel):
28
+ status: str # operational, degraded, outage
29
+ overall_uptime: float
30
+ checked_at: str
31
+ components: list[ComponentStatus]
32
+ recent_incidents: list[IncidentSummary]
33
+ uptime_history: list[UptimeEntry]
34
+
35
+
36
+ VALID_SEVERITIES = {"info", "warning", "critical"}
37
+ VALID_COMPONENTS = {"api", "chat", "models", "images", "database"}
38
+ VALID_STATUSES = {"investigating", "identified", "monitoring", "resolved"}
39
+
40
+
41
+ class CreateIncidentRequest(BaseModel):
42
+ title: str
43
+ description: str | None = None
44
+ severity: str = "warning"
45
+ component: str
46
+
47
+ @field_validator("severity")
48
+ @classmethod
49
+ def validate_severity(cls, v: str) -> str:
50
+ if v not in VALID_SEVERITIES:
51
+ raise ValueError(f"severity must be one of {VALID_SEVERITIES}")
52
+ return v
53
+
54
+ @field_validator("component")
55
+ @classmethod
56
+ def validate_component(cls, v: str) -> str:
57
+ if v not in VALID_COMPONENTS:
58
+ raise ValueError(f"component must be one of {VALID_COMPONENTS}")
59
+ return v
60
+
61
+
62
+ class UpdateIncidentRequest(BaseModel):
63
+ title: str | None = None
64
+ description: str | None = None
65
+ severity: str | None = None
66
+ status: str | None = None
67
+ resolved_at: str | None = None
68
+
69
+ @field_validator("severity")
70
+ @classmethod
71
+ def validate_severity(cls, v: str | None) -> str | None:
72
+ if v is not None and v not in VALID_SEVERITIES:
73
+ raise ValueError(f"severity must be one of {VALID_SEVERITIES}")
74
+ return v
75
+
76
+ @field_validator("status")
77
+ @classmethod
78
+ def validate_status(cls, v: str | None) -> str | None:
79
+ if v is not None and v not in VALID_STATUSES:
80
+ raise ValueError(f"status must be one of {VALID_STATUSES}")
81
+ return v
@@ -0,0 +1,15 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class UsageSummaryResponse(BaseModel):
5
+ daily_percent: float
6
+ monthly_percent: float
7
+
8
+
9
+ class UsageHistoryEntry(BaseModel):
10
+ date: str
11
+ usage_percent: float
12
+
13
+
14
+ class UsageHistoryResponse(BaseModel):
15
+ entries: list[UsageHistoryEntry]
File without changes
File without changes