Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/app/(dashboard)/dashboard/combos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -761,15 +761,21 @@ export default function CombosPage() {
const [proxyConfig, setProxyConfig] = useState(null);
const { comboProxyAssignedIds, fetchComboProxyAssignments } = useComboProxyAssignments();
const [providerNodes, setProviderNodes] = useState([]);
const [showUsageGuide, setShowUsageGuide] = useState(() => {
// Lazy initializer instead of a mount effect (react-hooks/set-state-in-effect).
// SSR has no localStorage, so a lazy initializer reading it here returns a
// different value server-side (always "not dismissed") than the client's
// real stored value -- exactly the kind of source React's hydration
// mismatch check is built to catch, and in dev mode a mismatch forces a
// 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 [showUsageGuide, setShowUsageGuide] = useState(true);
useEffect(() => {
try {
return globalThis.localStorage?.getItem(COMBO_USAGE_GUIDE_STORAGE_KEY) !== "1";
setShowUsageGuide(globalThis.localStorage?.getItem(COMBO_USAGE_GUIDE_STORAGE_KEY) !== "1");
} catch {
// Ignore storage access errors (privacy mode / restricted environments)
return true;
}
});
}, []);
const [recentlyCreatedCombo, setRecentlyCreatedCombo] = useState("");
const [creatingKimiPreset, setCreatingKimiPreset] = useState(false);
const [comboDragIndex, setComboDragIndex] = useState(null);
Expand Down
21 changes: 18 additions & 3 deletions src/app/api/providers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ import {
fetchModelSyncInternal,
getModelSyncInternalBaseUrl,
} from "@/shared/services/modelSyncScheduler";
import { finalizeValidatedChatGptWebCodexSecrets } from "@omniroute/open-sse/services/chatgptWebCodexAdmin.ts";
// Dynamically imported below, inside the one `provider === "chatgpt-web-codex"`
// branch that needs it: this module's transitive chain pulls in tiktoken's
// WASM tokenizer, which Turbopack dev mode fails to resolve for this graph
// even with `tiktoken` listed in serverExternalPackages (the standalone
// Node require works fine; only Turbopack's bundling of this import path
// doesn't). A static top-level import evaluates that whole chain on EVERY
// /api/providers request regardless of provider, turning an unrelated-
// provider bug into a route-wide 500. Loading it lazily, only when actually
// needed, avoids paying that cost (and that risk) on the common path.
import { isAutoFetchModelsEnabled } from "@/lib/providerModels/modelDiscovery";
import { testSingleConnection } from "./[id]/test/route";
import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse";
Expand Down Expand Up @@ -204,6 +212,8 @@ export async function POST(request: Request) {
? providerSpecificData.validationId
: "";
try {
const { finalizeValidatedChatGptWebCodexSecrets } =
await import("@omniroute/open-sse/services/chatgptWebCodexAdmin.ts");
const finalized = finalizeValidatedChatGptWebCodexSecrets(apiKey || "", validationId);
persistedApiKey = finalized.encodedCredential;
providerSpecificData = { ...(providerSpecificData || {}) };
Expand Down Expand Up @@ -324,11 +334,16 @@ export async function POST(request: Request) {
})
.then((syncRes) => {
if (!syncRes.ok) {
console.log(`[providers] Auto-sync failed for ${newConnection.id}: ${syncRes.status}`);
console.log(
`[providers] Auto-sync failed for ${newConnection.id}: ${syncRes.status}`
);
}
})
.catch((err) => {
console.log(`[providers] Auto-sync error for ${newConnection.id}:`, err?.message || err);
console.log(
`[providers] Auto-sync error for ${newConnection.id}:`,
err?.message || err
);
});
} catch (syncSetupError) {
// Defensive: if URL parsing or header construction itself throws, do
Expand Down
13 changes: 12 additions & 1 deletion src/lib/providers/validation/chatgptWebCodex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@ import { rmSync } from "node:fs";
import { CHATGPT_WEB_CODEX_CONNECTOR_NAME } from "@/shared/constants/chatgptWebCodex";
import { inspectBrowserLoginCapabilities } from "@omniroute/open-sse/vendor/codex-chatgpt-web/browser-login.ts";
import { decodeChatGptWebCodexSecrets } from "@omniroute/open-sse/executors/chatgpt-web-codex/credentials.ts";
import { detectChromeExecutable } from "@omniroute/open-sse/executors/chatgpt-web-codex.ts";
import {
connectionRuntimePaths,
ensureConnectionStorageState,
ensureConnectionStorageStateFromCredential,
} from "@omniroute/open-sse/executors/chatgpt-web-codex/storageState.ts";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";

// detectChromeExecutable (executors/chatgpt-web-codex.ts) is imported
// dynamically below, not statically here: this module is re-exported through
// the shared `@/lib/providers/validation` barrel that every provider
// validator's callers pull in, and executors/chatgpt-web-codex.ts's own
// import chain (its vendor browser adapter -> token-estimate.ts -> tiktoken's
// WASM tokenizer) fails to bundle under Turbopack dev mode even with
// `tiktoken` server-externalized -- turning validation of an unrelated
// provider into a route-wide crash for anyone who merely imports the barrel.
// A static import here evaluates that whole chain unconditionally.

export async function validateChatGptWebCodexProvider({
apiKey,
providerSpecificData = {},
Expand Down Expand Up @@ -54,6 +63,8 @@ export async function validateChatGptWebCodexProvider({
};
}
const cdpEndpoint = process.env.CHATGPT_WEB_CODEX_CDP_URL?.trim();
const { detectChromeExecutable } =
await import("@omniroute/open-sse/executors/chatgpt-web-codex.ts");
const chromeExecutablePath = detectChromeExecutable(
typeof providerSpecificData.chromeExecutablePath === "string"
? providerSpecificData.chromeExecutablePath
Expand Down