summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/internal/components
diff options
context:
space:
mode:
authorDax <[email protected]>2025-07-07 15:53:43 -0400
committerGitHub <[email protected]>2025-07-07 15:53:43 -0400
commitf884766445bbf1fbce11f1db4bc6174e72d9baa5 (patch)
treee7d9e7b3d8efc8bed249f9d29e2d8fb838a275f2 /packages/tui/internal/components
parent76b2e4539cb97bae5812ed2d832ce49d02e70c64 (diff)
downloadopencode-f884766445bbf1fbce11f1db4bc6174e72d9baa5.tar.gz
opencode-f884766445bbf1fbce11f1db4bc6174e72d9baa5.zip
v2 message format and upgrade to ai sdk v5 (#743)
Co-authored-by: GitHub Action <[email protected]> Co-authored-by: Liang-Shih Lin <[email protected]> Co-authored-by: Dominik Engelhardt <[email protected]> Co-authored-by: Jay V <[email protected]> Co-authored-by: adamdottv <[email protected]>
Diffstat (limited to 'packages/tui/internal/components')
-rw-r--r--packages/tui/internal/components/chat/editor.go8
-rw-r--r--packages/tui/internal/components/chat/message.go324
-rw-r--r--packages/tui/internal/components/chat/messages.go66
-rw-r--r--packages/tui/internal/components/status/status.go25
4 files changed, 203 insertions, 220 deletions
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 9466d541c..5aa05bd1c 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -248,10 +248,10 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
fileParts := make([]opencode.FilePartParam, 0)
for _, attachment := range attachments {
fileParts = append(fileParts, opencode.FilePartParam{
- Type: opencode.F(opencode.FilePartTypeFile),
- MediaType: opencode.F(attachment.MediaType),
- URL: opencode.F(attachment.URL),
- Filename: opencode.F(attachment.Filename),
+ Type: opencode.F(opencode.FilePartTypeFile),
+ Mime: opencode.F(attachment.MediaType),
+ URL: opencode.F(attachment.URL),
+ Filename: opencode.F(attachment.Filename),
})
}
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index 9e245c8b2..3414a77d5 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -17,7 +17,6 @@ import (
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
- "github.com/tidwall/gjson"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
@@ -217,51 +216,50 @@ func renderContentBlock(
func renderText(
app *app.App,
- message opencode.Message,
+ message opencode.MessageUnion,
text string,
author string,
showToolDetails bool,
highlight bool,
width int,
extra string,
- toolCalls ...opencode.ToolInvocationPart,
+ toolCalls ...opencode.ToolPart,
) string {
t := theme.CurrentTheme()
- timestamp := time.UnixMilli(int64(message.Metadata.Time.Created)).
- Local().
- Format("02 Jan 2006 03:04 PM")
- if time.Now().Format("02 Jan 2006") == timestamp[:11] {
- // don't show the date if it's today
- timestamp = timestamp[12:]
- }
- info := fmt.Sprintf("%s (%s)", author, timestamp)
- info = styles.NewStyle().Foreground(t.TextMuted()).Render(info)
-
+ var ts time.Time
backgroundColor := t.BackgroundPanel()
if highlight {
backgroundColor = t.BackgroundElement()
}
messageStyle := styles.NewStyle().Background(backgroundColor)
- if message.Role == opencode.MessageRoleUser {
+ content := messageStyle.Render(text)
+
+ switch casted := message.(type) {
+ case opencode.AssistantMessage:
+ ts = time.UnixMilli(int64(casted.Time.Created))
+ content = util.ToMarkdown(text, width, backgroundColor)
+ case opencode.UserMessage:
+ ts = time.UnixMilli(int64(casted.Time.Created))
messageStyle = messageStyle.Width(width - 6)
}
- content := messageStyle.Render(text)
- if message.Role == opencode.MessageRoleAssistant {
- content = util.ToMarkdown(text, width, backgroundColor)
+ timestamp := ts.
+ Local().
+ Format("02 Jan 2006 03:04 PM")
+ if time.Now().Format("02 Jan 2006") == timestamp[:11] {
+ // don't show the date if it's today
+ timestamp = timestamp[12:]
}
+ info := fmt.Sprintf("%s (%s)", author, timestamp)
+ info = styles.NewStyle().Foreground(t.TextMuted()).Render(info)
if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 {
content = content + "\n\n"
for _, toolCall := range toolCalls {
- title := renderToolTitle(toolCall, message.Metadata, width)
- metadata := opencode.MessageMetadataTool{}
- if _, ok := message.Metadata.Tool[toolCall.ToolInvocation.ToolCallID]; ok {
- metadata = message.Metadata.Tool[toolCall.ToolInvocation.ToolCallID]
- }
+ title := renderToolTitle(toolCall, width)
style := styles.NewStyle()
- if _, ok := metadata.ExtraFields["error"]; ok {
+ if toolCall.State.Status == opencode.ToolPartStateStatusError {
style = style.Foreground(t.Error())
}
title = style.Render(title)
@@ -276,8 +274,8 @@ func renderText(
}
content = strings.Join(sections, "\n")
- switch message.Role {
- case opencode.MessageRoleUser:
+ switch message.(type) {
+ case opencode.UserMessage:
return renderContentBlock(
app,
content,
@@ -286,7 +284,7 @@ func renderText(
WithTextColor(t.Text()),
WithBorderColorRight(t.Secondary()),
)
- case opencode.MessageRoleAssistant:
+ case opencode.AssistantMessage:
return renderContentBlock(
app,
content,
@@ -300,39 +298,32 @@ func renderText(
func renderToolDetails(
app *app.App,
- toolCall opencode.ToolInvocationPart,
- messageMetadata opencode.MessageMetadata,
+ toolCall opencode.ToolPart,
highlight bool,
width int,
) string {
ignoredTools := []string{"todoread"}
- if slices.Contains(ignoredTools, toolCall.ToolInvocation.ToolName) {
+ if slices.Contains(ignoredTools, toolCall.Tool) {
return ""
}
- toolCallID := toolCall.ToolInvocation.ToolCallID
- metadata := opencode.MessageMetadataTool{}
- if _, ok := messageMetadata.Tool[toolCallID]; ok {
- metadata = messageMetadata.Tool[toolCallID]
+ if toolCall.State.Status == opencode.ToolPartStateStatusPending || toolCall.State.Status == opencode.ToolPartStateStatusRunning {
+ title := renderToolTitle(toolCall, width)
+ return renderContentBlock(app, title, highlight, width)
}
var result *string
- if toolCall.ToolInvocation.Result != "" {
- result = &toolCall.ToolInvocation.Result
- }
-
- if toolCall.ToolInvocation.State == "partial-call" {
- title := renderToolTitle(toolCall, messageMetadata, width)
- return renderContentBlock(app, title, highlight, width)
+ if toolCall.State.Output != "" {
+ result = &toolCall.State.Output
}
- toolArgsMap := make(map[string]any)
- if toolCall.ToolInvocation.Args != nil {
- value := toolCall.ToolInvocation.Args
+ toolInputMap := make(map[string]any)
+ if toolCall.State.Input != nil {
+ value := toolCall.State.Input
if m, ok := value.(map[string]any); ok {
- toolArgsMap = m
- keys := make([]string, 0, len(toolArgsMap))
- for key := range toolArgsMap {
+ toolInputMap = m
+ keys := make([]string, 0, len(toolInputMap))
+ for key := range toolInputMap {
keys = append(keys, key)
}
slices.Sort(keys)
@@ -340,7 +331,6 @@ func renderToolDetails(
}
body := ""
- finished := result != nil && *result != ""
t := theme.CurrentTheme()
backgroundColor := t.BackgroundPanel()
borderColor := t.BackgroundPanel()
@@ -349,137 +339,128 @@ func renderToolDetails(
borderColor = t.BorderActive()
}
- switch toolCall.ToolInvocation.ToolName {
- case "read":
- preview := metadata.ExtraFields["preview"]
- if preview != nil && toolArgsMap["filePath"] != nil {
- filename := toolArgsMap["filePath"].(string)
- body = preview.(string)
- body = util.RenderFile(filename, body, width, util.WithTruncate(6))
- }
- case "edit":
- if filename, ok := toolArgsMap["filePath"].(string); ok {
- diffField := metadata.ExtraFields["diff"]
- if diffField != nil {
- patch := diffField.(string)
- var formattedDiff string
- formattedDiff, _ = diff.FormatUnifiedDiff(
- filename,
- patch,
- diff.WithWidth(width-2),
- )
- body = strings.TrimSpace(formattedDiff)
- style := styles.NewStyle().
- Background(backgroundColor).
- Foreground(t.TextMuted()).
- Padding(1, 2).
- Width(width - 4)
- if highlight {
- style = style.Foreground(t.Text()).Bold(true)
- }
+ if toolCall.State.Status == opencode.ToolPartStateStatusCompleted {
+ metadata := toolCall.State.Metadata.(map[string]any)
+ switch toolCall.Tool {
+ case "read":
+ preview := metadata["preview"]
+ if preview != nil && toolInputMap["filePath"] != nil {
+ filename := toolInputMap["filePath"].(string)
+ body = preview.(string)
+ body = util.RenderFile(filename, body, width, util.WithTruncate(6))
+ }
+ case "edit":
+ if filename, ok := toolInputMap["filePath"].(string); ok {
+ diffField := metadata["diff"]
+ if diffField != nil {
+ patch := diffField.(string)
+ var formattedDiff string
+ formattedDiff, _ = diff.FormatUnifiedDiff(
+ filename,
+ patch,
+ diff.WithWidth(width-2),
+ )
+ body = strings.TrimSpace(formattedDiff)
+ style := styles.NewStyle().
+ Background(backgroundColor).
+ Foreground(t.TextMuted()).
+ Padding(1, 2).
+ Width(width - 4)
+ if highlight {
+ style = style.Foreground(t.Text()).Bold(true)
+ }
- if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
- diagnostics = style.Render(diagnostics)
- body += "\n" + diagnostics
- }
+ if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
+ diagnostics = style.Render(diagnostics)
+ body += "\n" + diagnostics
+ }
- title := renderToolTitle(toolCall, messageMetadata, width)
- title = style.Render(title)
- content := title + "\n" + body
- content = renderContentBlock(
- app,
- content,
- highlight,
- width,
- WithPadding(0),
- WithBorderColor(borderColor),
- )
- return content
+ title := renderToolTitle(toolCall, width)
+ title = style.Render(title)
+ content := title + "\n" + body
+ content = renderContentBlock(
+ app,
+ content,
+ highlight,
+ width,
+ WithPadding(0),
+ WithBorderColor(borderColor),
+ )
+ return content
+ }
}
- }
- case "write":
- if filename, ok := toolArgsMap["filePath"].(string); ok {
- if content, ok := toolArgsMap["content"].(string); ok {
- body = util.RenderFile(filename, content, width)
- if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
- body += "\n\n" + diagnostics
+ case "write":
+ if filename, ok := toolInputMap["filePath"].(string); ok {
+ if content, ok := toolInputMap["content"].(string); ok {
+ body = util.RenderFile(filename, content, width)
+ if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
+ body += "\n\n" + diagnostics
+ }
}
}
- }
- case "bash":
- stdout := metadata.ExtraFields["stdout"]
- if stdout != nil {
- command := toolArgsMap["command"].(string)
- body = fmt.Sprintf("```console\n> %s\n%s```", command, stdout)
- body = util.ToMarkdown(body, width, backgroundColor)
- }
- case "webfetch":
- if format, ok := toolArgsMap["format"].(string); ok && result != nil {
- body = *result
- body = util.TruncateHeight(body, 10)
- if format == "html" || format == "markdown" {
+ case "bash":
+ stdout := metadata["stdout"]
+ if stdout != nil {
+ command := toolInputMap["command"].(string)
+ body = fmt.Sprintf("```console\n> %s\n%s```", command, stdout)
body = util.ToMarkdown(body, width, backgroundColor)
}
- }
- case "todowrite":
- 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":
- // body += fmt.Sprintf("- [ ] %s\n", content)
- default:
- body += fmt.Sprintf("- [ ] %s\n", content)
+ case "webfetch":
+ if format, ok := toolInputMap["format"].(string); ok && result != nil {
+ body = *result
+ body = util.TruncateHeight(body, 10)
+ if format == "html" || format == "markdown" {
+ body = util.ToMarkdown(body, width, backgroundColor)
}
}
- body = util.ToMarkdown(body, width, backgroundColor)
- }
- case "task":
- summary := metadata.JSON.ExtraFields["summary"]
- if !summary.IsNull() {
- strValue := summary.Raw()
- toolcalls := gjson.Parse(strValue).Array()
-
- steps := []string{}
- for _, toolcall := range toolcalls {
- call := toolcall.Value().(map[string]any)
- if toolInvocation, ok := call["toolInvocation"].(map[string]any); ok {
- data, _ := json.Marshal(toolInvocation)
- var toolCall opencode.ToolInvocationPart
- _ = json.Unmarshal(data, &toolCall)
-
- if metadata, ok := call["metadata"].(map[string]any); ok {
- data, _ = json.Marshal(metadata)
- var toolMetadata opencode.MessageMetadataTool
- _ = json.Unmarshal(data, &toolMetadata)
-
- step := renderToolTitle(toolCall, messageMetadata, width)
+ case "todowrite":
+ todos := metadata["todos"]
+ if todos != nil {
+ for _, item := range todos.([]any) {
+ todo := item.(map[string]any)
+ content := todo["content"].(string)
+ switch todo["status"] {
+ case "completed":
+ body += fmt.Sprintf("- [x] %s\n", content)
+ // case "in-progress":
+ // body += fmt.Sprintf("- [ ] %s\n", content)
+ default:
+ body += fmt.Sprintf("- [ ] %s\n", content)
+ }
+ }
+ body = util.ToMarkdown(body, width, backgroundColor)
+ }
+ case "task":
+ summary := metadata["summary"]
+ if summary != nil {
+ toolcalls := summary.([]any)
+ steps := []string{}
+ for _, toolcall := range toolcalls {
+ call := toolcall.(map[string]any)
+ if toolInvocation, ok := call["toolInvocation"].(map[string]any); ok {
+ data, _ := json.Marshal(toolInvocation)
+ var toolCall opencode.ToolPart
+ _ = json.Unmarshal(data, &toolCall)
+ step := renderToolTitle(toolCall, width)
step = "∟ " + step
steps = append(steps, step)
}
}
+ body = strings.Join(steps, "\n")
}
- body = strings.Join(steps, "\n")
- }
- default:
- if result == nil {
- empty := ""
- result = &empty
+ default:
+ if result == nil {
+ empty := ""
+ result = &empty
+ }
+ body = *result
+ body = util.TruncateHeight(body, 10)
}
- body = *result
- body = util.TruncateHeight(body, 10)
}
error := ""
- if err, ok := metadata.ExtraFields["error"].(bool); ok && err {
- if message, ok := metadata.ExtraFields["message"].(string); ok {
- error = message
- }
+ if toolCall.State.Status == opencode.ToolPartStateStatusError {
+ error = toolCall.State.Error
}
if error != "" {
@@ -494,7 +475,7 @@ func renderToolDetails(
body = util.TruncateHeight(body, 10)
}
- title := renderToolTitle(toolCall, messageMetadata, width)
+ title := renderToolTitle(toolCall, width)
content := title + "\n\n" + body
return renderContentBlock(app, content, highlight, width, WithBorderColor(borderColor))
}
@@ -515,20 +496,19 @@ func renderToolName(name string) string {
}
func renderToolTitle(
- toolCall opencode.ToolInvocationPart,
- messageMetadata opencode.MessageMetadata,
+ toolCall opencode.ToolPart,
width int,
) string {
// TODO: handle truncate to width
- if toolCall.ToolInvocation.State == "partial-call" {
- return renderToolAction(toolCall.ToolInvocation.ToolName)
+ if toolCall.State.Status == opencode.ToolPartStateStatusPending {
+ return renderToolAction(toolCall.Tool)
}
toolArgs := ""
toolArgsMap := make(map[string]any)
- if toolCall.ToolInvocation.Args != nil {
- value := toolCall.ToolInvocation.Args
+ if toolCall.State.Input != nil {
+ value := toolCall.State.Input
if m, ok := value.(map[string]any); ok {
toolArgsMap = m
@@ -546,8 +526,8 @@ func renderToolTitle(
}
}
- title := renderToolName(toolCall.ToolInvocation.ToolName)
- switch toolCall.ToolInvocation.ToolName {
+ title := renderToolName(toolCall.Tool)
+ switch toolCall.Tool {
case "read":
toolArgs = renderArgs(&toolArgsMap, "filePath")
title = fmt.Sprintf("%s %s", title, toolArgs)
@@ -565,7 +545,7 @@ func renderToolTitle(
case "todowrite", "todoread":
// title is just the tool name
default:
- toolName := renderToolName(toolCall.ToolInvocation.ToolName)
+ toolName := renderToolName(toolCall.Tool)
title = fmt.Sprintf("%s %s", toolName, toolArgs)
}
return title
@@ -645,8 +625,8 @@ type Diagnostic struct {
}
// renderDiagnostics formats LSP diagnostics for display in the TUI
-func renderDiagnostics(metadata opencode.MessageMetadataTool, filePath string) string {
- if diagnosticsData, ok := metadata.ExtraFields["diagnostics"].(map[string]any); ok {
+func renderDiagnostics(metadata map[string]any, filePath string) string {
+ if diagnosticsData, ok := metadata["diagnostics"].(map[string]any); ok {
if fileDiagnostics, ok := diagnosticsData[filePath].([]any); ok {
var errorDiagnostics []string
for _, diagInterface := range fileDiagnostics {
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index a59b5d79e..26bee8714 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -99,7 +99,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
case opencode.EventListResponseEventMessageUpdated:
- if msg.Properties.Info.Metadata.SessionID == m.app.Session.ID {
+ if msg.Properties.Info.SessionID == m.app.Session.ID {
m.renderView(m.width)
if m.tail {
m.viewport.GotoBottom()
@@ -124,19 +124,19 @@ func (m *messagesComponent) renderView(width int) {
m.partCount = 0
m.lineCount = 0
- orphanedToolCalls := make([]opencode.ToolInvocationPart, 0)
+ orphanedToolCalls := make([]opencode.ToolPart, 0)
for _, message := range m.app.Messages {
var content string
var cached bool
- switch message.Role {
- case opencode.MessageRoleUser:
+ switch casted := message.(type) {
+ case opencode.UserMessage:
userLoop:
- for partIndex, part := range message.Parts {
+ for partIndex, part := range casted.Parts {
switch part := part.AsUnion().(type) {
case opencode.TextPart:
- remainingParts := message.Parts[partIndex+1:]
+ remainingParts := casted.Parts[partIndex+1:]
fileParts := make([]opencode.FilePart, 0)
for _, part := range remainingParts {
switch part := part.AsUnion().(type) {
@@ -150,7 +150,7 @@ func (m *messagesComponent) renderView(width int) {
mediaTypeStyle := styles.NewStyle().Background(t.Secondary()).Foreground(t.BackgroundPanel()).Padding(0, 1)
for _, filePart := range fileParts {
mediaType := ""
- switch filePart.MediaType {
+ switch filePart.Mime {
case "text/plain":
mediaType = "txt"
case "image/png", "image/jpeg", "image/gif", "image/webp":
@@ -175,7 +175,7 @@ func (m *messagesComponent) renderView(width int) {
flexItems...,
)
- key := m.cache.GenerateKey(message.ID, part.Text, width, m.selectedPart == m.partCount, files)
+ key := m.cache.GenerateKey(casted.ID, part.Text, width, m.selectedPart == m.partCount, files)
content, cached = m.cache.Get(key)
if !cached {
content = renderText(
@@ -199,21 +199,21 @@ func (m *messagesComponent) renderView(width int) {
}
}
- case opencode.MessageRoleAssistant:
+ case opencode.AssistantMessage:
hasTextPart := false
- for partIndex, p := range message.Parts {
+ for partIndex, p := range casted.Parts {
switch part := p.AsUnion().(type) {
case opencode.TextPart:
hasTextPart = true
- finished := message.Metadata.Time.Completed > 0
- remainingParts := message.Parts[partIndex+1:]
- toolCallParts := make([]opencode.ToolInvocationPart, 0)
+ finished := casted.Time.Completed > 0
+ remainingParts := casted.Parts[partIndex+1:]
+ toolCallParts := make([]opencode.ToolPart, 0)
// sometimes tool calls happen without an assistant message
// these should be included in this assistant message as well
if len(orphanedToolCalls) > 0 {
toolCallParts = append(toolCallParts, orphanedToolCalls...)
- orphanedToolCalls = make([]opencode.ToolInvocationPart, 0)
+ orphanedToolCalls = make([]opencode.ToolPart, 0)
}
remaining := true
@@ -226,9 +226,9 @@ func (m *messagesComponent) renderView(width int) {
// we only want tool calls associated with the current text part.
// if we hit another text part, we're done.
remaining = false
- case opencode.ToolInvocationPart:
+ case opencode.ToolPart:
toolCallParts = append(toolCallParts, part)
- if part.ToolInvocation.State != "result" {
+ if part.State.Status != opencode.ToolPartStateStatusCompleted || part.State.Status != opencode.ToolPartStateStatusError {
// i don't think there's a case where a tool call isn't in result state
// and the message time is 0, but just in case
finished = false
@@ -237,14 +237,14 @@ func (m *messagesComponent) renderView(width int) {
}
if finished {
- key := m.cache.GenerateKey(message.ID, p.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
+ key := m.cache.GenerateKey(casted.ID, p.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
content, cached = m.cache.Get(key)
if !cached {
content = renderText(
m.app,
message,
p.Text,
- message.Metadata.Assistant.ModelID,
+ casted.ModelID,
m.showToolDetails,
m.partCount == m.selectedPart,
width,
@@ -258,7 +258,7 @@ func (m *messagesComponent) renderView(width int) {
m.app,
message,
p.Text,
- message.Metadata.Assistant.ModelID,
+ casted.ModelID,
m.showToolDetails,
m.partCount == m.selectedPart,
width,
@@ -270,7 +270,7 @@ func (m *messagesComponent) renderView(width int) {
m = m.updateSelected(content, p.Text)
blocks = append(blocks, content)
}
- case opencode.ToolInvocationPart:
+ case opencode.ToolPart:
if !m.showToolDetails {
if !hasTextPart {
orphanedToolCalls = append(orphanedToolCalls, part)
@@ -278,9 +278,9 @@ func (m *messagesComponent) renderView(width int) {
continue
}
- if part.ToolInvocation.State == "result" {
- key := m.cache.GenerateKey(message.ID,
- part.ToolInvocation.ToolCallID,
+ if part.State.Status == opencode.ToolPartStateStatusCompleted || part.State.Status == opencode.ToolPartStateStatusError {
+ key := m.cache.GenerateKey(casted.ID,
+ part.ID,
m.showToolDetails,
width,
m.partCount == m.selectedPart,
@@ -290,7 +290,6 @@ func (m *messagesComponent) renderView(width int) {
content = renderToolDetails(
m.app,
part,
- message.Metadata,
m.partCount == m.selectedPart,
width,
)
@@ -301,7 +300,6 @@ func (m *messagesComponent) renderView(width int) {
content = renderToolDetails(
m.app,
part,
- message.Metadata,
m.partCount == m.selectedPart,
width,
)
@@ -315,14 +313,16 @@ func (m *messagesComponent) renderView(width int) {
}
error := ""
- switch err := message.Metadata.Error.AsUnion().(type) {
- case nil:
- case opencode.MessageMetadataErrorMessageOutputLengthError:
- error = "Message output length exceeded"
- case opencode.ProviderAuthError:
- error = err.Data.Message
- case opencode.UnknownError:
- error = err.Data.Message
+ if assistant, ok := message.(opencode.AssistantMessage); ok {
+ switch err := assistant.Error.AsUnion().(type) {
+ case nil:
+ case opencode.AssistantMessageErrorMessageOutputLengthError:
+ error = "Message output length exceeded"
+ case opencode.ProviderAuthError:
+ error = err.Data.Message
+ case opencode.UnknownError:
+ error = err.Data.Message
+ }
}
if error != "" {
diff --git a/packages/tui/internal/components/status/status.go b/packages/tui/internal/components/status/status.go
index 791267e0a..7d5820de3 100644
--- a/packages/tui/internal/components/status/status.go
+++ b/packages/tui/internal/components/status/status.go
@@ -6,6 +6,7 @@ import (
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/styles"
"github.com/sst/opencode/internal/theme"
@@ -101,18 +102,20 @@ func (m statusComponent) View() string {
contextWindow := m.app.Model.Limit.Context
for _, message := range m.app.Messages {
- cost += message.Metadata.Assistant.Cost
- usage := message.Metadata.Assistant.Tokens
- if usage.Output > 0 {
- if message.Metadata.Assistant.Summary {
- tokens = usage.Output
- continue
+ if assistant, ok := message.(opencode.AssistantMessage); ok {
+ cost += assistant.Cost
+ usage := assistant.Tokens
+ if usage.Output > 0 {
+ if assistant.Summary {
+ tokens = usage.Output
+ continue
+ }
+ tokens = (usage.Input +
+ usage.Cache.Write +
+ usage.Cache.Read +
+ usage.Output +
+ usage.Reasoning)
}
- tokens = (usage.Input +
- usage.Cache.Write +
- usage.Cache.Read +
- usage.Output +
- usage.Reasoning)
}
}