mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. O ponto que sustenta a PR é o `models.dev` virar overlay de preço em vez de fonte de catálogo. Um catálogo estático que sobrevive à conta já ter listado ids mais novos é o tipo de defeito que só aparece quando o modelo novo é justamente o que se quer usar. Nota de integração: `activeSyncedCatalog.ts` colidiu com o #12934 (união dos `customModels` do picker no catálogo de despacho). Como você extraiu o bloco original para `loadConnectionCatalog`, os dois se compõem: a união dos irmãos agy/antigravity primeiro, o `unionCustomModels` por cima. Revalidei com `custom-models-live-catalog-12597`, `live-model-catalog-reconciliation-8926`, `sync-models-degraded-cached-catalog-9683`, `models-dev-catalog-read-gate`, `discovery-class`, `reactive-model-sync` e `l1-oauth-autosync-default` juntos — 48/48 — mais typecheck:core limpo. Sobre o `autoSync` padrão em Claude/Codex/Copilot com scheduler de 6h: passei isso pelo dono antes de mergear e a decisão foi manter como está.
188 lines
6.8 KiB
TypeScript
188 lines
6.8 KiB
TypeScript
/**
|
|
* Reactive model sync — a model-not-found 404 on a discovery-capable provider
|
|
* kicks a discovery sync for that connection so freshly shipped upstream
|
|
* models land in the synced catalog (pinned-catalog staleness self-heal).
|
|
* Guardrails under test: provider allow-list, per-connection cooldown,
|
|
* in-flight dedup.
|
|
*/
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const {
|
|
maybeTriggerReactiveModelSync,
|
|
__resetReactiveModelSyncForTests,
|
|
__setReactiveSyncFnForTests,
|
|
} = await import("../../src/lib/providerModels/reactiveModelSync.ts");
|
|
|
|
type SyncCall = { connectionId: string; provider: string; baseUrl: string };
|
|
|
|
function installCountingSync() {
|
|
const calls: SyncCall[] = [];
|
|
__setReactiveSyncFnForTests(async (connectionId, provider, baseUrl) => {
|
|
calls.push({ connectionId, provider, baseUrl });
|
|
return true;
|
|
});
|
|
return calls;
|
|
}
|
|
|
|
function flushMicrotasks(): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|
|
|
|
test("allowed provider triggers a discovery sync with the right arguments", async () => {
|
|
__resetReactiveModelSyncForTests();
|
|
const calls = installCountingSync();
|
|
|
|
const triggered = maybeTriggerReactiveModelSync("antigravity", "conn-aaa-111");
|
|
assert.equal(triggered, true);
|
|
await flushMicrotasks();
|
|
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].connectionId, "conn-aaa-111");
|
|
assert.equal(calls[0].provider, "antigravity");
|
|
assert.equal(typeof calls[0].baseUrl, "string");
|
|
});
|
|
|
|
test("agy provider id is also allowed and normalized", async () => {
|
|
__resetReactiveModelSyncForTests();
|
|
const calls = installCountingSync();
|
|
|
|
assert.equal(maybeTriggerReactiveModelSync("AGY ", "conn-bbb-222"), true);
|
|
await flushMicrotasks();
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].provider, "agy");
|
|
});
|
|
|
|
test("providers without discovery support never trigger", async () => {
|
|
__resetReactiveModelSyncForTests();
|
|
const calls = installCountingSync();
|
|
|
|
assert.equal(maybeTriggerReactiveModelSync("openai", "conn-ccc-333"), false);
|
|
assert.equal(maybeTriggerReactiveModelSync("", "conn-ccc-333"), false);
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", " "), false);
|
|
await flushMicrotasks();
|
|
assert.equal(calls.length, 0);
|
|
});
|
|
|
|
test("second trigger inside the cooldown window is a no-op", async () => {
|
|
__resetReactiveModelSyncForTests();
|
|
const calls = installCountingSync();
|
|
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-ddd-444"), true);
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-ddd-444"), false);
|
|
await flushMicrotasks();
|
|
assert.equal(calls.length, 1);
|
|
});
|
|
|
|
test("cooldown is per connection — a different connection still triggers", async () => {
|
|
__resetReactiveModelSyncForTests();
|
|
const calls = installCountingSync();
|
|
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-eee-555"), true);
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-fff-666"), true);
|
|
await flushMicrotasks();
|
|
assert.equal(calls.length, 2);
|
|
});
|
|
|
|
test("trigger is allowed again once the cooldown expires", async () => {
|
|
__resetReactiveModelSyncForTests(1); // 1ms cooldown
|
|
const calls = installCountingSync();
|
|
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-ggg-777"), true);
|
|
await flushMicrotasks();
|
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-ggg-777"), true);
|
|
await flushMicrotasks();
|
|
assert.equal(calls.length, 2);
|
|
});
|
|
|
|
test("in-flight dedup: concurrent triggers collapse into one sync", async () => {
|
|
__resetReactiveModelSyncForTests();
|
|
let releaseSync: ((value: boolean) => void) | null = null;
|
|
const calls: SyncCall[] = [];
|
|
__setReactiveSyncFnForTests(
|
|
(connectionId, provider, baseUrl) =>
|
|
new Promise<boolean>((resolve) => {
|
|
calls.push({ connectionId, provider, baseUrl });
|
|
releaseSync = resolve;
|
|
})
|
|
);
|
|
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-hhh-888"), true);
|
|
// First sync still pending — the second trigger must be dropped, not queued.
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-hhh-888"), false);
|
|
assert.equal(calls.length, 1);
|
|
|
|
assert.ok(releaseSync);
|
|
releaseSync(true);
|
|
await flushMicrotasks();
|
|
assert.equal(calls.length, 1);
|
|
});
|
|
|
|
test("a sync failure does not break subsequent triggers after cooldown", async () => {
|
|
__resetReactiveModelSyncForTests(1);
|
|
let attempts = 0;
|
|
__setReactiveSyncFnForTests(async () => {
|
|
attempts += 1;
|
|
return false; // sync endpoint reported failure
|
|
});
|
|
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-iii-999"), true);
|
|
await flushMicrotasks();
|
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
assert.equal(maybeTriggerReactiveModelSync("antigravity", "conn-iii-999"), true);
|
|
await flushMicrotasks();
|
|
assert.equal(attempts, 2);
|
|
});
|
|
|
|
test("cleanup restores the default loopback sync implementation", () => {
|
|
__setReactiveSyncFnForTests(null);
|
|
__resetReactiveModelSyncForTests();
|
|
assert.ok(true);
|
|
});
|
|
|
|
test("test 13: claude/codex/github do not trigger reactive sync", () => {
|
|
__resetReactiveModelSyncForTests();
|
|
const calls = installCountingSync();
|
|
assert.equal(maybeTriggerReactiveModelSync("claude", "conn-claude"), false);
|
|
assert.equal(maybeTriggerReactiveModelSync("codex", "conn-codex"), false);
|
|
assert.equal(maybeTriggerReactiveModelSync("github", "conn-github"), false);
|
|
assert.equal(calls.length, 0);
|
|
});
|
|
|
|
test("test 13: antigravity executor still calls maybeTriggerReactiveModelSync", () => {
|
|
const src = fs.readFileSync(
|
|
path.join(process.cwd(), "open-sse/executors/antigravity/executeAttempt.ts"),
|
|
"utf8",
|
|
);
|
|
assert.match(src, /maybeTriggerReactiveModelSync\(\s*provider,\s*credentials\.connectionId\s*\)/);
|
|
});
|
|
|
|
test("test 14: claude live non-200 uses catalog fallback, not empty 502", () => {
|
|
const src = fs.readFileSync(
|
|
path.join(process.cwd(), "src/app/api/providers/[id]/models/route.ts"),
|
|
"utf8",
|
|
);
|
|
// Task 2 deleted the claude static early return. Claude is a
|
|
// PROVIDER_MODELS_CONFIG live provider and must land in generic live.
|
|
assert.doesNotMatch(
|
|
src,
|
|
/if\s*\(\s*provider\s*===\s*"claude"\s*\)[\s\S]{0,400}getStaticModelsForProvider\(\s*"claude"/,
|
|
);
|
|
const assembleIdx = src.lastIndexOf("assembleProviderModelsHeaders");
|
|
assert.ok(assembleIdx >= 0, "generic live must assemble provider-models headers");
|
|
const tail = src.slice(assembleIdx);
|
|
// Generic live 401/non-200: warning + cached/local catalog, not a 502 empty body.
|
|
assert.match(
|
|
tail,
|
|
/if\s*\(\s*!response\.ok\s*\)[\s\S]{0,400}buildDiscoveryFallbackResponse\(\s*\)/,
|
|
);
|
|
assert.doesNotMatch(
|
|
tail,
|
|
/if\s*\(\s*!response\.ok\s*\)[\s\S]{0,500}status:\s*502/,
|
|
);
|
|
});
|