opencode-anthropic-fix 0.0.41 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.mjs +103 -6
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -2973,7 +2973,10 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
2973
2973
  },
2974
2974
  ...tail,
2975
2975
  ];
2976
- parsedBody.messages = trimmed;
2976
+ // Repair any orphaned tool_use blocks created by the trim boundary
2977
+ // (e.g. first 2 messages include an assistant tool_use whose
2978
+ // tool_result was in a trimmed middle message).
2979
+ parsedBody.messages = repairOrphanedToolUseBlocks(trimmed);
2977
2980
  requestInit.body = JSON.stringify(parsedBody);
2978
2981
  requestInit._reactiveCompactAttempted = true;
2979
2982
  // Retry with trimmed messages (decrement attempt to not consume account slot)
@@ -4739,6 +4742,94 @@ function computeBillingCacheHash(firstUserMessage, version) {
4739
4742
  * @param {Array} messages — The messages array from the parsed request body
4740
4743
  * @returns {Array} — Filtered messages array
4741
4744
  */
4745
+
4746
+ /**
4747
+ * Repair orphaned tool_use blocks in the message array.
4748
+ *
4749
+ * The Anthropic API requires that every assistant message containing `tool_use`
4750
+ * blocks is immediately followed by a user message with `tool_result` blocks
4751
+ * for each tool_use ID. When OpenCode crashes or hangs mid-tool-execution, the
4752
+ * conversation may be persisted with assistant tool_use blocks that lack
4753
+ * corresponding tool_result responses, causing:
4754
+ *
4755
+ * "messages.N: `tool_use` ids were found without `tool_result` blocks
4756
+ * immediately after: toolu_XXXXX"
4757
+ *
4758
+ * This function scans the entire message array and inserts synthetic
4759
+ * tool_result user messages wherever they are missing.
4760
+ *
4761
+ * @param {Array} messages — The messages array from the parsed request body
4762
+ * @returns {Array} — Repaired messages array
4763
+ */
4764
+ function repairOrphanedToolUseBlocks(messages) {
4765
+ if (!Array.isArray(messages) || messages.length === 0) return messages;
4766
+
4767
+ const repaired = [];
4768
+
4769
+ for (let i = 0; i < messages.length; i++) {
4770
+ const msg = messages[i];
4771
+ repaired.push(msg);
4772
+
4773
+ // Only check assistant messages with array content
4774
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
4775
+
4776
+ // Collect tool_use IDs from this assistant message
4777
+ const toolUseIds = [];
4778
+ for (const block of msg.content) {
4779
+ if (block.type === "tool_use" && block.id) {
4780
+ toolUseIds.push(block.id);
4781
+ }
4782
+ }
4783
+ if (toolUseIds.length === 0) continue;
4784
+
4785
+ // Check if the next message is a user message with matching tool_results
4786
+ const next = messages[i + 1];
4787
+ if (next && next.role === "user" && Array.isArray(next.content)) {
4788
+ // Collect tool_result IDs present in the next user message
4789
+ const resultIds = new Set();
4790
+ for (const block of next.content) {
4791
+ if (block.type === "tool_result" && block.tool_use_id) {
4792
+ resultIds.add(block.tool_use_id);
4793
+ }
4794
+ }
4795
+
4796
+ // Find which tool_use IDs are missing from the tool_results
4797
+ const missingIds = toolUseIds.filter((id) => !resultIds.has(id));
4798
+ if (missingIds.length === 0) continue; // All paired, nothing to fix
4799
+
4800
+ // There are missing tool_results — inject them into the existing user message.
4801
+ // Clone the next message to avoid mutating the original.
4802
+ const patchedNext = {
4803
+ ...next,
4804
+ content: [
4805
+ ...missingIds.map((id) => ({
4806
+ type: "tool_result",
4807
+ tool_use_id: id,
4808
+ content: "[Result unavailable — tool execution was interrupted]",
4809
+ })),
4810
+ ...next.content,
4811
+ ],
4812
+ };
4813
+ // Replace the next message in-place by skipping it and pushing the patched version
4814
+ i++; // skip original next
4815
+ repaired.push(patchedNext);
4816
+ } else {
4817
+ // Next message is missing or is not a user message — synthesize a full
4818
+ // tool_result user message for all tool_use IDs.
4819
+ repaired.push({
4820
+ role: "user",
4821
+ content: toolUseIds.map((id) => ({
4822
+ type: "tool_result",
4823
+ tool_use_id: id,
4824
+ content: "[Result unavailable — tool execution was interrupted]",
4825
+ })),
4826
+ });
4827
+ }
4828
+ }
4829
+
4830
+ return repaired;
4831
+ }
4832
+
4742
4833
  function stripSlashCommandMessages(messages) {
4743
4834
  if (!Array.isArray(messages) || messages.length === 0) return messages;
4744
4835
 
@@ -6130,14 +6221,20 @@ function transformRequestBody(body, signature, runtime, betaHeader, config) {
6130
6221
  parsed.speed = "fast";
6131
6222
  }
6132
6223
 
6133
- // Guard: ensure messages array never ends with an assistant message.
6134
- // The API rejects this as "assistant message prefill" when extended thinking is active.
6224
+ // Guard: repair orphaned tool_use blocks anywhere in the message array.
6225
+ // The Anthropic API requires that every assistant message containing tool_use
6226
+ // blocks is immediately followed by a user message with matching tool_result
6227
+ // blocks. When OpenCode crashes/hangs mid-tool-execution, the conversation
6228
+ // state may be saved with unpaired tool_use blocks. This causes:
6229
+ // "messages.N: `tool_use` ids were found without `tool_result` blocks
6230
+ // immediately after: toolu_XXXXX"
6231
+ // We scan the full array and synthesize missing tool_result messages.
6135
6232
  if (Array.isArray(parsed.messages) && parsed.messages.length > 0) {
6233
+ parsed.messages = repairOrphanedToolUseBlocks(parsed.messages);
6234
+
6235
+ // Also ensure the array never ends with an assistant message (prefill guard).
6136
6236
  const lastMsg = parsed.messages[parsed.messages.length - 1];
6137
6237
  if (lastMsg && lastMsg.role === "assistant") {
6138
- // Check if the assistant message contains tool_use blocks — if so,
6139
- // synthesize tool_result responses instead of bare "Continue." to
6140
- // respect the Anthropic tool protocol.
6141
6238
  const lastContent = Array.isArray(lastMsg.content) ? lastMsg.content : [];
6142
6239
  const toolUseBlocks = lastContent.filter((b) => b.type === "tool_use");
6143
6240
  if (toolUseBlocks.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-anthropic-fix",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "main": "./index.mjs",
6
6
  "files": [