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
77// Shutdown performs a clean shutdown of the application
78func (app *App) Shutdown() {
79 // Cancel all watcher goroutines
80 app.cancelFuncsMutex.Lock()
81 for _, cancel := range app.watcherCancelFuncs {
82 cancel()
83 }
84 app.cancelFuncsMutex.Unlock()
85 app.watcherWG.Wait()
86
87 // Perform additional cleanup for LSP clients
88 app.clientsMutex.RLock()
89 clients := make(map[string]*lsp.Client, len(app.LSPClients))
90 maps.Copy(clients, app.LSPClients)
91 app.clientsMutex.RUnlock()
92
93 for name, client := range clients {
94 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
95 if err := client.Shutdown(shutdownCtx); err != nil {
96 logging.Error("Failed to shutdown LSP client", "name", name, "error", err)
97 }
98 cancel()
99 }
100}