Giới thiệu

Trong bài viết này, tôi sẽ chia sẻ kinh nghiệm thực chiến khi triển khai hệ thống quản trị Claude Code cho team gồm 15 kỹ sư tại một startup fintech. Chúng tôi đã xây dựng complete governance framework với HolySheep API, đạt tiết kiệm 85% chi phí so với Anthropic direct pricing trong khi vẫn duy trì latency dưới 50ms và zero compliance incident. Vấn đề cốt lõi: Khi team mở rộng, việc không kiểm soát code generation quota dẫn đến chi phí Anthropic tăng 300% trong 2 tháng, audit log không đầy đủ khiến security audit thất bại, và không có fallback strategy gây ra production downtime. Bài viết sẽ hướng dẫn bạn implement giải pháp từ A-Z với HolySheep.

Mục lục

Kiến trúc tổng quan

Hệ thống governance của chúng tôi bao gồm 4 layer chính, mỗi layer đều integrate với HolySheep API endpoint:

┌─────────────────────────────────────────────────────────────────┐
│                    GOVERNANCE LAYER                             │
├──────────────┬──────────────┬──────────────┬───────────────────┤
│   Quota      │   Audit      │   Fallback   │   Cost Alert      │
│   Manager    │   Logger     │   Router     │   Monitor         │
├──────────────┴──────────────┴──────────────┴───────────────────┤
│                    HOLYSHEEP API GATEWAY                       │
│         https://api.holysheep.ai/v1/chat/completions           │
├─────────────────────────────────────────────────────────────────┤
│         Claude Sonnet 4.5    │    DeepSeek V3.2               │
│         Latency: 45ms        │    Latency: 38ms                │
│         Cost: $3/MTok        │    Cost: $0.08/MTok            │
└─────────────────────────────────────────────────────────────────┘

Code Generation Quota Management

1. Quota Configuration

Đầu tiên, chúng ta cần define quota rules cho từng team và user. Dưới đây là complete quota manager class:

import { EventEmitter } from 'events';
import { Redis } from 'ioredis';

interface QuotaConfig {
  teamId: string;
  dailyLimit: number;      // tokens per day
  monthlyLimit: number;     // tokens per month
  burstLimit: number;       // tokens per minute
  modelPreference: 'claude' | 'deepseek' | 'auto';
}

interface QuotaUsage {
  usedToday: number;
  usedMonth: number;
  usedMinute: number;
  remainingToday: number;
  remainingMinute: number;
}

class ClaudeCodeQuotaManager extends EventEmitter {
  private redis: Redis;
  private quotaConfigs: Map;
  private readonly HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
  
  constructor(redis: Redis) {
    super();
    this.redis = redis;
    this.quotaConfigs = new Map();
  }

  // Initialize quota for a team
  async initializeQuota(config: QuotaConfig): Promise {
    const key = quota:${config.teamId};
    const now = new Date();
    const dayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
    const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).getTime();
    
    await this.redis.hset(key, {
      dailyLimit: config.dailyLimit,
      monthlyLimit: config.monthlyLimit,
      burstLimit: config.burstLimit,
      dayStart: dayStart,
      monthStart: monthStart,
      usedToday: 0,
      usedMonth: 0,
      modelPreference: config.modelPreference
    });
    
