diff --git a/src/modules/llms/server/listModels.dispatch.ts b/src/modules/llms/server/listModels.dispatch.ts index 7eef7cff8..a496925c8 100644 --- a/src/modules/llms/server/listModels.dispatch.ts +++ b/src/modules/llms/server/listModels.dispatch.ts @@ -8,7 +8,7 @@ import { createDebugWireLogger } from '~/server/wire'; import { fetchJsonOrTRPCThrow } from '~/server/trpc/trpc.router.fetchers'; import type { ModelDescriptionSchema } from './llm.server.types'; -import { llmsAutoImplyInterfaces } from './models.mappings'; +import { llmDevValidateParameterSpecs_DEV, llmsAutoImplyInterfaces } from './models.mappings'; // protocol: Anthropic @@ -71,8 +71,14 @@ function createDispatch(dispatch: ListModelsDispatch): ListModelsDispatch< export async function listModelsRunDispatch(access: AixAPI_Access, signal?: AbortSignal): Promise { const dispatch = _listModelsCreateDispatch(access, signal); const wireModels = await dispatch.fetchModels(); - return dispatch.convertToDescriptions(wireModels) + const models = dispatch.convertToDescriptions(wireModels) .map(llmsAutoImplyInterfaces); // auto-inject implied IFs from parameterSpecs + + // DEV: validate parameterSpecs (enumValues ⊆ registry values, paramId existence) + if (process.env.NODE_ENV === 'development') + models.forEach(llmDevValidateParameterSpecs_DEV); + + return models; } diff --git a/src/modules/llms/server/models.mappings.ts b/src/modules/llms/server/models.mappings.ts index ffc97e609..72137b87b 100644 --- a/src/modules/llms/server/models.mappings.ts +++ b/src/modules/llms/server/models.mappings.ts @@ -1,5 +1,6 @@ import type { DModelInterfaceV1 } from '~/common/stores/llms/llms.types'; import type { DModelParameterId } from '~/common/stores/llms/llms.parameters'; +import { DModelParameterRegistry } from '~/common/stores/llms/llms.parameters'; import { LLM_IF_Outputs_Image, LLM_IF_Tools_WebSearch } from '~/common/stores/llms/llms.types'; import type { ModelDescriptionSchema } from './llm.server.types'; @@ -79,6 +80,36 @@ export function llmDevCheckModels_DEV(vendor: string, apiIds: string[], knownIds } } +// -- Dev parameterSpecs validation -- + +/** + * DEV: Validates parameterSpecs for a model description. + * - Checks that each paramId exists in the DModelParameterRegistry + * - For enum params with enumValues, checks that enumValues ⊆ registry.values + */ +export function llmDevValidateParameterSpecs_DEV(model: ModelDescriptionSchema): void { + if (!model.parameterSpecs?.length) return; + + for (const spec of model.parameterSpecs) { + const paramId = spec.paramId; + const regDef = DModelParameterRegistry[paramId]; + + // check paramId exists in registry + if (!regDef) { + console.warn(`[DEV] Model '${model.id}': unknown paramId '${paramId}' in parameterSpecs`); + continue; + } + + // for enum params with enumValues, check containment + if (regDef.type === 'enum' && 'values' in regDef && spec.enumValues) { + const registryValues = regDef.values as ReadonlyArray; + const invalid = spec.enumValues.filter(v => !registryValues.includes(v)); + if (invalid.length) + console.warn(`[DEV] Model '${model.id}': paramId '${paramId}' has enumValues not in registry: [${invalid.join(', ')}] (valid: [${registryValues.join(', ')}])`); + } + } +} + // -- Manual model mappings: types and helper --