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',