diff options
| author | adamdottv <[email protected]> | 2025-06-27 07:46:42 -0500 |
|---|---|---|
| committer | Jay V <[email protected]> | 2025-06-27 19:10:42 -0400 |
| commit | be0811ecc30bb8a4fef14111437aacf6fa51fe27 (patch) | |
| tree | 737ca49413e16756e2c2d779f9e3961fae4e7d13 /packages/tui/internal | |
| parent | 0676bcd4fd33b6acb37e248be53b9e6a0352be6b (diff) | |
| download | opencode-be0811ecc30bb8a4fef14111437aacf6fa51fe27.tar.gz opencode-be0811ecc30bb8a4fef14111437aacf6fa51fe27.zip | |
chore: rework openapi spec and use stainless sdk
Diffstat (limited to 'packages/tui/internal')
| -rw-r--r-- | packages/tui/internal/app/app.go | 235 | ||||
| -rw-r--r-- | packages/tui/internal/commands/command.go | 8 | ||||
| -rw-r--r-- | packages/tui/internal/completions/files-folders.go | 15 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/message.go | 129 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/messages.go | 102 | ||||
| -rw-r--r-- | packages/tui/internal/components/commands/commands.go | 2 | ||||
| -rw-r--r-- | packages/tui/internal/components/dialog/models.go | 24 | ||||
| -rw-r--r-- | packages/tui/internal/components/dialog/session.go | 10 | ||||
| -rw-r--r-- | packages/tui/internal/components/status/status.go | 12 | ||||
| -rw-r--r-- | packages/tui/internal/config/config.go | 8 | ||||
| -rw-r--r-- | packages/tui/internal/tui/tui.go | 75 |
11 files changed, 280 insertions, 340 deletions
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go index e8775921a..3bd48f02a 100644 --- a/packages/tui/internal/app/app.go +++ b/packages/tui/internal/app/app.go @@ -11,35 +11,35 @@ import ( "log/slog" tea "github.com/charmbracelet/bubbletea/v2" + "github.com/sst/opencode-sdk-go" "github.com/sst/opencode/internal/commands" "github.com/sst/opencode/internal/components/toast" "github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" "github.com/sst/opencode/internal/util" - "github.com/sst/opencode/pkg/client" ) var RootPath string type App struct { - Info client.AppInfo + Info opencode.App Version string StatePath string - Config *client.ConfigInfo - Client *client.ClientWithResponses + Config *opencode.Config + Client *opencode.Client State *config.State - Provider *client.ProviderInfo - Model *client.ModelInfo - Session *client.SessionInfo - Messages []client.MessageInfo + Provider *opencode.Provider + Model *opencode.Model + Session *opencode.Session + Messages []opencode.Message Commands commands.CommandRegistry } -type SessionSelectedMsg = *client.SessionInfo +type SessionSelectedMsg = *opencode.Session type ModelSelectedMsg struct { - Provider client.ProviderInfo - Model client.ModelInfo + Provider opencode.Provider + Model opencode.Model } type SessionClearedMsg struct{} type CompactSessionMsg struct{} @@ -51,31 +51,24 @@ type CompletionDialogTriggeredMsg struct { InitialValue string } type OptimisticMessageAddedMsg struct { - Message client.MessageInfo + Message opencode.Message } func New( ctx context.Context, version string, - appInfo client.AppInfo, - httpClient *client.ClientWithResponses, + appInfo opencode.App, + httpClient *opencode.Client, ) (*App, error) { RootPath = appInfo.Path.Root - configResponse, err := httpClient.PostConfigGetWithResponse(ctx) + configInfo, err := httpClient.Config.Get(ctx) if err != nil { return nil, err } - if configResponse.StatusCode() != 200 || configResponse.JSON200 == nil { - return nil, fmt.Errorf("failed to get config: %d", configResponse.StatusCode()) - } - configInfo := configResponse.JSON200 - if configInfo.Keybinds == nil { - leader := "ctrl+x" - keybinds := client.ConfigKeybinds{ - Leader: &leader, - } - configInfo.Keybinds = &keybinds + + if configInfo.Keybinds.Leader == "" { + configInfo.Keybinds.Leader = "ctrl+x" } appStatePath := filepath.Join(appInfo.Path.State, "tui") @@ -85,16 +78,16 @@ func New( config.SaveState(appStatePath, appState) } - if configInfo.Theme != nil { - appState.Theme = *configInfo.Theme + if configInfo.Theme != "" { + appState.Theme = configInfo.Theme } - if configInfo.Model != nil { - splits := strings.Split(*configInfo.Model, "/") + + if configInfo.Model != "" { + splits := strings.Split(configInfo.Model, "/") appState.Provider = splits[0] appState.Model = strings.Join(splits[1:], "/") } - // Load themes from all directories if err := theme.LoadThemesFromDirectories( appInfo.Path.Config, appInfo.Path.Root, @@ -122,8 +115,8 @@ func New( Config: configInfo, State: appState, Client: httpClient, - Session: &client.SessionInfo{}, - Messages: []client.MessageInfo{}, + Session: &opencode.Session{}, + Messages: []opencode.Message{}, Commands: commands.LoadFromConfig(configInfo), } @@ -132,23 +125,19 @@ func New( func (a *App) InitializeProvider() tea.Cmd { return func() tea.Msg { - providersResponse, err := a.Client.PostProviderListWithResponse(context.Background()) + providersResponse, err := a.Client.Config.Providers(context.Background()) if err != nil { slog.Error("Failed to list providers", "error", err) // TODO: notify user return nil } - if providersResponse != nil && providersResponse.StatusCode() != 200 { - slog.Error("failed to retrieve providers", "status", providersResponse.StatusCode(), "message", string(providersResponse.Body)) - return nil - } - providers := []client.ProviderInfo{} - var defaultProvider *client.ProviderInfo - var defaultModel *client.ModelInfo + providers := providersResponse.Providers + var defaultProvider *opencode.Provider + var defaultModel *opencode.Model - var anthropic *client.ProviderInfo - for _, provider := range providersResponse.JSON200.Providers { - if provider.Id == "anthropic" { + var anthropic *opencode.Provider + for _, provider := range providers { + if provider.ID == "anthropic" { anthropic = &provider } } @@ -159,7 +148,7 @@ func (a *App) InitializeProvider() tea.Cmd { defaultModel = getDefaultModel(providersResponse, *anthropic) } - for _, provider := range providersResponse.JSON200.Providers { + for _, provider := range providers { if defaultProvider == nil || defaultModel == nil { defaultProvider = &provider defaultModel = getDefaultModel(providersResponse, provider) @@ -171,14 +160,14 @@ func (a *App) InitializeProvider() tea.Cmd { return nil } - var currentProvider *client.ProviderInfo - var currentModel *client.ModelInfo + var currentProvider *opencode.Provider + var currentModel *opencode.Model for _, provider := range providers { - if provider.Id == a.State.Provider { + if provider.ID == a.State.Provider { currentProvider = &provider for _, model := range provider.Models { - if model.Id == a.State.Model { + if model.ID == a.State.Model { currentModel = &model } } @@ -189,7 +178,6 @@ func (a *App) InitializeProvider() tea.Cmd { currentModel = defaultModel } - // TODO: handle no provider or model setup, yet return ModelSelectedMsg{ Provider: *currentProvider, Model: *currentModel, @@ -197,8 +185,8 @@ func (a *App) InitializeProvider() tea.Cmd { } } -func getDefaultModel(response *client.PostProviderListResponse, provider client.ProviderInfo) *client.ModelInfo { - if match, ok := response.JSON200.Default[provider.Id]; ok { +func getDefaultModel(response *opencode.ConfigProvidersResponse, provider opencode.Provider) *opencode.Model { + if match, ok := response.Default[provider.ID]; ok { model := provider.Models[match] return &model } else { @@ -222,7 +210,7 @@ func (a *App) IsBusy() bool { } lastMessage := a.Messages[len(a.Messages)-1] - return lastMessage.Metadata.Time.Completed == nil + return lastMessage.Metadata.Time.Completed == 0 } func (a *App) SaveState() { @@ -245,19 +233,14 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd { cmds = append(cmds, util.CmdHandler(SessionSelectedMsg(session))) go func() { - response, err := a.Client.PostSessionInitialize(ctx, client.PostSessionInitializeJSONRequestBody{ - SessionID: a.Session.Id, - ProviderID: a.Provider.Id, - ModelID: a.Model.Id, + _, err := a.Client.Session.Init(ctx, a.Session.ID, opencode.SessionInitParams{ + ProviderID: opencode.F(a.Provider.ID), + ModelID: opencode.F(a.Model.ID), }) if err != nil { slog.Error("Failed to initialize project", "error", err) // status.Error(err.Error()) } - if response != nil && response.StatusCode != 200 { - slog.Error("Failed to initialize project", "error", response.StatusCode) - // status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode)) - } }() return tea.Batch(cmds...) @@ -265,48 +248,37 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd { func (a *App) CompactSession(ctx context.Context) tea.Cmd { go func() { - response, err := a.Client.PostSessionSummarizeWithResponse(ctx, client.PostSessionSummarizeJSONRequestBody{ - SessionID: a.Session.Id, - ProviderID: a.Provider.Id, - ModelID: a.Model.Id, + _, err := a.Client.Session.Summarize(ctx, a.Session.ID, opencode.SessionSummarizeParams{ + ProviderID: opencode.F(a.Provider.ID), + ModelID: opencode.F(a.Model.ID), }) if err != nil { slog.Error("Failed to compact session", "error", err) } - if response != nil && response.StatusCode() != 200 { - slog.Error("Failed to compact session", "error", response.StatusCode) - } }() return nil } func (a *App) MarkProjectInitialized(ctx context.Context) error { - response, err := a.Client.PostAppInitialize(ctx) + _, err := a.Client.App.Init(ctx) if err != nil { slog.Error("Failed to mark project as initialized", "error", err) return err } - if response != nil && response.StatusCode != 200 { - return fmt.Errorf("failed to initialize project: %d", response.StatusCode) - } return nil } -func (a *App) CreateSession(ctx context.Context) (*client.SessionInfo, error) { - resp, err := a.Client.PostSessionCreateWithResponse(ctx) +func (a *App) CreateSession(ctx context.Context) (*opencode.Session, error) { + session, err := a.Client.Session.New(ctx) if err != nil { return nil, err } - if resp != nil && resp.StatusCode() != 200 { - return nil, fmt.Errorf("failed to create session: %d", resp.StatusCode()) - } - session := resp.JSON200 return session, nil } func (a *App) SendChatMessage(ctx context.Context, text string, attachments []Attachment) tea.Cmd { var cmds []tea.Cmd - if a.Session.Id == "" { + if a.Session.ID == "" { session, err := a.CreateSession(ctx) if err != nil { return toast.NewErrorToast(err.Error()) @@ -315,26 +287,18 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At cmds = append(cmds, util.CmdHandler(SessionSelectedMsg(session))) } - part := client.MessagePart{} - part.FromMessagePartText(client.MessagePartText{ - Type: "text", - Text: text, - }) - parts := []client.MessagePart{part} - - optimisticMessage := client.MessageInfo{ - Id: fmt.Sprintf("optimistic-%d", time.Now().UnixNano()), - Role: client.User, - Parts: parts, - Metadata: client.MessageMetadata{ - SessionID: a.Session.Id, - Time: struct { - Completed *float32 `json:"completed,omitempty"` - Created float32 `json:"created"` - }{ - Created: float32(time.Now().Unix()), + optimisticMessage := opencode.Message{ + ID: fmt.Sprintf("optimistic-%d", time.Now().UnixNano()), + Role: opencode.MessageRoleUser, + Parts: []opencode.MessagePart{{ + Type: opencode.MessagePartTypeText, + Text: text, + }}, + Metadata: opencode.MessageMetadata{ + SessionID: a.Session.ID, + Time: opencode.MessageMetadataTime{ + Created: float64(time.Now().Unix()), }, - Tool: make(map[string]client.MessageMetadata_Tool_AdditionalProperties), }, } @@ -342,22 +306,21 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: optimisticMessage})) cmds = append(cmds, func() tea.Msg { - response, err := a.Client.PostSessionChat(ctx, client.PostSessionChatJSONRequestBody{ - SessionID: a.Session.Id, - Parts: parts, - ProviderID: a.Provider.Id, - ModelID: a.Model.Id, + _, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{ + Parts: opencode.F([]opencode.MessagePartUnionParam{ + opencode.TextPartParam{ + Type: opencode.F(opencode.TextPartTypeText), + Text: opencode.F(text), + }, + }), + ProviderID: opencode.F(a.Provider.ID), + ModelID: opencode.F(a.Model.ID), }) if err != nil { errormsg := fmt.Sprintf("failed to send message: %v", err) slog.Error(errormsg) return toast.NewErrorToast(errormsg)() } - if response != nil && response.StatusCode != 200 { - errormsg := fmt.Sprintf("failed to send message: %d", response.StatusCode) - slog.Error(errormsg) - return toast.NewErrorToast(errormsg)() - } return nil }) @@ -367,83 +330,61 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At } func (a *App) Cancel(ctx context.Context, sessionID string) error { - response, err := a.Client.PostSessionAbort(ctx, client.PostSessionAbortJSONRequestBody{ - SessionID: sessionID, - }) + _, err := a.Client.Session.Abort(ctx, sessionID) if err != nil { slog.Error("Failed to cancel session", "error", err) // status.Error(err.Error()) return err } - if response != nil && response.StatusCode != 200 { - slog.Error("Failed to cancel session", "error", fmt.Sprintf("failed to cancel session: %d", response.StatusCode)) - // status.Error(fmt.Sprintf("failed to cancel session: %d", response.StatusCode)) - return fmt.Errorf("failed to cancel session: %d", response.StatusCode) - } return nil } -func (a *App) ListSessions(ctx context.Context) ([]client.SessionInfo, error) { - resp, err := a.Client.PostSessionListWithResponse(ctx) +func (a *App) ListSessions(ctx context.Context) ([]opencode.Session, error) { + response, err := a.Client.Session.List(ctx) if err != nil { return nil, err } - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("failed to list sessions: %d", resp.StatusCode()) - } - if resp.JSON200 == nil { - return []client.SessionInfo{}, nil + if response == nil { + return []opencode.Session{}, nil } - sessions := *resp.JSON200 - + sessions := *response sort.Slice(sessions, func(i, j int) bool { return sessions[i].Time.Created-sessions[j].Time.Created > 0 }) - return sessions, nil } func (a *App) DeleteSession(ctx context.Context, sessionID string) error { - resp, err := a.Client.PostSessionDeleteWithResponse(ctx, client.PostSessionDeleteJSONRequestBody{ - SessionID: sessionID, - }) + _, err := a.Client.Session.Delete(ctx, sessionID) if err != nil { + slog.Error("Failed to delete session", "error", err) return err } - if resp.StatusCode() != 200 { - return fmt.Errorf("failed to delete session: %d", resp.StatusCode()) - } return nil } -func (a *App) ListMessages(ctx context.Context, sessionId string) ([]client.MessageInfo, error) { - resp, err := a.Client.PostSessionMessagesWithResponse(ctx, client.PostSessionMessagesJSONRequestBody{SessionID: sessionId}) +func (a *App) ListMessages(ctx context.Context, sessionId string) ([]opencode.Message, error) { + response, err := a.Client.Session.Messages(ctx, sessionId) if err != nil { return nil, err } - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("failed to list messages: %d", resp.StatusCode()) - } - if resp.JSON200 == nil { - return []client.MessageInfo{}, nil + if response == nil { + return []opencode.Message{}, nil } - messages := *resp.JSON200 + messages := *response return messages, nil } -func (a *App) ListProviders(ctx context.Context) ([]client.ProviderInfo, error) { - resp, err := a.Client.PostProviderListWithResponse(ctx) +func (a *App) ListProviders(ctx context.Context) ([]opencode.Provider, error) { + response, err := a.Client.Config.Providers(ctx) if err != nil { return nil, err } - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("failed to list sessions: %d", resp.StatusCode()) - } - if resp.JSON200 == nil { - return []client.ProviderInfo{}, nil + if response == nil { + return []opencode.Provider{}, nil } - providers := *resp.JSON200 + providers := *response return providers.Providers, nil } diff --git a/packages/tui/internal/commands/command.go b/packages/tui/internal/commands/command.go index fc27025f6..4ef458832 100644 --- a/packages/tui/internal/commands/command.go +++ b/packages/tui/internal/commands/command.go @@ -6,7 +6,7 @@ import ( "strings" tea "github.com/charmbracelet/bubbletea/v2" - "github.com/sst/opencode/pkg/client" + "github.com/sst/opencode-sdk-go" ) type ExecuteCommandMsg Command @@ -123,7 +123,7 @@ func parseBindings(bindings ...string) []Keybinding { return parsedBindings } -func LoadFromConfig(config *client.ConfigInfo) CommandRegistry { +func LoadFromConfig(config *opencode.Config) CommandRegistry { defaults := []Command{ { Name: AppHelpCommand, @@ -269,10 +269,10 @@ func LoadFromConfig(config *client.ConfigInfo) CommandRegistry { } registry := make(CommandRegistry) keybinds := map[string]string{} - marshalled, _ := json.Marshal(*config.Keybinds) + marshalled, _ := json.Marshal(config.Keybinds) json.Unmarshal(marshalled, &keybinds) for _, command := range defaults { - if keybind, ok := keybinds[string(command.Name)]; ok { + if keybind, ok := keybinds[string(command.Name)]; ok && keybind != "" { command.Keybindings = parseBindings(keybind) } registry[command.Name] = command diff --git a/packages/tui/internal/completions/files-folders.go b/packages/tui/internal/completions/files-folders.go index 491b67aac..6fb4316fc 100644 --- a/packages/tui/internal/completions/files-folders.go +++ b/packages/tui/internal/completions/files-folders.go @@ -3,9 +3,9 @@ package completions import ( "context" + "github.com/sst/opencode-sdk-go" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/dialog" - "github.com/sst/opencode/pkg/client" ) type filesAndFoldersContextGroup struct { @@ -29,17 +29,14 @@ func (cg *filesAndFoldersContextGroup) GetEmptyMessage() string { } func (cg *filesAndFoldersContextGroup) getFiles(query string) ([]string, error) { - response, err := cg.app.Client.PostFileSearchWithResponse(context.Background(), client.PostFileSearchJSONRequestBody{ - Query: query, - }) + files, err := cg.app.Client.File.Search( + context.Background(), + opencode.FileSearchParams{Query: opencode.F(query)}, + ) if err != nil { return []string{}, err } - if response.JSON200 == nil { - return []string{}, nil - } - - return *response.JSON200, nil + return *files, nil } func (cg *filesAndFoldersContextGroup) GetChildEntries(query string) ([]dialog.CompletionItemI, error) { 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")) } diff --git a/packages/tui/internal/components/commands/commands.go b/packages/tui/internal/components/commands/commands.go index d7f334c35..e20755575 100644 --- a/packages/tui/internal/components/commands/commands.go +++ b/packages/tui/internal/components/commands/commands.go @@ -128,7 +128,7 @@ func (c *commandsComponent) View() string { if c.showKeybinds { for _, kb := range cmd.Keybindings { if kb.RequiresLeader { - keybindStrs = append(keybindStrs, *c.app.Config.Keybinds.Leader+" "+kb.Key) + keybindStrs = append(keybindStrs, c.app.Config.Keybinds.Leader+" "+kb.Key) } else { keybindStrs = append(keybindStrs, kb.Key) } diff --git a/packages/tui/internal/components/dialog/models.go b/packages/tui/internal/components/dialog/models.go index 52ece493f..b43d48156 100644 --- a/packages/tui/internal/components/dialog/models.go +++ b/packages/tui/internal/components/dialog/models.go @@ -10,6 +10,7 @@ import ( "github.com/charmbracelet/bubbles/v2/key" 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/list" "github.com/sst/opencode/internal/components/modal" @@ -17,7 +18,6 @@ import ( "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" "github.com/sst/opencode/internal/util" - "github.com/sst/opencode/pkg/client" ) const ( @@ -32,8 +32,8 @@ type ModelDialog interface { type modelDialog struct { app *app.App - availableProviders []client.ProviderInfo - provider client.ProviderInfo + availableProviders []opencode.Provider + provider opencode.Provider width int height int hScrollOffset int @@ -69,7 +69,7 @@ var modelKeys = modelKeyMap{ } func (m *modelDialog) Init() tea.Cmd { - m.setupModelsForProvider(m.provider.Id) + m.setupModelsForProvider(m.provider.ID) return nil } @@ -90,7 +90,7 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, modelKeys.Enter): selectedItem, _ := m.modelList.GetSelectedItem() models := m.models() - var selectedModel client.ModelInfo + var selectedModel opencode.Model for _, model := range models { if model.Name == string(selectedItem) { selectedModel = model @@ -119,8 +119,8 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } -func (m *modelDialog) models() []client.ModelInfo { - models := slices.SortedFunc(maps.Values(m.provider.Models), func(a, b client.ModelInfo) int { +func (m *modelDialog) models() []opencode.Model { + models := slices.SortedFunc(maps.Values(m.provider.Models), func(a, b opencode.Model) int { return strings.Compare(a.Name, b.Name) }) return models @@ -139,7 +139,7 @@ func (m *modelDialog) switchProvider(offset int) { m.hScrollOffset = newOffset m.provider = m.availableProviders[m.hScrollOffset] m.modal.SetTitle(fmt.Sprintf("Select %s Model", m.provider.Name)) - m.setupModelsForProvider(m.provider.Id) + m.setupModelsForProvider(m.provider.ID) } func (m *modelDialog) View() string { @@ -175,9 +175,9 @@ func (m *modelDialog) setupModelsForProvider(providerId string) { m.modelList = list.NewStringList(modelNames, numVisibleModels, "No models available", true) m.modelList.SetMaxWidth(maxDialogWidth) - if m.app.Provider != nil && m.app.Model != nil && m.app.Provider.Id == providerId { + if m.app.Provider != nil && m.app.Model != nil && m.app.Provider.ID == providerId { for i, model := range models { - if model.Id == m.app.Model.Id { + if model.ID == m.app.Model.ID { m.modelList.SetSelectedIndex(i) break } @@ -200,7 +200,7 @@ func NewModelDialog(app *app.App) ModelDialog { hScrollOffset := 0 if app.Provider != nil { for i, provider := range availableProviders { - if provider.Id == app.Provider.Id { + if provider.ID == app.Provider.ID { currentProvider = provider hScrollOffset = i break @@ -220,6 +220,6 @@ func NewModelDialog(app *app.App) ModelDialog { ), } - dialog.setupModelsForProvider(currentProvider.Id) + dialog.setupModelsForProvider(currentProvider.ID) return dialog } diff --git a/packages/tui/internal/components/dialog/session.go b/packages/tui/internal/components/dialog/session.go index f38f37bf5..6f18dba0e 100644 --- a/packages/tui/internal/components/dialog/session.go +++ b/packages/tui/internal/components/dialog/session.go @@ -8,6 +8,7 @@ import ( tea "github.com/charmbracelet/bubbletea/v2" "github.com/muesli/reflow/truncate" + "github.com/sst/opencode-sdk-go" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/list" "github.com/sst/opencode/internal/components/modal" @@ -16,7 +17,6 @@ import ( "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" "github.com/sst/opencode/internal/util" - "github.com/sst/opencode/pkg/client" ) // SessionDialog interface for the session switching dialog @@ -79,7 +79,7 @@ type sessionDialog struct { width int height int modal *modal.Modal - sessions []client.SessionInfo + sessions []opencode.Session list list.List[sessionItem] app *app.App deleteConfirmation int // -1 means no confirmation, >= 0 means confirming deletion of session at this index @@ -122,7 +122,7 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) { s.updateListItems() return nil }, - s.deleteSession(sessionToDelete.Id), + s.deleteSession(sessionToDelete.ID), ) } else { // First press - enter delete confirmation mode @@ -193,10 +193,10 @@ func (s *sessionDialog) Close() tea.Cmd { func NewSessionDialog(app *app.App) SessionDialog { sessions, _ := app.ListSessions(context.Background()) - var filteredSessions []client.SessionInfo + var filteredSessions []opencode.Session var items []sessionItem for _, sess := range sessions { - if sess.ParentID != nil { + if sess.ParentID != "" { continue } filteredSessions = append(filteredSessions, sess) diff --git a/packages/tui/internal/components/status/status.go b/packages/tui/internal/components/status/status.go index fb5ff8ced..fb44cbcb8 100644 --- a/packages/tui/internal/components/status/status.go +++ b/packages/tui/internal/components/status/status.go @@ -48,7 +48,7 @@ func (m statusComponent) logo() string { Render(open + code + version) } -func formatTokensAndCost(tokens float32, contextWindow float32, cost float32) string { +func formatTokensAndCost(tokens float64, contextWindow float64, cost float64) string { // Format tokens in human-readable format (e.g., 110K, 1.2M) var formattedTokens string switch { @@ -77,7 +77,7 @@ func formatTokensAndCost(tokens float32, contextWindow float32, cost float32) st func (m statusComponent) View() string { t := theme.CurrentTheme() - if m.app.Session.Id == "" { + if m.app.Session.ID == "" { return styles.NewStyle(). Background(t.Background()). Width(m.width). @@ -94,13 +94,13 @@ func (m statusComponent) View() string { Render(m.app.Info.Path.Cwd) sessionInfo := "" - if m.app.Session.Id != "" { - tokens := float32(0) - cost := float32(0) + if m.app.Session.ID != "" { + tokens := float64(0) + cost := float64(0) contextWindow := m.app.Model.Limit.Context for _, message := range m.app.Messages { - if message.Metadata.Assistant != nil { + if message.Metadata.Assistant.Cost > 0 { cost += message.Metadata.Assistant.Cost usage := message.Metadata.Assistant.Tokens if usage.Output > 0 { diff --git a/packages/tui/internal/config/config.go b/packages/tui/internal/config/config.go index 29db8657e..258007a47 100644 --- a/packages/tui/internal/config/config.go +++ b/packages/tui/internal/config/config.go @@ -7,7 +7,6 @@ import ( "os" "github.com/BurntSushi/toml" - "github.com/sst/opencode/pkg/client" ) type State struct { @@ -22,13 +21,6 @@ func NewState() *State { } } -func MergeState(state *State, config *client.ConfigInfo) *client.ConfigInfo { - if config.Theme == nil { - config.Theme = &state.Theme - } - return config -} - // SaveState writes the provided Config struct to the specified TOML file. // It will create the file if it doesn't exist, or overwrite it if it does. func SaveState(filePath string, state *State) error { diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go index 500ab56d4..89a34e351 100644 --- a/packages/tui/internal/tui/tui.go +++ b/packages/tui/internal/tui/tui.go @@ -12,6 +12,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/commands" "github.com/sst/opencode/internal/completions" @@ -24,7 +25,6 @@ import ( "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" "github.com/sst/opencode/internal/util" - "github.com/sst/opencode/pkg/client" ) // InterruptDebounceTimeoutMsg is sent when the interrupt key debounce timeout expires @@ -74,7 +74,7 @@ func (a appModel) Init() tea.Cmd { // Check if we should show the init dialog cmds = append(cmds, func() tea.Msg { - shouldShow := a.app.Info.Git && a.app.Info.Time.Initialized == nil + shouldShow := a.app.Info.Git && a.app.Info.Time.Initialized > 0 return dialog.ShowInitDialogMsg{Show: shouldShow} }) @@ -267,31 +267,31 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) case dialog.CompletionDialogCloseMsg: a.showCompletionDialog = false - case client.EventInstallationUpdated: + case opencode.EventListResponseEventInstallationUpdated: return a, toast.NewSuccessToast( "opencode updated to "+msg.Properties.Version+", restart to apply.", toast.WithTitle("New version installed"), ) - case client.EventSessionDeleted: - if a.app.Session != nil && msg.Properties.Info.Id == a.app.Session.Id { - a.app.Session = &client.SessionInfo{} - a.app.Messages = []client.MessageInfo{} + case opencode.EventListResponseEventSessionDeleted: + if a.app.Session != nil && msg.Properties.Info.ID == a.app.Session.ID { + a.app.Session = &opencode.Session{} + a.app.Messages = []opencode.Message{} } return a, toast.NewSuccessToast("Session deleted successfully") - case client.EventSessionUpdated: - if msg.Properties.Info.Id == a.app.Session.Id { + case opencode.EventListResponseEventSessionUpdated: + if msg.Properties.Info.ID == a.app.Session.ID { a.app.Session = &msg.Properties.Info } - case client.EventMessageUpdated: - if msg.Properties.Info.Metadata.SessionID == a.app.Session.Id { + case opencode.EventListResponseEventMessageUpdated: + if msg.Properties.Info.Metadata.SessionID == a.app.Session.ID { exists := false optimisticReplaced := false // First check if this is replacing an optimistic message - if msg.Properties.Info.Role == client.User { + if msg.Properties.Info.Role == opencode.MessageRoleUser { // Look for optimistic messages to replace for i, m := range a.app.Messages { - if strings.HasPrefix(m.Id, "optimistic-") && m.Role == client.User { + if strings.HasPrefix(m.ID, "optimistic-") && m.Role == opencode.MessageRoleUser { // Replace the optimistic message with the real one a.app.Messages[i] = msg.Properties.Info exists = true @@ -304,7 +304,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // If not replacing optimistic, check for existing message with same ID if !optimisticReplaced { for i, m := range a.app.Messages { - if m.Id == msg.Properties.Info.Id { + if m.ID == msg.Properties.Info.ID { a.app.Messages[i] = msg.Properties.Info exists = true break @@ -316,11 +316,12 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.app.Messages = append(a.app.Messages, msg.Properties.Info) } } - case client.EventSessionError: - unknownError, err := msg.Properties.Error.AsUnknownError() - if err == nil { - slog.Error("Server error", "name", unknownError.Name, "message", unknownError.Data.Message) - return a, toast.NewErrorToast(unknownError.Data.Message, toast.WithTitle(unknownError.Name)) + case opencode.EventListResponseEventSessionError: + switch err := msg.Properties.Error.AsUnion().(type) { + case nil: + case opencode.UnknownError: + slog.Error("Server error", "name", err.Name, "message", err.Data.Message) + return a, toast.NewErrorToast(err.Data.Message, toast.WithTitle(string(err.Name))) } case tea.WindowSizeMsg: msg.Height -= 2 // Make space for the status bar @@ -336,7 +337,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } a.layout.SetSize(a.width, a.height) case app.SessionSelectedMsg: - messages, err := a.app.ListMessages(context.Background(), msg.Id) + messages, err := a.app.ListMessages(context.Background(), msg.ID) if err != nil { slog.Error("Failed to list messages", "error", err) return a, toast.NewErrorToast("Failed to open session") @@ -346,8 +347,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case app.ModelSelectedMsg: a.app.Provider = &msg.Provider a.app.Model = &msg.Model - a.app.State.Provider = msg.Provider.Id - a.app.State.Model = msg.Model.Id + a.app.State.Provider = msg.Provider.ID + a.app.State.Model = msg.Model.ID a.app.SaveState() case dialog.ThemeSelectedMsg: a.app.State.Theme = msg.ThemeName @@ -499,42 +500,32 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd) }) cmds = append(cmds, cmd) case commands.SessionNewCommand: - if a.app.Session.Id == "" { + if a.app.Session.ID == "" { return a, nil } - a.app.Session = &client.SessionInfo{} - a.app.Messages = []client.MessageInfo{} + a.app.Session = &opencode.Session{} + a.app.Messages = []opencode.Message{} cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{})) case commands.SessionListCommand: sessionDialog := dialog.NewSessionDialog(a.app) a.modal = sessionDialog case commands.SessionShareCommand: - if a.app.Session.Id == "" { + if a.app.Session.ID == "" { return a, nil } - response, err := a.app.Client.PostSessionShareWithResponse( - context.Background(), - client.PostSessionShareJSONRequestBody{ - SessionID: a.app.Session.Id, - }, - ) + _, err := a.app.Client.Session.Share(context.Background(), a.app.Session.ID) if err != nil { slog.Error("Failed to share session", "error", err) return a, toast.NewErrorToast("Failed to share session") } - if response.JSON200 != nil && response.JSON200.Share != nil { - shareUrl := response.JSON200.Share.Url - cmds = append(cmds, tea.SetClipboard(shareUrl)) - cmds = append(cmds, toast.NewSuccessToast("Share URL copied to clipboard!")) - } case commands.SessionInterruptCommand: - if a.app.Session.Id == "" { + if a.app.Session.ID == "" { return a, nil } - a.app.Cancel(context.Background(), a.app.Session.Id) + a.app.Cancel(context.Background(), a.app.Session.ID) return a, nil case commands.SessionCompactCommand: - if a.app.Session.Id == "" { + if a.app.Session.ID == "" { return a, nil } // TODO: block until compaction is complete @@ -642,8 +633,8 @@ func NewModel(app *app.App) tea.Model { messagesContainer := layout.NewContainer(messages) var leaderBinding *key.Binding - if (*app.Config.Keybinds).Leader != nil { - binding := key.NewBinding(key.WithKeys(*app.Config.Keybinds.Leader)) + if app.Config.Keybinds.Leader != "" { + binding := key.NewBinding(key.WithKeys(app.Config.Keybinds.Leader)) leaderBinding = &binding } |
