Files
thotamandClaude Opus 5 c2c5124a0c feat(image): add GPT Image 2.5 and default to flare (#1562)
OpenAI shipped GPT Image 2.5 on 2026-09-08 as two model IDs rather than one:
flare is tuned for speed, sunburst for editing precision. Both accept text and
image input and run on /images/generations, /images/edits and the Responses
API, which is the path the native image_generation tool already uses.

Flare replaces gpt-image-2 as the default because OpenAI reports higher quality
at roughly half the latency, which suits create_image's one-shot call. Both
gpt-image-2 and gpt-image-1.5 stay on the whitelist, so a saved image_model
keeps working untouched.

Also in this change:

- The rejection message is now built from the whitelist itself instead of a
  hand-written string that drifts whenever a model is added.
- codex_build.go uses the DefaultImageModel constant instead of hardcoding
  "gpt-image-2", so the inline chat path cannot drift from the shared default.
- Both new models join the isEditModel branch so reference images route to
  /images/edits.
- ParamField gains an optional labelKey, letting the dropdown read its label
  from i18n instead of showing English inside a translated UI. It passes a
  defaultValue, so a missing key degrades to the literal rather than breaking.
- Locale keys added to all five catalogs; ko was missing the whole
  mediaChain.imageModel* group.

Surface parity: the desktop UI is unchanged because sortable-provider-card
renders no param form and passes params through as Record<string, unknown>.
The CLI is unchanged because cmd/ exposes no image-model flag. The API contract
is unchanged because image_model lives in the free-form params map; only the
backend whitelist widened.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 10:31:48 +07:00

54 lines
1.7 KiB
Go

package providers
import (
"strings"
"testing"
)
func TestValidateImageModel(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{name: "empty falls back to default", input: "", want: DefaultImageModel},
{name: "flare", input: "gpt-image-2.5-flare", want: "gpt-image-2.5-flare"},
{name: "sunburst", input: "gpt-image-2.5-sunburst", want: "gpt-image-2.5-sunburst"},
{name: "previous generation", input: "gpt-image-2", want: "gpt-image-2"},
{name: "legacy", input: "gpt-image-1.5", want: "gpt-image-1.5"},
{name: "unversioned 2.5 is not a real model id", input: "gpt-image-2.5", wantErr: true},
{name: "unrelated model", input: "dall-e-3", wantErr: true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := ValidateImageModel(tc.input)
if tc.wantErr {
if err == nil {
t.Fatalf("ValidateImageModel(%q) = %q, want error", tc.input, got)
}
// The error must list the allowed models so the caller can self-correct.
if !strings.Contains(err.Error(), DefaultImageModel) {
t.Errorf("error %q does not mention the default model %q", err, DefaultImageModel)
}
return
}
if err != nil {
t.Fatalf("ValidateImageModel(%q) returned error: %v", tc.input, err)
}
if got != tc.want {
t.Errorf("ValidateImageModel(%q) = %q, want %q", tc.input, got, tc.want)
}
})
}
}
// TestDefaultImageModelIsAllowed guards against the default drifting out of the
// whitelist, which would make every create_image call with no explicit model fail.
func TestDefaultImageModelIsAllowed(t *testing.T) {
if !allowedImageModels[DefaultImageModel] {
t.Fatalf("DefaultImageModel %q is missing from allowedImageModels", DefaultImageModel)
}
}