fix(tooling): strip empty arrays from WebSearch and fix Codex prompt body propagation (#1852, #1853)

- Strips empty arrays from streaming tool arguments in responsesTransformer and openai-to-claude
- Fixes zero-results issue when Claude Code invokes WebSearch with allowed_domains: []
- Resolves prompt body propagation failures by ensuring messages-to-input mapping happens before system role conversion in codex executor
This commit is contained in:
Antigravity Assistant
2026-05-01 15:45:54 -03:00
parent 35d56358b8
commit c766aa5403
6 changed files with 73 additions and 43 deletions

View File

@@ -8,6 +8,16 @@ _The most complete open-source AI proxy — **one endpoint**, **160+ providers**
**Chat Completions • Responses API • Embeddings • Image Generation • Video • Music • Audio Speech/Transcription • Reranking • Moderations • Web Search • MCP Server • A2A Protocol • 4,600+ Tests • 100% TypeScript**
<br/>
<a href="https://agentrouter.org/register?aff=70LM">
<img src="https://img.shields.io/badge/🎁_GET_$100_FREE_AI_CREDITS-AgentRouter_(no_card_needed)-ff6600?style=for-the-badge&labelColor=1a1a2e&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyTDIgNy41bDEwIDUuNSAxMC01LjVMMTIgMnpNMiAxNy41bDEwIDUuNSAxMC01LjVMMTIgMTIgMiAxNy41eiIvPjwvc3ZnPg==" alt="Get $100 Free AI Credits" height="40"/>
</a>
<sub>🔥 <b>Limited offer:</b> Sign up at <a href="https://agentrouter.org/register?aff=70LM"><b>AgentRouter</b></a> and get <b>$100 in free AI credits</b></br> Access GPT-5, Claude, Gemini, DeepSeek & 100+ models. No credit card required. <a href="https://agentrouter.org/register?aff=70LM"><b>Claim your credits →</b></a></sub>
<br/>
<a href="https://trendshift.io/repositories/23589" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23589" alt="diegosouzapw%2FOmniRoute | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
[🚀 Quick Start](#-quick-start) • [💡 Features](#-key-features) • [🗜️ Compression](#%EF%B8%8F-prompt-compression--save-15-75-tokens-automatically) • [💰 Pricing](#-pricing-at-a-glance) • [🎯 Use Cases](#-use-cases--ready-made-combo-playbooks) • [🌍 Proxy](#-bypass-geographic-blocks--use-ai-from-any-country) • [❓ FAQ](#-frequently-asked-questions) • [📖 Docs](#-documentation) • [💬 WhatsApp](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
@@ -852,19 +862,19 @@ No proxy? Use the built-in **1proxy** integration for **hundreds of free, valida
> Combine all free providers into one unbreakable combo — OmniRoute auto-routes between them when quota runs out.
| Provider | Prefix | Free Models | Quota |
| ----------------- | ----------- | ------------------------------------------------------------- | ----------------- |
| **Kiro** | `kr/` | Claude Sonnet 4.5, Haiku 4.5, Opus 4.6 | ♾️ Unlimited |
| **Qoder** | `if/` | kimi-k2-thinking, qwen3-coder-plus, deepseek-r1, minimax-m2.1 | ♾️ Unlimited |
| **Qwen** | `qw/` | qwen3-coder-plus, qwen3-coder-flash, qwen3-coder-next | ♾️ Unlimited |
| **Pollinations** | `pol/` | GPT-5, Claude, Gemini, DeepSeek, Llama 4, Mistral | No key needed |
| **LongCat** | `lc/` | LongCat-Flash-Lite | 50M tokens/day 🔥 |
| **Gemini CLI** | `gc/` | gemini-3-flash, gemini-2.5-pro | 180K tok/mo |
| **Cloudflare AI** | `cf/` | 50+ models (Llama, Gemma, Mistral, Whisper) | 10K Neurons/day |
| **Groq** | `groq/` | Llama 3.3 70B, Qwen3 32B, Kimi K2 | 14.4K RPD |
| **NVIDIA NIM** | `nvidia/` | 129 models (DeepSeek, Llama, GLM, Kimi) | ~40 RPM |
| **Cerebras** | `cerebras/` | Qwen3 235B, GPT-OSS 120B, Llama 3.1 | 1M tok/day |
| **Scaleway** | `scw/` | Qwen3 235B, Llama 70B, DeepSeek V3 | 1M tokens (EU) |
| Provider | Prefix | Free Models | Quota |
| ----------------- | ----------- | ------------------------------------------------------------- | -------------------- |
| **Kiro** | `kr/` | Claude Sonnet 4.5, Haiku 4.5, Opus 4.6 | 50 CREDITS per month |
| **Qoder** | `if/` | kimi-k2-thinking, qwen3-coder-plus, deepseek-r1, minimax-m2.1 | ♾️ Unlimited |
| **Qwen** | `qw/` | qwen3-coder-plus, qwen3-coder-flash, qwen3-coder-next | ♾️ Unlimited |
| **Pollinations** | `pol/` | GPT-5, Claude, Gemini, DeepSeek, Llama 4, Mistral | No key needed |
| **LongCat** | `lc/` | LongCat-Flash-Lite | 50M tokens/day 🔥 |
| **Gemini CLI** | `gc/` | gemini-3-flash, gemini-2.5-pro | 180K tok/mo |
| **Cloudflare AI** | `cf/` | 50+ models (Llama, Gemma, Mistral, Whisper) | 10K Neurons/day |
| **Groq** | `groq/` | Llama 3.3 70B, Qwen3 32B, Kimi K2 | 14.4K RPD |
| **NVIDIA NIM** | `nvidia/` | 129 models (DeepSeek, Llama, GLM, Kimi) | ~40 RPM |
| **Cerebras** | `cerebras/` | Qwen3 235B, GPT-OSS 120B, Llama 3.1 | 1M tok/day |
| **Scaleway** | `scw/` | Qwen3 235B, Llama 70B, DeepSeek V3 | 1M tokens (EU) |
<details>
<summary><b>📖 25+ more free providers — Groq, Cerebras, Mistral, GitHub Models, OpenRouter, and more</b></summary>

View File

@@ -1182,6 +1182,24 @@ export class CodexExecutor extends BaseExecutor {
body.service_tier = requestDefaults.serviceTier;
}
// Issue #1832 & #1853: Map messages to input for clients like Cursor 5.5 that use responses/compact but send messages instead of input.
// This MUST run before convertSystemToDeveloperRole and stripStoredItemReferences.
if (!body.input && Array.isArray(body.messages)) {
body.input = body.messages.map((msg: any) => ({
type: "message",
role: typeof msg.role === "string" ? msg.role : "user",
content:
typeof msg.content === "string"
? [{ type: "input_text", text: msg.content }]
: Array.isArray(msg.content)
? msg.content.map((c: any) => {
if (c && c.type === "text") return { type: "input_text", text: c.text };
return c;
})
: [],
}));
}
// ── Cache-aware system prompt handling (both paths) ──
//
// Convert system → developer role IN-PLACE so system prompts remain in the
@@ -1251,23 +1269,6 @@ export class CodexExecutor extends BaseExecutor {
// so any references to previous response items would cause 404 errors.
stripStoredItemReferences(body);
// Issue #1832: Map messages to input for clients like Cursor 5.5 that use responses/compact but send messages instead of input
if (!body.input && Array.isArray(body.messages)) {
body.input = body.messages.map((msg: any) => ({
type: "message",
role: typeof msg.role === "string" ? msg.role : "user",
content:
typeof msg.content === "string"
? [{ type: "input_text", text: msg.content }]
: Array.isArray(msg.content)
? msg.content.map((c: any) => {
if (c && c.type === "text") return { type: "input_text", text: c.text };
return c;
})
: [],
}));
}
// Issue #806: Even for native passthrough, some clients (purist completions) might indiscriminately inject
// a `messages` or `prompt` array which the strict Codex Responses schema rejects.
delete body.messages;

View File

@@ -123,10 +123,17 @@ export function translateNonStreamingResponse(
toString(itemObj.call_id) ||
toString(itemObj.id) ||
`call_${Date.now()}_${toolCalls.length}`;
let argsToEmit = itemObj.arguments;
if (argsToEmit != null && typeof argsToEmit === "object" && !Array.isArray(argsToEmit)) {
const cleaned = { ...argsToEmit };
for (const [k, v] of Object.entries(cleaned)) {
if (v === "" || (Array.isArray(v) && v.length === 0)) delete cleaned[k];
}
argsToEmit = cleaned;
}
const fnArgs =
typeof itemObj.arguments === "string"
? itemObj.arguments
: JSON.stringify(itemObj.arguments || {});
typeof argsToEmit === "string" ? argsToEmit : JSON.stringify(argsToEmit || {});
const rawName = toString(itemObj.name);
// Strip Claude OAuth proxy_ prefix using toolNameMap
const resolvedName = toolNameMap?.get(rawName) ?? rawName;

View File

@@ -223,13 +223,13 @@ export function createResponsesApiTransformStream(logger = null) {
if (callId && !state.funcItemDone[idx]) {
let args = state.funcArgsBuf[idx] || "{}";
// Fix #1674: Final cleanup of empty string placeholders that might have been split across delta chunks
// Fix #1674 & #1852: Final cleanup of empty string and empty array placeholders
try {
const parsed = JSON.parse(args);
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
let modified = false;
for (const [k, v] of Object.entries(parsed)) {
if (v === "") {
if (v === "" || (Array.isArray(v) && v.length === 0)) {
delete parsed[k];
modified = true;
}
@@ -506,12 +506,13 @@ export function createResponsesApiTransformStream(logger = null) {
const refCallId = state.funcCallIds[tcIdx] || newCallId;
let deltaStr = tc.function.arguments;
// Fix #1674: cx/gpt-5.5 injects empty strings for optional parameters.
// We strip these directly from the streaming deltas to avoid breaking strict clients like Claude Code.
if (deltaStr.includes('""')) {
// Fix #1674 & #1852: Strip empty strings and empty arrays from streaming deltas
if (deltaStr.includes('""') || deltaStr.includes("[]") || deltaStr.includes("[ ]")) {
deltaStr = deltaStr
.replace(/,"[a-zA-Z0-9_]+":""/g, "")
.replace(/"[a-zA-Z0-9_]+":"",/g, "");
.replace(/"[a-zA-Z0-9_]+":"",/g, "")
.replace(/,"[a-zA-Z0-9_]+":\s*\[\s*\]/g, "")
.replace(/"[a-zA-Z0-9_]+":\s*\[\s*\],?/g, "");
}
if (refCallId) {

View File

@@ -633,10 +633,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
let argsToEmit = item.arguments;
if (argsToEmit != null && typeof argsToEmit === "object" && !Array.isArray(argsToEmit)) {
// Fix #1674: Strip empty string placeholders emitted by GPT-5.5 for optional fields
// Fix #1674 & #1852: Strip empty string and array placeholders emitted by GPT-5.5 for optional fields
const cleaned = { ...argsToEmit };
for (const [k, v] of Object.entries(cleaned)) {
if (v === "") delete cleaned[k];
if (v === "" || (Array.isArray(v) && v.length === 0)) delete cleaned[k];
}
argsToEmit = cleaned;
}
@@ -685,7 +685,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
if (argsToEmit != null && typeof argsToEmit === "object" && !Array.isArray(argsToEmit)) {
const cleaned = { ...argsToEmit };
for (const [k, v] of Object.entries(cleaned)) {
if (v === "") delete cleaned[k];
if (v === "" || (Array.isArray(v) && v.length === 0)) delete cleaned[k];
}
argsToEmit = cleaned;
}

View File

@@ -184,10 +184,21 @@ export function openaiToClaudeResponse(chunk, state) {
if (tc.function?.arguments) {
const toolInfo = state.toolCalls.get(idx);
if (toolInfo) {
let deltaStr = tc.function.arguments;
// Fix #1852: Strip empty string and array placeholders from streaming tool arguments
if (deltaStr.includes('""') || deltaStr.includes("[]") || deltaStr.includes("[ ]")) {
deltaStr = deltaStr
.replace(/,"[a-zA-Z0-9_]+":""/g, "")
.replace(/"[a-zA-Z0-9_]+":"",/g, "")
.replace(/,"[a-zA-Z0-9_]+":\s*\[\s*\]/g, "")
.replace(/"[a-zA-Z0-9_]+":\s*\[\s*\],?/g, "");
}
results.push({
type: "content_block_delta",
index: toolInfo.blockIndex,
delta: { type: "input_json_delta", partial_json: tc.function.arguments },
delta: { type: "input_json_delta", partial_json: deltaStr },
});
}
}