opencode-anthropic-fix 0.2.0 → 0.3.0-beta.0

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 (43) hide show
  1. package/README.md +31 -6
  2. package/cli.mjs +1 -0
  3. package/index.mjs +160 -231
  4. package/lib/backoff.mjs +1 -1
  5. package/lib/cc-credentials.mjs +1 -1
  6. package/lib/config.mjs +49 -27
  7. package/lib/mimicry/adapter-input.mjs +671 -0
  8. package/lib/mimicry/context-hint-threshold.mjs +127 -0
  9. package/lib/mimicry/headers.mjs +71 -66
  10. package/lib/mimicry/request-body.mjs +23 -24
  11. package/lib/mimicry/system-prompt.mjs +54 -23
  12. package/lib/mimicry/wire-compat.mjs +305 -0
  13. package/lib/rotation.mjs +16 -0
  14. package/package.json +3 -3
  15. package/lib/account-state.test.mjs +0 -109
  16. package/lib/accounts.test.mjs +0 -912
  17. package/lib/adaptive-thinking-models.test.mjs +0 -30
  18. package/lib/backoff.test.mjs +0 -411
  19. package/lib/cc-credentials.test.mjs +0 -391
  20. package/lib/config.test.mjs +0 -757
  21. package/lib/context-hint-persist.mjs +0 -68
  22. package/lib/diagnose.test.mjs +0 -122
  23. package/lib/env.test.mjs +0 -25
  24. package/lib/haiku-call.test.mjs +0 -72
  25. package/lib/message-transform.test.mjs +0 -173
  26. package/lib/mimicry/cache.test.mjs +0 -112
  27. package/lib/mimicry/headers.test.mjs +0 -50
  28. package/lib/mimicry/models.test.mjs +0 -132
  29. package/lib/mimicry/request-body.test.mjs +0 -34
  30. package/lib/mimicry/request-helpers.test.mjs +0 -155
  31. package/lib/mimicry/response-stream.test.mjs +0 -127
  32. package/lib/mimicry/system-prompt.test.mjs +0 -73
  33. package/lib/oauth.test.mjs +0 -343
  34. package/lib/redact.test.mjs +0 -154
  35. package/lib/refresh-lock.test.mjs +0 -131
  36. package/lib/request-headers.test.mjs +0 -164
  37. package/lib/retry/overload-loop.test.mjs +0 -107
  38. package/lib/rotation.test.mjs +0 -346
  39. package/lib/session-metrics.test.mjs +0 -67
  40. package/lib/storage.test.mjs +0 -696
  41. package/lib/token-economy/microcompact.test.mjs +0 -28
  42. package/lib/token-economy/transforms.test.mjs +0 -192
  43. package/lib/tuning.test.mjs +0 -89
package/lib/config.mjs CHANGED
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
4
4
  import { randomBytes } from "node:crypto";
5
5
 
6
6
  /**
7
- * @typedef {'sticky' | 'round-robin' | 'hybrid'} AccountSelectionStrategy
7
+ * @typedef {'sticky' | 'round-robin' | 'hybrid' | 'single'} AccountSelectionStrategy
8
8
  */
9
9
 
10
10
  /**
@@ -103,7 +103,7 @@ import { randomBytes } from "node:crypto";
103
103
  * @property {{ enabled: boolean, threshold_percent: number }} microcompact
104
104
  * @property {{ enabled: boolean, default_cooldown_ms: number, poll_quota_on_overload: boolean }} overload_recovery
105
105
  * @property {{ proactive_disabled: boolean }} account_management
106
- * @property {{ enabled: boolean, length_anchors: boolean }} anti_verbosity
106
+ * @property {{ enabled: boolean }} anti_verbosity
107
107
  * @property {{ sdk_token_useragent: boolean }} oauth
108
108
  */
109
109
 
@@ -119,8 +119,9 @@ export const DEFAULT_CONFIG = {
119
119
  /** Workload tag emitted in the `x-anthropic-billing-header` as
120
120
  * `cc_workload=<tag>;`. Mirrors real CC's `--workload <tag>` flag — used
121
121
  * by SDK daemon callers that spawn subprocesses for cron work, to tag
122
- * billing attribution. Empty string => omit (default). Provider-gated:
123
- * bedrock/anthropicAws/mantle skip this segment (same gate as `cch`).
122
+ * billing attribution. Empty string => omit (default). Not provider-gated:
123
+ * the plugin is Anthropic first-party only, so this segment is emitted
124
+ * whenever a tag is set (see "Provider Scope" in README.md).
124
125
  * Env var `CLAUDE_CODE_WORKLOAD` is a fallback when this is empty. */
125
126
  workload: "",
126
127
  },
