mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-18 22:04:45 +08:00
fix: strip empty text blocks in retry filter and fix error pattern matching
Empty text blocks ({"type":"text","text":""}) cause Anthropic upstream to
return 400: "text content blocks must be non-empty". This was not caught
by the existing error detection pattern in isThinkingBlockSignatureError,
nor handled by FilterThinkingBlocksForRetry.
- Add empty text block stripping to FilterThinkingBlocksForRetry
- Fix isThinkingBlockSignatureError to match new Anthropic error format
- Add fast-path byte patterns to avoid unnecessary JSON parsing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ var (
|
||||
patternEmptyContentSpaced = []byte(`"content": []`)
|
||||
patternEmptyContentSp1 = []byte(`"content" : []`)
|
||||
patternEmptyContentSp2 = []byte(`"content" :[]`)
|
||||
|
||||
// Fast-path patterns for empty text blocks: {"type":"text","text":""}
|
||||
patternEmptyText = []byte(`"text":""`)
|
||||
patternEmptyTextSpaced = []byte(`"text": ""`)
|
||||
)
|
||||
|
||||
// SessionContext 粘性会话上下文,用于区分不同来源的请求。
|
||||
@@ -233,15 +237,20 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
|
||||
bytes.Contains(body, patternThinkingField) ||
|
||||
bytes.Contains(body, patternThinkingFieldSpaced)
|
||||
|
||||
// Also check for empty content arrays that need fixing.
|
||||
// Also check for empty content arrays and empty text blocks that need fixing.
|
||||
// Note: This is a heuristic check; the actual empty content handling is done below.
|
||||
hasEmptyContent := bytes.Contains(body, patternEmptyContent) ||
|
||||
bytes.Contains(body, patternEmptyContentSpaced) ||
|
||||
bytes.Contains(body, patternEmptyContentSp1) ||
|
||||
bytes.Contains(body, patternEmptyContentSp2)
|
||||
|
||||
// Check for empty text blocks: {"type":"text","text":""}
|
||||
// These cause upstream 400: "text content blocks must be non-empty"
|
||||
hasEmptyTextBlock := bytes.Contains(body, patternEmptyText) ||
|
||||
bytes.Contains(body, patternEmptyTextSpaced)
|
||||
|
||||
// Fast path: nothing to process
|
||||
if !hasThinkingContent && !hasEmptyContent {
|
||||
if !hasThinkingContent && !hasEmptyContent && !hasEmptyTextBlock {
|
||||
return body
|
||||
}
|
||||
|
||||
@@ -260,7 +269,7 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
|
||||
bytes.Contains(body, patternTypeRedactedThinking) ||
|
||||
bytes.Contains(body, patternTypeRedactedSpaced) ||
|
||||
bytes.Contains(body, patternThinkingFieldSpaced)
|
||||
if !hasEmptyContent && !containsThinkingBlocks {
|
||||
if !hasEmptyContent && !hasEmptyTextBlock && !containsThinkingBlocks {
|
||||
if topThinking := gjson.Get(jsonStr, "thinking"); topThinking.Exists() {
|
||||
if out, err := sjson.DeleteBytes(body, "thinking"); err == nil {
|
||||
out = removeThinkingDependentContextStrategies(out)
|
||||
@@ -320,6 +329,16 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
|
||||
|
||||
blockType, _ := blockMap["type"].(string)
|
||||
|
||||
// Strip empty text blocks: {"type":"text","text":""}
|
||||
// Upstream rejects these with 400: "text content blocks must be non-empty"
|
||||
if blockType == "text" {
|
||||
if txt, _ := blockMap["text"].(string); txt == "" {
|
||||
modifiedThisMsg = true
|
||||
ensureNewContent(bi)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Convert thinking blocks to text (preserve content) and drop redacted_thinking.
|
||||
switch blockType {
|
||||
case "thinking":
|
||||
|
||||
Reference in New Issue
Block a user