1package provider
2
3import "testing"
4
5func TestOpenAICompatToolResultImagesFollowTextToolMessages(t *testing.T) {
6 c := NewOpenAICompat("commandcode", "token", "https://example.test/v1", "").(*openaiClient)
7
8 wire, err := c.buildRequest(Request{
9 Model: "vision-model",
10 Messages: []Message{
11 {Role: RoleUser, Content: []Content{TextBlock{Text: "read both images"}}},
12 {Role: RoleAssistant, Content: []Content{
13 ToolCallBlock{ID: "call-1", Name: "read"},
14 ToolCallBlock{ID: "call-2", Name: "read"},
15 }},
16 {Role: RoleTool, Content: []Content{
17 ToolResultBlock{CallID: "call-1", Content: []Content{
18 ImageBlock{MimeType: "image/png", Data: []byte("first")},
19 }},
20 }},
21 {Role: RoleTool, Content: []Content{
22 ToolResultBlock{CallID: "call-2", Content: []Content{
23 TextBlock{Text: "second image"},
24 ImageBlock{MimeType: "image/jpeg", Data: []byte("second")},
25 }},
26 }},
27 },
28 })
29 if err != nil {
30 t.Fatal(err)
31 }
32
33 wantRoles := []string{"user", "assistant", "tool", "tool", "user"}
34 if len(wire.Messages) != len(wantRoles) {
35 t.Fatalf("message count = %d, want %d: %+v", len(wire.Messages), len(wantRoles), wire.Messages)
36 }
37 for i, want := range wantRoles {
38 if got := wire.Messages[i].Role; got != want {
39 t.Fatalf("message %d role = %q, want %q", i, got, want)
40 }
41 }
42 if got, ok := wire.Messages[2].Content.(string); !ok || got != "(see attached image)" {
43 t.Fatalf("first tool content = %#v, want text placeholder", wire.Messages[2].Content)
44 }
45 if got, ok := wire.Messages[3].Content.(string); !ok || got != "second image" {
46 t.Fatalf("second tool content = %#v, want text", wire.Messages[3].Content)
47 }
48
49 parts, ok := wire.Messages[4].Content.([]interface{})
50 if !ok {
51 t.Fatalf("image message content type = %T, want []interface{}", wire.Messages[4].Content)
52 }
53 if len(parts) != 3 {
54 t.Fatalf("image message parts = %d, want prefix and two images", len(parts))
55 }
56 if text, ok := parts[0].(oaiContentText); !ok || text.Text != "Tool output included the following image content:" {
57 t.Fatalf("image message prefix = %#v", parts[0])
58 }
59 for i, part := range parts[1:] {
60 image, ok := part.(oaiContentImage)
61 if !ok || image.Type != "image_url" {
62 t.Fatalf("image part %d = %#v", i, part)
63 }
64 }
65}
66
67func TestOpenAICompatDoesNotDuplicatePersistedToolImageMirror(t *testing.T) {
68 c := NewOpenAICompat("custom", "token", "https://example.test/v1", "").(*openaiClient)
69 image := ImageBlock{MimeType: "image/png", Data: []byte("image")}
70
71 wire, err := c.buildRequest(Request{
72 Model: "vision-model",
73 Messages: []Message{
74 {Role: RoleAssistant, Content: []Content{
75 ToolCallBlock{ID: "call-1", Name: "read"},
76 }},
77 {Role: RoleTool, Content: []Content{
78 ToolResultBlock{CallID: "call-1", Content: []Content{image}},
79 }},
80 {Role: RoleUser, Content: []Content{
81 TextBlock{Text: "Tool output included the following image content:"},
82 image,
83 }},
84 },
85 })
86 if err != nil {
87 t.Fatal(err)
88 }
89
90 wantRoles := []string{"assistant", "tool", "user"}
91 if len(wire.Messages) != len(wantRoles) {
92 t.Fatalf("message count = %d, want %d: %+v", len(wire.Messages), len(wantRoles), wire.Messages)
93 }
94 for i, want := range wantRoles {
95 if got := wire.Messages[i].Role; got != want {
96 t.Fatalf("message %d role = %q, want %q", i, got, want)
97 }
98 }
99}