From 6dac1212fcf36803290c7b0f00b66799b7fb59a4 Mon Sep 17 00:00:00 2001 From: Koosha Paridehpour Date: Mon, 23 Feb 2026 21:01:48 -0700 Subject: [PATCH] fix: multiple issues - #210: Add cmd to Bash required fields for Ampcode compatibility - #206: Remove type uppercasing that breaks nullable type arrays Fixes #210 Fixes #206 --- .../gemini_openai-responses_request.go | 13 +---- .../kiro/claude/truncation_detector.go | 55 +++++++------------ 2 files changed, 23 insertions(+), 45 deletions(-) diff --git a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go index aca0171781..087ee0e322 100644 --- a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go +++ b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go @@ -357,16 +357,9 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte // Convert parameter types from OpenAI format to Gemini format cleaned := params.Raw // Convert type values to uppercase for Gemini - paramsResult := gjson.Parse(cleaned) - if properties := paramsResult.Get("properties"); properties.Exists() { - properties.ForEach(func(key, value gjson.Result) bool { - if propType := value.Get("type"); propType.Exists() { - upperType := strings.ToUpper(propType.String()) - cleaned, _ = sjson.Set(cleaned, "properties."+key.String()+".type", upperType) - } - return true - }) - } + // Skip type uppercasing - let CleanJSONSchemaForGemini handle type arrays + // This fixes the bug where nullable type arrays like ["string","null"] were + // incorrectly converted to strings causing 400 errors on Gemini API // Set the overall type to OBJECT cleaned, _ = sjson.Set(cleaned, "type", "OBJECT") funcDecl, _ = sjson.SetRaw(funcDecl, "parametersJsonSchema", cleaned) diff --git a/internal/translator/kiro/claude/truncation_detector.go b/internal/translator/kiro/claude/truncation_detector.go index 056c67028e..65c5f5a87e 100644 --- a/internal/translator/kiro/claude/truncation_detector.go +++ b/internal/translator/kiro/claude/truncation_detector.go @@ -53,25 +53,19 @@ var KnownCommandTools = map[string]bool{ "execute_python": true, } -// RequiredFieldsByTool maps tool names to their required field groups. -// Each outer element is a required group; each inner slice lists alternative field names (OR logic). -// A group is satisfied when ANY one of its alternatives exists in the parsed input. -// All groups must be satisfied for the tool input to be considered valid. -// -// Example: -// {{"cmd", "command"}} means the tool needs EITHER "cmd" OR "command". -// {{"file_path"}, {"content"}} means the tool needs BOTH "file_path" AND "content". -var RequiredFieldsByTool = map[string][][]string{ - "Write": {{"file_path"}, {"content"}}, - "write_to_file": {{"path"}, {"content"}}, - "fsWrite": {{"path"}, {"content"}}, - "create_file": {{"path"}, {"content"}}, - "edit_file": {{"path"}}, - "apply_diff": {{"path"}, {"diff"}}, - "str_replace_editor": {{"path"}, {"old_str"}, {"new_str"}}, - "Bash": {{"cmd", "command"}}, - "execute": {{"command"}}, - "run_command": {{"command"}}, +// RequiredFieldsByTool maps tool names to their required fields. +// If any of these fields are missing, the tool input is considered truncated. +var RequiredFieldsByTool = map[string][]string{ + "Write": {"file_path", "content"}, + "write_to_file": {"path", "content"}, + "fsWrite": {"path", "content"}, + "create_file": {"path", "content"}, + "edit_file": {"path"}, + "apply_diff": {"path", "diff"}, + "str_replace_editor": {"path", "old_str", "new_str"}, + "Bash": {"command", "cmd"}, // Ampcode uses "cmd", others use "command" + "execute": {"command"}, + "run_command": {"command"}, } // DetectTruncation checks if the tool use input appears to be truncated. @@ -110,9 +104,9 @@ func DetectTruncation(toolName, toolUseID, rawInput string, parsedInput map[stri // Scenario 3: JSON parsed but critical fields are missing if parsedInput != nil { - requiredGroups, hasRequirements := RequiredFieldsByTool[toolName] + requiredFields, hasRequirements := RequiredFieldsByTool[toolName] if hasRequirements { - missingFields := findMissingRequiredFields(parsedInput, requiredGroups) + missingFields := findMissingRequiredFields(parsedInput, requiredFields) if len(missingFields) > 0 { info.IsTruncated = true info.TruncationType = TruncationTypeMissingFields @@ -259,21 +253,12 @@ func extractParsedFieldNames(parsed map[string]interface{}) map[string]string { return fields } -// findMissingRequiredFields checks which required field groups are unsatisfied. -// Each group is a slice of alternative field names; the group is satisfied when ANY alternative exists. -// Returns the list of unsatisfied groups (represented by their alternatives joined with "/"). -func findMissingRequiredFields(parsed map[string]interface{}, requiredGroups [][]string) []string { +// findMissingRequiredFields checks which required fields are missing from the parsed input. +func findMissingRequiredFields(parsed map[string]interface{}, required []string) []string { var missing []string - for _, group := range requiredGroups { - satisfied := false - for _, field := range group { - if _, exists := parsed[field]; exists { - satisfied = true - break - } - } - if !satisfied { - missing = append(missing, strings.Join(group, "/")) + for _, field := range required { + if _, exists := parsed[field]; !exists { + missing = append(missing, field) } } return missing