Merge branch 'upstream-main' into feat/subturn-poc

This commit is contained in:
Administrator
2026-03-19 22:12:51 +08:00
22 changed files with 1125 additions and 72 deletions
@@ -221,6 +221,10 @@ func buildRequestBody(
// Add tool_use blocks
for _, tc := range msg.ToolCalls {
if strings.TrimSpace(tc.Name) == "" {
continue
}
// Handle nil Arguments (GLM-4 may return null input)
input := tc.Arguments
if input == nil {
@@ -492,6 +492,20 @@ func TestBuildRequestBodyEdgeCases(t *testing.T) {
},
wantErr: false,
},
{
name: "skip tool calls with empty names",
messages: []Message{
{Role: "assistant", Content: "Calling tool", ToolCalls: []ToolCall{
{ID: "tool-empty", Name: "", Arguments: map[string]any{"ignored": true}},
{ID: "tool-valid", Name: "test_tool", Arguments: map[string]any{"arg": "value"}},
}},
},
model: "test-model",
options: map[string]any{
"max_tokens": 8192,
},
wantErr: false,
},
}
for _, tt := range tests {
@@ -513,6 +527,37 @@ func TestBuildRequestBodyEdgeCases(t *testing.T) {
if got["model"] != tt.model {
t.Errorf("model = %v, want %v", got["model"], tt.model)
}
if tt.name == "skip tool calls with empty names" {
messages, ok := got["messages"].([]any)
if !ok || len(messages) != 1 {
t.Fatalf("messages = %#v, want single assistant message", got["messages"])
}
assistantMsg, ok := messages[0].(map[string]any)
if !ok {
t.Fatalf("assistant message = %#v, want map", messages[0])
}
content, ok := assistantMsg["content"].([]any)
if !ok {
t.Fatalf("assistant content = %#v, want []any", assistantMsg["content"])
}
if len(content) != 2 {
t.Fatalf("assistant content length = %d, want 2", len(content))
}
toolUse, ok := content[1].(map[string]any)
if !ok {
t.Fatalf("tool_use block = %#v, want map", content[1])
}
if gotName := toolUse["name"]; gotName != "test_tool" {
t.Fatalf("tool_use name = %v, want %q", gotName, "test_tool")
}
if gotID := toolUse["id"]; gotID != "tool-valid" {
t.Fatalf("tool_use id = %v, want %q", gotID, "tool-valid")
}
}
})
}
}
+26 -2
View File
@@ -115,8 +115,9 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
"vivgrid", "volcengine", "vllm", "qwen", "mistral", "avian",
"minimax", "longcat", "modelscope", "novita":
"vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl",
"qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita",
"coding-plan", "alibaba-coding", "qwen-coding":
// All other OpenAI-compatible HTTP providers
if cfg.APIKey == "" && cfg.APIBase == "" {
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,
), 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":
return NewAntigravityProvider(), modelID, nil
@@ -245,6 +261,14 @@ func getDefaultAPIBase(protocol string) string {
return "https://ark.cn-beijing.volces.com/api/v3"
case "qwen":
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":
return "http://localhost:8000/v1"
case "mistral":
+131
View File
@@ -472,3 +472,134 @@ func TestCreateProviderFromConfig_AzureMissingAPIBase(t *testing.T) {
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)
}
}
}
+8
View File
@@ -53,6 +53,14 @@ func NormalizeProvider(provider string) string {
return "zhipu"
case "google":
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
+8
View File
@@ -73,6 +73,14 @@ func TestNormalizeProvider(t *testing.T) {
{"glm", "zhipu"},
{"google", "gemini"},
{"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"},
{"", ""},
}