Compare commits

..

1 Commits

Author SHA1 Message Date
diegosouzapw
cd426a0d84 fix(tests): retire dead model ids from the chat-pipeline integration suite (base-red #12581)
Three integration failures, one cause: the suite still routes two model
ids the vendor lifecycle registry marks `retired`, so the pipeline
correctly refuses them and the tests assert on the refusal.

   persists Codex responses cache and reasoning tokens → 410 !== 200
     "codex/gpt-5.1-codex" — retired 2026-07-23, successor gpt-5.6-sol
   translates OpenAI requests to Claude              → 410 !== 200
   falls back across combo models                    → 502 !== 200
     both on "claude/claude-3-5-sonnet-20241022" — the combo case fails
     with 502 because the retired id is its fallback target

Only the first reaches the release-green report, which prints the first
failing line, so the other two were invisible. All three had been hidden
for longer than that: the integration gate has been dying on its 2400s
ceiling, and a gate killed by its ceiling never reports an individual
test failure.

Replaces both with live ids — the successor named by the lifecycle record
for the Codex one, and the `claude-sonnet-4-6` this same file already
uses elsewhere for the Claude one. `buildOpenAIResponsesSSE` also echoed
the retired Codex id, and the call log derives its provider from the
model in the response, so leaving it made the row log `provider:
"openai"` for a request that correctly used the codex connection.

No assertion is changed or relaxed. Verified with the CI invocation
(setupPolyfill + isolateDataDir + --test-concurrency=1): 28/28 pass,
exit 0, from 25/28 before.
2026-09-03 23:04:45 -03:00
3 changed files with 17 additions and 65 deletions

View File

@@ -1 +0,0 @@
- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised).

View File

