Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions internal/watcher/synthesizer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"fmt"
"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.
Expand All @@ -30,6 +32,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
Expand Down Expand Up @@ -292,3 +296,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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The kAuth != nil check is redundant. The kiroauth.NewKiroAuth() function always returns a non-nil pointer to a KiroAuth struct, so this check will always pass. Removing it simplifies the code.

Suggested change
if kk.TokenFile != "" && kAuth != nil {
if kk.TokenFile != "" {

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
}
Comment on lines +366 to +368

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The refreshToken is being stored in the Attributes map. According to the coreauth.Auth struct's documentation, Attributes are for immutable configuration, while Metadata is for mutable runtime state. Since a refresh token can be updated during the application's lifecycle, it should only be stored in Metadata. Storing it in Attributes could lead to confusion or bugs if other parts of the code rely on Attributes being static. The token is already correctly stored in Metadata later in this function.

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
}