fix(server): support attachments in client-server mode

Christian Rocha created

Change summary

internal/backend/agent.go |  2 
internal/client/proto.go  | 11 ---------
internal/proto/message.go | 42 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 44 insertions(+), 11 deletions(-)

Detailed changes

internal/backend/agent.go 🔗

@@ -19,7 +19,7 @@ func (b *Backend) SendMessage(ctx context.Context, workspaceID string, msg proto
 		return ErrAgentNotInitialized
 	}
 
-	_, err = ws.AgentCoordinator.Run(ctx, msg.SessionID, msg.Prompt)
+	_, err = ws.AgentCoordinator.Run(ctx, msg.SessionID, msg.Prompt, proto.AttachmentsToMessage(msg.Attachments)...)
 	return err
 }
 

internal/client/proto.go 🔗

@@ -335,19 +335,10 @@ func (c *Client) UpdateAgent(ctx context.Context, id string) error {
 
 // SendMessage sends a message to the agent for a workspace.
 func (c *Client) SendMessage(ctx context.Context, id string, sessionID, prompt string, attachments ...message.Attachment) error {
-	protoAttachments := make([]proto.Attachment, len(attachments))
-	for i, a := range attachments {
-		protoAttachments[i] = proto.Attachment{
-			FilePath: a.FilePath,
-			FileName: a.FileName,
-			MimeType: a.MimeType,
-			Content:  a.Content,
-		}
-	}
 	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/agent", id), nil, jsonBody(proto.AgentMessage{
 		SessionID:   sessionID,
 		Prompt:      prompt,
-		Attachments: protoAttachments,
+		Attachments: proto.AttachmentsFromMessage(attachments),
 	}), http.Header{"Content-Type": []string{"application/json"}})
 	if err != nil {
 		return fmt.Errorf("failed to send message to agent: %w", err)

internal/proto/message.go 🔗

@@ -8,6 +8,7 @@ import (
 	"time"
 
 	"charm.land/catwalk/pkg/catwalk"
+	"github.com/charmbracelet/crush/internal/message"
 )
 
 // CreateMessageParams represents parameters for creating a message.
@@ -621,6 +622,47 @@ type Attachment struct {
 	Content  []byte `json:"content"`
 }
 
+// ToMessage converts a proto Attachment to a [message.Attachment].
+func (a Attachment) ToMessage() message.Attachment {
+	return message.Attachment{
+		FilePath: a.FilePath,
+		FileName: a.FileName,
+		MimeType: a.MimeType,
+		Content:  a.Content,
+	}
+}
+
+// AttachmentFromMessage converts a [message.Attachment] to a proto
+// Attachment.
+func AttachmentFromMessage(a message.Attachment) Attachment {
+	return Attachment{
+		FilePath: a.FilePath,
+		FileName: a.FileName,
+		MimeType: a.MimeType,
+		Content:  a.Content,
+	}
+}
+
+// AttachmentsToMessage converts a slice of proto Attachments to a slice
+// of [message.Attachment].
+func AttachmentsToMessage(as []Attachment) []message.Attachment {
+	out := make([]message.Attachment, len(as))
+	for i, a := range as {
+		out[i] = a.ToMessage()
+	}
+	return out
+}
+
+// AttachmentsFromMessage converts a slice of [message.Attachment] to a
+// slice of proto Attachments.
+func AttachmentsFromMessage(as []message.Attachment) []Attachment {
+	out := make([]Attachment, len(as))
+	for i, a := range as {
+		out[i] = AttachmentFromMessage(a)
+	}
+	return out
+}
+
 // MarshalJSON implements the [json.Marshaler] interface.
 func (a Attachment) MarshalJSON() ([]byte, error) {
 	type Alias Attachment