1package proto
2
3import (
4 "encoding/json"
5 "errors"
6 "time"
7
8 "charm.land/catwalk/pkg/catwalk"
9 "github.com/charmbracelet/crush/internal/config"
10 "github.com/charmbracelet/crush/internal/lsp"
11)
12
13// Workspace represents a running app.App workspace with its associated
14// resources and state.
15type Workspace struct {
16 ID string `json:"id"`
17 Path string `json:"path"`
18 YOLO bool `json:"yolo,omitempty"`
19 Debug bool `json:"debug,omitempty"`
20 DataDir string `json:"data_dir,omitempty"`
21 Version string `json:"version,omitempty"`
22 ClientID string `json:"client_id,omitempty"`
23 Config *config.Config `json:"config,omitempty"`
24 Env []string `json:"env,omitempty"`
25}
26
27// Error represents an error response.
28type Error struct {
29 Message string `json:"message"`
30}
31
32// ConfigChanged is published whenever the workspace's configuration is
33// mutated by a backend operation. Clients react by re-fetching the
34// workspace snapshot so cached config stays in sync across subscribers.
35type ConfigChanged struct {
36 WorkspaceID string `json:"workspace_id"`
37}
38
39// CurrentSession is the request body for the per-client
40// current-session endpoint. An empty SessionID clears the entry.
41type CurrentSession struct {
42 SessionID string `json:"session_id"`
43}
44
45// AgentInfo represents information about the agent.
46type AgentInfo struct {
47 IsBusy bool `json:"is_busy"`
48 IsReady bool `json:"is_ready"`
49 Model catwalk.Model `json:"model"`
50 ModelCfg config.SelectedModel `json:"model_cfg"`
51}
52
53// IsZero checks if the AgentInfo is zero-valued.
54func (a AgentInfo) IsZero() bool {
55 return !a.IsBusy && !a.IsReady && a.Model.ID == ""
56}
57
58// AgentMessage represents a message sent to the agent.
59type AgentMessage struct {
60 SessionID string `json:"session_id"`
61 Prompt string `json:"prompt"`
62 Attachments []Attachment `json:"attachments,omitempty"`
63}
64
65// AgentSession represents a session with its busy status.
66type AgentSession struct {
67 Session
68 IsBusy bool `json:"is_busy"`
69}
70
71// IsZero checks if the AgentSession is zero-valued.
72func (a AgentSession) IsZero() bool {
73 return a.ID == "" && !a.IsBusy
74}
75
76// PermissionAction represents an action taken on a permission request.
77type PermissionAction string
78
79const (
80 PermissionAllow PermissionAction = "allow"
81 PermissionAllowForSession PermissionAction = "allow_session"
82 PermissionDeny PermissionAction = "deny"
83)
84
85// MarshalText implements the [encoding.TextMarshaler] interface.
86func (p PermissionAction) MarshalText() ([]byte, error) {
87 return []byte(p), nil
88}
89
90// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
91func (p *PermissionAction) UnmarshalText(text []byte) error {
92 *p = PermissionAction(text)
93 return nil
94}
95
96// PermissionGrant represents a permission grant request.
97type PermissionGrant struct {
98 Permission PermissionRequest `json:"permission"`
99 Action PermissionAction `json:"action"`
100}
101
102// PermissionGrantResponse is the server's response to a permission
103// grant call. Resolved is true when this call resolved the pending
104// request, and false when the request had already been resolved by a
105// previous caller (e.g., another client in a multi-subscriber UI). A
106// false value is not an error.
107type PermissionGrantResponse struct {
108 Resolved bool `json:"resolved"`
109}
110
111// PermissionSkipRequest represents a request to skip permission prompts.
112type PermissionSkipRequest struct {
113 Skip bool `json:"skip"`
114}
115
116// LSPEventType represents the type of LSP event.
117type LSPEventType string
118
119const (
120 LSPEventStateChanged LSPEventType = "state_changed"
121 LSPEventDiagnosticsChanged LSPEventType = "diagnostics_changed"
122)
123
124// MarshalText implements the [encoding.TextMarshaler] interface.
125func (e LSPEventType) MarshalText() ([]byte, error) {
126 return []byte(e), nil
127}
128
129// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
130func (e *LSPEventType) UnmarshalText(data []byte) error {
131 *e = LSPEventType(data)
132 return nil
133}
134
135// LSPEvent represents an event in the LSP system.
136type LSPEvent struct {
137 Type LSPEventType `json:"type"`
138 Name string `json:"name"`
139 State lsp.ServerState `json:"state"`
140 Error error `json:"error,omitempty"`
141 DiagnosticCount int `json:"diagnostic_count,omitempty"`
142}
143
144// MarshalJSON implements the [json.Marshaler] interface.
145func (e LSPEvent) MarshalJSON() ([]byte, error) {
146 type Alias LSPEvent
147 return json.Marshal(&struct {
148 Error string `json:"error,omitempty"`
149 Alias
150 }{
151 Error: func() string {
152 if e.Error != nil {
153 return e.Error.Error()
154 }
155 return ""
156 }(),
157 Alias: Alias(e),
158 })
159}
160
161// UnmarshalJSON implements the [json.Unmarshaler] interface.
162func (e *LSPEvent) UnmarshalJSON(data []byte) error {
163 type Alias LSPEvent
164 aux := &struct {
165 Error string `json:"error,omitempty"`
166 Alias
167 }{
168 Alias: Alias(*e),
169 }
170 if err := json.Unmarshal(data, &aux); err != nil {
171 return err
172 }
173 *e = LSPEvent(aux.Alias)
174 if aux.Error != "" {
175 e.Error = errors.New(aux.Error)
176 }
177 return nil
178}
179
180// LSPClientInfo holds information about an LSP client's state.
181type LSPClientInfo struct {
182 Name string `json:"name"`
183 State lsp.ServerState `json:"state"`
184 Error error `json:"error,omitempty"`
185 DiagnosticCount int `json:"diagnostic_count,omitempty"`
186 ConnectedAt time.Time `json:"connected_at"`
187}
188
189// MarshalJSON implements the [json.Marshaler] interface.
190func (i LSPClientInfo) MarshalJSON() ([]byte, error) {
191 type Alias LSPClientInfo
192 return json.Marshal(&struct {
193 Error string `json:"error,omitempty"`
194 Alias
195 }{
196 Error: func() string {
197 if i.Error != nil {
198 return i.Error.Error()
199 }
200 return ""
201 }(),
202 Alias: Alias(i),
203 })
204}
205
206// UnmarshalJSON implements the [json.Unmarshaler] interface.
207func (i *LSPClientInfo) UnmarshalJSON(data []byte) error {
208 type Alias LSPClientInfo
209 aux := &struct {
210 Error string `json:"error,omitempty"`
211 Alias
212 }{
213 Alias: Alias(*i),
214 }
215 if err := json.Unmarshal(data, &aux); err != nil {
216 return err
217 }
218 *i = LSPClientInfo(aux.Alias)
219 if aux.Error != "" {
220 i.Error = errors.New(aux.Error)
221 }
222 return nil
223}