mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* fix(sse): reconstruct Claude-format content in synthetic bypass responses
handleBypassRequest() returns a canned response for CLI warmup/title-
extraction patterns without calling the provider. For Claude-format
clients (e.g. Claude Code CLI), the non-streaming path merged translated
SSE chunks by taking message_start.message as-is — but the
openai-to-claude translator always initializes that message with
content: [] and streams the actual text via separate
content_block_start/delta events. Every synthetic Claude-format
bypass response therefore silently returned empty content.
mergeChunksToResponse() now rebuilds the content array from
content_block_start/delta events (mirroring the streaming path) and
carries over stop_reason/stop_sequence from message_delta. Extracted
the response-builder helpers (createOpenAIResponse,
create{Non}StreamingResponse, mergeChunksToResponse) out of
bypassHandler.ts into a new open-sse/utils/bypassResponse.ts module so
this logic has a single owner instead of being duplicated inline.
Co-authored-by: KunN-21 <kunn21.nv@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/2404
* chore(changelog): fragment for #7248
* refactor(sse): extract Claude chunk-merge helpers to fix complexity ratchet
mergeChunksToResponse() regressed both quality ratchets by +1
(complexity 2057>2056, cognitive 891>890). Split the Claude-format
reconstruction into buildClaudeContentBlocks(), applyClaudeMessageDelta()
and mergeClaudeChunks() — same behavior, verified by the existing
bypass-response-claude-merge.test.ts (4/4 passing unchanged).
---------
Co-authored-by: KunN-21 <kunn21.nv@gmail.com>
91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
import { CORS_HEADERS } from "./cors.ts";
|
|
import { detectFormat } from "../services/provider.ts";
|
|
import { SKIP_PATTERNS } from "../config/constants.ts";
|
|
import { createNonStreamingResponse, createStreamingResponse } from "./bypassResponse.ts";
|
|
|
|
/**
|
|
* Check for bypass patterns — return fake response without calling provider.
|
|
*
|
|
* Intentionally limited to Claude CLI requests only because:
|
|
* 1. The bypass patterns (title extraction, warmup, count) are specific to
|
|
* Claude CLI's internal protocol — other clients don't send these patterns.
|
|
* 2. False-positive bypasses would silently break real requests.
|
|
* 3. The SKIP_PATTERNS config allows user-defined patterns for every client.
|
|
*
|
|
* @param {object} body - Request body
|
|
* @param {string} model - Model name
|
|
* @param {string} userAgent - User-Agent header
|
|
* @returns {object|null} Bypass response or null to proceed normally
|
|
*/
|
|
export function handleBypassRequest(body, model, userAgent = "") {
|
|
const normalizedUserAgent = typeof userAgent === "string" ? userAgent : "";
|
|
if (!normalizedUserAgent.includes("claude-cli")) return null;
|
|
if (!body.messages?.length) return null;
|
|
|
|
const messages = body.messages;
|
|
const getText = (content) => {
|
|
if (typeof content === "string") return content;
|
|
if (Array.isArray(content)) {
|
|
return content
|
|
.filter((c) => c.type === "text")
|
|
.map((c) => c.text)
|
|
.join(" ");
|
|
}
|
|
return "";
|
|
};
|
|
|
|
let shouldBypass = false;
|
|
|
|
// Pattern 1: Title extraction (assistant message = "{")
|
|
const lastMsg = messages[messages.length - 1];
|
|
if (lastMsg?.role === "assistant" && lastMsg.content?.[0]?.text === "{") {
|
|
shouldBypass = true;
|
|
}
|
|
|
|
// Pattern 2: Warmup
|
|
if (!shouldBypass) {
|
|
const firstText = getText(messages[0]?.content);
|
|
if (firstText === "Warmup") {
|
|
shouldBypass = true;
|
|
}
|
|
}
|
|
|
|
// Pattern 3: Count
|
|
if (!shouldBypass && messages.length === 1 && messages[0]?.role === "user") {
|
|
const firstText = getText(messages[0]?.content);
|
|
if (firstText === "count") {
|
|
shouldBypass = true;
|
|
}
|
|
}
|
|
|
|
// Pattern 4: Skip patterns
|
|
if (!shouldBypass && SKIP_PATTERNS?.length) {
|
|
const userMessages = messages.filter((m) => m.role === "user");
|
|
const userText = userMessages.map((m) => getText(m.content)).join(" ");
|
|
if (SKIP_PATTERNS.some((p) => userText.includes(p))) {
|
|
shouldBypass = true;
|
|
}
|
|
}
|
|
|
|
// Pattern 5: Quota probe — max_tokens=1 + "quota" keyword (FCC try_quota_mock).
|
|
if (!shouldBypass && body.max_tokens === 1) {
|
|
const userText = messages
|
|
.filter((m) => m.role === "user")
|
|
.map((m) => getText(m.content))
|
|
.join(" ")
|
|
.toLowerCase();
|
|
if (userText.includes("quota")) {
|
|
shouldBypass = true;
|
|
}
|
|
}
|
|
|
|
if (!shouldBypass) return null;
|
|
|
|
const sourceFormat = detectFormat(body);
|
|
const stream = body.stream !== false;
|
|
|
|
return stream
|
|
? createStreamingResponse(sourceFormat, model)
|
|
: createNonStreamingResponse(sourceFormat, model);
|
|
}
|