mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-24 03:04:21 +01:00
Merge pull request #22 from adamdottv/adam/retries
fix(anthropic): better 429/529 handling
This commit is contained in:
@@ -7,16 +7,16 @@ import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/kujtimiihoxha/termai/internal/config"
|
||||
"github.com/kujtimiihoxha/termai/internal/llm/models"
|
||||
"github.com/kujtimiihoxha/termai/internal/pubsub"
|
||||
"github.com/kujtimiihoxha/termai/internal/tui/styles"
|
||||
"github.com/kujtimiihoxha/termai/internal/tui/util"
|
||||
"github.com/kujtimiihoxha/termai/internal/version"
|
||||
)
|
||||
|
||||
type statusCmp struct {
|
||||
err error
|
||||
info string
|
||||
width int
|
||||
messageTTL time.Duration
|
||||
info *util.InfoMsg
|
||||
width int
|
||||
messageTTL time.Duration
|
||||
}
|
||||
|
||||
// clearMessageCmd is a command that clears status messages after a timeout
|
||||
@@ -34,17 +34,15 @@ func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
case util.ErrorMsg:
|
||||
m.err = msg
|
||||
m.info = ""
|
||||
return m, m.clearMessageCmd()
|
||||
case pubsub.Event[util.InfoMsg]:
|
||||
m.info = &msg.Payload
|
||||
return m, m.clearMessageCmd()
|
||||
case util.InfoMsg:
|
||||
m.info = string(msg)
|
||||
m.err = nil
|
||||
m.info = &msg
|
||||
return m, m.clearMessageCmd()
|
||||
case util.ClearStatusMsg:
|
||||
m.info = ""
|
||||
m.err = nil
|
||||
m.info = nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -56,25 +54,25 @@ var (
|
||||
|
||||
func (m statusCmp) View() string {
|
||||
status := styles.Padded.Background(styles.Grey).Foreground(styles.Text).Render("? help")
|
||||
|
||||
if m.err != nil {
|
||||
status += styles.Regular.Padding(0, 1).
|
||||
Background(styles.Red).
|
||||
Foreground(styles.Text).
|
||||
Width(m.availableFooterMsgWidth()).
|
||||
Render(m.err.Error())
|
||||
} else if m.info != "" {
|
||||
status += styles.Padded.
|
||||
if m.info != nil {
|
||||
infoStyle := styles.Padded.
|
||||
Foreground(styles.Base).
|
||||
Background(styles.Green).
|
||||
Width(m.availableFooterMsgWidth()).
|
||||
Render(m.info)
|
||||
Width(m.availableFooterMsgWidth())
|
||||
switch m.info.Type {
|
||||
case util.InfoTypeInfo:
|
||||
infoStyle = infoStyle.Background(styles.Blue)
|
||||
case util.InfoTypeWarn:
|
||||
infoStyle = infoStyle.Background(styles.Peach)
|
||||
case util.InfoTypeError:
|
||||
infoStyle = infoStyle.Background(styles.Red)
|
||||
}
|
||||
status += infoStyle.Render(m.info.Msg)
|
||||
} else {
|
||||
status += styles.Padded.
|
||||
Foreground(styles.Base).
|
||||
Background(styles.LightGrey).
|
||||
Width(m.availableFooterMsgWidth()).
|
||||
Render(m.info)
|
||||
Render("")
|
||||
}
|
||||
status += m.model()
|
||||
status += versionWidget
|
||||
@@ -93,6 +91,6 @@ func (m statusCmp) model() string {
|
||||
|
||||
func NewStatusCmp() tea.Model {
|
||||
return &statusCmp{
|
||||
messageTTL: 5 * time.Second,
|
||||
messageTTL: 15 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,13 +69,13 @@ type permissionDialogCmp struct {
|
||||
func formatDiff(diffText string) string {
|
||||
lines := strings.Split(diffText, "\n")
|
||||
var formattedLines []string
|
||||
|
||||
|
||||
// Define styles for different line types
|
||||
addStyle := lipgloss.NewStyle().Foreground(styles.Green)
|
||||
removeStyle := lipgloss.NewStyle().Foreground(styles.Red)
|
||||
headerStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Blue)
|
||||
contextStyle := lipgloss.NewStyle().Foreground(styles.SubText0)
|
||||
|
||||
|
||||
// Process each line
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "+") {
|
||||
@@ -90,7 +90,7 @@ func formatDiff(diffText string) string {
|
||||
formattedLines = append(formattedLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Join all formatted lines
|
||||
return strings.Join(formattedLines, "\n")
|
||||
}
|
||||
@@ -112,13 +112,13 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
p.selectOption.Blur()
|
||||
// Add a visual indicator for focus change
|
||||
cmds = append(cmds, tea.Batch(
|
||||
util.CmdHandler(util.InfoMsg("Viewing content - use arrow keys to scroll")),
|
||||
util.ReportInfo("Viewing content - use arrow keys to scroll"),
|
||||
))
|
||||
} else {
|
||||
p.selectOption.Focus()
|
||||
// Add a visual indicator for focus change
|
||||
cmds = append(cmds, tea.Batch(
|
||||
util.CmdHandler(util.InfoMsg("Select an action")),
|
||||
util.CmdHandler(util.ReportInfo("Select an action")),
|
||||
))
|
||||
}
|
||||
return p, tea.Batch(cmds...)
|
||||
@@ -162,44 +162,44 @@ func (p *permissionDialogCmp) render() string {
|
||||
lipgloss.JoinHorizontal(lipgloss.Left, keyStyle.Render("Path:"), " ", valueStyle.Render(p.permission.Path)),
|
||||
" ",
|
||||
}
|
||||
|
||||
|
||||
// Create the header content first so it can be used in all cases
|
||||
headerContent := lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
|
||||
|
||||
|
||||
r, _ := glamour.NewTermRenderer(
|
||||
glamour.WithStyles(styles.CatppuccinMarkdownStyle()),
|
||||
glamour.WithWordWrap(p.width-10),
|
||||
glamour.WithEmoji(),
|
||||
)
|
||||
|
||||
|
||||
// Handle different tool types
|
||||
switch p.permission.ToolName {
|
||||
case tools.BashToolName:
|
||||
pr := p.permission.Params.(tools.BashPermissionsParams)
|
||||
headerParts = append(headerParts, keyStyle.Render("Command:"))
|
||||
content := fmt.Sprintf("```bash\n%s\n```", pr.Command)
|
||||
|
||||
|
||||
renderedContent, _ := r.Render(content)
|
||||
p.contentViewPort.Width = p.width - 2 - 2
|
||||
|
||||
|
||||
// Calculate content height dynamically based on content
|
||||
contentLines := len(strings.Split(renderedContent, "\n"))
|
||||
// Set a reasonable min/max for the viewport height
|
||||
minContentHeight := 3
|
||||
maxContentHeight := p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
|
||||
|
||||
|
||||
// Add some padding to the content lines
|
||||
contentHeight := contentLines + 2
|
||||
contentHeight = max(contentHeight, minContentHeight)
|
||||
contentHeight = min(contentHeight, maxContentHeight)
|
||||
p.contentViewPort.Height = contentHeight
|
||||
|
||||
|
||||
p.contentViewPort.SetContent(renderedContent)
|
||||
|
||||
|
||||
// Style the viewport
|
||||
var contentBorder lipgloss.Border
|
||||
var borderColor lipgloss.TerminalColor
|
||||
|
||||
|
||||
if p.isViewportFocus {
|
||||
contentBorder = lipgloss.DoubleBorder()
|
||||
borderColor = styles.Blue
|
||||
@@ -207,47 +207,47 @@ func (p *permissionDialogCmp) render() string {
|
||||
contentBorder = lipgloss.RoundedBorder()
|
||||
borderColor = styles.Flamingo
|
||||
}
|
||||
|
||||
|
||||
contentStyle := lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
Padding(0, 1).
|
||||
Border(contentBorder).
|
||||
BorderForeground(borderColor)
|
||||
|
||||
|
||||
if p.isViewportFocus {
|
||||
contentStyle = contentStyle.BorderBackground(styles.Surface0)
|
||||
}
|
||||
|
||||
|
||||
contentFinal := contentStyle.Render(p.contentViewPort.View())
|
||||
|
||||
|
||||
return lipgloss.JoinVertical(
|
||||
lipgloss.Top,
|
||||
headerContent,
|
||||
contentFinal,
|
||||
form,
|
||||
)
|
||||
|
||||
|
||||
case tools.EditToolName:
|
||||
pr := p.permission.Params.(tools.EditPermissionsParams)
|
||||
headerParts = append(headerParts, keyStyle.Render("Update"))
|
||||
// Recreate header content with the updated headerParts
|
||||
headerContent = lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
|
||||
|
||||
|
||||
// Format the diff with colors
|
||||
formattedDiff := formatDiff(pr.Diff)
|
||||
|
||||
|
||||
// Set up viewport for the diff content
|
||||
p.contentViewPort.Width = p.width - 2 - 2
|
||||
|
||||
|
||||
// Calculate content height dynamically based on window size
|
||||
maxContentHeight := p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
|
||||
p.contentViewPort.Height = maxContentHeight
|
||||
p.contentViewPort.SetContent(formattedDiff)
|
||||
|
||||
|
||||
// Style the viewport
|
||||
var contentBorder lipgloss.Border
|
||||
var borderColor lipgloss.TerminalColor
|
||||
|
||||
|
||||
if p.isViewportFocus {
|
||||
contentBorder = lipgloss.DoubleBorder()
|
||||
borderColor = styles.Blue
|
||||
@@ -255,47 +255,47 @@ func (p *permissionDialogCmp) render() string {
|
||||
contentBorder = lipgloss.RoundedBorder()
|
||||
borderColor = styles.Flamingo
|
||||
}
|
||||
|
||||
|
||||
contentStyle := lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
Padding(0, 1).
|
||||
Border(contentBorder).
|
||||
BorderForeground(borderColor)
|
||||
|
||||
|
||||
if p.isViewportFocus {
|
||||
contentStyle = contentStyle.BorderBackground(styles.Surface0)
|
||||
}
|
||||
|
||||
|
||||
contentFinal := contentStyle.Render(p.contentViewPort.View())
|
||||
|
||||
|
||||
return lipgloss.JoinVertical(
|
||||
lipgloss.Top,
|
||||
headerContent,
|
||||
contentFinal,
|
||||
form,
|
||||
)
|
||||
|
||||
|
||||
case tools.WriteToolName:
|
||||
pr := p.permission.Params.(tools.WritePermissionsParams)
|
||||
headerParts = append(headerParts, keyStyle.Render("Content"))
|
||||
// Recreate header content with the updated headerParts
|
||||
headerContent = lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
|
||||
|
||||
|
||||
// Format the diff with colors
|
||||
formattedDiff := formatDiff(pr.Content)
|
||||
|
||||
|
||||
// Set up viewport for the content
|
||||
p.contentViewPort.Width = p.width - 2 - 2
|
||||
|
||||
|
||||
// Calculate content height dynamically based on window size
|
||||
maxContentHeight := p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
|
||||
p.contentViewPort.Height = maxContentHeight
|
||||
p.contentViewPort.SetContent(formattedDiff)
|
||||
|
||||
|
||||
// Style the viewport
|
||||
var contentBorder lipgloss.Border
|
||||
var borderColor lipgloss.TerminalColor
|
||||
|
||||
|
||||
if p.isViewportFocus {
|
||||
contentBorder = lipgloss.DoubleBorder()
|
||||
borderColor = styles.Blue
|
||||
@@ -303,75 +303,75 @@ func (p *permissionDialogCmp) render() string {
|
||||
contentBorder = lipgloss.RoundedBorder()
|
||||
borderColor = styles.Flamingo
|
||||
}
|
||||
|
||||
|
||||
contentStyle := lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
Padding(0, 1).
|
||||
Border(contentBorder).
|
||||
BorderForeground(borderColor)
|
||||
|
||||
|
||||
if p.isViewportFocus {
|
||||
contentStyle = contentStyle.BorderBackground(styles.Surface0)
|
||||
}
|
||||
|
||||
|
||||
contentFinal := contentStyle.Render(p.contentViewPort.View())
|
||||
|
||||
|
||||
return lipgloss.JoinVertical(
|
||||
lipgloss.Top,
|
||||
headerContent,
|
||||
contentFinal,
|
||||
form,
|
||||
)
|
||||
|
||||
|
||||
case tools.FetchToolName:
|
||||
pr := p.permission.Params.(tools.FetchPermissionsParams)
|
||||
headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
|
||||
content := p.permission.Description
|
||||
|
||||
|
||||
renderedContent, _ := r.Render(content)
|
||||
p.contentViewPort.Width = p.width - 2 - 2
|
||||
p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
|
||||
p.contentViewPort.SetContent(renderedContent)
|
||||
|
||||
|
||||
// Style the viewport
|
||||
contentStyle := lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
Padding(0, 1).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(styles.Flamingo)
|
||||
|
||||
|
||||
contentFinal := contentStyle.Render(p.contentViewPort.View())
|
||||
if renderedContent == "" {
|
||||
contentFinal = ""
|
||||
}
|
||||
|
||||
|
||||
return lipgloss.JoinVertical(
|
||||
lipgloss.Top,
|
||||
headerContent,
|
||||
contentFinal,
|
||||
form,
|
||||
)
|
||||
|
||||
|
||||
default:
|
||||
content := p.permission.Description
|
||||
|
||||
|
||||
renderedContent, _ := r.Render(content)
|
||||
p.contentViewPort.Width = p.width - 2 - 2
|
||||
p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
|
||||
p.contentViewPort.SetContent(renderedContent)
|
||||
|
||||
|
||||
// Style the viewport
|
||||
contentStyle := lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
Padding(0, 1).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(styles.Flamingo)
|
||||
|
||||
|
||||
contentFinal := contentStyle.Render(p.contentViewPort.View())
|
||||
if renderedContent == "" {
|
||||
contentFinal = ""
|
||||
}
|
||||
|
||||
|
||||
return lipgloss.JoinVertical(
|
||||
lipgloss.Top,
|
||||
headerContent,
|
||||
|
||||
@@ -140,7 +140,7 @@ func (m *editorCmp) Send() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
messages, _ := m.app.Messages.List(m.sessionID)
|
||||
if hasUnfinishedMessages(messages) {
|
||||
return util.InfoMsg("Assistant is still working on the previous message")
|
||||
return util.ReportWarn("Assistant is still working on the previous message")
|
||||
}
|
||||
a, _ := agent.NewCoderAgent(m.app)
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case pubsub.Event[permission.PermissionRequest]:
|
||||
return a, dialog.NewPermissionDialogCmd(msg.Payload)
|
||||
case pubsub.Event[util.InfoMsg]:
|
||||
a.status, _ = a.status.Update(msg)
|
||||
case dialog.PermissionResponseMsg:
|
||||
switch msg.Action {
|
||||
case dialog.PermissionAllow:
|
||||
@@ -121,8 +123,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return a, a.moveToPage(msg.ID)
|
||||
case util.InfoMsg:
|
||||
a.status, _ = a.status.Update(msg)
|
||||
case util.ErrorMsg:
|
||||
a.status, _ = a.status.Update(msg)
|
||||
case tea.KeyMsg:
|
||||
if a.editorMode == vimtea.ModeNormal {
|
||||
switch {
|
||||
@@ -141,7 +141,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if a.currentPage == page.ReplPage {
|
||||
sessions, err := a.app.Sessions.List()
|
||||
if err != nil {
|
||||
return a, util.CmdHandler(util.ErrorMsg(err))
|
||||
return a, util.CmdHandler(util.ReportError(err))
|
||||
}
|
||||
lastSession := sessions[0]
|
||||
if lastSession.MessageCount == 0 {
|
||||
@@ -149,7 +149,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
s, err := a.app.Sessions.Create("New Session")
|
||||
if err != nil {
|
||||
return a, util.CmdHandler(util.ErrorMsg(err))
|
||||
return a, util.CmdHandler(util.ReportError(err))
|
||||
}
|
||||
return a, util.CmdHandler(repl.SelectedSessionMsg{SessionID: s.ID})
|
||||
}
|
||||
|
||||
@@ -9,12 +9,39 @@ func CmdHandler(msg tea.Msg) tea.Cmd {
|
||||
}
|
||||
|
||||
func ReportError(err error) tea.Cmd {
|
||||
return CmdHandler(ErrorMsg(err))
|
||||
return CmdHandler(InfoMsg{
|
||||
Type: InfoTypeError,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
type InfoType int
|
||||
|
||||
const (
|
||||
InfoTypeInfo InfoType = iota
|
||||
InfoTypeWarn
|
||||
InfoTypeError
|
||||
)
|
||||
|
||||
func ReportInfo(info string) tea.Cmd {
|
||||
return CmdHandler(InfoMsg{
|
||||
Type: InfoTypeInfo,
|
||||
Msg: info,
|
||||
})
|
||||
}
|
||||
|
||||
func ReportWarn(warn string) tea.Cmd {
|
||||
return CmdHandler(InfoMsg{
|
||||
Type: InfoTypeWarn,
|
||||
Msg: warn,
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
InfoMsg string
|
||||
ErrorMsg error
|
||||
InfoMsg struct {
|
||||
Type InfoType
|
||||
Msg string
|
||||
}
|
||||
ClearStatusMsg struct{}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user