opencode-anthropic-fix 0.1.0 → 0.1.2

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.mjs +60 -49
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -44,7 +44,7 @@ The [original plugin](https://github.com/anomalyco/opencode-anthropic-auth) prov
44
44
  - **Configurable strategies** — sticky, round-robin, or hybrid account selection
45
45
  - **Claude Code signature emulation** — full HTTP header, system prompt, beta flag, and metadata mimicry derived from Claude Code's open source code. System prompt cache scoping follows the real CC's three-path `splitSysPromptPrefix()` architecture (boundary/global/org) with exact marker detection and scope-aware `cache_control` generation
46
46
  - **OAuth endpoint fingerprint parity** — matches the real CLI's bundled axios 1.13.6 HTTP client signature (`Accept`, `User-Agent`, `Content-Type`) on all OAuth token endpoint calls, required since 2026-03-21 server-side enforcement
47
- - **Billing header fingerprint parity** — `cc_version` suffix uses the real CLI's 3-char fingerprint hash (SHA-256 of salt + first user message chars + version), `cch` is computed via xxHash64 attestation matching the Bun binary (omitted on bedrock/anthropicAws/mantle), system prompt is sanitized to match Claude Code's pattern validation, and `X-Claude-Code-Session-Id` header is sent on all requests
47
+ - **Billing header fingerprint parity** — `cc_version` suffix uses the real CLI's 3-char fingerprint hash (SHA-256 of salt + first user message chars + version), `cch=00000` is a static placeholder (xxHash64 attestation was removed in Claude Code v2.1.97), system prompt is sanitized to match Claude Code's pattern validation, and `X-Claude-Code-Session-Id` header is sent on all requests
48
48
  - **Adaptive thinking for Opus/Sonnet 4.6** — automatically normalizes thinking to `{type: "adaptive"}` for supported models, with `effort-2025-11-24` beta
49
49
  - **Upstream-aligned auto betas** — 13+ always-on betas matching Claude Code 2.1.92 (`redact-thinking-2026-02-12` available as opt-in to preserve thinking block visibility)
50
50
  - **1M context limit override** — patches `model.limit.context` so OpenCode compacts at the right threshold while `models.dev` catches up
package/index.mjs CHANGED
@@ -3,7 +3,7 @@ import { stdin, stdout } from "node:process";
3
3
  import { randomBytes, randomUUID, createHash as createHashCrypto } from "node:crypto";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { join, resolve, basename } from "node:path";
6
- import xxhashInit from "xxhash-wasm";
6
+ // xxhash-wasm import removed: CCH attestation was removed in CC v2.1.97
7
7
  import { AccountManager } from "./lib/accounts.mjs";
8
8
  import { authorize as oauthAuthorize, exchange as oauthExchange, refreshToken } from "./lib/oauth.mjs";
9
9
  import { loadConfig, loadConfigFresh, saveConfig, CLIENT_ID, getConfigDir } from "./lib/config.mjs";
@@ -1024,11 +1024,19 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
1024
1024
  const enabled = value === "on" || value === "true" || value === "1";
1025
1025
  saveConfig({ fast_mode: enabled });
1026
1026
  config.fast_mode = enabled;
1027
+ _fastModeAppliedToast = false; // reset so next application toasts
1028
+ toast(enabled ? "⚡ Fast mode ON (Opus 4.6 only)" : "⚡ Fast mode OFF", enabled ? "info" : "success", {
1029
+ debounceKey: "fast-mode-toggle",
1030
+ }).catch(() => {});
1027
1031
  },
1028
1032
  "fast-mode": () => {
1029
1033
  const enabled = value === "on" || value === "true" || value === "1";
1030
1034
  saveConfig({ fast_mode: enabled });
1031
1035
  config.fast_mode = enabled;
1036
+ _fastModeAppliedToast = false;
1037
+ toast(enabled ? "⚡ Fast mode ON (Opus 4.6 only)" : "⚡ Fast mode OFF", enabled ? "info" : "success", {
1038
+ debounceKey: "fast-mode-toggle",
1039
+ }).catch(() => {});
1032
1040
  },
1033
1041
  telemetry: () => {
1034
1042
  const enabled = value === "on" || value === "true" || value === "1";
@@ -1063,6 +1071,9 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
1063
1071
  adaptiveContextState.escalatedByError = false;
1064
1072
  adaptiveContextState.lastTransitionTurn = sessionMetrics.turns;
1065
1073
  }
1074
+ toast(enabled ? "⬡ Adaptive 1M context ON" : "⬡ Adaptive 1M context OFF", enabled ? "info" : "success", {
1075
+ debounceKey: "adaptive-ctx-toggle",
1076
+ }).catch(() => {});
1066
1077
  },
