Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6fad2f6
fix: typo
l0gicgate May 15, 2025
25bac47
fix: add initialization logic for setup dialog
l0gicgate May 15, 2025
92fac03
fix: split out bedrock models
l0gicgate May 15, 2025
1a99269
feat: add setup complete and update logic
l0gicgate May 15, 2025
eff2322
feat: add setup dialog
l0gicgate May 15, 2025
d4648c2
fix: sort models by alphabetical order
l0gicgate May 15, 2025
735ddc5
fix: add api key validation & blinking
l0gicgate May 15, 2025
98de5ba
refactor: extract setup in module
l0gicgate May 15, 2025
08df5ec
fix: tweak styles
l0gicgate May 15, 2025
63d45c4
refactor: render help
l0gicgate May 15, 2025
3a68df5
fix: q -> s
l0gicgate May 15, 2025
cdd2299
Merge branch 'sst:dev' into feature/setup-dialog
l0gicgate May 16, 2025
455e5c1
fix: move model/provider utils
l0gicgate May 16, 2025
71b017c
fix: show setup dialog again after quit attempt
l0gicgate May 16, 2025
4e47ad6
refactor: use simple-list component
l0gicgate May 16, 2025
90808bb
Merge branch 'dev' into feature/setup-dialog
l0gicgate May 16, 2025
6110a0c
fix: show setup dialog when no selected in quit dialog
l0gicgate May 16, 2025
d18e33b
fix: remove debug
l0gicgate May 16, 2025
5a2c093
fix: temporarily disable bedrock and vertex
l0gicgate May 16, 2025
eea5e42
fix: tweak list styling
l0gicgate May 16, 2025
2dd9692
fix: reinitialize model dialog after setup
l0gicgate May 16, 2025
7d53155
Merge branch 'sst:dev' into feature/setup-dialog
l0gicgate May 17, 2025
9e274d0
fix: move setup initialization
l0gicgate May 17, 2025
f417494
Merge branch 'sst:dev' into feature/setup-dialog
l0gicgate May 18, 2025
d55d7af
Merge branch 'dev' into feature/setup-dialog
l0gicgate May 19, 2025
fabf639
fix: auto initialize project
l0gicgate May 24, 2025
9b81d7b
fix: auto initialize project
l0gicgate May 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package app
import (
"context"
"database/sql"
"github.com/sst/opencode/internal/setup"
"maps"
"sync"
"time"
Expand Down Expand Up @@ -95,6 +96,29 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
// Initialize LSP clients in the background
go app.initLSPClients(ctx)

// Initialize setup
setup.Init()

if !setup.IsSetupComplete() {
app.PrimaryAgent, err = agent.NewSetupAgent()

if err != nil {
slog.Error("Failed to create setup agent", "error", err)
return nil, err
}

return app, nil
}

app.InitializePrimaryAgent()

return app, nil
}

// InitializePrimaryAgent initializes the primary agent with the necessary tools and services
func (app *App) InitializePrimaryAgent() {
var err error

app.PrimaryAgent, err = agent.NewAgent(
config.AgentPrimary,
app.Sessions,
Expand All @@ -107,12 +131,11 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
app.LSPClients,
),
)

if err != nil {
slog.Error("Failed to create primary agent", "error", err)
return nil, err
slog.Error("Failed to initialize primary agent", err)
panic(err)
}

return app, nil
}

// initTheme sets the application theme based on the configuration
Expand Down
12 changes: 12 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,21 @@ func Load(workingDir string, debug bool, lvl *slog.LevelVar) (*Config, error) {
Model: cfg.Agents[AgentTitle].Model,
MaxTokens: 80,
}

return cfg, nil
}

func Update(updateCfg func(config *Config)) error {
if cfg == nil {
return fmt.Errorf("config not loaded")
}

return updateCfgFile(func(config *Config) {
updateCfg(config)
cfg = config
})
}

