diff options
| author | adamdottv <[email protected]> | 2025-06-27 07:46:42 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-06-27 14:26:25 -0500 |
| commit | 79bbf90b727f35915e665b99ba13f260b0dc94fe (patch) | |
| tree | 4ee5e4d79635429a45b9035955323e3e03078e78 /packages/tui/internal/components/chat | |
| parent | 226a4a7f3610860d437ddf8d7d8216e909297418 (diff) | |
| download | opencode-79bbf90b727f35915e665b99ba13f260b0dc94fe.tar.gz opencode-79bbf90b727f35915e665b99ba13f260b0dc94fe.zip | |
chore: rework openapi spec and use stainless sdk
Diffstat (limited to 'packages/tui/internal/components/chat')
| -rw-r--r-- | packages/tui/internal/components/chat/message.go | 129 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/messages.go | 102 |
2 files changed, 125 insertions, 106 deletions
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go index 6fa52c1f4..cf4662d53 100644 --- a/packages/tui/internal/components/chat/message.go +++ b/packages/tui/internal/components/chat/message.go @@ -12,12 +12,13 @@ import ( "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/lipgloss/v2/compat" "github.com/charmbracelet/x/ansi" + "github.com/sst/opencode-sdk-go" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/diff" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" - "github.com/sst/opencode/pkg/client" + "github.com/tidwall/gjson" "golang.org/x/text/cases" "golang.org/x/text/language" ) @@ -209,7 +210,7 @@ func calculatePadding() int { } } -func renderText(message client.MessageInfo, text string, author string) string { +func renderText(message opencode.Message, text string, author string) string { t := theme.CurrentTheme() width := layout.Current.Container.Width padding := calculatePadding() @@ -223,7 +224,7 @@ func renderText(message client.MessageInfo, text string, author string) string { textWidth := max(lipgloss.Width(text), lipgloss.Width(info)) markdownWidth := min(textWidth, width-padding-4) // -4 for the border and padding - if message.Role == client.Assistant { + if message.Role == opencode.MessageRoleAssistant { markdownWidth = width - padding - 4 - 3 } minWidth := max(markdownWidth, (width-4)/2) @@ -235,18 +236,18 @@ func renderText(message client.MessageInfo, text string, author string) string { messageStyle = messageStyle.AlignHorizontal(lipgloss.Right) } content := messageStyle.Render(text) - if message.Role == client.Assistant { + if message.Role == opencode.MessageRoleAssistant { content = toMarkdown(text, markdownWidth, t.BackgroundPanel()) } content = strings.Join([]string{content, info}, "\n") switch message.Role { - case client.User: + case opencode.MessageRoleUser: return renderContentBlock(content, WithAlign(lipgloss.Right), WithBorderColor(t.Secondary()), ) - case client.Assistant: + case opencode.MessageRoleAssistant: return renderContentBlock(content, WithAlign(lipgloss.Left), WithBorderColor(t.Accent()), @@ -256,15 +257,16 @@ func renderText(message client.MessageInfo, text string, author string) string { } func renderToolInvocation( - toolCall client.MessageToolInvocationToolCall, + toolCall opencode.ToolInvocationPart, result *string, - metadata client.MessageMetadata_Tool_AdditionalProperties, + metadata opencode.MessageMetadataTool, showDetails bool, isLast bool, contentOnly bool, + messageMetadata opencode.MessageMetadata, ) string { ignoredTools := []string{"todoread"} - if slices.Contains(ignoredTools, toolCall.ToolName) { + if slices.Contains(ignoredTools, toolCall.ToolInvocation.ToolName) { return "" } @@ -294,8 +296,8 @@ func renderToolInvocation( BorderForeground(t.BackgroundPanel()). BorderStyle(lipgloss.ThickBorder()) - if toolCall.State == "partial-call" { - title := renderToolAction(toolCall.ToolName) + if toolCall.ToolInvocation.State == "partial-call" { + title := renderToolAction(toolCall.ToolInvocation.ToolName) if !showDetails { title = "∟ " + title padding := calculatePadding() @@ -316,8 +318,8 @@ func renderToolInvocation( toolArgs := "" toolArgsMap := make(map[string]any) - if toolCall.Args != nil { - value := *toolCall.Args + if toolCall.ToolInvocation.Args != nil { + value := toolCall.ToolInvocation.Args if m, ok := value.(map[string]any); ok { toolArgsMap = m @@ -339,28 +341,35 @@ func renderToolInvocation( error := "" finished := result != nil && *result != "" - if e, ok := metadata.Get("error"); ok && e.(bool) == true { - if m, ok := metadata.Get("message"); ok { - style = style.BorderLeftForeground(t.Error()) - error = styles.NewStyle(). - Foreground(t.Error()). - Background(t.BackgroundPanel()). - Render(m.(string)) - error = renderContentBlock( - error, - WithFullWidth(), - WithBorderColor(t.Error()), - WithMarginBottom(1), - ) - } + er := messageMetadata.Error.AsUnion() + switch er.(type) { + case nil: + default: + clientError := er.(opencode.UnknownError) + error = clientError.Data.Message + } + + if error != "" { + style = style.BorderLeftForeground(t.Error()) + error = styles.NewStyle(). + Foreground(t.Error()). + Background(t.BackgroundPanel()). + Render(error) + error = renderContentBlock( + error, + WithFullWidth(), + WithBorderColor(t.Error()), + WithMarginBottom(1), + ) } title := "" - switch toolCall.ToolName { + switch toolCall.ToolInvocation.ToolName { case "read": toolArgs = renderArgs(&toolArgsMap, "filePath") title = fmt.Sprintf("READ %s", toolArgs) - if preview, ok := metadata.Get("preview"); ok && toolArgsMap["filePath"] != nil { + preview := metadata.ExtraFields["preview"] + if preview != nil && toolArgsMap["filePath"] != nil { filename := toolArgsMap["filePath"].(string) body = preview.(string) body = renderFile(filename, body, WithTruncate(6)) @@ -368,8 +377,9 @@ func renderToolInvocation( case "edit": if filename, ok := toolArgsMap["filePath"].(string); ok { title = fmt.Sprintf("EDIT %s", relative(filename)) - if d, ok := metadata.Get("diff"); ok { - patch := d.(string) + diffField := metadata.ExtraFields["diff"] + if diffField != nil { + patch := diffField.(string) var formattedDiff string if layout.Current.Viewport.Width < 80 { formattedDiff, _ = diff.FormatUnifiedDiff( @@ -406,7 +416,7 @@ func renderToolInvocation( ) // Add diagnostics at the bottom if they exist - if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" { + if diagnostics := renderDiagnostics(messageMetadata, filename); diagnostics != "" { body += "\n" + renderContentBlock(diagnostics, WithFullWidth(), WithBorderColor(t.Error())) } } @@ -418,7 +428,7 @@ func renderToolInvocation( body = renderFile(filename, content) // Add diagnostics at the bottom if they exist - if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" { + if diagnostics := renderDiagnostics(messageMetadata, filename); diagnostics != "" { body += "\n" + renderContentBlock(diagnostics, WithFullWidth(), WithBorderColor(t.Error())) } } @@ -427,9 +437,10 @@ func renderToolInvocation( if description, ok := toolArgsMap["description"].(string); ok { title = fmt.Sprintf("SHELL %s", description) } - if stdout, ok := metadata.Get("stdout"); ok { + stdout := metadata.JSON.ExtraFields["stdout"] + if !stdout.IsNull() { command := toolArgsMap["command"].(string) - stdout := stdout.(string) + stdout := stdout.Raw() body = fmt.Sprintf("```console\n> %s\n%s```", command, stdout) body = toMarkdown(body, innerWidth, t.BackgroundPanel()) body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1)) @@ -450,12 +461,13 @@ func renderToolInvocation( case "todowrite": title = fmt.Sprintf("PLAN") - if to, ok := metadata.Get("todos"); ok && finished { - todos := to.([]any) - for _, todo := range todos { - t := todo.(map[string]any) - content := t["content"].(string) - switch t["status"].(string) { + todos := metadata.JSON.ExtraFields["todos"] + if !todos.IsNull() && finished { + strTodos := todos.Raw() + todos := gjson.Parse(strTodos) + for _, todo := range todos.Array() { + content := todo.Get("content").String() + switch todo.Get("status").String() { case "completed": body += fmt.Sprintf("- [x] %s\n", content) // case "in-progress": @@ -470,21 +482,22 @@ func renderToolInvocation( case "task": if description, ok := toolArgsMap["description"].(string); ok { title = fmt.Sprintf("TASK %s", description) - if summary, ok := metadata.Get("summary"); ok { - toolcalls := summary.([]any) - // toolcalls := + summary := metadata.JSON.ExtraFields["summary"] + if !summary.IsNull() { + strValue := summary.Raw() + toolcalls := gjson.Parse(strValue).Array() steps := []string{} for _, toolcall := range toolcalls { - call := toolcall.(map[string]any) + call := toolcall.Value().(map[string]any) if toolInvocation, ok := call["toolInvocation"].(map[string]any); ok { data, _ := json.Marshal(toolInvocation) - var toolCall client.MessageToolInvocationToolCall + var toolCall opencode.ToolInvocationPart _ = json.Unmarshal(data, &toolCall) if metadata, ok := call["metadata"].(map[string]any); ok { data, _ = json.Marshal(metadata) - var toolMetadata client.MessageMetadata_Tool_AdditionalProperties + var toolMetadata opencode.MessageMetadataTool _ = json.Unmarshal(data, &toolMetadata) step := renderToolInvocation( @@ -494,6 +507,7 @@ func renderToolInvocation( false, false, true, + messageMetadata, ) steps = append(steps, step) } @@ -505,7 +519,7 @@ func renderToolInvocation( } default: - toolName := renderToolName(toolCall.ToolName) + toolName := renderToolName(toolCall.ToolInvocation.ToolName) title = fmt.Sprintf("%s %s", toolName, toolArgs) if result == nil { empty := "" @@ -537,7 +551,7 @@ func renderToolInvocation( ) } - if body == "" && error == "" { + if body == "" && error == "" && result != nil { body = *result body = truncateHeight(body, 10) body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1)) @@ -716,31 +730,28 @@ type Diagnostic struct { } // renderDiagnostics formats LSP diagnostics for display in the TUI -func renderDiagnostics(metadata client.MessageMetadata_Tool_AdditionalProperties, filePath string) string { - diagnosticsData, ok := metadata.Get("diagnostics") - if !ok { +func renderDiagnostics(metadata opencode.MessageMetadata, filePath string) string { + diagnosticsData := metadata.JSON.ExtraFields["diagnostics"] + if diagnosticsData.IsNull() { return "" } // diagnosticsData should be a map[string][]Diagnostic - diagnosticsMap, ok := diagnosticsData.(map[string]interface{}) - if !ok { - return "" - } - + strDiagnosticsData := diagnosticsData.Raw() + diagnosticsMap := gjson.Parse(strDiagnosticsData).Value().(map[string]any) fileDiagnostics, ok := diagnosticsMap[filePath] if !ok { return "" } - diagnosticsList, ok := fileDiagnostics.([]interface{}) + diagnosticsList, ok := fileDiagnostics.([]any) if !ok { return "" } var errorDiagnostics []string for _, diagInterface := range diagnosticsList { - diagMap, ok := diagInterface.(map[string]interface{}) + diagMap, ok := diagInterface.(map[string]any) if !ok { continue } diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go index da45545c9..e1c7a9d14 100644 --- a/packages/tui/internal/components/chat/messages.go +++ b/packages/tui/internal/components/chat/messages.go @@ -9,13 +9,13 @@ import ( "github.com/charmbracelet/bubbles/v2/viewport" tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/lipgloss/v2" + "github.com/sst/opencode-sdk-go" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/commands" "github.com/sst/opencode/internal/components/dialog" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" - "github.com/sst/opencode/pkg/client" ) type MessagesComponent interface { @@ -83,7 +83,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.tail { m.viewport.GotoBottom() } - case client.EventSessionUpdated, client.EventMessageUpdated: + case opencode.EventListResponseEventSessionUpdated, opencode.EventListResponseEventMessageUpdated: m.renderView() if m.tail { m.viewport.GotoBottom() @@ -130,90 +130,97 @@ func (m *messagesComponent) renderView() { lastToolIndex := 0 lastToolIndices := []int{} for i, p := range message.Parts { - part, _ := p.ValueByDiscriminator() - switch part.(type) { - case client.MessagePartText: + switch p.Type { + case opencode.MessagePartTypeText: lastToolIndices = append(lastToolIndices, lastToolIndex) - case client.MessagePartToolInvocation: + case opencode.MessagePartTypeToolInvocation: lastToolIndex = i } } author := "" switch message.Role { - case client.User: + case opencode.MessageRoleUser: author = m.app.Info.User - case client.Assistant: + case opencode.MessageRoleAssistant: author = message.Metadata.Assistant.ModelID } for i, p := range message.Parts { - part, err := p.ValueByDiscriminator() - if err != nil { - continue //TODO: handle error? - } - - switch part.(type) { + switch part := p.AsUnion().(type) { // case client.MessagePartStepStart: // messages = append(messages, "") - case client.MessagePartText: - text := part.(client.MessagePartText) - key := m.cache.GenerateKey(message.Id, text.Text, layout.Current.Viewport.Width) + case opencode.TextPart: + key := m.cache.GenerateKey(message.ID, p.Text, layout.Current.Viewport.Width) content, cached = m.cache.Get(key) if !cached { - content = renderText(message, text.Text, author) + content = renderText(message, p.Text, author) m.cache.Set(key, content) } if previousBlockType != none { blocks = append(blocks, "") } blocks = append(blocks, content) - if message.Role == client.User { + if message.Role == opencode.MessageRoleUser { previousBlockType = userTextBlock - } else if message.Role == client.Assistant { + } else if message.Role == opencode.MessageRoleAssistant { previousBlockType = assistantTextBlock } - case client.MessagePartToolInvocation: + case opencode.ToolInvocationPart: isLastToolInvocation := slices.Contains(lastToolIndices, i) - toolInvocationPart := part.(client.MessagePartToolInvocation) - toolCall, _ := toolInvocationPart.ToolInvocation.AsMessageToolInvocationToolCall() - metadata := client.MessageMetadata_Tool_AdditionalProperties{} - if _, ok := message.Metadata.Tool[toolCall.ToolCallId]; ok { - metadata = message.Metadata.Tool[toolCall.ToolCallId] + metadata := opencode.MessageMetadataTool{} + + toolCallID := part.ToolInvocation.ToolCallID + // var toolCallID string + // var result *string + // switch toolCall := part.ToolInvocation.AsUnion().(type) { + // case opencode.ToolCall: + // toolCallID = toolCall.ToolCallID + // case opencode.ToolPartialCall: + // toolCallID = toolCall.ToolCallID + // case opencode.ToolResult: + // toolCallID = toolCall.ToolCallID + // result = &toolCall.Result + // } + + if _, ok := message.Metadata.Tool[toolCallID]; ok { + metadata = message.Metadata.Tool[toolCallID] } + var result *string - resultPart, resultError := toolInvocationPart.ToolInvocation.AsMessageToolInvocationToolResult() - if resultError == nil { - result = &resultPart.Result + if part.ToolInvocation.Result != "" { + result = &part.ToolInvocation.Result } - if toolCall.State == "result" { - key := m.cache.GenerateKey(message.Id, - toolCall.ToolCallId, + if part.ToolInvocation.State == "result" { + key := m.cache.GenerateKey(message.ID, + part.ToolInvocation.ToolCallID, m.showToolDetails, layout.Current.Viewport.Width, ) content, cached = m.cache.Get(key) if !cached { content = renderToolInvocation( - toolCall, + part, result, metadata, m.showToolDetails, isLastToolInvocation, false, + message.Metadata, ) m.cache.Set(key, content) } } else { // if the tool call isn't finished, don't cache content = renderToolInvocation( - toolCall, + part, result, metadata, m.showToolDetails, isLastToolInvocation, false, + message.Metadata, ) } @@ -226,16 +233,17 @@ func (m *messagesComponent) renderView() { } error := "" - if message.Metadata.Error != nil { - errorValue, _ := message.Metadata.Error.ValueByDiscriminator() - switch errorValue.(type) { - case client.UnknownError: - clientError := errorValue.(client.UnknownError) - error = clientError.Data.Message - error = renderContentBlock(error, WithBorderColor(t.Error()), WithFullWidth(), WithMarginTop(1), WithMarginBottom(1)) - blocks = append(blocks, error) - previousBlockType = errorBlock - } + switch err := message.Metadata.Error.AsUnion().(type) { + case nil: + default: + clientError := err.(opencode.UnknownError) + error = clientError.Data.Message + } + + if error != "" { + error = renderContentBlock(error, WithBorderColor(t.Error()), WithFullWidth(), WithMarginTop(1), WithMarginBottom(1)) + blocks = append(blocks, error) + previousBlockType = errorBlock } } @@ -254,7 +262,7 @@ func (m *messagesComponent) renderView() { } func (m *messagesComponent) header() string { - if m.app.Session.Id == "" { + if m.app.Session.ID == "" { return "" } @@ -264,8 +272,8 @@ func (m *messagesComponent) header() string { muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render headerLines := []string{} headerLines = append(headerLines, toMarkdown("# "+m.app.Session.Title, width-6, t.Background())) - if m.app.Session.Share != nil && m.app.Session.Share.Url != "" { - headerLines = append(headerLines, muted(m.app.Session.Share.Url)) + if m.app.Session.Share.URL != "" { + headerLines = append(headerLines, muted(m.app.Session.Share.URL)) } else { headerLines = append(headerLines, base("/share")+muted(" to create a shareable link")) } |
