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
@@ -44,6 +44,10 @@ type Params struct {
// Signature caching support
CurrentThinkingText strings.Builder // Accumulates thinking text for signature caching
// Reverse map: sanitized Gemini function name → original Claude tool name.
// Populated lazily on the first response chunk from the original request JSON.
ToolNameMap map[string]string
}
// toolUseIDCounter provides a process-wide unique counter for tool use identifiers.
@@ -77,6 +81,10 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq
params := (*param).(*Params)
if params.ToolNameMap == nil {
params.ToolNameMap = util.SanitizedToolNameMap(originalRequestRawJSON)
}
if bytes.Equal(rawJSON, []byte("[DONE]")) {
output := make([]byte, 0, 256)
// Only send final events if we have actually output content
@@ -212,7 +220,7 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq
// Handle function/tool calls from the AI model
// This processes tool usage requests and formats them for Claude Code API compatibility
params.HasToolUse = true
fcName := functionCallResult.Get("name").String()
fcName := util.RestoreSanitizedToolName(params.ToolNameMap, functionCallResult.Get("name").String())
// Handle state transitions when switching to function calls
// Close any existing function call block first
@@ -348,7 +356,7 @@ func resolveStopReason(params *Params) string {
// Returns:
// - []byte: A Claude-compatible JSON response.
func ConvertAntigravityResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []byte {
_ = originalRequestRawJSON
toolNameMap := util.SanitizedToolNameMap(originalRequestRawJSON)
modelName := gjson.GetBytes(requestRawJSON, "model").String()
root := gjson.ParseBytes(rawJSON)
@@ -450,7 +458,7 @@ func ConvertAntigravityResponseToClaudeNonStream(_ context.Context, _ string, or
flushText()
hasToolCall = true
name := functionCall.Get("name").String()
name := util.RestoreSanitizedToolName(toolNameMap, functionCall.Get("name").String())
toolIDCounter++
toolBlock := []byte(`{"type":"tool_use","id":"","name":"","input":{}}`)
toolBlock, _ = sjson.SetBytes(toolBlock, "id", fmt.Sprintf("tool_%d", toolIDCounter))