// configureViper sets up viper's configuration paths and environment variables.
func configureViper() {
viper.SetConfigName(fmt.Sprintf(".%s", appName))
Expand Down
57 changes: 57 additions & 0 deletions internal/llm/agent/setup-agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package agent

import (
"context"
"fmt"
"github.com/sst/opencode/internal/config"
"github.com/sst/opencode/internal/llm/models"
"github.com/sst/opencode/internal/llm/provider"
"github.com/sst/opencode/internal/message"
)

type setupAgent struct {
}

func NewSetupAgent() (Service, error) {
agent := &setupAgent{}

return agent, nil
}

func (a *setupAgent) Cancel(sessionID string) {

}

func (a *setupAgent) IsBusy() bool {
return true
}

func (a *setupAgent) IsSessionBusy(sessionID string) bool {
return true
}

func (a *setupAgent) Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error) {
return nil, ErrSessionBusy
}

func (a *setupAgent) GetUsage(ctx context.Context, sessionID string) (*int64, error) {
usage := int64(0)

return &usage, nil
}

func (a *setupAgent) EstimateContextWindowUsage(ctx context.Context, sessionID string) (float64, bool, error) {
return 0, false, nil
}

func (a *setupAgent) TrackUsage(ctx context.Context, sessionID string, model models.Model, usage provider.TokenUsage) error {
return nil
}

func (a *setupAgent) Update(agentName config.AgentName, modelID models.ModelID) (models.Model, error) {
return models.Model{}, fmt.Errorf("cannot change model while processing requests")
}

func (a *setupAgent) CompactSession(ctx context.Context, sessionID string, force bool) error {
return nil
}
85 changes: 84 additions & 1 deletion internal/llm/models/models.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package models

import "maps"
import (
"maps"
)

