mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
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:
36
README.md
36
README.md
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user