    this.quotaConfigs.set(config.teamId, config);
  }

  // Check and update quota usage
  async checkAndConsumeQuota(
    teamId: string, 
    userId: string, 
    tokens: number
  ): Promise<{ allowed: boolean; usage: QuotaUsage; fallback?: string }> {
    
    const config = this.quotaConfigs.get(teamId);
    if (!config) {
      throw new Error(Quota config not found for team: ${teamId});
    }

    const key = quota:${teamId};
    const now = Date.now();
    const usage = await this.getUsage(key, now);
    
    // Check minute burst limit
    if (usage.usedMinute + tokens > config.burstLimit) {
      this.emit('quota_exceeded', { teamId, userId, type: 'burst', usage });
      return {
        allowed: false,
        usage,
        fallback: await this.selectFallbackModel(config.modelPreference, tokens)
      };
    }

    // Check daily limit
    if (usage.usedToday + tokens > config.dailyLimit) {
      this.emit('quota_exceeded', { teamId, userId, type: 'daily', usage });
      return {
        allowed: false,
        usage,
        fallback: await this.selectFallbackModel(config.modelPreference, tokens)
      };
    }

    // Check monthly limit
    if (usage.usedMonth + tokens > config.monthlyLimit) {
      this.emit('quota_exceeded', { teamId, userId, type: 'monthly', usage });
      return {
        allowed: false,
        usage,
        fallback: await this.selectFallbackModel(config.modelPreference, tokens)
      };
    }

    // Consume quota atomically
    const pipeline = this.redis.pipeline();
    pipeline.hincrby(key, 'usedToday', tokens);
    pipeline.hincrby(key, 'usedMonth', tokens);
    pipeline.zadd(burst:${teamId}, now, ${userId}:${now});
    await pipeline.exec();

    this.emit('quota_consumed', { teamId, userId, tokens });
    return { allowed: true, usage: await this.getUsage(key, now) };
  }

  private async getUsage(key: string, now: number): Promise {
    const data = await this.redis.hgetall(key);
    const dayStart = parseInt(data.dayStart);
    const monthStart = parseInt(data.monthStart);
    
    // Reset daily if needed
    if (now - dayStart >= 86400000) {
      await this.redis.hset(key, { usedToday: 0, dayStart: now });
      return { usedToday: 0, usedMonth: parseInt(data.usedMonth) || 0, 
               usedMinute: 0, remainingToday: parseInt(data.dailyLimit),
               remainingMinute: parseInt(data.burstLimit) };
    }
    
    // Reset monthly if needed
    if (now - monthStart >= 2592000000) {
      await this.redis.hset(key, { usedMonth: 0, monthStart: now });
    }

    // Clean old burst entries (older than 1 minute)
    await this.redis.zremrangebyscore(burst:${key.replace('quota:', '')}, 0, now - 60000);
    const burstCount = await this.redis.zcard(burst:${key.replace('quota:', '')});
    
    return {
      usedToday: parseInt(data.usedToday) || 0,
      usedMonth: parseInt(data.usedMonth) || 0,
      usedMinute: burstCount,
      remainingToday: parseInt(data.dailyLimit) - (parseInt(data.usedToday) || 0),
      remainingMinute: parseInt(data.burstLimit) - burstCount
    };
  }

  private async selectFallbackModel(
    preference: string, 
    tokens: number
  ): Promise {
    if (preference === 'deepseek') {
      return 'deepseek-v3.2';
    }
    return tokens > 10000 ? 'claude-sonnet-4.5' : 'deepseek-v3.2';
  }
}

export { ClaudeCodeQuotaManager, QuotaConfig, QuotaUsage };

2. HolySheep Integration với Quota Manager

Integration thực tế với HolySheep API sử dụng quota manager:

import { ClaudeCodeQuotaManager } from './quota-manager';

interface ClaudeCodeRequest {
  teamId: string;
  userId: string;
  prompt: string;
  maxTokens: number;
  temperature?: number;
}

interface ClaudeCodeResponse {
  completion: string;
  model: string;
  tokensUsed: number;
  latencyMs: number;
  costUSD: number;
}

class HolySheepClaudeGateway {
  private quotaManager: ClaudeCodeQuotaManager;
  private apiKey: string;
  private baseUrl = 'https://api.holysheep.ai/v1';
  
  // Pricing: Claude Sonnet 4.5 = $3/MTok, DeepSeek V3.2 = $0.08/MTok
  private readonly PRICING = {
    'claude-sonnet-4.5': 3.0,
    'deepseek-v3.2': 0.08,
    'gpt-4.1': 1.5
  };

  constructor(quotaManager: ClaudeCodeQuotaManager) {
    this.quotaManager = quotaManager;
    this.apiKey = process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY';
  }

