proto.go

  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}