Files
awoooi/apps/api/src/models/terminal.py
OG T cd37befbe6
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
Type Sync Check / check-type-sync (push) Successful in 59s
fix(models): 全面替換 datetime.UTC → timezone.utc 相容 Python 3.10
terminal.py, incident.py, utils/timezone.py 同樣問題。
CI runner Python 3.10 無 UTC 常數,導致所有模型靜默 import 失敗。

# 2026-04-06 ogt: 完整修復,不再有漏網之魚

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:40:27 +08:00

259 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Terminal Models
===============
Phase 19.1 - Omni-Terminal API Models
Pydantic models for Terminal SSE communication.
@see ADR-031 Omni-Terminal SSE Architecture
@author Claude Code (首席架構師)
@version 1.0.0
@date 2026-03-28 (台北時間)
"""
from datetime import datetime, timezone
from enum import Enum
from typing import Any
from pydantic import BaseModel, Field
# =============================================================================
# Enums
# =============================================================================
class TerminalSessionStatus(str, Enum):
"""Terminal Session 狀態"""
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
ABORTED = "aborted"
ERROR = "error"
# =============================================================================
# Request Models
# =============================================================================
class SpatialContext(BaseModel):
"""
空間感知上下文 (Ghost Payload)
前端隱形夾帶,讓 AI 知道使用者正在看什麼
"""
current_page: str = Field(
...,
description="統帥當前所在的路由",
examples=["/zh-TW/authorizations", "/zh-TW"],
)
focused_entity_id: str | None = Field(
default=None,
description="正在檢視的實體 ID (incident/approval)",
examples=["INC-2026-0001", "APR-2026-0001"],
)
class TerminalIntentRequest(BaseModel):
"""
Terminal Intent 請求
統帥輸入的指令或問題
"""
intent: str = Field(
...,
description="統帥輸入的原始指令或意圖",
min_length=1,
max_length=2000,
examples=["列出所有待審核的 approval", "為什麼 awoooi-api pod 一直重啟?"],
)
context: SpatialContext = Field(
...,
description="前端空間感知上下文",
)
session_id: str | None = Field(
default=None,
description="現有 Session ID (用於續傳)",
)
class TerminalAbortRequest(BaseModel):
"""中斷執行請求"""
reason: str | None = Field(
default=None,
description="中斷原因",
)
# =============================================================================
# Response Models
# =============================================================================
class TerminalIntentResponse(BaseModel):
"""
Intent 請求回應
返回 session_id 供前端訂閱 SSE
"""
session_id: str = Field(
...,
description="Session UUID",
)
stream_url: str = Field(
...,
description="SSE 串流訂閱 URL",
)
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
)
class TerminalStatusResponse(BaseModel):
"""Session 狀態查詢回應"""
session_id: str
status: TerminalSessionStatus
created_at: datetime
last_event_id: int
message_count: int
class TerminalAbortResponse(BaseModel):
"""中斷執行回應"""
session_id: str
aborted: bool
message: str
# =============================================================================
# SSE Event Models
# =============================================================================
class TerminalThoughtEvent(BaseModel):
"""AI 思考軌跡事件"""
agent: str = Field(
...,
description="代理人名稱",
examples=["System", "Investigator", "Strategist"],
)
msg: str = Field(
...,
description="思考內容",
)
class TerminalToolCallEvent(BaseModel):
"""工具呼叫事件"""
tool: str = Field(
...,
description="工具名稱",
examples=["K8s-Log-Scanner", "Sentry-Query", "Playbook-Lookup"],
)
status: str = Field(
...,
description="執行狀態",
examples=["executing", "completed", "failed"],
)
result: Any | None = Field(
default=None,
description="執行結果",
)
class TerminalRenderUIEvent(BaseModel):
"""GenUI 渲染事件"""
component: str = Field(
...,
description="組件名稱 (必須在前端 Registry 中)",
examples=["ApprovalCard", "MetricsSummaryCard", "SentryErrorCard"],
)
props: dict[str, Any] = Field(
default_factory=dict,
description="組件 Props",
)
position: str = Field(
default="inline",
description="渲染位置",
examples=["inline", "modal", "panel"],
)
id: str | None = Field(
default=None,
description="組件 ID (用於更新/移除)",
)
class TerminalActionRequestEvent(BaseModel):
"""核鑰授權請求事件"""
approval_id: str = Field(
...,
description="Approval ID",
)
risk_level: str = Field(
...,
description="風險等級",
examples=["LOW", "MEDIUM", "CRITICAL"],
)
kubectl: str = Field(
...,
description="要執行的 kubectl 命令",
)
description: str | None = Field(
default=None,
description="操作說明",
)
class TerminalCompleteEvent(BaseModel):
"""串流完成事件"""
session_id: str
message: str = "Stream completed"
class TerminalErrorEvent(BaseModel):
"""錯誤事件"""
session_id: str
error_code: str
message: str
recoverable: bool = True
# =============================================================================
# Session Model (Redis Storage)
# =============================================================================
class TerminalSession(BaseModel):
"""
Terminal Session
儲存在 RedisTTL 5 分鐘
"""
session_id: str
user_id: str | None = None
intent: str
context: SpatialContext
status: TerminalSessionStatus = TerminalSessionStatus.PENDING
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
last_event_id: int = 0
message_count: int = 0
def next_event_id(self) -> int:
"""取得下一個事件 ID"""
self.last_event_id += 1
return self.last_event_id