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