From dca32335ce79c36fa5f013b006728bb22169c8ae Mon Sep 17 00:00:00 2001 From: vadim Date: Sun, 8 Mar 2026 04:23:56 +0300 Subject: [PATCH] feat(providers): support unknown protocols as OpenAI-compatible fallback Treat unknown protocol prefixes (e.g., zai-org/GLM-4.7) as OpenAI-compatible HTTP providers instead of returning an error. The full model string is passed to the API for unknown protocols. Added tests for unknown protocol with API key, API base, and missing auth scenarios. Fixes #661 Co-Authored-By: Claude Opus 4.6 --- pkg/providers/factory_provider.go | 16 +++++++++-- pkg/providers/factory_provider_test.go | 39 +++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index 941985964..3e2fc10dd 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/logger" ) // createClaudeAuthProvider creates a Claude provider using OAuth credentials from auth store. @@ -53,8 +54,8 @@ func ExtractProtocol(model string) (protocol, modelID string) { // CreateProviderFromConfig creates a provider based on the ModelConfig. // It uses the protocol prefix in the Model field to determine which provider to create. -// Supported protocols: openai, litellm, anthropic, antigravity, claude-cli, codex-cli, github-copilot -// Returns the provider, the model ID (without protocol prefix), and any error. +// Supported protocols: openai, litellm, anthropic, antigravity, claude-cli, codex-cli, github-copilot, and any OpenAI-compatible HTTP provider. +// Returns the provider, the model ID (full model string for unknown protocols, otherwise without protocol prefix), and any error. func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, error) { if cfg == nil { return nil, "", fmt.Errorf("config is nil") @@ -169,7 +170,16 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err return provider, modelID, nil default: - return nil, "", fmt.Errorf("unknown protocol %q in model %q", protocol, cfg.Model) + // Treat unknown protocols as OpenAI-compatible + logger.WarnF("unknown protocol, falling back to OpenAI-compatible HTTP provider", map[string]any{"protocol": protocol, "model": cfg.Model}) + if cfg.APIKey == "" && cfg.APIBase == "" { + return nil, "", fmt.Errorf("api_key or api_base is required for HTTP-based protocol %q", protocol) + } + apiBase := cfg.APIBase + if apiBase == "" { + apiBase = getDefaultAPIBase("openai") + } + return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(cfg.APIKey, apiBase, cfg.Proxy, cfg.MaxTokensField, cfg.RequestTimeout), cfg.Model, nil } } diff --git a/pkg/providers/factory_provider_test.go b/pkg/providers/factory_provider_test.go index 17bc55d25..10f8fe02e 100644 --- a/pkg/providers/factory_provider_test.go +++ b/pkg/providers/factory_provider_test.go @@ -254,9 +254,46 @@ func TestCreateProviderFromConfig_UnknownProtocol(t *testing.T) { APIKey: "test-key", } + provider, modelID, err := CreateProviderFromConfig(cfg) + if err != nil { + t.Fatalf("CreateProviderFromConfig() unexpected error: %v", err) + } + if provider == nil { + t.Fatal("CreateProviderFromConfig() returned nil provider for unknown protocol with APIKey") + } + if modelID != cfg.Model { + t.Fatalf("CreateProviderFromConfig() expected modelID=%q, got %q", cfg.Model, modelID) + } +} + +func TestCreateProviderFromConfig_UnknownProtocolWithAPIBase(t *testing.T) { + cfg := &config.ModelConfig{ + ModelName: "test-unknown", + Model: "unknown-protocol/model", + APIBase: "http://localhost:8080/v1", + } + + provider, modelID, err := CreateProviderFromConfig(cfg) + if err != nil { + t.Fatalf("CreateProviderFromConfig() unexpected error: %v", err) + } + if provider == nil { + t.Fatal("CreateProviderFromConfig() returned nil provider") + } + if modelID != cfg.Model { + t.Fatalf("CreateProviderFromConfig() expected modelID=%q, got %q", cfg.Model, modelID) + } +} + +func TestCreateProviderFromConfig_UnknownProtocolNoAuth(t *testing.T) { + cfg := &config.ModelConfig{ + ModelName: "test-unknown", + Model: "unknown-protocol/model", + } + _, _, err := CreateProviderFromConfig(cfg) if err == nil { - t.Fatal("CreateProviderFromConfig() expected error for unknown protocol") + t.Fatal("CreateProviderFromConfig() expected error for unknown protocol without api_key or api_base") } }