diff --git a/README.md b/README.md index 74e2f571bc..efb8578db3 100644 --- a/README.md +++ b/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** +
+ + + Get $100 Free AI Credits + + +🔥 Limited offer: Sign up at AgentRouter and get $100 in free AI credits
Access GPT-5, Claude, Gemini, DeepSeek & 100+ models. No credit card required. Claim your credits →
+ +
+ diegosouzapw%2FOmniRoute | Trendshift [🚀 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) |
📖 25+ more free providers — Groq, Cerebras, Mistral, GitHub Models, OpenRouter, and more diff --git a/open-sse/executors/codex.ts b/open-sse/executors/codex.ts index 5bc413ba96..e84be095b6 100644 --- a/open-sse/executors/codex.ts +++ b/open-sse/executors/codex.ts @@ -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; diff --git a/open-sse/handlers/responseTranslator.ts b/open-sse/handlers/responseTranslator.ts index 603e7c64da..c85bf34ea2 100644 --- a/open-sse/handlers/responseTranslator.ts +++ b/open-sse/handlers/responseTranslator.ts @@ -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; diff --git a/open-sse/transformer/responsesTransformer.ts b/open-sse/transformer/responsesTransformer.ts index 3006a69322..93e913b46f 100644 --- a/open-sse/transformer/responsesTransformer.ts +++ b/open-sse/transformer/responsesTransformer.ts @@ -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) { diff --git a/open-sse/translator/response/openai-responses.ts b/open-sse/translator/response/openai-responses.ts index 1ab95d06e4..77289dfb71 100644 --- a/open-sse/translator/response/openai-responses.ts +++ b/open-sse/translator/response/openai-responses.ts @@ -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; } diff --git a/open-sse/translator/response/openai-to-claude.ts b/open-sse/translator/response/openai-to-claude.ts index f3e8ed81ec..801594f01e 100644 --- a/open-sse/translator/response/openai-to-claude.ts +++ b/open-sse/translator/response/openai-to-claude.ts @@ -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 }, }); } }