mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix: unwrap Cline response envelope (#6046)
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
This commit is contained in:
@@ -28,6 +28,8 @@
|
||||
|
||||
- **antigravity/gemini tool calls (`400 Unknown name "multipleOf"`):** requests routed to antigravity/gemini models with tools that declare a `multipleOf` numeric constraint failed with a hard upstream `400` (`Invalid JSON payload received. Unknown name "multipleOf"`). `multipleOf` is not part of the Gemini/antigravity OpenAPI 3.0 schema subset and was not being stripped from `function_declarations`. It is now removed at every schema level (top-level, nested, and array `items`), alongside the other unsupported constraints; `minimum`/`maximum` remain untouched. Regression guard: `tests/unit/gemini-multipleof-2309.test.ts`. (thanks @abil0321)
|
||||
|
||||
- **cline (empty/false-502 non-streaming responses):** the Cline gateway can wrap OpenAI-compatible chat completions in a `{ success, data: { choices, usage, … } }` envelope. The non-streaming path checked the top-level body for empty content before unwrapping, so a valid Cline response was treated as malformed. The body is now unwrapped via `unwrapClineNonStreamingEnvelope()` right after the provider-envelope unwrap and before the empty-content check, closing the remaining gap from #5956/#5924. Regression guard: `tests/unit/cline-response-envelope.test.ts`. ([#5956](https://github.com/diegosouzapw/OmniRoute/issues/5956) — thanks @KooshaPari)
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
- **test (deflake `setup-claude`):** `tests/unit/cli/setup-claude.test.ts` failed ~50% of runs with `Unable to deserialize cloned data due to invalid or unsupported version` at file teardown (all subtests passed), randomly reddening `Unit Tests fast-path (2/2)` / `Fast Quality Gates` across the PR→release queue. Root cause: `node --test` streams each file's report to the parent as V8-serialized frames on fd 1 (stdout), and the CLI helper under test (`syncClaudeProfilesFromModels`) prints progress via `console.log` — that stdout output interleaved with the serialized frames and corrupted the stream. The test now silences the stdout-writing `console` methods for the file's duration (no assertion inspects stdout), making it deterministic (15/15 green locally). ([#5959](https://github.com/diegosouzapw/OmniRoute/issues/5959))
|
||||
|
||||
@@ -311,7 +311,7 @@
|
||||
"tests/unit/translator-helper-branches.test.ts": 870,
|
||||
"tests/unit/translator-openai-responses-req.test.ts": 1172,
|
||||
"tests/unit/translator-openai-to-gemini.test.ts": 1579,
|
||||
"tests/unit/translator-openai-to-kiro.test.ts": 1088,
|
||||
"tests/unit/translator-openai-to-kiro.test.ts": 1093,
|
||||
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1234,
|
||||
"tests/unit/usage-service-hardening.test.ts": 1633,
|
||||
"tests/unit/vscode-token-routes.test.ts": 1212,
|
||||
|
||||
@@ -252,6 +252,7 @@ import { isCompactResponsesEndpoint } from "../executors/codex.ts";
|
||||
import { buildCodexQuotaPersistence } from "./chatCore/codexQuota.ts";
|
||||
import { invalidateCodexQuotaCache } from "../services/codexQuotaFetcher.ts";
|
||||
import { translateNonStreamingResponse } from "./responseTranslator.ts";
|
||||
import { unwrapClineNonStreamingEnvelope } from "./chatCore/clineResponseEnvelope.ts";
|
||||
import { extractUsageFromResponse } from "./usageExtractor.ts";
|
||||
import {
|
||||
sanitizeOpenAIResponse,
|
||||
@@ -3612,6 +3613,7 @@ export async function handleChatCore({
|
||||
}
|
||||
responseBody = unwrapped;
|
||||
}
|
||||
responseBody = unwrapClineNonStreamingEnvelope(provider, responseBody);
|
||||
|
||||
// Check for empty content response (fake success) - trigger fallback
|
||||
if (isEmptyContentResponse(responseBody)) {
|
||||
|
||||
25
open-sse/handlers/chatCore/clineResponseEnvelope.ts
Normal file
25
open-sse/handlers/chatCore/clineResponseEnvelope.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
function isRecord(value: unknown): value is JsonRecord {
|
||||
return !!value && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function hasOpenAIChoices(value: unknown): boolean {
|
||||
return isRecord(value) && Array.isArray(value.choices);
|
||||
}
|
||||
|
||||
export function unwrapClineNonStreamingEnvelope(provider: string, responseBody: unknown): unknown {
|
||||
if (provider !== "cline" || !isRecord(responseBody)) {
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
const data = responseBody.data;
|
||||
if (!hasOpenAIChoices(data)) {
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
return {
|
||||
...data,
|
||||
usage: isRecord(data) && data.usage !== undefined ? data.usage : responseBody.usage,
|
||||
};
|
||||
}
|
||||
@@ -92,6 +92,7 @@
|
||||
"tests/unit/claude-passthrough-stream-boolean.test.ts",
|
||||
"tests/unit/claude-passthrough-thinking-2454.test.ts",
|
||||
"tests/unit/cli-simulate.test.ts",
|
||||
"tests/unit/cline-response-envelope.test.ts",
|
||||
"tests/unit/clinepass-provider.test.ts",
|
||||
"tests/unit/codex-failover.test.ts",
|
||||
"tests/unit/codex-session-affinity-reset-aware-5903.test.ts",
|
||||
|
||||
27
tests/unit/cline-response-envelope.test.ts
Normal file
27
tests/unit/cline-response-envelope.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { unwrapClineNonStreamingEnvelope } = await import(
|
||||
"../../open-sse/handlers/chatCore/clineResponseEnvelope.ts"
|
||||
);
|
||||
|
||||
test("unwrapClineNonStreamingEnvelope extracts Cline wrapped chat completions", () => {
|
||||
const wrapped = {
|
||||
success: true,
|
||||
data: {
|
||||
id: "chatcmpl_cline",
|
||||
model: "cline/model",
|
||||
choices: [{ index: 0, message: { role: "assistant", content: "ok" } }],
|
||||
usage: { prompt_tokens: 3, completion_tokens: 2, total_tokens: 5 },
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(unwrapClineNonStreamingEnvelope("cline", wrapped), wrapped.data);
|
||||
});
|
||||
|
||||
test("unwrapClineNonStreamingEnvelope keeps non-Cline and malformed envelopes untouched", () => {
|
||||
const wrapped = { success: true, data: { message: "missing choices" } };
|
||||
|
||||
assert.equal(unwrapClineNonStreamingEnvelope("openai", wrapped), wrapped);
|
||||
assert.equal(unwrapClineNonStreamingEnvelope("cline", wrapped), wrapped);
|
||||
});
|
||||
Reference in New Issue
Block a user