proto.go

  1package client
  2
  3import (
  4	"bufio"
  5	"bytes"
  6	"context"
  7	"encoding/json"
  8	"errors"
  9	"fmt"
 10	"io"
 11	"log/slog"
 12	"net/http"
 13	"time"
 14
 15	"github.com/charmbracelet/crush/internal/app"
 16	"github.com/charmbracelet/crush/internal/history"
 17	"github.com/charmbracelet/crush/internal/message"
 18	"github.com/charmbracelet/crush/internal/proto"
 19	"github.com/charmbracelet/crush/internal/session"
 20	"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
 21)
 22
 23func (c *Client) SubscribeEvents(ctx context.Context) (<-chan any, error) {
 24	events := make(chan any, 100)
 25	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/events", c.id), nil)
 26	if err != nil {
 27		return nil, fmt.Errorf("failed to create request: %w", err)
 28	}
 29	rsp, err := c.h.Do(r)
 30	if err != nil {
 31		return nil, fmt.Errorf("failed to subscribe to events: %w", err)
 32	}
 33	if rsp.StatusCode != http.StatusOK {
 34		rsp.Body.Close()
 35		return nil, fmt.Errorf("failed to subscribe to events: status code %d", rsp.StatusCode)
 36	}
 37
 38	go func() {
 39		defer rsp.Body.Close()
 40
 41		scr := bufio.NewReader(rsp.Body)
 42		for {
 43			line, err := scr.ReadBytes('\n')
 44			if errors.Is(err, io.EOF) {
 45				break
 46			}
 47			if err != nil {
 48				slog.Error("reading from events stream", "error", err)
 49				time.Sleep(time.Second * 2)
 50				continue
 51			}
 52			line = bytes.TrimSpace(line)
 53			if len(line) == 0 {
 54				// End of an event
 55				continue
 56			}
 57
 58			data, ok := bytes.CutPrefix(line, []byte("data:"))
 59			if !ok {
 60				slog.Warn("invalid event format", "line", string(line))
 61				continue
 62			}
 63
 64			data = bytes.TrimSpace(data)
 65
 66			var event any
 67			if err := json.Unmarshal(data, &event); err != nil {
 68				slog.Error("unmarshaling event", "error", err)
 69				continue
 70			}
 71
 72			select {
 73			case events <- event:
 74			case <-ctx.Done():
 75				close(events)
 76				return
 77			}
 78		}
 79	}()
 80
 81	return events, nil
 82}
 83
 84func (c *Client) GetLSPDiagnostics(ctx context.Context, lsp string) (map[protocol.DocumentURI][]protocol.Diagnostic, error) {
 85	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/lsps/%s/diagnostics", c.id, lsp), nil)
 86	if err != nil {
 87		return nil, fmt.Errorf("failed to create request: %w", err)
 88	}
 89	rsp, err := c.h.Do(r)
 90	if err != nil {
 91		return nil, fmt.Errorf("failed to get LSP diagnostics: %w", err)
 92	}
 93	defer rsp.Body.Close()
 94	if rsp.StatusCode != http.StatusOK {
 95		return nil, fmt.Errorf("failed to get LSP diagnostics: status code %d", rsp.StatusCode)
 96	}
 97	var diagnostics map[protocol.DocumentURI][]protocol.Diagnostic
 98	if err := json.NewDecoder(rsp.Body).Decode(&diagnostics); err != nil {
 99		return nil, fmt.Errorf("failed to decode LSP diagnostics: %w", err)
100	}
101	return diagnostics, nil
102}
103
104func (c *Client) GetLSPs(ctx context.Context) (map[string]app.LSPClientInfo, error) {
105	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/lsps", c.id), nil)
106	if err != nil {
107		return nil, fmt.Errorf("failed to create request: %w", err)
108	}
109	rsp, err := c.h.Do(r)
110	if err != nil {
111		return nil, fmt.Errorf("failed to get LSPs: %w", err)
112	}
113	defer rsp.Body.Close()
114	if rsp.StatusCode != http.StatusOK {
115		return nil, fmt.Errorf("failed to get LSPs: status code %d", rsp.StatusCode)
116	}
117	var lsps map[string]app.LSPClientInfo
118	if err := json.NewDecoder(rsp.Body).Decode(&lsps); err != nil {
119		return nil, fmt.Errorf("failed to decode LSPs: %w", err)
120	}
121	return lsps, nil
122}
123
124func (c *Client) GetAgentSessionQueuedPrompts(ctx context.Context, sessionID string) (int, error) {
125	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/agent/sessions/%s/prompts/queued", c.id, sessionID), nil)
126	if err != nil {
127		return 0, fmt.Errorf("failed to create request: %w", err)
128	}
129	rsp, err := c.h.Do(r)
130	if err != nil {
131		return 0, fmt.Errorf("failed to get session agent queued prompts: %w", err)
132	}
133	defer rsp.Body.Close()
134	if rsp.StatusCode != http.StatusOK {
135		return 0, fmt.Errorf("failed to get session agent queued prompts: status code %d", rsp.StatusCode)
136	}
137	var count int
138	if err := json.NewDecoder(rsp.Body).Decode(&count); err != nil {
139		return 0, fmt.Errorf("failed to decode session agent queued prompts: %w", err)
140	}
141	return count, nil
142}
143
144func (c *Client) ClearAgentSessionQueuedPrompts(ctx context.Context, sessionID string) error {
145	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/sessions/%s/prompts/clear", c.id, sessionID), nil)
146	if err != nil {
147		return fmt.Errorf("failed to create request: %w", err)
148	}
149	rsp, err := c.h.Do(r)
150	if err != nil {
151		return fmt.Errorf("failed to clear session agent queued prompts: %w", err)
152	}
153	defer rsp.Body.Close()
154	if rsp.StatusCode != http.StatusOK {
155		return fmt.Errorf("failed to clear session agent queued prompts: status code %d", rsp.StatusCode)
156	}
157	return nil
158}
159
160func (c *Client) GetAgentInfo(ctx context.Context) (*proto.AgentInfo, error) {
161	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/agent", c.id), nil)
162	if err != nil {
163		return nil, fmt.Errorf("failed to create request: %w", err)
164	}
165	rsp, err := c.h.Do(r)
166	if err != nil {
167		return nil, fmt.Errorf("failed to get agent status: %w", err)
168	}
169	defer rsp.Body.Close()
170	if rsp.StatusCode != http.StatusOK {
171		return nil, fmt.Errorf("failed to get agent status: status code %d", rsp.StatusCode)
172	}
173	var info proto.AgentInfo
174	if err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {
175		return nil, fmt.Errorf("failed to decode agent status: %w", err)
176	}
177	return &info, nil
178}
179
180func (c *Client) UpdateAgent(ctx context.Context) error {
181	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/update", c.id), nil)
182	if err != nil {
183		return fmt.Errorf("failed to create request: %w", err)
184	}
185	rsp, err := c.h.Do(r)
186	if err != nil {
187		return fmt.Errorf("failed to update agent: %w", err)
188	}
189	defer rsp.Body.Close()
190	if rsp.StatusCode != http.StatusOK {
191		return fmt.Errorf("failed to update agent: status code %d", rsp.StatusCode)
192	}
193	return nil
194}
195
196func (c *Client) SendMessage(ctx context.Context, sessionID, message string, attchments ...message.Attachment) error {
197	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent", c.id), jsonBody(proto.AgentMessage{
198		SessionID:   sessionID,
199		Prompt:      message,
200		Attachments: attchments,
201	}))
202	if err != nil {
203		return fmt.Errorf("failed to create request: %w", err)
204	}
205	rsp, err := c.h.Do(r)
206	if err != nil {
207		return fmt.Errorf("failed to send message to agent: %w", err)
208	}
209	defer rsp.Body.Close()
210	if rsp.StatusCode != http.StatusOK {
211		return fmt.Errorf("failed to send message to agent: status code %d", rsp.StatusCode)
212	}
213	return nil
214}
215
216func (c *Client) AgentSummarizeSession(ctx context.Context, sessionID string) error {
217	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/sessions/%s/summarize", c.id, sessionID), nil)
218	if err != nil {
219		return fmt.Errorf("failed to create request: %w", err)
220	}
221	rsp, err := c.h.Do(r)
222	if err != nil {
223		return fmt.Errorf("failed to summarize session: %w", err)
224	}
225	defer rsp.Body.Close()
226	if rsp.StatusCode != http.StatusOK {
227		return fmt.Errorf("failed to summarize session: status code %d", rsp.StatusCode)
228	}
229	return nil
230}
231
232func (c *Client) ListMessages(ctx context.Context, sessionID string) ([]message.Message, error) {
233	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions/%s/messages", c.id, sessionID), nil)
234	if err != nil {
235		return nil, fmt.Errorf("failed to create request: %w", err)
236	}
237	rsp, err := c.h.Do(r)
238	if err != nil {
239		return nil, fmt.Errorf("failed to get messages: %w", err)
240	}
241	defer rsp.Body.Close()
242	if rsp.StatusCode != http.StatusOK {
243		return nil, fmt.Errorf("failed to get messages: status code %d", rsp.StatusCode)
244	}
245	var messages []message.Message
246	if err := json.NewDecoder(rsp.Body).Decode(&messages); err != nil {
247		return nil, fmt.Errorf("failed to decode messages: %w", err)
248	}
249	return messages, nil
250}
251
252func (c *Client) GetSession(ctx context.Context, sessionID string) (*session.Session, error) {
253	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions/%s", c.id, sessionID), nil)
254	if err != nil {
255		return nil, fmt.Errorf("failed to create request: %w", err)
256	}
257	rsp, err := c.h.Do(r)
258	if err != nil {
259		return nil, fmt.Errorf("failed to get session: %w", err)
260	}
261	defer rsp.Body.Close()
262	if rsp.StatusCode != http.StatusOK {
263		return nil, fmt.Errorf("failed to get session: status code %d", rsp.StatusCode)
264	}
265	var sess session.Session
266	if err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {
267		return nil, fmt.Errorf("failed to decode session: %w", err)
268	}
269	return &sess, nil
270}
271
272func (c *Client) InitiateAgentProcessing(ctx context.Context) error {
273	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/init", c.id), nil)
274	if err != nil {
275		return fmt.Errorf("failed to create request: %w", err)
276	}
277	rsp, err := c.h.Do(r)
278	if err != nil {
279		return fmt.Errorf("failed to initiate session agent processing: %w", err)
280	}
281	defer rsp.Body.Close()
282	if rsp.StatusCode != http.StatusOK {
283		return fmt.Errorf("failed to initiate session agent processing: status code %d", rsp.StatusCode)
284	}
285	return nil
286}
287
288func (c *Client) ListSessionHistoryFiles(ctx context.Context, sessionID string) ([]history.File, error) {
289	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions/%s/history", c.id, sessionID), nil)
290	if err != nil {
291		return nil, fmt.Errorf("failed to create request: %w", err)
292	}
293	rsp, err := c.h.Do(r)
294	if err != nil {
295		return nil, fmt.Errorf("failed to get session history files: %w", err)
296	}
297	defer rsp.Body.Close()
298	if rsp.StatusCode != http.StatusOK {
299		return nil, fmt.Errorf("failed to get session history files: status code %d", rsp.StatusCode)
300	}
301	var files []history.File
302	if err := json.NewDecoder(rsp.Body).Decode(&files); err != nil {
303		return nil, fmt.Errorf("failed to decode session history files: %w", err)
304	}
305	return files, nil
306}
307
308func (c *Client) CreateSession(ctx context.Context, title string) (*session.Session, error) {
309	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/sessions", c.id), jsonBody(session.Session{Title: title}))
310	if err != nil {
311		return nil, fmt.Errorf("failed to create request: %w", err)
312	}
313	r.Header.Set("Content-Type", "application/json")
314	rsp, err := c.h.Do(r)
315	if err != nil {
316		return nil, fmt.Errorf("failed to create session: %w", err)
317	}
318	defer rsp.Body.Close()
319	if rsp.StatusCode != http.StatusOK {
320		return nil, fmt.Errorf("failed to create session: status code %d", rsp.StatusCode)
321	}
322	var sess session.Session
323	if err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {
324		return nil, fmt.Errorf("failed to decode session: %w", err)
325	}
326	return &sess, nil
327}
328
329func (c *Client) ListSessions(ctx context.Context) ([]session.Session, error) {
330	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions", c.id), nil)
331	if err != nil {
332		return nil, fmt.Errorf("failed to create request: %w", err)
333	}
334	rsp, err := c.h.Do(r)
335	if err != nil {
336		return nil, fmt.Errorf("failed to get sessions: %w", err)
337	}
338	defer rsp.Body.Close()
339	if rsp.StatusCode != http.StatusOK {
340		return nil, fmt.Errorf("failed to get sessions: status code %d", rsp.StatusCode)
341	}
342	var sessions []session.Session
343	if err := json.NewDecoder(rsp.Body).Decode(&sessions); err != nil {
344		return nil, fmt.Errorf("failed to decode sessions: %w", err)
345	}
346	return sessions, nil
347}
348
349func (c *Client) CreateInstance(ctx context.Context, ins proto.Instance) (*proto.Instance, error) {
350	r, err := http.NewRequestWithContext(ctx, "POST", "http://localhost/v1/instances", jsonBody(ins))
351	if err != nil {
352		return nil, fmt.Errorf("failed to create request: %w", err)
353	}
354
355	r.Header.Set("Content-Type", "application/json")
356	rsp, err := c.h.Do(r)
357	if err != nil {
358		return nil, fmt.Errorf("failed to create instance: %w", err)
359	}
360	defer rsp.Body.Close()
361	if rsp.StatusCode != http.StatusOK {
362		return nil, fmt.Errorf("failed to create instance: status code %d", rsp.StatusCode)
363	}
364	var created proto.Instance
365	if err := json.NewDecoder(rsp.Body).Decode(&created); err != nil {
366		return nil, fmt.Errorf("failed to decode instance: %w", err)
367	}
368	return &created, nil
369}
370
371func (c *Client) DeleteInstance(ctx context.Context, id string) error {
372	r, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("http://localhost/v1/instances/%s", id), nil)
373	if err != nil {
374		return fmt.Errorf("failed to create request: %w", err)
375	}
376	rsp, err := c.h.Do(r)
377	if err != nil {
378		return fmt.Errorf("failed to delete instance: %w", err)
379	}
380	defer rsp.Body.Close()
381	if rsp.StatusCode != http.StatusOK {
382		return fmt.Errorf("failed to delete instance: status code %d", rsp.StatusCode)
383	}
384	return nil
385}
386
387func (c *Client) DeleteInstances(ctx context.Context, ids []string) error {
388	r, err := http.NewRequestWithContext(ctx, "DELETE", "http://localhost/v1/instances", jsonBody(ids))
389	if err != nil {
390		return fmt.Errorf("failed to create request: %w", err)
391	}
392	r.Header.Set("Content-Type", "application/json")
393	rsp, err := c.h.Do(r)
394	if err != nil {
395		return fmt.Errorf("failed to delete instances: %w", err)
396	}
397	defer rsp.Body.Close()
398	if rsp.StatusCode != http.StatusOK {
399		return fmt.Errorf("failed to delete instances: status code %d", rsp.StatusCode)
400	}
401	return nil
402}
403
404func jsonBody(v any) *bytes.Buffer {
405	b := new(bytes.Buffer)
406	m, _ := json.Marshal(v)
407	b.Write(m)
408	return b
409}