app.go

 1package app
 2
 3import (
 4	"context"
 5	"database/sql"
 6	"maps"
 7	"sync"
 8	"time"
 9
10	"github.com/opencode-ai/opencode/internal/config"
11	"github.com/opencode-ai/opencode/internal/db"
12	"github.com/opencode-ai/opencode/internal/history"
13	"github.com/opencode-ai/opencode/internal/llm/agent"
14	"github.com/opencode-ai/opencode/internal/logging"
15	"github.com/opencode-ai/opencode/internal/lsp"
16	"github.com/opencode-ai/opencode/internal/message"
17	"github.com/opencode-ai/opencode/internal/permission"
18	"github.com/opencode-ai/opencode/internal/session"
19)
20
21type App struct {
22	Sessions    session.Service
23	Messages    message.Service
24	History     history.Service
25	Permissions permission.Service
26
27	CoderAgent agent.Service
28
29	LSPClients map[string]*lsp.Client
30
31	clientsMutex sync.RWMutex
32
33	watcherCancelFuncs []context.CancelFunc
34	cancelFuncsMutex   sync.Mutex
35	watcherWG          sync.WaitGroup
36}
37
38func New(ctx context.Context, conn *sql.DB) (*App, error) {
39	q := db.New(conn)
40	sessions := session.NewService(q)
41	messages := message.NewService(q)
42	files := history.NewService(q, conn)
43
44	app := &App{
45		Sessions:    sessions,
46		Messages:    messages,
47		History:     files,
48		Permissions: permission.NewPermissionService(),
49		LSPClients:  make(map[string]*lsp.Client),
50	}
51
52	// Initialize LSP clients in the background
53	go app.initLSPClients(ctx)
54
55	var err error
56	app.CoderAgent, err = agent.NewAgent(
57		config.AgentCoder,
58		app.Sessions,
59		app.Messages,
60		agent.CoderAgentTools(
61			app.Permissions,
62			app.Sessions,
63			app.Messages,
64			app.History,
65			app.LSPClients,
66		),
67	)
68	if err != nil {
69		logging.Error("Failed to create coder agent", err)
70		return nil, err
71	}
72
73	return app, nil
74}
75
76// Shutdown performs a clean shutdown of the application
77func (app *App) Shutdown() {
78	// Cancel all watcher goroutines
79	app.cancelFuncsMutex.Lock()
80	for _, cancel := range app.watcherCancelFuncs {
81		cancel()
82	}
83	app.cancelFuncsMutex.Unlock()
84	app.watcherWG.Wait()
85
86	// Perform additional cleanup for LSP clients
87	app.clientsMutex.RLock()
88	clients := make(map[string]*lsp.Client, len(app.LSPClients))
89	maps.Copy(clients, app.LSPClients)
90	app.clientsMutex.RUnlock()
91
92	for name, client := range clients {
93		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
94		if err := client.Shutdown(shutdownCtx); err != nil {
95			logging.Error("Failed to shutdown LSP client", "name", name, "error", err)
96		}
97		cancel()
98	}
99}