@@ -211,15 +212,17 @@ export const DEFAULT_CONFIG = {
211
212
  * server-side for lower bandwidth). On by default for non-Claude-3 models
212
213
  * (CC v2.1.150+). Opt out via `/anthropic set redact-thinking off`. */
213
214
  redact_thinking: true,
214
- /** Enable context-hint-2026-04-09 beta (CC v2.1.110+). When on, the server
215
- * MAY reject 422/424 and trigger client-side compaction retries.
216
- * Default ON (Phase C2). Server-side gating + contextHintState.disabled
217
- * latching means compatible servers use it and incompatible ones fall
218
- * back cleanly via the 400/409/529 error paths. Explicit opt-out via
219
- * `token_economy.context_hint: false` is respected. Even when on, the
220
- * beta is only sent for requests classified as "main-thread" (see
221
- * classifyRequestRole). */
222
- context_hint: true,
215
+ /** [DEPRECATED — has no effect] context-hint-2026-04-09.
216
+ * The profile derived from the real Claude Code 2.1.195 binary sends
217
+ * neither the `context-hint-2026-04-09` beta nor the paired
218
+ * `context_hint: { enabled: true }` body field. Emitting either one made
219
+ * the request distinguishable from the genuine client, so both emissions
220
+ * were removed from every path (adapter and legacy alike). The effect
221
+ * cannot come back without breaking the mimicry, so this knob is not
222
+ * silently ignored: validateConfig emits a one-time deprecation warning
223
+ * when a user explicitly sets it to `true`. Retained only so an existing
224
+ * config file keeps loading. */
225
+ context_hint: false,
223
226
 
224
227
  /** Dump outgoing request bodies to
225
228
  * `~/.opencode/opencode-anthropic-fix/request-dumps/` (rotating, last 10).
@@ -300,17 +303,17 @@ export const DEFAULT_CONFIG = {
300
303
  * thread is untouched. */
301
304
  lean_system_non_main: true,
302
305
 
303
- /** Simple system prompt mode (real CC v2.1.133+ `tengu_vellum_lantern`
306
+ /** Simple system prompt mode (real CC v2.1.133+ `tengu_velvet_cascade`
304
307
  * equivalent). When enabled AND the request model is eligible
305
308
  * (claude-opus-4-7, -eap variants — see `isSimpleSystemPromptEligible`),
306
- * skip the anti-verbosity boilerplate (`ANTI_VERBOSITY_SYSTEM_PROMPT` +
307
- * `NUMERIC_LENGTH_ANCHORS_PROMPT`) that the plugin injects on Opus 4.x.
309
+ * skip the anti-verbosity boilerplate (`ANTI_VERBOSITY_SYSTEM_PROMPT`)
310
+ * that the plugin injects on Opus 4.x.
308
311
  * Conservative implementation: gates ONLY anti-verbosity. Identity
309
312
  * preamble, billing block, user instructions, and `<system-reminder>`
310
313
  * blocks are all preserved so the CC fingerprint match still holds.
311
314
  * Saves ~600-1500 tokens per request on eligible models. Default ON:
312
315
  * gates ONLY the anti-verbosity boilerplate (which real CC v2.1.133+ also
313
- * drops for eligible Opus 4.x via tengu_vellum_lantern); identity, billing
316
+ * drops for eligible Opus 4.x via tengu_velvet_cascade); identity, billing
314
317
  * and system-reminders are preserved, so the fingerprint match holds. */
315
318
  simple_system_prompt: true,
316
319
 
@@ -426,14 +429,12 @@ export const DEFAULT_CONFIG = {
426
429
  proactive_disabled: true,
427
430
  },
428
431
  /** Anti-verbosity: inject conciseness instructions into system prompt for Opus 4.6.
429
- * Mirrors CC v2.1.100 anti_verbosity + numeric_length_anchors sections (gated on
432
+ * Mirrors the CC anti_verbosity section (gated on
430
433
  * quiet_salted_ember A/B test in CC; unconditional here since we always want savings).
431
434
  * Only activates when model is opus-4-6. */
432
435
  anti_verbosity: {
433
436
  /** Master switch: inject anti-verbosity communication style instructions. */
434
437
  enabled: true,
435
- /** Also inject numeric length anchors (≤25 words between tool calls, ≤100 words final). */
436
- length_anchors: true,
437
438
  },
438
439
  /** OAuth token-endpoint request fingerprint. */
