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// AgentInfo represents information about the agent.
40type AgentInfo struct {
41 IsBusy bool `json:"is_busy"`
42 IsReady bool `json:"is_ready"`
43 Model catwalk.Model `json:"model"`
44 ModelCfg config.SelectedModel `json:"model_cfg"`
45}
46
47// IsZero checks if the AgentInfo is zero-valued.
48func (a AgentInfo) IsZero() bool {
49 return !a.IsBusy && !a.IsReady && a.Model.ID == ""
50}
51
52// AgentMessage represents a message sent to the agent.
53type AgentMessage struct {
54 SessionID string `json:"session_id"`
55 Prompt string `json:"prompt"`
56 Attachments []Attachment `json:"attachments,omitempty"`
57}
58
59// AgentSession represents a session with its busy status.
60type AgentSession struct {
61 Session
62 IsBusy bool `json:"is_busy"`
63}
64
65// IsZero checks if the AgentSession is zero-valued.
66func (a AgentSession) IsZero() bool {
67 return a.ID == "" && !a.IsBusy
68}
69
70// PermissionAction represents an action taken on a permission request.
71type PermissionAction string
72
73const (
74 PermissionAllow PermissionAction = "allow"
75 PermissionAllowForSession PermissionAction = "allow_session"
76 PermissionDeny PermissionAction = "deny"
77)
78
79// MarshalText implements the [encoding.TextMarshaler] interface.
80func (p PermissionAction) MarshalText() ([]byte, error) {
81 return []byte(p), nil
82}
83
84// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
85func (p *PermissionAction) UnmarshalText(text []byte) error {
86 *p = PermissionAction(text)
87 return nil
88}
89
90// PermissionGrant represents a permission grant request.
91type PermissionGrant struct {
92 Permission PermissionRequest `json:"permission"`
93 Action PermissionAction `json:"action"`
94}
95
96// PermissionGrantResponse is the server's response to a permission
97// grant call. Resolved is true when this call resolved the pending
98// request, and false when the request had already been resolved by a
99// previous caller (e.g., another client in a multi-subscriber UI). A
100// false value is not an error.
101type PermissionGrantResponse struct {
102 Resolved bool `json:"resolved"`
103}
104
105// PermissionSkipRequest represents a request to skip permission prompts.
106type PermissionSkipRequest struct {
107 Skip bool `json:"skip"`
108}
109
110// LSPEventType represents the type of LSP event.
111type LSPEventType string
112
113const (
114 LSPEventStateChanged LSPEventType = "state_changed"
115 LSPEventDiagnosticsChanged LSPEventType = "diagnostics_changed"
116)
117
118// MarshalText implements the [encoding.TextMarshaler] interface.
119func (e LSPEventType) MarshalText() ([]byte, error) {
120 return []byte(e), nil
121}
122
123// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
124func (e *LSPEventType) UnmarshalText(data []byte) error {
125 *e = LSPEventType(data)
126 return nil
127}
128
129// LSPEvent represents an event in the LSP system.
130type LSPEvent struct {
131 Type LSPEventType `json:"type"`
132 Name string `json:"name"`
133 State lsp.ServerState `json:"state"`
134 Error error `json:"error,omitempty"`
135 DiagnosticCount int `json:"diagnostic_count,omitempty"`
136}
137
138// MarshalJSON implements the [json.Marshaler] interface.
139func (e LSPEvent) MarshalJSON() ([]byte, error) {
140 type Alias LSPEvent
141 return json.Marshal(&struct {
142 Error string `json:"error,omitempty"`
143 Alias
144 }{
145 Error: func() string {
146 if e.Error != nil {
147 return e.Error.Error()
148 }
149 return ""
150 }(),
151 Alias: Alias(e),
152 })
153}
154
155// UnmarshalJSON implements the [json.Unmarshaler] interface.
156func (e *LSPEvent) UnmarshalJSON(data []byte) error {
157 type Alias LSPEvent
158 aux := &struct {
159 Error string `json:"error,omitempty"`
160 Alias
161 }{
162 Alias: Alias(*e),
163 }
164 if err := json.Unmarshal(data, &aux); err != nil {
165 return err
166 }
167 *e = LSPEvent(aux.Alias)
168 if aux.Error != "" {
169 e.Error = errors.New(aux.Error)
170 }
171 return nil
172}
173
174// LSPClientInfo holds information about an LSP client's state.
175type LSPClientInfo struct {
176 Name string `json:"name"`
177 State lsp.ServerState `json:"state"`
178 Error error `json:"error,omitempty"`
179 DiagnosticCount int `json:"diagnostic_count,omitempty"`
180 ConnectedAt time.Time `json:"connected_at"`
181}
182
183// MarshalJSON implements the [json.Marshaler] interface.
184func (i LSPClientInfo) MarshalJSON() ([]byte, error) {
185 type Alias LSPClientInfo
186 return json.Marshal(&struct {
187 Error string `json:"error,omitempty"`
188 Alias
189 }{
190 Error: func() string {
191 if i.Error != nil {
192 return i.Error.Error()
193 }
194 return ""
195 }(),
196 Alias: Alias(i),
197 })
198}
199
200// UnmarshalJSON implements the [json.Unmarshaler] interface.
201func (i *LSPClientInfo) UnmarshalJSON(data []byte) error {
202 type Alias LSPClientInfo
203 aux := &struct {
204 Error string `json:"error,omitempty"`
205 Alias
206 }{
207 Alias: Alias(*i),
208 }
209 if err := json.Unmarshal(data, &aux); err != nil {
210 return err
211 }
212 *i = LSPClientInfo(aux.Alias)
213 if aux.Error != "" {
214 i.Error = errors.New(aux.Error)
215 }
216 return nil
217}