  async generateCode(request: ClaudeCodeRequest): Promise {
    const startTime = Date.now();
    
    // Step 1: Check quota before making API call
    const quotaResult = await this.quotaManager.checkAndConsumeQuota(
      request.teamId,
      request.userId,
      request.maxTokens
    );

    let model = 'claude-sonnet-4.5';
    
    if (!quotaResult.allowed && quotaResult.fallback) {
      console.log(Quota exceeded, using fallback: ${quotaResult.fallback});
      model = quotaResult.fallback;
    }

    // Step 2: Call HolySheep API
    const response = await fetch(${this.baseUrl}/chat/completions, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': Bearer ${this.apiKey}
      },
      body: JSON.stringify({
        model: model,
        messages: [
          {
            role: 'system',
            content: You are an expert software engineer. Generate production-quality code.
          },
          {
            role: 'user',
            content: request.prompt
          }
        ],
        max_tokens: request.maxTokens,
        temperature: request.temperature || 0.3
      })
    });

    if (!response.ok) {
      const error = await response.text();
      // Rollback quota on error
      await this.rollbackQuota(request.teamId, request.maxTokens);
      throw new Error(HolySheep API error: ${response.status} - ${error});
    }

    const data = await response.json();
    const latencyMs = Date.now() - startTime;
    const tokensUsed = data.usage?.total_tokens || request.maxTokens;
    const costUSD = (tokensUsed / 1000000) * this.PRICING[model as keyof typeof this.PRICING];

    return {
      completion: data.choices[0]?.message?.content || '',
      model: data.model,
      tokensUsed,
      latencyMs,
      costUSD
    };
  }

  private async rollbackQuota(teamId: string, tokens: number): Promise {
    const key = quota:${teamId};
    await this.redis.pipeline()
      .hincrby(key, 'usedToday', -tokens)
      .hincrby(key, 'usedMonth', -tokens)
      .exec();
  }
}

export { HolySheepClaudeGateway, ClaudeCodeRequest, ClaudeCodeResponse };

Audit Log Implementation

1. Compliance-First Audit Logger

Audit log không chỉ là log request/response mà cần track metadata cho compliance audit:

import { Client } from '@clickhouse/client';
import { Redis } from 'ioredis';

interface AuditEntry {
  id: string;
  timestamp: Date;
  teamId: string;
  userId: string;
  requestId: string;
  model: string;
  promptHash: string;
  promptPreview: string;      // First 500 chars
  responseHash: string;
  tokensUsed: number;
  costUSD: number;
  latencyMs: number;
  status: 'success' | 'quota_exceeded' | 'error' | 'fallback';
  fallbackFrom?: string;
  fallbackTo?: string;
  ipAddress: string;
  userAgent: string;
  metadata: Record;
}

class ClaudeCodeAuditLogger {
  private clickhouse: Client;
  private redis: Redis;
  private buffer: AuditEntry[] = [];
  private readonly BUFFER_SIZE = 100;
  private readonly FLUSH_INTERVAL = 5000;

  constructor(clickhouse: Client, redis: Redis) {
    this.clickhouse = clickhouse;
    this.redis = redis;
    
    // Start periodic flush
    setInterval(() => this.flush(), this.FLUSH_INTERVAL);
  }

  async log(entry: Omit): Promise {
    const fullEntry: AuditEntry = {
      ...entry,
      id: this.generateId(),
      timestamp: new Date()
    };

    // Buffer for batch insert
    this.buffer.push(fullEntry);
    
    // Also store in Redis for real-time queries
    await this.redis.lpush(
      audit:recent:${entry.teamId},
      JSON.stringify(fullEntry)
    );
    await this.redis.ltrim(audit:recent:${entry.teamId}, 0, 999);

    // Check buffer size
    if (this.buffer.length >= this.BUFFER_SIZE) {
      await this.flush();
    }

    // Emit event for real-time monitoring
    this.emit('audit_entry', fullEntry);
  }

