Makelore 2.0 initial clean snapshot
This commit is contained in:
381
electron/services/providers/provider-service.ts
Normal file
381
electron/services/providers/provider-service.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import {
|
||||
PROVIDER_DEFINITIONS,
|
||||
getProviderDefinition,
|
||||
} from '../../shared/providers/registry';
|
||||
import type {
|
||||
ProviderAccount,
|
||||
ProviderConfig,
|
||||
ProviderDefinition,
|
||||
} from '../../shared/providers/types';
|
||||
import type { UserSyncProviderAccount } from '../../../shared/user-sync';
|
||||
import { ensureProviderStoreMigrated } from './provider-migration';
|
||||
import {
|
||||
deleteProviderAccount,
|
||||
getDefaultProviderAccountId,
|
||||
getProviderAccount,
|
||||
listProviderAccounts,
|
||||
providerAccountToConfig,
|
||||
providerConfigToAccount,
|
||||
saveProviderAccount,
|
||||
setDefaultProviderAccount,
|
||||
} from './provider-store';
|
||||
import {
|
||||
deleteApiKey,
|
||||
deleteProvider,
|
||||
getApiKey,
|
||||
hasApiKey,
|
||||
setDefaultProvider,
|
||||
storeApiKey,
|
||||
} from '../../utils/secure-storage';
|
||||
import type { ProviderWithKeyInfo } from '../../shared/providers/types';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
function maskApiKey(apiKey: string | null): string | null {
|
||||
if (!apiKey) return null;
|
||||
if (apiKey.length > 12) {
|
||||
return `${apiKey.substring(0, 4)}${'*'.repeat(apiKey.length - 8)}${apiKey.substring(apiKey.length - 4)}`;
|
||||
}
|
||||
return '*'.repeat(apiKey.length);
|
||||
}
|
||||
|
||||
const legacyProviderApiWarned = new Set<string>();
|
||||
|
||||
function logLegacyProviderApiUsage(method: string, replacement: string): void {
|
||||
if (legacyProviderApiWarned.has(method)) {
|
||||
return;
|
||||
}
|
||||
legacyProviderApiWarned.add(method);
|
||||
logger.warn(
|
||||
`[provider-migration] Legacy provider API "${method}" is deprecated. Migrate to "${replacement}".`,
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeSyncedMetadata(metadata: Record<string, unknown>): ProviderAccount['metadata'] {
|
||||
const result: ProviderAccount['metadata'] = {};
|
||||
if (typeof metadata.region === 'string' && metadata.region.trim()) {
|
||||
result.region = metadata.region.trim();
|
||||
}
|
||||
if (typeof metadata.email === 'string' && metadata.email.trim()) {
|
||||
result.email = metadata.email.trim();
|
||||
}
|
||||
if (typeof metadata.resourceUrl === 'string' && metadata.resourceUrl.trim()) {
|
||||
result.resourceUrl = metadata.resourceUrl.trim();
|
||||
}
|
||||
if (Array.isArray(metadata.customModels)) {
|
||||
const customModels = metadata.customModels
|
||||
.filter((model): model is string => typeof model === 'string' && Boolean(model.trim()))
|
||||
.map((model) => model.trim());
|
||||
if (customModels.length > 0) {
|
||||
result.customModels = customModels;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class ProviderService {
|
||||
async listVendors(): Promise<ProviderDefinition[]> {
|
||||
return PROVIDER_DEFINITIONS;
|
||||
}
|
||||
|
||||
async listAccounts(): Promise<ProviderAccount[]> {
|
||||
await ensureProviderStoreMigrated();
|
||||
return listProviderAccounts();
|
||||
}
|
||||
|
||||
async getAccount(accountId: string): Promise<ProviderAccount | null> {
|
||||
await ensureProviderStoreMigrated();
|
||||
return getProviderAccount(accountId);
|
||||
}
|
||||
|
||||
async getDefaultAccountId(): Promise<string | undefined> {
|
||||
await ensureProviderStoreMigrated();
|
||||
return getDefaultProviderAccountId();
|
||||
}
|
||||
|
||||
async createAccount(account: ProviderAccount, apiKey?: string): Promise<ProviderAccount> {
|
||||
await ensureProviderStoreMigrated();
|
||||
await saveProviderAccount(account);
|
||||
if (apiKey !== undefined && apiKey.trim()) {
|
||||
await storeApiKey(account.id, apiKey.trim());
|
||||
}
|
||||
return (await getProviderAccount(account.id)) ?? account;
|
||||
}
|
||||
|
||||
async updateAccount(
|
||||
accountId: string,
|
||||
patch: Partial<ProviderAccount>,
|
||||
apiKey?: string,
|
||||
): Promise<ProviderAccount> {
|
||||
await ensureProviderStoreMigrated();
|
||||
const existing = await getProviderAccount(accountId);
|
||||
if (!existing) {
|
||||
throw new Error('Provider account not found');
|
||||
}
|
||||
|
||||
const nextAccount: ProviderAccount = {
|
||||
...existing,
|
||||
...patch,
|
||||
id: accountId,
|
||||
updatedAt: patch.updatedAt ?? new Date().toISOString(),
|
||||
};
|
||||
|
||||
await saveProviderAccount(nextAccount);
|
||||
if (apiKey !== undefined) {
|
||||
const trimmedKey = apiKey.trim();
|
||||
if (trimmedKey) {
|
||||
await storeApiKey(accountId, trimmedKey);
|
||||
} else {
|
||||
await deleteApiKey(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
return (await getProviderAccount(accountId)) ?? nextAccount;
|
||||
}
|
||||
|
||||
async upsertSyncedAccountMetadata(account: UserSyncProviderAccount): Promise<ProviderAccount> {
|
||||
await ensureProviderStoreMigrated();
|
||||
const existing = await getProviderAccount(account.id);
|
||||
const now = new Date().toISOString();
|
||||
const nextAccount: ProviderAccount = {
|
||||
id: account.id,
|
||||
vendorId: account.vendorId as ProviderAccount['vendorId'],
|
||||
label: account.label,
|
||||
authMode: account.authMode as ProviderAccount['authMode'],
|
||||
baseUrl: account.baseUrl,
|
||||
apiProtocol: account.apiProtocol as ProviderAccount['apiProtocol'] | undefined,
|
||||
headers: existing?.headers,
|
||||
model: account.model,
|
||||
fallbackModels: account.fallbackModels,
|
||||
fallbackAccountIds: account.fallbackAccountIds,
|
||||
enabled: account.enabled,
|
||||
isDefault: account.isDefault,
|
||||
metadata: normalizeSyncedMetadata(account.metadata ?? {}),
|
||||
createdAt: existing?.createdAt ?? now,
|
||||
updatedAt: account.updatedAt ?? now,
|
||||
};
|
||||
|
||||
await saveProviderAccount(nextAccount);
|
||||
if (nextAccount.isDefault) {
|
||||
await this.setDefaultAccount(nextAccount.id);
|
||||
}
|
||||
return (await getProviderAccount(nextAccount.id)) ?? nextAccount;
|
||||
}
|
||||
|
||||
async deleteAccount(accountId: string): Promise<boolean> {
|
||||
await ensureProviderStoreMigrated();
|
||||
return deleteProvider(accountId);
|
||||
}
|
||||
|
||||
/** Internal: list providers in the legacy ProviderConfig shape. */
|
||||
async _listProvidersFromAccountsInternal(): Promise<ProviderConfig[]> {
|
||||
const accounts = await this.listAccounts();
|
||||
return accounts.map(providerAccountToConfig);
|
||||
}
|
||||
|
||||
/** Internal: list providers with hasKey/keyMasked metadata. */
|
||||
async _listProvidersWithKeyInfoInternal(): Promise<ProviderWithKeyInfo[]> {
|
||||
const providers = await this._listProvidersFromAccountsInternal();
|
||||
const results: ProviderWithKeyInfo[] = [];
|
||||
for (const provider of providers) {
|
||||
const apiKey = await getApiKey(provider.id);
|
||||
results.push({
|
||||
...provider,
|
||||
hasKey: !!apiKey,
|
||||
keyMasked: maskApiKey(apiKey),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/** Internal: resolve a single provider in the legacy ProviderConfig shape. */
|
||||
async _getProviderInternal(providerId: string): Promise<ProviderConfig | null> {
|
||||
await ensureProviderStoreMigrated();
|
||||
const account = await getProviderAccount(providerId);
|
||||
return account ? providerAccountToConfig(account) : null;
|
||||
}
|
||||
|
||||
/** Internal: upsert a legacy provider config. */
|
||||
async _saveProviderInternal(config: ProviderConfig): Promise<void> {
|
||||
await ensureProviderStoreMigrated();
|
||||
const account = providerConfigToAccount(config);
|
||||
const existing = await getProviderAccount(config.id);
|
||||
if (existing) {
|
||||
await this.updateAccount(config.id, account);
|
||||
return;
|
||||
}
|
||||
await this.createAccount(account);
|
||||
}
|
||||
|
||||
/** Internal: delete a provider account by id. */
|
||||
async _deleteProviderInternal(providerId: string): Promise<boolean> {
|
||||
await ensureProviderStoreMigrated();
|
||||
await deleteProviderAccount(providerId);
|
||||
await deleteApiKey(providerId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Internal: set default account without warning. */
|
||||
async _setDefaultProviderInternal(providerId: string): Promise<void> {
|
||||
await this.setDefaultAccount(providerId);
|
||||
}
|
||||
|
||||
/** Internal: read default account id without warning. */
|
||||
async _getDefaultProviderInternal(): Promise<string | undefined> {
|
||||
return this.getDefaultAccountId();
|
||||
}
|
||||
|
||||
/** Internal: store an account's api key without warning. */
|
||||
async _setProviderApiKeyInternal(providerId: string, apiKey: string): Promise<boolean> {
|
||||
return storeApiKey(providerId, apiKey);
|
||||
}
|
||||
|
||||
/** Internal: read an account's api key without warning. */
|
||||
async _getProviderApiKeyInternal(providerId: string): Promise<string | null> {
|
||||
return getApiKey(providerId);
|
||||
}
|
||||
|
||||
/** Internal: delete an account's api key without warning. */
|
||||
async _deleteProviderApiKeyInternal(providerId: string): Promise<boolean> {
|
||||
return deleteApiKey(providerId);
|
||||
}
|
||||
|
||||
/** Internal: check if an account has a stored api key. */
|
||||
async _hasProviderApiKeyInternal(providerId: string): Promise<boolean> {
|
||||
return hasApiKey(providerId);
|
||||
}
|
||||
|
||||
/** Return per-account API key status for the account API surface. */
|
||||
async listAccountsKeyInfo(): Promise<Array<{ accountId: string; hasKey: boolean; keyMasked: string | null }>> {
|
||||
const accounts = await this.listAccounts();
|
||||
const results: Array<{ accountId: string; hasKey: boolean; keyMasked: string | null }> = [];
|
||||
for (const account of accounts) {
|
||||
const apiKey = await getApiKey(account.id);
|
||||
results.push({
|
||||
accountId: account.id,
|
||||
hasKey: !!apiKey,
|
||||
keyMasked: maskApiKey(apiKey),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/** Read an account's API key. */
|
||||
async getAccountApiKey(accountId: string): Promise<string | null> {
|
||||
return this._getProviderApiKeyInternal(accountId);
|
||||
}
|
||||
|
||||
/** Check whether an account has an API key stored. */
|
||||
async hasAccountApiKey(accountId: string): Promise<boolean> {
|
||||
return this._hasProviderApiKeyInternal(accountId);
|
||||
}
|
||||
|
||||
/** Delete only an account's stored API key while keeping its model configuration. */
|
||||
async deleteAccountApiKey(accountId: string): Promise<boolean> {
|
||||
await ensureProviderStoreMigrated();
|
||||
return deleteApiKey(accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use listAccounts() and map account data in callers.
|
||||
*/
|
||||
async listLegacyProviders(): Promise<ProviderConfig[]> {
|
||||
logLegacyProviderApiUsage('listLegacyProviders', 'listAccounts');
|
||||
return this._listProvidersFromAccountsInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use listAccountsKeyInfo() + the account snapshot API.
|
||||
*/
|
||||
async listLegacyProvidersWithKeyInfo(): Promise<ProviderWithKeyInfo[]> {
|
||||
logLegacyProviderApiUsage('listLegacyProvidersWithKeyInfo', 'listAccountsKeyInfo');
|
||||
return this._listProvidersWithKeyInfoInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAccount(accountId).
|
||||
*/
|
||||
async getLegacyProvider(providerId: string): Promise<ProviderConfig | null> {
|
||||
logLegacyProviderApiUsage('getLegacyProvider', 'getAccount');
|
||||
return this._getProviderInternal(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use createAccount()/updateAccount().
|
||||
*/
|
||||
async saveLegacyProvider(config: ProviderConfig): Promise<void> {
|
||||
logLegacyProviderApiUsage('saveLegacyProvider', 'createAccount/updateAccount');
|
||||
return this._saveProviderInternal(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use deleteAccount(accountId).
|
||||
*/
|
||||
async deleteLegacyProvider(providerId: string): Promise<boolean> {
|
||||
logLegacyProviderApiUsage('deleteLegacyProvider', 'deleteAccount');
|
||||
return this._deleteProviderInternal(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use setDefaultAccount(accountId).
|
||||
*/
|
||||
async setDefaultLegacyProvider(providerId: string): Promise<void> {
|
||||
logLegacyProviderApiUsage('setDefaultLegacyProvider', 'setDefaultAccount');
|
||||
return this._setDefaultProviderInternal(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getDefaultAccountId().
|
||||
*/
|
||||
async getDefaultLegacyProvider(): Promise<string | undefined> {
|
||||
logLegacyProviderApiUsage('getDefaultLegacyProvider', 'getDefaultAccountId');
|
||||
return this._getDefaultProviderInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use secret-store APIs by accountId.
|
||||
*/
|
||||
async setLegacyProviderApiKey(providerId: string, apiKey: string): Promise<boolean> {
|
||||
logLegacyProviderApiUsage('setLegacyProviderApiKey', 'setProviderSecret(accountId, api_key)');
|
||||
return this._setProviderApiKeyInternal(providerId, apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAccountApiKey(accountId).
|
||||
*/
|
||||
async getLegacyProviderApiKey(providerId: string): Promise<string | null> {
|
||||
logLegacyProviderApiUsage('getLegacyProviderApiKey', 'getAccountApiKey');
|
||||
return this._getProviderApiKeyInternal(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use secret-store APIs by accountId.
|
||||
*/
|
||||
async deleteLegacyProviderApiKey(providerId: string): Promise<boolean> {
|
||||
logLegacyProviderApiUsage('deleteLegacyProviderApiKey', 'deleteProviderSecret(accountId)');
|
||||
return this._deleteProviderApiKeyInternal(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use hasAccountApiKey(accountId).
|
||||
*/
|
||||
async hasLegacyProviderApiKey(providerId: string): Promise<boolean> {
|
||||
logLegacyProviderApiUsage('hasLegacyProviderApiKey', 'hasAccountApiKey');
|
||||
return this._hasProviderApiKeyInternal(providerId);
|
||||
}
|
||||
|
||||
async setDefaultAccount(accountId: string): Promise<void> {
|
||||
await ensureProviderStoreMigrated();
|
||||
await setDefaultProviderAccount(accountId);
|
||||
await setDefaultProvider(accountId);
|
||||
}
|
||||
|
||||
getVendorDefinition(vendorId: string): ProviderDefinition | undefined {
|
||||
return getProviderDefinition(vendorId);
|
||||
}
|
||||
}
|
||||
|
||||
const providerService = new ProviderService();
|
||||
|
||||
export function getProviderService(): ProviderService {
|
||||
return providerService;
|
||||
}
|
||||
Reference in New Issue
Block a user