gjson.Parse() marks any string starting with { or [ as gjson.JSON type,
even when the content is not valid JSON (e.g. macOS plist format, truncated
tool results). This caused sjson.SetRaw to embed non-JSON content directly
into the Gemini API request payload, producing 400 errors.
Add json.Valid() check before using SetRaw to ensure only actually valid
JSON is embedded raw. Non-JSON content now falls through to sjson.Set
which properly escapes it as a JSON string.
Fixes#2161
The Claude-to-Gemini translator derived function names by splitting
tool_use_id on "-", which produced empty strings for IDs with exactly
2 segments (e.g. toolu_tool-<uuid>). Replace the string-splitting
heuristic with a lookup map built from tool_use blocks during the
main processing loop, with fallback to the raw ID on miss.
When assistant has tool_calls but no text content, the translator
emitted an empty message into the Responses API input array before
function_call items. The API then couldn't match function_call_output
to its function_call by call_id, returning:
No tool output found for function call ...
Only emit assistant messages that have content parts. Tool-call-only
messages now produce function_call items directly.
Added 9 tests for tool calling translation covering single/parallel
calls, multi-turn conversations, name shortening, empty content
edge cases, and call_id integrity.
1. Claude translator: add CleanJSONSchemaForGemini() to sanitize tool
input schemas (removes $schema, anyOf, const, format, etc.) and
delete eager_input_streaming from tool declarations. Remove fragile
bytes.Replace for format:"uri" now covered by schema cleaner.
2. Gemini native translator: filter out content entries with empty or
missing parts arrays to prevent Gemini API 400 error "required
oneof field 'data' must have one initialized field".
Both fixes align gemini-cli with protections already present in the
antigravity translator.
when Amp or Claude Code sends functionResponse with an empty name in Gemini
conversation history, the Gemini API rejects the request with 400
"Name cannot be empty". this fix backfills empty names from the
corresponding preceding functionCall parts using positional matching.
covers all three Gemini translator paths:
- gemini/gemini (direct API key)
- antigravity/gemini (OAuth)
- gemini-cli/gemini (Gemini CLI)
also switches fixCLIToolResponse pending group matching from LIFO to
FIFO to correctly handle multiple sequential tool call groups.
fixes#1903
The Gemini v1internal (cloudcode-pa) and Antigravity Manager endpoints
require camelCase "systemInstruction" in request JSON. The current
snake_case "system_instruction" causes system prompts to be silently
ignored when routing through these endpoints.
Replace all "system_instruction" JSON keys with "systemInstruction" in
chat-completions and responses request translators.
test(translator): add unit tests for OpenAI to Claude requests and tool result handling
- Introduced tests for converting OpenAI requests to Claude with text, base64 images, and URL images in tool results.
- Refactored `convertClaudeToolResultContent` and related functionality to properly handle raw content with images and text.
- Updated conversion logic to streamline image handling for both base64 and URL formats.
Add util.SanitizeClaudeToolID() to replace non-conforming characters in
tool_use.id fields across all five response translators (gemini, codex,
openai, antigravity, gemini-cli).
Upstream tool names may contain dots or other special characters
(e.g. "fs.readFile") that violate Claude's ID validation regex.
The sanitizer replaces such characters with underscores and provides
a generated fallback for empty IDs.
Fixes#1872, Fixes#1849
Made-with: Cursor
The instructions restore logic was originally needed when the proxy
injected custom instructions (per-model system prompts) into requests.
Since ac802a46 removed the injection system, the proxy no longer
modifies instructions before forwarding. The upstream response's
instructions field now matches the client's original value, making
the restore a no-op.
Also removes unused sjson import.
Closesrouter-for-me/CLIProxyAPI#1868
Claude's Tool Search feature (advanced-tool-use-2025-11-20 beta) adds
defer_loading field to tool definitions. When proxying Claude requests
to Codex or Gemini, this unknown field causes 400 errors upstream.
Strip defer_loading (and cache_control where missing) in all three
Claude-to-upstream translation paths:
- codex/claude: defer_loading + cache_control
- gemini-cli/claude: defer_loading
- gemini/claude: defer_loading
Fixes#1725, Fixes#1375
Add support for Claude's "adaptive" and "auto" thinking modes using `output_config.effort`. Introduce support for new effort level "max" in adaptive thinking. Update thinking logic, validate model capabilities, and extend converters and handling to ensure compatibility with adaptive modes. Adjust static model data with supported levels and refine handling across translators and executors.
## Problem
When using Antigravity Claude models through CLIProxyAPI, the thinking
chain (reasoning content) does not display in the Amp client.
## Root Cause
The Amp client sends `thinking: {"type": "auto"}` in its requests,
but `ConvertClaudeRequestToAntigravity` only handled `"enabled"` and
`"adaptive"` types in its switch statement. The `"auto"` type was
silently ignored, resulting in no `thinkingConfig` being set in the
translated Gemini request. Without `thinkingConfig`, the Antigravity
API returns responses without any thinking content.
Additionally, the Antigravity API for Claude models does not support
`thinkingBudget: -1` (auto mode sentinel). It requires a concrete
positive budget value. The fix uses 128000 as the budget for "auto"
mode, which `ApplyThinking` will then normalize to stay within the
model's actual limits (e.g., capped to `maxOutputTokens - 1`).
## Changes
### internal/translator/antigravity/claude/antigravity_claude_request.go
1. **Add "auto" case** to the thinking type switch statement.
Sets `thinkingBudget: 128000` and `includeThoughts: true`.
The budget is subsequently normalized by `ApplyThinking` based
on model-specific limits.
2. **Add "auto" to hasThinking check** so that interleaved thinking
hints are injected for tool-use scenarios when Amp sends
`thinking.type="auto"`.
### internal/registry/model_definitions_static_data.go
3. **Add Thinking configuration** for `claude-sonnet-4-6`,
`claude-sonnet-4-5`, and `claude-opus-4-6` in
`GetAntigravityModelConfig()` -- these were previously missing,
causing `ApplyThinking` to skip thinking config entirely.
## Testing
- Deployed to Railway test instance (cpa-thinking-test)
- Verified via debug logging that:
- Amp sends `thinking: {"type": "auto"}`
- CPA now translates this to `thinkingConfig: {thinkingBudget: 128000, includeThoughts: true}`
- `ApplyThinking` normalizes the budget to model-specific limits
- Antigravity API receives the correct thinkingConfig
Amp-Thread-ID: https://ampcode.com/threads/T-019ca511-710d-776d-a07c-4b750f871a93
Co-authored-by: Amp <amp@ampcode.com>