opencode-auth-load-balancer 0.4.0 → 0.4.1

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.
@@ -83,6 +83,33 @@ export declare const FALLBACK_CLAUDE_CODE_VERSION = "2.1.258";
83
83
  * `cch` fingerprint algorithm and the newest version starts 400ing).
84
84
  */
85
85
  export declare const CLAUDE_CODE_VERSION_ENV = "OPENCODE_AUTH_LB_ANTHROPIC_CLAUDE_CODE_VERSION";
86
+ /**
87
+ * The structured error code Anthropic answers a version-gated model with:
88
+ *
89
+ * ```json
90
+ * {"type":"error","error":{"type":"invalid_request_error",
91
+ * "message":"Claude Code 2.1.278 does not support this model; version 2.1.280 or newer is required.",
92
+ * "details":{"error_code":"claude_code_version_too_old"}}}
93
+ * ```
94
+ *
95
+ * Matched as a SUBSTRING of the raw body rather than through a parsed path
96
+ * (`error.details.error_code`): the envelope's shape is Anthropic's to change,
97
+ * and this string appearing anywhere in a rejection is both sufficient — no
98
+ * other error carries it — and immune to a reshuffle that would silently break
99
+ * a path lookup. See `recoverClaudeCodeVersion` in version.ts.
100
+ */
101
+ export declare const CLAUDE_CODE_VERSION_GATE_CODE = "claude_code_version_too_old";
102
+ /**
103
+ * The MINIMUM version that rejection's human-readable message demands.
104
+ *
105
+ * Anchored on the `or newer is required` tail rather than the first version in
106
+ * the string, because the message names TWO: the one we just sent ("Claude
107
+ * Code 2.1.278 does not support this model") and the one being asked for
108
+ * ("version 2.1.280 or newer is required"). Capturing the leading one would
109
+ * adopt the version that just failed — a no-op the recovery would correctly
110
+ * refuse to retry, i.e. a silent regression to the old stuck behavior.
111
+ */
112
+ export declare const CLAUDE_CODE_REQUIRED_VERSION_RE: RegExp;
86
113
  /**
87
114
  * npm dist-tags for the Claude Code CLI — `{"latest":"2.1.258",…}`, ~56 bytes.
88
115
  * Deliberately NOT `/@anthropic-ai/claude-code/latest`, which returns the whole
@@ -20,6 +20,27 @@ export declare function usageUserAgent(): string;
20
20
  * usable answer, so a failure does NOT refresh the TTL).
21
21
  */
22
22
  export declare function refreshClaudeCodeVersion(now: number): Promise<void>;
23
+ /**
24
+ * REACTIVE recovery, driven by the gate's own rejection.
25
+ *
26
+ * `primeClaudeCodeVersion` resolves once at startup and then trusts the cache
27
+ * for `CLAUDE_CODE_VERSION_TTL_MS`. A model that launches INSIDE that window is
28
+ * therefore rejected for as long as the TTL has left to run — up to a full day
29
+ * of hard failures — even when npm already tags the version Anthropic is asking
30
+ * for, and even though the rejection itself NAMES that version. No polling
31
+ * cadence fixes this in general either: Anthropic can gate a model before npm
32
+ * tags the release, which is why the env pin exists as a manual escape hatch.
33
+ *
34
+ * So learn from the rejection instead. Adopt the minimum it demands, then ask
35
+ * npm for the real `latest` in the same breath — the minimum only clears the
36
+ * model that just failed, whereas `latest` is what the NEXT launch will want.
37
+ *
38
+ * Returns whether the reported version actually MOVED, which is precisely the
39
+ * condition under which a retry can succeed; the caller spends a second
40
+ * round-trip on true and passes the rejection through untouched on false.
41
+ * `res` is consumed, so callers that still need the body must pass a clone.
42
+ */
43
+ export declare function recoverClaudeCodeVersion(res: Response, now: number): Promise<boolean>;
23
44
  /**
24
45
  * Startup hook. Awaits only the LOCAL cache read — cheap, and enough to have
25
46
  * the right version in hand before the loader returns the fetch — then leaves
@@ -136,4 +136,24 @@ export interface ProviderAdapter {
136
136
  * downgraded body. null = treat the 429 as a normal account rotation.
137
137
  */
138
138
  planReactiveFallback?(res: Response, body: string, now: number, models: readonly string[]): ReactiveModelFallback | null;
139
+ /**
140
+ * REACTIVE recovery from a CLIENT-VERSION gate: a provider may reject an
141
+ * otherwise-valid request until the plugin claims to be a newer client
142
+ * (Anthropic answers a too-new model with 400 `claude_code_version_too_old`,
143
+ * naming the minimum it wants). Such a status classifies as `ok` — the fetch
144
+ * loop is one statement away from handing it to the caller — so this is the
145
+ * last point at which it can become a recoverable event instead of a failed
146
+ * turn.
147
+ *
148
+ * Return true ONLY when the version the adapter will now report actually
149
+ * changed: the fetch loop reads that as "a retry can succeed" and restarts
150
+ * the request once. Returning true without a change would buy a second,
151
+ * byte-identical round-trip and the same rejection.
152
+ *
153
+ * `res` is a CLONE the implementation may consume freely; the original is
154
+ * still returned to the caller when this resolves false. Absent on providers
155
+ * with no such gate (OpenAI) — the fetch loop treats that as "nothing to
156
+ * recover".
157
+ */
158
+ recoverClientVersion?(res: Response, now: number): Promise<boolean>;
139
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auth-load-balancer",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Load-balance opencode requests across multiple Anthropic (Claude) and OpenAI (Codex) OAuth accounts, weighted primarily by weekly usage with drain-before-reset scheduling.",
5
5
  "type": "module",
6
6
  "license": "MIT",