mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
* fix(chatcore): exempt opencode client from the default 128-tool truncation The default MAX_TOOLS_LIMIT (128) cap made truncateToolList blind-slice tools.slice(0, 128), dropping opencode's built-in task tool and part of its MCP tools when the inbound list exceeded 128 — so models routed through OmniRoute could not launch subagents or reach all their tools. Detect the opencode client (any x-opencode-* header, or 'opencode' in the user-agent) and bypass ONLY the speculative 128 default. A known provider ceiling (proactive PROVIDER_TOOL_LIMITS or a detected limit) always wins and still truncates, even for opencode, so upstreams with real hard limits (e.g. grok-cli 200) keep their 400-avoidance guard. Non-opencode clients are unchanged. - requestFormat.ts: add isOpencodeClient(headers, userAgent) + expose it on resolveChatCoreRequestFormat. - toolLimitDetector.ts: add getKnownToolLimit(); getEffectiveToolLimit becomes getKnownToolLimit(provider) ?? DEFAULT_LIMIT (byte-identical for existing callers). - upstreamBody.ts: truncateToolList takes bypassDefaultToolLimit and encodes the precedence; fix cosmetic debug-log count. - chatCore.ts: thread the flag into prepareUpstreamBody. - tests: extend tool-limit-detector unit tests. * refactor(tools): accept nullable provider in tool-limit resolvers Address PR review: widen getKnownToolLimit / getEffectiveToolLimit to (provider: string | null | undefined) to match the call sites in truncateToolList, and add unit assertions covering null/undefined providers (getKnownToolLimit -> null, getEffectiveToolLimit -> 128). --------- Co-authored-by: DKotsyuba <16292493+DKotsyuba@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
112 lines
4.2 KiB
TypeScript
112 lines
4.2 KiB
TypeScript
/**
|
|
* chatCore request endpoint/format resolvers (Quality Gate v2 / Fase 9 — chatCore god-file
|
|
* decomposition, #3501).
|
|
*
|
|
* Pure slice of handleChatCore's request-setup phase: derives the wire-format facts of an inbound
|
|
* request from its endpoint, body, provider, and user-agent — the source format, whether it targets
|
|
* the Responses endpoint, native-Codex passthrough eligibility, Droid CLI / Copilot detection, and
|
|
* the effective client response format (an OpenAI Responses shape off a non-/responses, non-Droid
|
|
* endpoint collapses back to plain OpenAI). Side-effect-free; behaviour is byte-identical to the
|
|
* previous inline block. Sits alongside resolveChatCoreRequestSetup as the request-setup phase grows.
|
|
*/
|
|
|
|
import { detectFormatFromEndpoint } from "../../services/provider.ts";
|
|
import { shouldUseNativeCodexPassthrough } from "./passthroughHelpers.ts";
|
|
import { FORMATS } from "../../translator/formats.ts";
|
|
|
|
/** True when the request originates from a Copilot client (matched by user-agent or any header). */
|
|
function isCopilotClient(
|
|
headers: Headers | Record<string, unknown> | null | undefined,
|
|
userAgent?: string | null
|
|
) {
|
|
const isMatch = (value: unknown) =>
|
|
typeof value === "string" && value.toLowerCase().includes("copilot");
|
|
|
|
if (isMatch(userAgent)) return true;
|
|
|
|
if (headers instanceof Headers) {
|
|
for (const [key, value] of headers as unknown as Iterable<[string, string]>) {
|
|
if (isMatch(key) || isMatch(value)) return true;
|
|
}
|
|
} else if (headers && typeof headers === "object") {
|
|
for (const [key, value] of Object.entries(headers)) {
|
|
if (isMatch(key) || isMatch(value)) return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isOpencodeClient(
|
|
headers: Headers | Record<string, unknown> | null | undefined,
|
|
userAgent?: string | null
|
|
): boolean {
|
|
const matchesUserAgent = (value: unknown) =>
|
|
typeof value === "string" && value.toLowerCase().includes("opencode");
|
|
const matchesHeaderKey = (key: string) => key.toLowerCase().startsWith("x-opencode-");
|
|
|
|
if (matchesUserAgent(userAgent)) return true;
|
|
|
|
if (headers instanceof Headers) {
|
|
for (const [key, value] of headers as unknown as Iterable<[string, string]>) {
|
|
if (matchesHeaderKey(key) || (key.toLowerCase() === "user-agent" && matchesUserAgent(value))) {
|
|
return true;
|
|
}
|
|
}
|
|
} else if (headers && typeof headers === "object") {
|
|
for (const [key, value] of Object.entries(headers)) {
|
|
if (matchesHeaderKey(key) || (key.toLowerCase() === "user-agent" && matchesUserAgent(value))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Resolve the per-request endpoint/format facts at the top of handleChatCore. Pure: a function of
|
|
* the inbound endpoint, the (possibly already-mutated) body, the resolved provider, and the
|
|
* user-agent.
|
|
*/
|
|
export function resolveChatCoreRequestFormat(opts: {
|
|
clientRawRequest:
|
|
| { endpoint?: unknown; headers?: Headers | Record<string, unknown> | null }
|
|
| null
|
|
| undefined;
|
|
body: unknown;
|
|
provider: string | null | undefined;
|
|
userAgent: string | null | undefined;
|
|
}) {
|
|
const { clientRawRequest, body, provider, userAgent } = opts;
|
|
const endpointPath = String(clientRawRequest?.endpoint || "");
|
|
const sourceFormat = detectFormatFromEndpoint(body, endpointPath);
|
|
const isResponsesEndpoint =
|
|
/\/responses(?=\/|$)/i.test(endpointPath) || /^responses(?=\/|$)/i.test(endpointPath);
|
|
const nativeCodexPassthrough = shouldUseNativeCodexPassthrough({
|
|
provider,
|
|
sourceFormat,
|
|
endpointPath,
|
|
});
|
|
const isDroidCLI =
|
|
userAgent?.toLowerCase().includes("droid") || userAgent?.toLowerCase().includes("codex-cli");
|
|
const copilotCompatibleReasoning = isCopilotClient(clientRawRequest?.headers, userAgent);
|
|
const isOpencodeClientRequest = isOpencodeClient(clientRawRequest?.headers, userAgent);
|
|
const clientResponseFormat =
|
|
sourceFormat === FORMATS.OPENAI_RESPONSES && !isResponsesEndpoint && !isDroidCLI
|
|
? FORMATS.OPENAI
|
|
: sourceFormat;
|
|
return {
|
|
endpointPath,
|
|
sourceFormat,
|
|
isResponsesEndpoint,
|
|
nativeCodexPassthrough,
|
|
isDroidCLI,
|
|
copilotCompatibleReasoning,
|
|
isOpencodeClient: isOpencodeClientRequest,
|
|
clientResponseFormat,
|
|
};
|
|
}
|
|
|
|
export type ChatCoreRequestFormat = ReturnType<typeof resolveChatCoreRequestFormat>;
|