Просмотр исходного кода

fix(opencode): preserve signed thinking during anthropic reorder (#30182)

Aiden Cline 3 месяцев назад
Родитель
Сommit
42173bca4b

+ 12 - 0
packages/opencode/src/provider/transform.ts

@@ -230,6 +230,18 @@ function normalizeMessages(
       const parts = msg.content
       const first = parts.findIndex((part) => part.type === "tool-call")
       if (first === -1) return [msg]
+      // Anthropic signs thinking blocks, so moving them during replay invalidates the request.
+      if (
+        parts
+          .slice(first)
+          .some(
+            (part) =>
+              part.type === "reasoning" &&
+              (part.providerOptions?.anthropic?.signature != null ||
+                part.providerOptions?.anthropic?.redactedData != null),
+          )
+      )
+        return [msg]
       if (!parts.slice(first).some((part) => part.type !== "tool-call")) return [msg]
       return [
         { ...msg, content: parts.filter((part) => part.type !== "tool-call") },

+ 47 - 0
packages/opencode/test/provider/transform.test.ts

@@ -1680,6 +1680,25 @@ describe("ProviderTransform.message - anthropic empty content filtering", () =>
     ])
   })
 
+  test("leaves signed anthropic reasoning after tool calls unchanged", () => {
+    const msgs = [
+      {
+        role: "assistant",
+        content: [
+          { type: "reasoning", text: "First thought", providerOptions: { anthropic: { signature: "sig-1" } } },
+          { type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
+          { type: "reasoning", text: "Second thought", providerOptions: { anthropic: { signature: "sig-2" } } },
+          { type: "tool-call", toolCallId: "toolu_2", toolName: "glob", input: { pattern: "**/*.pdf" } },
+        ],
+      },
+    ] as any[]
+
+    const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[]
+
+    expect(result).toHaveLength(1)
+    expect(result[0].content).toMatchObject(msgs[0].content)
+  })
+
   test("splits vertex anthropic assistant messages when text trails tool calls", () => {
     const model = {
       ...anthropicModel,
@@ -1717,6 +1736,34 @@ describe("ProviderTransform.message - anthropic empty content filtering", () =>
       ],
     })
   })
+
+  test("leaves redacted vertex anthropic reasoning after tool calls unchanged", () => {
+    const model = {
+      ...anthropicModel,
+      providerID: "google-vertex-anthropic",
+      api: {
+        id: "claude-sonnet-4@20250514",
+        url: "https://us-central1-aiplatform.googleapis.com",
+        npm: "@ai-sdk/google-vertex/anthropic",
+      },
+    }
+
+    const msgs = [
+      {
+        role: "assistant",
+        content: [
+          { type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
+          { type: "reasoning", text: "", providerOptions: { anthropic: { redactedData: "redacted-1" } } },
+          { type: "tool-call", toolCallId: "toolu_2", toolName: "glob", input: { pattern: "**/*.pdf" } },
+        ],
+      },
+    ] as any[]
+
+    const result = ProviderTransform.message(msgs, model, {}) as any[]
+
+    expect(result).toHaveLength(1)
+    expect(result[0].content).toMatchObject(msgs[0].content)
+  })
 })
 
 describe("ProviderTransform.message - strip openai metadata when store=false", () => {