mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
Merge pull request #233 from diegosouzapw/fix/issue-204-apiformat-routing
fix: wire apiFormat from custom model DB into routing layer (#204)
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -6905,6 +6905,7 @@
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -9012,6 +9013,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
|
||||
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
|
||||
@@ -369,7 +369,14 @@ async function resolveModelOrError(modelStr: string, body: any) {
|
||||
const { provider, model } = modelInfo;
|
||||
const sourceFormat = detectFormat(body);
|
||||
const providerAlias = PROVIDER_ID_TO_ALIAS[provider] || provider;
|
||||
const targetFormat = getModelTargetFormat(providerAlias, model) || getTargetFormat(provider);
|
||||
|
||||
// If the custom model specifies apiFormat="responses", override targetFormat
|
||||
// to route through the Responses API translator instead of Chat Completions
|
||||
let targetFormat = getModelTargetFormat(providerAlias, model) || getTargetFormat(provider);
|
||||
if ((modelInfo as any).apiFormat === "responses") {
|
||||
targetFormat = "openai-responses";
|
||||
log.info("ROUTING", `Custom model apiFormat=responses → targetFormat=openai-responses`);
|
||||
}
|
||||
|
||||
if (modelStr !== `${provider}/${model}`) {
|
||||
log.info("ROUTING", `${modelStr} → ${provider}/${model}`);
|
||||
|
||||
@@ -34,7 +34,12 @@ const HTTP_STATUS = {
|
||||
* @param {Function} errorResponse - Error response factory
|
||||
* @returns {Promise<{ error?: Response, provider: string, model: string, sourceFormat: string, targetFormat: string }>}
|
||||
*/
|
||||
export async function resolveModelOrError(modelStr: string, body: any, log: any, errorResponse: Function) {
|
||||
export async function resolveModelOrError(
|
||||
modelStr: string,
|
||||
body: any,
|
||||
log: any,
|
||||
errorResponse: Function
|
||||
) {
|
||||
const modelInfo = await getModelInfo(modelStr);
|
||||
|
||||
if (!modelInfo.provider) {
|
||||
@@ -44,7 +49,8 @@ export async function resolveModelOrError(modelStr: string, body: any, log: any,
|
||||
`Ambiguous model '${modelStr}'. Use provider/model prefix (ex: gh/${modelStr} or cc/${modelStr}).`;
|
||||
log.warn("CHAT", message, {
|
||||
model: modelStr,
|
||||
candidates: (modelInfo as any).candidateAliases || (modelInfo as any).candidateProviders || [],
|
||||
candidates:
|
||||
(modelInfo as any).candidateAliases || (modelInfo as any).candidateProviders || [],
|
||||
});
|
||||
return { error: errorResponse(HTTP_STATUS.BAD_REQUEST, message) };
|
||||
}
|
||||
@@ -56,7 +62,14 @@ export async function resolveModelOrError(modelStr: string, body: any, log: any,
|
||||
const { provider, model } = modelInfo;
|
||||
const sourceFormat = detectFormat(body);
|
||||
const providerAlias = PROVIDER_ID_TO_ALIAS[provider] || provider;
|
||||
const targetFormat = getModelTargetFormat(providerAlias, model) || getTargetFormat(provider);
|
||||
|
||||
// If the custom model specifies apiFormat="responses", override targetFormat
|
||||
// to route through the Responses API translator instead of Chat Completions
|
||||
let targetFormat = getModelTargetFormat(providerAlias, model) || getTargetFormat(provider);
|
||||
if ((modelInfo as any).apiFormat === "responses") {
|
||||
targetFormat = "openai-responses";
|
||||
log.info("ROUTING", `Custom model apiFormat=responses → targetFormat=openai-responses`);
|
||||
}
|
||||
|
||||
// Log routing
|
||||
if (modelStr !== `${provider}/${model}`) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Re-export from open-sse with localDb integration
|
||||
import { getModelAliases, getComboByName, getProviderNodes } from "@/lib/localDb";
|
||||
import { getModelAliases, getComboByName, getProviderNodes, getCustomModels } from "@/lib/localDb";
|
||||
import {
|
||||
parseModel,
|
||||
resolveModelAliasFromMap,
|
||||
@@ -16,13 +16,30 @@ export async function resolveModelAlias(alias) {
|
||||
return resolveModelAliasFromMap(alias, aliases);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the apiFormat for a custom model from the DB.
|
||||
* Returns "responses" if the model is configured for the Responses API, otherwise undefined.
|
||||
*/
|
||||
async function lookupCustomModelApiFormat(
|
||||
providerId: string,
|
||||
modelId: string
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
const models = await getCustomModels(providerId);
|
||||
if (!Array.isArray(models)) return undefined;
|
||||
const match = models.find((m: any) => m.id === modelId);
|
||||
return match?.apiFormat === "responses" ? "responses" : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full model info (parse or resolve)
|
||||
*/
|
||||
export async function getModelInfo(modelStr) {
|
||||
const parsed = parseModel(modelStr);
|
||||
|
||||
// Check custom provider nodes first (for both alias and non-alias formats)
|
||||
// Check custom provider nodes first (for both alias and non-alias formats)
|
||||
if (parsed.providerAlias || parsed.provider) {
|
||||
// Ensure prefixToCheck is always a concise identifier, not a full model string
|
||||
@@ -32,14 +49,26 @@ export async function getModelInfo(modelStr) {
|
||||
const openaiNodes = await getProviderNodes({ type: "openai-compatible" });
|
||||
const matchedOpenAI = openaiNodes.find((node) => node.prefix === prefixToCheck);
|
||||
if (matchedOpenAI) {
|
||||
return { provider: matchedOpenAI.id, model: parsed.model };
|
||||
const apiFormat = await lookupCustomModelApiFormat(
|
||||
matchedOpenAI.id as string,
|
||||
parsed.model as string
|
||||
);
|
||||
return { provider: matchedOpenAI.id, model: parsed.model, ...(apiFormat && { apiFormat }) };
|
||||
}
|
||||
|
||||
// Check Anthropic Compatible nodes
|
||||
const anthropicNodes = await getProviderNodes({ type: "anthropic-compatible" });
|
||||
const matchedAnthropic = anthropicNodes.find((node) => node.prefix === prefixToCheck);
|
||||
if (matchedAnthropic) {
|
||||
return { provider: matchedAnthropic.id, model: parsed.model };
|
||||
const apiFormat = await lookupCustomModelApiFormat(
|
||||
matchedAnthropic.id as string,
|
||||
parsed.model as string
|
||||
);
|
||||
return {
|
||||
provider: matchedAnthropic.id,
|
||||
model: parsed.model,
|
||||
...(apiFormat && { apiFormat }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user