import { DyFM_Error } from '@futdevpro/fsm-dynamo'; import { DyFM_AI_ModelInfo, DyFM_AI_ModelRegistry_Util, DyFM_AI_ModelSettingsSchema, DyFM_AI_SettingConstraint, } from '@futdevpro/fsm-dynamo/ai'; import { DyNTS_AI_ProviderError } from '../_models/interfaces/dynts-ai-provider-error.interface'; import { DyNTS_AI_ProviderError_Util } from './ai-provider-error.util'; /** * FR-048 **validate-mode** — a per-model `settingsSchema` szerinti config-ellenorzes MUTACIO NELKUL. * * Testvere a `DyNTS_AI_LLM_ServiceBase.reconcileCallSettings`-nek: a reconciler a hivas elott CSENDBEN * dropol/clampel (hogy a hivas atmenjen), ez viszont HIBAT AD — igy hasznalhato **production * pre-validaciora** (UI-form submit elott, backend service-boundary-n) es tesztben egyarant. * **Ugyanabbol az egy forrasbol** (`settingsSchema`) dolgozik mindketto — nincs parhuzamos szabaly-keszlet. * * A visszateres `null` = OK; kulonben provider-faithful `DyFM_Error` (a nativ hiba-body az * `additionalContent.providerErrorBody`-ban). * * Ismeretlen modell / schema-mentes registry-bejegyzes → `null` (permisszi­v, best-effort) — NEM talalgatunk. */ export class DyNTS_AI_ModelConfig_Validator { /** * Egy hivas-config ellenorzese a modell `settingsSchema`-ja ellen. * @param set - provider + modell + a vizsgalando config + a provider modell-registry-je */ static validate( set: { provider: string, model: string, config?: Record, registry: DyFM_AI_ModelInfo[], } ): DyFM_Error { if (!set.config) { return null; } const modelInfo: DyFM_AI_ModelInfo = DyFM_AI_ModelRegistry_Util.findModelInfo(set.registry ?? [], set.model); const schema: DyFM_AI_ModelSettingsSchema = modelInfo?.settingsSchema; if (!schema) { return null; } // 1) NEM tamogatott parameter (a modell 400-zal elutasitana) for (const param of schema.unsupported ?? []) { if (set.config[param] !== undefined) { return this.toError( DyNTS_AI_ProviderError_Util.buildUnsupportedParamError({ provider: set.provider, model: set.model, param: param, }) ); } } // 2) Tamogatott parameter, de ervenytelen ertek (constraints: allowed / min / max) return this.validateConstraints({ provider: set.provider, model: set.model, config: set.config, schema: schema, }); } /** A `constraints` (allowed / min / max) ellenorzese. `null` = OK. */ private static validateConstraints( set: { provider: string, model: string, config: Record, schema: DyFM_AI_ModelSettingsSchema, } ): DyFM_Error { const constraints: Record = set.schema.constraints ?? {}; for (const param of Object.keys(constraints)) { const constraint: DyFM_AI_SettingConstraint = constraints[param]; const value: unknown = set.config[param]; if (value === undefined) { continue; } if (constraint.allowed?.length && !constraint.allowed.includes(value)) { return this.toError( DyNTS_AI_ProviderError_Util.buildInvalidValueError({ provider: set.provider, model: set.model, param: param, detail: `must be one of [${constraint.allowed.join(', ')}]`, }) ); } if (typeof value === 'number') { const belowMin: boolean = typeof constraint.min === 'number' && value < constraint.min; const aboveMax: boolean = typeof constraint.max === 'number' && value > constraint.max; if (belowMin || aboveMax) { return this.toError( DyNTS_AI_ProviderError_Util.buildInvalidValueError({ provider: set.provider, model: set.model, param: param, detail: `out of range (${constraint.min ?? '-∞'}..${constraint.max ?? '∞'})`, }) ); } } } return null; } /** Provider-faithful hiba-leiroból a `DyFM_Error`. */ private static toError(providerError: DyNTS_AI_ProviderError): DyFM_Error { return new DyFM_Error({ message: `[FR-048] provider-faithful error (HTTP ${providerError.httpStatus}): ${providerError.reason}`, userMessage: 'The AI request contains a parameter this model does not accept.', errorCode: 'DyNTS-AICFG-UNSUP', additionalContent: { providerErrorBody: providerError.providerErrorBody }, }); } }