fix: handle unsigned thinking blocks in tool loops (#120)
When Claude Code strips thinking signatures it doesn't recognize, the proxy would drop unsigned thinking blocks, causing the error "Expected thinking but found text". This fix detects unsigned thinking blocks and triggers recovery to close the tool loop. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
reorderAssistantContent,
|
||||
filterUnsignedThinkingBlocks,
|
||||
hasGeminiHistory,
|
||||
hasUnsignedThinkingBlocks,
|
||||
needsThinkingRecovery,
|
||||
closeToolLoopForThinking
|
||||
} from './thinking-utils.js';
|
||||
@@ -87,10 +88,11 @@ export function convertAnthropicToGoogle(anthropicRequest) {
|
||||
processedMessages = closeToolLoopForThinking(messages, 'gemini');
|
||||
}
|
||||
|
||||
// For Claude: apply recovery only for cross-model (Gemini→Claude) switch
|
||||
// Detected by checking if history has Gemini-style tool_use with thoughtSignature
|
||||
if (isClaudeModel && isThinking && hasGeminiHistory(messages) && needsThinkingRecovery(messages)) {
|
||||
logger.debug('[RequestConverter] Applying thinking recovery for Claude (cross-model from Gemini)');
|
||||
// For Claude: apply recovery for cross-model (Gemini→Claude) or unsigned thinking blocks
|
||||
// Unsigned thinking blocks occur when Claude Code strips signatures it doesn't understand
|
||||
const needsClaudeRecovery = hasGeminiHistory(messages) || hasUnsignedThinkingBlocks(messages);
|
||||
if (isClaudeModel && isThinking && needsClaudeRecovery && needsThinkingRecovery(messages)) {
|
||||
logger.debug('[RequestConverter] Applying thinking recovery for Claude');
|
||||
processedMessages = closeToolLoopForThinking(messages, 'claude');
|
||||
}
|
||||
|
||||
|
||||
@@ -112,3 +112,11 @@ export function getCachedSignatureFamily(signature) {
|
||||
export function getThinkingCacheSize() {
|
||||
return thinkingSignatureCache.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all entries from the thinking signature cache.
|
||||
* Used for testing cold cache scenarios.
|
||||
*/
|
||||
export function clearThinkingSignatureCache() {
|
||||
thinkingSignatureCache.clear();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,22 @@ export function hasGeminiHistory(messages) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if conversation has unsigned thinking blocks that will be dropped.
|
||||
* These cause "Expected thinking but found text" errors.
|
||||
* @param {Array<Object>} messages - Array of messages
|
||||
* @returns {boolean} True if any assistant message has unsigned thinking blocks
|
||||
*/
|
||||
export function hasUnsignedThinkingBlocks(messages) {
|
||||
return messages.some(msg => {
|
||||
if (msg.role !== 'assistant' && msg.role !== 'model') return false;
|
||||
if (!Array.isArray(msg.content)) return false;
|
||||
return msg.content.some(block =>
|
||||
isThinkingPart(block) && !hasValidSignature(block)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a thinking part by keeping only allowed fields
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@ const __dirname = path.dirname(__filename);
|
||||
import { forceRefresh } from './auth/token-extractor.js';
|
||||
import { REQUEST_BODY_LIMIT } from './constants.js';
|
||||
import { AccountManager } from './account-manager/index.js';
|
||||
import { clearThinkingSignatureCache } from './format/signature-cache.js';
|
||||
import { formatDuration } from './utils/helpers.js';
|
||||
import { logger } from './utils/logger.js';
|
||||
import usageStats from './modules/usage-stats.js';
|
||||
@@ -161,6 +162,16 @@ app.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test endpoint - Clear thinking signature cache
|
||||
* Used for testing cold cache scenarios in cross-model tests
|
||||
*/
|
||||
app.post('/test/clear-signature-cache', (req, res) => {
|
||||
clearThinkingSignatureCache();
|
||||
logger.debug('[Test] Cleared thinking signature cache');
|
||||
res.json({ success: true, message: 'Thinking signature cache cleared' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Health check endpoint - Detailed status
|
||||
* Returns status of all accounts including rate limits and model quotas
|
||||
|
||||
Reference in New Issue
Block a user