feat(provider): add Alibaba Coding Plan and regional Qwen endpoints (#1748)
* feat(provider): add Alibaba Coding Plan and regional Qwen endpoints - Add Alibaba Coding Plan provider with OpenAI-compatible endpoint (https://coding-intl.dashscope.aliyuncs.com/v1) - Add Coding Plan Anthropic-compatible endpoint (https://coding-intl.dashscope.aliyuncs.com/apps/anthropic) - Add regional Qwen endpoints (qwen-intl, qwen-us) - Add provider aliases: coding-plan, alibaba-coding, qwen-coding - Normalize provider names for coding-plan variants 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(provider): add reviewer-requested fixes for Alibaba Coding Plan - Add qwen-international, dashscope-intl, dashscope-us aliases to switch case - Add coding-plan-anthropic case with anthropicmessages.NewProviderWithTimeout - Add alibaba-coding-anthropic -> coding-plan-anthropic normalization - Add qwen-international -> qwen-intl and dashscope-us -> qwen-us normalization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test(provider): add tests for Alibaba Coding Plan protocol aliases - Add tests for qwen-international, dashscope-intl, dashscope-us aliases - Add tests for coding-plan-anthropic and alibaba-coding-anthropic - Add getDefaultAPIBase tests for all new aliases - Add normalization tests for new provider aliases 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
276a0cb92c
commit
9a3ca8e54d
4 changed files with 173 additions and 2 deletions
|
|
@ -115,8 +115,9 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
|
||||||
|
|
||||||
case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
|
case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
|
||||||
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
|
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
|
||||||
"vivgrid", "volcengine", "vllm", "qwen", "mistral", "avian",
|
"vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl",
|
||||||
"minimax", "longcat", "modelscope", "novita":
|
"qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita",
|
||||||
|
"coding-plan", "alibaba-coding", "qwen-coding":
|
||||||
// All other OpenAI-compatible HTTP providers
|
// All other OpenAI-compatible HTTP providers
|
||||||
if cfg.APIKey == "" && cfg.APIBase == "" {
|
if cfg.APIKey == "" && cfg.APIBase == "" {
|
||||||
return nil, "", fmt.Errorf("api_key or api_base is required for HTTP-based protocol %q", protocol)
|
return nil, "", fmt.Errorf("api_key or api_base is required for HTTP-based protocol %q", protocol)
|
||||||
|
|
@ -173,6 +174,21 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
|
||||||
cfg.RequestTimeout,
|
cfg.RequestTimeout,
|
||||||
), modelID, nil
|
), modelID, nil
|
||||||
|
|
||||||
|
case "coding-plan-anthropic", "alibaba-coding-anthropic":
|
||||||
|
// Alibaba Coding Plan with Anthropic-compatible API
|
||||||
|
apiBase := cfg.APIBase
|
||||||
|
if apiBase == "" {
|
||||||
|
apiBase = getDefaultAPIBase(protocol)
|
||||||
|
}
|
||||||
|
if cfg.APIKey == "" {
|
||||||
|
return nil, "", fmt.Errorf("api_key is required for %q protocol (model: %s)", protocol, cfg.Model)
|
||||||
|
}
|
||||||
|
return anthropicmessages.NewProviderWithTimeout(
|
||||||
|
cfg.APIKey,
|
||||||
|
apiBase,
|
||||||
|
cfg.RequestTimeout,
|
||||||
|
), modelID, nil
|
||||||
|
|
||||||
case "antigravity":
|
case "antigravity":
|
||||||
return NewAntigravityProvider(), modelID, nil
|
return NewAntigravityProvider(), modelID, nil
|
||||||
|
|
||||||
|
|
@ -245,6 +261,14 @@ func getDefaultAPIBase(protocol string) string {
|
||||||
return "https://ark.cn-beijing.volces.com/api/v3"
|
return "https://ark.cn-beijing.volces.com/api/v3"
|
||||||
case "qwen":
|
case "qwen":
|
||||||
return "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
return "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||||
|
case "qwen-intl", "qwen-international", "dashscope-intl":
|
||||||
|
return "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
||||||
|
case "qwen-us", "dashscope-us":
|
||||||
|
return "https://dashscope-us.aliyuncs.com/compatible-mode/v1"
|
||||||
|
case "coding-plan", "alibaba-coding", "qwen-coding":
|
||||||
|
return "https://coding-intl.dashscope.aliyuncs.com/v1"
|
||||||
|
case "coding-plan-anthropic", "alibaba-coding-anthropic":
|
||||||
|
return "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"
|
||||||
case "vllm":
|
case "vllm":
|
||||||
return "http://localhost:8000/v1"
|
return "http://localhost:8000/v1"
|
||||||
case "mistral":
|
case "mistral":
|
||||||
|
|
|
||||||
|
|
@ -472,3 +472,134 @@ func TestCreateProviderFromConfig_AzureMissingAPIBase(t *testing.T) {
|
||||||
t.Fatal("CreateProviderFromConfig() expected error for missing API base")
|
t.Fatal("CreateProviderFromConfig() expected error for missing API base")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateProviderFromConfig_QwenInternationalAlias(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
protocol string
|
||||||
|
}{
|
||||||
|
{"qwen-international", "qwen-international"},
|
||||||
|
{"dashscope-intl", "dashscope-intl"},
|
||||||
|
{"qwen-intl", "qwen-intl"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := &config.ModelConfig{
|
||||||
|
ModelName: "test-" + tt.protocol,
|
||||||
|
Model: tt.protocol + "/qwen-max",
|
||||||
|
APIKey: "test-key",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 != "qwen-max" {
|
||||||
|
t.Errorf("modelID = %q, want %q", modelID, "qwen-max")
|
||||||
|
}
|
||||||
|
if _, ok := provider.(*HTTPProvider); !ok {
|
||||||
|
t.Fatalf("expected *HTTPProvider, got %T", provider)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateProviderFromConfig_QwenUSAlias(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
protocol string
|
||||||
|
}{
|
||||||
|
{"qwen-us", "qwen-us"},
|
||||||
|
{"dashscope-us", "dashscope-us"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := &config.ModelConfig{
|
||||||
|
ModelName: "test-" + tt.protocol,
|
||||||
|
Model: tt.protocol + "/qwen-max",
|
||||||
|
APIKey: "test-key",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 != "qwen-max" {
|
||||||
|
t.Errorf("modelID = %q, want %q", modelID, "qwen-max")
|
||||||
|
}
|
||||||
|
if _, ok := provider.(*HTTPProvider); !ok {
|
||||||
|
t.Fatalf("expected *HTTPProvider, got %T", provider)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateProviderFromConfig_CodingPlanAnthropic(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
protocol string
|
||||||
|
}{
|
||||||
|
{"coding-plan-anthropic", "coding-plan-anthropic"},
|
||||||
|
{"alibaba-coding-anthropic", "alibaba-coding-anthropic"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := &config.ModelConfig{
|
||||||
|
ModelName: "test-" + tt.protocol,
|
||||||
|
Model: tt.protocol + "/claude-sonnet-4-20250514",
|
||||||
|
APIKey: "test-key",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 != "claude-sonnet-4-20250514" {
|
||||||
|
t.Errorf("modelID = %q, want %q", modelID, "claude-sonnet-4-20250514")
|
||||||
|
}
|
||||||
|
// coding-plan-anthropic uses Anthropic Messages provider
|
||||||
|
// Verify it's the anthropic messages provider by checking interface
|
||||||
|
var _ LLMProvider = provider
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDefaultAPIBase_CodingPlanAnthropic(t *testing.T) {
|
||||||
|
expectedURL := "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"
|
||||||
|
if got := getDefaultAPIBase("coding-plan-anthropic"); got != expectedURL {
|
||||||
|
t.Fatalf("getDefaultAPIBase(%q) = %q, want %q", "coding-plan-anthropic", got, expectedURL)
|
||||||
|
}
|
||||||
|
if got := getDefaultAPIBase("alibaba-coding-anthropic"); got != expectedURL {
|
||||||
|
t.Fatalf("getDefaultAPIBase(%q) = %q, want %q", "alibaba-coding-anthropic", got, expectedURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDefaultAPIBase_QwenIntlAliases(t *testing.T) {
|
||||||
|
expectedURL := "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
||||||
|
for _, protocol := range []string{"qwen-intl", "qwen-international", "dashscope-intl"} {
|
||||||
|
if got := getDefaultAPIBase(protocol); got != expectedURL {
|
||||||
|
t.Fatalf("getDefaultAPIBase(%q) = %q, want %q", protocol, got, expectedURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDefaultAPIBase_QwenUSAliases(t *testing.T) {
|
||||||
|
expectedURL := "https://dashscope-us.aliyuncs.com/compatible-mode/v1"
|
||||||
|
for _, protocol := range []string{"qwen-us", "dashscope-us"} {
|
||||||
|
if got := getDefaultAPIBase(protocol); got != expectedURL {
|
||||||
|
t.Fatalf("getDefaultAPIBase(%q) = %q, want %q", protocol, got, expectedURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,14 @@ func NormalizeProvider(provider string) string {
|
||||||
return "zhipu"
|
return "zhipu"
|
||||||
case "google":
|
case "google":
|
||||||
return "gemini"
|
return "gemini"
|
||||||
|
case "alibaba-coding", "qwen-coding":
|
||||||
|
return "coding-plan"
|
||||||
|
case "alibaba-coding-anthropic":
|
||||||
|
return "coding-plan-anthropic"
|
||||||
|
case "qwen-international", "dashscope-intl":
|
||||||
|
return "qwen-intl"
|
||||||
|
case "dashscope-us":
|
||||||
|
return "qwen-us"
|
||||||
}
|
}
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,14 @@ func TestNormalizeProvider(t *testing.T) {
|
||||||
{"glm", "zhipu"},
|
{"glm", "zhipu"},
|
||||||
{"google", "gemini"},
|
{"google", "gemini"},
|
||||||
{"groq", "groq"},
|
{"groq", "groq"},
|
||||||
|
// Alibaba Coding Plan aliases
|
||||||
|
{"alibaba-coding", "coding-plan"},
|
||||||
|
{"qwen-coding", "coding-plan"},
|
||||||
|
{"alibaba-coding-anthropic", "coding-plan-anthropic"},
|
||||||
|
// Qwen international aliases
|
||||||
|
{"qwen-international", "qwen-intl"},
|
||||||
|
{"dashscope-intl", "qwen-intl"},
|
||||||
|
{"dashscope-us", "qwen-us"},
|
||||||
{"", ""},
|
{"", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue