Refine desktop setup and remove bundled app center apps
This commit is contained in:
@@ -6,8 +6,27 @@ import { getAllSettings, setSetting } from './store';
|
||||
import { getOpenClawConfigDir, reinstallManagedOpenClawRuntime } from './paths';
|
||||
import { logger } from './logger';
|
||||
import { ensureOfficeSkillRuntimeReady } from './office-skill-runtime';
|
||||
import {
|
||||
YINIAN_MODEL_ENTRY,
|
||||
YINIAN_MODEL_PROVIDER_KEY,
|
||||
} from '../../shared/yinian-model';
|
||||
|
||||
type JsonObject = Record<string, unknown>;
|
||||
type InternalModelAuthSeedResult =
|
||||
| { status: 'seeded'; path: string; config: ModelRuntimeConfig }
|
||||
| { status: 'skipped'; path?: string; reason: string; modelRef?: string };
|
||||
|
||||
interface ModelRuntimeConfig {
|
||||
providerKey: string;
|
||||
modelId: string;
|
||||
modelName: string;
|
||||
modelRef: string;
|
||||
baseUrl: string;
|
||||
api: string;
|
||||
authHeader?: boolean;
|
||||
fallbackModelRefs: string[];
|
||||
authProfileId: string;
|
||||
}
|
||||
|
||||
export type YinianInitializationStepStatus = 'pending' | 'running' | 'success' | 'error';
|
||||
|
||||
@@ -26,18 +45,18 @@ export interface YinianInitializationStatus {
|
||||
steps: YinianInitializationStep[];
|
||||
}
|
||||
|
||||
const INTERNAL_PROVIDER_KEY = 'minimax';
|
||||
const INTERNAL_MODEL_ID = 'MiniMax-M2.7';
|
||||
const INTERNAL_MODEL_REF = `${INTERNAL_PROVIDER_KEY}/${INTERNAL_MODEL_ID}`;
|
||||
const INTERNAL_AUTH_PROFILE_ID = 'minimax:default';
|
||||
const DESKTOP_TOOLS_PROFILE = 'coding';
|
||||
const YINIAN_FALLBACK_SKILL_IDS = ['docx', 'pdf', 'pptx', 'xlsx', 'design', 'image-search', 'web-search'];
|
||||
const REQUIRED_RUNTIME_FILES = [
|
||||
'package.json',
|
||||
'openclaw.mjs',
|
||||
join('docs', 'reference', 'templates', 'SOUL.md'),
|
||||
join('docs', 'reference', 'templates', 'IDENTITY.md'),
|
||||
join('docs', 'reference', 'templates', 'USER.md'),
|
||||
join('docs', 'reference', 'templates', 'AGENTS.md'),
|
||||
join('docs', 'reference', 'templates', 'TOOLS.md'),
|
||||
join('docs', 'reference', 'templates', 'HEARTBEAT.md'),
|
||||
join('docs', 'reference', 'templates', 'BOOT.md'),
|
||||
join('node_modules', 'openclaw', 'package.json'),
|
||||
] as const;
|
||||
let initializationInFlight: Promise<YinianInitializationStatus> | null = null;
|
||||
@@ -45,7 +64,7 @@ let initializationInFlight: Promise<YinianInitializationStatus> | null = null;
|
||||
const DEFAULT_STEPS: YinianInitializationStep[] = [
|
||||
{ id: 'runtime', label: '安装运行环境', status: 'pending' },
|
||||
{ id: 'workspace', label: '准备本地工作区', status: 'pending' },
|
||||
{ id: 'model', label: '写入内测模型配置', status: 'pending' },
|
||||
{ id: 'model', label: '准备模型 API 配置', status: 'pending' },
|
||||
{ id: 'python', label: '准备文档处理环境', status: 'pending' },
|
||||
];
|
||||
|
||||
@@ -54,8 +73,8 @@ export async function getYinianInitializationStatus(): Promise<YinianInitializat
|
||||
const runtimeStatus = getManagedRuntimeVerification();
|
||||
const workspaceReady = existsSync(join(getOpenClawConfigDir(), 'workspace'));
|
||||
const modelConfigReady = existsSync(join(getOpenClawConfigDir(), 'openclaw.json'));
|
||||
const modelAuthReady = await hasYinianModelAuthProfile();
|
||||
const modelReady = modelConfigReady && modelAuthReady;
|
||||
const currentModelRef = modelConfigReady ? await getConfiguredPrimaryModelRef() : undefined;
|
||||
const modelReady = modelConfigReady;
|
||||
const officeMarkerReady = existsSync(join(getOpenClawConfigDir(), 'runtime', 'yinian-initialized.json'));
|
||||
const initialized = settings.setupComplete === true
|
||||
&& runtimeStatus.ok
|
||||
@@ -69,7 +88,6 @@ export async function getYinianInitializationStatus(): Promise<YinianInitializat
|
||||
runtimeMissing: runtimeStatus.missing,
|
||||
workspaceReady,
|
||||
modelConfigReady,
|
||||
modelAuthReady,
|
||||
officeMarkerReady,
|
||||
});
|
||||
}
|
||||
@@ -78,7 +96,7 @@ export async function getYinianInitializationStatus(): Promise<YinianInitializat
|
||||
initialized,
|
||||
initializedAt: settings.openclawInitializedAt,
|
||||
openclawDir: runtimeStatus.runtimeDir,
|
||||
model: initialized ? INTERNAL_MODEL_REF : undefined,
|
||||
model: initialized ? currentModelRef : undefined,
|
||||
steps: DEFAULT_STEPS.map((step) => {
|
||||
if (step.id === 'runtime') {
|
||||
return {
|
||||
@@ -97,12 +115,10 @@ export async function getYinianInitializationStatus(): Promise<YinianInitializat
|
||||
if (step.id === 'model') {
|
||||
return {
|
||||
...step,
|
||||
status: modelReady ? 'success' : (settings.setupComplete === true && modelConfigReady ? 'error' : 'pending'),
|
||||
status: modelReady ? 'success' : 'pending',
|
||||
message: modelReady
|
||||
? INTERNAL_MODEL_REF
|
||||
: modelConfigReady && !modelAuthReady
|
||||
? '缺少内测模型调用凭据'
|
||||
: undefined,
|
||||
? currentModelRef || '模型 API 可在设置中配置'
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
if (step.id === 'python') {
|
||||
@@ -154,13 +170,12 @@ async function runYinianRuntimeInitialization(): Promise<YinianInitializationSta
|
||||
await ensureWorkspaceFiles();
|
||||
setStep('workspace', 'success', '本地工作区已准备');
|
||||
|
||||
setStep('model', 'running', '正在写入内测模型配置');
|
||||
await seedInternalModelConfig();
|
||||
await seedInternalModelAuthProfiles();
|
||||
if (!(await hasYinianModelAuthProfile())) {
|
||||
throw new Error('内测模型调用凭据未配置,请使用内测包或联系管理员配置模型服务');
|
||||
}
|
||||
setStep('model', 'success', INTERNAL_MODEL_REF);
|
||||
setStep('model', 'running', '正在准备模型 API 配置');
|
||||
const authSeedResult = await seedModelApiConfiguration();
|
||||
const modelRef = authSeedResult.status === 'seeded'
|
||||
? authSeedResult.config.modelRef
|
||||
: authSeedResult.modelRef;
|
||||
setStep('model', 'success', modelRef || '模型 API 可在设置中配置');
|
||||
|
||||
setStep('python', 'running', '正在准备文档与办公能力环境');
|
||||
const officeRuntime = await ensureAuxiliaryRuntimeMarkers();
|
||||
@@ -179,14 +194,15 @@ async function runYinianRuntimeInitialization(): Promise<YinianInitializationSta
|
||||
|
||||
logger.info('[yinian-init] First-run initialization completed', {
|
||||
openclawDir: runtime.dir,
|
||||
model: INTERNAL_MODEL_REF,
|
||||
model: modelRef,
|
||||
modelSeedStatus: authSeedResult.status,
|
||||
});
|
||||
|
||||
return {
|
||||
initialized: true,
|
||||
initializedAt,
|
||||
openclawDir: runtime.dir,
|
||||
model: INTERNAL_MODEL_REF,
|
||||
model: modelRef,
|
||||
steps,
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -223,7 +239,88 @@ async function ensureWorkspaceFiles(): Promise<void> {
|
||||
await mkdir(join(openclawDir, 'agents', 'main', 'agent'), { recursive: true });
|
||||
}
|
||||
|
||||
async function seedInternalModelConfig(): Promise<void> {
|
||||
async function seedModelApiConfiguration(): Promise<InternalModelAuthSeedResult> {
|
||||
const bundledAuthPath = resolveBundledModelAuthPath();
|
||||
if (!bundledAuthPath) {
|
||||
logger.warn('[yinian-init] Model API auth bundle was not found');
|
||||
const modelRef = await ensureBaseOpenClawConfig();
|
||||
return {
|
||||
status: 'skipped',
|
||||
reason: '安装包缺少模型 API 凭据资源',
|
||||
modelRef,
|
||||
};
|
||||
}
|
||||
|
||||
const bundled = await readJsonFile(bundledAuthPath);
|
||||
if (bundled.bundled !== true) {
|
||||
const reason = typeof bundled.reason === 'string' && bundled.reason.trim()
|
||||
? bundled.reason.trim()
|
||||
: '构建时未启用模型 API 凭据打包';
|
||||
logger.warn('[yinian-init] Model API auth bundle is disabled', {
|
||||
bundledAuthPath,
|
||||
reason,
|
||||
});
|
||||
const modelRef = await ensureBaseOpenClawConfig();
|
||||
return {
|
||||
status: 'skipped',
|
||||
path: bundledAuthPath,
|
||||
reason,
|
||||
modelRef,
|
||||
};
|
||||
}
|
||||
|
||||
let runtimeConfig: ModelRuntimeConfig;
|
||||
try {
|
||||
runtimeConfig = resolveModelRuntimeConfig(bundled);
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error);
|
||||
logger.warn('[yinian-init] Model API runtime config bundle is incomplete; skipping bundled model seed', {
|
||||
bundledAuthPath,
|
||||
reason,
|
||||
});
|
||||
const modelRef = await ensureBaseOpenClawConfig();
|
||||
return {
|
||||
status: 'skipped',
|
||||
path: bundledAuthPath,
|
||||
reason,
|
||||
modelRef,
|
||||
};
|
||||
}
|
||||
|
||||
const hasBundledAuth = hasUsableBundledModelAuthProfile(bundled, runtimeConfig);
|
||||
const hasExistingAuth = await hasYinianModelAuthProfile(runtimeConfig.providerKey);
|
||||
if (!hasBundledAuth && !hasExistingAuth) {
|
||||
logger.warn('[yinian-init] Model API auth bundle is incomplete; skipping bundled model seed', { bundledAuthPath });
|
||||
const modelRef = await ensureBaseOpenClawConfig();
|
||||
return {
|
||||
status: 'skipped',
|
||||
path: bundledAuthPath,
|
||||
reason: '模型 API 凭据资源不完整',
|
||||
modelRef,
|
||||
};
|
||||
}
|
||||
|
||||
await seedModelApiConfig(runtimeConfig);
|
||||
if (hasBundledAuth) {
|
||||
await seedModelApiAuthProfiles(bundledAuthPath, bundled, runtimeConfig);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'seeded',
|
||||
path: bundledAuthPath,
|
||||
config: runtimeConfig,
|
||||
};
|
||||
}
|
||||
|
||||
async function seedModelApiConfig(runtimeConfig: ModelRuntimeConfig): Promise<void> {
|
||||
await writeBaseOpenClawConfig(runtimeConfig);
|
||||
}
|
||||
|
||||
async function ensureBaseOpenClawConfig(): Promise<string | undefined> {
|
||||
return writeBaseOpenClawConfig();
|
||||
}
|
||||
|
||||
async function writeBaseOpenClawConfig(runtimeConfig?: ModelRuntimeConfig): Promise<string | undefined> {
|
||||
const configDir = getOpenClawConfigDir();
|
||||
const configPath = join(configDir, 'openclaw.json');
|
||||
await mkdir(configDir, { recursive: true });
|
||||
@@ -231,21 +328,20 @@ async function seedInternalModelConfig(): Promise<void> {
|
||||
const config = await readJsonFile(configPath);
|
||||
const models = asObject(config.models);
|
||||
const providers = asObject(models.providers);
|
||||
providers[INTERNAL_PROVIDER_KEY] = {
|
||||
baseUrl: 'https://api.minimaxi.com/anthropic',
|
||||
api: 'anthropic-messages',
|
||||
authHeader: true,
|
||||
models: [
|
||||
{
|
||||
id: INTERNAL_MODEL_ID,
|
||||
name: 'MiniMax M2.7',
|
||||
reasoning: true,
|
||||
input: ['text', 'image'],
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
},
|
||||
],
|
||||
};
|
||||
if (runtimeConfig) {
|
||||
providers[runtimeConfig.providerKey] = {
|
||||
baseUrl: runtimeConfig.baseUrl,
|
||||
api: runtimeConfig.api,
|
||||
...(typeof runtimeConfig.authHeader === 'boolean' ? { authHeader: runtimeConfig.authHeader } : {}),
|
||||
models: [
|
||||
{
|
||||
...YINIAN_MODEL_ENTRY,
|
||||
id: runtimeConfig.modelId,
|
||||
name: runtimeConfig.modelName,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
models.mode = 'merge';
|
||||
models.providers = providers;
|
||||
delete models.pricing;
|
||||
@@ -261,10 +357,12 @@ async function seedInternalModelConfig(): Promise<void> {
|
||||
const agents = asObject(config.agents);
|
||||
const defaults = asObject(agents.defaults);
|
||||
const enabledSkillIds = resolveYinianEnabledSkillIds(config);
|
||||
defaults.model = {
|
||||
primary: INTERNAL_MODEL_REF,
|
||||
fallbacks: ['minimax/MiniMax-M2.5'],
|
||||
};
|
||||
if (runtimeConfig) {
|
||||
defaults.model = {
|
||||
primary: runtimeConfig.modelRef,
|
||||
fallbacks: [...runtimeConfig.fallbackModelRefs],
|
||||
};
|
||||
}
|
||||
defaults.workspace = join(homedir(), '.openclaw', 'workspace');
|
||||
defaults.skills = enabledSkillIds;
|
||||
defaults.heartbeat = {
|
||||
@@ -304,20 +402,23 @@ async function seedInternalModelConfig(): Promise<void> {
|
||||
config.agents = agents;
|
||||
|
||||
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
||||
return getNonEmptyString(asObject(defaults.model).primary);
|
||||
}
|
||||
|
||||
async function seedInternalModelAuthProfiles(): Promise<void> {
|
||||
const bundledAuthPath = resolveBundledModelAuthPath();
|
||||
if (!bundledAuthPath) return;
|
||||
|
||||
const bundled = await readJsonFile(bundledAuthPath);
|
||||
if (bundled.bundled !== true) return;
|
||||
|
||||
async function seedModelApiAuthProfiles(
|
||||
bundledAuthPath: string,
|
||||
bundled: JsonObject,
|
||||
runtimeConfig: ModelRuntimeConfig,
|
||||
): Promise<void> {
|
||||
const bundledStore = asObject(bundled.store);
|
||||
const bundledProfiles = asObject(bundledStore.profiles);
|
||||
const bundledDefault = asObject(bundledProfiles[INTERNAL_AUTH_PROFILE_ID]);
|
||||
if (!isUsableMinimaxAuthProfile(bundledDefault)) {
|
||||
throw new Error('内测模型凭据资源不完整');
|
||||
const bundledDefault = asObject(bundledProfiles[runtimeConfig.authProfileId]);
|
||||
const fallbackBundledDefault = Object.values(bundledProfiles)
|
||||
.map(asObject)
|
||||
.find((profile) => isUsableModelApiKeyProfile(profile));
|
||||
if (!isUsableModelApiKeyProfile(bundledDefault) && !fallbackBundledDefault) {
|
||||
logger.warn('[yinian-init] Model API auth bundle is incomplete', { bundledAuthPath });
|
||||
throw new Error('模型 API 凭据资源不完整');
|
||||
}
|
||||
|
||||
const authProfilesPath = join(getOpenClawConfigDir(), 'agents', 'main', 'agent', 'auth-profiles.json');
|
||||
@@ -329,11 +430,16 @@ async function seedInternalModelAuthProfiles(): Promise<void> {
|
||||
|
||||
for (const [profileId, profile] of Object.entries(bundledProfiles)) {
|
||||
const bundledProfile = asObject(profile);
|
||||
if (!isUsableMinimaxAuthProfile(bundledProfile)) continue;
|
||||
if (!isUsableMinimaxAuthProfile(asObject(profiles[profileId]))) {
|
||||
profiles[profileId] = {
|
||||
if (!isUsableModelApiKeyProfile(bundledProfile)) continue;
|
||||
const targetProfileId = profileId === runtimeConfig.authProfileId
|
||||
? runtimeConfig.authProfileId
|
||||
: profileId.startsWith(`${runtimeConfig.providerKey}:`)
|
||||
? profileId
|
||||
: runtimeConfig.authProfileId;
|
||||
if (!isUsableModelApiKeyProfile(asObject(profiles[targetProfileId]), runtimeConfig.providerKey)) {
|
||||
profiles[targetProfileId] = {
|
||||
type: 'api_key',
|
||||
provider: INTERNAL_PROVIDER_KEY,
|
||||
provider: runtimeConfig.providerKey,
|
||||
key: bundledProfile.key,
|
||||
};
|
||||
changed = true;
|
||||
@@ -341,24 +447,26 @@ async function seedInternalModelAuthProfiles(): Promise<void> {
|
||||
}
|
||||
|
||||
const order = asObject(current.order) as Record<string, unknown>;
|
||||
const minimaxOrder = Array.isArray(order[INTERNAL_PROVIDER_KEY])
|
||||
? (order[INTERNAL_PROVIDER_KEY] as unknown[]).filter((value): value is string => typeof value === 'string')
|
||||
const currentOrder = Array.isArray(order[runtimeConfig.providerKey])
|
||||
? (order[runtimeConfig.providerKey] as unknown[]).filter((value): value is string => typeof value === 'string')
|
||||
: [];
|
||||
if (!minimaxOrder.includes(INTERNAL_AUTH_PROFILE_ID)) {
|
||||
order[INTERNAL_PROVIDER_KEY] = [
|
||||
INTERNAL_AUTH_PROFILE_ID,
|
||||
...minimaxOrder.filter((profileId) => profileId !== INTERNAL_AUTH_PROFILE_ID),
|
||||
if (!currentOrder.includes(runtimeConfig.authProfileId)) {
|
||||
order[runtimeConfig.providerKey] = [
|
||||
runtimeConfig.authProfileId,
|
||||
...currentOrder.filter((profileId) => profileId !== runtimeConfig.authProfileId),
|
||||
];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const lastGood = asObject(current.lastGood) as Record<string, unknown>;
|
||||
if (lastGood[INTERNAL_PROVIDER_KEY] !== INTERNAL_AUTH_PROFILE_ID) {
|
||||
lastGood[INTERNAL_PROVIDER_KEY] = INTERNAL_AUTH_PROFILE_ID;
|
||||
if (lastGood[runtimeConfig.providerKey] !== runtimeConfig.authProfileId) {
|
||||
lastGood[runtimeConfig.providerKey] = runtimeConfig.authProfileId;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) return;
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
current.version = typeof current.version === 'number' ? current.version : 1;
|
||||
current.profiles = profiles;
|
||||
current.order = order;
|
||||
@@ -377,16 +485,89 @@ function resolveBundledModelAuthPath(): string | undefined {
|
||||
return candidates.find((candidate) => existsSync(candidate));
|
||||
}
|
||||
|
||||
async function hasYinianModelAuthProfile(): Promise<boolean> {
|
||||
function hasUsableBundledModelAuthProfile(bundle: JsonObject, runtimeConfig: ModelRuntimeConfig): boolean {
|
||||
const bundledStore = asObject(bundle.store);
|
||||
const bundledProfiles = asObject(bundledStore.profiles);
|
||||
const bundledDefault = asObject(bundledProfiles[runtimeConfig.authProfileId]);
|
||||
if (isUsableModelApiKeyProfile(bundledDefault)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.values(bundledProfiles)
|
||||
.map(asObject)
|
||||
.some((profile) => isUsableModelApiKeyProfile(profile));
|
||||
}
|
||||
|
||||
function resolveModelRuntimeConfig(bundle: JsonObject): ModelRuntimeConfig {
|
||||
const model = asObject(bundle.model);
|
||||
const providerKey = getNonEmptyString(model.providerKey);
|
||||
const modelId = getNonEmptyString(model.modelId);
|
||||
const baseUrl = getNonEmptyString(model.baseUrl);
|
||||
const api = getNonEmptyString(model.api);
|
||||
const missingFields = [
|
||||
!providerKey ? 'model.providerKey' : '',
|
||||
!modelId ? 'model.modelId' : '',
|
||||
!baseUrl ? 'model.baseUrl' : '',
|
||||
!api ? 'model.api' : '',
|
||||
].filter(Boolean);
|
||||
if (missingFields.length > 0) {
|
||||
throw new Error(`模型 API 配置资源不完整:缺少 ${missingFields.join(', ')}`);
|
||||
}
|
||||
const modelName = getNonEmptyString(model.modelName) || getNonEmptyString(model.name) || modelId;
|
||||
const authProfileId = getNonEmptyString(model.authProfileId) || `${providerKey}:default`;
|
||||
const fallbackModelRefs = readModelRefList(model.fallbackModelRefs)
|
||||
.concat(readModelRefList(model.fallbacks))
|
||||
.filter((value, index, list) => list.indexOf(value) === index);
|
||||
|
||||
return {
|
||||
providerKey,
|
||||
modelId,
|
||||
modelName,
|
||||
modelRef: `${providerKey}/${modelId}`,
|
||||
baseUrl,
|
||||
api,
|
||||
authHeader: typeof model.authHeader === 'boolean' ? model.authHeader : undefined,
|
||||
fallbackModelRefs,
|
||||
authProfileId,
|
||||
};
|
||||
}
|
||||
|
||||
function getNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function readModelRefList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value
|
||||
.map((item) => typeof item === 'string' ? item.trim() : '')
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
async function getConfiguredPrimaryModelRef(): Promise<string | undefined> {
|
||||
const configPath = join(getOpenClawConfigDir(), 'openclaw.json');
|
||||
const config = await readJsonFile(configPath);
|
||||
const agents = asObject(config.agents);
|
||||
const defaults = asObject(agents.defaults);
|
||||
const model = asObject(defaults.model);
|
||||
return getNonEmptyString(model.primary);
|
||||
}
|
||||
|
||||
function splitProviderKey(modelRef: string): string | undefined {
|
||||
const separatorIndex = modelRef.indexOf('/');
|
||||
if (separatorIndex <= 0) return undefined;
|
||||
return modelRef.slice(0, separatorIndex);
|
||||
}
|
||||
|
||||
async function hasYinianModelAuthProfile(providerKey = YINIAN_MODEL_PROVIDER_KEY): Promise<boolean> {
|
||||
const authProfilesPath = join(getOpenClawConfigDir(), 'agents', 'main', 'agent', 'auth-profiles.json');
|
||||
const store = await readJsonFile(authProfilesPath);
|
||||
const profiles = asObject(store.profiles);
|
||||
return Object.values(profiles).some((profile) => isUsableMinimaxAuthProfile(asObject(profile)));
|
||||
return Object.values(profiles).some((profile) => isUsableModelApiKeyProfile(asObject(profile), providerKey));
|
||||
}
|
||||
|
||||
function isUsableMinimaxAuthProfile(profile: JsonObject): boolean {
|
||||
function isUsableModelApiKeyProfile(profile: JsonObject, providerKey?: string): boolean {
|
||||
return profile.type === 'api_key'
|
||||
&& profile.provider === INTERNAL_PROVIDER_KEY
|
||||
&& (!providerKey || profile.provider === providerKey)
|
||||
&& typeof profile.key === 'string'
|
||||
&& profile.key.trim().length >= 8;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user