type (
ModelID string
Expand Down Expand Up @@ -52,3 +54,84 @@ func init() {
maps.Copy(SupportedModels, XAIModels)
maps.Copy(SupportedModels, VertexAIGeminiModels)
}

var providerLabels map[ModelProvider]string
var providerList []ModelProvider

// AvailableProviders returns a list of all available providers
func AvailableProviders() ([]ModelProvider, map[ModelProvider]string) {
if providerLabels != nil && providerList != nil {
return providerList, providerLabels
}

providerLabels = make(map[ModelProvider]string)
providerLabels[ProviderAnthropic] = "Anthropic"
providerLabels[ProviderAzure] = "Azure"
providerLabels[ProviderBedrock] = "Bedrock"
providerLabels[ProviderGemini] = "Gemini"
providerLabels[ProviderGROQ] = "Groq"
providerLabels[ProviderOpenAI] = "OpenAI"
providerLabels[ProviderOpenRouter] = "OpenRouter"
providerLabels[ProviderVertexAI] = "Vertex AI"
providerLabels[ProviderXAI] = "xAI"

providerList = make([]ModelProvider, 0, len(providerLabels))
providerList = append(providerList, ProviderAnthropic)
providerList = append(providerList, ProviderAzure)
// FIXME: Re-add when the setup wizard supports it
// providerList = append(providerList, ProviderBedrock)
providerList = append(providerList, ProviderGemini)
providerList = append(providerList, ProviderGROQ)
providerList = append(providerList, ProviderOpenAI)
providerList = append(providerList, ProviderOpenRouter)
// FIXME: Re-add when the setup wizard supports it
// providerList = append(providerList, ProviderVertexAI)
providerList = append(providerList, ProviderXAI)

return providerList, providerLabels
}

var modelsByProvider map[ModelProvider][]Model

// AvailableModelsByProvider returns a list of all available models by provider
func AvailableModelsByProvider() map[ModelProvider][]Model {
if modelsByProvider != nil {
return modelsByProvider
}

providers, _ := AvailableProviders()

modelsByProviderMap := make(map[ModelProvider]map[ModelID]Model)
modelsByProviderMap[ProviderAnthropic] = AnthropicModels
modelsByProviderMap[ProviderAzure] = AzureModels
modelsByProviderMap[ProviderBedrock] = BedrockModels
modelsByProviderMap[ProviderGemini] = GeminiModels
modelsByProviderMap[ProviderGROQ] = GroqModels
modelsByProviderMap[ProviderOpenAI] = OpenAIModels
modelsByProviderMap[ProviderOpenRouter] = OpenRouterModels
modelsByProviderMap[ProviderVertexAI] = VertexAIGeminiModels
modelsByProviderMap[ProviderXAI] = XAIModels

modelsByProvider = make(map[ModelProvider][]Model)

// Add models to the map sorted alphabetically
for _, provider := range providers {
models := make([]Model, 0, len(modelsByProviderMap[provider]))
for _, model := range modelsByProviderMap[provider] {
models = append(models, model)
}

// Sort models by alphabetical order
for i := 0; i < len(models)-1; i++ {
for j := i + 1; j < len(models); j++ {
if models[i].Name > models[j].Name {
models[i], models[j] = models[j], models[i]
}
}
}

modelsByProvider[provider] = models
}

return modelsByProvider
}
69 changes: 69 additions & 0 deletions internal/setup/setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package setup

import (
"github.com/sst/opencode/internal/config"
"github.com/sst/opencode/internal/llm/models"
"log/slog"
)

// Global variable to track if setup is complete
var setupComplete = false

// IsSetupComplete checks if the setup is complete
func IsSetupComplete() bool {
return setupComplete
}

func markSetupComplete() {
setupComplete = true
}

func Init() {
cfg := config.Get()
if cfg == nil || len(cfg.Agents) < 1 {
return
}

// Ensure primary agent is set
_, exists := cfg.Agents[config.AgentPrimary]
if exists {
markSetupComplete()
}
}

func CompleteSetup(provider models.ModelProvider, model models.Model, apiKey string) {
err := config.Update(func(cfg *config.Config) {
// Add Agent
if cfg.Agents == nil {
cfg.Agents = make(map[config.AgentName]config.Agent)
}
cfg.Agents[config.AgentPrimary] = config.Agent{
Model: model.ID,
MaxTokens: model.DefaultMaxTokens,
}
cfg.Agents[config.AgentTitle] = config.Agent{
Model: model.ID,
MaxTokens: 80,
}
cfg.Agents[config.AgentTask] = config.Agent{
Model: model.ID,
MaxTokens: model.DefaultMaxTokens,
}

// Add Provider
if cfg.Providers == nil {
cfg.Providers = make(map[models.ModelProvider]config.Provider)
}

cfg.Providers[provider] = config.Provider{
APIKey: apiKey,
}
})

if err != nil {
slog.Debug("Failed to complete setup", "error", err)
panic(err)
}

markSetupComplete()
}
15 changes: 12 additions & 3 deletions internal/tui/components/chat/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/message"
"github.com/sst/opencode/internal/setup"
"github.com/sst/opencode/internal/status"
"github.com/sst/opencode/internal/tui/components/dialog"
"github.com/sst/opencode/internal/tui/image"
Expand Down Expand Up @@ -317,14 +318,22 @@ func (m *editorCmp) View() string {
Bold(true).
Foreground(t.Primary())

// Only show textarea if setup is complete
prefix := ""
textarea := ""
if setup.IsSetupComplete() {
prefix = style.Render(">")
textarea = m.textarea.View()
}

if len(m.attachments) == 0 {
return lipgloss.JoinHorizontal(lipgloss.Top, style.Render(">"), m.textarea.View())
return lipgloss.JoinHorizontal(lipgloss.Top, prefix, textarea)
}
m.textarea.SetHeight(m.height - 1)

return lipgloss.JoinVertical(lipgloss.Top,
m.attachmentsContent(),
lipgloss.JoinHorizontal(lipgloss.Top, style.Render(">"),
m.textarea.View()),
lipgloss.JoinHorizontal(lipgloss.Top, prefix, textarea),
)
}

Expand Down
Loading