feat: 集成 CloudCost 云管系统作为运营大脑数据源
Trigger auto deployment for soc-langgraph / build-and-deploy (push) Failing after 26s
Deploy LangGraph UI to Azure Static Web Apps / build-and-deploy (push) Failing after 42s

- 新增 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:
gongzhiyong
2026-04-14 03:35:25 +08:00
co-authored by Claude Sonnet 4.6
parent d93bb5966c
commit a6ae0ef5eb
4 changed files with 178 additions and 2 deletions
@@ -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");
}
+3
View File
@@ -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;
+6 -1
View File
@@ -15,7 +15,12 @@ export const TOOL_CONFIGS: Record<string, ToolConfig> = {
web_search_deep: { timeoutMs: 45_000, maxRetries: 1 },
sandbox_run: { timeoutMs: 15_000, maxRetries: 1 },
code_execute: { timeoutMs: 15_000, maxRetries: 1 },
chart_generate: { timeoutMs: 5_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 {