The OpenAI Chat Completions translator was silently dropping
response.function_call_arguments.delta and
response.function_call_arguments.done Codex SSE events, meaning
tool call arguments were never streamed incrementally to clients.
Add proper handling mirroring the proven Claude translator pattern:
- response.output_item.added: announce tool call (id, name, empty args)
- response.function_call_arguments.delta: stream argument chunks
- response.function_call_arguments.done: emit full args if no deltas
- response.output_item.done: defensive fallback for backward compat
State tracking via HasReceivedArgumentsDelta and HasToolCallAnnounced
ensures no duplicate argument emission and correct behavior for models
like codex-spark that skip delta events entirely.
Change GenerateSearchIndicatorEvents to return [][]byte instead of []sseEvent
for consistency with BuildFallbackTextEvents and other event building functions.
Benefits:
- Consistent API across all event generation functions
- Eliminates intermediate sseEvent type conversion in caller
- Simplifies usage by returning ready-to-send SSE byte slices
This addresses the code quality feedback from PR #226 review.
Consolidate web search handler, SSE event generation, stream analysis,
and MCP HTTP I/O into the executor layer. Merge the separate
kiro_websearch_handler.go back into kiro_executor.go to align with
the single-file-per-executor convention. Translator retains only pure
data types, detection, and payload transformation.
Key changes:
- Move SSE construction (search indicators, fallback text, message_start)
from translator to executor, consistent with streamToChannel pattern
- Move MCP handler (callMcpAPI, setMcpHeaders, fetchToolDescription)
from translator to executor alongside other HTTP I/O
- Reuse applyDynamicFingerprint for MCP UA headers (eliminate duplication)
- Centralize MCP endpoint URL via BuildMcpEndpoint in translator
- Add atomic Set/GetWebSearchDescription for cross-layer tool desc cache
- Thread context.Context through MCP HTTP calls for cancellation support
- Thread usage reporter through all web search API call paths
- Add token expiry pre-check before MCP/GAR calls
- Clean up dead code (GenerateMessageID, webSearchAuthContext fp logic,
ContainsWebSearchTool, StripWebSearchTool)
Kiro/AmazonQ API requires the conversation history to start with a user message.
Some clients (e.g., OpenClaw) send conversations starting with an assistant message,
which is valid for the native Claude API but causes 'Improperly formed request' (400)
on the Kiro endpoint.
This fix detects when the first message has role=assistant and prepends a minimal
placeholder user message ('.') to satisfy the Kiro API's message ordering requirement.
Upstream error: {"message":"Improperly formed request.","reason":null}
Verified: original request returns 400, fixed request returns 200.
Adjacent assistant messages where any message contains tool_calls
were being merged by MergeAdjacentMessages, causing tool_calls to
be silently dropped. This led to orphaned tool results that could
not match any toolUse in history, resulting in Kiro API returning
'Improperly formed request.'
Now assistant messages with tool_calls are kept separate during
merge, preserving the tool call chain integrity.
- Added support for single and array-based `content` cases.
- Enhanced `system_instruction` structure population logic.
- Improved handling of user role assignment for string-based `content`.
fix(translator): restructure message content handling to support multiple content types
- Consolidated `input_text` and `output_text` handling into a single case.
- Added support for processing `input_image` content with associated URLs.
- Standardized the handling of `stop_reason` and `finish_reason` across Codex and Gemini responses.
- Restricted pass-through of specific reasons (`max_tokens`, `stop`) for consistency.
- Enhanced fallback logic for undefined reasons.
Kiro API requires non-empty content on assistant messages, so
CLIProxyAPI injects placeholder text when assistant messages only
contain tool_use blocks (no text). The previous placeholders were
conversational phrases:
- DefaultAssistantContentWithTools: "I'll help you with that."
- DefaultAssistantContent: "I understand."
In agentic sessions with many tool calls, these phrases appeared
dozens of times in conversation history. Opus 4.6 (and likely other
models) picked up on this pattern and started parroting "I'll help
you with that." before every tool call in its actual responses.
Fix: Replace both placeholders with a single dot ".", which
satisfies Kiro's non-empty requirement without giving the model
a phrase to mimic.
- Added support to extract and include `cachedContentTokenCount` in `usage.prompt_tokens_details`.
- Logged warnings for failures to set cached token count for better debugging.
Problem:
- PR #186 fixed empty content for assistant messages and history user messages
- But current user message (isLastMessage == true) was not fixed
- When user message contains only tool_result (no text), content becomes empty
- This causes 'Improperly formed request' errors from Kiro API
- Compaction requests from OpenCode commonly have this pattern
Solution:
- Move empty content check BEFORE the isLastMessage branch
- Apply fallback content to ALL user messages, not just history
- Add DefaultUserContentWithToolResults and DefaultUserContent constants
Fixes compaction failures for OpenCode + Quotio + CLIProxyAPIPlus + Kiro stack
- Replaced all instances of `bytes.Clone` with direct references to enhance efficiency.
- Simplified payload handling across executors and translators by eliminating unnecessary data duplication.
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
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
- Replaced repetitive string operations with a centralized `escapeGJSONPathKey` function.
- Streamlined handling of JSON schema cleaning for Gemini and Antigravity requests.
- Improved payload management by transitioning from byte slices to strings for processing.
- Removed unnecessary cloning of byte slices in several places.
Apply code review feedback from gemini-code-assist:
- Initialize inputMap upfront instead of using nested if blocks
- Combine Exists() and IsObject() checks into single condition
- Remove redundant nil check
Problem:
- PR #162 fixed empty string content but missed array content with tool_use
- OpenCode's compaction requests send assistant messages with content as array
- When content array contains 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:
- Extract tool_use from content array (Anthropic/OpenCode format)
- This is in addition to existing tool_calls handling (OpenAI format)
- The empty content fallback from PR #162 will then work correctly
Fixes compaction failures that persisted after PR #162 merge.
Google official Gemini Python SDK sends thinking_level, thinking_budget,
and include_thoughts (snake_case) instead of thinkingLevel, thinkingBudget,
and includeThoughts (camelCase). This caused thinking configuration to be
ignored when using Python SDK.
Changes:
- Extract layer: extractGeminiConfig now reads snake_case as fallback
- Apply layer: Gemini/CLI/Antigravity appliers clean up snake_case fields
- Translator layer: Gemini->OpenAI/Claude/Codex translators support fallback
- Tests: Added 4 test cases for snake_case field coverage
Fixes#1426