summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-06-19 15:02:13 -0500
committeradamdottv <[email protected]>2025-06-19 15:02:13 -0500
commit4e4cff49c01947018876fdc6cd586281652a5d82 (patch)
tree0705103710fbc9e4fd90ce32422897303b466651
parent5540503beefc8de48179e7727066f81148080c4f (diff)
downloadopencode-4e4cff49c01947018876fdc6cd586281652a5d82.tar.gz
opencode-4e4cff49c01947018876fdc6cd586281652a5d82.zip
feat(tui): better task tool rendering
-rw-r--r--packages/tui/internal/components/chat/message.go55
-rw-r--r--packages/tui/internal/components/chat/messages.go2
-rw-r--r--packages/tui/internal/tui/tui.go5
-rw-r--r--packages/tui/pkg/client/gen/openapi.json167
-rw-r--r--packages/tui/pkg/client/generated-client.go190
5 files changed, 288 insertions, 131 deletions
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index d862e5285..6db5509d1 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -1,6 +1,7 @@
package chat
import (
+ "encoding/json"
"fmt"
"path/filepath"
"slices"
@@ -252,6 +253,7 @@ func renderToolInvocation(
metadata client.MessageInfo_Metadata_Tool_AdditionalProperties,
showDetails bool,
isLast bool,
+ contentOnly bool,
) string {
ignoredTools := []string{"todoread"}
if slices.Contains(ignoredTools, toolCall.ToolName) {
@@ -313,7 +315,6 @@ func renderToolInvocation(
keys = append(keys, key)
}
slices.Sort(keys)
-
firstKey := ""
if len(keys) > 0 {
firstKey = keys[0]
@@ -454,14 +455,60 @@ func renderToolInvocation(
body = toMarkdown(body, innerWidth, t.BackgroundSubtle())
body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1))
}
+ case "task":
+ if description, ok := toolArgsMap["description"].(string); ok {
+ title = fmt.Sprintf("TASK %s %s", description, elapsed)
+ if summary, ok := metadata.Get("summary"); ok {
+ toolcalls := summary.([]any)
+ // toolcalls :=
+
+ 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 client.MessageToolInvocationToolCall
+ _ = json.Unmarshal(data, &toolCall)
+
+ if metadata, ok := call["metadata"].(map[string]any); ok {
+ data, _ = json.Marshal(metadata)
+ var toolMetadata client.MessageInfo_Metadata_Tool_AdditionalProperties
+ _ = json.Unmarshal(data, &toolMetadata)
+
+ step := renderToolInvocation(
+ toolCall,
+ nil,
+ toolMetadata,
+ false,
+ false,
+ true,
+ )
+ steps = append(steps, step)
+ }
+ }
+ }
+ body = strings.Join(steps, "\n")
+ body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1))
+ }
+ }
+
default:
toolName := renderToolName(toolCall.ToolName)
title = fmt.Sprintf("%s %s %s", toolName, toolArgs, elapsed)
+ if result == nil {
+ empty := ""
+ result = &empty
+ }
body = *result
body = truncateHeight(body, 10)
body = renderContentBlock(body, WithFullWidth(), WithMarginBottom(1))
}
+ if contentOnly {
+ title = "∟ " + title
+ return title
+ }
+
if !showDetails {
title = "∟ " + title
padding := calculatePadding()
@@ -502,8 +549,6 @@ func renderToolInvocation(
func renderToolName(name string) string {
switch name {
- // case agent.AgentToolName:
- // return "Task"
case "list":
return "LIST"
case "webfetch":
@@ -563,8 +608,8 @@ func renderFile(filename string, content string, options ...fileRenderingOption)
func renderToolAction(name string) string {
switch name {
- // case agent.AgentToolName:
- // return "Preparing prompt..."
+ case "task":
+ return "Searching..."
case "bash":
return "Building command..."
case "edit":
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index c05bd27c9..cee8aea8f 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -191,6 +191,7 @@ func (m *messagesComponent) renderView() {
metadata,
m.showToolDetails,
isLastToolInvocation,
+ false,
)
m.cache.Set(key, content)
}
@@ -202,6 +203,7 @@ func (m *messagesComponent) renderView() {
metadata,
m.showToolDetails,
isLastToolInvocation,
+ false,
)
}
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 7dc0737e0..0ce22a8ee 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -212,6 +212,11 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case dialog.CompletionDialogCloseMsg:
a.showCompletionDialog = false
a.completions.SetProvider(a.completionManager.DefaultProvider())
+ case client.EventInstallationUpdated:
+ return a, toast.NewSuccessToast(
+ "New version installed",
+ toast.WithTitle("opencode updated to "+msg.Properties.Version+", restart to apply."),
+ )
case client.EventSessionUpdated:
if msg.Properties.Info.Id == a.app.Session.Id {
a.app.Session = &msg.Properties.Info
diff --git a/packages/tui/pkg/client/gen/openapi.json b/packages/tui/pkg/client/gen/openapi.json
index 862a84d7d..9ec1223da 100644
--- a/packages/tui/pkg/client/gen/openapi.json
+++ b/packages/tui/pkg/client/gen/openapi.json
@@ -526,6 +526,9 @@
"$ref": "#/components/schemas/Event.storage.write"
},
{
+ "$ref": "#/components/schemas/Event.installation.updated"
+ },
+ {
"$ref": "#/components/schemas/Event.lsp.client.diagnostics"
},
{
@@ -538,9 +541,6 @@
"$ref": "#/components/schemas/Event.message.part.updated"
},
{
- "$ref": "#/components/schemas/Event.installation.updated"
- },
- {
"$ref": "#/components/schemas/Event.session.updated"
},
{
@@ -551,11 +551,11 @@
"propertyName": "type",
"mapping": {
"storage.write": "#/components/schemas/Event.storage.write",
+ "installation.updated": "#/components/schemas/Event.installation.updated",
"lsp.client.diagnostics": "#/components/schemas/Event.lsp.client.diagnostics",
"permission.updated": "#/components/schemas/Event.permission.updated",
"message.updated": "#/components/schemas/Event.message.updated",
"message.part.updated": "#/components/schemas/Event.message.part.updated",
- "installation.updated": "#/components/schemas/Event.installation.updated",
"session.updated": "#/components/schemas/Event.session.updated",
"session.error": "#/components/schemas/Event.session.error"
}
@@ -586,6 +586,30 @@
"properties"
]
},
+ "Event.installation.updated": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "installation.updated"
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "version"
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "properties"
+ ]
+ },
"Event.lsp.client.diagnostics": {
"type": "object",
"properties": {
@@ -1201,30 +1225,6 @@
"properties"
]
},
- "Event.installation.updated": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "const": "installation.updated"
- },
- "properties": {
- "type": "object",
- "properties": {
- "version": {
- "type": "string"
- }
- },
- "required": [
- "version"
- ]
- }
- },
- "required": [
- "type",
- "properties"
- ]
- },
"Event.session.updated": {
"type": "object",
"properties": {
@@ -1391,13 +1391,16 @@
"type": "object",
"properties": {
"$schema": {
- "type": "string"
+ "type": "string",
+ "description": "JSON schema reference for configuration validation"
},
"theme": {
- "type": "string"
+ "type": "string",
+ "description": "Theme name to use for the interface"
},
"keybinds": {
- "$ref": "#/components/schemas/Config.Keybinds"
+ "$ref": "#/components/schemas/Config.Keybinds",
+ "description": "Custom keybind configurations"
},
"autoshare": {
"type": "boolean",
@@ -1508,7 +1511,8 @@
"required": [
"models"
]
- }
+ },
+ "description": "Custom provider configurations and model overrides"
},
"mcp": {
"type": "object",
@@ -1528,7 +1532,8 @@
"remote": "#/components/schemas/Config.McpRemote"
}
}
- }
+ },
+ "description": "MCP (Model Context Protocol) server configurations"
}
}
},
@@ -1536,85 +1541,112 @@
"type": "object",
"properties": {
"leader": {
- "type": "string"
+ "type": "string",
+ "description": "Leader key for keybind combinations"
},
"help": {
- "type": "string"
+ "type": "string",
+ "description": "Show help dialog"
},
"editor_open": {
- "type": "string"
+ "type": "string",
+ "description": "Open external editor"
},
"session_new": {
- "type": "string"
+ "type": "string",
+ "description": "Create a new session"
},
"session_list": {
- "type": "string"
+ "type": "string",
+ "description": "List all sessions"
},
"session_share": {
- "type": "string"
+ "type": "string",
+ "description": "Share current session"
},
"session_interrupt": {
- "type": "string"
+ "type": "string",
+ "description": "Interrupt current session"
},
"session_compact": {
- "type": "string"
+ "type": "string",
+ "description": "Toggle compact mode for session"
},
"tool_details": {
- "type": "string"
+ "type": "string",
+ "description": "Show tool details"
},
"model_list": {
- "type": "string"
+ "type": "string",
+ "description": "List available models"
},
"theme_list": {
- "type": "string"
+ "type": "string",
+ "description": "List available themes"
},
"project_init": {
- "type": "string"
+ "type": "string",
+ "description": "Initialize project configuration"
},
"input_clear": {
- "type": "string"
+ "type": "string",
+ "description": "Clear input field"
},
"input_paste": {
- "type": "string"
+ "type": "string",
+ "description": "Paste from clipboard"
},
"input_submit": {
- "type": "string"
+ "type": "string",
+ "description": "Submit input"
},
"input_newline": {
- "type": "string"
+ "type": "string",
+ "description": "Insert newline in input"
},
"history_previous": {
- "type": "string"
+ "type": "string",
+ "description": "Navigate to previous history item"
},
"history_next": {
- "type": "string"
+ "type": "string",
+ "description": "Navigate to next history item"
},
"messages_page_up": {
- "type": "string"
+ "type": "string",
+ "description": "Scroll messages up by one page"
},
"messages_page_down": {
- "type": "string"
+ "type": "string",
+ "description": "Scroll messages down by one page"
},
"messages_half_page_up": {
- "type": "string"
+ "type": "string",
+ "description": "Scroll messages up by half page"
},
"messages_half_page_down": {
- "type": "string"
+ "type": "string",
+ "description": "Scroll messages down by half page"
},
"messages_previous": {
- "type": "string"
+ "type": "string",
+ "description": "Navigate to previous message"
},
"messages_next": {
- "type": "string"
+ "type": "string",
+ "description": "Navigate to next message"
},
"messages_first": {
- "type": "string"
+ "type": "string",
+ "description": "Navigate to first message"
},
"messages_last": {
- "type": "string"
+ "type": "string",
+ "description": "Navigate to last message"
},
"app_exit": {
- "type": "string"
+ "type": "string",
+ "description": "Exit the application"
}
}
},
@@ -1723,19 +1755,22 @@
"properties": {
"type": {
"type": "string",
- "const": "local"
+ "const": "local",
+ "description": "Type of MCP server connection"
},
"command": {
"type": "array",
"items": {
"type": "string"
- }
+ },
+ "description": "Command and arguments to run the MCP server"
},
"environment": {
"type": "object",
"additionalProperties": {
"type": "string"
- }
+ },
+ "description": "Environment variables to set when running the MCP server"
}
},
"required": [
@@ -1748,10 +1783,12 @@
"properties": {
"type": {
"type": "string",
- "const": "remote"
+ "const": "remote",
+ "description": "Type of MCP server connection"
},
"url": {
- "type": "string"
+ "type": "string",
+ "description": "URL of the remote MCP server"
}
},
"required": [
diff --git a/packages/tui/pkg/client/generated-client.go b/packages/tui/pkg/client/generated-client.go
index 15acb5488..a108357d7 100644
--- a/packages/tui/pkg/client/generated-client.go
+++ b/packages/tui/pkg/client/generated-client.go
@@ -41,6 +41,7 @@ type AppInfo struct {
// ConfigInfo defines model for Config.Info.
type ConfigInfo struct {
+ // Schema JSON schema reference for configuration validation
Schema *string `json:"$schema,omitempty"`
// Autoshare Share newly created sessions automatically
@@ -50,12 +51,16 @@ type ConfigInfo struct {
Autoupdate *bool `json:"autoupdate,omitempty"`
// DisabledProviders Disable providers that are loaded automatically
- DisabledProviders *[]string `json:"disabled_providers,omitempty"`
- Keybinds *ConfigKeybinds `json:"keybinds,omitempty"`
- Mcp *map[string]ConfigInfo_Mcp_AdditionalProperties `json:"mcp,omitempty"`
+ DisabledProviders *[]string `json:"disabled_providers,omitempty"`
+ Keybinds *ConfigKeybinds `json:"keybinds,omitempty"`
+
+ // Mcp MCP (Model Context Protocol) server configurations
+ Mcp *map[string]ConfigInfo_Mcp_AdditionalProperties `json:"mcp,omitempty"`
// Model Model to use in the format of provider/model, eg anthropic/claude-2
- Model *string `json:"model,omitempty"`
+ Model *string `json:"model,omitempty"`
+
+ // Provider Custom provider configurations and model overrides
Provider *map[string]struct {
Api *string `json:"api,omitempty"`
Env *[]string `json:"env,omitempty"`
@@ -81,6 +86,8 @@ type ConfigInfo struct {
Npm *string `json:"npm,omitempty"`
Options *map[string]interface{} `json:"options,omitempty"`
} `json:"provider,omitempty"`
+
+ // Theme Theme name to use for the interface
Theme *string `json:"theme,omitempty"`
}
@@ -91,46 +98,107 @@ type ConfigInfo_Mcp_AdditionalProperties struct {
// ConfigKeybinds defines model for Config.Keybinds.
type ConfigKeybinds struct {
- AppExit *string `json:"app_exit,omitempty"`
- EditorOpen *string `json:"editor_open,omitempty"`
- Help *string `json:"help,omitempty"`
- HistoryNext *string `json:"history_next,omitempty"`
- HistoryPrevious *string `json:"history_previous,omitempty"`
- InputClear *string `json:"input_clear,omitempty"`
- InputNewline *string `json:"input_newline,omitempty"`
- InputPaste *string `json:"input_paste,omitempty"`
- InputSubmit *string `json:"input_submit,omitempty"`
- Leader *string `json:"leader,omitempty"`
- MessagesFirst *string `json:"messages_first,omitempty"`
+ // AppExit Exit the application
+ AppExit *string `json:"app_exit,omitempty"`
+
+ // EditorOpen Open external editor
+ EditorOpen *string `json:"editor_open,omitempty"`
+
+ // Help Show help dialog
+ Help *string `json:"help,omitempty"`
+
+ // HistoryNext Navigate to next history item
+ HistoryNext *string `json:"history_next,omitempty"`
+
+ // HistoryPrevious Navigate to previous history item
+ HistoryPrevious *string `json:"history_previous,omitempty"`
+
+ // InputClear Clear input field
+ InputClear *string `json:"input_clear,omitempty"`
+
+ // InputNewline Insert newline in input
+ InputNewline *string `json:"input_newline,omitempty"`
+
+ // InputPaste Paste from clipboard
+ InputPaste *string `json:"input_paste,omitempty"`
+
+ // InputSubmit Submit input
+ InputSubmit *string `json:"input_submit,omitempty"`
+
+ // Leader Leader key for keybind combinations
+ Leader *string `json:"leader,omitempty"`
+
+ // MessagesFirst Navigate to first message
+ MessagesFirst *string `json:"messages_first,omitempty"`
+
+ // MessagesHalfPageDown Scroll messages down by half page
MessagesHalfPageDown *string `json:"messages_half_page_down,omitempty"`
- MessagesHalfPageUp *string `json:"messages_half_page_up,omitempty"`
- MessagesLast *string `json:"messages_last,omitempty"`
- MessagesNext *string `json:"messages_next,omitempty"`
- MessagesPageDown *string `json:"messages_page_down,omitempty"`
- MessagesPageUp *string `json:"messages_page_up,omitempty"`
- MessagesPrevious *string `json:"messages_previous,omitempty"`
- ModelList *string `json:"model_list,omitempty"`
- ProjectInit *string `json:"project_init,omitempty"`
- SessionCompact *string `json:"session_compact,omitempty"`
- SessionInterrupt *string `json:"session_interrupt,omitempty"`
- SessionList *string `json:"session_list,omitempty"`
- SessionNew *string `json:"session_new,omitempty"`
- SessionShare *string `json:"session_share,omitempty"`
- ThemeList *string `json:"theme_list,omitempty"`
- ToolDetails *string `json:"tool_details,omitempty"`
+
+ // MessagesHalfPageUp Scroll messages up by half page
+ MessagesHalfPageUp *string `json:"messages_half_page_up,omitempty"`
+
+ // MessagesLast Navigate to last message
+ MessagesLast *string `json:"messages_last,omitempty"`
+
+ // MessagesNext Navigate to next message
+ MessagesNext *string `json:"messages_next,omitempty"`
+
+ // MessagesPageDown Scroll messages down by one page
+ MessagesPageDown *string `json:"messages_page_down,omitempty"`
+
+ // MessagesPageUp Scroll messages up by one page
+ MessagesPageUp *string `json:"messages_page_up,omitempty"`
+
+ // MessagesPrevious Navigate to previous message
+ MessagesPrevious *string `json:"messages_previous,omitempty"`
+
+ // ModelList List available models
+ ModelList *string `json:"model_list,omitempty"`
+
+ // ProjectInit Initialize project configuration
+ ProjectInit *string `json:"project_init,omitempty"`
+
+ // SessionCompact Toggle compact mode for session
+ SessionCompact *string `json:"session_compact,omitempty"`
+
+ // SessionInterrupt Interrupt current session
+ SessionInterrupt *string `json:"session_interrupt,omitempty"`
+
+ // SessionList List all sessions
+ SessionList *string `json:"session_list,omitempty"`
+
+ // SessionNew Create a new session
+ SessionNew *string `json:"session_new,omitempty"`
+
+ // SessionShare Share current session
+ SessionShare *string `json:"session_share,omitempty"`
+
+ // ThemeList List available themes
+ ThemeList *string `json:"theme_list,omitempty"`
+
+ // ToolDetails Show tool details
+ ToolDetails *string `json:"tool_details,omitempty"`
}
// ConfigMcpLocal defines model for Config.McpLocal.
type ConfigMcpLocal struct {
- Command []string `json:"command"`
+ // Command Command and arguments to run the MCP server
+ Command []string `json:"command"`
+
+ // Environment Environment variables to set when running the MCP server
Environment *map[string]string `json:"environment,omitempty"`
- Type string `json:"type"`
+
+ // Type Type of MCP server connection
+ Type string `json:"type"`
}
// ConfigMcpRemote defines model for Config.McpRemote.
type ConfigMcpRemote struct {
+ // Type Type of MCP server connection
Type string `json:"type"`
- Url string `json:"url"`
+
+ // Url URL of the remote MCP server
+ Url string `json:"url"`
}
// Error defines model for Error.
@@ -684,6 +752,34 @@ func (t *Event) MergeEventStorageWrite(v EventStorageWrite) error {
return err
}
+// AsEventInstallationUpdated returns the union data inside the Event as a EventInstallationUpdated
+func (t Event) AsEventInstallationUpdated() (EventInstallationUpdated, error) {
+ var body EventInstallationUpdated
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromEventInstallationUpdated overwrites any union data inside the Event as the provided EventInstallationUpdated
+func (t *Event) FromEventInstallationUpdated(v EventInstallationUpdated) error {
+ v.Type = "installation.updated"
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeEventInstallationUpdated performs a merge with any union data inside the Event, using the provided EventInstallationUpdated
+func (t *Event) MergeEventInstallationUpdated(v EventInstallationUpdated) error {
+ v.Type = "installation.updated"
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JSONMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
// AsEventLspClientDiagnostics returns the union data inside the Event as a EventLspClientDiagnostics
func (t Event) AsEventLspClientDiagnostics() (EventLspClientDiagnostics, error) {
var body EventLspClientDiagnostics
@@ -796,34 +892,6 @@ func (t *Event) MergeEventMessagePartUpdated(v EventMessagePartUpdated) error {
return err
}
-// AsEventInstallationUpdated returns the union data inside the Event as a EventInstallationUpdated
-func (t Event) AsEventInstallationUpdated() (EventInstallationUpdated, error) {
- var body EventInstallationUpdated
- err := json.Unmarshal(t.union, &body)
- return body, err
-}
-
-// FromEventInstallationUpdated overwrites any union data inside the Event as the provided EventInstallationUpdated
-func (t *Event) FromEventInstallationUpdated(v EventInstallationUpdated) error {
- v.Type = "installation.updated"
- b, err := json.Marshal(v)
- t.union = b
- return err
-}
-
-// MergeEventInstallationUpdated performs a merge with any union data inside the Event, using the provided EventInstallationUpdated
-func (t *Event) MergeEventInstallationUpdated(v EventInstallationUpdated) error {
- v.Type = "installation.updated"
- b, err := json.Marshal(v)
- if err != nil {
- return err
- }
-
- merged, err := runtime.JSONMerge(t.union, b)
- t.union = merged
- return err
-}
-
// AsEventSessionUpdated returns the union data inside the Event as a EventSessionUpdated
func (t Event) AsEventSessionUpdated() (EventSessionUpdated, error) {
var body EventSessionUpdated