  private async flush(): Promise {
    if (this.buffer.length === 0) return;

    const entries = this.buffer.splice(0, this.BUFFER_SIZE);
    
    try {
      await this.clickhouse.insert({
        table: 'claude_code_audit',
        values: entries.map(e => ({
          id: e.id,
          timestamp: e.timestamp,
          team_id: e.teamId,
          user_id: e.userId,
          request_id: e.requestId,
          model: e.model,
          prompt_hash: e.promptHash,
          prompt_preview: e.promptPreview,
          response_hash: e.responseHash,
          tokens_used: e.tokensUsed,
          cost_usd: e.costUSD,
          latency_ms: e.latencyMs,
          status: e.status,
          fallback_from: e.fallbackFrom,
          fallback_to: e.fallbackTo,
          ip_address: e.ipAddress,
          user_agent: e.userAgent,
          metadata: JSON.stringify(e.metadata)
        })),
        format: 'JSONEachRow'
      });
    } catch (error) {
      console.error('Failed to flush audit log:', error);
      // Re-add to buffer for retry
      this.buffer.unshift(...entries);
    }
  }

  // Query audit logs for compliance report
  async queryAuditLogs(params: {
    teamId?: string;
    userId?: string;
    startDate?: Date;
    endDate?: Date;
    status?: string;
    limit?: number;
  }): Promise {
    const query = `
      SELECT 
        id, timestamp, team_id, user_id, request_id, model,
        prompt_hash, prompt_preview, response_hash, tokens_used,
        cost_usd, latency_ms, status, fallback_from, fallback_to,
        ip_address, user_agent, metadata
      FROM claude_code_audit
      WHERE 1=1
      ${params.teamId ? AND team_id = '${params.teamId}' : ''}
      ${params.userId ? AND user_id = '${params.userId}' : ''}
      ${params.startDate ? AND timestamp >= '${params.startDate.toISOString()}' : ''}
      ${params.endDate ? AND timestamp <= '${params.endDate.toISOString()}' : ''}
      ${params.status ? AND status = '${params.status}' : ''}
      ORDER BY timestamp DESC
      LIMIT ${params.limit || 1000}
    `;

    const result = await this.clickhouse.query({ query, format: 'JSON' });
    const rows = await result.json();
    
    return rows.map((row: any) => ({
      id: row.id,
      timestamp: new Date(row.timestamp),
      teamId: row.team_id,
      userId: row.user_id,
      requestId: row.request_id,
      model: row.model,
      promptHash: row.prompt_hash,
      promptPreview: row.prompt_preview,
      responseHash: row.response_hash,
      tokensUsed: row.tokens_used,
      costUSD: row.cost_usd,
      latencyMs: row.latency_ms,
      status: row.status,
      fallbackFrom: row.fallback_from,
      fallbackTo: row.fallback_to,
      ipAddress: row.ip_address,
      userAgent: row.user_agent,
      metadata: JSON.parse(row.metadata || '{}')
    }));
  }

  // Generate monthly compliance report
  async generateComplianceReport(teamId: string, month: Date): Promise {
    const startDate = new Date(month.getFullYear(), month.getMonth(), 1);
    const endDate = new Date(month.getFullYear(), month.getMonth() + 1, 0);

    const logs = await this.queryAuditLogs({
      teamId,
      startDate,
      endDate,
      limit: 100000
    });

    const totalTokens = logs.reduce((sum, l) => sum + l.tokensUsed, 0);
    const totalCost = logs.reduce((sum, l) => sum + l.costUSD, 0);
    const byModel = this.groupBy(logs, 'model');
    const byStatus = this.groupBy(logs, 'status');
    const byUser = this.groupBy(logs, 'userId');

    return {
      period: { start: startDate, end: endDate },
      summary: {
        totalRequests: logs.length,
        totalTokens,
        totalCostUSD: totalCost,
        avgLatencyMs: logs.reduce((s, l) => s + l.latencyMs, 0) / logs.length
      },
      byModel: Object.entries(byModel).map(([model, entries]) => ({
        model,
        requests: entries.length,
        tokens: entries.reduce((s: number, e: AuditEntry) => s + e.tokensUsed, 0),
        cost: entries.reduce((s: number, e: AuditEntry) => s + e.costUSD, 0)
      })),
      byStatus: Object.entries(byStatus).map(([status, entries]) => ({
        status,
        count: (entries as AuditEntry[]).length
      })),
      topUsers: Object.entries(byUser)
        .map(([userId, entries]) => ({
          userId,
          requests: (entries as AuditEntry[]).length,
          tokens: (entries as AuditEntry[]).reduce((s: number, e: AuditEntry) => s + e.tokensUsed, 0)
        }))
        .sort((a, b) => b.tokens - a.tokens)
        .slice(0, 10)
    };
  }

