mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix(ui): prevent silent overwrite of existing API key connections on re-add (#6499)
Unique default connection name prevents silent overwrite of existing API-key connections. Integrated into release/v3.8.46 with a unit-tested helper; remaining file-size reds are pre-existing base-red drift.
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **fix(dashboard):** adding a second API key connection for the same provider no longer silently overwrites the first — the Add-API-key modal now derives a unique default connection name (`main`, then `main-2`, `main-3`, …) so the backend name-based upsert can't collide (#6499 — thanks @dilneiss).
|
||||
- **fix(compression):** the session-dedup engine now also deduplicates a large multi-line block repeated **within a single message** (intra-message dedup), not just across turns; the compression-preview API surfaces a `fallbackReason`, and the fusion panel reports how many models were rate-limited vs failed on total-panel failure (#6501 — thanks @chirag127).
|
||||
- **fix(compression):** a stacked-pipeline step naming an unregistered engine now surfaces a `validationErrors` entry instead of silently no-op'ing, so misconfigured pipelines are visible in the preview API (#6506 — thanks @chirag127).
|
||||
- **feat(usage):** add a **Codex reset-credit redemption** flow to the Provider Limits UI — a `useCodexResetCreditRedemption` hook + `/api/usage/codex-reset-credit` route + `codexResetCredits` lib let you redeem banked Codex reset credits from the quota card. Regression guard: `tests/unit/codex-reset-credits.test.ts`. (thanks @JxnLexn)
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
"src/app/(dashboard)/dashboard/playground/components/tabs/ApiTab.tsx": 847,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": 784,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/ConnectionRow.tsx": 942,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 952,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 959,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1278,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 954,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderModels.ts": 155,
|
||||
@@ -378,5 +378,6 @@
|
||||
"_rebaseline_2026_07_05_6235_doubao_dola": "PR #6235 own growth: tests/unit/web-cookie-providers-new.test.ts 850->890 (+40 = doubao-web -> Dola global provider switch regression cases: new host/cookie-domain/token-source assertions for www.dola.com). Cohesive test growth alongside the provider switch; contributor backryun. Fast-path PR->release skips check:file-size, so this bump lands with the PR.",
|
||||
"_rebaseline_2026_07_06_v3845_release_close": "Release v3.8.45 cycle-close rebaseline (captain, sess ce897453): 13 files grown by the cycle's merged fix/feature PRs (#6216 streaming fixes + request-logger UI grew RequestLoggerV2/chat/chatHelpers/auth/stream/response-sanitizer.test; #6251/#6253 dashboard UX grew combos page/modals/wizard/ComboDefaultsTab/ProxyRegistryManager/providerPageHelpers). Growth is legitimate merged-feature code, absorbed at release per Phase 0 drift policy; all remain frozen (cannot grow further).",
|
||||
"_rebaseline_2026_07_06_6118_zed_oauthmodal": "PR #6118 own growth: OAuthModal.tsx 989->993 (+4 = Zed hosted native-app sign-in modal branch). Cohesive UI growth for the zed-hosted OAuth provider; not extractable. The prior 6118 comment set the note but left the frozen value at 989.",
|
||||
"_rebaseline_2026_07_06_6351_glm_team_quota": "PR #6351 own growth (GLM team-plan quota fields threaded through the connection modals; new GlmTeamQuotaFields.tsx extracted): AddApiKeyModal.tsx ->951 (+9), EditConnectionModal.tsx ->1277 (+18). Absorbs the pre-existing session base-red on these frozen modals; release captain rebaseline-at-release supersedes."
|
||||
"_rebaseline_2026_07_06_6351_glm_team_quota": "PR #6351 own growth (GLM team-plan quota fields threaded through the connection modals; new GlmTeamQuotaFields.tsx extracted): AddApiKeyModal.tsx ->951 (+9), EditConnectionModal.tsx ->1277 (+18). Absorbs the pre-existing session base-red on these frozen modals; release captain rebaseline-at-release supersedes.",
|
||||
"_rebaseline_2026_07_06_6499_unique_default_name": "PR #6499 own growth: AddApiKeyModal.tsx 952->959 (+7 = a unique default connection name so a second API key for the same provider does not reuse 'main' and trigger the backend name-based upsert that silently overwrote the first connection). The pure name derivation was extracted to computeConnectionDefaultName.ts (unit-tested) to keep the growth minimal; the contributor's original full-form-reset rewrite was trimmed to a spread reset to avoid dropping the GLM team-quota fields #6351 added and to hold the frozen god-file growth down. Release captain rebaseline-at-release supersedes."
|
||||
}
|
||||
|
||||
@@ -709,6 +709,7 @@ export default function ProviderDetailPageClient() {
|
||||
isCommandCode={isCommandCode}
|
||||
isUpstreamProxyProvider={isUpstreamProxyProvider}
|
||||
subscriptionRisk={subscriptionRisk}
|
||||
existingConnectionCount={connections.length}
|
||||
showRiskNoticeModal={showRiskNoticeModal}
|
||||
handleConfirmRiskNotice={handleConfirmRiskNotice}
|
||||
handleCancelRiskNotice={handleCancelRiskNotice}
|
||||
|
||||
@@ -50,6 +50,7 @@ interface ProviderModalsPanelProps {
|
||||
isCommandCode: boolean;
|
||||
isUpstreamProxyProvider: boolean;
|
||||
subscriptionRisk: boolean;
|
||||
existingConnectionCount?: number;
|
||||
// Risk notice
|
||||
showRiskNoticeModal: boolean;
|
||||
handleConfirmRiskNotice: () => void;
|
||||
@@ -146,6 +147,7 @@ export default function ProviderModalsPanel({
|
||||
isCcCompatible,
|
||||
isUpstreamProxyProvider,
|
||||
subscriptionRisk,
|
||||
existingConnectionCount,
|
||||
showRiskNoticeModal,
|
||||
handleConfirmRiskNotice,
|
||||
handleCancelRiskNotice,
|
||||
@@ -280,6 +282,7 @@ export default function ProviderModalsPanel({
|
||||
providerName={providerInfo.name}
|
||||
providerWebsite={providerInfo.website}
|
||||
initialBaseUrl={siliconFlowInitialBaseUrl}
|
||||
existingConnectionCount={existingConnectionCount}
|
||||
isCompatible={isCompatible}
|
||||
isAnthropic={isAnthropicProtocolCompatible}
|
||||
isCcCompatible={isCcCompatible}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { useOpenRouterPresetControl } from "../OpenRouterPresetInput";
|
||||
import WebSessionCredentialGuide from "../WebSessionCredentialGuide";
|
||||
import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields";
|
||||
import { buildAddProviderSpecificData } from "./connectionProviderSpecificData";
|
||||
import { computeConnectionDefaultName } from "./computeConnectionDefaultName";
|
||||
import QuotaScrapingFields, { EMPTY_QUOTA_SCRAPING_FIELDS } from "./QuotaScrapingFields";
|
||||
import GlmTeamQuotaFields, { EMPTY_GLM_TEAM_QUOTA_FIELDS } from "./GlmTeamQuotaFields";
|
||||
export interface AddApiKeyModalProps {
|
||||
@@ -41,6 +42,7 @@ export interface AddApiKeyModalProps {
|
||||
providerName?: string;
|
||||
providerWebsite?: string;
|
||||
initialBaseUrl?: string;
|
||||
existingConnectionCount?: number;
|
||||
isCompatible?: boolean;
|
||||
isAnthropic?: boolean;
|
||||
isCcCompatible?: boolean;
|
||||
@@ -64,6 +66,7 @@ export default function AddApiKeyModal({
|
||||
providerName,
|
||||
providerWebsite,
|
||||
initialBaseUrl,
|
||||
existingConnectionCount = 0,
|
||||
isCompatible,
|
||||
isAnthropic,
|
||||
isCcCompatible,
|
||||
@@ -114,7 +117,7 @@ export default function AddApiKeyModal({
|
||||
}[commandCodeAuthState.phase]
|
||||
: null;
|
||||
const [formData, setFormData] = useState({
|
||||
name: "main", // #5421: required field; default resists autofill garbage (was "" → "wiw")
|
||||
name: computeConnectionDefaultName(existingConnectionCount),
|
||||
apiKey: "",
|
||||
tokenSecret: "", // #5446 — Modal Token Secret (joined with apiKey as id:secret)
|
||||
defaultModel: "",
|
||||
@@ -148,11 +151,15 @@ export default function AddApiKeyModal({
|
||||
const wasOpen = wasOpenRef.current;
|
||||
wasOpenRef.current = isOpen;
|
||||
if (!isOpen || wasOpen) return;
|
||||
// On open, reset baseUrl and assign a unique default name so a second API key
|
||||
// for the same provider doesn't reuse "main" and trigger the backend
|
||||
// name-based upsert that would silently overwrite the first connection (#6499).
|
||||
setFormData((current) => ({
|
||||
...current,
|
||||
name: computeConnectionDefaultName(existingConnectionCount),
|
||||
baseUrl: initialBaseUrl || defaultBaseUrl,
|
||||
}));
|
||||
}, [defaultBaseUrl, initialBaseUrl, isOpen]);
|
||||
}, [defaultBaseUrl, initialBaseUrl, isOpen, existingConnectionCount]);
|
||||
const bulkSupported = supportsBulkApiKey(provider);
|
||||
const [mode, setMode] = useState<"single" | "bulk">("single");
|
||||
const [bulkText, setBulkText] = useState("");
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// #6499 — a fresh API-key connection defaults its name to "main". The backend
|
||||
// upserts connections by (provider, name), so opening the modal a second time for
|
||||
// the same provider with the same "main" name silently OVERWRITES the first
|
||||
// connection. Deriving a unique default from the existing connection count keeps
|
||||
// the first connection ("main") backward-compatible while giving each subsequent
|
||||
// one a distinct name ("main-2", "main-3", …).
|
||||
export function computeConnectionDefaultName(existingConnectionCount?: number): string {
|
||||
const count = existingConnectionCount ?? 0;
|
||||
return count <= 0 ? "main" : `main-${count + 1}`;
|
||||
}
|
||||
27
tests/unit/compute-connection-default-name-6499.test.ts
Normal file
27
tests/unit/compute-connection-default-name-6499.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { computeConnectionDefaultName } from "@/app/(dashboard)/dashboard/providers/[id]/components/modals/computeConnectionDefaultName";
|
||||
|
||||
// #6499 — the backend upserts API-key connections by (provider, name), so a second
|
||||
// connection defaulting to "main" silently overwrites the first. The default name
|
||||
// must stay "main" for the first connection (backward compatible) and get a unique
|
||||
// numeric suffix afterwards.
|
||||
|
||||
test("first connection defaults to 'main'", () => {
|
||||
assert.equal(computeConnectionDefaultName(0), "main");
|
||||
});
|
||||
|
||||
test("undefined count is treated as zero → 'main'", () => {
|
||||
assert.equal(computeConnectionDefaultName(undefined), "main");
|
||||
});
|
||||
|
||||
test("subsequent connections get a unique numeric suffix", () => {
|
||||
assert.equal(computeConnectionDefaultName(1), "main-2");
|
||||
assert.equal(computeConnectionDefaultName(2), "main-3");
|
||||
assert.equal(computeConnectionDefaultName(9), "main-10");
|
||||
});
|
||||
|
||||
test("negative counts are clamped to 'main' (never a broken 'main-0')", () => {
|
||||
assert.equal(computeConnectionDefaultName(-1), "main");
|
||||
});
|
||||
Reference in New Issue
Block a user