fix(pico): preserve text when client media parsing fails
- Skip non-inline Pico attachment URLs instead of treating them as invalid inline media - Preserve pico_client text messages when malformed media payloads are received - Add regression coverage for media.create, download attachments, and invalid media payloads
This commit is contained in:
parent
68a5ca8928
commit
475719012d
3 changed files with 122 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue