diff --git a/cmd/cognitive-worker/multimodal.go b/cmd/cognitive-worker/multimodal.go index 7d20de4..a3368ac 100644 --- a/cmd/cognitive-worker/multimodal.go +++ b/cmd/cognitive-worker/multimodal.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "io" "log" "net/http" @@ -38,7 +39,8 @@ func (g multimodalGateway) handler(operation capability.MultimodalOperation) htt http.Error(w, "local image generation capability unavailable", http.StatusBadGateway) return } - image, err := g.imageGenerator.Generate(r.Context(), g.imageModel, r.FormValue("prompt"), r.FormValue("size")) + prompt := r.FormValue("prompt") + image, err := g.imageGenerator.Generate(r.Context(), g.imageModel, prompt, imageRequestSize(prompt, r.FormValue("size"))) if err != nil || strings.TrimSpace(image.DataBase64) == "" { logMultimodalFailure(operation, err) http.Error(w, "local image generation capability unavailable", http.StatusBadGateway) @@ -96,6 +98,40 @@ func (g multimodalGateway) handler(operation capability.MultimodalOperation) htt } } +// imageRequestSize is a transport-level normalization for the local image +// capability. An explicit, valid size always wins. When the caller only +// expresses a conventional output-quality target, the capability translates it +// to a supported provider size without exposing provider workflow details to +// the interaction core. This policy is deliberately local and replaceable +// with the image capability implementation. +func imageRequestSize(prompt, requested string) string { + if isImageSize(requested) { + return strings.TrimSpace(requested) + } + words := strings.Fields(strings.ToLower(prompt)) + for index, word := range words { + switch word { + case "4k", "uhd": + return "2048x2048" + case "hd": + return "1024x1024" + case "ultra": + if index+1 < len(words) && words[index+1] == "hd" { + return "2048x2048" + } + } + } + return "" +} + +func isImageSize(value string) bool { + var width, height int + if _, err := fmt.Sscanf(strings.TrimSpace(value), "%dx%d", &width, &height); err != nil { + return false + } + return width >= 256 && height >= 256 && width <= 2048 && height <= 2048 +} + func logMultimodalFailure(operation capability.MultimodalOperation, err error) { // Keep provider details in server diagnostics; never return them to chat. log.Printf("multimodal operation %s failed: %v", operation, err) diff --git a/cmd/cognitive-worker/multimodal_test.go b/cmd/cognitive-worker/multimodal_test.go index a1207fd..aed4fe8 100644 --- a/cmd/cognitive-worker/multimodal_test.go +++ b/cmd/cognitive-worker/multimodal_test.go @@ -47,6 +47,27 @@ func TestMultimodalImageGenerationFailsClosedWhenSkillIsUnbound(t *testing.T) { } } +func TestImageRequestSizeUsesExplicitSizeOrQualityTarget(t *testing.T) { + for _, test := range []struct { + name string + prompt string + requested string + want string + }{ + {name: "explicit", prompt: "нарисуй птицу", requested: "768x512", want: "768x512"}, + {name: "hd", prompt: "нарисуй птицу в HD", want: "1024x1024"}, + {name: "uhd", prompt: "draw a bird in UHD", want: "2048x2048"}, + {name: "ordinary", prompt: "нарисуй птицу", want: ""}, + {name: "invalid explicit", prompt: "нарисуй птицу", requested: "100x100", want: ""}, + } { + t.Run(test.name, func(t *testing.T) { + if got := imageRequestSize(test.prompt, test.requested); got != test.want { + t.Fatalf("imageRequestSize(%q, %q) = %q, want %q", test.prompt, test.requested, got, test.want) + } + }) + } +} + func TestMultimodalVideoUnderstandingFailsClosedWhenSkillIsUnbound(t *testing.T) { server := multimodalGateway{now: time.Now} request := multipartRequest(t, "/v1/multimodal/video-understand", "clip.mp4", []byte("video"))