/**
 * Normalize tool call ID for cross-provider compatibility.
 * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
 * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
 */
export function transformMessages(messages, model, normalizeToolCallId) {
    // Build a map of original tool call IDs to normalized IDs
    const toolCallIdMap = new Map();
    // First pass: transform messages (thinking blocks, tool call ID normalization)
    const transformed = messages.map((msg) => {
        // User messages pass through unchanged
        if (msg.role === 'user') {
            return msg;
        }
        // Handle toolResult messages - normalize toolCallId if we have a mapping
        if (msg.role === 'toolResult') {
            const normalizedId = toolCallIdMap.get(msg.toolCallId);
            if (normalizedId && normalizedId !== msg.toolCallId) {
                return { ...msg, toolCallId: normalizedId };
            }
            return msg;
        }
        // Assistant messages need transformation check
        if (msg.role === 'assistant') {
            const assistantMsg = msg;
            const isSameModel = assistantMsg.provider === model.provider &&
                assistantMsg.api === model.api &&
                assistantMsg.model === model.id;
            const transformedContent = assistantMsg.content.flatMap((block) => {
                if (block.type === 'thinking') {
                    // Redacted thinking is opaque encrypted content, only valid for the same model.
                    // Drop it for cross-model to avoid API errors.
                    if (block.redacted) {
                        return isSameModel ? block : [];
                    }
                    // For same model: keep thinking blocks with signatures (needed for replay)
                    // even if the thinking text is empty (OpenAI encrypted reasoning)
                    if (isSameModel && block.thinkingSignature)
                        return block;
                    // Skip empty thinking blocks, convert others to plain text
                    if (!block.thinking || block.thinking.trim() === '')
                        return [];
                    if (isSameModel)
                        return block;
                    return {
                        type: 'text',
                        text: block.thinking
                    };
                }
                if (block.type === 'text') {
                    if (isSameModel)
                        return block;
                    return {
                        type: 'text',
                        text: block.text
                    };
                }
                if (block.type === 'toolCall') {
                    const toolCall = block;
                    let normalizedToolCall = toolCall;
                    if (!isSameModel && toolCall.thoughtSignature) {
                        normalizedToolCall = { ...toolCall };
                        delete normalizedToolCall.thoughtSignature;
                    }
                    if (!isSameModel && normalizeToolCallId) {
                        const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
                        if (normalizedId !== toolCall.id) {
                            toolCallIdMap.set(toolCall.id, normalizedId);
                            normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
                        }
                    }
                    return normalizedToolCall;
                }
                return block;
            });
            return {
                ...assistantMsg,
                content: transformedContent
            };
        }
        return msg;
    });
    // Second pass: insert synthetic empty tool results for orphaned tool calls
    // This preserves thinking signatures and satisfies API requirements
    const result = [];
    let pendingToolCalls = [];
    let existingToolResultIds = new Set();
    for (let i = 0; i < transformed.length; i++) {
        const msg = transformed[i];
        if (msg.role === 'assistant') {
            // If we have pending orphaned tool calls from a previous assistant, insert synthetic results now
            if (pendingToolCalls.length > 0) {
                for (const tc of pendingToolCalls) {
                    if (!existingToolResultIds.has(tc.id)) {
                        result.push({
                            role: 'toolResult',
                            toolCallId: tc.id,
                            toolName: tc.name,
                            content: [{ type: 'text', text: 'No result provided' }],
                            isError: true,
                            timestamp: Date.now()
                        });
                    }
                }
                pendingToolCalls = [];
                existingToolResultIds = new Set();
            }
            // Skip errored/aborted assistant messages entirely.
            // These are incomplete turns that shouldn't be replayed:
            // - May have partial content (reasoning without message, incomplete tool calls)
            // - Replaying them can cause API errors (e.g., OpenAI "reasoning without following item")
            // - The model should retry from the last valid state
            const assistantMsg = msg;
            if (assistantMsg.stopReason === 'error' || assistantMsg.stopReason === 'aborted') {
                continue;
            }
            // Track tool calls from this assistant message
            const toolCalls = assistantMsg.content.filter((b) => b.type === 'toolCall');
            if (toolCalls.length > 0) {
                pendingToolCalls = toolCalls;
                existingToolResultIds = new Set();
            }
            result.push(msg);
        }
        else if (msg.role === 'toolResult') {
            existingToolResultIds.add(msg.toolCallId);
            result.push(msg);
        }
        else if (msg.role === 'user') {
            // User message interrupts tool flow - insert synthetic results for orphaned calls
            if (pendingToolCalls.length > 0) {
                for (const tc of pendingToolCalls) {
                    if (!existingToolResultIds.has(tc.id)) {
                        result.push({
                            role: 'toolResult',
                            toolCallId: tc.id,
                            toolName: tc.name,
                            content: [{ type: 'text', text: 'No result provided' }],
                            isError: true,
                            timestamp: Date.now()
                        });
                    }
                }
                pendingToolCalls = [];
                existingToolResultIds = new Set();
            }
            result.push(msg);
        }
        else {
            result.push(msg);
        }
    }
    return result;
}
//# sourceMappingURL=transform-messages.js.map