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/config"
 17	"github.com/charmbracelet/crush/internal/history"
 18	"github.com/charmbracelet/crush/internal/message"
 19	"github.com/charmbracelet/crush/internal/proto"
 20	"github.com/charmbracelet/crush/internal/pubsub"
 21	"github.com/charmbracelet/crush/internal/session"
 22	"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
 23)
 24
 25func (c *Client) SubscribeEvents(ctx context.Context) (<-chan any, error) {
 26	events := make(chan any, 100)
 27	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/events", c.id), nil)
 28	if err != nil {
 29		return nil, fmt.Errorf("failed to create request: %w", err)
 30	}
 31
 32	r.Header.Set("Accept", "text/event-stream")
 33	r.Header.Set("Cache-Control", "no-cache")
 34	r.Header.Set("Connection", "keep-alive")
 35
 36	go func() {
 37		rsp, err := c.h.Do(r)
 38		if err != nil {
 39			slog.Error("subscribing to events", "error", err)
 40			return
 41		}
 42
 43		defer rsp.Body.Close()
 44
 45		if rsp.StatusCode != http.StatusOK {
 46			slog.Error("subscribing to events", "status_code", rsp.StatusCode)
 47			return
 48		}
 49
 50		scr := bufio.NewReader(rsp.Body)
 51		for {
 52			line, err := scr.ReadBytes('\n')
 53			if errors.Is(err, io.EOF) {
 54				break
 55			}
 56			if err != nil {
 57				slog.Error("reading from events stream", "error", err)
 58				time.Sleep(time.Second * 2)
 59				continue
 60			}
 61			line = bytes.TrimSpace(line)
 62			if len(line) == 0 {
 63				// End of an event
 64				continue
 65			}
 66
 67			data, ok := bytes.CutPrefix(line, []byte("data:"))
 68			if !ok {
 69				slog.Warn("invalid event format", "line", string(line))
 70				continue
 71			}
 72
 73			data = bytes.TrimSpace(data)
 74
 75			var event pubsub.Event[any]
 76			if err := json.Unmarshal(data, &event); err != nil {
 77				slog.Error("unmarshaling event", "error", err)
 78				continue
 79			}
 80
 81			type alias pubsub.Event[any]
 82			aux := &struct {
 83				Payload json.RawMessage `json:"payload"`
 84				*alias
 85			}{
 86				alias: (*alias)(&event),
 87			}
 88
 89			if err := json.Unmarshal(data, &aux); err != nil {
 90				slog.Error("unmarshaling event payload", "error", err)
 91				continue
 92			}
 93
 94			var p pubsub.Payload
 95			if err := json.Unmarshal(aux.Payload, &p); err != nil {
 96				slog.Error("unmarshaling event payload", "error", err)
 97				continue
 98			}
 99
100			switch p.Type {
101			case pubsub.PayloadTypeLSPEvent:
102				var e pubsub.Event[proto.LSPEvent]
103				_ = json.Unmarshal(data, &e)
104				sendEvent(ctx, events, e)
105			case pubsub.PayloadTypeMCPEvent:
106				var e pubsub.Event[proto.MCPEvent]
107				_ = json.Unmarshal(data, &e)
108				sendEvent(ctx, events, e)
109			case pubsub.PayloadTypePermissionRequest:
110				var e pubsub.Event[proto.PermissionRequest]
111				_ = json.Unmarshal(data, &e)
112				sendEvent(ctx, events, e)
113			case pubsub.PayloadTypePermissionNotification:
114				var e pubsub.Event[proto.PermissionNotification]
115				_ = json.Unmarshal(data, &e)
116				sendEvent(ctx, events, e)
117			case pubsub.PayloadTypeMessage:
118				var e pubsub.Event[proto.Message]
119				_ = json.Unmarshal(data, &e)
120				sendEvent(ctx, events, e)
121			case pubsub.PayloadTypeSession:
122				var e pubsub.Event[proto.Session]
123				_ = json.Unmarshal(data, &e)
124				sendEvent(ctx, events, e)
125			case pubsub.PayloadTypeFile:
126				var e pubsub.Event[proto.File]
127				_ = json.Unmarshal(data, &e)
128				sendEvent(ctx, events, e)
129			case pubsub.PayloadTypeAgentEvent:
130				var e pubsub.Event[proto.AgentEvent]
131				_ = json.Unmarshal(data, &e)
132				sendEvent(ctx, events, e)
133			default:
134				slog.Warn("unknown event type", "type", p.Type)
135				continue
136			}
137		}
138	}()
139
140	return events, nil
141}
142
143func sendEvent(ctx context.Context, evc chan any, ev any) {
144	slog.Info("event received", "event", fmt.Sprintf("%T %+v", ev, ev))
145	select {
146	case evc <- ev:
147	case <-ctx.Done():
148		close(evc)
149		return
150	}
151}
152
153func (c *Client) GetLSPDiagnostics(ctx context.Context, lsp string) (map[protocol.DocumentURI][]protocol.Diagnostic, error) {
154	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/lsps/%s/diagnostics", c.id, lsp), nil)
155	if err != nil {
156		return nil, fmt.Errorf("failed to create request: %w", err)
157	}
158	rsp, err := c.h.Do(r)
159	if err != nil {
160		return nil, fmt.Errorf("failed to get LSP diagnostics: %w", err)
161	}
162	defer rsp.Body.Close()
163	if rsp.StatusCode != http.StatusOK {
164		return nil, fmt.Errorf("failed to get LSP diagnostics: status code %d", rsp.StatusCode)
165	}
166	var diagnostics map[protocol.DocumentURI][]protocol.Diagnostic
167	if err := json.NewDecoder(rsp.Body).Decode(&diagnostics); err != nil {
168		return nil, fmt.Errorf("failed to decode LSP diagnostics: %w", err)
169	}
170	return diagnostics, nil
171}
172
173func (c *Client) GetLSPs(ctx context.Context) (map[string]app.LSPClientInfo, error) {
174	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/lsps", c.id), nil)
175	if err != nil {
176		return nil, fmt.Errorf("failed to create request: %w", err)
177	}
178	rsp, err := c.h.Do(r)
179	if err != nil {
180		return nil, fmt.Errorf("failed to get LSPs: %w", err)
181	}
182	defer rsp.Body.Close()
183	if rsp.StatusCode != http.StatusOK {
184		return nil, fmt.Errorf("failed to get LSPs: status code %d", rsp.StatusCode)
185	}
186	var lsps map[string]app.LSPClientInfo
187	if err := json.NewDecoder(rsp.Body).Decode(&lsps); err != nil {
188		return nil, fmt.Errorf("failed to decode LSPs: %w", err)
189	}
190	return lsps, nil
191}
192
193func (c *Client) GetAgentSessionQueuedPrompts(ctx context.Context, sessionID string) (int, error) {
194	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/agent/sessions/%s/prompts/queued", c.id, sessionID), nil)
195	if err != nil {
196		return 0, fmt.Errorf("failed to create request: %w", err)
197	}
198	rsp, err := c.h.Do(r)
199	if err != nil {
200		return 0, fmt.Errorf("failed to get session agent queued prompts: %w", err)
201	}
202	defer rsp.Body.Close()
203	if rsp.StatusCode != http.StatusOK {
204		return 0, fmt.Errorf("failed to get session agent queued prompts: status code %d", rsp.StatusCode)
205	}
206	var count int
207	if err := json.NewDecoder(rsp.Body).Decode(&count); err != nil {
208		return 0, fmt.Errorf("failed to decode session agent queued prompts: %w", err)
209	}
210	return count, nil
211}
212
213func (c *Client) ClearAgentSessionQueuedPrompts(ctx context.Context, sessionID string) error {
214	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/sessions/%s/prompts/clear", c.id, sessionID), nil)
215	if err != nil {
216		return fmt.Errorf("failed to create request: %w", err)
217	}
218	rsp, err := c.h.Do(r)
219	if err != nil {
220		return fmt.Errorf("failed to clear session agent queued prompts: %w", err)
221	}
222	defer rsp.Body.Close()
223	if rsp.StatusCode != http.StatusOK {
224		return fmt.Errorf("failed to clear session agent queued prompts: status code %d", rsp.StatusCode)
225	}
226	return nil
227}
228
229func (c *Client) GetAgentInfo(ctx context.Context) (*proto.AgentInfo, error) {
230	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/agent", c.id), nil)
231	if err != nil {
232		return nil, fmt.Errorf("failed to create request: %w", err)
233	}
234	rsp, err := c.h.Do(r)
235	if err != nil {
236		return nil, fmt.Errorf("failed to get agent status: %w", err)
237	}
238	defer rsp.Body.Close()
239	if rsp.StatusCode != http.StatusOK {
240		return nil, fmt.Errorf("failed to get agent status: status code %d", rsp.StatusCode)
241	}
242	var info proto.AgentInfo
243	if err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {
244		return nil, fmt.Errorf("failed to decode agent status: %w", err)
245	}
246	return &info, nil
247}
248
249func (c *Client) UpdateAgent(ctx context.Context) error {
250	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/update", c.id), nil)
251	if err != nil {
252		return fmt.Errorf("failed to create request: %w", err)
253	}
254	rsp, err := c.h.Do(r)
255	if err != nil {
256		return fmt.Errorf("failed to update agent: %w", err)
257	}
258	defer rsp.Body.Close()
259	if rsp.StatusCode != http.StatusOK {
260		return fmt.Errorf("failed to update agent: status code %d", rsp.StatusCode)
261	}
262	return nil
263}
264
265func (c *Client) SendMessage(ctx context.Context, sessionID, message string, attchments ...message.Attachment) error {
266	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent", c.id), jsonBody(proto.AgentMessage{
267		SessionID:   sessionID,
268		Prompt:      message,
269		Attachments: attchments,
270	}))
271	if err != nil {
272		return fmt.Errorf("failed to create request: %w", err)
273	}
274	rsp, err := c.h.Do(r)
275	if err != nil {
276		return fmt.Errorf("failed to send message to agent: %w", err)
277	}
278	defer rsp.Body.Close()
279	if rsp.StatusCode != http.StatusOK {
280		return fmt.Errorf("failed to send message to agent: status code %d", rsp.StatusCode)
281	}
282	return nil
283}
284
285func (c *Client) GetAgentSessionInfo(ctx context.Context, sessionID string) (*proto.AgentSession, error) {
286	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/agent/sessions/%s", c.id, sessionID), nil)
287	if err != nil {
288		return nil, fmt.Errorf("failed to create request: %w", err)
289	}
290	rsp, err := c.h.Do(r)
291	if err != nil {
292		return nil, fmt.Errorf("failed to get session agent info: %w", err)
293	}
294	defer rsp.Body.Close()
295	if rsp.StatusCode != http.StatusOK {
296		return nil, fmt.Errorf("failed to get session agent info: status code %d", rsp.StatusCode)
297	}
298	var info proto.AgentSession
299	if err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {
300		return nil, fmt.Errorf("failed to decode session agent info: %w", err)
301	}
302	return &info, nil
303}
304
305func (c *Client) AgentSummarizeSession(ctx context.Context, sessionID string) error {
306	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/sessions/%s/summarize", c.id, sessionID), nil)
307	if err != nil {
308		return fmt.Errorf("failed to create request: %w", err)
309	}
310	rsp, err := c.h.Do(r)
311	if err != nil {
312		return fmt.Errorf("failed to summarize session: %w", err)
313	}
314	defer rsp.Body.Close()
315	if rsp.StatusCode != http.StatusOK {
316		return fmt.Errorf("failed to summarize session: status code %d", rsp.StatusCode)
317	}
318	return nil
319}
320
321func (c *Client) ListMessages(ctx context.Context, sessionID string) ([]message.Message, error) {
322	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions/%s/messages", c.id, sessionID), nil)
323	if err != nil {
324		return nil, fmt.Errorf("failed to create request: %w", err)
325	}
326	rsp, err := c.h.Do(r)
327	if err != nil {
328		return nil, fmt.Errorf("failed to get messages: %w", err)
329	}
330	defer rsp.Body.Close()
331	if rsp.StatusCode != http.StatusOK {
332		return nil, fmt.Errorf("failed to get messages: status code %d", rsp.StatusCode)
333	}
334	var messages []message.Message
335	if err := json.NewDecoder(rsp.Body).Decode(&messages); err != nil {
336		return nil, fmt.Errorf("failed to decode messages: %w", err)
337	}
338	return messages, nil
339}
340
341func (c *Client) GetSession(ctx context.Context, sessionID string) (*session.Session, error) {
342	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions/%s", c.id, sessionID), nil)
343	if err != nil {
344		return nil, fmt.Errorf("failed to create request: %w", err)
345	}
346	rsp, err := c.h.Do(r)
347	if err != nil {
348		return nil, fmt.Errorf("failed to get session: %w", err)
349	}
350	defer rsp.Body.Close()
351	if rsp.StatusCode != http.StatusOK {
352		return nil, fmt.Errorf("failed to get session: status code %d", rsp.StatusCode)
353	}
354	var sess session.Session
355	if err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {
356		return nil, fmt.Errorf("failed to decode session: %w", err)
357	}
358	return &sess, nil
359}
360
361func (c *Client) InitiateAgentProcessing(ctx context.Context) error {
362	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/agent/init", c.id), nil)
363	if err != nil {
364		return fmt.Errorf("failed to create request: %w", err)
365	}
366	rsp, err := c.h.Do(r)
367	if err != nil {
368		return fmt.Errorf("failed to initiate session agent processing: %w", err)
369	}
370	defer rsp.Body.Close()
371	if rsp.StatusCode != http.StatusOK {
372		return fmt.Errorf("failed to initiate session agent processing: status code %d", rsp.StatusCode)
373	}
374	return nil
375}
376
377func (c *Client) ListSessionHistoryFiles(ctx context.Context, sessionID string) ([]history.File, error) {
378	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions/%s/history", c.id, sessionID), nil)
379	if err != nil {
380		return nil, fmt.Errorf("failed to create request: %w", err)
381	}
382	rsp, err := c.h.Do(r)
383	if err != nil {
384		return nil, fmt.Errorf("failed to get session history files: %w", err)
385	}
386	defer rsp.Body.Close()
387	if rsp.StatusCode != http.StatusOK {
388		return nil, fmt.Errorf("failed to get session history files: status code %d", rsp.StatusCode)
389	}
390	var files []history.File
391	if err := json.NewDecoder(rsp.Body).Decode(&files); err != nil {
392		return nil, fmt.Errorf("failed to decode session history files: %w", err)
393	}
394	return files, nil
395}
396
397func (c *Client) CreateSession(ctx context.Context, title string) (*session.Session, error) {
398	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/sessions", c.id), jsonBody(session.Session{Title: title}))
399	if err != nil {
400		return nil, fmt.Errorf("failed to create request: %w", err)
401	}
402	r.Header.Set("Content-Type", "application/json")
403	rsp, err := c.h.Do(r)
404	if err != nil {
405		return nil, fmt.Errorf("failed to create session: %w", err)
406	}
407	defer rsp.Body.Close()
408	if rsp.StatusCode != http.StatusOK {
409		return nil, fmt.Errorf("failed to create session: status code %d", rsp.StatusCode)
410	}
411	var sess session.Session
412	if err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {
413		return nil, fmt.Errorf("failed to decode session: %w", err)
414	}
415	return &sess, nil
416}
417
418func (c *Client) ListSessions(ctx context.Context) ([]session.Session, error) {
419	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/sessions", c.id), nil)
420	if err != nil {
421		return nil, fmt.Errorf("failed to create request: %w", err)
422	}
423	rsp, err := c.h.Do(r)
424	if err != nil {
425		return nil, fmt.Errorf("failed to get sessions: %w", err)
426	}
427	defer rsp.Body.Close()
428	if rsp.StatusCode != http.StatusOK {
429		return nil, fmt.Errorf("failed to get sessions: status code %d", rsp.StatusCode)
430	}
431	var sessions []session.Session
432	if err := json.NewDecoder(rsp.Body).Decode(&sessions); err != nil {
433		return nil, fmt.Errorf("failed to decode sessions: %w", err)
434	}
435	return sessions, nil
436}
437
438func (c *Client) GrantPermission(ctx context.Context, req proto.PermissionGrant) error {
439	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/permissions/grant", c.id), jsonBody(req))
440	if err != nil {
441		return fmt.Errorf("failed to create request: %w", err)
442	}
443	r.Header.Set("Content-Type", "application/json")
444	rsp, err := c.h.Do(r)
445	if err != nil {
446		return fmt.Errorf("failed to grant permission: %w", err)
447	}
448	defer rsp.Body.Close()
449	if rsp.StatusCode != http.StatusOK {
450		return fmt.Errorf("failed to grant permission: status code %d", rsp.StatusCode)
451	}
452	return nil
453}
454
455func (c *Client) SetPermissionsSkipRequests(ctx context.Context, skip bool) error {
456	r, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost/v1/instances/%s/permissions/skip", c.id), jsonBody(proto.PermissionSkipRequest{Skip: skip}))
457	if err != nil {
458		return fmt.Errorf("failed to create request: %w", err)
459	}
460	r.Header.Set("Content-Type", "application/json")
461	rsp, err := c.h.Do(r)
462	if err != nil {
463		return fmt.Errorf("failed to set permissions skip requests: %w", err)
464	}
465	defer rsp.Body.Close()
466	if rsp.StatusCode != http.StatusOK {
467		return fmt.Errorf("failed to set permissions skip requests: status code %d", rsp.StatusCode)
468	}
469	return nil
470}
471
472func (c *Client) GetPermissionsSkipRequests(ctx context.Context) (bool, error) {
473	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/permissions/skip", c.id), nil)
474	if err != nil {
475		return false, fmt.Errorf("failed to create request: %w", err)
476	}
477	rsp, err := c.h.Do(r)
478	if err != nil {
479		return false, fmt.Errorf("failed to get permissions skip requests: %w", err)
480	}
481	defer rsp.Body.Close()
482	if rsp.StatusCode != http.StatusOK {
483		return false, fmt.Errorf("failed to get permissions skip requests: status code %d", rsp.StatusCode)
484	}
485	var skip proto.PermissionSkipRequest
486	if err := json.NewDecoder(rsp.Body).Decode(&skip); err != nil {
487		return false, fmt.Errorf("failed to decode permissions skip requests: %w", err)
488	}
489	return skip.Skip, nil
490}
491
492func (c *Client) GetConfig(ctx context.Context) (*config.Config, error) {
493	r, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost/v1/instances/%s/config", c.id), nil)
494	if err != nil {
495		return nil, fmt.Errorf("failed to create request: %w", err)
496	}
497	rsp, err := c.h.Do(r)
498	if err != nil {
499		return nil, fmt.Errorf("failed to get config: %w", err)
500	}
501	defer rsp.Body.Close()
502	if rsp.StatusCode != http.StatusOK {
503		return nil, fmt.Errorf("failed to get config: status code %d", rsp.StatusCode)
504	}
505	var cfg config.Config
506	if err := json.NewDecoder(rsp.Body).Decode(&cfg); err != nil {
507		return nil, fmt.Errorf("failed to decode config: %w", err)
508	}
509	return &cfg, nil
510}
511
512func (c *Client) CreateInstance(ctx context.Context, ins proto.Instance) (*proto.Instance, error) {
513	r, err := http.NewRequestWithContext(ctx, "POST", "http://localhost/v1/instances", jsonBody(ins))
514	if err != nil {
515		return nil, fmt.Errorf("failed to create request: %w", err)
516	}
517
518	r.Header.Set("Content-Type", "application/json")
519	rsp, err := c.h.Do(r)
520	if err != nil {
521		return nil, fmt.Errorf("failed to create instance: %w", err)
522	}
523	defer rsp.Body.Close()
524	if rsp.StatusCode != http.StatusOK {
525		return nil, fmt.Errorf("failed to create instance: status code %d", rsp.StatusCode)
526	}
527	var created proto.Instance
528	if err := json.NewDecoder(rsp.Body).Decode(&created); err != nil {
529		return nil, fmt.Errorf("failed to decode instance: %w", err)
530	}
531	return &created, nil
532}
533
534func (c *Client) DeleteInstance(ctx context.Context, id string) error {
535	r, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("http://localhost/v1/instances/%s", id), nil)
536	if err != nil {
537		return fmt.Errorf("failed to create request: %w", err)
538	}
539	rsp, err := c.h.Do(r)
540	if err != nil {
541		return fmt.Errorf("failed to delete instance: %w", err)
542	}
543	defer rsp.Body.Close()
544	if rsp.StatusCode != http.StatusOK {
545		return fmt.Errorf("failed to delete instance: status code %d", rsp.StatusCode)
546	}
547	return nil
548}
549
550func (c *Client) DeleteInstances(ctx context.Context, ids []string) error {
551	r, err := http.NewRequestWithContext(ctx, "DELETE", "http://localhost/v1/instances", jsonBody(ids))
552	if err != nil {
553		return fmt.Errorf("failed to create request: %w", err)
554	}
555	r.Header.Set("Content-Type", "application/json")
556	rsp, err := c.h.Do(r)
557	if err != nil {
558		return fmt.Errorf("failed to delete instances: %w", err)
559	}
560	defer rsp.Body.Close()
561	if rsp.StatusCode != http.StatusOK {
562		return fmt.Errorf("failed to delete instances: status code %d", rsp.StatusCode)
563	}
564	return nil
565}
566
567func jsonBody(v any) *bytes.Buffer {
568	b := new(bytes.Buffer)
569	m, _ := json.Marshal(v)
570	b.Write(m)
571	return b
572}