Merge branch 'IanShaw027/main'

This commit is contained in:
shaw
2026-01-05 11:20:28 +08:00
4 changed files with 58 additions and 24 deletions

View File

@@ -574,7 +574,17 @@ func isSignatureRelatedError(respBody []byte) bool {
} }
// Keep this intentionally broad: different upstreams may use "signature" or "thought_signature". // Keep this intentionally broad: different upstreams may use "signature" or "thought_signature".
return strings.Contains(msg, "thought_signature") || strings.Contains(msg, "signature") if strings.Contains(msg, "thought_signature") || strings.Contains(msg, "signature") {
return true
}
// Also detect thinking block structural errors:
// "Expected `thinking` or `redacted_thinking`, but found `text`"
if strings.Contains(msg, "expected") && (strings.Contains(msg, "thinking") || strings.Contains(msg, "redacted_thinking")) {
return true
}
return false
} }
func extractAntigravityErrorMessage(body []byte) string { func extractAntigravityErrorMessage(body []byte) string {

View File

@@ -99,13 +99,22 @@ func FilterThinkingBlocks(body []byte) []byte {
// - Remove `redacted_thinking` blocks (cannot be converted to text). // - Remove `redacted_thinking` blocks (cannot be converted to text).
// - Ensure no message ends up with empty content. // - Ensure no message ends up with empty content.
func FilterThinkingBlocksForRetry(body []byte) []byte { func FilterThinkingBlocksForRetry(body []byte) []byte {
// Fast path: check for presence of thinking-related keys in messages or top-level thinking config. hasThinkingContent := bytes.Contains(body, []byte(`"type":"thinking"`)) ||
if !bytes.Contains(body, []byte(`"type":"thinking"`)) && bytes.Contains(body, []byte(`"type": "thinking"`)) ||
!bytes.Contains(body, []byte(`"type": "thinking"`)) && bytes.Contains(body, []byte(`"type":"redacted_thinking"`)) ||
!bytes.Contains(body, []byte(`"type":"redacted_thinking"`)) && bytes.Contains(body, []byte(`"type": "redacted_thinking"`)) ||
!bytes.Contains(body, []byte(`"type": "redacted_thinking"`)) && bytes.Contains(body, []byte(`"thinking":`)) ||
!bytes.Contains(body, []byte(`"thinking":`)) && bytes.Contains(body, []byte(`"thinking" :`))
!bytes.Contains(body, []byte(`"thinking" :`)) {
// Also check for empty content arrays that need fixing.
// Note: This is a heuristic check; the actual empty content handling is done below.
hasEmptyContent := bytes.Contains(body, []byte(`"content":[]`)) ||
bytes.Contains(body, []byte(`"content": []`)) ||
bytes.Contains(body, []byte(`"content" : []`)) ||
bytes.Contains(body, []byte(`"content" :[]`))
// Fast path: nothing to process
if !hasThinkingContent && !hasEmptyContent {
return body return body
} }
@@ -195,20 +204,20 @@ func FilterThinkingBlocksForRetry(body []byte) []byte {
newContent = append(newContent, block) newContent = append(newContent, block)
} }
if modifiedThisMsg { // Handle empty content: either from filtering or originally empty
if len(newContent) == 0 {
modified = true modified = true
// Handle empty content after filtering placeholder := "(content removed)"
if len(newContent) == 0 { if role == "assistant" {
// Always add a placeholder to avoid upstream "non-empty content" errors. placeholder = "(assistant content removed)"
placeholder := "(content removed)"
if role == "assistant" {
placeholder = "(assistant content removed)"
}
newContent = append(newContent, map[string]any{
"type": "text",
"text": placeholder,
})
} }
newContent = append(newContent, map[string]any{
"type": "text",
"text": placeholder,
})
msgMap["content"] = newContent
} else if modifiedThisMsg {
modified = true
msgMap["content"] = newContent msgMap["content"] = newContent
} }
newMessages = append(newMessages, msgMap) newMessages = append(newMessages, msgMap)

View File

@@ -91,6 +91,11 @@ services:
- GEMINI_OAUTH_CLIENT_SECRET=${GEMINI_OAUTH_CLIENT_SECRET:-} - GEMINI_OAUTH_CLIENT_SECRET=${GEMINI_OAUTH_CLIENT_SECRET:-}
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-} - GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-} - GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
# =======================================================================
# Security Configuration
# =======================================================================
- SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS=${SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS:-}
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy

View File

@@ -1522,7 +1522,7 @@
</ul> </ul>
<div class="mt-2 flex flex-wrap gap-2"> <div class="mt-2 flex flex-wrap gap-2">
<a <a
href="https://gemini.google.com/faq#location" href="https://policies.google.com/terms"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400" class="text-sm text-blue-600 hover:underline dark:text-blue-400"
@@ -1531,7 +1531,16 @@
</a> </a>
<span class="text-gray-400">·</span> <span class="text-gray-400">·</span>
<a <a
href="https://gemini.google.com" href="https://policies.google.com/country-association-form"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
修改归属地
</a>
<span class="text-gray-400">·</span>
<a
href="https://gemini.google.com/gems/create?hl=en-US&pli=1"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400" class="text-sm text-blue-600 hover:underline dark:text-blue-400"
@@ -1869,8 +1878,9 @@ const geminiHelpLinks = {
apiKey: 'https://aistudio.google.com/app/apikey', apiKey: 'https://aistudio.google.com/app/apikey',
aiStudioPricing: 'https://ai.google.dev/pricing', aiStudioPricing: 'https://ai.google.dev/pricing',
gcpProject: 'https://console.cloud.google.com/welcome/new', gcpProject: 'https://console.cloud.google.com/welcome/new',
geminiWebActivation: 'https://gemini.google.com/gems/create?hl=en-US', geminiWebActivation: 'https://gemini.google.com/gems/create?hl=en-US&pli=1',
countryCheck: 'https://policies.google.com/country-association-form' countryCheck: 'https://policies.google.com/terms',
countryChange: 'https://policies.google.com/country-association-form'
} }
// Computed: current preset mappings based on platform // Computed: current preset mappings based on platform