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.
Files changed (2) hide show
  1. package/index.mjs +98 -0
  2. 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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-anthropic-fix",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "main": "./index.mjs",
6
6
  "files": [