mirror of
https://github.com/githubhjs/CLIProxyAPIPlus.git
synced 2026-07-12 01:25:13 +00:00
feat: add Kiro OAuth web, rate limiter, metrics, fingerprint, background refresh and model converter
This commit is contained in:
+200
-1
@@ -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: ®istry.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 = ®istry.ThinkingSupport{
|
||||
Min: m.Thinking.Min,
|
||||
Max: m.Thinking.Max,
|
||||
ZeroAllowed: m.Thinking.ZeroAllowed,
|
||||
DynamicAllowed: m.Thinking.DynamicAllowed,
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, agentic)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user