From 88872baffc8d9f74915c8d9af4e205518abc6ca2 Mon Sep 17 00:00:00 2001 From: taetaetae Date: Thu, 5 Feb 2026 23:27:35 +0900 Subject: [PATCH 1/2] fix(kiro): handle empty content in Claude format assistant messages Problem: - PR #181 fixed empty content for OpenAI format (kiro_openai_request.go) - But Claude format (kiro_claude_request.go) was not fixed - OpenCode uses Claude format (/v1/messages endpoint) - When assistant messages have only tool_use (no text), content becomes empty - This causes 'Improperly formed request' errors from Kiro API Example of problematic message format: { "role": "assistant", "content": [ {"type": "tool_use", "id": "...", "name": "todowrite", "input": {...}} ] } Solution: - Add empty content fallback in BuildAssistantMessageStruct (Claude format) - Same fix as PR #181 applied to kiro_openai_request.go Fixes compaction failures for OpenCode + Quotio + CLIProxyAPIPlus + Kiro stack --- .../translator/kiro/claude/kiro_claude_request.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/translator/kiro/claude/kiro_claude_request.go b/internal/translator/kiro/claude/kiro_claude_request.go index 4e498c24..f663a419 100644 --- a/internal/translator/kiro/claude/kiro_claude_request.go +++ b/internal/translator/kiro/claude/kiro_claude_request.go @@ -883,8 +883,21 @@ func BuildAssistantMessageStruct(msg gjson.Result) KiroAssistantResponseMessage contentBuilder.WriteString(content.String()) } + // CRITICAL FIX: Kiro API requires non-empty content for assistant messages + // This can happen with compaction requests where assistant messages have only tool_use + // (no text content). Without this fix, Kiro API returns "Improperly formed request" error. + finalContent := contentBuilder.String() + if strings.TrimSpace(finalContent) == "" { + if len(toolUses) > 0 { + finalContent = "I'll help you with that." + } else { + finalContent = "I understand." + } + log.Debugf("kiro: assistant content was empty, using default: %s", finalContent) + } + return KiroAssistantResponseMessage{ - Content: contentBuilder.String(), + Content: finalContent, ToolUses: toolUses, } } From 14f044ce4f92d756c5292a37d7e1a27b92b0fc96 Mon Sep 17 00:00:00 2001 From: taetaetae Date: Thu, 5 Feb 2026 23:36:57 +0900 Subject: [PATCH 2/2] refactor: extract default assistant content to shared constants Apply code review feedback from gemini-code-assist: - Move fallback strings to kirocommon package as exported constants - Update kiro_claude_request.go to use shared constants - Update kiro_openai_request.go to use shared constants - Improves maintainability and avoids duplication --- internal/translator/kiro/claude/kiro_claude_request.go | 4 ++-- internal/translator/kiro/common/constants.go | 8 ++++++++ internal/translator/kiro/openai/kiro_openai_request.go | 7 ++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/translator/kiro/claude/kiro_claude_request.go b/internal/translator/kiro/claude/kiro_claude_request.go index f663a419..259ae9f5 100644 --- a/internal/translator/kiro/claude/kiro_claude_request.go +++ b/internal/translator/kiro/claude/kiro_claude_request.go @@ -889,9 +889,9 @@ func BuildAssistantMessageStruct(msg gjson.Result) KiroAssistantResponseMessage finalContent := contentBuilder.String() if strings.TrimSpace(finalContent) == "" { if len(toolUses) > 0 { - finalContent = "I'll help you with that." + finalContent = kirocommon.DefaultAssistantContentWithTools } else { - finalContent = "I understand." + finalContent = kirocommon.DefaultAssistantContent } log.Debugf("kiro: assistant content was empty, using default: %s", finalContent) } diff --git a/internal/translator/kiro/common/constants.go b/internal/translator/kiro/common/constants.go index 2327ab59..ab000972 100644 --- a/internal/translator/kiro/common/constants.go +++ b/internal/translator/kiro/common/constants.go @@ -29,6 +29,14 @@ const ( // InlineCodeMarker is the markdown inline code marker (backtick). InlineCodeMarker = "`" + // DefaultAssistantContentWithTools is the fallback content for assistant messages + // that have tool_use but no text content. Kiro API requires non-empty content. + DefaultAssistantContentWithTools = "I'll help you with that." + + // DefaultAssistantContent is the fallback content for assistant messages + // that have no content at all. Kiro API requires non-empty content. + DefaultAssistantContent = "I understand." + // KiroAgenticSystemPrompt is injected only for -agentic models to prevent timeouts on large writes. // AWS Kiro API has a 2-3 minute timeout for large file write operations. KiroAgenticSystemPrompt = ` diff --git a/internal/translator/kiro/openai/kiro_openai_request.go b/internal/translator/kiro/openai/kiro_openai_request.go index 25800928..9515848f 100644 --- a/internal/translator/kiro/openai/kiro_openai_request.go +++ b/internal/translator/kiro/openai/kiro_openai_request.go @@ -718,13 +718,10 @@ func buildAssistantMessageFromOpenAI(msg gjson.Result) KiroAssistantResponseMess // This can happen with compaction requests or error recovery scenarios finalContent := contentBuilder.String() if strings.TrimSpace(finalContent) == "" { - const defaultAssistantContentWithTools = "I'll help you with that." - const defaultAssistantContent = "I understand." - if len(toolUses) > 0 { - finalContent = defaultAssistantContentWithTools + finalContent = kirocommon.DefaultAssistantContentWithTools } else { - finalContent = defaultAssistantContent + finalContent = kirocommon.DefaultAssistantContent } log.Debugf("kiro-openai: assistant content was empty, using default: %s", finalContent) }