diff --git a/translate/translate.go b/translate/translate.go index 4ce71e9..0a2bac9 100644 --- a/translate/translate.go +++ b/translate/translate.go @@ -8,8 +8,10 @@ package translate import ( + "encoding/base64" "encoding/json" "fmt" + "os" "git.hangman-lab.top/hzhang/Plexum-sdk-go/canonical" @@ -74,6 +76,40 @@ func CanonicalToAnthropic(req canonical.TurnRequest, modelID string, defaultMaxT return out, nil } +// imageSource converts a canonical.ImageBlock into the +// Anthropic-shaped {"type":..., ...} source map. Picks the most +// efficient source mode available; returns nil if all sources are +// empty or the path is unreadable. +// +// URL set → {"type":"url", "url":...} +// DataBase64 set → {"type":"base64", "media_type":..., "data":...} +// Path set → read file → base64 → {"type":"base64", ...} +func imageSource(v *canonical.ImageBlock) map[string]any { + media := v.MediaType + if media == "" { + media = "application/octet-stream" + } + if v.URL != "" { + return map[string]any{"type": "url", "url": v.URL} + } + if v.DataBase64 != "" { + return map[string]any{ + "type": "base64", "media_type": media, "data": v.DataBase64, + } + } + if v.Path == "" { + return nil + } + raw, err := os.ReadFile(v.Path) + if err != nil { + return nil + } + return map[string]any{ + "type": "base64", "media_type": media, + "data": base64.StdEncoding.EncodeToString(raw), + } +} + func roleToAnthropic(r canonical.Role) string { switch r { case canonical.RoleUser: @@ -129,6 +165,16 @@ func blockToAnthropic(b canonical.Block) anthropic.ContentBlock { return anthropic.ContentBlock{ "type": "thinking", "thinking": v.Thinking, "signature": v.Signature, } + case *canonical.ImageBlock: + // Three source shapes possible; emit whatever the canonical + // block carries. Anthropic accepts base64 + url + (vendor- + // specific) file shape. Path → read + base64 (default). + src := imageSource(v) + if src == nil { + return anthropic.ContentBlock{"type": "text", + "text": fmt.Sprintf("[image %q unavailable]", v.Path)} + } + return anthropic.ContentBlock{"type": "image", "source": src} default: // Best-effort generic: marshal then unmarshal into a map. raw, err := json.Marshal(b)