

















@@ -2321,7 +2321,109 @@ describe("wrapStreamFnSanitizeMalformedToolCalls", () => {
23212321]);
23222322});
232323232324-it("drops signed thinking turns when replay would expose inline sessions_spawn attachments", async () => {
2324+it("keeps signed thinking turns that reuse a mutable earlier tool id", async () => {
2325+const messages = [
2326+{
2327+role: "assistant",
2328+content: [{ type: "toolCall", id: "call_1", name: "read", arguments: {} }],
2329+},
2330+{
2331+role: "toolResult",
2332+toolCallId: "call_1",
2333+content: [{ type: "text", text: "mutable result" }],
2334+},
2335+{
2336+role: "assistant",
2337+content: [
2338+{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
2339+{ type: "toolUse", id: "call_1", name: "read", input: {} },
2340+],
2341+},
2342+{
2343+role: "toolResult",
2344+toolCallId: "call_1",
2345+content: [{ type: "text", text: "signed result" }],
2346+},
2347+{
2348+role: "user",
2349+content: [{ type: "text", text: "retry" }],
2350+},
2351+];
2352+const baseFn = vi.fn((_model, _context) =>
2353+createFakeStream({ events: [], resultMessage: { role: "assistant", content: [] } }),
2354+);
2355+2356+const wrapped = wrapStreamFnSanitizeMalformedToolCalls(baseFn as never, new Set(["read"]), {
2357+validateAnthropicTurns: true,
2358+preserveSignatures: true,
2359+dropThinkingBlocks: false,
2360+} as never);
2361+const stream = wrapped(
2362+{ api: "anthropic-messages" } as never,
2363+{ messages } as never,
2364+{} as never,
2365+) as FakeWrappedStream | Promise<FakeWrappedStream>;
2366+await Promise.resolve(stream);
2367+2368+expect(baseFn).toHaveBeenCalledTimes(1);
2369+const seenContext = firstBaseContext(baseFn);
2370+expect(seenContext.messages).toBe(messages);
2371+});
2372+2373+it("drops signed thinking reused ids when their real result is displaced", async () => {
2374+const firstAssistant = {
2375+role: "assistant",
2376+content: [{ type: "toolCall", id: "call_1", name: "read", arguments: {} }],
2377+};
2378+const firstResult = {
2379+role: "toolResult",
2380+toolCallId: "call_1",
2381+toolName: "read",
2382+content: [{ type: "text", text: "mutable result" }],
2383+};
2384+const userMessage = {
2385+role: "user",
2386+content: [{ type: "text", text: "retry" }],
2387+};
2388+const messages = [
2389+firstAssistant,
2390+firstResult,
2391+{
2392+role: "assistant",
2393+content: [
2394+{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
2395+{ type: "toolUse", id: "call_1", name: "read", input: {} },
2396+],
2397+},
2398+userMessage,
2399+{
2400+role: "toolResult",
2401+toolCallId: "call_1",
2402+content: [{ type: "text", text: "signed result" }],
2403+},
2404+];
2405+const baseFn = vi.fn((_model, _context) =>
2406+createFakeStream({ events: [], resultMessage: { role: "assistant", content: [] } }),
2407+);
2408+2409+const wrapped = wrapStreamFnSanitizeMalformedToolCalls(baseFn as never, new Set(["read"]), {
2410+validateAnthropicTurns: true,
2411+preserveSignatures: true,
2412+dropThinkingBlocks: false,
2413+} as never);
2414+const stream = wrapped(
2415+{ api: "anthropic-messages" } as never,
2416+{ messages } as never,
2417+{} as never,
2418+) as FakeWrappedStream | Promise<FakeWrappedStream>;
2419+await Promise.resolve(stream);
2420+2421+expect(baseFn).toHaveBeenCalledTimes(1);
2422+const seenContext = firstBaseContext(baseFn);
2423+expect(seenContext.messages).toEqual([firstAssistant, firstResult, userMessage]);
2424+});
2425+2426+it("drops signed thinking turns with inline sessions_spawn attachments when the result is missing", async () => {
23252427const attachmentContent = "SIGNED_THINKING_INLINE_ATTACHMENT";
23262428const messages = [
23272429{
@@ -2374,7 +2476,7 @@ describe("wrapStreamFnSanitizeMalformedToolCalls", () => {
23742476]);
23752477});
237624782377-it("drops signed thinking turns when replay would expose non-content attachment payload fields", async () => {
2479+it("drops signed thinking turns with non-content attachment payload fields when the result is missing", async () => {
23782480const attachmentContent = "SIGNED_THINKING_NESTED_ATTACHMENT";
23792481const messages = [
23802482{
@@ -2433,6 +2535,60 @@ describe("wrapStreamFnSanitizeMalformedToolCalls", () => {
24332535]);
24342536});
243525372538+it("keeps signed thinking turns with sessions_spawn attachments when the tool result is present", async () => {
2539+const attachmentContent = "SIGNED_THINKING_PAIRED_ATTACHMENT";
2540+const messages = [
2541+{
2542+role: "assistant",
2543+content: [
2544+{ type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
2545+{
2546+type: "toolUse",
2547+id: "call_1",
2548+name: "sessions_spawn",
2549+input: {
2550+task: "inspect attachment",
2551+attachments: [{ name: "snapshot.txt", content: attachmentContent }],
2552+},
2553+},
2554+],
2555+},
2556+{
2557+role: "toolResult",
2558+toolCallId: "call_1",
2559+toolName: "sessions_spawn",
2560+content: [{ type: "text", text: "done" }],
2561+},
2562+{
2563+role: "user",
2564+content: [{ type: "text", text: "retry" }],
2565+},
2566+];
2567+const baseFn = vi.fn((_model, _context) =>
2568+createFakeStream({ events: [], resultMessage: { role: "assistant", content: [] } }),
2569+);
2570+2571+const wrapped = wrapStreamFnSanitizeMalformedToolCalls(
2572+baseFn as never,
2573+new Set(["sessions_spawn"]),
2574+{
2575+validateAnthropicTurns: true,
2576+preserveSignatures: true,
2577+dropThinkingBlocks: false,
2578+} as never,
2579+);
2580+const stream = wrapped(
2581+{ api: "anthropic-messages" } as never,
2582+{ messages } as never,
2583+{} as never,
2584+) as FakeWrappedStream | Promise<FakeWrappedStream>;
2585+await Promise.resolve(stream);
2586+2587+expect(baseFn).toHaveBeenCalledTimes(1);
2588+const seenContext = firstBaseContext(baseFn);
2589+expect(seenContext.messages).toBe(messages);
2590+});
2591+24362592it("keeps mutable thinking turns outside anthropic replay-only preservation", async () => {
24372593const messages = [
24382594{
@@ -3044,10 +3200,6 @@ describe("wrapStreamFnSanitizeMalformedToolCalls", () => {
30443200messages: Array<{ role?: string; content?: unknown[] }>;
30453201};
30463202expect(seenContext.messages).toEqual([
3047-{
3048-role: "assistant",
3049-content: [{ type: "text", text: "[tool calls omitted]" }],
3050-},
30513203{
30523204role: "user",
30533205content: [{ type: "text", text: "retry" }],
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。