Preserve valid thinking blocks during recovery
Instead of stripping all thinking blocks during thinking recovery, now only strips invalid or incompatible blocks. Uses signature cache to validate family compatibility for cross-model fallback scenarios. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -85,7 +85,7 @@ export function convertAnthropicToGoogle(anthropicRequest) {
|
|||||||
|
|
||||||
if (isThinking && targetFamily && needsThinkingRecovery(messages, targetFamily)) {
|
if (isThinking && targetFamily && needsThinkingRecovery(messages, targetFamily)) {
|
||||||
logger.debug(`[RequestConverter] Applying thinking recovery for ${targetFamily}`);
|
logger.debug(`[RequestConverter] Applying thinking recovery for ${targetFamily}`);
|
||||||
processedMessages = closeToolLoopForThinking(messages);
|
processedMessages = closeToolLoopForThinking(messages, targetFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert messages to contents, then filter unsigned thinking blocks
|
// Convert messages to contents, then filter unsigned thinking blocks
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { MIN_SIGNATURE_LENGTH } from '../constants.js';
|
import { MIN_SIGNATURE_LENGTH } from '../constants.js';
|
||||||
|
import { getCachedSignatureFamily } from './signature-cache.js';
|
||||||
import { logger } from '../utils/logger.js';
|
import { logger } from '../utils/logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -407,18 +408,40 @@ export function needsThinkingRecovery(messages, targetFamily = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strip all thinking blocks from messages.
|
* Strip invalid or incompatible thinking blocks from messages.
|
||||||
* Used before injecting synthetic messages for recovery.
|
* Used before injecting synthetic messages for recovery.
|
||||||
|
* Keeps valid thinking blocks to preserve context from previous turns.
|
||||||
*
|
*
|
||||||
* @param {Array<Object>} messages - Array of messages
|
* @param {Array<Object>} messages - Array of messages
|
||||||
* @returns {Array<Object>} Messages with all thinking blocks removed
|
* @param {string} targetFamily - Target model family ('claude' or 'gemini')
|
||||||
|
* @returns {Array<Object>} Messages with invalid thinking blocks removed
|
||||||
*/
|
*/
|
||||||
function stripAllThinkingBlocks(messages) {
|
function stripInvalidThinkingBlocks(messages, targetFamily = null) {
|
||||||
return messages.map(msg => {
|
return messages.map(msg => {
|
||||||
const content = msg.content || msg.parts;
|
const content = msg.content || msg.parts;
|
||||||
if (!Array.isArray(content)) return msg;
|
if (!Array.isArray(content)) return msg;
|
||||||
|
|
||||||
const filtered = content.filter(block => !isThinkingPart(block));
|
const filtered = content.filter(block => {
|
||||||
|
// Keep non-thinking blocks
|
||||||
|
if (!isThinkingPart(block)) return true;
|
||||||
|
|
||||||
|
// Check generic validity (has signature of sufficient length)
|
||||||
|
if (!hasValidSignature(block)) return false;
|
||||||
|
|
||||||
|
// Check family compatibility if targetFamily is provided
|
||||||
|
if (targetFamily) {
|
||||||
|
const signature = block.thought === true ? block.thoughtSignature : block.signature;
|
||||||
|
const signatureFamily = getCachedSignatureFamily(signature);
|
||||||
|
|
||||||
|
// Strict validation: If we don't know the family (cache miss) or it doesn't match,
|
||||||
|
// we drop it. We don't assume validity for unknown signatures.
|
||||||
|
if (signatureFamily !== targetFamily) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
if (msg.content) {
|
if (msg.content) {
|
||||||
return { ...msg, content: filtered.length > 0 ? filtered : [{ type: 'text', text: '.' }] };
|
return { ...msg, content: filtered.length > 0 ? filtered : [{ type: 'text', text: '.' }] };
|
||||||
@@ -439,16 +462,17 @@ function stripAllThinkingBlocks(messages) {
|
|||||||
* loop and allow the model to continue.
|
* loop and allow the model to continue.
|
||||||
*
|
*
|
||||||
* @param {Array<Object>} messages - Array of messages
|
* @param {Array<Object>} messages - Array of messages
|
||||||
|
* @param {string} targetFamily - Target model family ('claude' or 'gemini')
|
||||||
* @returns {Array<Object>} Modified messages with synthetic messages injected
|
* @returns {Array<Object>} Modified messages with synthetic messages injected
|
||||||
*/
|
*/
|
||||||
export function closeToolLoopForThinking(messages) {
|
export function closeToolLoopForThinking(messages, targetFamily = null) {
|
||||||
const state = analyzeConversationState(messages);
|
const state = analyzeConversationState(messages);
|
||||||
|
|
||||||
// Handle neither tool loop nor interrupted tool
|
// Handle neither tool loop nor interrupted tool
|
||||||
if (!state.inToolLoop && !state.interruptedTool) return messages;
|
if (!state.inToolLoop && !state.interruptedTool) return messages;
|
||||||
|
|
||||||
// Strip all thinking blocks
|
// Strip only invalid/incompatible thinking blocks (keep valid ones)
|
||||||
let modified = stripAllThinkingBlocks(messages);
|
let modified = stripInvalidThinkingBlocks(messages, targetFamily);
|
||||||
|
|
||||||
if (state.interruptedTool) {
|
if (state.interruptedTool) {
|
||||||
// For interrupted tools: just strip thinking and add a synthetic assistant message
|
// For interrupted tools: just strip thinking and add a synthetic assistant message
|
||||||
|
|||||||
Reference in New Issue
Block a user