wavetermdev/waveterm · error

part %d: unsupported part type '%s'

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.

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)

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.