mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 03:27:02 +08:00
@@ -156,6 +156,18 @@ func anthropicEndpointURL(baseURL string) string {
|
||||
return base + "/v1/messages"
|
||||
}
|
||||
|
||||
// shouldRelocateAnthropicImages 判断是否需要把图片块搬运到末条 user 消息。
|
||||
//
|
||||
// 官方 Anthropic 端点(api.anthropic.com)可正确处理任意位置的图片,保持原样;
|
||||
// 其余第三方中转站默认启用搬运,规避「非末尾图片被丢弃」的转换问题。
|
||||
func shouldRelocateAnthropicImages(baseURL string) bool {
|
||||
base := strings.ToLower(strings.TrimSpace(baseURL))
|
||||
if base == "" {
|
||||
return false
|
||||
}
|
||||
return !strings.Contains(base, "api.anthropic.com")
|
||||
}
|
||||
|
||||
// ApplyAnthropicCompatibleAuthHeaders 同时兼容 Anthropic 原生 x-api-key 和 Bearer token 代理。
|
||||
func ApplyAnthropicCompatibleAuthHeaders(httpReq *http.Request, apiKey string) {
|
||||
if httpReq == nil {
|
||||
@@ -215,8 +227,9 @@ func (adapter *AnthropicAdapter) Stream(ctx context.Context, req StreamRequest,
|
||||
body := cloneRequestBodyOverride(req.RequestBodyOverride)
|
||||
if len(body) == 0 {
|
||||
thinkingConfig := buildAnthropicThinkingConfig(req)
|
||||
relocateImages := shouldRelocateAnthropicImages(baseURL)
|
||||
stableMessageCount := anthropicStableProviderMessageCount(req.Messages, req.StableMessageCount, thinkingConfig != nil)
|
||||
systemParts, messages, err := normalizeAnthropicProviderMessages(req.Messages, thinkingConfig != nil)
|
||||
systemParts, messages, err := normalizeAnthropicProviderMessages(req.Messages, thinkingConfig != nil, relocateImages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1066,7 +1079,7 @@ func anthropicStableProviderMessageCount(input []Message, stableReplayMessageCou
|
||||
if len(stableReplayMessages) == 0 {
|
||||
return 0
|
||||
}
|
||||
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled)
|
||||
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled, false)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
@@ -1122,7 +1135,7 @@ func isAnthropicCacheableBlock(block map[string]any) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) ([]string, []anthropicMessage, error) {
|
||||
func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool, relocateImages bool) ([]string, []anthropicMessage, error) {
|
||||
systemParts := make([]string, 0, len(input))
|
||||
messages := make([]anthropicMessage, 0, len(input))
|
||||
pendingToolResults := make([]map[string]any, 0, 2)
|
||||
@@ -1208,9 +1221,70 @@ func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) (
|
||||
}
|
||||
}
|
||||
flushToolResults()
|
||||
if relocateImages {
|
||||
messages = relocateAnthropicImagesToLastUserMessage(messages)
|
||||
}
|
||||
return systemParts, messages, nil
|
||||
}
|
||||
|
||||
// relocateAnthropicImagesToLastUserMessage 把所有 user 消息里的 image 块搬运到最后一条 user 消息的末尾。
|
||||
//
|
||||
// 背景:部分第三方中转站(如 Bedrock 代理)在 Anthropic→上游 的消息转换中,
|
||||
// 会丢弃「后面还跟着大量文本/消息」的非末尾图片块。将图片统一移动到末条 user 消息
|
||||
// 可规避该问题,同时保留图片信息本身。
|
||||
//
|
||||
// 数据流演变:
|
||||
//
|
||||
// [user_info] [query + IMG] [reminder] [reminder] [current_request]
|
||||
// → [user_info] [query] [reminder] [reminder] [current_request + IMG]
|
||||
//
|
||||
// 搬运后若某条 user 消息 content 变空,则丢弃该消息,避免 Anthropic 拒绝空内容消息。
|
||||
func relocateAnthropicImagesToLastUserMessage(messages []anthropicMessage) []anthropicMessage {
|
||||
lastUserIndex := -1
|
||||
for index := len(messages) - 1; index >= 0; index-- {
|
||||
if strings.TrimSpace(messages[index].Role) == "user" {
|
||||
lastUserIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
if lastUserIndex < 0 {
|
||||
return messages
|
||||
}
|
||||
|
||||
relocated := make([]map[string]any, 0, 2)
|
||||
for index := 0; index < len(messages); index++ {
|
||||
if index == lastUserIndex || strings.TrimSpace(messages[index].Role) != "user" {
|
||||
continue
|
||||
}
|
||||
kept := make([]map[string]any, 0, len(messages[index].Content))
|
||||
for _, block := range messages[index].Content {
|
||||
if isAnthropicImageBlock(block) {
|
||||
relocated = append(relocated, block)
|
||||
continue
|
||||
}
|
||||
kept = append(kept, block)
|
||||
}
|
||||
messages[index].Content = kept
|
||||
}
|
||||
if len(relocated) == 0 {
|
||||
return messages
|
||||
}
|
||||
messages[lastUserIndex].Content = append(messages[lastUserIndex].Content, relocated...)
|
||||
|
||||
compacted := make([]anthropicMessage, 0, len(messages))
|
||||
for index, message := range messages {
|
||||
if index != lastUserIndex && strings.TrimSpace(message.Role) == "user" && len(message.Content) == 0 {
|
||||
continue
|
||||
}
|
||||
compacted = append(compacted, message)
|
||||
}
|
||||
return compacted
|
||||
}
|
||||
|
||||
func isAnthropicImageBlock(block map[string]any) bool {
|
||||
return strings.TrimSpace(anthropicStringField(block, "type")) == "image"
|
||||
}
|
||||
|
||||
func anthropicProviderContentBlocks(message Message, thinkingEnabled bool) ([]map[string]any, error) {
|
||||
blocks, err := anthropicContentBlocks(message)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user