{"record":{"id":"5234e9699c4149ff","repo":"linshenkx/prompt-optimizer","slug":"each-message-must-have-role-and-content","errorCode":null,"errorMessage":"Each message must have role and content","messagePattern":"Each message must have role and content","errorType":"validation","errorClass":"RequestConfigError","httpStatus":null,"severity":"error","filePath":"packages/core/src/services/llm/adapters/abstract-adapter.ts","lineNumber":189,"sourceCode":"  // ===== 公共验证方法 =====\n\n  /**\n   * 验证消息数组格式\n   * @param messages 消息数组\n   * @throws {RequestConfigError} 当消息数组无效时\n   */\n  protected validateMessages(messages: Message[]): void {\n    if (!Array.isArray(messages)) {\n      throw new RequestConfigError('Messages must be an array')\n    }\n\n    if (messages.length === 0) {\n      throw new RequestConfigError('Messages array cannot be empty')\n    }\n\n    for (const msg of messages) {\n      if (!msg.role || !msg.content) {\n        throw new RequestConfigError('Each message must have role and content')\n      }\n\n      if (!['system', 'user', 'assistant', 'tool'].includes(msg.role)) {\n        throw new RequestConfigError(`Invalid message role: ${msg.role}`)\n      }\n\n      if (typeof msg.content !== 'string') {\n        throw new RequestConfigError('Message content must be a string')\n      }\n    }\n  }\n\n  protected validateImageUnderstandingRequest(request: ImageUnderstandingRequest): void {\n    if (!request || typeof request !== 'object') {\n      throw new RequestConfigError('Image understanding request cannot be empty')\n    }\n\n    if (typeof request.userPrompt !== 'string' || !request.userPrompt.trim()) {","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/core/src/services/llm/adapters/abstract-adapter.ts#L171-L207","documentation":"Thrown by validateMessages when any element of the messages array lacks a truthy role or content field. The adapter validates each message shape before constructing the provider request; a message without role/content cannot be serialized into a chat completion. This is a client-side RequestConfigError thrown before any API call.","triggerScenarios":"Passing objects like {role:'user'} (no content), {content:'hi'} (no role), or {role:'user', content:''} (empty string is falsy) in the messages array of sendMessage/sendMessageStream/sendMessageStreamWithTools.","commonSituations":"Constructing messages from untyped API payloads or JSON where fields are optional; tool-result messages where content was set to undefined; empty-string content (e.g. assistant messages representing tool calls) being rejected by the truthiness check.","solutions":["Ensure every message has both a non-empty role and non-empty content","If content can legitimately be empty (tool-call messages), use a placeholder like ' ' or check the API's expectations","Validate messages with a type guard before calling send*"],"exampleFix":"// before\nmessages = [{ role: 'assistant' }]\n\n// after\nmessages = [{ role: 'assistant', content: '(tool call)' }]","handlingStrategy":"type-guard","validationCode":"const ok = messages.every(m => m && typeof m.role === 'string' && m.role && typeof m.content === 'string' && m.content.length > 0)\nif (!ok) throw new Error('Invalid message shape')","typeGuard":"function isMessage(m: unknown): m is Message {\n  return !!m && typeof m === 'object'\n    && typeof (m as Message).role === 'string' && (m as Message).role.length > 0\n    && typeof (m as Message).content === 'string' && (m as Message).content.length > 0\n}","tryCatchPattern":"try { await adapter.sendMessage(msgs, opts) } catch (e) { if (e instanceof RequestConfigError && /role and content/.test(e.message)) { /* fix message shapes */ } else throw e }","preventionTips":["Type message arrays as Message[] instead of any[]","Run messages.every(isMessage) before sending","Remember empty-string content is rejected too"],"tags":["validation","messages","llm","missing-field"],"backgroundTag":"message-shape-validation","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}