返回 DeepSeek-Reasonix
message_input.go
根目录 / internal / provider / anthropic / message_input.go
1 package anthropic
2
3 import (
4 "encoding/json"
5
6 "reasonix/internal/provider"
7 )
8
9 type messageAssembler struct {
10 client *client
11 messages []anthMessage
12 pendingImages []contentBlock
13 }
14
15 func (b *messageAssembler) appendBlocks(role string, blocks ...contentBlock) {
16 if len(blocks) == 0 {
17 return
18 }
19 if n := len(b.messages); n > 0 && b.messages[n-1].Role == role {
20 b.messages[n-1].Content = mergeThinkingFirst(b.messages[n-1].Content, blocks)
21 return
22 }
23 b.messages = append(b.messages, anthMessage{Role: role, Content: blocks})
24 }
25 func (b *messageAssembler) flushImages() {
26 b.appendBlocks("user", b.pendingImages...)
27 b.pendingImages = nil
28 }
29 func (c *client) buildMessages(messages []provider.Message) ([]textBlock, []anthMessage) {
30 var system []textBlock
31 b := messageAssembler{client: c}
32 for _, m := range provider.SanitizeToolPairing(c.replayMessages(messages)) {
33 if m.Role != provider.RoleTool {
34 b.flushImages()
35 }
36 switch m.Role {
37 case provider.RoleSystem:
38 if m.Content != "" {
39 system = append(system, textBlock{Type: "text", Text: m.Content})
40 }
41 case provider.RoleUser:
42 b.addUser(m)
43 case provider.RoleTool:
44 b.addTool(m)
45 case provider.RoleAssistant:
46 b.addAssistant(m)
47 }
48 }
49 b.flushImages()
50 return system, b.messages
51 }
52
53 func (b *messageAssembler) addUser(m provider.Message) {
54 if m.Content != "" {
55 b.appendBlocks("user", sessionContextTextBlocks(m.Content)...)
56 }
57 if b.client.vision {
58 for _, ref := range m.Images {
59 if src := imageSourceFromRef(ref); src != nil {
60 b.appendBlocks("user", contentBlock{Type: "image", Source: src})
61 }
62 }
63 }
64 }
65
66 func (b *messageAssembler) addTool(m provider.Message) {
67 content := m.Content
68 if content == "" {
69 content = "(no output)" // tool_result content must be non-empty
70 }
71 block := contentBlock{Type: "tool_result", ToolUseID: m.ToolCallID, Content: content}
72 if b.client.vision && !b.client.deepseek {
73 if blocks := toolResultBlocks(content, m.Images); blocks != nil {
74 block.Content = blocks
75 }
76 }
77 b.appendBlocks("user", block)
78 if b.client.vision && b.client.deepseek {
79 for _, ref := range m.Images {
80 if src := imageSourceFromRef(ref); src != nil {
81 b.pendingImages = append(b.pendingImages, contentBlock{Type: "image", Source: src})
82 }
83 }
84 }
85 }
86
87 func (b *messageAssembler) addAssistant(m provider.Message) {
88 var blocks []contentBlock
89 // Replay reasoning before content: DeepSeek needs historical thinking;
90 // Anthropic requires a valid signature. replayReasoningBlocks owns both rules.
91 blocks = append(blocks, b.client.replayReasoningBlocks(m)...)
92 blocks = appendServerSearchBlocks(blocks, m.ServerSearch)
93 if m.Content != "" {
94 blocks = append(blocks, contentBlock{Type: "text", Text: m.Content})
95 }
96 for _, tc := range m.ToolCalls {
97 input := json.RawMessage(tc.Arguments)
98 if len(input) == 0 {
99 input = json.RawMessage("{}") // input is required, even when empty
100 }
101 blocks = append(blocks, contentBlock{Type: "tool_use", ID: tc.ID, Name: tc.Name, Input: input})
102 }
103 b.appendBlocks("assistant", blocks...)
104 }
105
105 lines GO