on main help sign in
refactor(coordinator): extract token refresh helpers to reduce duplication

Extract `refreshTokenIfExpired` and `retryAfterUnauthorized` functions to eliminate duplicated OAuth/API key refresh logic in both `Run` and `Summarize`. ๐Ÿ’˜ Generated with Crush Assisted-by: Kimi K2.6 via Crush <crush@charm.land>

commit8cd4786cc66b29da2412e6cc9f40cfa364cd1a17 authorAndrey Nering <andreynering@users.noreply.github.com> dateWed 6 May 2026, 17:23 (5 months ago) parent6bdd233
1 file changed+32โˆ’39
internal/agent/coordinator.go+32โˆ’39
internal/agent/coordinator.go+32โˆ’39
@@ -184,13 +184,10 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string,
184184
185185 mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg)
186186
187โˆ’ if providerCfg.OAuthToken != nil && providerCfg.OAuthToken.IsExpired() {
188โˆ’ slog.Debug("Token needs to be refreshed", "provider", providerCfg.ID)
189โˆ’ if err := c.refreshOAuth2Token(ctx, providerCfg); err != nil {
190โˆ’ // NOTE(@andreynering): We don't return here because the event handling to ask the user to reauthenticate
191โˆ’ // depends on the flow below. If refresh fails, proceed with the token we have.
192โˆ’ slog.Error("Failed to refresh OAuth2 token. Proceeding with existing token.", "error", err)
193โˆ’ }
187+ if err := c.refreshTokenIfExpired(ctx, providerCfg); err != nil {
188+ // NOTE(@andreynering): We don't return here because the event handling to ask the user to reauthenticate
189+ // depends on the flow below. If refresh fails, proceed with the token we have.
190+ slog.Error("Failed to refresh OAuth2 token. Proceeding with existing token.", "error", err)
194191 }
195192
196193 run := func() (*fantasy.AgentResult, error) {
@@ -212,20 +209,7 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string,
212209 logTurnSkillUsage(sessionID, prompt, c.activeSkills, c.skillTracker, beforeLoaded)
213210
214211 if c.isUnauthorized(originalErr) {
215โˆ’ switch {
216โˆ’ case providerCfg.OAuthToken != nil:
217โˆ’ slog.Debug("Received 401. Refreshing token and retrying", "provider", providerCfg.ID)
218โˆ’ if err := c.refreshOAuth2Token(ctx, providerCfg); err != nil {
219โˆ’ return nil, originalErr
220โˆ’ }
221โˆ’ slog.Debug("Retrying request with refreshed OAuth token", "provider", providerCfg.ID)
222โˆ’ return run()
223โˆ’ case strings.Contains(providerCfg.APIKeyTemplate, "$"):
224โˆ’ slog.Debug("Received 401. Refreshing API Key template and retrying", "provider", providerCfg.ID)
225โˆ’ if err := c.refreshApiKeyTemplate(ctx, providerCfg); err != nil {
226โˆ’ return nil, originalErr
227โˆ’ }
228โˆ’ slog.Debug("Retrying request with refreshed API key", "provider", providerCfg.ID)
212+ if err := c.retryAfterUnauthorized(ctx, providerCfg); err == nil {
229213 return run()
230214 }
231215 }
@@ -962,12 +946,8 @@ func (c *coordinator) Summarize(ctx context.Context, sessionID string) error {
962946 return errModelProviderNotConfigured
963947 }
964948
965โˆ’ // Proactively refresh OAuth token if expired, same as Run().
966โˆ’ if providerCfg.OAuthToken != nil && providerCfg.OAuthToken.IsExpired() {
967โˆ’ slog.Debug("Token needs to be refreshed before summarize", "provider", providerCfg.ID)
968โˆ’ if err := c.refreshOAuth2Token(ctx, providerCfg); err != nil {
969โˆ’ slog.Error("Failed to refresh OAuth2 token before summarize. Proceeding with existing token.", "error", err)
970โˆ’ }
949+ if err := c.refreshTokenIfExpired(ctx, providerCfg); err != nil {
950+ slog.Error("Failed to refresh OAuth2 token before summarize. Proceeding with existing token.", "error", err)
971951 }
972952
973953 summarize := func() error {
@@ -976,18 +956,7 @@ func (c *coordinator) Summarize(ctx context.Context, sessionID string) error {
976956
977957 err := summarize()
978958 if err != nil && c.isUnauthorized(err) {
979โˆ’ switch {
980โˆ’ case providerCfg.OAuthToken != nil:
981โˆ’ slog.Debug("Received 401 during summarize. Refreshing token and retrying", "provider", providerCfg.ID)
982โˆ’ if refreshErr := c.refreshOAuth2Token(ctx, providerCfg); refreshErr != nil {
983โˆ’ return err
984โˆ’ }
985โˆ’ return summarize()
986โˆ’ case strings.Contains(providerCfg.APIKeyTemplate, "$"):
987โˆ’ slog.Debug("Received 401 during summarize. Refreshing API Key template and retrying", "provider", providerCfg.ID)
988โˆ’ if refreshErr := c.refreshApiKeyTemplate(ctx, providerCfg); refreshErr != nil {
989โˆ’ return err
990โˆ’ }
959+ if retryErr := c.retryAfterUnauthorized(ctx, providerCfg); retryErr == nil {
991960 return summarize()
992961 }
993962 }
@@ -995,6 +964,30 @@ func (c *coordinator) Summarize(ctx context.Context, sessionID string) error {
995964 return err
996965}
997966
967+// refreshTokenIfExpired proactively refreshes the OAuth token if it has expired.
968+func (c *coordinator) refreshTokenIfExpired(ctx context.Context, providerCfg config.ProviderConfig) error {
969+ if providerCfg.OAuthToken == nil || !providerCfg.OAuthToken.IsExpired() {
970+ return nil
971+ }
972+ slog.Debug("Token needs to be refreshed", "provider", providerCfg.ID)
973+ return c.refreshOAuth2Token(ctx, providerCfg)
974+}
975+
976+// retryAfterUnauthorized attempts to refresh credentials after receiving a 401
977+// and returns nil if retry should be attempted.
978+func (c *coordinator) retryAfterUnauthorized(ctx context.Context, providerCfg config.ProviderConfig) error {
979+ switch {
980+ case providerCfg.OAuthToken != nil:
981+ slog.Debug("Received 401. Refreshing token and retrying", "provider", providerCfg.ID)
982+ return c.refreshOAuth2Token(ctx, providerCfg)
983+ case strings.Contains(providerCfg.APIKeyTemplate, "$"):
984+ slog.Debug("Received 401. Refreshing API Key template and retrying", "provider", providerCfg.ID)
985+ return c.refreshApiKeyTemplate(ctx, providerCfg)
986+ default:
987+ return nil
988+ }
989+}
990+
998991func (c *coordinator) isUnauthorized(err error) bool {
999992 var providerErr *fantasy.ProviderError
1000993 return errors.As(err, &providerErr) && providerErr.StatusCode == http.StatusUnauthorized