package service import ( "encoding/json" "strings" "testing" ) // TestConvertClaudeToolsToGeminiTools_CustomType 测试custom类型工具转换 func TestConvertClaudeToolsToGeminiTools_CustomType(t *testing.T) { tests := []struct { name string tools any expectedLen int description string }{ { name: "Standard tools", tools: []any{ map[string]any{ "name": "get_weather", "description": "Get weather info", "input_schema": map[string]any{"type": "object"}, }, }, expectedLen: 1, description: "标准工具格式应该正常转换", }, { name: "Custom type tool (MCP format)", tools: []any{ map[string]any{ "type": "custom", "name": "mcp_tool", "custom": map[string]any{ "description": "MCP tool description", "input_schema": map[string]any{"type": "object"}, }, }, }, expectedLen: 1, description: "Custom类型工具应该从custom字段读取", }, { name: "Mixed standard and custom tools", tools: []any{ map[string]any{ "name": "standard_tool", "description": "Standard", "input_schema": map[string]any{"type": "object"}, }, map[string]any{ "type": "custom", "name": "custom_tool", "custom": map[string]any{ "description": "Custom", "input_schema": map[string]any{"type": "object"}, }, }, }, expectedLen: 1, description: "混合工具应该都能正确转换", }, { name: "Custom tool without custom field", tools: []any{ map[string]any{ "type": "custom", "name": "invalid_custom", // 缺少 custom 字段 }, }, expectedLen: 0, // 应该被跳过 description: "缺少custom字段的custom工具应该被跳过", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := convertClaudeToolsToGeminiTools(tt.tools) if tt.expectedLen == 0 { if result != nil { t.Errorf("%s: expected nil result, got %v", tt.description, result) } return } if result == nil { t.Fatalf("%s: expected non-nil result", tt.description) } if len(result) != 1 { t.Errorf("%s: expected 1 tool declaration, got %d", tt.description, len(result)) return } toolDecl, ok := result[0].(map[string]any) if !ok { t.Fatalf("%s: result[0] is not map[string]any", tt.description) } funcDecls, ok := toolDecl["functionDeclarations"].([]any) if !ok { t.Fatalf("%s: functionDeclarations is not []any", tt.description) } toolsArr, _ := tt.tools.([]any) expectedFuncCount := 0 for _, tool := range toolsArr { toolMap, _ := tool.(map[string]any) if toolMap["name"] != "" { // 检查是否为有效的custom工具 if toolMap["type"] == "custom" { if toolMap["custom"] != nil { expectedFuncCount++ } } else { expectedFuncCount++ } } } if len(funcDecls) != expectedFuncCount { t.Errorf("%s: expected %d function declarations, got %d", tt.description, expectedFuncCount, len(funcDecls)) } }) } } func TestConvertClaudeMessagesToGeminiGenerateContent_AddsThoughtSignatureForToolUse(t *testing.T) { claudeReq := map[string]any{ "model": "claude-haiku-4-5-20251001", "max_tokens": 10, "messages": []any{ map[string]any{ "role": "user", "content": []any{ map[string]any{"type": "text", "text": "hi"}, }, }, map[string]any{ "role": "assistant", "content": []any{ map[string]any{"type": "text", "text": "ok"}, map[string]any{ "type": "tool_use", "id": "toolu_123", "name": "default_api:write_file", "input": map[string]any{"path": "a.txt", "content": "x"}, // no signature on purpose }, }, }, }, "tools": []any{ map[string]any{ "name": "default_api:write_file", "description": "write file", "input_schema": map[string]any{ "type": "object", "properties": map[string]any{"path": map[string]any{"type": "string"}}, }, }, }, } b, _ := json.Marshal(claudeReq) out, err := convertClaudeMessagesToGeminiGenerateContent(b) if err != nil { t.Fatalf("convert failed: %v", err) } s := string(out) if !strings.Contains(s, "\"functionCall\"") { t.Fatalf("expected functionCall in output, got: %s", s) } if !strings.Contains(s, "\"thoughtSignature\":\""+geminiDummyThoughtSignature+"\"") { t.Fatalf("expected injected thoughtSignature %q, got: %s", geminiDummyThoughtSignature, s) } } func TestEnsureGeminiFunctionCallThoughtSignatures_InsertsWhenMissing(t *testing.T) { geminiReq := map[string]any{ "contents": []any{ map[string]any{ "role": "user", "parts": []any{ map[string]any{ "functionCall": map[string]any{ "name": "default_api:write_file", "args": map[string]any{"path": "a.txt"}, }, }, }, }, }, } b, _ := json.Marshal(geminiReq) out := ensureGeminiFunctionCallThoughtSignatures(b) s := string(out) if !strings.Contains(s, "\"thoughtSignature\":\""+geminiDummyThoughtSignature+"\"") { t.Fatalf("expected injected thoughtSignature %q, got: %s", geminiDummyThoughtSignature, s) } }