  private generateId(): string {
    return audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
  }

  private groupBy(arr: T[], key: keyof T): Record {
    return arr.reduce((acc, item) => {
      const k = String(item[key]);
      (acc[k] = acc[k] || []).push(item);
      return acc;
    }, {} as Record);
  }
}

export { ClaudeCodeAuditLogger, AuditEntry };


Fallback Routing Strategy

1. Multi-Model Fallback Router

Fallback routing thông minh với health check và latency-based selection:

interface ModelEndpoint {
  name: string;
  baseUrl: string;
  apiKey: string;
  maxTokens: number;
  pricingPerMToken: number;
  healthScore: number;
  avgLatency: number;
  isHealthy: boolean;
  lastCheck: Date;
}

class ClaudeCodeFallbackRouter {
  private endpoints: Map = new Map();
  private readonly HEALTH_CHECK_INTERVAL = 30000; // 30 seconds
  private readonly LATENCY_WEIGHT = 0.6;
  private readonly COST_WEIGHT = 0.3;
  private readonly HEALTH_WEIGHT = 0.1;

  constructor() {
    this.initializeEndpoints();
    this.startHealthCheck();
  }

  private initializeEndpoints(): void {
    // HolySheep hosted models
    this.endpoints.set('claude-sonnet-4.5', {
      name: 'claude-sonnet-4.5',
      baseUrl: 'https://api.holysheep.ai/v1',
      apiKey: process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY',
      maxTokens: 200000,
      pricingPerMToken: 3.0,   // $3/MTok on HolySheep
      healthScore: 1.0,
      avgLatency: 45,
      isHealthy: true,
      lastCheck: new Date()
    });

    this.endpoints.set('deepseek-v3.2', {
      name: 'deepseek-v3.2',
      baseUrl: 'https://api.holysheep.ai/v1',
      apiKey: process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY',
      maxTokens: 64000,
      pricingPerMToken: 0.08,  // $0.08/MTok on HolySheep
      healthScore: 1.0,
      avgLatency: 38,
      isHealthy: true,
      lastCheck: new Date()
    });

    this.endpoints.set('gpt-4.1', {
      name: 'gpt-4.1',
      baseUrl: 'https://api.holysheep.ai/v1',
      apiKey: process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY',
      maxTokens: 128000,
      pricingPerMToken: 1.5,   // $1.5/MTok on HolySheep
      healthScore: 1.0,
      avgLatency: 52,
      isHealthy: true,
      lastCheck: new Date()
    });
  }

  // Score-based model selection
  selectOptimalModel(params: {
    requiredTokens: number;
    priority: 'latency' | 'cost' | 'quality';
    fallbackChain?: string[];
  }): string {
    const { requiredTokens, priority, fallbackChain } = params;

    // If fallback chain provided, use it
    if (fallbackChain && fallbackChain.length > 0) {
      for (const modelName of fallbackChain) {
        const endpoint = this.endpoints.get(modelName);
        if (endpoint && endpoint.isHealthy && endpoint.maxTokens >= requiredTokens) {
          return modelName;
        }
      }
    }

    // Score all healthy endpoints
    const scored = Array.from(this.endpoints.entries())
      .filter(([_, ep]) => ep.isHealthy && ep.maxTokens >= requiredTokens)
      .map(([name, ep]) => ({
        name,
        score: this.calculateScore(ep, priority)
      }))
      .sort((a, b) => b.score - a.score);

    if (scored.length === 0) {
      throw new Error('No healthy endpoints available');
    }

    return scored[0].name;
  }

