{"record":{"id":"6f3ca9da6ee92e99","repo":"wavetermdev/waveterm","slug":"unsupported-url-protocol-in-file-part-s-6f3ca9","errorCode":null,"errorMessage":"unsupported URL protocol in file part: %s","messagePattern":"unsupported URL protocol in file part: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-convertmessage.go","lineNumber":367,"sourceCode":"}\n\n// convertFileUIMessagePart converts a file part to Anthropic image or document block format\nfunc convertFileUIMessagePart(p uctypes.UIMessagePart) (*anthropicMessageContentBlock, error) {\n\tif p.Type != \"file\" {\n\t\treturn nil, fmt.Errorf(\"convertFileUIMessagePart expects 'file' type, got '%s'\", p.Type)\n\t}\n\tif p.URL == \"\" {\n\t\treturn nil, errors.New(\"file part missing url\")\n\t}\n\tif p.MediaType == \"\" {\n\t\treturn nil, errors.New(\"file part missing mediaType\")\n\t}\n\n\t// Validate URL protocol - only allow data:, http:, https:\n\tif !strings.HasPrefix(p.URL, \"data:\") &&\n\t\t!strings.HasPrefix(p.URL, \"http://\") &&\n\t\t!strings.HasPrefix(p.URL, \"https://\") {\n\t\treturn nil, fmt.Errorf(\"unsupported URL protocol in file part: %s\", p.URL)\n\t}\n\n\t// Branch on mediaType first to determine block type and constraints\n\tswitch {\n\tcase strings.HasPrefix(p.MediaType, \"image/\"):\n\t\t// image/* (jpeg, png, gif, webp) → Anthropic image block\n\t\tif strings.HasPrefix(p.URL, \"data:\") {\n\t\t\t// Data URL → base64 source\n\t\t\tparts := strings.SplitN(p.URL, \",\", 2)\n\t\t\tif len(parts) != 2 {\n\t\t\t\treturn nil, errors.New(\"invalid data URL format\")\n\t\t\t}\n\t\t\treturn &anthropicMessageContentBlock{\n\t\t\t\tType: \"image\",\n\t\t\t\tSource: &anthropicSource{\n\t\t\t\t\tType:      \"base64\",\n\t\t\t\t\tData:      parts[1],\n\t\t\t\t\tMediaType: p.MediaType,","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-convertmessage.go#L349-L385","documentation":"The Anthropic converter only accepts file-part URLs with a data:, http://, or https:// scheme. Any other protocol (ftp://, file://, blob:, ws:, or a bare path like /tmp/x.png) is rejected before any block is built, because the library will not fetch arbitrary schemes or guess a source type. For AIMessageParts (unlike UIMessageParts) this check only applies when a URL is present at all.","triggerScenarios":"Setting p.URL to an unsupported scheme such as file:///home/user/doc.pdf, ftp://..., blob:..., or a bare local path and then sending the message through ConvertUIMessageToAnthropicChatMessage / convertFileUIMessagePart.","commonSituations":"Server-side code reusing browser blob: URLs that are meaningless outside the client; passing local filesystem paths as URLs; generating presigned or internal scheme URLs (s3://, gs://) that Anthropic cannot consume.","solutions":["Convert the file content to a data: URL (base64) before building the part","Serve the file over public http(s) and use that URL instead","Upload to the Anthropic Files API and reference by file_id if supported by your flow","Strip or fix blob:/file:/custom-scheme URLs when ingesting parts from the frontend"],"exampleFix":"// before\npart := uctypes.UIMessagePart{Type: \"file\", URL: \"file:///tmp/report.pdf\", MediaType: \"application/pdf\"}\n// after\ndata, _ := os.ReadFile(\"/tmp/report.pdf\")\npart := uctypes.UIMessagePart{Type: \"file\", URL: \"data:application/pdf;base64,\" + base64.StdEncoding.EncodeToString(data), MediaType: \"application/pdf\"}","handlingStrategy":"validation","validationCode":"func validURLScheme(u string) bool {\n    return strings.HasPrefix(u, \"data:\") || strings.HasPrefix(u, \"http://\") || strings.HasPrefix(u, \"https://\")\n}\nif part.URL != \"\" && !validURLScheme(part.URL) { return fmt.Errorf(\"unsupported scheme: %s\", part.URL) }","typeGuard":"func isFetchableOrDataURL(u string) bool {\n    return strings.HasPrefix(u, \"data:\") || strings.HasPrefix(u, \"http://\") || strings.HasPrefix(u, \"https://\")\n}","tryCatchPattern":"if err := convertAndSend(part); err != nil {\n    var protoErr = \"unsupported URL protocol\"\n    if strings.Contains(err.Error(), protoErr) {\n        // fall back: fetch bytes and resend as data: URL\n        return resendAsDataURL(part)\n    }\n    return err\n}","preventionTips":["Convert local/blob/custom-scheme URLs to base64 data URLs before building parts","Run url.Parse and check the scheme whitelist at part-construction time","Never pass browser blob: URLs to server-side converters"],"tags":["go","url-validation","unsupported-protocol","anthropic"],"backgroundTag":"unsupported-url-scheme","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}