feat: 集成 CloudCost 云管系统作为运营大脑数据源
- 新增 cloudcost-client.ts:5 个 API 函数(dashboard/metering/detail/alerts/accounts) - config.ts 添加 cloudcost.apiBase(默认 orange-wave-09002e800.7.azurestaticapps.net) - toolConfig.ts 添加 cloudcost_* 超时/重试配置 - CLAUDE.md 更新架构图和 env var 文档 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d93bb5966c
commit
a6ae0ef5eb
@@ -52,7 +52,8 @@ sshpass -p xiaohei ssh xiaohei@192.168.30.30 "cd ~/SOC && docker compose --profi
|
||||
├── Gongdan 工单 API
|
||||
├── Jina Search/Reader/Rerank
|
||||
├── Serper Google Search
|
||||
└── Daytona Sandbox (process/execute endpoint)
|
||||
├── Daytona Sandbox (process/execute endpoint)
|
||||
└── CloudCost 云管系统 (orange-wave-09002e800.7.azurestaticapps.net)
|
||||
```
|
||||
|
||||
### Agent Graph
|
||||
@@ -93,6 +94,11 @@ src/agent/{name}/
|
||||
| writer | `doc_create/edit/translate` | `canvas-doc` | CanvasPanel右侧抽屉 |
|
||||
| writer | `report_generate` | `canvas-doc` | 结构化报告模板 |
|
||||
| writer | `reply_draft` | `reply-draft` | customer/internal双模式 |
|
||||
| enterprise | `cloudcost_dashboard` | `cost-result` (dashboard) | 月度总览:总费用/环比/趋势/按厂商服务拆分 |
|
||||
| enterprise | `cloudcost_metering` | `cost-result` (metering) | 用量费用汇总,按服务 Top N |
|
||||
| enterprise | `cloudcost_detail` | `cost-result` (detail) | 费用明细分页,最细粒度 |
|
||||
| enterprise | `cloudcost_alerts` | `cost-result` (alerts) | 告警规则状态,triggered 红色高亮 |
|
||||
| enterprise | `cloudcost_accounts` | `cost-result` (accounts) | 云服务账号列表,状态徽章 |
|
||||
| enterprise (自动) | — | `next-actions` | 工具成功后自动附带2-3条推荐动作 |
|
||||
|
||||
所有卡片 props 包含 `sourceType`(internal_kb/ticket_system/external_web/code_execution/generated_doc)和 `confidence`(high/medium/low)。
|
||||
@@ -229,6 +235,9 @@ DOC_CREATOR_API_KEY
|
||||
|
||||
# PostgreSQL (checkpoint + 持久化)
|
||||
DATABASE_URL
|
||||
|
||||
# CloudCost 云管系统(可选,有默认值)
|
||||
CLOUDCOST_API_BASE=https://orange-wave-09002e800.7.azurestaticapps.net
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* CloudCost 云管系统 API 客户端
|
||||
* Base URL: https://orange-wave-09002e800.7.azurestaticapps.net/
|
||||
* 只读 GET 接口,无需鉴权(依赖网关/内网策略)
|
||||
*/
|
||||
|
||||
import { config } from "@/agent/utils/config";
|
||||
import { getToolConfig } from "@/agent/utils/toolConfig";
|
||||
|
||||
function buildUrl(path: string, params: Record<string, string | number | undefined>): string {
|
||||
const base = config.cloudcost.apiBase.replace(/\/$/, "");
|
||||
const url = new URL(`${base}${path}`);
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
if (v !== undefined && v !== "") url.searchParams.set(k, String(v));
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async function ccGet<T>(url: string, toolName: string): Promise<T> {
|
||||
const resp = await fetch(url, {
|
||||
signal: AbortSignal.timeout(getToolConfig(toolName).timeoutMs),
|
||||
});
|
||||
if (!resp.ok) throw new Error(`CloudCost ${toolName} failed: HTTP ${resp.status}`);
|
||||
return resp.json() as Promise<T>;
|
||||
}
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DashboardBundle {
|
||||
overview: {
|
||||
total_cost: number;
|
||||
prev_month_cost: number;
|
||||
mom_change_pct: number;
|
||||
active_projects: number;
|
||||
};
|
||||
trend: Array<{ date: string; cost: number; cost_by_provider: Record<string, number> }>;
|
||||
by_provider: Array<{ provider: string; cost: number; percentage: number }>;
|
||||
by_service: Array<{ product: string; cost: number; percentage: number }>;
|
||||
}
|
||||
|
||||
export interface MeteringSummary {
|
||||
total_cost: number;
|
||||
total_usage: number;
|
||||
record_count: number;
|
||||
service_count: number;
|
||||
}
|
||||
|
||||
export interface ServiceUsage {
|
||||
product: string;
|
||||
usage_quantity: number;
|
||||
usage_unit: string;
|
||||
cost: number;
|
||||
record_count: number;
|
||||
}
|
||||
|
||||
export interface MeteringDetail {
|
||||
id: number;
|
||||
date: string;
|
||||
provider: string;
|
||||
project_id: string;
|
||||
product: string;
|
||||
usage_type: string;
|
||||
region: string;
|
||||
cost: number;
|
||||
usage_quantity: number;
|
||||
usage_unit: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface AlertRuleStatus {
|
||||
rule_id: number;
|
||||
rule_name: string;
|
||||
threshold_type: string;
|
||||
threshold_value: number;
|
||||
actual: number;
|
||||
pct: number;
|
||||
triggered: boolean;
|
||||
account_name: string;
|
||||
provider: string;
|
||||
external_project_id: string;
|
||||
}
|
||||
|
||||
export interface ServiceAccount {
|
||||
id: number;
|
||||
name: string;
|
||||
supply_source_id: number;
|
||||
supplier_name: string;
|
||||
provider: string;
|
||||
external_project_id: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// ─── API Functions ─────────────────────────────────────────────────────────────
|
||||
|
||||
/** GET /api/dashboard/bundle — 月度费用总览(overview + trend + by_provider + by_service) */
|
||||
export async function cloudcostDashboard(
|
||||
month: string,
|
||||
granularity: "daily" | "weekly" | "monthly" = "daily",
|
||||
serviceLimit = 10,
|
||||
): Promise<DashboardBundle> {
|
||||
const url = buildUrl("/api/dashboard/bundle", { month, granularity, service_limit: serviceLimit });
|
||||
return ccGet<DashboardBundle>(url, "cloudcost_dashboard");
|
||||
}
|
||||
|
||||
/** GET /api/metering/summary + /api/metering/by-service — 用量费用汇总 */
|
||||
export async function cloudcostMetering(params: {
|
||||
date_start?: string;
|
||||
date_end?: string;
|
||||
provider?: string;
|
||||
product?: string;
|
||||
account_id?: number;
|
||||
}): Promise<{ summary: MeteringSummary; by_service: ServiceUsage[] }> {
|
||||
const p = params as Record<string, string | number | undefined>;
|
||||
const [summary, by_service] = await Promise.all([
|
||||
ccGet<MeteringSummary>(buildUrl("/api/metering/summary", p), "cloudcost_metering"),
|
||||
ccGet<ServiceUsage[]>(buildUrl("/api/metering/by-service", p), "cloudcost_metering"),
|
||||
]);
|
||||
return { summary, by_service };
|
||||
}
|
||||
|
||||
/** GET /api/metering/detail + /detail/count — 费用明细分页 */
|
||||
export async function cloudcostDetail(params: {
|
||||
date_start?: string;
|
||||
date_end?: string;
|
||||
provider?: string;
|
||||
product?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<{ items: MeteringDetail[]; total: number }> {
|
||||
const p = { page: 1, page_size: 20, ...params } as Record<string, string | number | undefined>;
|
||||
const countParams = { ...p } as Record<string, string | number | undefined>;
|
||||
delete countParams.page;
|
||||
delete countParams.page_size;
|
||||
|
||||
const [items, countResp] = await Promise.all([
|
||||
ccGet<MeteringDetail[]>(buildUrl("/api/metering/detail", p), "cloudcost_detail"),
|
||||
ccGet<{ total: number }>(buildUrl("/api/metering/detail/count", countParams), "cloudcost_detail"),
|
||||
]);
|
||||
return { items, total: countResp.total };
|
||||
}
|
||||
|
||||
/** GET /api/alerts/rule-status — 告警规则执行态 */
|
||||
export async function cloudcostAlerts(month?: string): Promise<AlertRuleStatus[]> {
|
||||
const url = buildUrl("/api/alerts/rule-status", { month });
|
||||
return ccGet<AlertRuleStatus[]>(url, "cloudcost_alerts");
|
||||
}
|
||||
|
||||
/** GET /api/service-accounts/ — 服务账号列表 */
|
||||
export async function cloudcostAccounts(params: {
|
||||
provider?: string;
|
||||
status?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<ServiceAccount[]> {
|
||||
const p = { page: 1, page_size: 100, ...params } as Record<string, string | number | undefined>;
|
||||
const url = buildUrl("/api/service-accounts/", p);
|
||||
return ccGet<ServiceAccount[]>(url, "cloudcost_accounts");
|
||||
}
|
||||
@@ -126,4 +126,7 @@ export const config = {
|
||||
apiKey: process.env.DOC_CREATOR_API_KEY ?? "",
|
||||
fallbackEndpoint: process.env.DOC_CREATOR_FALLBACK_ENDPOINT ?? "",
|
||||
},
|
||||
cloudcost: {
|
||||
apiBase: process.env.CLOUDCOST_API_BASE ?? "https://orange-wave-09002e800.7.azurestaticapps.net",
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -16,6 +16,11 @@ export const TOOL_CONFIGS: Record<string, ToolConfig> = {
|
||||
sandbox_run: { timeoutMs: 15_000, maxRetries: 1 },
|
||||
code_execute: { timeoutMs: 15_000, maxRetries: 1 },
|
||||
chart_generate: { timeoutMs: 5_000, maxRetries: 1 },
|
||||
cloudcost_dashboard: { timeoutMs: 15_000, maxRetries: 2, backoffMs: 500 },
|
||||
cloudcost_metering: { timeoutMs: 15_000, maxRetries: 2, backoffMs: 500 },
|
||||
cloudcost_detail: { timeoutMs: 15_000, maxRetries: 2, backoffMs: 500 },
|
||||
cloudcost_alerts: { timeoutMs: 10_000, maxRetries: 2, backoffMs: 500 },
|
||||
cloudcost_accounts: { timeoutMs: 10_000, maxRetries: 2, backoffMs: 500 },
|
||||
};
|
||||
|
||||
export function getToolConfig(toolName: string): ToolConfig {
|
||||
|
||||
Reference in New Issue
Block a user