feat(api): add OpenAI compatibility for image models

- Introduced OpenAI-compatible image model support in the API, enabling integration through image generation and editing endpoints.
- Added registry type for OpenAIImageModelType to classify and validate compatibility.
- Implemented request handling for OpenAI-compatible image models, including JSON and multipart formats.
- Enhanced executor methods to support OpenAI-compatible image streaming and non-streaming requests.
- Included tests to validate model registration, streaming behavior, and multipart payload formatting.
This commit is contained in:
Luis Pater
2026-05-19 09:36:05 +08:00
parent b67eb6f25d
commit feebe6c7f2
16 changed files with 1962 additions and 37 deletions
@@ -4,6 +4,7 @@ import (
"strings"
"testing"
internalregistry "github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
)
@@ -63,3 +64,71 @@ func TestRegisterModelsForAuth_UsesPreMergedExcludedModelsAttribute(t *testing.T
t.Fatal("expected global excluded model to be present when attribute override is set")
}
}
func TestRegisterModelsForAuth_OpenAICompatibilityImageModelType(t *testing.T) {
service := &Service{
cfg: &config.Config{
OpenAICompatibility: []config.OpenAICompatibility{
{
Name: "images",
BaseURL: "https://example.com/v1",
Models: []config.OpenAICompatibilityModel{
{Name: "upstream-image", Alias: "compat-image", Image: true},
{Name: "upstream-chat", Alias: "compat-chat"},
},
},
},
},
}
auth := &coreauth.Auth{
ID: "auth-openai-compat-image",
Provider: "openai-compatibility",
Status: coreauth.StatusActive,
Attributes: map[string]string{
"auth_kind": "api_key",
"compat_name": "images",
"provider_key": "images",
},
}
modelRegistry := internalregistry.GetGlobalRegistry()
modelRegistry.UnregisterClient(auth.ID)
t.Cleanup(func() {
modelRegistry.UnregisterClient(auth.ID)
})
service.registerModelsForAuth(auth)
models := modelRegistry.GetModelsForClient(auth.ID)
var imageModel *internalregistry.ModelInfo
var chatModel *internalregistry.ModelInfo
for _, model := range models {
if model == nil {
continue
}
switch strings.TrimSpace(model.ID) {
case "compat-image":
imageModel = model
case "compat-chat":
chatModel = model
}
}
if imageModel == nil {
t.Fatal("expected compat-image to be registered")
}
if imageModel.Type != internalregistry.OpenAIImageModelType {
t.Fatalf("image model type = %q, want %q", imageModel.Type, internalregistry.OpenAIImageModelType)
}
if imageModel.Thinking != nil {
t.Fatalf("image model thinking = %+v, want nil", imageModel.Thinking)
}
if chatModel == nil {
t.Fatal("expected compat-chat to be registered")
}
if chatModel.Type != "openai-compatibility" {
t.Fatalf("chat model type = %q, want openai-compatibility", chatModel.Type)
}
if chatModel.Thinking == nil {
t.Fatal("expected chat model to keep default thinking support")
}
}