opencode-anthropic-fix 0.0.46 → 0.0.48
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 +62 -35
- package/package.json +1 -1
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
|
|
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
|
-
- **
|
|
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` matches the Bun native client attestation placeholder, 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
|
@@ -2780,6 +2780,7 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2780
2780
|
claudeCliVersion,
|
|
2781
2781
|
customBetas: config.custom_betas,
|
|
2782
2782
|
strategy: getEffectiveStrategy(),
|
|
2783
|
+
sessionId: signatureSessionId,
|
|
2783
2784
|
},
|
|
2784
2785
|
_adaptiveOverride,
|
|
2785
2786
|
_tokenEconomy,
|
|
@@ -5553,19 +5554,34 @@ function buildRequestMetadata(input) {
|
|
|
5553
5554
|
|
|
5554
5555
|
/**
|
|
5555
5556
|
* Build the billing header block for Claude Code system prompt injection.
|
|
5556
|
-
* Claude Code v2.1.
|
|
5557
|
+
* Claude Code v2.1.92: cc_version includes 3-char fingerprint hash (not model ID).
|
|
5558
|
+
* cch is a static "00000" placeholder for Bun native client attestation.
|
|
5559
|
+
*
|
|
5560
|
+
* Real CC (system.ts:78): version = `${MACRO.VERSION}.${fingerprint}`
|
|
5561
|
+
* Real CC (system.ts:82): cch = feature('NATIVE_CLIENT_ATTESTATION') ? ' cch=00000;' : ''
|
|
5557
5562
|
*
|
|
5558
|
-
* @param {string} version - CLI version (e.g., "2.1.
|
|
5559
|
-
* @param {string} [
|
|
5560
|
-
* @param {string} [
|
|
5563
|
+
* @param {string} version - CLI version (e.g., "2.1.92")
|
|
5564
|
+
* @param {string} [firstUserMessage] - First user message text for fingerprint computation
|
|
5565
|
+
* @param {string} [provider] - API provider ("anthropic" | "bedrock" | "vertex" | "foundry")
|
|
5561
5566
|
* @returns {string}
|
|
5562
5567
|
*/
|
|
5563
|
-
function buildAnthropicBillingHeader(version,
|
|
5568
|
+
function buildAnthropicBillingHeader(version, firstUserMessage, provider) {
|
|
5564
5569
|
if (isFalsyEnv(process.env.CLAUDE_CODE_ATTRIBUTION_HEADER)) return "";
|
|
5565
5570
|
const entrypoint = process.env.CLAUDE_CODE_ENTRYPOINT || "unknown";
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5571
|
+
// Fix #1: cc_version suffix is the 3-char fingerprint hash, NOT the model ID.
|
|
5572
|
+
// computeBillingCacheHash() computes SHA256(salt + msg[4]+msg[7]+msg[20] + version)[:3]
|
|
5573
|
+
// which matches computeFingerprint() in the real CC source (utils/fingerprint.ts).
|
|
5574
|
+
// Always call the hash function — even for empty messages the real CC computes
|
|
5575
|
+
// the hash from "000" chars (indices 4,7,20 all missing → fallback "0").
|
|
5576
|
+
const fingerprint = computeBillingCacheHash(firstUserMessage || "", version);
|
|
5577
|
+
const ccVersion = `${version}.${fingerprint}`;
|
|
5578
|
+
// Fix #4: cch is a static "00000" placeholder for Bun's native client attestation.
|
|
5579
|
+
// Real CC v92: cch is included for all providers EXCEPT bedrock/anthropicAws.
|
|
5580
|
+
// The real Bun binary overwrites these zeros in the serialized body bytes.
|
|
5581
|
+
// For non-Bun runtimes, the server sees "00000" and skips attestation verification.
|
|
5582
|
+
const isBedrock = provider === "bedrock" || provider === "anthropicAws";
|
|
5583
|
+
const cchPart = isBedrock ? "" : " cch=00000;";
|
|
5584
|
+
let header = `x-anthropic-billing-header: cc_version=${ccVersion}; cc_entrypoint=${entrypoint};${cchPart}`;
|
|
5569
5585
|
const workload = process.env.CLAUDE_CODE_WORKLOAD;
|
|
5570
5586
|
if (workload) {
|
|
5571
5587
|
// QA fix M5: sanitize workload value to prevent header injection
|
|
@@ -5757,8 +5773,8 @@ function buildSystemPromptBlocks(system, signature) {
|
|
|
5757
5773
|
const blocks = [];
|
|
5758
5774
|
const billingHeader = buildAnthropicBillingHeader(
|
|
5759
5775
|
signature.claudeCliVersion,
|
|
5760
|
-
signature.modelId,
|
|
5761
5776
|
signature.firstUserMessage,
|
|
5777
|
+
signature.provider,
|
|
5762
5778
|
);
|
|
5763
5779
|
if (billingHeader) {
|
|
5764
5780
|
// Billing header: no cache_control (null scope — never cached)
|
|
@@ -5785,16 +5801,20 @@ function buildSystemPromptBlocks(system, signature) {
|
|
|
5785
5801
|
// we always include it for better cache hit rates on the proxy side.
|
|
5786
5802
|
blocks.push({ type: "text", text: CLAUDE_CODE_IDENTITY_STRING, cache_control: baseCacheControl });
|
|
5787
5803
|
|
|
5788
|
-
//
|
|
5804
|
+
// Real CC (utils/api.ts splitSysPromptPrefix): ALL user system blocks are joined
|
|
5805
|
+
// with '\n\n' into a SINGLE text block. This is true in ALL modes (default, MCP, boundary).
|
|
5806
|
+
// Wire format: [billing (no cache), identity (ephemeral), ONE_JOINED_BLOCK (ephemeral)]
|
|
5807
|
+
// Sending separate blocks is a detectable fingerprinting signal.
|
|
5789
5808
|
if (filtered.length > 0) {
|
|
5790
5809
|
const useBoundary =
|
|
5791
5810
|
signature.cachePolicy?.boundary_marker || isTruthyEnv(process.env.CLAUDE_CODE_FORCE_GLOBAL_CACHE);
|
|
5792
5811
|
|
|
5793
5812
|
if (useBoundary) {
|
|
5794
|
-
//
|
|
5795
|
-
//
|
|
5796
|
-
//
|
|
5797
|
-
//
|
|
5813
|
+
// Global cache mode: split blocks into static (pre-boundary) and dynamic (post-boundary).
|
|
5814
|
+
// Real CC (splitSysPromptPrefix boundary path):
|
|
5815
|
+
// static blocks → joined + cacheScope:'global' → cache_control:{type:'ephemeral',scope:'global',ttl:'1h'}
|
|
5816
|
+
// dynamic blocks → joined + cacheScope:null → NO cache_control
|
|
5817
|
+
// We use a heuristic to find the split point (environment/date/CWD info = dynamic).
|
|
5798
5818
|
const splitIndex = filtered.findIndex((block) => {
|
|
5799
5819
|
const text = block.text.toLowerCase();
|
|
5800
5820
|
return (
|
|
@@ -5808,29 +5828,29 @@ function buildSystemPromptBlocks(system, signature) {
|
|
|
5808
5828
|
|
|
5809
5829
|
const effectiveSplit = splitIndex > 0 ? splitIndex : Math.ceil(filtered.length / 2);
|
|
5810
5830
|
|
|
5811
|
-
// Static blocks (before boundary)
|
|
5812
|
-
|
|
5813
|
-
|
|
5831
|
+
// Static blocks (before boundary): joined into ONE block with global cache
|
|
5832
|
+
const staticText = filtered
|
|
5833
|
+
.slice(0, effectiveSplit)
|
|
5834
|
+
.map((b) => b.text)
|
|
5835
|
+
.join("\n\n");
|
|
5836
|
+
if (staticText) {
|
|
5837
|
+
blocks.push({ type: "text", text: staticText, cache_control: globalCacheControl });
|
|
5814
5838
|
}
|
|
5815
5839
|
|
|
5816
|
-
//
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
blocks.push(
|
|
5840
|
+
// Dynamic blocks (after boundary): joined into ONE block with NO cache
|
|
5841
|
+
const dynamicText = filtered
|
|
5842
|
+
.slice(effectiveSplit)
|
|
5843
|
+
.map((b) => b.text)
|
|
5844
|
+
.join("\n\n");
|
|
5845
|
+
if (dynamicText) {
|
|
5846
|
+
blocks.push({ type: "text", text: dynamicText });
|
|
5823
5847
|
}
|
|
5824
5848
|
} else {
|
|
5825
|
-
//
|
|
5826
|
-
//
|
|
5827
|
-
//
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
blocks.push(rest);
|
|
5831
|
-
}
|
|
5832
|
-
const lastFiltered = filtered[filtered.length - 1];
|
|
5833
|
-
blocks.push({ ...lastFiltered, cache_control: baseCacheControl });
|
|
5849
|
+
// Default mode (and MCP tools mode): ALL filtered blocks joined into ONE block.
|
|
5850
|
+
// Real CC (splitSysPromptPrefix default/MCP path):
|
|
5851
|
+
// rest.join('\n\n') → cacheScope:'org' → cache_control:{type:'ephemeral',ttl:'1h'}
|
|
5852
|
+
const joinedText = filtered.map((b) => b.text).join("\n\n");
|
|
5853
|
+
blocks.push({ type: "text", text: joinedText, cache_control: baseCacheControl });
|
|
5834
5854
|
}
|
|
5835
5855
|
}
|
|
5836
5856
|
|
|
@@ -6118,7 +6138,7 @@ async function fetchLatestClaudeCodeVersion(timeoutMs = 1200) {
|
|
|
6118
6138
|
* @param {string} accessToken
|
|
6119
6139
|
* @param {string | undefined} requestBody
|
|
6120
6140
|
* @param {URL | null} requestUrl
|
|
6121
|
-
* @param {{enabled: boolean, claudeCliVersion: string, strategy?: import('./lib/config.mjs').AccountSelectionStrategy, customBetas?: string[]}} signature
|
|
6141
|
+
* @param {{enabled: boolean, claudeCliVersion: string, strategy?: import('./lib/config.mjs').AccountSelectionStrategy, customBetas?: string[], sessionId?: string}} signature
|
|
6122
6142
|
* @returns {Headers}
|
|
6123
6143
|
*/
|
|
6124
6144
|
function buildRequestHeaders(
|
|
@@ -6182,7 +6202,14 @@ function buildRequestHeaders(
|
|
|
6182
6202
|
requestHeaders.set("user-agent", buildExtendedUserAgent(signature.claudeCliVersion));
|
|
6183
6203
|
if (signature.enabled) {
|
|
6184
6204
|
requestHeaders.set("anthropic-version", "2023-06-01");
|
|
6185
|
-
|
|
6205
|
+
// Fix #6: x-app is "cli" for interactive mode, "cli-bg" for background tasks.
|
|
6206
|
+
// Real CC (client.ts:106): 'x-app': 'cli' (foreground) or 'cli-bg' (background agent).
|
|
6207
|
+
requestHeaders.set("x-app", isTruthyEnv(process.env.CLAUDE_CODE_BACKGROUND) ? "cli-bg" : "cli");
|
|
6208
|
+
// Fix #3: X-Claude-Code-Session-Id — sent in ALL requests by real CC (client.ts:108).
|
|
6209
|
+
// Value matches metadata.user_id.session_id for server-side correlation.
|
|
6210
|
+
if (signature.sessionId) {
|
|
6211
|
+
requestHeaders.set("X-Claude-Code-Session-Id", signature.sessionId);
|
|
6212
|
+
}
|
|
6186
6213
|
requestHeaders.set("x-stainless-arch", getStainlessArch(process.arch));
|
|
6187
6214
|
requestHeaders.set("x-stainless-lang", "js");
|
|
6188
6215
|
requestHeaders.set("x-stainless-os", getStainlessOs(process.platform));
|