1067
1078
  "token-efficient-tools": () => {
1068
1079
  const enabled = value === "on" || value === "true" || value === "1";
@@ -2783,6 +2794,12 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
2783
2794
  );
2784
2795
  logTransformedSystemPrompt(body);
2785
2796
 
2797
+ // Toast on first fast-mode application in session (reset on toggle)
2798
+ if (!_fastModeAppliedToast && typeof body === "string" && body.includes('"speed":"fast"')) {
2799
+ _fastModeAppliedToast = true;
2800
+ toast("⚡ Fast mode active", "info", { debounceKey: "fast-mode-active" }).catch(() => {});
2801
+ }
2802
+
2786
2803
  // Capture request body for /anthropic context (2MB cap)
2787
2804
  if (typeof body === "string" && body.length <= 2_000_000) {
2788
2805
  sessionMetrics.lastRequestBody = body;
@@ -2815,8 +2832,9 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
2815
2832
  _adaptiveOverride,
2816
2833
  _tokenEconomy,
2817
2834
  );
2818
- // Compute cch attestation: xxHash64 of body with seed, replaces "00000"
2819
- const finalBody = typeof body === "string" ? await computeAndReplaceCch(body) : body;
2835
+ // v2.1.97: cch=00000 is now static (xxHash64 attestation removed).
2836
+ // Send body as-is without cch replacement.
2837
+ const finalBody = body;
2820
2838
 
2821
2839
  // Execute the request
2822
2840
  let response;
@@ -3270,7 +3288,11 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
3270
3288
  if (response.status === 400 && errorBody && errorBody.includes("speed")) {
3271
3289
  if (config.fast_mode) {
3272
3290
  config.fast_mode = false;
3291
+ _fastModeAppliedToast = false;
3273
3292
  saveConfig({ fast_mode: false });
3293
+ toast("⚡ Fast mode OFF — not supported by API", "warning", {
3294
+ debounceKey: "fast-mode-off",
3295
+ }).catch(() => {});
3274
3296
  debugLog("fast mode not supported by API, auto-disabled");
3275
3297
  }
3276
3298
  }
@@ -3340,7 +3362,8 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
3340
3362
  // Graceful degradation: disable fast mode on rate limits
