mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-25 16:42:16 +03:00
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host. Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean. Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
151 lines
5.4 KiB
TypeScript
151 lines
5.4 KiB
TypeScript
import type { Page } from "playwright";
|
|
import {
|
|
browserModelName,
|
|
getZaiModelCapabilities,
|
|
type ZaiThinkingConfig,
|
|
type ZaiVlmConfig,
|
|
} from "./protocol.ts";
|
|
|
|
/**
|
|
* Run one Playwright interaction, re-throwing any failure tagged with the stage
|
|
* that produced it. Every step below is a blind DOM poke against a UI we do not
|
|
* control, so an untagged "click timed out" is unactionable in a bug report.
|
|
*/
|
|
async function runStage(name: string, action: () => Promise<void>): Promise<void> {
|
|
try {
|
|
await action();
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
throw new Error(`${name}: ${message}`);
|
|
}
|
|
}
|
|
|
|
async function selectZaiBrowserModel(page: Page, modelName: string): Promise<void> {
|
|
const selector = page.locator('[aria-label="Select a model"]').first();
|
|
await selector.waitFor({ state: "visible", timeout: 10_000 });
|
|
if ((await selector.innerText()).includes(modelName)) return;
|
|
|
|
// The landing-page hero animation can remain above the already-visible
|
|
// selector and make coordinate-based clicks time out.
|
|
await selector.evaluate((element) => (element as HTMLElement).click());
|
|
const menu = page.locator('[role="menu"]').filter({ hasText: modelName }).first();
|
|
await menu.waitFor({ state: "visible", timeout: 5_000 });
|
|
const modelButton = menu.locator("button").filter({ hasText: modelName }).first();
|
|
await modelButton.evaluate((element) => (element as HTMLElement).click());
|
|
await page
|
|
.locator('[aria-label="Select a model"]')
|
|
.filter({ hasText: modelName })
|
|
.first()
|
|
.waitFor({ state: "visible", timeout: 5_000 });
|
|
}
|
|
|
|
async function setZaiBrowserToggle(
|
|
page: Page,
|
|
label: "Deep think" | "Tools" | "Web search",
|
|
dataAttribute: "data-autothink" | "data-selected",
|
|
enabled: boolean
|
|
): Promise<void> {
|
|
const wrapper = page.locator(`[aria-label^="${label} "]`).first();
|
|
await wrapper.waitFor({ state: "visible", timeout: 5_000 });
|
|
const button = wrapper.locator(`button[${dataAttribute}]`).first();
|
|
const current = (await button.getAttribute(dataAttribute)) === "true";
|
|
if (current !== enabled) await button.click({ timeout: 5_000 });
|
|
}
|
|
|
|
async function setZaiBrowserWebSearch(page: Page, enabled: boolean): Promise<void> {
|
|
const labelledWrapper = page.locator('[aria-label^="Web search "]').first();
|
|
if ((await labelledWrapper.count()) > 0) {
|
|
await setZaiBrowserToggle(page, "Web search", "data-selected", enabled);
|
|
return;
|
|
}
|
|
|
|
// Text-model UI: the globe button has no accessible label. Anchor the
|
|
// lookup to the adjacent upload button instead of generated IDs.
|
|
const button = page
|
|
.locator("#upload-file-button")
|
|
.locator("xpath=../../../following-sibling::div//button[@data-active]")
|
|
.first();
|
|
await button.waitFor({ state: "visible", timeout: 5_000 });
|
|
const current = (await button.getAttribute("data-active")) === "true";
|
|
if (current !== enabled) {
|
|
await button.click({ timeout: 5_000 });
|
|
await page.keyboard.press("Escape");
|
|
}
|
|
}
|
|
|
|
/** Pick the High/Max effort button inside an already-open Deep Think menu. */
|
|
async function selectZaiBrowserEffortLevel(
|
|
menu: ReturnType<Page["locator"]>,
|
|
effort: ZaiThinkingConfig["effort"]
|
|
): Promise<void> {
|
|
const effortButton = menu.locator("button").filter({
|
|
hasText: effort === "high" ? "High" : "Max",
|
|
});
|
|
if ((await effortButton.getAttribute("data-selected")) === "true") return;
|
|
await runStage(`select ${effort}`, () =>
|
|
effortButton.evaluate((element) => (element as HTMLElement).click())
|
|
);
|
|
}
|
|
|
|
async function configureZaiBrowserEffort(page: Page, config: ZaiThinkingConfig): Promise<void> {
|
|
const trigger = page
|
|
.locator("[data-dropdown-menu-trigger]")
|
|
.filter({ hasText: "Deep Think" })
|
|
.first();
|
|
await trigger.waitFor({ state: "visible", timeout: 10_000 });
|
|
await runStage("open menu", () =>
|
|
trigger.evaluate((element) => (element as HTMLElement).click())
|
|
);
|
|
|
|
const menu = page.locator('[role="menu"]').filter({ hasText: "Deep Think" }).first();
|
|
await menu.waitFor({ state: "visible", timeout: 5_000 });
|
|
const toggle = menu.locator('[role="switch"]').first();
|
|
const checked = (await toggle.getAttribute("aria-checked")) === "true";
|
|
|
|
if (checked !== config.enabled) {
|
|
await runStage(config.enabled ? "enable toggle" : "disable toggle", () =>
|
|
toggle.click({ timeout: 5_000 })
|
|
);
|
|
}
|
|
if (config.enabled) {
|
|
await selectZaiBrowserEffortLevel(menu, config.effort);
|
|
}
|
|
|
|
if (await menu.isVisible()) {
|
|
await page.keyboard.press("Escape");
|
|
}
|
|
}
|
|
|
|
export async function configureZaiBrowserRequest(
|
|
page: Page,
|
|
input: {
|
|
modelId: string;
|
|
thinking: ZaiThinkingConfig;
|
|
vlm: ZaiVlmConfig;
|
|
}
|
|
): Promise<void> {
|
|
await runStage("model selection", () =>
|
|
selectZaiBrowserModel(page, browserModelName(input.modelId))
|
|
);
|
|
|
|
if (input.thinking.effortSupported) {
|
|
await runStage("Deep Think effort", () => configureZaiBrowserEffort(page, input.thinking));
|
|
} else if (input.thinking.supported) {
|
|
await runStage("Deep Think toggle", () =>
|
|
setZaiBrowserToggle(page, "Deep think", "data-autothink", input.thinking.enabled)
|
|
);
|
|
}
|
|
|
|
const capabilities = getZaiModelCapabilities(input.modelId);
|
|
if (capabilities.webSearch) {
|
|
await runStage("web search toggle", () =>
|
|
setZaiBrowserWebSearch(page, input.vlm.webSearchEnabled)
|
|
);
|
|
}
|
|
if (capabilities.vlmTools) {
|
|
await runStage("tools toggle", () =>
|
|
setZaiBrowserToggle(page, "Tools", "data-selected", input.vlm.toolsEnabled)
|
|
);
|
|
}
|
|
}
|