Refine desktop setup and remove bundled app center apps
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { GatewayManager } from '../../gateway/manager';
|
||||
import { getProviderAccount, listProviderAccounts } from './provider-store';
|
||||
import { getProviderAccount, listProviderAccounts, providerAccountToConfig } from './provider-store';
|
||||
import { getProviderSecret } from '../secrets/secret-store';
|
||||
import type { ProviderConfig } from '../../utils/secure-storage';
|
||||
import { getAllProviders, getApiKey, getDefaultProvider, getProvider } from '../../utils/secure-storage';
|
||||
@@ -144,6 +144,16 @@ export function getProviderModelRef(config: ProviderConfig): string | undefined
|
||||
: `${providerKey}/${defaultModel}`;
|
||||
}
|
||||
|
||||
async function getProviderConfigForRuntime(providerId: string): Promise<ProviderConfig | null> {
|
||||
const legacyProvider = await getProvider(providerId);
|
||||
if (legacyProvider) {
|
||||
return legacyProvider;
|
||||
}
|
||||
|
||||
const account = await getProviderAccount(providerId);
|
||||
return account ? providerAccountToConfig(account) : null;
|
||||
}
|
||||
|
||||
export async function getProviderFallbackModelRefs(config: ProviderConfig): Promise<string[]> {
|
||||
const allProviders = await getAllProviders();
|
||||
const providerMap = new Map(allProviders.map((provider) => [provider.id, provider]));
|
||||
@@ -495,7 +505,8 @@ export async function syncSavedProviderToRuntime(
|
||||
|
||||
scheduleGatewayRefresh(
|
||||
gatewayManager,
|
||||
`Scheduling Gateway reload after saving provider "${context.runtimeProviderKey}" config`,
|
||||
`Scheduling Gateway restart after saving provider "${context.runtimeProviderKey}" config`,
|
||||
{ mode: 'restart' },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -543,7 +554,8 @@ export async function syncUpdatedProviderToRuntime(
|
||||
|
||||
scheduleGatewayRefresh(
|
||||
gatewayManager,
|
||||
`Scheduling Gateway reload after updating provider "${ock}" config`,
|
||||
`Scheduling Gateway restart after updating provider "${ock}" config`,
|
||||
{ mode: 'restart' },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -584,7 +596,7 @@ export async function syncDefaultProviderToRuntime(
|
||||
providerId: string,
|
||||
gatewayManager?: GatewayManager,
|
||||
): Promise<void> {
|
||||
const provider = await getProvider(providerId);
|
||||
const provider = await getProviderConfigForRuntime(providerId);
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
@@ -656,7 +668,8 @@ export async function syncDefaultProviderToRuntime(
|
||||
}
|
||||
scheduleGatewayRefresh(
|
||||
gatewayManager,
|
||||
`Scheduling Gateway reload after provider switch to "${browserOAuthRuntimeProvider}"`,
|
||||
`Scheduling Gateway restart after provider switch to "${browserOAuthRuntimeProvider}"`,
|
||||
{ mode: 'restart' },
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -718,7 +731,7 @@ export async function syncDefaultProviderToRuntime(
|
||||
|
||||
scheduleGatewayRefresh(
|
||||
gatewayManager,
|
||||
`Scheduling Gateway reload after provider switch to "${ock}"`,
|
||||
{ onlyIfRunning: true },
|
||||
`Scheduling Gateway restart after provider switch to "${ock}"`,
|
||||
{ onlyIfRunning: true, mode: 'restart' },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ import { getActiveOpenClawProviders, getOpenClawProvidersConfig } from '../../ut
|
||||
import { getAliasSourceTypes, getOpenClawProviderKeyForType } from '../../utils/provider-keys';
|
||||
import type { ProviderWithKeyInfo } from '../../shared/providers/types';
|
||||
import { logger } from '../../utils/logger';
|
||||
import {
|
||||
YINIAN_MODEL_DEFAULT_BASE_URL,
|
||||
YINIAN_MODEL_DEFAULT_ID,
|
||||
YINIAN_MODEL_PROVIDER_KEY,
|
||||
YINIAN_MODEL_REF,
|
||||
} from '../../../shared/yinian-model';
|
||||
|
||||
function maskApiKey(apiKey: string | null): string | null {
|
||||
if (!apiKey) return null;
|
||||
@@ -67,6 +73,35 @@ function inferProviderVendorIdFromOpenClawEntry(
|
||||
return ((BUILTIN_PROVIDER_TYPES as readonly string[]).includes(key) ? key : 'custom') as ProviderType | 'custom';
|
||||
}
|
||||
|
||||
function isPlaceholderYinianModelAccount(account: ProviderAccount): boolean {
|
||||
const model = account.model?.startsWith(`${YINIAN_MODEL_PROVIDER_KEY}/`)
|
||||
? account.model
|
||||
: `${YINIAN_MODEL_PROVIDER_KEY}/${account.model ?? ''}`;
|
||||
|
||||
return account.id === YINIAN_MODEL_PROVIDER_KEY
|
||||
&& account.vendorId === 'custom'
|
||||
&& account.baseUrl === YINIAN_MODEL_DEFAULT_BASE_URL
|
||||
&& model === YINIAN_MODEL_REF;
|
||||
}
|
||||
|
||||
function isPlaceholderYinianModelEntry(
|
||||
key: string,
|
||||
entry: Record<string, unknown>,
|
||||
defaultModel: string | undefined,
|
||||
): boolean {
|
||||
if (key !== YINIAN_MODEL_PROVIDER_KEY) return false;
|
||||
if (!entry.baseUrl) return true;
|
||||
if (entry.baseUrl !== YINIAN_MODEL_DEFAULT_BASE_URL) return false;
|
||||
|
||||
const models = Array.isArray(entry.models) ? entry.models : [];
|
||||
const hasPlaceholderModel = models.some((model) => {
|
||||
if (!model || typeof model !== 'object') return false;
|
||||
return (model as Record<string, unknown>).id === YINIAN_MODEL_DEFAULT_ID;
|
||||
});
|
||||
|
||||
return defaultModel === YINIAN_MODEL_REF || hasPlaceholderModel;
|
||||
}
|
||||
|
||||
export class ProviderService {
|
||||
async listVendors(): Promise<ProviderDefinition[]> {
|
||||
return PROVIDER_DEFINITIONS;
|
||||
@@ -75,21 +110,16 @@ export class ProviderService {
|
||||
async listAccounts(): Promise<ProviderAccount[]> {
|
||||
await ensureProviderStoreMigrated();
|
||||
|
||||
// ── openclaw.json is the ONLY source of truth ──
|
||||
// The provider list is derived entirely from openclaw.json.
|
||||
// The electron-store is only used as a metadata cache (label, authMode, etc.).
|
||||
// Provider accounts are the settings source of truth. openclaw.json is the
|
||||
// generated runtime output, but we still import from it for older installs.
|
||||
|
||||
const { providers: openClawProviders, defaultModel } = await getOpenClawProvidersConfig();
|
||||
const activeProviders = await getActiveOpenClawProviders();
|
||||
|
||||
if (activeProviders.size === 0) {
|
||||
return [];
|
||||
}
|
||||
const rawStoreAccounts = await listProviderAccounts();
|
||||
const allStoreAccounts = rawStoreAccounts
|
||||
.filter((account) => !isPlaceholderYinianModelAccount(account));
|
||||
|
||||
// Read store accounts as a lookup cache (NOT as the source of what to display).
|
||||
const allStoreAccounts = await listProviderAccounts();
|
||||
|
||||
// Index store accounts by their openclaw runtime key for fast lookup.
|
||||
const storeByKey = new Map<string, ProviderAccount[]>();
|
||||
for (const account of allStoreAccounts) {
|
||||
const ock = getOpenClawProviderKeyForType(account.vendorId, account.id);
|
||||
@@ -101,47 +131,59 @@ export class ProviderService {
|
||||
const result: ProviderAccount[] = [];
|
||||
const processedKeys = new Set<string>();
|
||||
|
||||
// For each active provider in openclaw.json, produce exactly ONE account.
|
||||
for (const key of activeProviders) {
|
||||
const storeKeys = Array.from(storeByKey.keys());
|
||||
for (const key of storeKeys) {
|
||||
if (processedKeys.has(key)) continue;
|
||||
processedKeys.add(key);
|
||||
|
||||
const storeGroup = storeByKey.get(key) ?? [];
|
||||
const aliasAccounts = storeGroup.filter((a) => a.vendorId !== key);
|
||||
const candidates = aliasAccounts.length > 0 ? aliasAccounts : storeGroup;
|
||||
candidates.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
||||
result.push(candidates[0]);
|
||||
|
||||
if (storeGroup.length > 0) {
|
||||
// Pick the best store account for this key:
|
||||
// 1. Prefer alias variants (e.g. minimax-portal-cn over minimax-portal)
|
||||
// 2. Among equal variants, prefer the most recently updated
|
||||
const aliasAccounts = storeGroup.filter((a) => a.vendorId !== key);
|
||||
const candidates = aliasAccounts.length > 0 ? aliasAccounts : storeGroup;
|
||||
candidates.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
||||
result.push(candidates[0]);
|
||||
|
||||
// Clean up orphaned duplicates from the store.
|
||||
const kept = candidates[0];
|
||||
for (const account of storeGroup) {
|
||||
if (account.id !== kept.id) {
|
||||
logger.info(
|
||||
`[provider-sync] Removing orphaned account "${account.id}" for key "${key}" (keeping "${kept.id}")`,
|
||||
);
|
||||
await deleteProviderAccount(account.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No store account for this key — create a seed from openclaw.json.
|
||||
const entry = openClawProviders[key];
|
||||
if (entry) {
|
||||
const seeded = ProviderService.buildAccountsFromOpenClawEntries(
|
||||
{ [key]: entry },
|
||||
new Set(),
|
||||
new Set(),
|
||||
defaultModel,
|
||||
const kept = candidates[0];
|
||||
for (const account of storeGroup) {
|
||||
if (account.id !== kept.id) {
|
||||
logger.info(
|
||||
`[provider-sync] Removing orphaned account "${account.id}" for key "${key}" (keeping "${kept.id}")`,
|
||||
);
|
||||
for (const account of seeded) {
|
||||
await saveProviderAccount(account);
|
||||
result.push(account);
|
||||
logger.info(`[provider-sync] Seeded provider account "${account.id}" from openclaw.json`);
|
||||
}
|
||||
await deleteProviderAccount(account.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const existingIds = new Set(result.map((account) => account.id));
|
||||
const existingVendorIds = new Set(result.map((account) => account.vendorId));
|
||||
|
||||
for (const key of activeProviders) {
|
||||
if (processedKeys.has(key)) continue;
|
||||
const entry = openClawProviders[key];
|
||||
if (!entry || isPlaceholderYinianModelEntry(key, entry, defaultModel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const seeded = ProviderService.buildAccountsFromOpenClawEntries(
|
||||
{ [key]: entry },
|
||||
existingIds,
|
||||
existingVendorIds,
|
||||
defaultModel,
|
||||
);
|
||||
for (const account of seeded) {
|
||||
await saveProviderAccount(account);
|
||||
result.push(account);
|
||||
existingIds.add(account.id);
|
||||
existingVendorIds.add(account.vendorId);
|
||||
logger.info(`[provider-sync] Seeded provider account "${account.id}" from openclaw.json`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const account of rawStoreAccounts) {
|
||||
if (isPlaceholderYinianModelAccount(account)) {
|
||||
try {
|
||||
await deleteProviderAccount(account.id);
|
||||
} catch (err) {
|
||||
logger.warn(`[provider-sync] Failed to remove placeholder model account "${account.id}":`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user