mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +03:00
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host. Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean. Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
import { getOpenAICompatibleType } from "../services/provider.ts";
|
|
|
|
/**
|
|
* Force OpenAI-compatible upstreams onto the native `/responses` endpoint.
|
|
*
|
|
* A Responses-API-shaped request (`input` / `previous_response_id` /
|
|
* `max_output_tokens` / `reasoning`) that carries MCP (`namespace`) or
|
|
* `tool_search*` tools loses the Codex deferred tool-discovery mechanism when
|
|
* OmniRoute downgrades it to `/chat/completions` — so the MCP namespaces never
|
|
* surface to the model and `apply_patch` is mis-handled (#5483). Detecting that
|
|
* shape lets the executor pass it through natively instead of downgrading.
|
|
*/
|
|
|
|
type CredentialsLike = { providerSpecificData?: Record<string, unknown> | null };
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
export function shouldForceResponsesUpstream(
|
|
provider: string,
|
|
body: unknown,
|
|
credentials: CredentialsLike | null
|
|
): boolean {
|
|
if (!provider.startsWith("openai-compatible-")) return false;
|
|
if (!isRecord(body)) return false;
|
|
|
|
const providerSpecificData = credentials?.providerSpecificData ?? null;
|
|
if (providerSpecificData?._omnirouteForceResponsesUpstream === true) return true;
|
|
if (getOpenAICompatibleType(provider, providerSpecificData) === "responses") return false;
|
|
// apiType="chat" means the operator explicitly chose the chat/completions
|
|
// wire. Don't second-guess that choice by forcing /responses just because the
|
|
// body carries namespace tools — the standard namespace→flatten path
|
|
// (openai-responses.ts) handles those correctly for chat backends.
|
|
if (
|
|
providerSpecificData &&
|
|
typeof providerSpecificData.apiType === "string" &&
|
|
providerSpecificData.apiType === "chat"
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const hasResponsesShape =
|
|
body.input !== undefined ||
|
|
body.previous_response_id !== undefined ||
|
|
body.max_output_tokens !== undefined ||
|
|
body.reasoning !== undefined;
|
|
if (!hasResponsesShape) return false;
|
|
|
|
const tools = Array.isArray(body.tools) ? body.tools : [];
|
|
return tools.some((toolValue) => {
|
|
if (!isRecord(toolValue)) return false;
|
|
const toolType = typeof toolValue.type === "string" ? toolValue.type : "";
|
|
return toolType === "namespace" || /^tool_search/.test(toolType);
|
|
});
|
|
}
|
|
|
|
export function withForcedResponsesUpstream<T extends CredentialsLike>(
|
|
provider: string,
|
|
body: unknown,
|
|
credentials: T
|
|
): T {
|
|
if (!shouldForceResponsesUpstream(provider, body, credentials)) return credentials;
|
|
return {
|
|
...credentials,
|
|
providerSpecificData: {
|
|
...credentials.providerSpecificData,
|
|
_omnirouteForceResponsesUpstream: true,
|
|
},
|
|
} as T;
|
|
}
|