























@@ -2007,4 +2007,181 @@ describe("sanitizeSessionHistory", () => {
20072007const types = getAssistantContentTypes(result);
20082008expect(types).toContain("thinking");
20092009});
2010+2011+it("strips unsigned thinking blocks for anthropic api even when preserveSignatures is false", async () => {
2012+setNonGoogleModelApi();
2013+2014+const messages = castAgentMessages([
2015+makeUserMessage("analyze"),
2016+makeAssistantMessage([
2017+{ type: "thinking", thinking: "no signature" },
2018+{ type: "thinking", thinking: "empty sig", thinkingSignature: "" },
2019+{ type: "thinking", thinking: "blank sig", thinkingSignature: " " },
2020+{ type: "thinking", thinking: "valid", thinkingSignature: "sig_abc" },
2021+{ type: "text", text: "result" },
2022+]),
2023+]);
2024+2025+const result = await sanitizeAnthropicHistory({
2026+ messages,
2027+modelApi: "anthropic-messages",
2028+preserveLatestAssistantThinking: false,
2029+policy: {
2030+sanitizeMode: "full",
2031+sanitizeToolCallIds: true,
2032+toolCallIdMode: "strict",
2033+preserveNativeAnthropicToolUseIds: false,
2034+repairToolUseResultPairing: true,
2035+preserveSignatures: false,
2036+sanitizeThinkingSignatures: false,
2037+dropThinkingBlocks: false,
2038+applyGoogleTurnOrdering: false,
2039+validateGeminiTurns: false,
2040+validateAnthropicTurns: true,
2041+allowSyntheticToolResults: false,
2042+},
2043+});
2044+2045+const assistant = getAssistantMessage(result);
2046+const content = assistant.content;
2047+const thinkingBlocks = content.filter(
2048+(b: { type: string }) => b.type === "thinking" || b.type === "redacted_thinking",
2049+);
2050+const textBlocks = content.filter((b: { type: string }) => b.type === "text");
2051+2052+// Only the valid signed thinking block should survive
2053+expect(thinkingBlocks).toHaveLength(1);
2054+expect((thinkingBlocks[0] as { thinkingSignature?: string }).thinkingSignature).toBe("sig_abc");
2055+expect(textBlocks).toHaveLength(1);
2056+expect((textBlocks[0] as { text?: string }).text).toBe("result");
2057+});
2058+2059+it("preserves unsigned thinking blocks for kimi coding with anthropic-messages transport", async () => {
2060+setNonGoogleModelApi();
2061+2062+const messages = castAgentMessages([
2063+makeUserMessage("analyze"),
2064+makeAssistantMessage([
2065+{ type: "thinking", thinking: "unsigned kimi reasoning" },
2066+{ type: "text", text: "result" },
2067+]),
2068+]);
2069+2070+// Kimi uses anthropic-messages transport but does not require signed thinking.
2071+// Its provider-level preserveSignatures is false and should stay gated.
2072+const result = await sanitizeSessionHistory({
2073+ messages,
2074+modelApi: "anthropic-messages",
2075+provider: "kimi",
2076+modelId: "kimi-for-coding",
2077+sessionManager: makeMockSessionManager(),
2078+sessionId: TEST_SESSION_ID,
2079+policy: {
2080+sanitizeMode: "full",
2081+sanitizeToolCallIds: true,
2082+toolCallIdMode: "strict",
2083+preserveNativeAnthropicToolUseIds: false,
2084+repairToolUseResultPairing: true,
2085+preserveSignatures: false,
2086+sanitizeThinkingSignatures: false,
2087+dropThinkingBlocks: false,
2088+dropReasoningFromHistory: false,
2089+applyGoogleTurnOrdering: false,
2090+validateGeminiTurns: false,
2091+validateAnthropicTurns: false,
2092+allowSyntheticToolResults: false,
2093+},
2094+});
2095+2096+const assistant = getAssistantMessage(result);
2097+const thinkingBlocks = assistant.content.filter((b: { type: string }) => b.type === "thinking");
2098+expect(thinkingBlocks).toHaveLength(1);
2099+expect((thinkingBlocks[0] as { thinking?: string }).thinking).toBe("unsigned kimi reasoning");
2100+});
2101+2102+it("preserves unsigned thinking blocks for github copilot claude with anthropic-messages transport", async () => {
2103+setNonGoogleModelApi();
2104+2105+const messages = castAgentMessages([
2106+makeUserMessage("analyze"),
2107+makeAssistantMessage([
2108+{ type: "thinking", thinking: "unsigned copilot reasoning" },
2109+{ type: "text", text: "result" },
2110+]),
2111+]);
2112+2113+// GitHub Copilot Claude uses anthropic-messages transport but does not
2114+// require signed thinking. Its provider-level preserveSignatures is false.
2115+const result = await sanitizeSessionHistory({
2116+ messages,
2117+modelApi: "anthropic-messages",
2118+provider: "github-copilot",
2119+modelId: "claude-opus-4.6",
2120+sessionManager: makeMockSessionManager(),
2121+sessionId: TEST_SESSION_ID,
2122+policy: {
2123+sanitizeMode: "full",
2124+sanitizeToolCallIds: true,
2125+toolCallIdMode: "strict",
2126+preserveNativeAnthropicToolUseIds: false,
2127+repairToolUseResultPairing: true,
2128+preserveSignatures: false,
2129+sanitizeThinkingSignatures: false,
2130+dropThinkingBlocks: false,
2131+dropReasoningFromHistory: false,
2132+applyGoogleTurnOrdering: false,
2133+validateGeminiTurns: false,
2134+validateAnthropicTurns: false,
2135+allowSyntheticToolResults: false,
2136+},
2137+});
2138+2139+const assistant = getAssistantMessage(result);
2140+const thinkingBlocks = assistant.content.filter((b: { type: string }) => b.type === "thinking");
2141+expect(thinkingBlocks).toHaveLength(1);
2142+expect((thinkingBlocks[0] as { thinking?: string }).thinking).toBe(
2143+"unsigned copilot reasoning",
2144+);
2145+});
2146+2147+it("strips unsigned thinking for bedrock-converse-stream even when preserveSignatures is false", async () => {
2148+setNonGoogleModelApi();
2149+2150+const messages = castAgentMessages([
2151+makeUserMessage("analyze"),
2152+makeAssistantMessage([
2153+{ type: "thinking", thinking: "no sig" },
2154+{ type: "thinking", thinking: "signed", thinkingSignature: "sig_bedrock" },
2155+{ type: "text", text: "done" },
2156+]),
2157+]);
2158+2159+const result = await sanitizeAnthropicHistory({
2160+ messages,
2161+provider: "amazon-bedrock",
2162+modelApi: "bedrock-converse-stream",
2163+preserveLatestAssistantThinking: false,
2164+policy: {
2165+sanitizeMode: "full",
2166+sanitizeToolCallIds: true,
2167+toolCallIdMode: "strict",
2168+preserveNativeAnthropicToolUseIds: false,
2169+repairToolUseResultPairing: true,
2170+preserveSignatures: false,
2171+sanitizeThinkingSignatures: false,
2172+dropThinkingBlocks: false,
2173+applyGoogleTurnOrdering: false,
2174+validateGeminiTurns: false,
2175+validateAnthropicTurns: true,
2176+allowSyntheticToolResults: false,
2177+},
2178+});
2179+2180+const assistant = getAssistantMessage(result);
2181+const thinkingBlocks = assistant.content.filter((b: { type: string }) => b.type === "thinking");
2182+expect(thinkingBlocks).toHaveLength(1);
2183+expect((thinkingBlocks[0] as { thinkingSignature?: string }).thinkingSignature).toBe(
2184+"sig_bedrock",
2185+);
2186+});
20102187});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。