feat(v2/anthropic): apply cache_control markers from CacheHints
buildRequest now tracks a source-index → built-message-index mapping
during the role-merge pass, then uses the mapping to attach
cache_control: {type: ephemeral} markers at the positions indicated by
Request.CacheHints. The last tool, the last system part, and the last
non-system message each get a marker when the corresponding hint is set.
Covers the merge-induced index drift that would otherwise cause the
breakpoint to land on the wrong content block when consecutive same-role
source messages are combined into a single Anthropic message with
multiple content blocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,16 @@ func (p *Provider) buildRequest(req provider.Request) anth.MessagesRequest {
|
||||
var msgs []anth.Message
|
||||
var systemText string
|
||||
|
||||
for _, msg := range req.Messages {
|
||||
// sourceLanding[srcIdx] = (msgIndex, blockIndex) of the LAST content block
|
||||
// corresponding to that source message, after merging. System-role source
|
||||
// messages map to {-1, -1} since they don't appear in msgs.
|
||||
type landing struct{ msg, block int }
|
||||
sourceLanding := make([]landing, len(req.Messages))
|
||||
for i := range sourceLanding {
|
||||
sourceLanding[i] = landing{-1, -1}
|
||||
}
|
||||
|
||||
for srcIdx, msg := range req.Messages {
|
||||
if msg.Role == "system" {
|
||||
if len(systemText) > 0 {
|
||||
systemText += "\n"
|
||||
@@ -98,7 +107,7 @@ func (p *Provider) buildRequest(req provider.Request) anth.MessagesRequest {
|
||||
toolUseID := msg.ToolCallID
|
||||
content := msg.Content
|
||||
isError := false
|
||||
msgs = append(msgs, anth.Message{
|
||||
newMsg := anth.Message{
|
||||
Role: anth.RoleUser,
|
||||
Content: []anth.MessageContent{
|
||||
{
|
||||
@@ -115,7 +124,14 @@ func (p *Provider) buildRequest(req provider.Request) anth.MessagesRequest {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
// Tool-result messages bypass the role-merge logic — they always
|
||||
// create a new msgs entry. Preserve that.
|
||||
msgs = append(msgs, newMsg)
|
||||
sourceLanding[srcIdx] = landing{
|
||||
msg: len(msgs) - 1,
|
||||
block: len(newMsg.Content) - 1,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -207,11 +223,23 @@ func (p *Provider) buildRequest(req provider.Request) anth.MessagesRequest {
|
||||
|
||||
// Audio is not supported by Anthropic — skip silently.
|
||||
|
||||
// Merge consecutive same-role messages (Anthropic requires alternating)
|
||||
// Merge consecutive same-role messages (Anthropic requires alternating).
|
||||
if len(msgs) > 0 && msgs[len(msgs)-1].Role == role {
|
||||
// Track the landing BEFORE mutating: the source message lands in
|
||||
// the existing last msgs entry, and its last block is at the
|
||||
// current end-of-content plus len(m.Content)-1.
|
||||
existingEnd := len(msgs[len(msgs)-1].Content)
|
||||
msgs[len(msgs)-1].Content = append(msgs[len(msgs)-1].Content, m.Content...)
|
||||
sourceLanding[srcIdx] = landing{
|
||||
msg: len(msgs) - 1,
|
||||
block: existingEnd + len(m.Content) - 1,
|
||||
}
|
||||
} else {
|
||||
msgs = append(msgs, m)
|
||||
sourceLanding[srcIdx] = landing{
|
||||
msg: len(msgs) - 1,
|
||||
block: len(m.Content) - 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +273,34 @@ func (p *Provider) buildRequest(req provider.Request) anth.MessagesRequest {
|
||||
anthReq.StopSequences = req.Stop
|
||||
}
|
||||
|
||||
// Apply cache_control markers from hints.
|
||||
if req.CacheHints != nil {
|
||||
h := req.CacheHints
|
||||
|
||||
if h.CacheTools && len(anthReq.Tools) > 0 {
|
||||
anthReq.Tools[len(anthReq.Tools)-1].CacheControl = &anth.MessageCacheControl{
|
||||
Type: anth.CacheControlTypeEphemeral,
|
||||
}
|
||||
}
|
||||
if h.CacheSystem && len(anthReq.MultiSystem) > 0 {
|
||||
anthReq.MultiSystem[len(anthReq.MultiSystem)-1].CacheControl = &anth.MessageCacheControl{
|
||||
Type: anth.CacheControlTypeEphemeral,
|
||||
}
|
||||
}
|
||||
if h.LastCacheableMessageIndex >= 0 && h.LastCacheableMessageIndex < len(sourceLanding) {
|
||||
land := sourceLanding[h.LastCacheableMessageIndex]
|
||||
if land.msg >= 0 && land.msg < len(anthReq.Messages) {
|
||||
blocks := anthReq.Messages[land.msg].Content
|
||||
if land.block >= 0 && land.block < len(blocks) {
|
||||
blocks[land.block].CacheControl = &anth.MessageCacheControl{
|
||||
Type: anth.CacheControlTypeEphemeral,
|
||||
}
|
||||
anthReq.Messages[land.msg].Content = blocks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return anthReq
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user