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 // Skills carries the snapshot of skill discovery state at workspace
26 // creation time. Subsequent updates flow through the SSE event
27 // stream.
28 Skills []SkillState `json:"skills,omitempty"`
29}
30
31// Error represents an error response.
32type Error struct {
33 Message string `json:"message"`
34}
35
36// ConfigChanged is published whenever the workspace's configuration is
37// mutated by a backend operation. Clients react by re-fetching the
38// workspace snapshot so cached config stays in sync across subscribers.
39type ConfigChanged struct {
40 WorkspaceID string `json:"workspace_id"`
41}
42
43// CurrentSession is the request body for the per-client
44// current-session endpoint. An empty SessionID clears the entry.
45type CurrentSession struct {
46 SessionID string `json:"session_id"`
47}
48
49// SkillInfo describes a visible skill exposed to a frontend.
50type SkillInfo struct {
51 ID string `json:"id"`
52 Name string `json:"name"`
53 Description string `json:"description"`
54 Label string `json:"label"`
55 Source string `json:"source"`
56}
57
58// ReadSkillRequest is the request body for reading a skill's content.
59type ReadSkillRequest struct {
60 SkillID string `json:"skill_id"`
61}
62
63// ReadSkillResponse is the response for reading a skill's content.
64type ReadSkillResponse struct {
65 Content []byte `json:"content"`
66 Result SkillReadResult `json:"result"`
67}
68
69// SkillReadResult holds metadata about a skill returned alongside its
70// content.
71type SkillReadResult struct {
72 Name string `json:"name"`
73 Description string `json:"description"`
74 Source string `json:"source"`
75 Builtin bool `json:"builtin"`
76}
77
78// AgentInfo represents information about the agent.
79type AgentInfo struct {
80 IsBusy bool `json:"is_busy"`
81 IsReady bool `json:"is_ready"`
82 Model catwalk.Model `json:"model"`
83 ModelCfg config.SelectedModel `json:"model_cfg"`
84}
85
86// IsZero checks if the AgentInfo is zero-valued.
87func (a AgentInfo) IsZero() bool {
88 return !a.IsBusy && !a.IsReady && a.Model.ID == ""
89}
90
91// AgentMessage represents a message sent to the agent.
92type AgentMessage struct {
93 SessionID string `json:"session_id"`
94 Prompt string `json:"prompt"`
95 Attachments []Attachment `json:"attachments,omitempty"`
96}
97
98// AgentSession represents a session with its busy status.
99type AgentSession struct {
100 Session
101 IsBusy bool `json:"is_busy"`
102}
103
104// IsZero checks if the AgentSession is zero-valued.
105func (a AgentSession) IsZero() bool {
106 return a.ID == "" && !a.IsBusy
107}
108
109// PermissionAction represents an action taken on a permission request.
110type PermissionAction string
111
112const (
113 PermissionAllow PermissionAction = "allow"
114 PermissionAllowForSession PermissionAction = "allow_session"
115 PermissionDeny PermissionAction = "deny"
116)
117
118// MarshalText implements the [encoding.TextMarshaler] interface.
119func (p PermissionAction) MarshalText() ([]byte, error) {
120 return []byte(p), nil
121}
122
123// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
124func (p *PermissionAction) UnmarshalText(text []byte) error {
125 *p = PermissionAction(text)
126 return nil
127}
128
129// PermissionGrant represents a permission grant request.
130type PermissionGrant struct {
131 Permission PermissionRequest `json:"permission"`
132 Action PermissionAction `json:"action"`
133}
134
135// PermissionGrantResponse is the server's response to a permission
136// grant call. Resolved is true when this call resolved the pending
137// request, and false when the request had already been resolved by a
138// previous caller (e.g., another client in a multi-subscriber UI). A
139// false value is not an error.
140type PermissionGrantResponse struct {
141 Resolved bool `json:"resolved"`
142}
143
144// PermissionSkipRequest represents a request to skip permission prompts.
145type PermissionSkipRequest struct {
146 Skip bool `json:"skip"`
147}
148
149// LSPEventType represents the type of LSP event.
150type LSPEventType string
151
152const (
153 LSPEventStateChanged LSPEventType = "state_changed"
154 LSPEventDiagnosticsChanged LSPEventType = "diagnostics_changed"
155)
156
157// MarshalText implements the [encoding.TextMarshaler] interface.
158func (e LSPEventType) MarshalText() ([]byte, error) {
159 return []byte(e), nil
160}
161
162// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
163func (e *LSPEventType) UnmarshalText(data []byte) error {
164 *e = LSPEventType(data)
165 return nil
166}
167
168// LSPEvent represents an event in the LSP system.
169type LSPEvent struct {
170 Type LSPEventType `json:"type"`
171 Name string `json:"name"`
172 State lsp.ServerState `json:"state"`
173 Error error `json:"error,omitempty"`
174 DiagnosticCount int `json:"diagnostic_count,omitempty"`
175}
176
177// MarshalJSON implements the [json.Marshaler] interface.
178func (e LSPEvent) MarshalJSON() ([]byte, error) {
179 type Alias LSPEvent
180 return json.Marshal(&struct {
181 Error string `json:"error,omitempty"`
182 Alias
183 }{
184 Error: func() string {
185 if e.Error != nil {
186 return e.Error.Error()
187 }
188 return ""
189 }(),
190 Alias: Alias(e),
191 })
192}
193
194// UnmarshalJSON implements the [json.Unmarshaler] interface.
195func (e *LSPEvent) UnmarshalJSON(data []byte) error {
196 type Alias LSPEvent
197 aux := &struct {
198 Error string `json:"error,omitempty"`
199 Alias
200 }{
201 Alias: Alias(*e),
202 }
203 if err := json.Unmarshal(data, &aux); err != nil {
204 return err
205 }
206 *e = LSPEvent(aux.Alias)
207 if aux.Error != "" {
208 e.Error = errors.New(aux.Error)
209 }
210 return nil
211}
212
213// LSPClientInfo holds information about an LSP client's state.
214type LSPClientInfo struct {
215 Name string `json:"name"`
216 State lsp.ServerState `json:"state"`
217 Error error `json:"error,omitempty"`
218 DiagnosticCount int `json:"diagnostic_count,omitempty"`
219 ConnectedAt time.Time `json:"connected_at"`
220}
221
222// MarshalJSON implements the [json.Marshaler] interface.
223func (i LSPClientInfo) MarshalJSON() ([]byte, error) {
224 type Alias LSPClientInfo
225 return json.Marshal(&struct {
226 Error string `json:"error,omitempty"`
227 Alias
228 }{
229 Error: func() string {
230 if i.Error != nil {
231 return i.Error.Error()
232 }
233 return ""
234 }(),
235 Alias: Alias(i),
236 })
237}
238
239// UnmarshalJSON implements the [json.Unmarshaler] interface.
240func (i *LSPClientInfo) UnmarshalJSON(data []byte) error {
241 type Alias LSPClientInfo
242 aux := &struct {
243 Error string `json:"error,omitempty"`
244 Alias
245 }{
246 Alias: Alias(*i),
247 }
248 if err := json.Unmarshal(data, &aux); err != nil {
249 return err
250 }
251 *i = LSPClientInfo(aux.Alias)
252 if aux.Error != "" {
253 i.Error = errors.New(aux.Error)
254 }
255 return nil
256}