feat(providers): make video_url passthrough configurable per provider/model (#9248) (#9419)

Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679).
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-07 20:52:57 -03:00
committed by GitHub
parent 915b383a71
commit 74cf860ccc
4 changed files with 118 additions and 2 deletions

View File

@@ -0,0 +1 @@
- **feat(providers):** make video_url passthrough configurable per provider/model via compat override ([#9248](https://github.com/diegosouzapw/OmniRoute/issues/9248)) — thanks @HellFiveOsborn

View File

@@ -25,6 +25,7 @@ import { bootstrapTranslatorRegistry } from "./bootstrap.ts";
import { hasThinkingConfig, normalizeThinkingConfig } from "../services/provider.ts";
import { applyThinkingBudget } from "../services/thinkingBudget.ts";
import { applyReasoningRuleDirective } from "@/lib/reasoningRouting/policy";
import { getModelPreserveVideoUrl } from "@/lib/db/models/modelPreserveVideoUrl";
import { getResolvedModelCapabilities, supportsReasoning } from "../services/modelCapabilities.ts";
import { normalizeRoles } from "../services/roleNormalizer.ts";
import { hoistLeadingSystemMessage } from "./helpers/strictSystemHoist.ts";
@@ -368,8 +369,9 @@ export function translateRequest(
providerHonorsOpenAIFormatCacheControl(provider, connectionCacheOverride),
// #4849 regression guard: keep client reasoning_content for replay providers.
preserveReasoningContent: isReasoner,
// Moonshot's Chat API accepts its own OpenAI-compatible `video_url` block.
preserveVideoUrl: normalizedProvider === "moonshot" || normalizedProvider === "kimi",
// Per-provider/model preserveVideoUrl flag from compat overrides.
// Falls back to true for moonshot/kimi when unset (legacy behavior).
preserveVideoUrl: getModelPreserveVideoUrl(normalizedProvider, options?.model ?? "") ?? (normalizedProvider === "moonshot" || normalizedProvider === "kimi"),
});
}

View File

@@ -0,0 +1,67 @@
/**
* modelPreserveVideoUrl.ts — preserveVideoUrl resolver for model-compat overrides.
*
* Extracted from models.ts to avoid growing the frozen file-size baseline.
* Follows the same pattern as getModelPreserveOpenAIDeveloperRole.
*/
import { getDbInstance } from "../core";
import { type CompatByProtocolMap, readCompatList, isCompatProtocolKey } from "./compat";
/** The model-compat override key for the preserveVideoUrl flag. */
const KEY = "preserveVideoUrl";
function getCustomModelRow(providerId: string, modelId: string): Record<string, unknown> | undefined {
const db = getDbInstance();
const row = db
.prepare(
"SELECT value FROM key_value WHERE namespace = 'modelCompatOverrides' AND key = ?"
)
.get(`${providerId}::${modelId}`);
if (!row) return undefined;
try {
const v = JSON.parse((row as { value: string }).value);
return typeof v === "object" && v !== null ? (v as Record<string, unknown>) : undefined;
} catch {
return undefined;
}
}
/**
* Get the explicit preserve-video-url preference for a provider/model.
* `undefined` = unset → fall back to moonshot/kimi hardcoded behavior (caller decides).
* `true` = keep video_url content parts in the translated request.
* Per-protocol overrides live under `compatByProtocol[sourceFormat]`.
*/
export function getModelPreserveVideoUrl(
providerId: string,
modelId: string,
sourceFormat?: string | null
): boolean | undefined {
const m = getCustomModelRow(providerId, modelId);
const protocol = sourceFormat && isCompatProtocolKey(sourceFormat) ? sourceFormat : null;
if (m) {
if (protocol) {
const pc = (m.compatByProtocol as CompatByProtocolMap | undefined)?.[protocol];
if (pc && Object.prototype.hasOwnProperty.call(pc, KEY)) {
return Boolean(pc[KEY]);
}
}
if (Object.prototype.hasOwnProperty.call(m, KEY)) {
return Boolean(m[KEY]);
}
return undefined;
}
const co = readCompatList(providerId).find((e) => e.id === modelId);
if (protocol && co?.compatByProtocol?.[protocol]) {
const pc = co.compatByProtocol[protocol]!;
if (Object.prototype.hasOwnProperty.call(pc, KEY)) {
return Boolean(pc[KEY]);
}
}
if (co && Object.prototype.hasOwnProperty.call(co, KEY)) {
return Boolean(co[KEY]);
}
return undefined;
}

View File

@@ -0,0 +1,46 @@
import { describe, it } from "node:test";
import { ok, equal } from "node:assert/strict";
describe("getModelPreserveVideoUrl", () => {
it("exports getModelPreserveVideoUrl as a function", async () => {
const mod = await import("@/lib/db/models/modelPreserveVideoUrl");
equal(typeof mod.getModelPreserveVideoUrl, "function");
});
it("fallback preserves moonshot and kimi legacy behavior", () => {
const fallback = (provider: string) =>
provider === "moonshot" || provider === "kimi";
ok(fallback("moonshot"));
ok(fallback("kimi"));
equal(fallback("dashscope"), false);
equal(fallback("unknown"), false);
});
it("translator import resolves correctly", async () => {
const mod = await import("@/lib/db/models/modelPreserveVideoUrl");
// Calling with unknown provider/model returns undefined (no compat override)
const result = mod.getModelPreserveVideoUrl("test_provider", "test_model");
equal(result, undefined);
// Calling with known hardcoded defaults also returns undefined (no compat row)
const result2 = mod.getModelPreserveVideoUrl("moonshot", "moonshot-v1");
equal(result2, undefined);
});
it("mergeModelCompatOverride accepts preserveVideoUrl", async () => {
const { mergeModelCompatOverride, removeModelCompatOverride } = await import("@/lib/db/models/compat");
const PROVIDER = "test_provider_9248v3";
const MODEL = "test_model_qwen_vl";
mergeModelCompatOverride(PROVIDER, MODEL, { preserveVideoUrl: true });
removeModelCompatOverride(PROVIDER, MODEL);
ok(true, "should accept preserveVideoUrl in ModelCompatPatch");
});
it("deepMergeCompatByProtocol accepts preserveVideoUrl under openai protocol", async () => {
const { deepMergeCompatByProtocol } = await import("@/lib/db/models/compat");
const result = deepMergeCompatByProtocol({}, {
openai: { preserveVideoUrl: true },
});
// Valid protocol keys are 'openai', 'openai-responses', 'claude'
equal(result.openai?.preserveVideoUrl, true);
});
});