import type { BaseModelCatalogItem } from '@easyai-ai-gateway/contracts'; import type { PlatformCreateInput, PlatformModelBindingInput } from '../../types'; export const authTypes = [ { value: 'APIKey', label: 'API Key', description: 'apiKey 字段,兼容 OpenAI / Gemini' }, { value: 'Token', label: 'Token', description: 'token 字段,适合 Bearer Token 类平台' }, { value: 'AccessKey-SecretKey', label: 'AccessKey + SecretKey', description: 'accessKey / secretKey 双字段' }, { value: 'none', label: '无授权', description: '仅用于内网或测试模式' }, ]; export interface PlatformWizardForm { provider: string; platformKey: string; name: string; internalName: string; baseUrl: string; authType: string; credentialsPreview: Record; apiKey: string; token: string; accessKey: string; secretKey: string; pricingRuleSetId: string; defaultDiscountFactor: string; priority: string; retryEnabled: boolean; retryMaxAttempts: string; rpmLimit: string; rpsLimit: string; tpmLimit: string; concurrencyLimit: string; testMode: boolean; supportBase64Input: boolean; supportUrlInput: boolean; modelDiscountFactor: string; modelDiscountFactors: Record; modelNameMappings: Record; modelOverrideRetry: boolean; modelRetryEnabled: boolean; modelRetryMaxAttempts: string; modelOverrideRateLimit: boolean; modelRpmLimit: string; modelRpsLimit: string; modelTpmLimit: string; modelConcurrencyLimit: string; selectionMode: 'all' | 'partial'; selectedModelIds: string[]; } export interface ProviderConnectionDefaults { defaultAuthType?: string; defaultBaseUrl?: string; } export function createEmptyPlatformForm(provider = '', defaults?: ProviderConnectionDefaults): PlatformWizardForm { return { provider, platformKey: '', name: '', internalName: '', baseUrl: defaults?.defaultBaseUrl ?? '', authType: defaults?.defaultAuthType ?? 'APIKey', credentialsPreview: {}, apiKey: '', token: '', accessKey: '', secretKey: '', pricingRuleSetId: '', defaultDiscountFactor: '1', priority: '100', retryEnabled: true, retryMaxAttempts: '2', rpmLimit: '', rpsLimit: '', tpmLimit: '', concurrencyLimit: '', testMode: false, supportBase64Input: true, supportUrlInput: true, modelDiscountFactor: '', modelDiscountFactors: {}, modelNameMappings: {}, modelOverrideRetry: false, modelRetryEnabled: true, modelRetryMaxAttempts: '2', modelOverrideRateLimit: false, modelRpmLimit: '', modelRpsLimit: '', modelTpmLimit: '', modelConcurrencyLimit: '', selectionMode: 'partial', selectedModelIds: [], }; } export function applyProviderDefaults(form: PlatformWizardForm, provider: string, defaults?: ProviderConnectionDefaults): PlatformWizardForm { return { ...form, provider, baseUrl: defaults?.defaultBaseUrl ?? '', authType: defaults?.defaultAuthType ?? 'APIKey', selectedModelIds: [], modelDiscountFactors: {}, modelNameMappings: {}, }; } export function modelsForProvider(models: BaseModelCatalogItem[], provider: string) { return models.filter((item) => item.providerKey === provider && item.status !== 'hidden'); } export function selectedModelsForForm(models: BaseModelCatalogItem[], form: PlatformWizardForm) { const visibleModels = models.filter((item) => item.status !== 'hidden'); if (form.selectionMode === 'all') { return visibleModels; } const selectedIds = new Set(form.selectedModelIds); return visibleModels.filter((item) => selectedIds.has(item.id)); } export function platformPayload(form: PlatformWizardForm, options: { preserveEmptyCredentials?: boolean } = {}): PlatformCreateInput { const credentials = credentialsPayload(form, options); return { provider: form.provider, platformKey: optionalString(form.platformKey), name: form.name.trim(), internalName: optionalString(form.internalName), baseUrl: form.baseUrl.trim(), authType: form.authType, credentials, config: { testMode: form.testMode, supportBase64Input: form.supportBase64Input, supportUrlInput: form.supportUrlInput, source: 'gateway-admin', }, retryPolicy: { enabled: form.retryEnabled, maxAttempts: form.retryEnabled ? positiveInt(form.retryMaxAttempts, 2) : 1, }, rateLimitPolicy: rateLimitPolicyPayload(form), defaultPricingMode: 'inherit_discount', defaultDiscountFactor: positiveNumber(form.defaultDiscountFactor, 1), pricingRuleSetId: optionalString(form.pricingRuleSetId), priority: positiveInt(form.priority, 100), }; } export function platformModelPayloads(models: BaseModelCatalogItem[], form: PlatformWizardForm): PlatformModelBindingInput[] { return selectedModelsForForm(models, form).map((model) => ({ baseModelId: model.id, canonicalModelKey: model.canonicalModelKey, modelName: model.providerModelName, providerModelName: optionalString(form.modelNameMappings[model.id]) ?? model.providerModelName, modelAlias: stableModelAlias(model), modelType: baseModelTypes(model), displayName: stableModelAlias(model) || model.providerModelName, pricingMode: 'inherit_discount', discountFactor: optionalPositiveNumber(form.modelDiscountFactors[model.id]) ?? optionalPositiveNumber(form.modelDiscountFactor), retryPolicy: form.modelOverrideRetry ? { enabled: form.modelRetryEnabled, maxAttempts: form.modelRetryEnabled ? positiveInt(form.modelRetryMaxAttempts, 2) : 1 } : undefined, rateLimitPolicy: form.modelOverrideRateLimit ? rateLimitPolicyPayload({ rpmLimit: form.modelRpmLimit, rpsLimit: form.modelRpsLimit, tpmLimit: form.modelTpmLimit, concurrencyLimit: form.modelConcurrencyLimit, }) : undefined, runtimePolicyOverride: platformModelRuntimeOverride(form), })); } export function stableModelAlias(model: BaseModelCatalogItem) { return stripProviderPrefix( readString(model.modelAlias) || readString(model.displayName) || readString(model.metadata?.alias) || readString(readRecord(model.metadata?.rawModel)?.alias) || model.providerModelName || model.canonicalModelKey, model.providerKey, model.canonicalModelKey, ); } export function baseModelTypes(model: BaseModelCatalogItem) { if (Array.isArray(model.modelType)) return model.modelType.map(String).filter(Boolean); const values = model.metadata?.originalTypes ?? model.capabilities?.originalTypes; if (Array.isArray(values)) return values.map(String).filter(Boolean); return []; } export function baseModelTypeText(model: BaseModelCatalogItem) { return baseModelTypes(model).join(', '); } export function primaryBaseModelType(model: BaseModelCatalogItem) { return baseModelTypes(model)[0] ?? 'text_generate'; } function stripProviderPrefix(value: string, providerKey: string, canonicalModelKey: string) { const trimmed = value.trim(); if (!trimmed) return trimmed; const prefix = `${providerKey}:`; if (providerKey && trimmed.startsWith(prefix)) return trimmed.slice(prefix.length); if (trimmed === canonicalModelKey) { const [, alias] = trimmed.split(/:(.*)/s); return alias || trimmed; } return trimmed; } function readRecord(value: unknown) { return value && typeof value === 'object' && !Array.isArray(value) ? value as Record : undefined; } function readString(value: unknown) { return typeof value === 'string' ? value.trim() : ''; } function platformModelRuntimeOverride(form: PlatformWizardForm) { const override: Record = {}; if (form.modelOverrideRetry) { override.retryPolicy = { enabled: form.modelRetryEnabled, maxAttempts: form.modelRetryEnabled ? positiveInt(form.modelRetryMaxAttempts, 2) : 1, }; } if (form.modelOverrideRateLimit) { override.rateLimitPolicy = rateLimitPolicyPayload({ rpmLimit: form.modelRpmLimit, rpsLimit: form.modelRpsLimit, tpmLimit: form.modelTpmLimit, concurrencyLimit: form.modelConcurrencyLimit, }); } return Object.keys(override).length ? override : undefined; } export function validatePlatformForm(form: PlatformWizardForm, selectedCount: number, options: { allowEmptyCredentials?: boolean } = {}): string { if (!form.provider.trim()) return '请选择模型厂商。'; if (!form.name.trim()) return '请填写平台名称。'; if (!form.baseUrl.trim()) return '请填写 Base URL。'; if (options.allowEmptyCredentials && !hasCredentialInput(form)) { if (selectedCount === 0) return '请至少添加一个模型。'; return ''; } if (form.authType === 'APIKey' && !form.apiKey.trim() && !form.testMode) return '请填写 API Key,或开启测试模式。'; if (form.authType === 'Token' && !form.token.trim() && !form.testMode) return '请填写 Token,或开启测试模式。'; if (form.authType === 'AccessKey-SecretKey' && (!form.accessKey.trim() || !form.secretKey.trim()) && !form.testMode) { return '请填写 AccessKey 和 SecretKey,或开启测试模式。'; } if (selectedCount === 0) return '请至少添加一个模型。'; return ''; } export function providerLabel(provider: string) { return provider || 'provider'; } function credentialsPayload(form: PlatformWizardForm, options: { preserveEmptyCredentials?: boolean } = {}) { if (form.authType === 'APIKey') { return credentialPayloadForFields(form, options, [{ key: 'apiKey', value: form.apiKey, previewKeys: ['apiKey', 'api_key', 'key'] }]); } if (form.authType === 'Token') { return credentialPayloadForFields(form, options, [{ key: 'token', value: form.token, previewKeys: ['token'] }]); } if (form.authType === 'AccessKey-SecretKey') { return credentialPayloadForFields(form, options, [ { key: 'accessKey', value: form.accessKey, previewKeys: ['accessKey', 'access_key'] }, { key: 'secretKey', value: form.secretKey, previewKeys: ['secretKey', 'secret_key'] }, ]); } return {}; } function credentialPayloadForFields( form: PlatformWizardForm, options: { preserveEmptyCredentials?: boolean }, fields: Array<{ key: string; previewKeys: string[]; value: string }>, ) { const entries = fields.map((field) => ({ key: field.key, preview: credentialPreviewValue(form.credentialsPreview, ...field.previewKeys), value: field.value.trim(), })); const allUnchanged = entries.every((entry) => entry.preview && entry.value === entry.preview); if (options.preserveEmptyCredentials && allUnchanged) return undefined; const allEmpty = entries.every((entry) => !entry.value); if (allEmpty) return {}; const payloadEntries: Array<[string, string | null]> = []; for (const entry of entries) { if (entry.preview && entry.value === entry.preview) continue; payloadEntries.push([entry.key, entry.value || null]); } return Object.fromEntries(payloadEntries); } function hasCredentialInput(form: PlatformWizardForm) { if (form.authType === 'APIKey') return Boolean(form.apiKey.trim()); if (form.authType === 'Token') return Boolean(form.token.trim()); if (form.authType === 'AccessKey-SecretKey') return Boolean(form.accessKey.trim() || form.secretKey.trim()); return true; } function credentialPreviewValue(preview: Record | undefined, ...keys: string[]) { if (!preview) return ''; for (const key of keys) { const value = preview[key]; if (typeof value === 'string' && value.trim()) return value; } return ''; } function rateLimitPolicyPayload(form: Pick) { const rules = [ limitRule('rpm', form.rpmLimit), limitRule('rps', form.rpsLimit, 1), limitRule('tpm_total', form.tpmLimit), limitRule('concurrent', form.concurrencyLimit), ].filter((rule): rule is NonNullable> => Boolean(rule)); return rules.length ? { rules } : {}; } function limitRule(metric: string, value: string, windowSeconds = 60) { const limit = positiveNumber(value, 0); if (!limit) return undefined; return { metric, limit, windowSeconds, leaseTtlSeconds: metric === 'concurrent' ? 120 : 70, }; } function optionalString(value: string | null | undefined) { const trimmed = value?.trim() ?? ''; return trimmed || undefined; } function positiveNumber(value: string, fallback: number) { const parsed = Number(value); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } function optionalPositiveNumber(value: string) { const parsed = Number(value); return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; } function positiveInt(value: string, fallback: number) { const parsed = Number.parseInt(value, 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; }