feat: auto-combo optimization, playground model dropdown, only-configured toggle (#3322)

Integrated into release/v3.8.13 — auto-combo candidate expansion + playground dropdown + only-configured toggle
This commit is contained in:
Paijo
2026-06-07 04:12:48 +07:00
committed by GitHub
parent 2688f3cd74
commit 77aa09213c
4 changed files with 889 additions and 660 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,7 @@ export default function StudioConfigPane({ configState, setConfigState }: Studio
const { provider, setProvider, providerOptions, loading: loadingProviders } = useProviderOptions(
configState.provider ?? ""
);
const { availableModels, loading: loadingModels } = useAvailableModels();
const { availableModels, loading: loadingModels } = useAvailableModels(provider || undefined);
function update<K extends keyof ConfigState>(key: K, value: ConfigState[K]) {
setConfigState({ ...configState, [key]: value });
@@ -125,6 +125,7 @@ export default function StudioConfigPane({ configState, setConfigState }: Studio
onChange={(e) => {
setProvider(e.target.value);
update("provider", e.target.value);
update("model", "");
}}
disabled={loadingProviders}
className="w-full text-xs bg-surface border border-border rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary text-text-main"

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useMemo } from "react";
import { compareTr } from "@/shared/utils/turkishText";
/**
@@ -25,9 +25,9 @@ const FORMAT_MODEL_PREFIXES = {
* pickModelForFormat: (format: string) => string
* }}
*/
export function useAvailableModels() {
export function useAvailableModels(provider?: string) {
const [model, setModel] = useState("");
const [availableModels, setAvailableModels] = useState([]);
const [allModels, setAllModels] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
@@ -36,9 +36,9 @@ export function useAvailableModels() {
const res = await fetch("/api/v1/models");
const data = await res.json();
const models = (data.data || []).map((m) => m.id).sort((a, b) => compareTr(a, b));
setAvailableModels(models);
setAllModels(models);
} catch {
setAvailableModels([]);
setAllModels([]);
} finally {
setLoading(false);
}
@@ -46,6 +46,12 @@ export function useAvailableModels() {
fetchModels();
}, []);
const availableModels = useMemo(() => {
return provider
? allModels.filter((m) => m.startsWith(`${provider}/`) || m === provider)
: allModels;
}, [allModels, provider]);
/**
* Pick the best model for a given format from the available models.
* Returns the first model matching the format prefixes, or the first available model.

View File

@@ -0,0 +1,127 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
// Regression coverage for the #3322 auto-combo candidate expansion: an auto-combo
// without an explicit candidatePool broadens its eligible targets to every model
// of every active provider connection (so the router has the full pool to score).
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-expand-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const combo = await import("../../open-sse/services/combo.ts");
const providerModels = await import("../../open-sse/config/providerModels.ts");
function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(() => resetStorage());
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = ORIGINAL_DATA_DIR;
});
test("expandAutoComboCandidatePool adds every model of an active provider when no candidatePool is set", async () => {
await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "OpenAI",
apiKey: "sk-test-openai",
defaultModel: "gpt-4o-mini",
});
const expanded = await combo.expandAutoComboCandidatePool([], { config: {} });
// It should surface at least one openai/<model> target, all well-formed.
assert.ok(expanded.length > 0, "expected the active provider's models to be expanded in");
const openaiTargets = expanded.filter((t) => t.provider === "openai");
assert.ok(openaiTargets.length > 0, "expected openai targets");
for (const t of openaiTargets) {
assert.equal(t.kind, "model");
assert.equal(t.modelStr, `openai/${t.modelStr.split("/").slice(1).join("/")}`);
assert.equal(t.connectionId, null);
}
// Every catalog model for openai should be represented.
const catalogIds = providerModels.getProviderModels("openai").map((m) => `openai/${m.id}`);
assert.ok(catalogIds.length > 0);
for (const id of catalogIds) {
assert.ok(
expanded.some((t) => t.modelStr === id),
`expected expanded targets to include ${id}`
);
}
});
test("expandAutoComboCandidatePool is a no-op when an explicit candidatePool exists", async () => {
await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "OpenAI",
apiKey: "sk-test-openai",
defaultModel: "gpt-4o-mini",
});
const seed = [
{
kind: "model" as const,
stepId: "openai/gpt-4o",
executionKey: "openai/gpt-4o",
modelStr: "openai/gpt-4o",
provider: "openai",
providerId: "openai",
connectionId: null,
weight: 1,
label: null,
},
];
const result = await combo.expandAutoComboCandidatePool(seed, {
config: { auto: { candidatePool: ["openai"] } },
});
assert.equal(result.length, 1, "candidatePool present → no expansion");
assert.equal(result[0].modelStr, "openai/gpt-4o");
});
test("expandAutoComboCandidatePool does not duplicate an already-present modelStr", async () => {
await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "OpenAI",
apiKey: "sk-test-openai",
defaultModel: "gpt-4o-mini",
});
const firstCatalogId = providerModels.getProviderModels("openai")[0]?.id;
assert.ok(firstCatalogId, "expected at least one openai catalog model");
const existing = `openai/${firstCatalogId}`;
const seed = [
{
kind: "model" as const,
stepId: existing,
executionKey: existing,
modelStr: existing,
provider: "openai",
providerId: "openai",
connectionId: "conn-1",
weight: 5,
label: "pinned",
},
];
const result = await combo.expandAutoComboCandidatePool(seed, { config: {} });
const matches = result.filter((t) => t.modelStr === existing);
assert.equal(matches.length, 1, "the pre-existing target must not be duplicated");
// …and the original pinned entry (weight 5 / conn-1) is preserved, not overwritten.
assert.equal(matches[0].connectionId, "conn-1");
assert.equal(matches[0].weight, 5);
});