  private calculateScore(endpoint: ModelEndpoint, priority: string): number {
    const latencyScore = Math.max(0, 1 - endpoint.avgLatency / 200);
    const costScore = Math.max(0, 1 - endpoint.pricingPerMToken / 10);
    const healthScore = endpoint.healthScore;

    switch (priority) {
      case 'latency':
        return this.LATENCY_WEIGHT * latencyScore * 2 +
               this.COST_WEIGHT * costScore +
               this.HEALTH_WEIGHT * healthScore;
      case 'cost':
        return this.LATENCY_WEIGHT * latencyScore +
               this.COST_WEIGHT * costScore * 2 +
               this.HEALTH_WEIGHT * healthScore;
      case 'quality':
        return this.LATENCY_WEIGHT * latencyScore * 0.5 +
               this.COST_WEIGHT * costScore * 0.5 +
               this.HEALTH_WEIGHT * healthScore * 2;
      default:
        return this.LATENCY_WEIGHT * latencyScore +
               this.COST_WEIGHT * costScore +
               this.HEALTH_WEIGHT * healthScore;
    }
  }

  // Health check for all endpoints
  private startHealthCheck(): void {
    setInterval(async () => {
      for (const [name, endpoint] of this.endpoints.entries()) {
        try {
          const start = Date.now();
          const response = await fetch(${endpoint.baseUrl}/models, {
            headers: { 'Authorization': Bearer ${endpoint.apiKey} }
          });
          const latency = Date.now() - start;

          if (response.ok) {
            endpoint.isHealthy = true;
            endpoint.healthScore = Math.min(1, endpoint.healthScore + 0.1);
            endpoint.avgLatency = endpoint.avgLatency * 0.8 + latency * 0.2;
          } else {
            endpoint.isHealthy = false;
            endpoint.healthScore = Math.max(0, endpoint.healthScore - 0.3);
          }
        } catch {
          endpoint.isHealthy = false;
          endpoint.healthScore = Math.max(0, endpoint.healthScore - 0.3);
        }
        endpoint.lastCheck = new Date();
      }
    }, this.HEALTH_CHECK_INTERVAL);
  }

  // Execute request with automatic fallback
  async executeWithFallback(params: {
    prompt: string;
    maxTokens: number;
    priority: 'latency' | 'cost' | 'quality';
    fallbackChain?: string[];
  }): Promise<{
    completion: string;
    model: string;
    fallbackUsed: boolean;
    latencyMs: number;
    costUSD: number;
  }> {
    const model = this.selectOptimalModel({
      requiredTokens: params.maxTokens,
      priority: params.priority,
      fallbackChain: params.fallbackChain
    });

    const endpoint = this.endpoints.get(model)!;
    const startTime = Date.now();
    let fallbackUsed = false;
    let lastError: Error | null = null;

    const chain = params.fallbackChain || [model];

    for (const modelName of chain) {
      const ep = this.endpoints.get(modelName);
      if (!ep || !ep.isHealthy) continue;

      try {
        const response = await fetch(${ep.baseUrl}/chat/completions, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': Bearer ${ep.apiKey}
          },
          body: JSON.stringify({
            model: modelName,
            messages: [{ role: 'user', content: params.prompt }],
            max_tokens: params.maxTokens,
            temperature: 0.3
          })
        });

        if (!response.ok) {
          throw new Error(API error: ${response.status});
        }

        const data = await response.json();
        const latencyMs = Date.now() - startTime;
        const tokensUsed = data.usage?.total_tokens || params.maxTokens;
        const costUSD = (tokensUsed / 1000000) * ep.pricingPerMToken;

        return {
          completion: data.choices[0]?.message?.content || '',
          model: modelName,
          fallbackUsed,
          latencyMs,
          costUSD
        };
      } catch (error) {
        lastError = error as Error;
        fallbackUsed = true;
        console.warn(Model ${modelName} failed, trying next fallback...);
        continue;
      }
    }

    throw new Error(All fallback models failed. Last error: ${lastError?.message});
  }

  getEndpointStats(): object[] {
    return Array.from(this.endpoints.entries()).map(([name, ep]) => ({
      name,
      isHealthy: ep.isHealthy,
      avgLatencyMs: Math.round(ep.avgLatency),
      healthScore: Math.round(ep.healthScore * 100) / 100,
      pricingPerMToken: ep.pricingPerMToken,
      lastCheck: ep.lastCheck
    }));
  }
}

export { ClaudeCodeFallbackRouter, ModelEndpoint };

Cost Alert System

