wavetermdev/waveterm · error

invalid AIMessage: %w

Error message

invalid AIMessage: %w

What it means

ConvertAIMessageToAnthropicChatMessage first runs aiMsg.Validate() and wraps any validation failure as "invalid AIMessage". This means the AIMessage struct itself failed the library's own validation rules (e.g. missing required fields like MessageId, no parts, or invalid part structure), before any per-part conversion happens.

Source

Thrown at pkg/aiusechat/anthropic/anthropic-convertmessage.go:463

				},
			}, nil
		} else {
			// HTTP/HTTPS URL → not supported inline, would need to fetch
			return nil, fmt.Errorf("dropping text/plain file with URL (must be fetched and converted to base64 or uploaded to Files API)")
		}

	default:
		// Other media types → not supported inline, must upload and use file_id
		return nil, fmt.Errorf("dropping file with unsupported media type '%s' (must be uploaded to Files API and sent as file_id)", p.MediaType)
	}

}

// convertAIMessageToAnthropicChatMessage converts an AIMessage to anthropicChatMessage
// These messages are ALWAYS role "user"
func ConvertAIMessageToAnthropicChatMessage(aiMsg uctypes.AIMessage) (*anthropicChatMessage, error) {
	if err := aiMsg.Validate(); err != nil {
		return nil, fmt.Errorf("invalid AIMessage: %w", err)
	}

	var contentBlocks []anthropicMessageContentBlock

	for i, part := range aiMsg.Parts {
		switch part.Type {
		case uctypes.AIMessagePartTypeText:
			if part.Text == "" {
				return nil, fmt.Errorf("part %d: text type requires non-empty text field", i)
			}
			contentBlocks = append(contentBlocks, anthropicMessageContentBlock{
				Type: "text",
				Text: part.Text,
			})

		case uctypes.AIMessagePartTypeFile:
			block, err := convertFileAIMessagePart(part)
			if err != nil {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Call aiMsg.Validate() yourself before conversion and log the wrapped error to see the exact rule that failed
  2. Populate required fields (MessageId, at least one valid Part)
  3. Check that JSON unmarshaling into uctypes.AIMessage maps the expected fields (correct json tags)
  4. Re-run validation after upgrading the library in case Validate() requirements changed

Example fix

// before
msg := uctypes.AIMessage{Parts: []uctypes.AIMessagePart{{Type: uctypes.AIMessagePartTypeText, Text: "hi"}}}
out, err := ConvertAIMessageToAnthropicChatMessage(msg) // fails validation
// after
msg := uctypes.AIMessage{MessageId: "msg_123", Parts: []uctypes.AIMessagePart{{Type: uctypes.AIMessagePartTypeText, Text: "hi"}}}
if err := msg.Validate(); err != nil { log.Fatal(err) }
out, err := ConvertAIMessageToAnthropicChatMessage(msg)
Defensive patterns

Strategy: validation

Validate before calling

if err := aiMsg.Validate(); err != nil {
    return fmt.Errorf("AIMessage not convertible: %w", err)
}
out, err := ConvertAIMessageToAnthropicChatMessage(aiMsg)

Type guard

func isValidAIMessage(m uctypes.AIMessage) bool {
    return m.Validate() == nil
}

Try / catch

out, err := ConvertAIMessageToAnthropicChatMessage(aiMsg)
if err != nil {
    var verr *uctypes.ValidationError
    if errors.As(err, &verr) {
        log.Printf("invalid AIMessage (%s): %v", aiMsg.MessageId, verr)
        return fallbackPlainMessage(aiMsg)
    }
    return err
}

Prevention

When it happens

Trigger: Calling ConvertAIMessageToAnthropicChatMessage (directly or via ConvertAIMessageToNativeChatMessage) with an AIMessage that has an empty Parts slice, an unset/invalid required field, or otherwise fails uctypes.AIMessage.Validate().

Common situations: Constructing AIMessage programmatically while skipping required fields; deserializing model output from JSON where MessageId/Parts were absent; passing a zero-value AIMessage{}; version drift where Validate() gained new required-field rules.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/a856087ef9f49508. Report an issue: GitHub.