fix(translator): sanitize tool names for Gemini function_declarations compatibility

Claude Code and MCP clients may send tool names containing characters
invalid for Gemini's function_declarations (e.g. '/', '@', spaces).
Sanitize on request via SanitizeFunctionName and restore original names
on response for both antigravity/claude and gemini-cli/claude translators.
This commit is contained in:
sususu98
2026-03-22 13:10:53 +08:00
parent f81acd0760
commit 2398ebad55
6 changed files with 135 additions and 12 deletions
+60
View File
@@ -54,3 +54,63 @@ func TestSanitizeFunctionName(t *testing.T) {
})
}
}
func TestSanitizedToolNameMap(t *testing.T) {
t.Run("returns map for tools needing sanitization", func(t *testing.T) {
raw := []byte(`{"tools":[
{"name":"valid_tool","input_schema":{}},
{"name":"mcp/server/read","input_schema":{}},
{"name":"tool@v2","input_schema":{}}
]}`)
m := SanitizedToolNameMap(raw)
if m == nil {
t.Fatal("expected non-nil map")
}
if m["mcp_server_read"] != "mcp/server/read" {
t.Errorf("expected mcp_server_read → mcp/server/read, got %q", m["mcp_server_read"])
}
if m["tool_v2"] != "tool@v2" {
t.Errorf("expected tool_v2 → tool@v2, got %q", m["tool_v2"])
}
if _, exists := m["valid_tool"]; exists {
t.Error("valid_tool should not be in the map (no sanitization needed)")
}
})
t.Run("returns nil when no tools need sanitization", func(t *testing.T) {
raw := []byte(`{"tools":[{"name":"Read","input_schema":{}},{"name":"Write","input_schema":{}}]}`)
m := SanitizedToolNameMap(raw)
if m != nil {
t.Errorf("expected nil, got %v", m)
}
})
t.Run("returns nil for empty/missing tools", func(t *testing.T) {
if m := SanitizedToolNameMap([]byte(`{}`)); m != nil {
t.Error("expected nil for no tools")
}
if m := SanitizedToolNameMap(nil); m != nil {
t.Error("expected nil for nil input")
}
})
}
func TestRestoreSanitizedToolName(t *testing.T) {
m := map[string]string{
"mcp_server_read": "mcp/server/read",
"tool_v2": "tool@v2",
}
if got := RestoreSanitizedToolName(m, "mcp_server_read"); got != "mcp/server/read" {
t.Errorf("expected mcp/server/read, got %q", got)
}
if got := RestoreSanitizedToolName(m, "unknown"); got != "unknown" {
t.Errorf("expected passthrough for unknown, got %q", got)
}
if got := RestoreSanitizedToolName(nil, "name"); got != "name" {
t.Errorf("expected passthrough for nil map, got %q", got)
}
if got := RestoreSanitizedToolName(m, ""); got != "" {
t.Errorf("expected empty for empty name, got %q", got)
}
}