on main help sign in
fix: refresh oauth token in the background
b621f93Raphael Amorim
internal/agent/coordinator.go+15−1
@@ -130,7 +130,20 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string,
130
130
131 mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg)
131 mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg)
132
132
133+ if providerCfg.OAuthToken != nil && providerCfg.OAuthToken.IsExpired() {
134+ slog.Info("Detected expired OAuth token, attempting refresh", "provider", providerCfg.ID)
135+ if refreshErr := c.cfg.RefreshOAuthToken(ctx, providerCfg.ID); refreshErr != nil {
136+ slog.Error("Failed to refresh OAuth token", "provider", providerCfg.ID, "error", refreshErr)
137+ return nil, refreshErr
138+ }
139+
140+ // Rebuild models with refreshed token
141+ if updateErr := c.UpdateModels(ctx); updateErr != nil {
142+ slog.Error("Failed to update models after token refresh", "error", updateErr)
143+ return nil, updateErr
144+ }
145+ }
133− return c.currentAgent.Run(ctx, SessionAgentCall{
146+ result, err := c.currentAgent.Run(ctx, SessionAgentCall{
134 SessionID: sessionID,
147 SessionID: sessionID,
135 Prompt: prompt,
148 Prompt: prompt,
136 Attachments: attachments,
149 Attachments: attachments,
@@ -142,6 +155,7 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string,
142 FrequencyPenalty: freqPenalty,
155 FrequencyPenalty: freqPenalty,
143 PresencePenalty: presPenalty,
156 PresencePenalty: presPenalty,
144 })
157 })
158+ return result, err
145}
159}
146
160
147func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.ProviderOptions {
161func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.ProviderOptions {
internal/config/config.go+38
@@ -16,6 +16,7 @@ import (
16 "github.com/charmbracelet/crush/internal/csync"
16 "github.com/charmbracelet/crush/internal/csync"
17 "github.com/charmbracelet/crush/internal/env"
17 "github.com/charmbracelet/crush/internal/env"
18 "github.com/charmbracelet/crush/internal/oauth"
18 "github.com/charmbracelet/crush/internal/oauth"
19+ "github.com/charmbracelet/crush/internal/oauth/claude"
19 "github.com/invopop/jsonschema"
20 "github.com/invopop/jsonschema"
20 "github.com/tidwall/sjson"
21 "github.com/tidwall/sjson"
21)
22)
@@ -468,6 +469,43 @@ func (c *Config) SetConfigField(key string, value any) error {
468 return nil
469 return nil
469}
470}
470
471
472+func (c *Config) RefreshOAuthToken(ctx context.Context, providerID string) error {
473+ providerConfig, exists := c.Providers.Get(providerID)
474+ if !exists {
475+ return fmt.Errorf("provider %s not found", providerID)
476+ }
477+
478+ if providerConfig.OAuthToken == nil {
479+ return fmt.Errorf("provider %s does not have an OAuth token", providerID)
480+ }
481+
482+ // Only Anthropic provider uses OAuth for now
483+ if providerID != string(catwalk.InferenceProviderAnthropic) {
484+ return fmt.Errorf("OAuth refresh not supported for provider %s", providerID)
485+ }
486+
487+ newToken, err := claude.RefreshToken(ctx, providerConfig.OAuthToken.RefreshToken)
488+ if err != nil {
489+ return fmt.Errorf("failed to refresh OAuth token for provider %s: %w", providerID, err)
490+ }
491+
492+ slog.Info("Successfully refreshed OAuth token in background", "provider", providerID)
493+ providerConfig.OAuthToken = newToken
494+ providerConfig.APIKey = fmt.Sprintf("Bearer %s", newToken.AccessToken)
495+ providerConfig.SetupClaudeCode()
496+
497+ c.Providers.Set(providerID, providerConfig)
498+
499+ if err := cmp.Or(
500+ c.SetConfigField(fmt.Sprintf("providers.%s.api_key", providerID), newToken.AccessToken),
501+ c.SetConfigField(fmt.Sprintf("providers.%s.oauth", providerID), newToken),
502+ ); err != nil {
503+ return fmt.Errorf("failed to persist refreshed token: %w", err)
504+ }
505+
506+ return nil
507+}
508+
471func (c *Config) SetProviderAPIKey(providerID string, apiKey any) error {
509func (c *Config) SetProviderAPIKey(providerID string, apiKey any) error {
472 var providerConfig ProviderConfig
510 var providerConfig ProviderConfig
473 var exists bool
511 var exists bool