439
440
  oauth: {
@@ -449,7 +450,7 @@ export const DEFAULT_CONFIG = {
449
450
  },
450
451
  };
451
452
 
452
- export const VALID_STRATEGIES = ["sticky", "round-robin", "hybrid"];
453
+ export const VALID_STRATEGIES = ["sticky", "round-robin", "hybrid", "single"];
453
454
 
454
455
  /** OpenCode's OAuth client ID for Anthropic console auth flows. */
455
456
  export const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
@@ -529,6 +530,13 @@ function clampNumber(value, min, max, fallback) {
529
530
  * @param {Record<string, unknown>} raw
530
531
  * @returns {AnthropicAuthConfig}
531
532
  */
533
+ /**
534
+ * One-shot latch for the `token_economy.context_hint` deprecation warning, so a
535
+ * long-running session does not repeat it on every config reload.
536
+ * @type {boolean}
537
+ */
538
+ let contextHintDeprecationWarned = false;
539
+
532
540
  function validateConfig(raw) {
533
541
  const config = createDefaultConfig();
534
542
 
@@ -757,6 +765,21 @@ function validateConfig(raw) {
757
765
  // Token economy sub-config
758
766
  if (raw.token_economy && typeof raw.token_economy === "object") {
759
767
  const te = /** @type {Record<string, unknown>} */ (raw.token_economy);
768
+
769
+ // Non-silent deprecation. Only an explicit `true` warns: the `false` default
770
+ // costs the user nothing and must stay quiet. See the DEFAULT_CONFIG comment
771
+ // on token_economy.context_hint for why the effect cannot be restored.
772
+ if (te.context_hint === true && !contextHintDeprecationWarned) {
773
+ contextHintDeprecationWarned = true;
774
+
775
+ console.warn(
776
+ "[anthropic-auth] token_economy.context_hint is deprecated and has no effect: " +
777
+ "the context-hint-2026-04-09 beta and the context_hint body field are no longer sent on any " +
778
+ "request path, because the genuine Claude Code client does not send them and emitting them " +
779
+ "would make requests fingerprintable. Remove the setting from your config to silence this warning.",
780
+ );
781
+ }
782
+
760
783
  config.token_economy = {
761
784
  token_efficient_tools:
762
785
  typeof te.token_efficient_tools === "boolean"
@@ -764,6 +787,11 @@ function validateConfig(raw) {
764
787
  : DEFAULT_CONFIG.token_economy.token_efficient_tools,
765
788
  redact_thinking:
766
789
  typeof te.redact_thinking === "boolean" ? te.redact_thinking : DEFAULT_CONFIG.token_economy.redact_thinking,
790
+ // DEPRECATED: the value is still normalized so an existing config file
791
+ // keeps loading, but nothing reads it any more — the context-hint beta and
792
+ // body field were removed from every request path because the genuine
793
+ // Claude Code 2.1.195 client does not send them. A user switch that turns
794
+ // into a silent no-op is not acceptable, so an explicit opt-in warns once.
767
795
  context_hint: typeof te.context_hint === "boolean" ? te.context_hint : DEFAULT_CONFIG.token_economy.context_hint,
768
796
  conservative: typeof te.conservative === "boolean" ? te.conservative : DEFAULT_CONFIG.token_economy.conservative,
769
797
  debug_dump_bodies:
@@ -1011,8 +1039,6 @@ function validateConfig(raw) {
1011
1039
  const av = /** @type {Record<string, unknown>} */ (raw.anti_verbosity);
1012
1040
  config.anti_verbosity = {
1013
1041
  enabled: typeof av.enabled === "boolean" ? av.enabled : DEFAULT_CONFIG.anti_verbosity.enabled,
1014
- length_anchors:
1015
- typeof av.length_anchors === "boolean" ? av.length_anchors : DEFAULT_CONFIG.anti_verbosity.length_anchors,
1016
1042
  };
1017
1043
  }
1018
1044
 
@@ -1128,10 +1154,6 @@ function applyEnvOverrides(config) {
1128
1154
  if (env.OPENCODE_ANTHROPIC_ANTI_VERBOSITY === "0" || env.OPENCODE_ANTHROPIC_ANTI_VERBOSITY === "false") {
1129
1155
  config.anti_verbosity.enabled = false;
1130
1156
  }
1131
- if (env.OPENCODE_ANTHROPIC_LENGTH_ANCHORS === "0" || env.OPENCODE_ANTHROPIC_LENGTH_ANCHORS === "false") {
1132
- config.anti_verbosity.length_anchors = false;
1133
- }
1134
-
1135
1157
  return config;
1136
1158
  }
1137
1159