1. Real-time Cost Monitoring với Alert


import { Telegram, Webhook } from 'telegraf';
import { Client } from '@clickhouse/client';

interface CostThreshold {
  level: 'warning' | 'critical' | 'emergency';
  percentage: number;  // % of monthly budget
  notifyChannels: ('telegram' | 'email' | 'webhook' | 'slack')[];
  cooldownMinutes: number;
}

class ClaudeCodeCostAlert {
  private clickhouse: Client;
  private telegram?: Telegram;
  private webhooks: Webhook[] = [];
  private thresholds: CostThreshold[] = [
    { level: 'warning', percentage: 50, notifyChannels: ['telegram'], cooldownMinutes: 60 },
    { level: 'critical', percentage: 75, notifyChannels: ['telegram', 'webhook'], cooldownMinutes: 30 },
    { level: 'emergency', percentage: 90, notifyChannels: ['telegram', 'email', 'webhook', 'slack'], cooldownMinutes: 15 }
  ];
  private notifiedThresholds: Map = new Map();
  private readonly CHECK_INTERVAL = 60000; // 1 minute

  constructor(clickhouse: Client) {
    this.clickhouse = clickhouse;
    this.startMonitoring();
  }

  setTelegram(token: string, chatId: string): void {
    this.telegram = new Telegram(token);
    this.telegram.telegram.sendMessage(chatId, '✅ Claude Code Cost Alert System Started');
  }

  addWebhook(name: string, url: string, secret: string): void {
    this.webhooks.push({ name, url, secret });
  }

  private startMonitoring(): void {
    setInterval(() => this.checkCosts(), this.CHECK_INTERVAL);
  }

  private async checkCosts(): Promise {
    const teams = await this.getMonitoredTeams();
    
    for (const team of teams) {
      const usage = await this.calculateCurrentUsage(team.id);
      const budget = team.monthlyBudget;
      const percentage = (usage.totalCost / budget) * 100;

      for (const threshold of this.thresholds) {
        if (percentage >= threshold.percentage) {
          const thresholdKey = ${team.id}:${threshold.level};
          const lastNotified = this.notifiedThresholds.get(thresholdKey);
          
          if (!lastNotified || 
              Date.now() - lastNotified.getTime() > threshold.cooldownMinutes * 60000) {
            await this.sendAlert(team, usage, percentage, threshold);
            this.notifiedThresholds.set(thresholdKey, new Date());
          }
        }
      }
    }
  }

  private async calculateCurrentUsage(teamId: string): Promise<{
    totalCost: number;
    totalTokens: number;
    requests: number;
    avgCostPerRequest: number;
    projectedMonthlyCost: number;
  }> {
    const now = new Date();
    const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
    
    const query = `
      SELECT 
        count() as requests,
        sum(cost_usd) as total_cost,
        sum(tokens_used) as total_tokens,
        avg(cost_usd) as avg_cost
      FROM claude_code_audit
      WHERE team_id = '${teamId}'
        AND timestamp >= '${monthStart.toISOString()}'
    `;

    const result = await this.clickhouse.query({ query, format: 'JSON' });
    const rows = await result.json();
    const row = rows[0] || {};

    const totalCost = parseFloat(row.total_cost) || 0;
    const totalTokens = parseInt(row.total_tokens) || 0;
    const requests = parseInt(row.requests) || 0;
    const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
    const daysPassed = now.getDate();
    const projectedMonthlyCost = totalCost * (daysInMonth / daysPassed);

    return {
      totalCost,
      totalTokens,
      requests,
      avgCostPerRequest: requests > 0 ? totalCost / requests : 0,
      projectedMonthlyCost
    };
  }

  private async sendAlert(
    team: { id: string; name: string },
    usage: { totalCost: number; totalTokens: number; requests: number; projectedMonthlyCost: number },
    percentage: number,
    threshold: CostThreshold
  ): Promise {
    const emoji = threshold.level === 'warning' ?


🔥 Thử HolySheep AI

Cổng AI API trực tiếp. Hỗ trợ Claude, GPT-5, Gemini, DeepSeek — một khóa, không cần VPN.

👉 Đăng ký miễn phí →