feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+80 -15
View File
@@ -3,6 +3,7 @@ package dto
import (
"encoding/json"
"fmt"
"maps"
"net/http"
"strings"
@@ -135,6 +136,9 @@ type ClaudeMessageSource struct {
type ClaudeMessage struct {
Role string `json:"role"`
Content any `json:"content"`
// OutputConfig carries per-message effort (beta): an effort-only system
// message has empty content and the new level in output_config.effort.
OutputConfig json.RawMessage `json:"output_config,omitempty"`
}
func (c *ClaudeMessage) IsStringContent() bool {
@@ -238,6 +242,7 @@ type ClaudeRequest struct {
TopK *int `json:"top_k,omitempty"`
Stream *bool `json:"stream,omitempty"`
Tools any `json:"tools,omitempty"`
Safeguards json.RawMessage `json:"safeguards,omitempty"`
ContextManagement json.RawMessage `json:"context_management,omitempty"`
OutputConfig json.RawMessage `json:"output_config,omitempty"`
OutputFormat json.RawMessage `json:"output_format,omitempty"`
@@ -332,9 +337,25 @@ func (c *ClaudeRequest) GetTokenCountMeta() *types.TokenCountMeta {
texts = append(texts, string(b))
}
case "tool_result":
if media.Content != nil {
b, _ := kitutil.Marshal(media.Content)
texts = append(texts, string(b))
// Count tool output as the text the model reads; JSON-encoding it
// would escape newlines and quotes and count image base64 as text.
if media.IsStringContent() {
texts = append(texts, media.GetStringContent())
break
}
for _, part := range media.ParseMediaContent() {
if source := part.ToFileSource(); source != nil {
fileType := types.FileTypeFile
if part.Type == "image" {
fileType = types.FileTypeImage
}
fileMeta = append(fileMeta, &types.FileMeta{FileType: fileType, Source: source})
} else if part.Type == "text" {
texts = append(texts, part.GetText())
} else {
b, _ := kitutil.Marshal(part)
texts = append(texts, string(b))
}
}
}
}
@@ -391,18 +412,6 @@ func (c *ClaudeRequest) SetModelName(modelName string) {
}
}
func (c *ClaudeRequest) SearchToolNameByToolCallId(toolCallId string) string {
for _, message := range c.Messages {
content, _ := message.ParseContent()
for _, mediaMessage := range content {
if mediaMessage.Id == toolCallId {
return mediaMessage.Name
}
}
}
return ""
}
// AddTool 添加工具到请求中
func (c *ClaudeRequest) AddTool(tool any) {
if c.Tools == nil {
@@ -456,6 +465,18 @@ func ProcessTools(tools []any) ([]*Tool, []*ClaudeWebSearchTool) {
normalTools = append(normalTools, &t)
case ClaudeWebSearchTool:
webSearchTools = append(webSearchTools, &t)
case map[string]any:
// Tools decoded from client JSON arrive as maps.
toolType, _ := t["type"].(string)
if strings.HasPrefix(toolType, "web_search") {
if webSearchTool, err := kitutil.Any2Type[ClaudeWebSearchTool](t); err == nil {
webSearchTools = append(webSearchTools, &webSearchTool)
}
continue
}
if normalTool, err := kitutil.Any2Type[Tool](t); err == nil {
normalTools = append(normalTools, &normalTool)
}
default:
// 未知类型,跳过
continue
@@ -473,6 +494,50 @@ type Thinking struct {
// the visible summary that was default on Opus 4.6; "omitted" (default on
// 4.7) suppresses it. Pass-through field from upstream Anthropic API.
Display string `json:"display,omitempty"`
// BlockBinding carries the client's preserved-thinking controls, such as
// {"prefix_mismatch_behavior":"drop_block"} (beta
// thinking-binding-controls-2026-08-01). It is forwarded verbatim and
// never added by the gateway.
BlockBinding json.RawMessage `json:"block_binding,omitempty"`
// Extra keeps thinking fields this struct does not model yet, so a native
// Claude request forwards them instead of silently dropping them. It is
// exported so common.DeepCopy copies it; MarshalJSON writes it back.
Extra map[string]json.RawMessage `json:"-"`
}
func (c *Thinking) UnmarshalJSON(data []byte) error {
type thinkingFields Thinking
var fields thinkingFields
if err := kitutil.Unmarshal(data, &fields); err != nil {
return err
}
var all map[string]json.RawMessage
if err := kitutil.Unmarshal(data, &all); err != nil {
return err
}
for _, known := range []string{"type", "budget_tokens", "display", "block_binding"} {
delete(all, known)
}
*c = Thinking(fields)
if len(all) > 0 {
c.Extra = all
}
return nil
}
func (c Thinking) MarshalJSON() ([]byte, error) {
type thinkingFields Thinking
data, err := kitutil.Marshal(thinkingFields(c))
if err != nil || len(c.Extra) == 0 {
return data, err
}
merged := make(map[string]json.RawMessage, len(c.Extra)+4)
maps.Copy(merged, c.Extra)
if err := kitutil.Unmarshal(data, &merged); err != nil {
return nil, err
}
return kitutil.Marshal(merged)
}
func (c *Thinking) GetBudgetTokens() int {