diff --git a/pkg/channels/pico/client.go b/pkg/channels/pico/client.go index cfdaf7aa8..04d7323f9 100644 --- a/pkg/channels/pico/client.go +++ b/pkg/channels/pico/client.go @@ -255,7 +255,10 @@ func (c *PicoClientChannel) handleServerMessage(pc *picoConn, msg PicoMessage) { logger.WarnCF("pico_client", "Ignoring invalid media payload", map[string]any{ "error": err.Error(), }) - return + if strings.TrimSpace(content) == "" { + return + } + media = nil } if strings.TrimSpace(content) == "" && len(media) == 0 { return diff --git a/pkg/channels/pico/client_test.go b/pkg/channels/pico/client_test.go index 2f5e8d27c..53e6c93d9 100644 --- a/pkg/channels/pico/client_test.go +++ b/pkg/channels/pico/client_test.go @@ -384,6 +384,121 @@ func TestPicoClientChannel_HandleServerMessage_ForwardsMedia(t *testing.T) { } } +func TestPicoClientChannel_HandleInbound_ForwardsMediaCreate(t *testing.T) { + mb := bus.NewMessageBus() + bc := &config.Channel{Type: config.ChannelPicoClient, Enabled: true} + ch, err := NewPicoClientChannel(bc, &config.PicoClientSettings{ + URL: "ws://localhost:8080/ws", + }, mb) + if err != nil { + t.Fatalf("NewPicoClientChannel() error = %v", err) + } + + ch.ctx = context.Background() + pc := &picoConn{sessionID: "sess-media-create"} + imageURL := "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+X2ioAAAAASUVORK5CYII=" + + ch.handleInbound(pc, PicoMessage{ + Type: TypeMediaCreate, + Payload: map[string]any{ + PayloadKeyContent: "describe media.create", + "attachments": []any{ + map[string]any{ + "type": "image", + "url": imageURL, + }, + }, + }, + }) + + select { + case msg := <-mb.InboundChan(): + if msg.Content != "describe media.create" { + t.Fatalf("msg.Content = %q, want describe media.create", msg.Content) + } + if len(msg.Media) != 1 || msg.Media[0] != imageURL { + t.Fatalf("msg.Media = %#v, want forwarded media.create image payload", msg.Media) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for media.create message") + } +} + +func TestPicoClientChannel_HandleServerMessage_ForwardsTextWithDownloadAttachment(t *testing.T) { + mb := bus.NewMessageBus() + bc := &config.Channel{Type: config.ChannelPicoClient, Enabled: true} + ch, err := NewPicoClientChannel(bc, &config.PicoClientSettings{ + URL: "ws://localhost:8080/ws", + }, mb) + if err != nil { + t.Fatalf("NewPicoClientChannel() error = %v", err) + } + + ch.ctx = context.Background() + pc := &picoConn{sessionID: "sess-download-attachment"} + + ch.handleServerMessage(pc, PicoMessage{ + Type: TypeMessageCreate, + Payload: map[string]any{ + PayloadKeyContent: "see attached", + "attachments": []any{ + map[string]any{ + "type": "image", + "url": "/pico/media/abc", + "filename": "image.png", + "content_type": "image/png", + }, + }, + }, + }) + + select { + case msg := <-mb.InboundChan(): + if msg.Content != "see attached" { + t.Fatalf("msg.Content = %q, want see attached", msg.Content) + } + if len(msg.Media) != 0 { + t.Fatalf("msg.Media = %#v, want no inline media", msg.Media) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for text message with download attachment") + } +} + +func TestPicoClientChannel_HandleServerMessage_ForwardsTextWithInvalidMediaPayload(t *testing.T) { + mb := bus.NewMessageBus() + bc := &config.Channel{Type: config.ChannelPicoClient, Enabled: true} + ch, err := NewPicoClientChannel(bc, &config.PicoClientSettings{ + URL: "ws://localhost:8080/ws", + }, mb) + if err != nil { + t.Fatalf("NewPicoClientChannel() error = %v", err) + } + + ch.ctx = context.Background() + pc := &picoConn{sessionID: "sess-invalid-media"} + + ch.handleServerMessage(pc, PicoMessage{ + Type: TypeMessageCreate, + Payload: map[string]any{ + PayloadKeyContent: "hello despite invalid media", + "attachments": "not-an-array", + }, + }) + + select { + case msg := <-mb.InboundChan(): + if msg.Content != "hello despite invalid media" { + t.Fatalf("msg.Content = %q, want hello despite invalid media", msg.Content) + } + if len(msg.Media) != 0 { + t.Fatalf("msg.Media = %#v, want no inline media", msg.Media) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for text message with invalid media payload") + } +} + func TestIsThoughtPayload(t *testing.T) { tests := []struct { name string diff --git a/pkg/channels/pico/pico.go b/pkg/channels/pico/pico.go index 58f917124..fefb64ee5 100644 --- a/pkg/channels/pico/pico.go +++ b/pkg/channels/pico/pico.go @@ -1073,6 +1073,9 @@ func parseInlineImageAttachments(raw any) ([]string, error) { } continue } + if !strings.HasPrefix(value, "data:") { + continue + } if err := validateInlineImageDataURL(value); err != nil { return nil, fmt.Errorf("attachments[%d]: %w", i, err) }