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.
- package/index.mjs +103 -6
- 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
|
-
|
|
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:
|
|
6134
|
-
// The API
|
|
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) {
|