mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-08 06:43:41 +00:00
- Add IAM Identity Center (IDC) authentication with CLI flags (--kiro-idc-login, --kiro-idc-start-url, --kiro-idc-region) and login flow - Add ProfileArn auto-fetching in Execute/ExecuteStream for imported IDC accounts - Simplify endpoint preference with map-based alias lookup and getAuthValue helper - Redesign fingerprint as global singleton with external config and per-account deterministic generation - Add StartURL and FingerprintConfig fields to Kiro config - Add AgentContinuationID/AgentTaskType support in Kiro translators - Add comprehensive tests for executor, fingerprint, SSO OIDC, and AWS helpers - Add CLI login documentation to README
237 lines
7.3 KiB
Go
237 lines
7.3 KiB
Go
// Package kiro provides authentication functionality for AWS CodeWhisperer (Kiro) API.
|
|
// This file implements usage quota checking and monitoring.
|
|
package kiro
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
|
)
|
|
|
|
// UsageQuotaResponse represents the API response structure for usage quota checking.
|
|
type UsageQuotaResponse struct {
|
|
UsageBreakdownList []UsageBreakdownExtended `json:"usageBreakdownList"`
|
|
SubscriptionInfo *SubscriptionInfo `json:"subscriptionInfo,omitempty"`
|
|
NextDateReset float64 `json:"nextDateReset,omitempty"`
|
|
}
|
|
|
|
// UsageBreakdownExtended represents detailed usage information for quota checking.
|
|
// Note: UsageBreakdown is already defined in codewhisperer_client.go
|
|
type UsageBreakdownExtended struct {
|
|
ResourceType string `json:"resourceType"`
|
|
UsageLimitWithPrecision float64 `json:"usageLimitWithPrecision"`
|
|
CurrentUsageWithPrecision float64 `json:"currentUsageWithPrecision"`
|
|
FreeTrialInfo *FreeTrialInfoExtended `json:"freeTrialInfo,omitempty"`
|
|
}
|
|
|
|
// FreeTrialInfoExtended represents free trial usage information.
|
|
type FreeTrialInfoExtended struct {
|
|
FreeTrialStatus string `json:"freeTrialStatus"`
|
|
UsageLimitWithPrecision float64 `json:"usageLimitWithPrecision"`
|
|
CurrentUsageWithPrecision float64 `json:"currentUsageWithPrecision"`
|
|
}
|
|
|
|
// QuotaStatus represents the quota status for a token.
|
|
type QuotaStatus struct {
|
|
TotalLimit float64
|
|
CurrentUsage float64
|
|
RemainingQuota float64
|
|
IsExhausted bool
|
|
ResourceType string
|
|
NextReset time.Time
|
|
}
|
|
|
|
// UsageChecker provides methods for checking token quota usage.
|
|
type UsageChecker struct {
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// NewUsageChecker creates a new UsageChecker instance.
|
|
func NewUsageChecker(cfg *config.Config) *UsageChecker {
|
|
return &UsageChecker{
|
|
httpClient: util.SetProxy(&cfg.SDKConfig, &http.Client{Timeout: 30 * time.Second}),
|
|
}
|
|
}
|
|
|
|
// NewUsageCheckerWithClient creates a UsageChecker with a custom HTTP client.
|
|
func NewUsageCheckerWithClient(client *http.Client) *UsageChecker {
|
|
return &UsageChecker{
|
|
httpClient: client,
|
|
}
|
|
}
|
|
|
|
// CheckUsage retrieves usage limits for the given token.
|
|
func (c *UsageChecker) CheckUsage(ctx context.Context, tokenData *KiroTokenData) (*UsageQuotaResponse, error) {
|
|
if tokenData == nil {
|
|
return nil, fmt.Errorf("token data is nil")
|
|
}
|
|
|
|
if tokenData.AccessToken == "" {
|
|
return nil, fmt.Errorf("access token is empty")
|
|
}
|
|
|
|
queryParams := map[string]string{
|
|
"origin": "AI_EDITOR",
|
|
"profileArn": tokenData.ProfileArn,
|
|
"resourceType": "AGENTIC_REQUEST",
|
|
}
|
|
|
|
// Use endpoint from profileArn if available
|
|
endpoint := GetKiroAPIEndpointFromProfileArn(tokenData.ProfileArn)
|
|
url := buildURL(endpoint, pathGetUsageLimits, queryParams)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
accountKey := GetAccountKey(tokenData.ClientID, tokenData.RefreshToken)
|
|
setRuntimeHeaders(req, tokenData.AccessToken, accountKey)
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result UsageQuotaResponse
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse usage response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// CheckUsageByAccessToken retrieves usage limits using an access token and profile ARN directly.
|
|
func (c *UsageChecker) CheckUsageByAccessToken(ctx context.Context, accessToken, profileArn string) (*UsageQuotaResponse, error) {
|
|
tokenData := &KiroTokenData{
|
|
AccessToken: accessToken,
|
|
ProfileArn: profileArn,
|
|
}
|
|
return c.CheckUsage(ctx, tokenData)
|
|
}
|
|
|
|
// GetRemainingQuota calculates the remaining quota from usage limits.
|
|
func GetRemainingQuota(usage *UsageQuotaResponse) float64 {
|
|
if usage == nil || len(usage.UsageBreakdownList) == 0 {
|
|
return 0
|
|
}
|
|
|
|
var totalRemaining float64
|
|
for _, breakdown := range usage.UsageBreakdownList {
|
|
remaining := breakdown.UsageLimitWithPrecision - breakdown.CurrentUsageWithPrecision
|
|
if remaining > 0 {
|
|
totalRemaining += remaining
|
|
}
|
|
|
|
if breakdown.FreeTrialInfo != nil {
|
|
freeRemaining := breakdown.FreeTrialInfo.UsageLimitWithPrecision - breakdown.FreeTrialInfo.CurrentUsageWithPrecision
|
|
if freeRemaining > 0 {
|
|
totalRemaining += freeRemaining
|
|
}
|
|
}
|
|
}
|
|
|
|
return totalRemaining
|
|
}
|
|
|
|
// IsQuotaExhausted checks if the quota is exhausted based on usage limits.
|
|
func IsQuotaExhausted(usage *UsageQuotaResponse) bool {
|
|
if usage == nil || len(usage.UsageBreakdownList) == 0 {
|
|
return true
|
|
}
|
|
|
|
for _, breakdown := range usage.UsageBreakdownList {
|
|
if breakdown.CurrentUsageWithPrecision < breakdown.UsageLimitWithPrecision {
|
|
return false
|
|
}
|
|
|
|
if breakdown.FreeTrialInfo != nil {
|
|
if breakdown.FreeTrialInfo.CurrentUsageWithPrecision < breakdown.FreeTrialInfo.UsageLimitWithPrecision {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetQuotaStatus retrieves a comprehensive quota status for a token.
|
|
func (c *UsageChecker) GetQuotaStatus(ctx context.Context, tokenData *KiroTokenData) (*QuotaStatus, error) {
|
|
usage, err := c.CheckUsage(ctx, tokenData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
status := &QuotaStatus{
|
|
IsExhausted: IsQuotaExhausted(usage),
|
|
}
|
|
|
|
if len(usage.UsageBreakdownList) > 0 {
|
|
breakdown := usage.UsageBreakdownList[0]
|
|
status.TotalLimit = breakdown.UsageLimitWithPrecision
|
|
status.CurrentUsage = breakdown.CurrentUsageWithPrecision
|
|
status.RemainingQuota = breakdown.UsageLimitWithPrecision - breakdown.CurrentUsageWithPrecision
|
|
status.ResourceType = breakdown.ResourceType
|
|
|
|
if breakdown.FreeTrialInfo != nil {
|
|
status.TotalLimit += breakdown.FreeTrialInfo.UsageLimitWithPrecision
|
|
status.CurrentUsage += breakdown.FreeTrialInfo.CurrentUsageWithPrecision
|
|
freeRemaining := breakdown.FreeTrialInfo.UsageLimitWithPrecision - breakdown.FreeTrialInfo.CurrentUsageWithPrecision
|
|
if freeRemaining > 0 {
|
|
status.RemainingQuota += freeRemaining
|
|
}
|
|
}
|
|
}
|
|
|
|
if usage.NextDateReset > 0 {
|
|
status.NextReset = time.Unix(int64(usage.NextDateReset/1000), 0)
|
|
}
|
|
|
|
return status, nil
|
|
}
|
|
|
|
// CalculateAvailableCount calculates the available request count based on usage limits.
|
|
func CalculateAvailableCount(usage *UsageQuotaResponse) float64 {
|
|
return GetRemainingQuota(usage)
|
|
}
|
|
|
|
// GetUsagePercentage calculates the usage percentage.
|
|
func GetUsagePercentage(usage *UsageQuotaResponse) float64 {
|
|
if usage == nil || len(usage.UsageBreakdownList) == 0 {
|
|
return 100.0
|
|
}
|
|
|
|
var totalLimit, totalUsage float64
|
|
for _, breakdown := range usage.UsageBreakdownList {
|
|
totalLimit += breakdown.UsageLimitWithPrecision
|
|
totalUsage += breakdown.CurrentUsageWithPrecision
|
|
|
|
if breakdown.FreeTrialInfo != nil {
|
|
totalLimit += breakdown.FreeTrialInfo.UsageLimitWithPrecision
|
|
totalUsage += breakdown.FreeTrialInfo.CurrentUsageWithPrecision
|
|
}
|
|
}
|
|
|
|
if totalLimit == 0 {
|
|
return 100.0
|
|
}
|
|
|
|
return (totalUsage / totalLimit) * 100
|
|
}
|