3341
3363
  if (config.fast_mode && (response.status === 429 || response.status === 529)) {
3342
3364
  config.fast_mode = false;
3343
- toast("Fast mode disabled due to rate limiting", "warning", {
3365
+ _fastModeAppliedToast = false;
3366
+ toast("⚡ Fast mode OFF — rate limited", "warning", {
3344
3367
  debounceKey: "fast-mode-off",
3345
3368
  }).catch(() => {});
3346
3369
  debugLog("auto-disabled fast mode after rate limit");
@@ -3893,6 +3916,10 @@ const adaptiveContextState = {
3893
3916
  escalatedByError: false,
3894
3917
  };
3895
3918
 
3919
+ /** Track whether we've already toasted about fast mode being applied this session.
3920
+ * Resets when fast mode is toggled off/on so the user gets fresh feedback. */
3921
+ let _fastModeAppliedToast = false;
3922
+
3896
3923
  // ---------------------------------------------------------------------------
3897
3924
  // Cache break detection state (Phase 2, Task 2.3)
3898
3925
  // ---------------------------------------------------------------------------
@@ -4957,17 +4984,18 @@ process.once("beforeExit", _beforeExitHandler);
4957
4984
  // Request building helpers (extracted from original fetch interceptor)
4958
4985
  // ---------------------------------------------------------------------------
4959
4986
 
4960
- const FALLBACK_CLAUDE_CLI_VERSION = "2.1.96";
4987
+ const FALLBACK_CLAUDE_CLI_VERSION = "2.1.97";
4961
4988
  const CLAUDE_CODE_NPM_LATEST_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
4962
- const CLAUDE_CODE_BUILD_TIME = "2026-04-08T03:13:25Z";
4989
+ const CLAUDE_CODE_BUILD_TIME = "2026-04-08T20:46:46Z";
4963
4990
 
4964
- // The @anthropic-ai/sdk version bundled with Claude Code v2.1.96.
4991
+ // The @anthropic-ai/sdk version bundled with Claude Code v2.1.97.
4965
4992
  // This is distinct from the CLI version and goes in X-Stainless-Package-Version.
4966
- // Verified by extracting VERSION="0.208.0" from the bundled cli.js of all versions .80-.96.
4993
+ // Verified by extracting VERSION="0.208.0" from the bundled cli.js of all versions .80-.97.
4967
4994
  const ANTHROPIC_SDK_VERSION = "0.208.0";
4968
4995
 
4969
4996
  // Map of CLI version → bundled SDK version (update when CLI version changes)
4970
4997
  const CLI_TO_SDK_VERSION = new Map([
4998
+ ["2.1.97", "0.208.0"],
4971
4999
  ["2.1.96", "0.208.0"],
4972
5000
  ["2.1.95", "0.208.0"],
4973
5001
  ["2.1.94", "0.208.0"],
@@ -4998,31 +5026,11 @@ function getSdkVersion(cliVersion) {
4998
5026
  const BILLING_HASH_SALT = "59cf53e54c78";
4999
5027
  const BILLING_HASH_INDICES = [4, 7, 20];
5000
5028
 
5001
- // cch attestation: xxHash64 of the full serialized body with seed, masked to 20 bits.
5002
- // Seed is static per CC version, extracted from Bun's compiled Zig layer.
5003
- // See: https://a10k.co/b/reverse-engineering-claude-code-cch.html
5004
- const CCH_SEED = 0x6e52736ac806831en; // BigInt changes per CC version
5005
-
5006
- /** @type {null | ((buf: Uint8Array, seed: bigint) => bigint)} */
5007
- let _xxh64Raw = null;
5008
- const _xxhashReady = xxhashInit().then((h) => {
5009
- _xxh64Raw = h.h64Raw;
5010
- });
5011
-
5012
- /**
5013
- * Compute the cch attestation hash for a serialized request body.
5014
- * @param {string} bodyWithPlaceholder - JSON body containing "cch=00000"
5015
- * @returns {Promise<string>} The body with "cch=00000" replaced by the computed hash
5016
- */
5017
- async function computeAndReplaceCch(bodyWithPlaceholder) {
5018
- if (!bodyWithPlaceholder.includes("cch=00000")) return bodyWithPlaceholder;
5019
- await _xxhashReady;
5020
- if (!_xxh64Raw) return bodyWithPlaceholder;
5021
- const bodyBytes = Buffer.from(bodyWithPlaceholder, "utf-8");
5022
- const hash = _xxh64Raw(bodyBytes, CCH_SEED);
5023
- const cch = (hash & 0xfffffn).toString(16).padStart(5, "0");
5024
- return bodyWithPlaceholder.replace("cch=00000", `cch=${cch}`);
5025
- }
5029
+ // cch attestation: REMOVED in v2.1.97.
5030
+ // Previously (v2.1.96), CC computed xxHash64 of the serialized body and replaced
5031
+ // "cch=00000" with the 20-bit masked hash. In v2.1.97, the xxHash64 computation
5032
+ // was completely removed — "cch=00000" is now sent as a static placeholder.
5033
+ // Sending a computed cch value would now be detected as non-genuine.
5026
5034
 
5027
5035
  /**
5028
5036
  * Compute the billing cache hash (cch) matching Claude Code's NP1() function.
@@ -5250,24 +5258,23 @@ const BEDROCK_UNSUPPORTED_BETAS = new Set([
5250
5258
  "interleaved-thinking-2025-05-14",
5251
5259
  "context-1m-2025-08-07",
5252
5260
  "tool-search-tool-2025-10-19",
5253
- "code-execution-2025-08-25",
5254
- "files-api-2025-04-14",
5255
- "fine-grained-tool-streaming-2025-05-14",
5256
5261
  ]);
5257
5262
  const EXPERIMENTAL_BETA_FLAGS = new Set([
5258
5263
  "adaptive-thinking-2026-01-28",
5259
5264
  "advanced-tool-use-2025-11-20",
5265
+ "advisor-tool-2026-03-01",
5260
5266
  "afk-mode-2026-01-31",
5261
5267
  "code-execution-2025-08-25",
5268
+ "compact-2026-01-12",
5262
5269
  "context-1m-2025-08-07",
5263
5270
  "context-management-2025-06-27",
5264
5271
  "fast-mode-2026-02-01",
5265
5272
  "files-api-2025-04-14",
5266
- "fine-grained-tool-streaming-2025-05-14",
5267
5273
  "interleaved-thinking-2025-05-14",
5268
5274
  "prompt-caching-scope-2026-01-05",
5269
5275
  "redact-thinking-2026-02-12",
5270
5276
  "structured-outputs-2025-12-15",
5277
+ "task-budgets-2026-03-13",
5271
5278
  "tool-search-tool-2025-10-19",
5272
5279
  "web-search-2025-03-05",
5273
5280
  ]);
@@ -5498,12 +5505,17 @@ function isAdaptiveThinkingModel(model) {
5498
5505
 
5499
5506
  /**
5500
5507
  * Check if a model is eligible for 1M context (can receive context-1m beta).
5501
- * This includes models with explicit "1m" in the name AND Opus 4.6.
5508
+ * Real CC v2.1.97 U01(): claude-sonnet-4* || opus-4-6 are eligible.
5509
+ * Also matches explicit "1m" in the name (e.g. "claude-opus-4-6[1m]").
5502
5510
  * @param {string} model
5503
5511
  * @returns {boolean}
5504
5512
  */
5505
5513
  function isEligibleFor1MContext(model) {
5506
- return /(^|[-_ ])1m($|[-_ ])|context[-_]?1m/i.test(model) || isOpus46Model(model);
5514
+ if (!model) return false;
5515
+ // Explicit 1m suffix/tag in model name
5516
+ if (/(^|[-_ ])1m($|[-_ ])|context[-_]?1m|\[1m\]/i.test(model)) return true;
5517
+ // CC v2.1.97 U01: claude-sonnet-4* (any Sonnet 4.x) or opus-4-6
5518
+ return /claude-sonnet-4|sonnet[._-]4/i.test(model) || isOpus46Model(model);
5507
5519
  }
5508
5520
 
5509
5521
  /**
@@ -5664,13 +5676,13 @@ function buildRequestMetadata(input) {
5664
5676
 
5665
5677
  /**
5666
5678
  * Build the billing header block for Claude Code system prompt injection.
5667
- * Claude Code v2.1.96: cc_version includes 3-char fingerprint hash (not model ID).
5668
- * cch is a static "00000" placeholder for Bun native client attestation.
5679
+ * Claude Code v2.1.97: cc_version includes 3-char fingerprint hash (not model ID).
5680
+ * cch is a static "00000" placeholder (xxHash64 attestation removed in v2.1.97).
5669
5681
  *
5670
5682
  * Real CC (system.ts:78): version = `${MACRO.VERSION}.${fingerprint}`
5671
- * Real CC (system.ts:82): cch = feature('NATIVE_CLIENT_ATTESTATION') ? ' cch=00000;' : ''
5683
+ * Real CC (system.ts:82): cch = ' cch=00000;' (static, no longer computed)
5672
5684
  *
5673
- * @param {string} version - CLI version (e.g., "2.1.96")
5685
+ * @param {string} version - CLI version (e.g., "2.1.97")
5674
5686
  * @param {string} [firstUserMessage] - First user message text for fingerprint computation
5675
5687
  * @param {string} [provider] - API provider ("anthropic" | "bedrock" | "vertex" | "foundry" | "anthropicAws" | "mantle")
5676
5688
  * @returns {string}
@@ -5686,10 +5698,8 @@ function buildAnthropicBillingHeader(version, firstUserMessage, provider) {
5686
5698
  // the hash from "000" chars (indices 4,7,20 all missing → fallback "0").
5687
5699
  const fingerprint = computeBillingCacheHash(firstUserMessage || "", version);
5688
5700
  const ccVersion = `${version}.${fingerprint}`;
5689
- // cch: The server may use the PRESENCE of cch as a CC identification signal.
5690
- // Real CC sends cch=00000 placeholder which Bun's Zig stack overwrites.
5691
- // For non-Bun runtimes: send cch=00000 so the server recognizes this as CC.
5692
- // The server should skip attestation verification for all-zeros cch.
5701
+ // cch: v2.1.97 sends static "cch=00000" xxHash64 attestation was removed.
5702
+ // The server uses the PRESENCE of cch=00000 as a CC identification signal.
5693
5703
  const cchDisabled = provider === "bedrock" || provider === "anthropicAws" || provider === "mantle";
5694
5704
  const cchPart = cchDisabled ? "" : " cch=00000;";
5695
5705
  // Build workload part (upstream concatenates directly, no regex replace)
@@ -6689,9 +6699,10 @@ function transformRequestBody(body, signature, runtime, betaHeader, config) {
6689
6699
  }
6690
6700
  }
6691
6701
 
6692
- // Fast mode: inject speed parameter for supported models
6702
+ // Fast mode: inject speed parameter for Opus 4.6 only (v2.1.97 restriction).
6703
+ // Real CC v2.1.97 xJ() checks: model.includes("opus-4-6") — Sonnet is NOT eligible.
6693
6704
  const fastModeEnabled = signature.fastMode && !isFalsyEnv(process.env.OPENCODE_ANTHROPIC_DISABLE_FAST_MODE);
6694
- if (fastModeEnabled && parsed.model && (isOpus46Model(parsed.model) || isSonnet46Model(parsed.model))) {
6705
+ if (fastModeEnabled && parsed.model && isOpus46Model(parsed.model)) {
6695
6706
  parsed.speed = "fast";
6696
6707
  }
6697
6708
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-anthropic-fix",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "main": "./index.mjs",
6
6
  "files": [
@@ -43,7 +43,6 @@
43
43
  "vitest": "^4.0.18"
44
44
  },
45
45
  "dependencies": {
46
- "@openauthjs/openauth": "^0.4.3",
47
- "xxhash-wasm": "^1.1.0"
46
+ "@openauthjs/openauth": "^0.4.3"
48
47
  }
49
48
  }