203 lines
5.7 KiB
Go
203 lines
5.7 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/media"
|
|
)
|
|
|
|
type fakeImageGenerationProvider struct {
|
|
id string
|
|
defaultModel string
|
|
request imageGenerationRequest
|
|
}
|
|
|
|
func (p *fakeImageGenerationProvider) ID() string { return p.id }
|
|
|
|
func (p *fakeImageGenerationProvider) DefaultModel() string { return p.defaultModel }
|
|
|
|
func (p *fakeImageGenerationProvider) GenerateImages(
|
|
_ context.Context,
|
|
req imageGenerationRequest,
|
|
) ([]generatedImage, error) {
|
|
p.request = req
|
|
return []generatedImage{{
|
|
Data: []byte("fake-image"),
|
|
MimeType: "image/png",
|
|
Ext: "png",
|
|
}}, nil
|
|
}
|
|
|
|
func TestImageGenerateToolCodexOAuthRequestAndMediaResult(t *testing.T) {
|
|
var captured map[string]any
|
|
var gotAuth string
|
|
var gotAccount string
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotAuth = r.Header.Get("Authorization")
|
|
gotAccount = r.Header.Get("Chatgpt-Account-Id")
|
|
if r.URL.Path != "/responses" {
|
|
t.Fatalf("path = %q, want /responses", r.URL.Path)
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&captured); err != nil {
|
|
t.Fatalf("decode request: %v", err)
|
|
}
|
|
payload := base64.StdEncoding.EncodeToString([]byte("fake-png"))
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
_, _ = w.Write([]byte(`data: {"type":"response.output_item.done","item":{"type":"image_generation_call","result":"` + payload + `"}}` + "\n\n"))
|
|
_, _ = w.Write([]byte("data: [DONE]\n\n"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
store := media.NewFileMediaStore()
|
|
tool := NewImageGenerateTool(
|
|
t.TempDir(),
|
|
"openai/gpt-image-2",
|
|
store,
|
|
WithImageGenerateBaseURL(server.URL),
|
|
WithImageGenerateTokenSource(func() (string, string, error) {
|
|
return "test-token", "acct-123", nil
|
|
}),
|
|
)
|
|
|
|
result := tool.Execute(
|
|
WithToolContext(t.Context(), "telegram", "chat-1"),
|
|
map[string]any{
|
|
"prompt": "make a tiny icon",
|
|
"size": "1536x1024",
|
|
"quality": "medium",
|
|
"output_format": "png",
|
|
},
|
|
)
|
|
if result.IsError {
|
|
t.Fatalf("Execute returned error: %s", result.ContentForLLM())
|
|
}
|
|
if !result.ResponseHandled {
|
|
t.Fatal("ResponseHandled = false, want true")
|
|
}
|
|
if len(result.Media) != 1 {
|
|
t.Fatalf("media refs = %d, want 1", len(result.Media))
|
|
}
|
|
path, err := store.Resolve(result.Media[0])
|
|
if err != nil {
|
|
t.Fatalf("resolve media: %v", err)
|
|
}
|
|
if gotAuth != "Bearer test-token" {
|
|
t.Fatalf("Authorization = %q, want Bearer test-token", gotAuth)
|
|
}
|
|
if gotAccount != "acct-123" {
|
|
t.Fatalf("Chatgpt-Account-Id = %q, want acct-123", gotAccount)
|
|
}
|
|
if captured["model"] != "gpt-5.4" {
|
|
t.Fatalf("request model = %v, want gpt-5.4", captured["model"])
|
|
}
|
|
toolsRaw := captured["tools"].([]any)
|
|
imageTool := toolsRaw[0].(map[string]any)
|
|
if imageTool["model"] != "gpt-image-2" {
|
|
t.Fatalf("image model = %v, want gpt-image-2", imageTool["model"])
|
|
}
|
|
if imageTool["size"] != "1536x1024" {
|
|
t.Fatalf("size = %v, want 1536x1024", imageTool["size"])
|
|
}
|
|
if imageTool["quality"] != "medium" {
|
|
t.Fatalf("quality = %v, want medium", imageTool["quality"])
|
|
}
|
|
if path == "" {
|
|
t.Fatal("generated media path is empty")
|
|
}
|
|
}
|
|
|
|
func TestImageGenerateToolCanUseInjectedProvider(t *testing.T) {
|
|
store := media.NewFileMediaStore()
|
|
provider := &fakeImageGenerationProvider{
|
|
id: "test-provider",
|
|
defaultModel: "test-default-image-model",
|
|
}
|
|
tool := NewImageGenerateTool(
|
|
t.TempDir(),
|
|
"test-provider/custom-image-model",
|
|
store,
|
|
WithImageGenerationProvider(provider),
|
|
)
|
|
|
|
result := tool.Execute(
|
|
WithToolContext(t.Context(), "telegram", "chat-1"),
|
|
map[string]any{"prompt": "make a tiny icon"},
|
|
)
|
|
if result.IsError {
|
|
t.Fatalf("Execute returned error: %s", result.ContentForLLM())
|
|
}
|
|
if provider.request.Model != "custom-image-model" {
|
|
t.Fatalf("model = %q, want custom-image-model", provider.request.Model)
|
|
}
|
|
if len(result.Media) != 1 {
|
|
t.Fatalf("media refs = %d, want 1", len(result.Media))
|
|
}
|
|
}
|
|
|
|
func TestParseImageGenerationModel(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
model string
|
|
wantProvider string
|
|
wantModel string
|
|
}{
|
|
{
|
|
name: "empty uses default provider and model",
|
|
model: "",
|
|
wantProvider: "openai-codex",
|
|
wantModel: "gpt-image-2",
|
|
},
|
|
{
|
|
name: "bare model uses default provider",
|
|
model: "custom-image-model",
|
|
wantProvider: "openai-codex",
|
|
wantModel: "custom-image-model",
|
|
},
|
|
{
|
|
name: "openai alias routes to codex oauth provider",
|
|
model: "openai/gpt-image-2",
|
|
wantProvider: "openai-codex",
|
|
wantModel: "gpt-image-2",
|
|
},
|
|
{
|
|
name: "future provider prefix is preserved",
|
|
model: "gemini/imagen-4",
|
|
wantProvider: "gemini",
|
|
wantModel: "imagen-4",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := parseImageGenerationModel(tt.model)
|
|
if got.Provider != tt.wantProvider {
|
|
t.Fatalf("provider = %q, want %q", got.Provider, tt.wantProvider)
|
|
}
|
|
if got.Model != tt.wantModel {
|
|
t.Fatalf("model = %q, want %q", got.Model, tt.wantModel)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseCodexImageSSECompletedResponseFallback(t *testing.T) {
|
|
payload := base64.StdEncoding.EncodeToString([]byte("fake-png"))
|
|
body := `data: {"type":"response.completed","response":{"output":[{"type":"image_generation_call","result":"` + payload + `"}]}}` + "\n\n"
|
|
|
|
images, err := parseCodexImageSSE(strings.NewReader(body), "png")
|
|
if err != nil {
|
|
t.Fatalf("parseCodexImageSSE: %v", err)
|
|
}
|
|
if len(images) != 1 {
|
|
t.Fatalf("images = %d, want 1", len(images))
|
|
}
|
|
if string(images[0].Data) != "fake-png" {
|
|
t.Fatalf("image data = %q, want fake-png", string(images[0].Data))
|
|
}
|
|
}
|