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.
- package/README.md +1 -1
- package/index.mjs +60 -49
- 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
|
|
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
|
|
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
|
-
//
|
|
2819
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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.
|
|
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-.
|
|
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:
|
|
5002
|
-
//
|
|
5003
|
-
//
|
|
5004
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
5668
|
-
* cch is a static "00000" placeholder
|
|
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 =
|
|
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.
|
|
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:
|
|
5690
|
-
//
|
|
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
|
|
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 &&
|
|
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.
|
|
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
|
}
|