diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index b7567f9fc..c7833f6e9 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -46,6 +46,9 @@ func createCodexAuthProvider() (LLMProvider, error) { // - "gpt-4o" -> ("openai", "gpt-4o") // default protocol func ExtractProtocol(model string) (protocol, modelID string) { model = strings.TrimSpace(model) + if strings.HasPrefix(model, "@") { + return "openai", model + } protocol, modelID, found := strings.Cut(model, "/") if !found { return "openai", model diff --git a/pkg/providers/factory_provider_test.go b/pkg/providers/factory_provider_test.go index b678a7eb6..f5fad289b 100644 --- a/pkg/providers/factory_provider_test.go +++ b/pkg/providers/factory_provider_test.go @@ -70,6 +70,12 @@ func TestExtractProtocol(t *testing.T) { wantProtocol: "azure", wantModelID: "my-gpt5-deployment", }, + { + name: "at-prefixed cloudflare model id", + model: "@cf/qwen/qwen1.5-0.5b-chat", + wantProtocol: "openai", + wantModelID: "@cf/qwen/qwen1.5-0.5b-chat", + }, } for _, tt := range tests { @@ -105,6 +111,26 @@ func TestCreateProviderFromConfig_OpenAI(t *testing.T) { } } +func TestCreateProviderFromConfig_OpenAIPreservesAtPrefixedModelID(t *testing.T) { + cfg := &config.ModelConfig{ + ModelName: "test-cf-openai", + Model: "@cf/qwen/qwen1.5-0.5b-chat", + APIKey: "test-key", + APIBase: "https://api.example.com/v1", + } + + provider, modelID, err := CreateProviderFromConfig(cfg) + if err != nil { + t.Fatalf("CreateProviderFromConfig() error = %v", err) + } + if provider == nil { + t.Fatal("CreateProviderFromConfig() returned nil provider") + } + if modelID != "@cf/qwen/qwen1.5-0.5b-chat" { + t.Fatalf("modelID = %q, want full @cf model id", modelID) + } +} + func TestCreateProviderFromConfig_DefaultAPIBase(t *testing.T) { tests := []struct { name string diff --git a/pkg/providers/model_ref.go b/pkg/providers/model_ref.go index 0d1b02d16..4db78f7d8 100644 --- a/pkg/providers/model_ref.go +++ b/pkg/providers/model_ref.go @@ -17,6 +17,15 @@ func ParseModelRef(raw string, defaultProvider string) *ModelRef { return nil } + // Providers like Cloudflare use model IDs such as "@cf/..." where the slash + // belongs to the model path, not a provider prefix. + if strings.HasPrefix(raw, "@") { + return &ModelRef{ + Provider: NormalizeProvider(defaultProvider), + Model: raw, + } + } + if idx := strings.Index(raw, "/"); idx > 0 { provider := NormalizeProvider(raw[:idx]) model := strings.TrimSpace(raw[idx+1:]) diff --git a/pkg/providers/model_ref_test.go b/pkg/providers/model_ref_test.go index 6dd25167f..8ae5e2ce3 100644 --- a/pkg/providers/model_ref_test.go +++ b/pkg/providers/model_ref_test.go @@ -123,3 +123,16 @@ func TestParseModelRef_DefaultProviderNormalization(t *testing.T) { t.Errorf("provider = %q, want openai (normalized from GPT)", ref.Provider) } } + +func TestParseModelRef_AtPrefixedModelUsesDefaultProvider(t *testing.T) { + ref := ParseModelRef("@cf/qwen/qwen1.5-0.5b-chat", "openai") + if ref == nil { + t.Fatal("expected non-nil ref") + } + if ref.Provider != "openai" { + t.Fatalf("provider = %q, want %q", ref.Provider, "openai") + } + if ref.Model != "@cf/qwen/qwen1.5-0.5b-chat" { + t.Fatalf("model = %q, want full @cf model id", ref.Model) + } +}