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
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user