Changeset 0.12.0 (#63)
This commit is contained in:
@@ -263,6 +263,40 @@ func (p *AnthropicProvider) doRequest(ctx context.Context, cfg ProviderConfig, r
|
||||
Content: m.Content,
|
||||
},
|
||||
}
|
||||
} else if len(m.ContentParts) > 0 && m.Role == "user" {
|
||||
// Multimodal user message: convert ContentParts to Anthropic blocks
|
||||
for _, p := range m.ContentParts {
|
||||
switch p.Type {
|
||||
case "image_url":
|
||||
if p.ImageURL != nil {
|
||||
// Parse data URI: "data:image/jpeg;base64,{data}"
|
||||
mediaType, b64Data := parseDataURI(p.ImageURL.URL)
|
||||
if b64Data != "" {
|
||||
antMsg.Content = append(antMsg.Content, anthropicContentBlock{
|
||||
Type: "image",
|
||||
Source: &anthropicImageSource{
|
||||
Type: "base64",
|
||||
MediaType: mediaType,
|
||||
Data: b64Data,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
default: // "text", "document"
|
||||
if p.Text != "" {
|
||||
antMsg.Content = append(antMsg.Content, anthropicContentBlock{
|
||||
Type: "text",
|
||||
Text: p.Text,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(antMsg.Content) == 0 {
|
||||
// Fallback: shouldn't happen, but safety net
|
||||
antMsg.Content = []anthropicContentBlock{
|
||||
{Type: "text", Text: m.Content},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular text message
|
||||
antMsg.Content = []anthropicContentBlock{
|
||||
@@ -354,6 +388,8 @@ type anthropicContentBlock struct {
|
||||
Type string `json:"type"`
|
||||
// text
|
||||
Text string `json:"text,omitempty"`
|
||||
// image (type="image")
|
||||
Source *anthropicImageSource `json:"source,omitempty"`
|
||||
// tool_use
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
@@ -363,6 +399,15 @@ type anthropicContentBlock struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
// anthropicImageSource is the Anthropic-native image format.
|
||||
// Anthropic uses {"type":"base64","media_type":"image/jpeg","data":"..."}
|
||||
// rather than OpenAI's data URI approach.
|
||||
type anthropicImageSource struct {
|
||||
Type string `json:"type"` // "base64"
|
||||
MediaType string `json:"media_type"` // "image/jpeg", "image/png", etc.
|
||||
Data string `json:"data"` // raw base64 (no data: prefix)
|
||||
}
|
||||
|
||||
type anthropicMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content []anthropicContentBlock `json:"content"`
|
||||
@@ -425,3 +470,33 @@ type anthropicStreamEvent struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"content_block,omitempty"`
|
||||
}
|
||||
|
||||
// parseDataURI splits a data URI into media type and base64 data.
|
||||
// Input: "data:image/jpeg;base64,/9j/4AAQ..."
|
||||
// Output: "image/jpeg", "/9j/4AAQ..."
|
||||
// Returns empty strings if the URI is malformed.
|
||||
func parseDataURI(uri string) (mediaType, data string) {
|
||||
// Strip "data:" prefix
|
||||
if !strings.HasPrefix(uri, "data:") {
|
||||
return "", ""
|
||||
}
|
||||
rest := uri[5:]
|
||||
|
||||
// Split on comma (separates header from data)
|
||||
commaIdx := strings.Index(rest, ",")
|
||||
if commaIdx < 0 {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
header := rest[:commaIdx]
|
||||
data = rest[commaIdx+1:]
|
||||
|
||||
// Extract media type from header (before ";base64")
|
||||
if semiIdx := strings.Index(header, ";"); semiIdx >= 0 {
|
||||
mediaType = header[:semiIdx]
|
||||
} else {
|
||||
mediaType = header
|
||||
}
|
||||
|
||||
return mediaType, data
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user