opencode-anthropic-fix 0.1.12 → 0.1.14

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/index.mjs CHANGED
@@ -3721,7 +3721,7 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
3721
3721
  cacheBreakState.lastAlertTurn !== sessionMetrics.turns
3722
3722
  ) {
3723
3723
  const drop = cacheBreakState.prevCacheRead - cacheRead;
3724
- let alertMsg = `Cache break detected (u2212${drop.toLocaleString()} tokens)`;
3724
+ let alertMsg = `Cache break detected (−${drop.toLocaleString()} tokens)`;
3725
3725
 
3726
3726
  // Identify changed sources if we have pending hashes
3727
3727
  if (cacheBreakState._pendingHashes && cacheBreakState.sourceHashes.size > 0) {
@@ -4327,6 +4327,30 @@ function extractCacheSourceHashes(bodyStr, parsedBody = undefined) {
4327
4327
  }
4328
4328
  }
4329
4329
  }
4330
+
4331
+ // Hash messages prefix (everything except the last message) so we can
4332
+ // detect when the conversation history prefix changes byte-for-byte
4333
+ // between turns — a common cause of prompt-cache invalidation that
4334
+ // system_prompt/tool hashes alone don't explain.
4335
+ if (Array.isArray(parsed.messages) && parsed.messages.length > 1) {
4336
+ const prefix = parsed.messages.slice(0, -1);
4337
+ // Strip cache_control markers before hashing — they're legitimately
4338
+ // re-stamped each turn and shouldn't trigger a false positive.
4339
+ const normalized = prefix.map((m) => {
4340
+ if (!Array.isArray(m.content)) return m;
4341
+ return {
4342
+ ...m,
4343
+ content: m.content.map((b) => {
4344
+ if (b && typeof b === "object" && b.cache_control) {
4345
+ const { cache_control: _cc, ...rest } = b;
4346
+ return rest;
4347
+ }
4348
+ return b;
4349
+ }),
4350
+ };
4351
+ });
4352
+ hashes.set("messages_prefix", hashCacheSource(JSON.stringify(normalized)));
4353
+ }
4330
4354
  } catch {
4331
4355
  // Ignore parse errors
4332
4356
  }
@@ -7424,7 +7448,17 @@ function transformRequestBody(body, signature, runtime, betaHeader, config) {
7424
7448
  const tes = runtime?.tokenEconomySession;
7425
7449
  const isMainRole = runtime?.requestRole === "main" || runtime?.requestRole == null;
7426
7450
 
7427
- if (isMainRole && Array.isArray(parsed.messages) && tes) {
7451
+ // `conservative` (default ON) disables all history-rewriting and tool-array
7452
+ // transforms. These optimizations shrink each request body but cause the
7453
+ // prompt-cache prefix to change turn-to-turn, invalidating the 1h cache
7454
+ // and forcing a fresh cache_write each turn — which costs 2x base input
7455
+ // tokens. For long opencode sessions, cache reuse dominates; flip to
7456
+ // `false` only if you have measurements showing otherwise. Adaptive
7457
+ // thinking zero-out remains active (affects only the thinking budget,
7458
+ // not cached content).
7459
+ const conservative = te.conservative !== false;
7460
+
7461
+ if (!conservative && isMainRole && Array.isArray(parsed.messages) && tes) {
7428
7462
  // (1) TTL-based thinking strip
7429
7463
  if (te.ttl_thinking_strip !== false) {
7430
7464
  const ttlMs = signature?.cachePolicy?.ttl === "5m" ? 5 * 60_000 : 60 * 60_000;
@@ -7474,7 +7508,7 @@ function transformRequestBody(body, signature, runtime, betaHeader, config) {
7474
7508
  }
7475
7509
 
7476
7510
  // === Token economy: tool-array transforms (stable ordering, deferral) ===
7477
- if (isMainRole && Array.isArray(parsed.tools)) {
7511
+ if (!conservative && isMainRole && Array.isArray(parsed.tools)) {
7478
7512
  if (te.stable_tool_ordering !== false) {
7479
7513
  parsed.tools = applyStableToolOrdering(parsed.tools);
7480
7514
  }
package/lib/config.mjs CHANGED
@@ -201,6 +201,17 @@ export const DEFAULT_CONFIG = {
201
201
  * classified as "main-thread" (see classifyRequestRole). */
202
202
  context_hint: false,
203
203
 
204
+ /** Conservative mode (default ON). Disables every history-rewriting and
205
+ * tool-array transform below that can invalidate the prompt-cache prefix
206
+ * turn-to-turn. Rationale: every rewrite risks breaking the 1h cache,
207
+ * forcing a fresh cache_write (2x base input cost). For long opencode
208
+ * sessions, cache reuse dominates per-turn cost — the optimizations below
209
+ * save kB in the request body but cost much more in cache misses.
210
+ * Affects: ttl_thinking_strip, proactive_microcompact, trailing_summary_trim,
211
+ * tool_result_dedupe, stable_tool_ordering, deferred_tool_names.
212
+ * Unaffected: adaptive_thinking_zero_simple (only changes thinking budget). */
213
+ conservative: true,
214
+
204
215
  /** TTL-based thinking strip (CC parity). When the time since the last
205
216
  * thinking strip exceeds the cache TTL, drop all `thinking` /
206
217
  * `redacted_thinking` blocks from prior assistant messages. Mirrors CC's
@@ -644,6 +655,7 @@ function validateConfig(raw) {
644
655
  redact_thinking:
645
656
  typeof te.redact_thinking === "boolean" ? te.redact_thinking : DEFAULT_CONFIG.token_economy.redact_thinking,
646
657
  context_hint: typeof te.context_hint === "boolean" ? te.context_hint : DEFAULT_CONFIG.token_economy.context_hint,
658
+ conservative: typeof te.conservative === "boolean" ? te.conservative : DEFAULT_CONFIG.token_economy.conservative,
647
659
  ttl_thinking_strip:
648
660
  typeof te.ttl_thinking_strip === "boolean"
649
661
  ? te.ttl_thinking_strip
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-anthropic-fix",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "main": "./index.mjs",
6
6
  "files": [