{"record":{"id":"399e0b9636b05c0b","repo":"yikart/AiToEarn","slug":"invalidmodel-399e0b","errorCode":"InvalidModel","errorMessage":"ResponseCode.InvalidModel","messagePattern":"ResponseCode\\.InvalidModel","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-ai/src/core/draft-generation/draft-generation-planner.service.ts","lineNumber":80,"sourceCode":"  items: z.array(z.object({\n    text: z.string().min(1).max(120).describe('Short memory description'),\n  })).max(20),\n})\n\nexport type AutoMemoryResult = z.infer<typeof AutoMemoryResultSchema>\n\n@Injectable()\nexport class DraftGenerationPlannerService {\n  constructor(\n    private readonly aiAvailability: AiAvailabilityService,\n    @Optional() private readonly relayMediaResolver?: RelayMediaResolverService,\n  ) {}\n\n  async planVideo(input: VideoPlanInput): Promise<{ plan: VideoDraftPlanResult, model: string }> {\n    const modelName = input.plannerModel ?? config.ai.draftGeneration.planner.defaultModel\n    const modelConfig = config.ai.models.chat.find(model => model.name === modelName && model.scenes?.includes('draft-generation'))\n    if (!modelConfig) {\n      throw new AppException(ResponseCode.InvalidModel)\n    }\n    const resolvedInput = await this.resolveReferenceUrls(input)\n    const prompt = this.buildVideoPrompt(resolvedInput)\n    const plan = await this.invokeStructuredPlanner(modelConfig, prompt, VideoDraftPlanResultSchema, resolvedInput.referenceImageUrls)\n    return { plan, model: modelConfig.name }\n  }\n\n  async planImageText(input: ImageTextPlanInput): Promise<{ plan: ImageTextDraftPlanResult, model: string }> {\n    const modelName = input.plannerModel ?? config.ai.draftGeneration.planner.defaultModel\n    const modelConfig = config.ai.models.chat.find(model => model.name === modelName && model.scenes?.includes('draft-generation'))\n    if (!modelConfig) {\n      throw new AppException(ResponseCode.InvalidModel)\n    }\n    const resolvedInput = await this.resolveReferenceUrls(input)\n    const prompt = this.buildImageTextPrompt(resolvedInput)\n    const plan = await this.invokeStructuredPlanner(modelConfig, prompt, ImageTextDraftPlanResultSchema, resolvedInput.referenceImageUrls)\n    if (plan.imagePrompts.length !== input.imageCount) {\n      plan.imagePrompts = Array.from({ length: input.imageCount }, (_, index) => plan.imagePrompts[index] ?? plan.imagePrompts[0] ?? input.userPrompt ?? '')","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-ai/src/core/draft-generation/draft-generation-planner.service.ts#L62-L98","documentation":"DraftGenerationPlannerService.planVideo resolves the planner model name (input.plannerModel or the configured default) and looks it up in config.ai.models.chat, requiring the entry to also have 'draft-generation' in its scenes array. If no such chat model config exists, it throws InvalidModel before any AI call is made.","triggerScenarios":"planVideo(input) is called with input.plannerModel set to a name not present in config.ai.models.chat, or present but whose scenes array does not include 'draft-generation'; or the default planner.defaultModel is missing/misnamed in the AI app config.","commonSituations":"Deployment config (yaml/env-derived config) never registered the chosen model under scenes ['draft-generation']; a model was renamed in the provider but not in app config; caller passes a raw provider model string that has no matching config entry; planner.defaultModel typo after a config refactor.","solutions":["Add the requested model to config.ai.models.chat with scenes including 'draft-generation', or pass a plannerModel that matches an existing entry","Log the available chat model names/scenes at startup and compare with the requested modelName","Validate input.plannerModel against the allowed model list in the DTO (Zod enum) before reaching the service","Fix planner.defaultModel in config if the default itself is wrong"],"exampleFix":"// before (config)\nmodels:\n  chat:\n    - name: gpt-4o\n      scenes: ['chat']\n// after (config)\nmodels:\n  chat:\n    - name: gpt-4o\n      scenes: ['chat', 'draft-generation']","handlingStrategy":"validation","validationCode":"const modelName = input.plannerModel ?? config.ai.draftGeneration.planner.defaultModel\nconst valid = config.ai.models.chat.some(m => m.name === modelName && m.scenes?.includes('draft-generation'))\nif (!valid) throw new Error(`Invalid planner model for draft-generation: ${modelName}`)\nawait plannerService.planVideo(input)","typeGuard":"function isValidPlannerModel(name: string): boolean {\n  return config.ai.models.chat.some(m => m.name === name && m.scenes?.includes('draft-generation'))\n}","tryCatchPattern":"try {\n  const { plan, model } = await plannerService.planVideo(input)\n} catch (e) {\n  if (e instanceof AppException && e.code === ResponseCode.InvalidModel) {\n    // fall back to the configured default planner\n    return plannerService.planVideo({ ...input, plannerModel: config.ai.draftGeneration.planner.defaultModel })\n  }\n  throw e\n}","preventionTips":["Expose the valid model list to clients from config instead of hardcoding model names","Add a Zod enum validation on plannerModel derived from config at the DTO layer","Tag every intended planner model with scenes: ['draft-generation'] in config","Add a startup check that planner.defaultModel resolves to a configured chat model"],"tags":["configuration","model-selection","validation","ai"],"backgroundTag":"model-not-configured","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}