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**
+
+
+
+
+
+
+🔥 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 →
+
+
+
[🚀 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 },
});
}
}