From 74cf860cccd6f43196b7e0df53a751566703eb48 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 7 Aug 2026 20:52:57 -0300 Subject: [PATCH] feat(providers): make video_url passthrough configurable per provider/model (#9248) (#9419) Merge-train validated (tip 6ce4effef8). Vitest failures confirmed as base-red (#9679). --- .../features/9248-video-url-passthrough.md | 1 + open-sse/translator/index.ts | 6 +- src/lib/db/models/modelPreserveVideoUrl.ts | 67 +++++++++++++++++++ tests/unit/preserve-video-url-compat.test.ts | 46 +++++++++++++ 4 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 changelog.d/features/9248-video-url-passthrough.md create mode 100644 src/lib/db/models/modelPreserveVideoUrl.ts create mode 100644 tests/unit/preserve-video-url-compat.test.ts diff --git a/changelog.d/features/9248-video-url-passthrough.md b/changelog.d/features/9248-video-url-passthrough.md new file mode 100644 index 0000000000..de1c81b6f0 --- /dev/null +++ b/changelog.d/features/9248-video-url-passthrough.md @@ -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 diff --git a/open-sse/translator/index.ts b/open-sse/translator/index.ts index e083787014..9484641da2 100644 --- a/open-sse/translator/index.ts +++ b/open-sse/translator/index.ts @@ -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"), }); } diff --git a/src/lib/db/models/modelPreserveVideoUrl.ts b/src/lib/db/models/modelPreserveVideoUrl.ts new file mode 100644 index 0000000000..379e0fe933 --- /dev/null +++ b/src/lib/db/models/modelPreserveVideoUrl.ts @@ -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 | 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) : 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; +} diff --git a/tests/unit/preserve-video-url-compat.test.ts b/tests/unit/preserve-video-url-compat.test.ts new file mode 100644 index 0000000000..32ba05b2a1 --- /dev/null +++ b/tests/unit/preserve-video-url-compat.test.ts @@ -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); + }); +});