{"record":{"id":"f60acc3d2dc63572","repo":"wavetermdev/waveterm","slug":"part-d-text-type-requires-non-empty-text-field","errorCode":null,"errorMessage":"part %d: text type requires non-empty text field","messagePattern":"part (.+?): text type requires non-empty text field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-convertmessage.go","lineNumber":472,"sourceCode":"\t\treturn nil, fmt.Errorf(\"dropping file with unsupported media type '%s' (must be uploaded to Files API and sent as file_id)\", p.MediaType)\n\t}\n\n}\n\n// convertAIMessageToAnthropicChatMessage converts an AIMessage to anthropicChatMessage\n// These messages are ALWAYS role \"user\"\nfunc ConvertAIMessageToAnthropicChatMessage(aiMsg uctypes.AIMessage) (*anthropicChatMessage, error) {\n\tif err := aiMsg.Validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid AIMessage: %w\", err)\n\t}\n\n\tvar contentBlocks []anthropicMessageContentBlock\n\n\tfor i, part := range aiMsg.Parts {\n\t\tswitch part.Type {\n\t\tcase uctypes.AIMessagePartTypeText:\n\t\t\tif part.Text == \"\" {\n\t\t\t\treturn nil, fmt.Errorf(\"part %d: text type requires non-empty text field\", i)\n\t\t\t}\n\t\t\tcontentBlocks = append(contentBlocks, anthropicMessageContentBlock{\n\t\t\t\tType: \"text\",\n\t\t\t\tText: part.Text,\n\t\t\t})\n\n\t\tcase uctypes.AIMessagePartTypeFile:\n\t\t\tblock, err := convertFileAIMessagePart(part)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"part %d: %w\", i, err)\n\t\t\t}\n\t\t\tcontentBlocks = append(contentBlocks, *block)\n\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"part %d: unsupported part type '%s'\", i, part.Type)\n\t\t}\n\t}\n","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-convertmessage.go#L454-L490","documentation":"Each text-type part of an AIMessage must carry a non-empty Text field. The library throws this when a part declares Type == AIMessagePartTypeText but part.Text is \"\", because an empty Anthropic text block would be rejected or meaningless.","triggerScenarios":"Building an AIMessage with a part {Type: AIMessagePartTypeText} but leaving Text unset, or setting Text from a variable that is empty at conversion time; trimming user input to \"\" and still appending the part.","commonSituations":"Optional prompt fields that default to empty string; frontend sending {type:\"text\", text:\"\"} placeholders; string-sanitization pipelines that strip all content; JSON payloads where the field is named \"content\" but unmarshals into Text incorrectly.","solutions":["Ensure Text is non-empty before appending a text part, or omit the part entirely","Check upstream sanitization/trimming that may empty the text","Verify JSON field names map into part.Text during unmarshal","Filter out empty-text parts before calling the converter"],"exampleFix":"// before\nparts := append(parts, uctypes.AIMessagePart{Type: uctypes.AIMessagePartTypeText, Text: strings.TrimSpace(userInput)})\n// after\nif t := strings.TrimSpace(userInput); t != \"\" {\n    parts = append(parts, uctypes.AIMessagePart{Type: uctypes.AIMessagePartTypeText, Text: t})\n}","handlingStrategy":"validation","validationCode":"for _, p := range aiMsg.Parts {\n    if p.Type == uctypes.AIMessagePartTypeText && p.Text == \"\" {\n        return errors.New(\"text part with empty text\")\n    }\n}","typeGuard":"func hasNonEmptyText(p uctypes.AIMessagePart) bool {\n    return p.Type != uctypes.AIMessagePartTypeText || p.Text != \"\"\n}","tryCatchPattern":"if err := convert(aiMsg); err != nil {\n    if strings.Contains(err.Error(), \"requires non-empty text\") {\n        aiMsg.Parts = dropEmptyTextParts(aiMsg.Parts)\n        return convert(aiMsg)\n    }\n    return err\n}","preventionTips":["Filter out empty-text parts before appending to Parts","Beware Trim strings.TrimSpace results that can produce \"\"","Check JSON field mapping so text actually lands in part.Text"],"tags":["go","validation","empty-field","anthropic"],"backgroundTag":"empty-required-field","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}