diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index 14f3cf6562865b3f492e263c24fb69f8d96c0c87..5690b1311a4fd0d0290277d01e734b17ab3d2a66 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -132,6 +132,13 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string, mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg) + if providerCfg.OAuthToken != nil && providerCfg.OAuthToken.IsExpired() { + slog.Info("Token needs to be refreshed", "provider", providerCfg.ID) + if err := c.refreshOAuth2Token(ctx, providerCfg); err != nil { + return nil, err + } + } + run := func() (*fantasy.AgentResult, error) { return c.currentAgent.Run(ctx, SessionAgentCall{ SessionID: sessionID, diff --git a/internal/tui/util/shell.go b/internal/tui/util/shell.go index 43690c8aaacd6a396b02220536d022c674f16111..7bf30e2640e79a80291077faa5134a9eea28a87b 100644 --- a/internal/tui/util/shell.go +++ b/internal/tui/util/shell.go @@ -2,25 +2,14 @@ package util import ( "context" - "errors" - "os/exec" tea "charm.land/bubbletea/v2" - "mvdan.cc/sh/v3/shell" + "github.com/charmbracelet/crush/internal/uiutil" ) // ExecShell parses a shell command string and executes it with exec.Command. // Uses shell.Fields for proper handling of shell syntax like quotes and // arguments while preserving TTY handling for terminal editors. func ExecShell(ctx context.Context, cmdStr string, callback tea.ExecCallback) tea.Cmd { - fields, err := shell.Fields(cmdStr, nil) - if err != nil { - return ReportError(err) - } - if len(fields) == 0 { - return ReportError(errors.New("empty command")) - } - - cmd := exec.CommandContext(ctx, fields[0], fields[1:]...) - return tea.ExecProcess(cmd, callback) + return uiutil.ExecShell(ctx, cmdStr, callback) } diff --git a/internal/uiutil/uiutil.go b/internal/uiutil/uiutil.go index 2a05f95a3a142c55673f19a303b4f1b2cbeea65b..efd89dda69f780b354777916b459675154780372 100644 --- a/internal/uiutil/uiutil.go +++ b/internal/uiutil/uiutil.go @@ -4,10 +4,14 @@ package uiutil import ( + "context" + "errors" "log/slog" + "os/exec" "time" tea "charm.land/bubbletea/v2" + "mvdan.cc/sh/v3/shell" ) type Cursor interface { @@ -60,3 +64,19 @@ type ( } ClearStatusMsg struct{} ) + +// ExecShell parses a shell command string and executes it with exec.Command. +// Uses shell.Fields for proper handling of shell syntax like quotes and +// arguments while preserving TTY handling for terminal editors. +func ExecShell(ctx context.Context, cmdStr string, callback tea.ExecCallback) tea.Cmd { + fields, err := shell.Fields(cmdStr, nil) + if err != nil { + return ReportError(err) + } + if len(fields) == 0 { + return ReportError(errors.New("empty command")) + } + + cmd := exec.CommandContext(ctx, fields[0], fields[1:]...) + return tea.ExecProcess(cmd, callback) +}