fix(look-at): preserve hardcoded multimodal variants
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
describe("buildMultimodalLookerFallbackChain", () => {
|
||||||
|
it("preserves hardcoded variant metadata when dynamic and hardcoded entries share the same model", async () => {
|
||||||
|
// given
|
||||||
|
const { buildMultimodalLookerFallbackChain } = await import("./multimodal-fallback-chain")
|
||||||
|
const visionCapableModels = [
|
||||||
|
{ providerID: "openai", modelID: "gpt-5.4" },
|
||||||
|
{ providerID: "opencode", modelID: "gpt-5.4" },
|
||||||
|
]
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = buildMultimodalLookerFallbackChain(visionCapableModels)
|
||||||
|
const matchingEntries = result.filter((entry) => entry.model === "gpt-5.4")
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(matchingEntries).toHaveLength(1)
|
||||||
|
expect(matchingEntries[0]).toEqual({
|
||||||
|
providers: ["openai", "opencode"],
|
||||||
|
model: "gpt-5.4",
|
||||||
|
variant: "medium",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("merges missing hardcoded providers into an existing dynamic entry", async () => {
|
||||||
|
// given
|
||||||
|
const { buildMultimodalLookerFallbackChain } = await import("./multimodal-fallback-chain")
|
||||||
|
const visionCapableModels = [{ providerID: "openai", modelID: "gpt-5.4" }]
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = buildMultimodalLookerFallbackChain(visionCapableModels)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result[0]).toEqual({
|
||||||
|
providers: ["openai", "opencode"],
|
||||||
|
model: "gpt-5.4",
|
||||||
|
variant: "medium",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -19,14 +19,14 @@ export function isHardcodedMultimodalFallbackModel(model: VisionCapableModel): b
|
|||||||
export function buildMultimodalLookerFallbackChain(
|
export function buildMultimodalLookerFallbackChain(
|
||||||
visionCapableModels: VisionCapableModel[],
|
visionCapableModels: VisionCapableModel[],
|
||||||
): FallbackEntry[] {
|
): FallbackEntry[] {
|
||||||
const seen = new Set<string>()
|
const entryIndexByKey = new Map<string, number>()
|
||||||
const fallbackChain: FallbackEntry[] = []
|
const fallbackChain: FallbackEntry[] = []
|
||||||
|
|
||||||
for (const visionCapableModel of visionCapableModels) {
|
for (const visionCapableModel of visionCapableModels) {
|
||||||
const key = getFullModelKey(visionCapableModel.providerID, visionCapableModel.modelID)
|
const key = getFullModelKey(visionCapableModel.providerID, visionCapableModel.modelID)
|
||||||
if (seen.has(key)) continue
|
if (entryIndexByKey.has(key)) continue
|
||||||
|
|
||||||
seen.add(key)
|
entryIndexByKey.set(key, fallbackChain.length)
|
||||||
fallbackChain.push({
|
fallbackChain.push({
|
||||||
providers: [visionCapableModel.providerID],
|
providers: [visionCapableModel.providerID],
|
||||||
model: visionCapableModel.modelID,
|
model: visionCapableModel.modelID,
|
||||||
@@ -37,13 +37,45 @@ export function buildMultimodalLookerFallbackChain(
|
|||||||
const providerModelKeys = entry.providers.map((providerID) =>
|
const providerModelKeys = entry.providers.map((providerID) =>
|
||||||
getFullModelKey(providerID, entry.model),
|
getFullModelKey(providerID, entry.model),
|
||||||
)
|
)
|
||||||
if (providerModelKeys.every((key) => seen.has(key))) {
|
|
||||||
|
const existingIndexes = [...new Set(
|
||||||
|
providerModelKeys
|
||||||
|
.map((key) => entryIndexByKey.get(key))
|
||||||
|
.filter((index): index is number => index !== undefined),
|
||||||
|
)]
|
||||||
|
|
||||||
|
if (existingIndexes.length > 0) {
|
||||||
|
const [targetIndex, ...duplicateIndexes] = existingIndexes
|
||||||
|
const targetEntry = fallbackChain[targetIndex]
|
||||||
|
const mergedProviders = new Set(targetEntry.providers)
|
||||||
|
|
||||||
|
for (const providerID of entry.providers) {
|
||||||
|
mergedProviders.add(providerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const duplicateIndex of duplicateIndexes) {
|
||||||
|
const duplicateEntry = fallbackChain[duplicateIndex]
|
||||||
|
for (const providerID of duplicateEntry.providers) {
|
||||||
|
mergedProviders.add(providerID)
|
||||||
|
}
|
||||||
|
duplicateEntry.providers = []
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEntry.providers = [...mergedProviders]
|
||||||
|
targetEntry.variant ??= entry.variant
|
||||||
|
|
||||||
|
for (const providerID of targetEntry.providers) {
|
||||||
|
entryIndexByKey.set(getFullModelKey(providerID, targetEntry.model), targetIndex)
|
||||||
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
providerModelKeys.forEach((key) => seen.add(key))
|
providerModelKeys.forEach((key) => {
|
||||||
|
entryIndexByKey.set(key, fallbackChain.length)
|
||||||
|
})
|
||||||
fallbackChain.push(entry)
|
fallbackChain.push(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fallbackChain
|
return fallbackChain.filter((entry) => entry.providers.length > 0)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user