Improve thinking/tool signature handling for Claude and Gemini requests

Prefer cached signatures and avoid injecting dummy thinking blocks; instead remove unsigned thinking blocks and add a skip sentinel for tool calls without a valid signature. Generate stable session IDs from the first user message, apply schema cleaning only for Claude models, and reorder thinking parts so thinking appears first. For Gemini, remove thinking blocks and attach a skip sentinel to function calls. Simplify response handling by passing raw function args through (remove special Bash conversion). Update and add tests to reflect the new behavior.

These changes prevent rejected dummy signatures, improve compatibility with Antigravity’s signature validation, provide more stable session IDs for conversation grouping, and make request/response translation more robust.
This commit is contained in:
이대희
2025-12-21 15:15:50 +09:00
parent 406a27271a
commit 1e9e4a86a2
7 changed files with 420 additions and 181 deletions

View File

@@ -253,13 +253,8 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq
output = output + fmt.Sprintf("data: %s\n\n\n", data)
if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() {
argsRaw := fcArgsResult.Raw
// Convert command → cmd for Bash tools using proper JSON parsing
if fcName == "Bash" || fcName == "bash" || fcName == "bash_20241022" {
argsRaw = convertBashCommandToCmdField(argsRaw)
}
output = output + "event: content_block_delta\n"
data, _ = sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"input_json_delta","partial_json":""}}`, params.ResponseIndex), "delta.partial_json", argsRaw)
data, _ = sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"input_json_delta","partial_json":""}}`, params.ResponseIndex), "delta.partial_json", fcArgsResult.Raw)
output = output + fmt.Sprintf("data: %s\n\n\n", data)
}
params.ResponseType = 3
@@ -347,36 +342,6 @@ func resolveStopReason(params *Params) string {
return "end_turn"
}
// convertBashCommandToCmdField converts "command" field to "cmd" field for Bash tools.
// Amp expects "cmd" but Gemini sends "command". This uses proper JSON parsing
// to avoid accidentally replacing "command" that appears in values.
func convertBashCommandToCmdField(argsRaw string) string {
// Only process valid JSON
if !gjson.Valid(argsRaw) {
return argsRaw
}
// Check if "command" key exists and "cmd" doesn't
commandVal := gjson.Get(argsRaw, "command")
cmdVal := gjson.Get(argsRaw, "cmd")
if commandVal.Exists() && !cmdVal.Exists() {
// Set "cmd" to the value of "command", preserve the raw value type
result, err := sjson.SetRaw(argsRaw, "cmd", commandVal.Raw)
if err != nil {
return argsRaw
}
// Delete "command" key
result, err = sjson.Delete(result, "command")
if err != nil {
return argsRaw
}
return result
}
return argsRaw
}
// ConvertAntigravityResponseToClaudeNonStream converts a non-streaming Gemini CLI response to a non-streaming Claude response.
//
// Parameters:
@@ -488,12 +453,7 @@ func ConvertAntigravityResponseToClaudeNonStream(_ context.Context, _ string, or
toolBlock, _ = sjson.Set(toolBlock, "name", name)
if args := functionCall.Get("args"); args.Exists() && args.Raw != "" && gjson.Valid(args.Raw) {
argsRaw := args.Raw
// Convert command → cmd for Bash tools
if name == "Bash" || name == "bash" || name == "bash_20241022" {
argsRaw = convertBashCommandToCmdField(argsRaw)
}
toolBlock, _ = sjson.SetRaw(toolBlock, "input", argsRaw)
toolBlock, _ = sjson.SetRaw(toolBlock, "input", args.Raw)
}
ensureContentArray()