@@ -1,15 +1,6 @@
"use client";
import {
useState,
useEffect,
useCallback,
useMemo,
useRef,
useSyncExternalStore,
memo,
Suspense,
} from "react";
import { useState, useEffect, useCallback, useMemo, useRef, memo, Suspense } from "react";
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
@@ -397,42 +388,6 @@ const STRATEGY_RECOMMENDATIONS_FALLBACK = {
const COMBO_USAGE_GUIDE_STORAGE_KEY = "omniroute:combos:hide-usage-guide";
// The dismissal lives in localStorage, which SSR cannot read: a lazy useState
// initializer would render "not dismissed" on the server and the real value on
// the client, and correcting that in an effect is a synchronous setState inside
// an effect (react-hooks/set-state-in-effect) that costs an extra commit of this
// whole tree. useSyncExternalStore is the sanctioned shape for exactly this —
// getServerSnapshot supplies the SSR-safe default, getSnapshot reads the store
// after hydration, and the two handlers below notify subscribers instead of
// setting state. The `storage` listener keeps other tabs in sync for free.
const usageGuideListeners = new Set<() => void>();
function subscribeUsageGuide(onStoreChange: () => void): () => void {
usageGuideListeners.add(onStoreChange);
globalThis.addEventListener?.("storage", onStoreChange);
return () => {
usageGuideListeners.delete(onStoreChange);
globalThis.removeEventListener?.("storage", onStoreChange);
};
}
function emitUsageGuideChange(): void {
for (const listener of usageGuideListeners) listener();
}
function getUsageGuideSnapshot(): boolean {
try {
return globalThis.localStorage?.getItem(COMBO_USAGE_GUIDE_STORAGE_KEY) !== "1";
} catch {
// Storage access errors (privacy mode / restricted environments) show the guide.
return true;
}
}
function getUsageGuideServerSnapshot(): boolean {
return true;
}
// Pure predicate hoisted out of the page component to keep its cyclomatic budget flat
// (check:complexity new-code mode).
function isStaleIntelligentSelection(
@@ -813,15 +768,14 @@ function CombosPageContent() {
// full client-only re-render of this tree, discarding whatever the fetch
// effects below had already populated. Start with the SSR-safe default on
// both passes and correct it client-only, after hydration, in an effect.
const usageGuideNotDismissed = useSyncExternalStore(
subscribeUsageGuide,
getUsageGuideSnapshot,
getUsageGuideServerSnapshot
);
// "Hide" (as opposed to "hide forever") is intentionally per-mount: it is not
// persisted, and remounting the page brings the guide back — same as before.
const [usageGuideHiddenForNow, setUsageGuideHiddenForNow] = useState(false);
const showUsageGuide = usageGuideNotDismissed && !usageGuideHiddenForNow;
const [showUsageGuide, setShowUsageGuide] = useState(true);
useEffect(() => {
try {
setShowUsageGuide(globalThis.localStorage?.getItem(COMBO_USAGE_GUIDE_STORAGE_KEY) !== "1");
} catch {
// Ignore storage access errors (privacy mode / restricted environments)
}
}, []);
const [recentlyCreatedCombo, setRecentlyCreatedCombo] = useState("");
const [creatingKimiPreset, setCreatingKimiPreset] = useState(false);
const [comboDragIndex, setComboDragIndex] = useState(null);
@@ -1052,18 +1006,17 @@ function CombosPageContent() {
};
const handleHideUsageGuideForever = () => {
setShowUsageGuide(false);
try {
globalThis.localStorage?.setItem(COMBO_USAGE_GUIDE_STORAGE_KEY, "1");
} catch {}
emitUsageGuideChange();
};
const handleShowUsageGuide = () => {
setShowUsageGuide(true);
try {
globalThis.localStorage?.removeItem(COMBO_USAGE_GUIDE_STORAGE_KEY);
} catch {}
setUsageGuideHiddenForNow(false);
emitUsageGuideChange();
};
const handleFilterChange = (nextFilter) => {
@@ -1196,7 +1149,7 @@ function CombosPageContent() {
{showUsageGuide && (
<ComboUsageGuide
onHide={() => setUsageGuideHiddenForNow(true)}
onHide={() => setShowUsageGuide(false)}
onHideForever={handleHideUsageGuideForever}
onCreateCombo={() => setShowCreateModal(true)}
/>

View File

@@ -170,7 +170,7 @@ function buildOpenAIToolCallResponse({
);
}
function buildClaudeResponse(text = "ok", model = "claude-3-5-sonnet-20241022") {
function buildClaudeResponse(text = "ok", model = "claude-sonnet-4-6") {
return new Response(
JSON.stringify({
id: "msg_json",
@@ -286,7 +286,7 @@ function buildOpenAIStreamResponse(text = "streamed from openai") {
function buildOpenAIResponsesSSE({
text = "responses streamed from codex",
model = "gpt-5.1-codex",
model = "gpt-5.6-sol",
usage = null,
} = {}) {
return new Response(
@@ -567,7 +567,7 @@ test("chat pipeline persists Codex responses cache and reasoning tokens to call
buildRequest({
url: "http://localhost/v1/responses",
body: {
model: "codex/gpt-5.1-codex",
model: "codex/gpt-5.6-sol",
stream: false,
input: "Persist cache + reasoning usage",
},
@@ -983,7 +983,7 @@ test("chat pipeline translates OpenAI requests to Claude and returns OpenAI-shap
const response = await handleChat(
buildRequest({
body: {
model: "claude/claude-3-5-sonnet-20241022",
model: "claude/claude-sonnet-4-6",
stream: false,
messages: [{ role: "user", content: "Hello Claude" }],
},
@@ -1566,7 +1566,7 @@ test("chat pipeline falls back across combo models when the first provider fails
name: "combo-fallback",
strategy: "priority",
config: { maxRetries: 0, retryDelayMs: 0 },
models: ["openai/gpt-4o-mini", "claude/claude-3-5-sonnet-20241022"],
models: ["openai/gpt-4o-mini", "claude/claude-sonnet-4-6"],
});
const attempts = [];