opencode-anthropic-fix 0.0.38 → 0.0.39
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 +98 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -3725,6 +3725,96 @@ function computeBillingCacheHash(firstUserMessage, version) {
|
|
|
3725
3725
|
* @param {any[] | undefined} messages
|
|
3726
3726
|
* @returns {string}
|
|
3727
3727
|
*/
|
|
3728
|
+
/**
|
|
3729
|
+
* Strip leaked /anthropic slash command messages from conversation history.
|
|
3730
|
+
*
|
|
3731
|
+
* When a user runs `/anthropic <subcommand>`, OpenCode may still include the
|
|
3732
|
+
* command text as a user message and the sendCommandMessage output as an
|
|
3733
|
+
* assistant message in the API request. This function removes those messages
|
|
3734
|
+
* so the model never sees internal plugin commands in its context.
|
|
3735
|
+
*
|
|
3736
|
+
* Detection heuristics:
|
|
3737
|
+
* - User messages that start with `/anthropic` (with optional leading whitespace)
|
|
3738
|
+
* - User messages where the ONLY text content is a `/anthropic` command
|
|
3739
|
+
* - Assistant messages that start with the `▣ Anthropic` prefix used by sendCommandMessage
|
|
3740
|
+
*
|
|
3741
|
+
* After filtering, if the last remaining message is an assistant message, drop it
|
|
3742
|
+
* to maintain the user→assistant alternation required by the API.
|
|
3743
|
+
*
|
|
3744
|
+
* @param {Array} messages — The messages array from the parsed request body
|
|
3745
|
+
* @returns {Array} — Filtered messages array
|
|
3746
|
+
*/
|
|
3747
|
+
function stripSlashCommandMessages(messages) {
|
|
3748
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
3749
|
+
|
|
3750
|
+
// Pattern: /anthropic followed by optional subcommand
|
|
3751
|
+
const CMD_RE = /^\s*\/anthropic\b/i;
|
|
3752
|
+
// Pattern: ▣ Anthropic — prefix used by all sendCommandMessage outputs
|
|
3753
|
+
const RESP_RE = /^▣\s*Anthropic/;
|
|
3754
|
+
|
|
3755
|
+
/**
|
|
3756
|
+
* Extract the first text content from a message's content field.
|
|
3757
|
+
* Handles both string content and array-of-blocks content.
|
|
3758
|
+
*/
|
|
3759
|
+
function getFirstText(msg) {
|
|
3760
|
+
if (typeof msg.content === "string") return msg.content;
|
|
3761
|
+
if (Array.isArray(msg.content)) {
|
|
3762
|
+
for (const block of msg.content) {
|
|
3763
|
+
if (block.type === "text" && typeof block.text === "string") return block.text;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
return "";
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
/**
|
|
3770
|
+
* Check if a user message is a /anthropic command.
|
|
3771
|
+
* A message is a command if its text content starts with /anthropic.
|
|
3772
|
+
*/
|
|
3773
|
+
function isCommandMessage(msg) {
|
|
3774
|
+
if (msg.role !== "user") return false;
|
|
3775
|
+
const text = getFirstText(msg);
|
|
3776
|
+
return CMD_RE.test(text);
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
/**
|
|
3780
|
+
* Check if an assistant message is a sendCommandMessage response.
|
|
3781
|
+
* These always start with ▣ Anthropic.
|
|
3782
|
+
*/
|
|
3783
|
+
function isCommandResponse(msg) {
|
|
3784
|
+
if (msg.role !== "assistant") return false;
|
|
3785
|
+
const text = getFirstText(msg);
|
|
3786
|
+
return RESP_RE.test(text);
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
const filtered = [];
|
|
3790
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3791
|
+
const msg = messages[i];
|
|
3792
|
+
|
|
3793
|
+
// Drop /anthropic command messages
|
|
3794
|
+
if (isCommandMessage(msg)) {
|
|
3795
|
+
// Also drop the immediately following assistant response if it's a command response
|
|
3796
|
+
if (i + 1 < messages.length && isCommandResponse(messages[i + 1])) {
|
|
3797
|
+
i++; // Skip the response too
|
|
3798
|
+
}
|
|
3799
|
+
continue;
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
// Drop orphaned command responses (in case the command message was already removed
|
|
3803
|
+
// or the ordering is different)
|
|
3804
|
+
if (isCommandResponse(msg)) {
|
|
3805
|
+
continue;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
filtered.push(msg);
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
// Safety: if filtering removed ALL messages, return the original to avoid sending
|
|
3812
|
+
// an empty messages array to the API.
|
|
3813
|
+
if (filtered.length === 0) return messages;
|
|
3814
|
+
|
|
3815
|
+
return filtered;
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3728
3818
|
function extractFirstUserMessageText(messages) {
|
|
3729
3819
|
if (!Array.isArray(messages)) return "";
|
|
3730
3820
|
for (const msg of messages) {
|
|
@@ -4898,6 +4988,14 @@ function transformRequestBody(body, signature, runtime, betaHeader) {
|
|
|
4898
4988
|
parsed.temperature = 1;
|
|
4899
4989
|
}
|
|
4900
4990
|
|
|
4991
|
+
// Strip leaked /anthropic slash command messages from conversation history.
|
|
4992
|
+
// OpenCode may include command text and sendCommandMessage output as regular
|
|
4993
|
+
// user messages even when output.noReply = true was set. Filter them out
|
|
4994
|
+
// so the agent never sees /anthropic commands in its context.
|
|
4995
|
+
if (Array.isArray(parsed.messages)) {
|
|
4996
|
+
parsed.messages = stripSlashCommandMessages(parsed.messages);
|
|
4997
|
+
}
|
|
4998
|
+
|
|
4901
4999
|
// QA fix H2: avoid mutating the signature parameter; capture modelId locally
|
|
4902
5000
|
const modelId = parsed.model || "";
|
|
4903
5001
|
// Extract first user message text for billing hash computation (cch)
|