mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-24 04:30:27 +00:00
Merge remote-tracking branch 'origin/main' into pr-104
# Conflicts: # config.example.yaml # internal/config/config.go # sdk/cliproxy/auth/model_name_mappings.go
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
kiroauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kiro"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -39,12 +40,35 @@ func (w *Watcher) start(ctx context.Context) error {
|
||||
}
|
||||
log.Debugf("watching auth directory: %s", w.authDir)
|
||||
|
||||
w.watchKiroIDETokenFile()
|
||||
|
||||
go w.processEvents(ctx)
|
||||
|
||||
w.reloadClients(true, nil, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) watchKiroIDETokenFile() {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Debugf("failed to get home directory for Kiro IDE token watch: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
kiroTokenDir := filepath.Join(homeDir, ".aws", "sso", "cache")
|
||||
|
||||
if _, statErr := os.Stat(kiroTokenDir); os.IsNotExist(statErr) {
|
||||
log.Debugf("Kiro IDE token directory does not exist: %s", kiroTokenDir)
|
||||
return
|
||||
}
|
||||
|
||||
if errAdd := w.watcher.Add(kiroTokenDir); errAdd != nil {
|
||||
log.Debugf("failed to watch Kiro IDE token directory %s: %v", kiroTokenDir, errAdd)
|
||||
return
|
||||
}
|
||||
log.Debugf("watching Kiro IDE token directory: %s", kiroTokenDir)
|
||||
}
|
||||
|
||||
func (w *Watcher) processEvents(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
@@ -73,11 +97,17 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
|
||||
isConfigEvent := normalizedName == normalizedConfigPath && event.Op&configOps != 0
|
||||
authOps := fsnotify.Create | fsnotify.Write | fsnotify.Remove | fsnotify.Rename
|
||||
isAuthJSON := strings.HasPrefix(normalizedName, normalizedAuthDir) && strings.HasSuffix(normalizedName, ".json") && event.Op&authOps != 0
|
||||
if !isConfigEvent && !isAuthJSON {
|
||||
isKiroIDEToken := w.isKiroIDETokenFile(event.Name) && event.Op&authOps != 0
|
||||
if !isConfigEvent && !isAuthJSON && !isKiroIDEToken {
|
||||
// Ignore unrelated files (e.g., cookie snapshots *.cookie) and other noise.
|
||||
return
|
||||
}
|
||||
|
||||
if isKiroIDEToken {
|
||||
w.handleKiroIDETokenChange(event)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
log.Debugf("file system event detected: %s %s", event.Op.String(), event.Name)
|
||||
|
||||
@@ -124,6 +154,42 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) isKiroIDETokenFile(path string) bool {
|
||||
normalized := filepath.ToSlash(path)
|
||||
return strings.HasSuffix(normalized, "kiro-auth-token.json") && strings.Contains(normalized, ".aws/sso/cache")
|
||||
}
|
||||
|
||||
func (w *Watcher) handleKiroIDETokenChange(event fsnotify.Event) {
|
||||
log.Debugf("Kiro IDE token file event detected: %s %s", event.Op.String(), event.Name)
|
||||
|
||||
if event.Op&(fsnotify.Remove|fsnotify.Rename) != 0 {
|
||||
time.Sleep(replaceCheckDelay)
|
||||
if _, statErr := os.Stat(event.Name); statErr != nil {
|
||||
log.Debugf("Kiro IDE token file removed: %s", event.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tokenData, err := kiroauth.LoadKiroIDEToken()
|
||||
if err != nil {
|
||||
log.Debugf("failed to load Kiro IDE token after change: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("Kiro IDE token file updated, access token refreshed (provider: %s)", tokenData.Provider)
|
||||
|
||||
w.refreshAuthState(true)
|
||||
|
||||
w.clientsMutex.RLock()
|
||||
cfg := w.config
|
||||
w.clientsMutex.RUnlock()
|
||||
|
||||
if w.reloadCallback != nil && cfg != nil {
|
||||
log.Debugf("triggering server update callback after Kiro IDE token change")
|
||||
w.reloadCallback(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) authFileUnchanged(path string) (bool, error) {
|
||||
data, errRead := os.ReadFile(path)
|
||||
if errRead != nil {
|
||||
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
kiroauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kiro"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ConfigSynthesizer generates Auth entries from configuration API keys.
|
||||
@@ -31,6 +33,8 @@ func (s *ConfigSynthesizer) Synthesize(ctx *SynthesisContext) ([]*coreauth.Auth,
|
||||
out = append(out, s.synthesizeClaudeKeys(ctx)...)
|
||||
// Codex API Keys
|
||||
out = append(out, s.synthesizeCodexKeys(ctx)...)
|
||||
// Kiro (AWS CodeWhisperer)
|
||||
out = append(out, s.synthesizeKiroKeys(ctx)...)
|
||||
// OpenAI-compat
|
||||
out = append(out, s.synthesizeOpenAICompat(ctx)...)
|
||||
// Vertex-compat
|
||||
@@ -317,3 +321,96 @@ func (s *ConfigSynthesizer) synthesizeVertexCompat(ctx *SynthesisContext) []*cor
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// synthesizeKiroKeys creates Auth entries for Kiro (AWS CodeWhisperer) tokens.
|
||||
func (s *ConfigSynthesizer) synthesizeKiroKeys(ctx *SynthesisContext) []*coreauth.Auth {
|
||||
cfg := ctx.Config
|
||||
now := ctx.Now
|
||||
idGen := ctx.IDGenerator
|
||||
|
||||
if len(cfg.KiroKey) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]*coreauth.Auth, 0, len(cfg.KiroKey))
|
||||
kAuth := kiroauth.NewKiroAuth(cfg)
|
||||
|
||||
for i := range cfg.KiroKey {
|
||||
kk := cfg.KiroKey[i]
|
||||
var accessToken, profileArn, refreshToken string
|
||||
|
||||
// Try to load from token file first
|
||||
if kk.TokenFile != "" && kAuth != nil {
|
||||
tokenData, err := kAuth.LoadTokenFromFile(kk.TokenFile)
|
||||
if err != nil {
|
||||
log.Warnf("failed to load kiro token file %s: %v", kk.TokenFile, err)
|
||||
} else {
|
||||
accessToken = tokenData.AccessToken
|
||||
profileArn = tokenData.ProfileArn
|
||||
refreshToken = tokenData.RefreshToken
|
||||
}
|
||||
}
|
||||
|
||||
// Override with direct config values if provided
|
||||
if kk.AccessToken != "" {
|
||||
accessToken = kk.AccessToken
|
||||
}
|
||||
if kk.ProfileArn != "" {
|
||||
profileArn = kk.ProfileArn
|
||||
}
|
||||
if kk.RefreshToken != "" {
|
||||
refreshToken = kk.RefreshToken
|
||||
}
|
||||
|
||||
if accessToken == "" {
|
||||
log.Warnf("kiro config[%d] missing access_token, skipping", i)
|
||||
continue
|
||||
}
|
||||
|
||||
// profileArn is optional for AWS Builder ID users
|
||||
id, token := idGen.Next("kiro:token", accessToken, profileArn)
|
||||
attrs := map[string]string{
|
||||
"source": fmt.Sprintf("config:kiro[%s]", token),
|
||||
"access_token": accessToken,
|
||||
}
|
||||
if profileArn != "" {
|
||||
attrs["profile_arn"] = profileArn
|
||||
}
|
||||
if kk.Region != "" {
|
||||
attrs["region"] = kk.Region
|
||||
}
|
||||
if kk.AgentTaskType != "" {
|
||||
attrs["agent_task_type"] = kk.AgentTaskType
|
||||
}
|
||||
if kk.PreferredEndpoint != "" {
|
||||
attrs["preferred_endpoint"] = kk.PreferredEndpoint
|
||||
} else if cfg.KiroPreferredEndpoint != "" {
|
||||
// Apply global default if not overridden by specific key
|
||||
attrs["preferred_endpoint"] = cfg.KiroPreferredEndpoint
|
||||
}
|
||||
if refreshToken != "" {
|
||||
attrs["refresh_token"] = refreshToken
|
||||
}
|
||||
proxyURL := strings.TrimSpace(kk.ProxyURL)
|
||||
a := &coreauth.Auth{
|
||||
ID: id,
|
||||
Provider: "kiro",
|
||||
Label: "kiro-token",
|
||||
Status: coreauth.StatusActive,
|
||||
ProxyURL: proxyURL,
|
||||
Attributes: attrs,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if refreshToken != "" {
|
||||
if a.Metadata == nil {
|
||||
a.Metadata = make(map[string]any)
|
||||
}
|
||||
a.Metadata["refresh_token"] = refreshToken
|
||||
}
|
||||
|
||||
out = append(out, a)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user