2828 patternEmptyContentSpaced = []byte (`"content": []` )
2929 patternEmptyContentSp1 = []byte (`"content" : []` )
3030 patternEmptyContentSp2 = []byte (`"content" :[]` )
31+
32+ // Fast-path patterns for empty text blocks: {"type":"text","text":""}
33+ patternEmptyText = []byte (`"text":""` )
34+ patternEmptyTextSpaced = []byte (`"text": ""` )
35+ patternEmptyTextSp1 = []byte (`"text" : ""` )
36+ patternEmptyTextSp2 = []byte (`"text" :""` )
3137)
3238
3339// SessionContext 粘性会话上下文,用于区分不同来源的请求。
@@ -233,15 +239,22 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
233239 bytes .Contains (body , patternThinkingField ) ||
234240 bytes .Contains (body , patternThinkingFieldSpaced )
235241
236- // Also check for empty content arrays that need fixing.
242+ // Also check for empty content arrays and empty text blocks that need fixing.
237243 // Note: This is a heuristic check; the actual empty content handling is done below.
238244 hasEmptyContent := bytes .Contains (body , patternEmptyContent ) ||
239245 bytes .Contains (body , patternEmptyContentSpaced ) ||
240246 bytes .Contains (body , patternEmptyContentSp1 ) ||
241247 bytes .Contains (body , patternEmptyContentSp2 )
242248
249+ // Check for empty text blocks: {"type":"text","text":""}
250+ // These cause upstream 400: "text content blocks must be non-empty"
251+ hasEmptyTextBlock := bytes .Contains (body , patternEmptyText ) ||
252+ bytes .Contains (body , patternEmptyTextSpaced ) ||
253+ bytes .Contains (body , patternEmptyTextSp1 ) ||
254+ bytes .Contains (body , patternEmptyTextSp2 )
255+
243256 // Fast path: nothing to process
244- if ! hasThinkingContent && ! hasEmptyContent {
257+ if ! hasThinkingContent && ! hasEmptyContent && ! hasEmptyTextBlock {
245258 return body
246259 }
247260
@@ -260,7 +273,7 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
260273 bytes .Contains (body , patternTypeRedactedThinking ) ||
261274 bytes .Contains (body , patternTypeRedactedSpaced ) ||
262275 bytes .Contains (body , patternThinkingFieldSpaced )
263- if ! hasEmptyContent && ! containsThinkingBlocks {
276+ if ! hasEmptyContent && ! hasEmptyTextBlock && ! containsThinkingBlocks {
264277 if topThinking := gjson .Get (jsonStr , "thinking" ); topThinking .Exists () {
265278 if out , err := sjson .DeleteBytes (body , "thinking" ); err == nil {
266279 out = removeThinkingDependentContextStrategies (out )
@@ -320,6 +333,16 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
320333
321334 blockType , _ := blockMap ["type" ].(string )
322335
336+ // Strip empty text blocks: {"type":"text","text":""}
337+ // Upstream rejects these with 400: "text content blocks must be non-empty"
338+ if blockType == "text" {
339+ if txt , _ := blockMap ["text" ].(string ); txt == "" {
340+ modifiedThisMsg = true
341+ ensureNewContent (bi )
342+ continue
343+ }
344+ }
345+
323346 // Convert thinking blocks to text (preserve content) and drop redacted_thinking.
324347 switch blockType {
325348 case "thinking" :
0 commit comments