feat: add Kiro OAuth web, rate limiter, metrics, fingerprint, background refresh and model converter

This commit is contained in:
781456868@qq.com
2026-01-18 15:04:29 +08:00
parent 93d7883513
commit 0e77e93e5d
37 changed files with 10396 additions and 282 deletions
+200 -1
View File
@@ -13,6 +13,7 @@ import (
"time"
"github.com/router-for-me/CLIProxyAPI/v6/internal/api"
kiroauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kiro"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/usage"
@@ -775,7 +776,7 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
models = registry.GetGitHubCopilotModels()
models = applyExcludedModels(models, excluded)
case "kiro":
models = registry.GetKiroModels()
models = s.fetchKiroModels(a)
models = applyExcludedModels(models, excluded)
default:
// Handle OpenAI-compatibility providers by name using config
@@ -1338,3 +1339,201 @@ func applyOAuthModelAlias(cfg *config.Config, provider, authKind string, models
}
return out
}
// fetchKiroModels attempts to dynamically fetch Kiro models from the API.
// If dynamic fetch fails, it falls back to static registry.GetKiroModels().
func (s *Service) fetchKiroModels(a *coreauth.Auth) []*ModelInfo {
if a == nil {
log.Debug("kiro: auth is nil, using static models")
return registry.GetKiroModels()
}
// Extract token data from auth attributes
tokenData := s.extractKiroTokenData(a)
if tokenData == nil || tokenData.AccessToken == "" {
log.Debug("kiro: no valid token data in auth, using static models")
return registry.GetKiroModels()
}
// Create KiroAuth instance
kAuth := kiroauth.NewKiroAuth(s.cfg)
if kAuth == nil {
log.Warn("kiro: failed to create KiroAuth instance, using static models")
return registry.GetKiroModels()
}
// Use timeout context for API call
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// Attempt to fetch dynamic models
apiModels, err := kAuth.ListAvailableModels(ctx, tokenData)
if err != nil {
log.Warnf("kiro: failed to fetch dynamic models: %v, using static models", err)
return registry.GetKiroModels()
}
if len(apiModels) == 0 {
log.Debug("kiro: API returned no models, using static models")
return registry.GetKiroModels()
}
// Convert API models to ModelInfo
models := convertKiroAPIModels(apiModels)
// Generate agentic variants
models = generateKiroAgenticVariants(models)
log.Infof("kiro: successfully fetched %d models from API (including agentic variants)", len(models))
return models
}
// extractKiroTokenData extracts KiroTokenData from auth attributes and metadata.
func (s *Service) extractKiroTokenData(a *coreauth.Auth) *kiroauth.KiroTokenData {
if a == nil || a.Attributes == nil {
return nil
}
accessToken := strings.TrimSpace(a.Attributes["access_token"])
if accessToken == "" {
return nil
}
tokenData := &kiroauth.KiroTokenData{
AccessToken: accessToken,
ProfileArn: strings.TrimSpace(a.Attributes["profile_arn"]),
}
// Also try to get refresh token from metadata
if a.Metadata != nil {
if rt, ok := a.Metadata["refresh_token"].(string); ok {
tokenData.RefreshToken = rt
}
}
return tokenData
}
// convertKiroAPIModels converts Kiro API models to ModelInfo slice.
func convertKiroAPIModels(apiModels []*kiroauth.KiroModel) []*ModelInfo {
if len(apiModels) == 0 {
return nil
}
now := time.Now().Unix()
models := make([]*ModelInfo, 0, len(apiModels))
for _, m := range apiModels {
if m == nil || m.ModelID == "" {
continue
}
// Create model ID with kiro- prefix
modelID := "kiro-" + normalizeKiroModelID(m.ModelID)
info := &ModelInfo{
ID: modelID,
Object: "model",
Created: now,
OwnedBy: "aws",
Type: "kiro",
DisplayName: formatKiroDisplayName(m.ModelName, m.RateMultiplier),
Description: m.Description,
ContextLength: 200000,
MaxCompletionTokens: 64000,
Thinking: &registry.ThinkingSupport{Min: 1024, Max: 32000, ZeroAllowed: true, DynamicAllowed: true},
}
if m.MaxInputTokens > 0 {
info.ContextLength = m.MaxInputTokens
}
models = append(models, info)
}
return models
}
// normalizeKiroModelID normalizes a Kiro model ID by converting dots to dashes
// and removing common prefixes.
func normalizeKiroModelID(modelID string) string {
// Remove common prefixes
modelID = strings.TrimPrefix(modelID, "anthropic.")
modelID = strings.TrimPrefix(modelID, "amazon.")
// Replace dots with dashes for consistency
modelID = strings.ReplaceAll(modelID, ".", "-")
// Replace underscores with dashes
modelID = strings.ReplaceAll(modelID, "_", "-")
return strings.ToLower(modelID)
}
// formatKiroDisplayName formats the display name with rate multiplier info.
func formatKiroDisplayName(modelName string, rateMultiplier float64) string {
if modelName == "" {
return ""
}
displayName := "Kiro " + modelName
if rateMultiplier > 0 && rateMultiplier != 1.0 {
displayName += fmt.Sprintf(" (%.1fx credit)", rateMultiplier)
}
return displayName
}
// generateKiroAgenticVariants generates agentic variants for Kiro models.
// Agentic variants have optimized system prompts for coding agents.
func generateKiroAgenticVariants(models []*ModelInfo) []*ModelInfo {
if len(models) == 0 {
return models
}
result := make([]*ModelInfo, 0, len(models)*2)
result = append(result, models...)
for _, m := range models {
if m == nil {
continue
}
// Skip if already an agentic variant
if strings.HasSuffix(m.ID, "-agentic") {
continue
}
// Skip auto models from agentic variant generation
if strings.Contains(m.ID, "-auto") {
continue
}
// Create agentic variant
agentic := &ModelInfo{
ID: m.ID + "-agentic",
Object: m.Object,
Created: m.Created,
OwnedBy: m.OwnedBy,
Type: m.Type,
DisplayName: m.DisplayName + " (Agentic)",
Description: m.Description + " - Optimized for coding agents (chunked writes)",
ContextLength: m.ContextLength,
MaxCompletionTokens: m.MaxCompletionTokens,
}
// Copy thinking support if present
if m.Thinking != nil {
agentic.Thinking = &registry.ThinkingSupport{
Min: m.Thinking.Min,
Max: m.Thinking.Max,
ZeroAllowed: m.Thinking.ZeroAllowed,
DynamicAllowed: m.Thinking.DynamicAllowed,
}
}
result = append(result, agentic)
}
return result
}