Merge remote-tracking branch 'origin/aether-rust-pioneer' into codex/async-cleanup-records

# Conflicts:
#	apps/aether-gateway/src/maintenance/mod.rs
#	apps/aether-gateway/src/maintenance/runtime/runners.rs
This commit is contained in:
fawney19
2026-05-10 00:28:39 +08:00
315 changed files with 19750 additions and 3183 deletions

View File

@@ -1,39 +1,78 @@
import apiClient from './client'
// 异步任务状态
export type AsyncTaskStatus = 'pending' | 'submitted' | 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled'
export type AsyncTaskStatus =
| 'queued'
| 'running'
| 'retrying'
| 'succeeded'
| 'failed'
| 'cancelled'
| 'skipped'
| 'pending'
| 'submitted'
| 'processing'
| 'completed'
// 异步任务类型
export type AsyncTaskType = 'video'
export type AsyncTaskKind = 'scheduled' | 'daemon' | 'on_demand' | 'fire_and_forget'
export type AsyncTaskType = AsyncTaskKind | 'video'
// 异步任务列表项
export interface AsyncTaskItem {
id: string
external_task_id: string
user_id: string
username: string
task_type: AsyncTaskType
model: string
prompt: string
status: AsyncTaskStatus
progress_percent: number
progress_message: string | null
provider_id: string
provider_name: string
duration_seconds: number
resolution: string
aspect_ratio: string
video_url: string | null
error_code: string | null
error_message: string | null
poll_count: number
max_poll_count: number
created_at: string
completed_at: string | null
submitted_at: string | null
export interface AsyncTaskDefinition {
task_key: string
kind: AsyncTaskKind
trigger: string
max_attempts: number
singleton: boolean
persist_history: boolean
}
export interface AsyncTaskItem {
id: string
task_key?: string
kind?: AsyncTaskKind
trigger?: string
task_type?: AsyncTaskType
external_task_id?: string
user_id?: string
username?: string
model?: string
prompt?: string
status: AsyncTaskStatus
attempt?: number
max_attempts?: number
owner_instance?: string | null
progress_percent: number
progress_message: string | null
payload?: unknown
result?: unknown
error_message: string | null
cancel_requested?: boolean
created_by?: string | null
provider_id?: string
provider_name?: string
duration_seconds?: number
resolution?: string
aspect_ratio?: string
video_url?: string | null
error_code?: string | null
poll_count?: number
max_poll_count?: number
created_at: string
started_at?: string | null
updated_at?: string | null
finished_at?: string | null
completed_at?: string | null
submitted_at?: string | null
}
export interface AsyncTaskEvent {
id: string
run_id: string
event_type: string
message: string
payload: unknown
created_at: string
}
// 候选 Key 信息
export interface CandidateKeyInfo {
index: number
provider_id: string
@@ -47,7 +86,6 @@ export interface CandidateKeyInfo {
selected?: boolean
}
// 请求元数据
export interface AsyncTaskRequestMetadata {
candidate_keys: CandidateKeyInfo[]
selected_key_id: string
@@ -56,111 +94,118 @@ export interface AsyncTaskRequestMetadata {
user_agent: string
request_id: string
request_headers?: Record<string, string>
poll_raw_response?: unknown // 轮询完成时的原始响应
billing_snapshot?: unknown // 计费快照
poll_raw_response?: unknown
billing_snapshot?: unknown
}
// 异步任务详情
export interface AsyncTaskDetail extends AsyncTaskItem {
api_key_id: string
endpoint_id: string
key_id: string
client_api_format: string
provider_api_format: string
format_converted: boolean
original_request_body: unknown
converted_request_body: unknown
size: string | null
video_urls: string[] | null
thumbnail_url: string | null
video_size_bytes: number | null
video_duration_seconds: number | null // 实际视频时长(秒)
video_expires_at: string | null
stored_video_path: string | null
storage_provider: string | null
retry_count: number
max_retries: number
poll_interval_seconds: number
next_poll_at: string | null
updated_at: string | null
endpoint: {
api_key_id?: string
endpoint_id?: string
key_id?: string
client_api_format?: string
provider_api_format?: string
format_converted?: boolean
original_request_body?: unknown
converted_request_body?: unknown
size?: string | null
video_urls?: string[] | null
thumbnail_url?: string | null
video_size_bytes?: number | null
video_duration_seconds?: number | null
video_expires_at?: string | null
stored_video_path?: string | null
storage_provider?: string | null
retry_count?: number
max_retries?: number
poll_interval_seconds?: number
next_poll_at?: string | null
endpoint?: {
id: string
base_url: string
api_format: string
} | null
request_metadata: AsyncTaskRequestMetadata | null
request_metadata?: AsyncTaskRequestMetadata | null
}
// 异步任务列表响应
export interface AsyncTaskListResponse {
items: AsyncTaskItem[]
total: number
page: number
page_size: number
pages: number
definitions?: AsyncTaskDefinition[]
}
// 异步任务统计响应
export interface AsyncTaskStatsResponse {
total: number
by_status: Record<AsyncTaskStatus, number>
by_model: Record<string, number>
today_count: number
active_users?: number // 仅管理员
processing_count?: number // 仅管理员
running_count?: number
registered_tasks?: number
by_status: Partial<Record<AsyncTaskStatus, number>>
by_kind?: Partial<Record<AsyncTaskKind, number>>
by_model?: Record<string, number>
today_count?: number
active_users?: number
processing_count?: number
}
// 异步任务查询参数
export interface AsyncTaskQueryParams {
status?: AsyncTaskStatus
kind?: AsyncTaskKind
task_type?: AsyncTaskType
task_key?: string
trigger?: string
user_id?: string
model?: string
page?: number
page_size?: number
}
function normalizeStatus(status: AsyncTaskStatus): AsyncTaskStatus {
if (status === 'processing') return 'running'
if (status === 'submitted' || status === 'pending') return 'queued'
if (status === 'completed') return 'succeeded'
return status
}
export const asyncTasksApi = {
/**
* 获取异步任务列表
*/
async list(params: AsyncTaskQueryParams = {}): Promise<AsyncTaskListResponse> {
const searchParams = new URLSearchParams()
if (params.status) searchParams.append('status', params.status)
if (params.task_type) searchParams.append('task_type', params.task_type)
if (params.user_id) searchParams.append('user_id', params.user_id)
if (params.model) searchParams.append('model', params.model)
if (params.status) searchParams.append('status', normalizeStatus(params.status))
if (params.kind) searchParams.append('kind', params.kind)
if (params.task_type && params.task_type !== 'video') searchParams.append('kind', params.task_type)
if (params.task_key) searchParams.append('task_key', params.task_key)
if (params.trigger) searchParams.append('trigger', params.trigger)
if (params.page) searchParams.append('page', params.page.toString())
if (params.page_size) searchParams.append('page_size', params.page_size.toString())
const query = searchParams.toString()
// 后端 API 路径保持不变,前端抽象为异步任务
const url = query ? `/api/admin/video-tasks?${query}` : '/api/admin/video-tasks'
const url = query ? `/api/admin/tasks?${query}` : '/api/admin/tasks'
const response = await apiClient.get(url)
return response.data
},
/**
* 获取异步任务统计
*/
async getStats(): Promise<AsyncTaskStatsResponse> {
const response = await apiClient.get('/api/admin/video-tasks/stats')
const response = await apiClient.get('/api/admin/tasks/stats')
return response.data
},
/**
* 获取异步任务详情
*/
async getDetail(taskId: string): Promise<AsyncTaskDetail> {
const response = await apiClient.get(`/api/admin/video-tasks/${taskId}`)
const response = await apiClient.get(`/api/admin/tasks/${taskId}`)
return response.data
},
async getEvents(taskId: string): Promise<{ items: AsyncTaskEvent[] }> {
const response = await apiClient.get(`/api/admin/tasks/${taskId}/events`)
return response.data
},
/**
* 取消异步任务
*/
async cancel(taskId: string): Promise<{ id: string; status: string; message: string }> {
const response = await apiClient.post(`/api/admin/video-tasks/${taskId}/cancel`)
const response = await apiClient.post(`/api/admin/tasks/${taskId}/cancel`)
return response.data
},
async trigger(taskKey: string, payload: Record<string, unknown> = {}): Promise<{ run_id: string; status: string }> {
const response = await apiClient.post(`/api/admin/tasks/${taskKey}/trigger`, payload)
return response.data
},
}

View File

@@ -171,6 +171,21 @@ export interface ApiKey {
force_capabilities?: Record<string, boolean> | null // 强制能力配置
}
export type InstallTargetCli = 'claude_code' | 'codex_cli' | 'gemini_cli'
export type InstallTargetSystem = 'macos' | 'linux' | 'windows' | 'auto'
export interface ApiKeyInstallSession {
install_code: string
expires_at_unix_secs: number
expires_in_seconds: number
target_cli: InstallTargetCli
target_cli_label: string
target_system: InstallTargetSystem
target_system_label: string
unix_command: string
powershell_command: string
}
// 不再需要 ProviderBinding 接口
export interface ChangePasswordRequest {
@@ -270,6 +285,17 @@ export const meApi = {
return response.data
},
async createApiKeyInstallSession(
keyId: string,
data: { target_cli: InstallTargetCli; target_system: InstallTargetSystem }
): Promise<ApiKeyInstallSession> {
const response = await apiClient.post<ApiKeyInstallSession>(
`/api/users/me/api-keys/${keyId}/install-sessions`,
data
)
return response.data
},
// 使用统计
async getUsage(params?: {
start_date?: string

View File

@@ -48,9 +48,58 @@ export interface ProxyNode {
export interface ProxyNodeEvent {
id: number
event_type: 'connected' | 'disconnected' | 'error'
event_type: string
detail: string | null
created_at: string
event_metadata?: Record<string, unknown> | null
created_at: string | null
}
export type ProxyNodeMetricsStep = '1m' | '1h'
export interface ProxyNodeMetricsSummary {
samples: number
uptime_samples: number
uptime_ratio: number | null
active_connections_sum: number
active_connections_max: number
active_connections_avg: number | null
heartbeat_rtt_ms_sum: number
heartbeat_rtt_ms_max: number
heartbeat_rtt_ms_avg: number | null
connect_errors_delta: number
disconnects_delta: number
error_events_delta: number
ws_in_bytes_delta: number
ws_out_bytes_delta: number
ws_in_frames_delta: number
ws_out_frames_delta: number
}
export interface ProxyNodeMetricsBucket extends ProxyNodeMetricsSummary {
node_id?: string
bucket_start_unix_secs: number
bucket_start: string | null
}
export interface ProxyNodeMetricsResponse {
step: ProxyNodeMetricsStep
from: number
to: number
items: ProxyNodeMetricsBucket[]
summary: ProxyNodeMetricsSummary
}
export interface ProxyNodeMetricsQuery {
from: number
to: number
step: ProxyNodeMetricsStep
}
export interface ProxyNodeEventQuery {
limit?: number
from?: number
to?: number
event_type?: string
}
export interface ProxyNodeUpgradeRolloutProbe {
@@ -272,8 +321,19 @@ export const proxyNodesApi = {
return response.data
},
async listNodeEvents(nodeId: string, limit = 50): Promise<{ items: ProxyNodeEvent[] }> {
const response = await apiClient.get<{ items: ProxyNodeEvent[] }>(`/api/admin/proxy-nodes/${nodeId}/events`, { params: { limit } })
async listNodeMetrics(nodeId: string, params: ProxyNodeMetricsQuery): Promise<ProxyNodeMetricsResponse> {
const response = await apiClient.get<ProxyNodeMetricsResponse>(`/api/admin/proxy-nodes/${nodeId}/metrics`, { params })
return response.data
},
async listFleetMetrics(params: ProxyNodeMetricsQuery): Promise<ProxyNodeMetricsResponse> {
const response = await apiClient.get<ProxyNodeMetricsResponse>('/api/admin/proxy-nodes/metrics/fleet', { params })
return response.data
},
async listNodeEvents(nodeId: string, query: ProxyNodeEventQuery | number = 50): Promise<{ items: ProxyNodeEvent[] }> {
const params = typeof query === 'number' ? { limit: query } : query
const response = await apiClient.get<{ items: ProxyNodeEvent[] }>(`/api/admin/proxy-nodes/${nodeId}/events`, { params })
return response.data
},
}

View File

@@ -9,6 +9,15 @@ export interface CandidateRankingMetadata {
demoted_by?: string
}
export interface CandidateResponseBoundary {
source?: string
status_code?: number | null
headers?: Record<string, unknown> | null
body?: unknown
body_ref?: string | null
body_state?: string | null
}
export interface CandidateRecord {
id: string
request_id: string
@@ -46,7 +55,9 @@ export interface CandidateRecord {
latency_ms?: number
concurrent_requests?: number
ranking?: CandidateRankingMetadata | null
extra_data?: Record<string, unknown>
extra_data?: Record<string, unknown> & {
upstream_response?: CandidateResponseBoundary
}
created_at: string
started_at?: string
finished_at?: string
@@ -54,6 +65,9 @@ export interface CandidateRecord {
export interface RequestTrace {
request_id: string
request_path?: string
request_query_string?: string
request_path_and_query?: string
total_candidates: number
final_status: 'success' | 'failed' | 'streaming' | 'pending' | 'cancelled'
total_latency_ms: number