( conversations: NormalizedConversation[], resultNormalizers: ToolResultNormalizer[], )
| 821 | } |
| 822 | |
| 823 | function normalizeToolCalls( |
| 824 | conversations: NormalizedConversation[], |
| 825 | resultNormalizers: ToolResultNormalizer[], |
| 826 | ) { |
| 827 | // We normalize: |
| 828 | // - Tool call IDs (mapping from tooluse_rjaaFdJRRhqAZevU_1aBSA etc to toolcall_0, toolcall_1, etc) |
| 829 | // - Tool names (e.g., bash/powershell -> ${shell}) |
| 830 | // - Tool call results that may vary between execution environments |
| 831 | // This is so that we're not storing random or environment-specific data in snapshots, and so we can |
| 832 | // still match cached responses even if these details change. |
| 833 | for (const conv of conversations) { |
| 834 | const idMap = new Map<string, string>(); |
| 835 | const precedingMessages: NormalizedMessage[] = []; |
| 836 | let counter = 0; |
| 837 | for (const msg of conv.messages) { |
| 838 | for (const tc of msg.tool_calls ?? []) { |
| 839 | // Normalize ID in tool calls |
| 840 | idMap.set(tc.id, (tc.id = idMap.get(tc.id) ?? `toolcall_${counter++}`)); |
| 841 | |
| 842 | // Normalize name |
| 843 | const originalToolName = tc.function?.name; |
| 844 | const normalizedToolName = |
| 845 | originalToolName && normalizedToolNames[originalToolName]; |
| 846 | if (normalizedToolName) { |
| 847 | tc.function!.name = normalizedToolName; |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | if (msg.role === "tool" && msg.tool_call_id) { |
| 852 | // Normalize ID in tool results |
| 853 | msg.tool_call_id = idMap.get(msg.tool_call_id) ?? msg.tool_call_id; |
| 854 | |
| 855 | // Normalize result |
| 856 | if (msg.content) { |
| 857 | const precedingToolCall = precedingMessages |
| 858 | .flatMap((m) => m.tool_calls ?? []) |
| 859 | .find((tc) => tc.id === msg.tool_call_id); |
| 860 | if (precedingToolCall) { |
| 861 | for (const normalizer of resultNormalizers) { |
| 862 | if ( |
| 863 | precedingToolCall.function?.name === normalizer.toolName || |
| 864 | normalizer.toolName === "*" |
| 865 | ) { |
| 866 | msg.content = normalizer.normalizer(msg.content); |
| 867 | } |
| 868 | } |
| 869 | } |
| 870 | } |
| 871 | } |
| 872 | |
| 873 | precedingMessages.push(msg); |
| 874 | } |
| 875 | } |
| 876 | } |
| 877 | |
| 878 | function normalizeToolResultOrder(conversations: NormalizedConversation[]) { |
| 879 | for (const conv of conversations) { |
no test coverage detected
searching dependent graphs…