summaryrefslogtreecommitdiffhomepage
path: root/packages/tui
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-06-05 14:59:07 -0400
committerDax Raad <[email protected]>2025-06-05 14:59:16 -0400
commitdb2bb32bcf0c3fdc8ede5530946e85a852448679 (patch)
tree31f81b89cd8ed7b642a09e25fcb0a2a4b1c0e6bb /packages/tui
parent1384a5e3e69522001571980f147a5aa0d985f895 (diff)
downloadopencode-db2bb32bcf0c3fdc8ede5530946e85a852448679.tar.gz
opencode-db2bb32bcf0c3fdc8ede5530946e85a852448679.zip
integrate with models.dev
Diffstat (limited to 'packages/tui')
-rw-r--r--packages/tui/cmd/opencode/main.go8
-rw-r--r--packages/tui/internal/app/app.go28
-rw-r--r--packages/tui/internal/components/core/status.go4
-rw-r--r--packages/tui/internal/components/dialog/models.go17
-rw-r--r--packages/tui/pkg/client/gen/openapi.json66
-rw-r--r--packages/tui/pkg/client/generated-client.go33
6 files changed, 98 insertions, 58 deletions
diff --git a/packages/tui/cmd/opencode/main.go b/packages/tui/cmd/opencode/main.go
index 3d9dd2947..d2809aaea 100644
--- a/packages/tui/cmd/opencode/main.go
+++ b/packages/tui/cmd/opencode/main.go
@@ -23,7 +23,10 @@ func main() {
slog.Error("Failed to create client", "error", err)
os.Exit(1)
}
- paths, _ := httpClient.PostPathGetWithResponse(context.Background())
+ paths, err := httpClient.PostPathGetWithResponse(context.Background())
+ if err != nil {
+ panic(err)
+ }
logfile := filepath.Join(paths.JSON200.Data, "log", "tui.log")
if _, err := os.Stat(filepath.Dir(logfile)); os.IsNotExist(err) {
@@ -48,8 +51,7 @@ func main() {
app_, err := app.New(ctx, httpClient)
if err != nil {
- slog.Error("Failed to create app", "error", err)
- // return err
+ panic(err)
}
// Set up the TUI
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index d4b59c0b9..9ce5c2a66 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -43,18 +43,24 @@ func New(ctx context.Context, httpClient *client.ClientWithResponses) (*App, err
appInfoResponse, _ := httpClient.PostAppInfoWithResponse(ctx)
appInfo := appInfoResponse.JSON200
- providersResponse, _ := httpClient.PostProviderListWithResponse(ctx)
+ providersResponse, err := httpClient.PostProviderListWithResponse(ctx)
+ if err != nil {
+ return nil, err
+ }
providers := []client.ProviderInfo{}
var defaultProvider *client.ProviderInfo
var defaultModel *client.ProviderModel
- for _, provider := range *providersResponse.JSON200 {
- if provider.Id == "anthropic" {
- defaultProvider = &provider
-
- for _, model := range provider.Models {
- if model.Id == "claude-sonnet-4-20250514" {
+ for i, provider := range providersResponse.JSON200.Providers {
+ if i == 0 || provider.Id == "anthropic" {
+ defaultProvider = &providersResponse.JSON200.Providers[i]
+ if match, ok := providersResponse.JSON200.Default[provider.Id]; ok {
+ model := defaultProvider.Models[match]
+ defaultModel = &model
+ } else {
+ for _, model := range provider.Models {
defaultModel = &model
+ break
}
}
}
@@ -63,12 +69,6 @@ func New(ctx context.Context, httpClient *client.ClientWithResponses) (*App, err
if len(providers) == 0 {
return nil, fmt.Errorf("no providers found")
}
- if defaultProvider == nil {
- defaultProvider = &providers[0]
- }
- if defaultModel == nil {
- defaultModel = &defaultProvider.Models[0]
- }
appConfigPath := filepath.Join(appInfo.Path.Config, "tui.toml")
appConfig, err := config.LoadConfig(appConfigPath)
@@ -296,7 +296,7 @@ func (a *App) ListProviders(ctx context.Context) ([]client.ProviderInfo, error)
}
providers := *resp.JSON200
- return providers, nil
+ return providers.Providers, nil
}
// IsFilepickerOpen returns whether the filepicker is currently open
diff --git a/packages/tui/internal/components/core/status.go b/packages/tui/internal/components/core/status.go
index b631ce766..5c3e5eb3c 100644
--- a/packages/tui/internal/components/core/status.go
+++ b/packages/tui/internal/components/core/status.go
@@ -7,9 +7,9 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
+ "github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/pubsub"
"github.com/sst/opencode/internal/status"
- "github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
)
@@ -145,7 +145,7 @@ func (m statusCmp) View() string {
if m.app.Session.Id != "" {
tokens := float32(0)
cost := float32(0)
- contextWindow := m.app.Model.ContextWindow
+ contextWindow := m.app.Model.Limit.Context
for _, message := range m.app.Messages {
if message.Metadata.Assistant != nil {
diff --git a/packages/tui/internal/components/dialog/models.go b/packages/tui/internal/components/dialog/models.go
index a9dcb6f6c..5d0ee772b 100644
--- a/packages/tui/internal/components/dialog/models.go
+++ b/packages/tui/internal/components/dialog/models.go
@@ -3,6 +3,9 @@ package dialog
import (
"context"
"fmt"
+ "maps"
+ "slices"
+ "strings"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
@@ -38,7 +41,6 @@ type modelDialogCmp struct {
app *app.App
availableProviders []client.ProviderInfo
provider client.ProviderInfo
- model *client.ProviderModel
selectedIdx int
width int
@@ -144,7 +146,8 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.switchProvider(1)
}
case key.Matches(msg, modelKeys.Enter):
- return m, util.CmdHandler(CloseModelDialogMsg{Provider: &m.provider, Model: &m.provider.Models[m.selectedIdx]})
+ models := m.models()
+ return m, util.CmdHandler(CloseModelDialogMsg{Provider: &m.provider, Model: &models[m.selectedIdx]})
case key.Matches(msg, modelKeys.Escape):
return m, util.CmdHandler(CloseModelDialogMsg{})
}
@@ -156,6 +159,13 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
+func (m *modelDialogCmp) models() []client.ProviderModel {
+ models := slices.SortedFunc(maps.Values(m.provider.Models), func(a, b client.ProviderModel) int {
+ return strings.Compare(*a.Name, *b.Name)
+ })
+ return models
+}
+
// moveSelectionUp moves the selection up or wraps to bottom
func (m *modelDialogCmp) moveSelectionUp() {
if m.selectedIdx > 0 {
@@ -218,13 +228,14 @@ func (m *modelDialogCmp) View() string {
endIdx := min(m.scrollOffset+numVisibleModels, len(m.provider.Models))
modelItems := make([]string, 0, endIdx-m.scrollOffset)
+ models := m.models()
for i := m.scrollOffset; i < endIdx; i++ {
itemStyle := baseStyle.Width(maxDialogWidth)
if i == m.selectedIdx {
itemStyle = itemStyle.Background(t.Primary()).
Foreground(t.Background()).Bold(true)
}
- modelItems = append(modelItems, itemStyle.Render(*m.provider.Models[i].Name))
+ modelItems = append(modelItems, itemStyle.Render(*models[i].Name))
}
scrollIndicator := m.getScrollIndicators(maxDialogWidth)
diff --git a/packages/tui/pkg/client/gen/openapi.json b/packages/tui/pkg/client/gen/openapi.json
index a9f19d4b3..f329b48b3 100644
--- a/packages/tui/pkg/client/gen/openapi.json
+++ b/packages/tui/pkg/client/gen/openapi.json
@@ -401,10 +401,25 @@
"content": {
"application/json": {
"schema": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/Provider.Info"
- }
+ "type": "object",
+ "properties": {
+ "providers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Provider.Info"
+ }
+ },
+ "default": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "providers",
+ "default"
+ ]
}
}
}
@@ -1080,13 +1095,9 @@
"name": {
"type": "string"
},
- "options": {
- "type": "object",
- "additionalProperties": {}
- },
"models": {
- "type": "array",
- "items": {
+ "type": "object",
+ "additionalProperties": {
"$ref": "#/components/schemas/Provider.Model"
}
}
@@ -1106,6 +1117,12 @@
"name": {
"type": "string"
},
+ "attachment": {
+ "type": "boolean"
+ },
+ "reasoning": {
+ "type": "boolean"
+ },
"cost": {
"type": "object",
"properties": {
@@ -1129,24 +1146,27 @@
"outputCached"
]
},
- "contextWindow": {
- "type": "number"
- },
- "maxOutputTokens": {
- "type": "number"
- },
- "attachment": {
- "type": "boolean"
- },
- "reasoning": {
- "type": "boolean"
+ "limit": {
+ "type": "object",
+ "properties": {
+ "context": {
+ "type": "number"
+ },
+ "output": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "context",
+ "output"
+ ]
}
},
"required": [
"id",
+ "attachment",
"cost",
- "contextWindow",
- "attachment"
+ "limit"
]
}
}
diff --git a/packages/tui/pkg/client/generated-client.go b/packages/tui/pkg/client/generated-client.go
index 1237685a9..247af33a7 100644
--- a/packages/tui/pkg/client/generated-client.go
+++ b/packages/tui/pkg/client/generated-client.go
@@ -203,26 +203,27 @@ type MessageToolInvocationToolResult struct {
// ProviderInfo defines model for Provider.Info.
type ProviderInfo struct {
- Id string `json:"id"`
- Models []ProviderModel `json:"models"`
- Name string `json:"name"`
- Options *map[string]interface{} `json:"options,omitempty"`
+ Id string `json:"id"`
+ Models map[string]ProviderModel `json:"models"`
+ Name string `json:"name"`
}
// ProviderModel defines model for Provider.Model.
type ProviderModel struct {
- Attachment bool `json:"attachment"`
- ContextWindow float32 `json:"contextWindow"`
- Cost struct {
+ Attachment bool `json:"attachment"`
+ Cost struct {
Input float32 `json:"input"`
InputCached float32 `json:"inputCached"`
Output float32 `json:"output"`
OutputCached float32 `json:"outputCached"`
} `json:"cost"`
- Id string `json:"id"`
- MaxOutputTokens *float32 `json:"maxOutputTokens,omitempty"`
- Name *string `json:"name,omitempty"`
- Reasoning *bool `json:"reasoning,omitempty"`
+ Id string `json:"id"`
+ Limit struct {
+ Context float32 `json:"context"`
+ Output float32 `json:"output"`
+ } `json:"limit"`
+ Name *string `json:"name,omitempty"`
+ Reasoning *bool `json:"reasoning,omitempty"`
}
// PermissionInfo defines model for permission.info.
@@ -1815,7 +1816,10 @@ func (r PostPathGetResponse) StatusCode() int {
type PostProviderListResponse struct {
Body []byte
HTTPResponse *http.Response
- JSON200 *[]ProviderInfo
+ JSON200 *struct {
+ Default map[string]string `json:"default"`
+ Providers []ProviderInfo `json:"providers"`
+ }
}
// Status returns HTTPResponse.Status
@@ -2299,7 +2303,10 @@ func ParsePostProviderListResponse(rsp *http.Response) (*PostProviderListRespons
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
- var dest []ProviderInfo
+ var dest struct {
+ Default map[string]string `json:"default"`
+ Providers []ProviderInfo `json:"providers"`
+ }
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}