wavetermdev/waveterm · error

part : unsupported part type

Error message

part %d: unsupported part type '%s'

What it means

ConvertAIMessageToAnthropicChatMessage only understands two part types: text and file. Any other AIMessagePartType (e.g. tool-call, thinking, image-as-its-own-type, or unknown/garbage type values) hits the default branch and is rejected with the offending type name and its index.

Solutions

  1. Filter aiMsg.Parts to only text and file types before conversion
  2. Convert unsupported parts (e.g. tool calls) into their text/file equivalents or drop them
  3. Set part.Type explicitly; never rely on zero values
  4. Check for library version mismatches if a previously-accepted type is now rejected

Example fix

// before
msg := uctypes.AIMessage{MessageId: id, Parts: allParts}
// after
var kept []uctypes.AIMessagePart
for _, p := range allParts {
    if p.Type == uctypes.AIMessagePartTypeText || p.Type == uctypes.AIMessagePartTypeFile {
        kept = append(kept, p)
    }
}
msg := uctypes.AIMessage{MessageId: id, Parts: kept}
Defensive patterns

Strategy: type-guard

Validate before calling

var convertible []uctypes.AIMessagePart
for _, p := range aiMsg.Parts {
    if p.Type == uctypes.AIMessagePartTypeText || p.Type == uctypes.AIMessagePartTypeFile {
        convertible = append(convertible, p)
    }
}
aiMsg.Parts = convertible

Type guard

func isAnthropicConvertiblePart(p uctypes.AIMessagePart) bool {
    return p.Type == uctypes.AIMessagePartTypeText || p.Type == uctypes.AIMessagePartTypeFile
}

Try / catch

if err := convert(aiMsg); err != nil {
    if strings.Contains(err.Error(), "unsupported part type") {
        aiMsg.Parts = filterConvertibleParts(aiMsg.Parts)
        return convert(aiMsg)
    }
    return err
}

Prevention

When it happens

Trigger: Appending an AIMessagePart whose Type is not AIMessagePartTypeText or AIMessagePartTypeFile (including "" from a zero-value part or a mistyped constant) to aiMsg.Parts and converting for Anthropic.

Common situations: Reusing AIMessage parts populated for a different provider (OpenAI tool calls) without filtering; typo'd type constants; unmarshal producing empty Type; newer library versions adding part types this converter does not handle.

Related errors


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

Appendix: source

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

		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 {
				return nil, fmt.Errorf("part %d: %w", i, err)
			}
			contentBlocks = append(contentBlocks, *block)

		default:
			return nil, fmt.Errorf("part %d: unsupported part type '%s'", i, part.Type)
		}
	}

	return &anthropicChatMessage{
		MessageId: aiMsg.MessageId,
		Role:      "user",
		Content:   contentBlocks,
	}, nil
}

// hasInlineData checks if the part has data available for inline use (either Data field or data URL)
func hasInlineData(part uctypes.AIMessagePart) bool {
	hasData := len(part.Data) > 0
	hasURL := part.URL != "" && strings.HasPrefix(part.URL, "data:")
	return hasData || hasURL
}

// extractBase64Data extracts base64 data from either the Data field or a data URL

View on GitHub (pinned to a4447c1563)