From 072e756e4c01795b62a6cc584a16bd61ff880ca8 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Thu, 16 Jul 2026 03:52:46 +0800 Subject: [PATCH] Allow custom provider endpoint edits Stop blocking custom provider endpoint updates just because saved conversations still reference the provider. Keep delete protection tied to API modes and saved conversations, while removing the now-unused endpoint rewrite guard and its tests. --- src/popup/sections/ApiModes.jsx | 64 +- .../sections/api-modes-provider-utils.mjs | 166 ---- .../popup/api-modes-provider-utils.test.mjs | 743 ++---------------- 3 files changed, 50 insertions(+), 923 deletions(-) diff --git a/src/popup/sections/ApiModes.jsx b/src/popup/sections/ApiModes.jsx index 69f8584b9..37b649795 100644 --- a/src/popup/sections/ApiModes.jsx +++ b/src/popup/sections/ApiModes.jsx @@ -26,7 +26,6 @@ import { getProviderReferenceCheckApiModes, getReferencedCustomProviderIdsFromSessions, getSelectableProviders, - isProviderEndpointRewriteBlockedBySavedConversations, isProviderDeleteDisabled, isProviderReferencedByApiModes, loadSavedConversationState, @@ -71,7 +70,6 @@ const defaultProviderDraft = { const defaultProviderDraftValidation = { name: false, apiUrl: false, - savedConversations: false, } export function ApiModes({ config, updateConfig }) { @@ -203,27 +201,6 @@ export function ApiModes({ config, updateConfig }) { return getConfiguredCustomApiModesForSessionRecovery(recoveryApiModes, recoverySelectedApiMode) }, [apiModes, config.apiMode, editing, editingApiMode, editingIndex]) - const configuredCustomApiModesForSaveGuard = useMemo(() => { - let nextApiModes = apiModes - if (editing && editingIndex !== -1) { - nextApiModes = apiModes.map((apiMode, index) => - index === editingIndex ? editingApiMode : apiMode, - ) - } else if ( - editing && - editingIndex === -1 && - editingApiMode.groupName === 'customApiModelKeys' - ) { - nextApiModes = [...apiModes, editingApiMode] - } - const nextSelectedApiMode = - editing && editingIndex !== -1 && isApiModeSelected(apiModes[editingIndex], config) - ? editingApiMode - : config.apiMode - - return getConfiguredCustomApiModesForSessionRecovery(nextApiModes, nextSelectedApiMode) - }, [apiModes, config, editing, editingApiMode, editingIndex]) - const sessionReferencedProviderIds = useMemo( () => getReferencedCustomProviderIdsFromSessions( @@ -314,37 +291,13 @@ export function ApiModes({ config, updateConfig }) { pendingNewProvider && pendingNewProvider.id === providerEditingId ? pendingNewProvider : selectedCustomProvider || {} - const persistedProvider = customProviders.find((provider) => provider.id === providerEditingId) const endpointDraft = validateProviderEndpointDraft(providerDraft.apiUrl) const parsedEndpoint = endpointDraft.parsedEndpoint - const providerEndpointChanged = - Boolean(providerEditingId) && - Boolean(persistedProvider) && - parsedEndpoint.valid && - parsedEndpoint.chatCompletionsUrl !== resolveProviderChatEndpointUrl(persistedProvider) - const effectiveProviderSecrets = - pendingDeletedProviderSecretIds.length > 0 - ? applyDeletedProviderSecrets(config.providerSecrets, pendingDeletedProviderSecretIds) - : config.providerSecrets const nextProviderDraftValidation = { name: !providerName, apiUrl: !endpointDraft.valid, - savedConversations: - providerEndpointChanged && - isProviderEndpointRewriteBlockedBySavedConversations( - providerEditingId, - sessionsLoaded, - sessions, - effectiveProviders, - configuredCustomApiModesForSaveGuard, - effectiveProviderSecrets, - ), } - if ( - nextProviderDraftValidation.name || - nextProviderDraftValidation.apiUrl || - nextProviderDraftValidation.savedConversations - ) { + if (nextProviderDraftValidation.name || nextProviderDraftValidation.apiUrl) { setProviderDraftValidation(nextProviderDraftValidation) if (nextProviderDraftValidation.name) { providerNameInputRef.current?.focus() @@ -602,11 +555,10 @@ export function ApiModes({ config, updateConfig }) { placeholder={t('Provider')} onChange={(e) => { setProviderDraft({ ...providerDraft, name: e.target.value }) - if (providerDraftValidation.name || providerDraftValidation.savedConversations) { + if (providerDraftValidation.name) { setProviderDraftValidation({ ...providerDraftValidation, name: false, - savedConversations: false, }) } }} @@ -621,11 +573,10 @@ export function ApiModes({ config, updateConfig }) { title={t('API Url')} onChange={(e) => { setProviderDraft({ ...providerDraft, apiUrl: e.target.value }) - if (providerDraftValidation.apiUrl || providerDraftValidation.savedConversations) { + if (providerDraftValidation.apiUrl) { setProviderDraftValidation({ ...providerDraftValidation, apiUrl: false, - savedConversations: false, }) } }} @@ -635,15 +586,6 @@ export function ApiModes({ config, updateConfig }) { {providerDraftValidation.apiUrl && (
{t('Please enter a full Chat Completions URL')}
)} - {providerDraftValidation.savedConversations && ( -
- {t( - sessionsLoaded - ? 'This provider endpoint is still needed by saved conversations' - : 'Loading saved conversations…', - )} -
- )}
normalizeText(provider?.id) === normalizedTargetProviderId, - ) - if (matchesTargetProvider) return true - } - - return false -} - function getProviderIdsMatchingSessionLabel(session = null, providers = [], apiModes = []) { if (normalizeText(session?.apiMode?.groupName) !== 'customApiModelKeys') return [] @@ -541,42 +519,6 @@ function getProviderIdsMatchingSessionLabel(session = null, providers = [], apiM ) } -function canSessionRecoverViaLegacyLabelFallback(session = null, apiModes = []) { - if (normalizeText(session?.apiMode?.groupName) !== 'customApiModelKeys') return false - - const normalizedSessionLabel = { - groupName: normalizeText(session?.apiMode?.groupName), - itemName: normalizeText(session?.apiMode?.itemName), - isCustom: Boolean(session?.apiMode?.isCustom), - customName: normalizeText(session?.apiMode?.customName), - } - if (!normalizedSessionLabel.customName) return false - - const allCandidates = (Array.isArray(apiModes) ? apiModes : []).filter((apiMode) => { - if (!apiMode || typeof apiMode !== 'object') return false - return ( - normalizeText(apiMode.groupName) === normalizedSessionLabel.groupName && - normalizeText(apiMode.customName) === normalizedSessionLabel.customName - ) - }) - const exactCandidates = allCandidates.filter( - (apiMode) => - normalizeText(apiMode?.itemName) === normalizedSessionLabel.itemName && - Boolean(apiMode?.isCustom) === normalizedSessionLabel.isCustom, - ) - const matchedApiModes = exactCandidates.length === 1 ? exactCandidates : [] - const isLegacyCustomShape = !normalizedSessionLabel.itemName - const fallbackApiModes = - matchedApiModes.length === 0 && isLegacyCustomShape && allCandidates.length === 1 - ? allCandidates - : matchedApiModes - - return fallbackApiModes - .map((apiMode) => normalizeProviderId(apiMode?.providerId)) - .filter((providerId, index, providerIds) => providerIds.indexOf(providerId) === index) - .includes('legacy-custom-default') -} - export function getReferencedCustomProviderIdsFromSessions( sessions = [], providers = [], @@ -627,114 +569,6 @@ export function getReferencedCustomProviderIdsFromSessions( return Array.from(referencedProviderIds) } -export function isProviderReferencedBySessionsViaUrl( - providerId, - sessions = [], - providers = [], - apiModes = [], - providerSecrets = {}, -) { - const normalizedTargetProviderId = normalizeText(providerId) - if (!normalizedTargetProviderId || normalizedTargetProviderId === 'legacy-custom-default') { - return false - } - - for (const session of Array.isArray(sessions) ? sessions : []) { - if (normalizeText(session?.apiMode?.groupName) !== 'customApiModelKeys') continue - - const matchedByProviderId = getProvidersMatchingSessionProviderId( - providers, - session?.apiMode?.providerId, - ) - if (matchedByProviderId.length > 0) continue - - const matchedByCustomUrl = getProvidersMatchingLegacySessionUrl(providers, session) - const matchesTargetByCustomUrl = matchedByCustomUrl.some( - (provider) => normalizeText(provider?.id) === normalizedTargetProviderId, - ) - if (!matchesTargetByCustomUrl) continue - const sessionApiKey = - session?.apiMode && - typeof session.apiMode === 'object' && - typeof session.apiMode.apiKey === 'string' - ? session.apiMode.apiKey.trim() - : '' - if (matchedByCustomUrl.length > 1 && sessionApiKey) { - const matchedBySessionKey = matchedByCustomUrl.filter((provider) => { - if (!provider || typeof provider !== 'object') return false - const providerSecretValue = - providerSecrets && typeof providerSecrets === 'object' ? providerSecrets[provider.id] : '' - return String(providerSecretValue || '').trim() === sessionApiKey - }) - if ( - matchedBySessionKey.length === 1 && - normalizeText(matchedBySessionKey[0]?.id) !== normalizedTargetProviderId - ) { - continue - } - } - - const matchedByLabel = getProviderIdsMatchingSessionLabel(session, providers, apiModes) - if (matchedByLabel.length > 0) continue - if (canSessionRecoverViaLegacyLabelFallback(session, apiModes)) continue - - return true - } - - return false -} - -export function isProviderEndpointRewriteBlockedBySavedConversations( - providerId, - sessionsLoaded = true, - sessions = [], - providers = [], - apiModes = [], - providerSecrets = {}, -) { - if (!sessionsLoaded) return true - - const normalizedTargetProviderId = normalizeProviderId(providerId) - const isProviderReferencedBySessionsViaLabel = () => { - if (!normalizedTargetProviderId || normalizedTargetProviderId === 'legacy-custom-default') { - return false - } - - for (const session of Array.isArray(sessions) ? sessions : []) { - if (normalizeText(session?.apiMode?.groupName) !== 'customApiModelKeys') continue - if ( - getProvidersMatchingSessionProviderId(providers, session?.apiMode?.providerId).length > 0 - ) { - continue - } - if (getProvidersMatchingLegacySessionUrl(providers, session).length > 0) { - continue - } - if ( - getProviderIdsMatchingSessionLabel(session, providers, apiModes).includes( - normalizedTargetProviderId, - ) - ) { - return true - } - } - - return false - } - - return ( - isProviderReferencedBySessionsViaProviderId(providerId, sessions, providers) || - isProviderReferencedBySessionsViaUrl( - providerId, - sessions, - providers, - apiModes, - providerSecrets, - ) || - isProviderReferencedBySessionsViaLabel() - ) -} - export function getApiModeDisplayLabel(apiMode, t, providers = []) { const modelName = apiModeToModelName(apiMode) const fallbackLabel = modelNameToDesc(modelName, t) diff --git a/tests/unit/popup/api-modes-provider-utils.test.mjs b/tests/unit/popup/api-modes-provider-utils.test.mjs index 167dc4732..520e57d73 100644 --- a/tests/unit/popup/api-modes-provider-utils.test.mjs +++ b/tests/unit/popup/api-modes-provider-utils.test.mjs @@ -11,11 +11,9 @@ import { getSelectedApiModeOptionValue, getConfiguredCustomApiModesForSessionRecovery, getProviderDeleteDisabledReasonKey, - isProviderEndpointRewriteBlockedBySavedConversations, getProviderReferenceCheckApiModes, getReferencedCustomProviderIdsFromSessions, getSelectableProviders, - isProviderReferencedBySessionsViaUrl, isProviderDeleteDisabled, isProviderReferencedByApiModes, parseChatCompletionsEndpointUrl, @@ -761,313 +759,6 @@ test('isProviderReferencedByApiModes only matches custom modes with the same pro assert.equal(isProviderReferencedByApiModes('provider-b', apiModes), false) }) -test('provider endpoint rewrite stays fail-closed while saved conversations load', () => { - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations('provider-a', false, [], []), - true, - ) -}) - -test('provider endpoint rewrite is blocked by direct saved conversation provider id reference', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: 'provider-a', - }, - }, - ] - const providers = [{ id: 'provider-a', enabled: true }] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations('provider-a', true, sessions, providers), - true, - ) -}) - -test('provider endpoint rewrite is blocked by migrated saved conversation provider id reference', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: 'Provider A', - }, - }, - ] - const providers = [{ id: 'provider-a', enabled: true }] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations('provider-a', true, sessions, providers), - true, - ) -}) - -test('provider endpoint rewrite does not treat legacy custom provider id as direct reference', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: 'legacy-custom-default', - customUrl: '', - }, - }, - ] - const providers = [{ id: 'provider-a', enabled: true }] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations('provider-a', true, sessions, providers), - false, - ) -}) - -test('provider endpoint rewrite is blocked by url-only saved conversation recovery', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: 'stale-provider', - customUrl: 'https://old.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://old.example.com/v1/chat/completions', - }, - ] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations('provider-a', true, sessions, providers), - true, - ) -}) - -test('provider endpoint rewrite is blocked by label-only saved conversation recovery targeting provider', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: '', - }, - }, - ] - const providers = [{ id: 'provider-a', enabled: true }] - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'proxy-model', - providerId: 'provider-a', - active: true, - }, - ] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations( - 'provider-a', - true, - sessions, - providers, - apiModes, - ), - true, - ) -}) - -test('provider endpoint rewrite is allowed when providerId recovery already matches another provider', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - providerId: 'provider-b', - customName: 'proxy-model', - customUrl: '', - }, - }, - ] - const providers = [ - { id: 'provider-a', enabled: true }, - { id: 'provider-b', enabled: true }, - ] - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'proxy-model', - providerId: 'provider-a', - active: true, - }, - ] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations( - 'provider-a', - true, - sessions, - providers, - apiModes, - ), - false, - ) -}) - -test('provider endpoint rewrite is allowed when customUrl recovery already matches another provider', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: 'https://provider-b.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { id: 'provider-a', enabled: true }, - { - id: 'provider-b', - enabled: true, - chatCompletionsUrl: 'https://provider-b.example.com/v1/chat/completions', - }, - ] - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'proxy-model', - providerId: 'provider-a', - active: true, - }, - ] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations( - 'provider-a', - true, - sessions, - providers, - apiModes, - ), - false, - ) -}) - -test('provider endpoint rewrite is allowed when label-only recovery targets another provider', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: '', - }, - }, - ] - const providers = [ - { id: 'provider-a', enabled: true }, - { id: 'provider-b', enabled: true }, - ] - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'proxy-model', - providerId: 'provider-b', - active: true, - }, - ] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations( - 'provider-a', - true, - sessions, - providers, - apiModes, - ), - false, - ) -}) - -test('provider endpoint rewrite ignores legacy label fallback for non-legacy providers', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: '', - isCustom: false, - providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: '', - }, - }, - ] - const providers = [{ id: 'provider-a', enabled: true }] - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'proxy-model', - providerId: 'legacy-custom-default', - active: true, - }, - ] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations( - 'provider-a', - true, - sessions, - providers, - apiModes, - ), - false, - ) -}) - -test('provider endpoint rewrite is allowed when saved conversations do not reference provider', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: 'provider-b', - }, - }, - { - apiMode: { - groupName: 'chatgptWebModelKeys', - providerId: 'provider-a', - }, - }, - ] - const providers = [ - { id: 'provider-a', enabled: true }, - { id: 'provider-b', enabled: true }, - ] - - assert.equal( - isProviderEndpointRewriteBlockedBySavedConversations('provider-a', true, sessions, providers), - false, - ) -}) - test('getProviderReferenceCheckApiModes excludes the currently edited mode from delete checks', () => { const apiModes = [ { groupName: 'customApiModelKeys', providerId: 'provider-a' }, @@ -1231,207 +922,77 @@ test('session recovery excludes the edited selected custom mode and uses in-flig recoveryApiModes, recoverySelectedApiMode, ) - const referencedProviderIds = getReferencedCustomProviderIdsFromSessions( - sessions, - providers, - configuredApiModes, - ) - - assert.deepEqual(configuredApiModes, [editingApiMode]) - assert.deepEqual(referencedProviderIds, ['new-provider']) -}) - -test('session recovery still keeps persisted selected mode when editing a different row', () => { - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'mode-a', - providerId: 'provider-a', - active: true, - }, - ] - const configApiMode = { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'mode-b', - providerId: 'provider-b', - active: true, - } - const editingApiMode = { - ...apiModes[0], - providerId: 'provider-a-updated', - } - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'mode-b', - providerId: 'stale-provider-id', - customUrl: '', - }, - }, - ] - const providers = [ - { id: 'provider-a', enabled: true }, - { id: 'provider-b', enabled: true }, - ] - - const recoveryApiModes = getProviderReferenceCheckApiModes(apiModes, true, 0) - const recoverySelectedApiMode = shouldIncludeSelectedApiModeInReferenceCheck( - apiModes, - true, - 0, - configApiMode, - ) - ? configApiMode - : editingApiMode - const configuredApiModes = getConfiguredCustomApiModesForSessionRecovery( - recoveryApiModes, - recoverySelectedApiMode, - ) - const referencedProviderIds = getReferencedCustomProviderIdsFromSessions( - sessions, - providers, - configuredApiModes, - ) - - assert.deepEqual(configuredApiModes, [configApiMode]) - assert.deepEqual(referencedProviderIds, ['provider-b']) -}) - -test('save guard migration can use in-flight replacement provider and mode', () => { - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'old-mode', - providerId: 'old-provider', - active: true, - }, - ] - const configApiMode = { - groupName: 'gptApiModelKeys', - itemName: 'gpt-4', - active: true, - } - const editingApiMode = { - ...apiModes[0], - customName: 'proxy-model', - providerId: 'new-provider', - } - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: 'https://old.example.com/v1/chat/completions', - }, - }, - ] - const persistedProviders = [ - { - id: 'old-provider', - enabled: true, - chatCompletionsUrl: 'https://old.example.com/v1/chat/completions', - }, - ] - const effectiveProviders = [ - ...persistedProviders, - { - id: 'new-provider', - enabled: true, - chatCompletionsUrl: 'https://new.example.com/v1/chat/completions', - }, - ] - const saveGuardApiModes = getConfiguredCustomApiModesForSessionRecovery( - apiModes.map((apiMode, index) => (index === 0 ? editingApiMode : apiMode)), - configApiMode, - ) - - assert.equal( - isProviderReferencedBySessionsViaUrl('old-provider', sessions, persistedProviders, apiModes), - true, - ) - assert.equal( - isProviderReferencedBySessionsViaUrl( - 'old-provider', - sessions, - effectiveProviders, - saveGuardApiModes, - ), - false, + const referencedProviderIds = getReferencedCustomProviderIdsFromSessions( + sessions, + providers, + configuredApiModes, ) + + assert.deepEqual(configuredApiModes, [editingApiMode]) + assert.deepEqual(referencedProviderIds, ['new-provider']) }) -test('save guard create flow can use in-flight new custom mode for label recovery', () => { - const apiModes = [] +test('session recovery still keeps persisted selected mode when editing a different row', () => { + const apiModes = [ + { + groupName: 'customApiModelKeys', + itemName: 'customModel', + isCustom: true, + customName: 'mode-a', + providerId: 'provider-a', + active: true, + }, + ] const configApiMode = { - groupName: 'gptApiModelKeys', - itemName: 'gpt-4', - active: true, - } - const editingApiMode = { groupName: 'customApiModelKeys', itemName: 'customModel', isCustom: true, - customName: 'proxy-model', - providerId: 'new-provider', + customName: 'mode-b', + providerId: 'provider-b', active: true, } + const editingApiMode = { + ...apiModes[0], + providerId: 'provider-a-updated', + } const sessions = [ { apiMode: { groupName: 'customApiModelKeys', itemName: 'customModel', isCustom: true, + customName: 'mode-b', providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: 'https://old.example.com/v1/chat/completions', + customUrl: '', }, }, ] - const persistedProviders = [ - { - id: 'old-provider', - enabled: true, - chatCompletionsUrl: 'https://old.example.com/v1/chat/completions', - }, - ] - const effectiveProviders = [ - ...persistedProviders, - { - id: 'new-provider', - enabled: true, - chatCompletionsUrl: 'https://new.example.com/v1/chat/completions', - }, + const providers = [ + { id: 'provider-a', enabled: true }, + { id: 'provider-b', enabled: true }, ] - const saveGuardApiModes = getConfiguredCustomApiModesForSessionRecovery( - [...apiModes, editingApiMode], - configApiMode, - ) - assert.equal( - isProviderReferencedBySessionsViaUrl('old-provider', sessions, persistedProviders, apiModes), + const recoveryApiModes = getProviderReferenceCheckApiModes(apiModes, true, 0) + const recoverySelectedApiMode = shouldIncludeSelectedApiModeInReferenceCheck( + apiModes, true, + 0, + configApiMode, ) - assert.equal( - isProviderReferencedBySessionsViaUrl( - 'old-provider', - sessions, - effectiveProviders, - saveGuardApiModes, - ), - false, + ? configApiMode + : editingApiMode + const configuredApiModes = getConfiguredCustomApiModesForSessionRecovery( + recoveryApiModes, + recoverySelectedApiMode, + ) + const referencedProviderIds = getReferencedCustomProviderIdsFromSessions( + sessions, + providers, + configuredApiModes, ) + + assert.deepEqual(configuredApiModes, [configApiMode]) + assert.deepEqual(referencedProviderIds, ['provider-b']) }) test('createProviderId reuses missing provider ids that only stale modes still reference', () => { @@ -1812,216 +1373,6 @@ test('getReferencedCustomProviderIdsFromSessions conservatively returns all lega ]) }) -test('isProviderReferencedBySessionsViaUrl returns true when a session only recovers via customUrl', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: '', - customUrl: 'https://proxy.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - ] - - assert.equal(isProviderReferencedBySessionsViaUrl('provider-a', sessions, providers), true) -}) - -test('isProviderReferencedBySessionsViaUrl keeps multi-match customUrl sessions protected', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: '', - customUrl: 'https://proxy.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - { - id: 'provider-b', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - ] - - assert.equal(isProviderReferencedBySessionsViaUrl('provider-a', sessions, providers), true) -}) - -test('isProviderReferencedBySessionsViaUrl returns false when apiKey uniquely resolves to another provider', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: '', - apiKey: 'provider-b-secret', - customUrl: 'https://proxy.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - { - id: 'provider-b', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - ] - const providerSecrets = { - 'provider-a': 'provider-a-secret', - 'provider-b': 'provider-b-secret', - } - - assert.equal( - isProviderReferencedBySessionsViaUrl('provider-a', sessions, providers, [], providerSecrets), - false, - ) -}) - -test('isProviderReferencedBySessionsViaUrl returns false for exact and normalized providerId recovery', () => { - const exactSessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: 'provider-a', - customUrl: 'https://old.example.com/v1/chat/completions', - }, - }, - ] - const normalizedSessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: ' Provider A ', - customUrl: 'https://old.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - ] - - assert.equal(isProviderReferencedBySessionsViaUrl('provider-a', exactSessions, providers), false) - assert.equal( - isProviderReferencedBySessionsViaUrl('provider-a', normalizedSessions, providers), - false, - ) -}) - -test('isProviderReferencedBySessionsViaUrl returns false for label-only recovery', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: '', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - ] - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'proxy-model', - providerId: 'provider-a', - active: true, - }, - ] - - assert.equal( - isProviderReferencedBySessionsViaUrl('provider-a', sessions, providers, apiModes), - false, - ) -}) - -test('isProviderReferencedBySessionsViaUrl returns false for legacy label fallback recovery', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - providerId: 'stale-provider-id', - customName: 'proxy-model', - customUrl: 'https://proxy.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - ] - const apiModes = [ - { - groupName: 'customApiModelKeys', - itemName: 'customModel', - isCustom: true, - customName: 'proxy-model', - providerId: 'legacy-custom-default', - active: true, - }, - ] - - assert.equal( - isProviderReferencedBySessionsViaUrl('provider-a', sessions, providers, apiModes), - false, - ) -}) - -test('isProviderReferencedBySessionsViaUrl returns false when there is no recover path', () => { - const sessions = [ - { - apiMode: { - groupName: 'customApiModelKeys', - providerId: '', - customUrl: 'https://missing.example.com/v1/chat/completions', - }, - }, - ] - const providers = [ - { - id: 'provider-a', - enabled: true, - chatCompletionsUrl: 'https://proxy.example.com/v1/chat/completions', - }, - ] - - assert.equal(isProviderReferencedBySessionsViaUrl('provider-a', sessions, providers), false) -}) - test('getApiModeDisplayLabel shows provider name for custom provider-backed modes', () => { const apiMode = { groupName: 'customApiModelKeys',