opencode-anthropic-fix 0.0.37 → 0.0.38
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 +204 -25
- package/lib/config.mjs +14 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -158,6 +158,24 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
158
158
|
let willowLastRequestTime = Date.now();
|
|
159
159
|
let willowLastSuggestionTime = 0;
|
|
160
160
|
|
|
161
|
+
// Beta header latching: once a beta is sent in a session, it stays on for
|
|
162
|
+
// the rest of the session to prevent cache key churn (~50-70K tokens per flip).
|
|
163
|
+
// Cleared on /clear or /compact if needed.
|
|
164
|
+
const betaLatchState = {
|
|
165
|
+
/** @type {Set<string>} betas that have been sent at least once this session */
|
|
166
|
+
sent: new Set(),
|
|
167
|
+
/** When true, a config change invalidated the latch and next request rebuilds. */
|
|
168
|
+
dirty: false,
|
|
169
|
+
/** @type {string | null} The last computed beta header string (for latching). */
|
|
170
|
+
lastHeader: null,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Cache TTL session latching: latch the cache policy at session start
|
|
174
|
+
// so mid-session toggles don't bust the server-side prompt cache.
|
|
175
|
+
let sessionCachePolicyLatched = false;
|
|
176
|
+
/** @type {{ttl: string, ttl_supported: boolean, boundary_marker?: boolean} | null} */
|
|
177
|
+
let latchedCachePolicy = null;
|
|
178
|
+
|
|
161
179
|
/**
|
|
162
180
|
* Whether OPENCODE_ANTHROPIC_INITIAL_ACCOUNT env var pinned this session to a
|
|
163
181
|
* specific account. When true, syncActiveIndexFromDisk is skipped and strategy
|
|
@@ -846,6 +864,43 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
846
864
|
adaptiveContextState.lastTransitionTurn = sessionMetrics.turns;
|
|
847
865
|
}
|
|
848
866
|
},
|
|
867
|
+
"token-efficient-tools": () => {
|
|
868
|
+
const enabled = value === "on" || value === "true" || value === "1";
|
|
869
|
+
const te = config.token_economy || {
|
|
870
|
+
token_efficient_tools: true,
|
|
871
|
+
redact_thinking: false,
|
|
872
|
+
connector_text_summarization: true,
|
|
873
|
+
};
|
|
874
|
+
te.token_efficient_tools = enabled;
|
|
875
|
+
saveConfig({ token_economy: te });
|
|
876
|
+
config.token_economy = te;
|
|
877
|
+
// Invalidate latched betas so the change takes effect next request
|
|
878
|
+
betaLatchState.dirty = true;
|
|
879
|
+
},
|
|
880
|
+
"redact-thinking": () => {
|
|
881
|
+
const enabled = value === "on" || value === "true" || value === "1";
|
|
882
|
+
const te = config.token_economy || {
|
|
883
|
+
token_efficient_tools: true,
|
|
884
|
+
redact_thinking: false,
|
|
885
|
+
connector_text_summarization: true,
|
|
886
|
+
};
|
|
887
|
+
te.redact_thinking = enabled;
|
|
888
|
+
saveConfig({ token_economy: te });
|
|
889
|
+
config.token_economy = te;
|
|
890
|
+
betaLatchState.dirty = true;
|
|
891
|
+
},
|
|
892
|
+
"connector-text": () => {
|
|
893
|
+
const enabled = value === "on" || value === "true" || value === "1";
|
|
894
|
+
const te = config.token_economy || {
|
|
895
|
+
token_efficient_tools: true,
|
|
896
|
+
redact_thinking: false,
|
|
897
|
+
connector_text_summarization: true,
|
|
898
|
+
};
|
|
899
|
+
te.connector_text_summarization = enabled;
|
|
900
|
+
saveConfig({ token_economy: te });
|
|
901
|
+
config.token_economy = te;
|
|
902
|
+
betaLatchState.dirty = true;
|
|
903
|
+
},
|
|
849
904
|
};
|
|
850
905
|
|
|
851
906
|
if (!key || !setters[key]) {
|
|
@@ -2397,7 +2452,10 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2397
2452
|
|
|
2398
2453
|
const _adaptiveOverride = _adaptiveOverrideForRequest;
|
|
2399
2454
|
|
|
2400
|
-
|
|
2455
|
+
// Token economy config (resolved once, passed to beta builder)
|
|
2456
|
+
const _tokenEconomy = config.token_economy || {};
|
|
2457
|
+
|
|
2458
|
+
let computedBetaHeader = buildAnthropicBetaHeader(
|
|
2401
2459
|
"",
|
|
2402
2460
|
getSignatureEmulationEnabled(),
|
|
2403
2461
|
_reqModel,
|
|
@@ -2407,8 +2465,43 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2407
2465
|
requestUrl?.pathname,
|
|
2408
2466
|
_reqHasFileRefs,
|
|
2409
2467
|
_adaptiveOverride,
|
|
2468
|
+
_tokenEconomy,
|
|
2410
2469
|
);
|
|
2411
2470
|
|
|
2471
|
+
// Beta header latching: once a beta has been sent in this session,
|
|
2472
|
+
// keep sending it to avoid server-side cache key churn (~50-70K tokens
|
|
2473
|
+
// per flip). The latch is sticky-on: new betas can be added but never
|
|
2474
|
+
// removed mid-session unless explicitly invalidated by config change.
|
|
2475
|
+
{
|
|
2476
|
+
const currentBetas = computedBetaHeader
|
|
2477
|
+
.split(",")
|
|
2478
|
+
.map((b) => b.trim())
|
|
2479
|
+
.filter(Boolean);
|
|
2480
|
+
// Add all current betas to the latch set
|
|
2481
|
+
for (const b of currentBetas) betaLatchState.sent.add(b);
|
|
2482
|
+
// If a config change dirtied the latch, rebuild from current (allow removal)
|
|
2483
|
+
if (betaLatchState.dirty) {
|
|
2484
|
+
betaLatchState.dirty = false;
|
|
2485
|
+
betaLatchState.sent = new Set(currentBetas);
|
|
2486
|
+
}
|
|
2487
|
+
// Merge latched betas that aren't in the current set
|
|
2488
|
+
const merged = new Set(currentBetas);
|
|
2489
|
+
for (const b of betaLatchState.sent) merged.add(b);
|
|
2490
|
+
computedBetaHeader = [...merged].join(",");
|
|
2491
|
+
betaLatchState.lastHeader = computedBetaHeader;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// Cache TTL session latching: latch the cache policy at session start
|
|
2495
|
+
// so mid-session toggles don't bust the server-side prompt cache.
|
|
2496
|
+
if (!sessionCachePolicyLatched) {
|
|
2497
|
+
sessionCachePolicyLatched = true;
|
|
2498
|
+
latchedCachePolicy = config.cache_policy
|
|
2499
|
+
? { ...config.cache_policy }
|
|
2500
|
+
: { ttl: "1h", ttl_supported: true };
|
|
2501
|
+
}
|
|
2502
|
+
const effectiveCachePolicy = latchedCachePolicy ||
|
|
2503
|
+
config.cache_policy || { ttl: "1h", ttl_supported: true };
|
|
2504
|
+
|
|
2412
2505
|
let body = transformRequestBody(
|
|
2413
2506
|
requestInit.body,
|
|
2414
2507
|
{
|
|
@@ -2416,7 +2509,7 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2416
2509
|
claudeCliVersion,
|
|
2417
2510
|
promptCompactionMode: getPromptCompactionMode(),
|
|
2418
2511
|
provider: _reqProvider,
|
|
2419
|
-
cachePolicy:
|
|
2512
|
+
cachePolicy: effectiveCachePolicy,
|
|
2420
2513
|
fastMode: config.fast_mode || false,
|
|
2421
2514
|
strategy: getEffectiveStrategy(),
|
|
2422
2515
|
},
|
|
@@ -2443,6 +2536,7 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2443
2536
|
strategy: getEffectiveStrategy(),
|
|
2444
2537
|
},
|
|
2445
2538
|
_adaptiveOverride,
|
|
2539
|
+
_tokenEconomy,
|
|
2446
2540
|
);
|
|
2447
2541
|
// Execute the request
|
|
2448
2542
|
let response;
|
|
@@ -2629,10 +2723,15 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2629
2723
|
// Auto-escalate adaptive context on prompt_too_long so the retry
|
|
2630
2724
|
// includes the 1M beta header (if model supports it).
|
|
2631
2725
|
if (config.adaptive_context?.enabled) {
|
|
2632
|
-
forceEscalateAdaptiveContext();
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2726
|
+
const stateChanged = forceEscalateAdaptiveContext();
|
|
2727
|
+
if (stateChanged) {
|
|
2728
|
+
// Invalidate cached adaptive decision so the retry loop
|
|
2729
|
+
// re-evaluates with the new active=true state.
|
|
2730
|
+
_adaptiveDecisionMade = false;
|
|
2731
|
+
toast("⬡ 1M context force-activated (prompt too long)", "warning", {
|
|
2732
|
+
debounceKey: "adaptive-ctx",
|
|
2733
|
+
}).catch(() => {});
|
|
2734
|
+
}
|
|
2636
2735
|
}
|
|
2637
2736
|
try {
|
|
2638
2737
|
const parsedBody = JSON.parse(body);
|
|
@@ -3215,7 +3314,7 @@ function estimatePromptTokens(bodyString) {
|
|
|
3215
3314
|
* @returns {boolean}
|
|
3216
3315
|
*/
|
|
3217
3316
|
function resolveAdaptiveContext(bodyString, model, adaptiveConfig) {
|
|
3218
|
-
// Non-adaptive: use static check
|
|
3317
|
+
// Non-adaptive: use static check (only explicit "1m" models)
|
|
3219
3318
|
if (!adaptiveConfig.enabled) {
|
|
3220
3319
|
return hasOneMillionContext(model);
|
|
3221
3320
|
}
|
|
@@ -3225,8 +3324,8 @@ function resolveAdaptiveContext(bodyString, model, adaptiveConfig) {
|
|
|
3225
3324
|
return false;
|
|
3226
3325
|
}
|
|
3227
3326
|
|
|
3228
|
-
// Model must be eligible for 1M context at all
|
|
3229
|
-
if (!
|
|
3327
|
+
// Model must be eligible for 1M context at all (includes Opus 4.6)
|
|
3328
|
+
if (!isEligibleFor1MContext(model)) {
|
|
3230
3329
|
return false;
|
|
3231
3330
|
}
|
|
3232
3331
|
|
|
@@ -3235,10 +3334,25 @@ function resolveAdaptiveContext(bodyString, model, adaptiveConfig) {
|
|
|
3235
3334
|
|
|
3236
3335
|
if (adaptiveContextState.active) {
|
|
3237
3336
|
// Currently active — consider de-escalation
|
|
3238
|
-
|
|
3337
|
+
|
|
3338
|
+
// Error-escalated: sticky for ERROR_STICKY_TURNS turns, then allow de-escalation
|
|
3339
|
+
// if prompt has dropped well below threshold (prevents permanent 1M lock-in).
|
|
3340
|
+
const ERROR_STICKY_TURNS = 5;
|
|
3239
3341
|
if (adaptiveContextState.escalatedByError) {
|
|
3240
|
-
|
|
3342
|
+
if (turnsSinceTransition < ERROR_STICKY_TURNS) {
|
|
3343
|
+
return true; // Still within sticky window
|
|
3344
|
+
}
|
|
3345
|
+
// Past sticky window: allow de-escalation if tokens dropped significantly
|
|
3346
|
+
// (below 75% of deescalation threshold to avoid flapping)
|
|
3347
|
+
if (estimatedTokens < adaptiveConfig.deescalation_threshold * 0.75) {
|
|
3348
|
+
adaptiveContextState.active = false;
|
|
3349
|
+
adaptiveContextState.escalatedByError = false;
|
|
3350
|
+
adaptiveContextState.lastTransitionTurn = sessionMetrics.turns;
|
|
3351
|
+
return false;
|
|
3352
|
+
}
|
|
3353
|
+
return true; // Still high enough to keep 1M
|
|
3241
3354
|
}
|
|
3355
|
+
|
|
3242
3356
|
// Hysteresis: require at least 2 turns before considering de-escalation
|
|
3243
3357
|
if (turnsSinceTransition < 2) {
|
|
3244
3358
|
return true;
|
|
@@ -3268,13 +3382,17 @@ function resolveAdaptiveContext(bodyString, model, adaptiveConfig) {
|
|
|
3268
3382
|
|
|
3269
3383
|
/**
|
|
3270
3384
|
* Force-escalate adaptive context (e.g. after prompt_too_long error).
|
|
3385
|
+
* Returns true so callers can invalidate cached decisions.
|
|
3386
|
+
* @returns {boolean}
|
|
3271
3387
|
*/
|
|
3272
3388
|
function forceEscalateAdaptiveContext() {
|
|
3389
|
+
const wasActive = adaptiveContextState.active;
|
|
3273
3390
|
if (!adaptiveContextState.active) {
|
|
3274
3391
|
adaptiveContextState.active = true;
|
|
3275
3392
|
adaptiveContextState.lastTransitionTurn = sessionMetrics.turns;
|
|
3276
3393
|
}
|
|
3277
3394
|
adaptiveContextState.escalatedByError = true;
|
|
3395
|
+
return !wasActive; // true if state actually changed
|
|
3278
3396
|
}
|
|
3279
3397
|
|
|
3280
3398
|
const MODEL_PRICING = {
|
|
@@ -3656,6 +3774,8 @@ const EXPERIMENTAL_BETA_FLAGS = new Set([
|
|
|
3656
3774
|
"prompt-caching-scope-2026-01-05",
|
|
3657
3775
|
"redact-thinking-2026-02-12",
|
|
3658
3776
|
"structured-outputs-2025-12-15",
|
|
3777
|
+
"summarize-connector-text-2026-03-13",
|
|
3778
|
+
"token-efficient-tools-2026-03-28",
|
|
3659
3779
|
"tool-examples-2025-10-29",
|
|
3660
3780
|
"tool-search-tool-2025-10-19",
|
|
3661
3781
|
"web-search-2025-03-05",
|
|
@@ -3669,6 +3789,11 @@ const BETA_SHORTCUTS = new Map([
|
|
|
3669
3789
|
["opus-fast", "fast-mode-2026-02-01"],
|
|
3670
3790
|
["task-budgets", "task-budgets-2026-03-13"],
|
|
3671
3791
|
["budgets", "task-budgets-2026-03-13"],
|
|
3792
|
+
["efficient-tools", "token-efficient-tools-2026-03-28"],
|
|
3793
|
+
["token-efficient", "token-efficient-tools-2026-03-28"],
|
|
3794
|
+
["redact-thinking", "redact-thinking-2026-02-12"],
|
|
3795
|
+
["connector-text", "summarize-connector-text-2026-03-13"],
|
|
3796
|
+
["anti-distill", "summarize-connector-text-2026-03-13"],
|
|
3672
3797
|
]);
|
|
3673
3798
|
const STAINLESS_HELPER_KEYS = [
|
|
3674
3799
|
"x_stainless_helper",
|
|
@@ -3884,14 +4009,26 @@ function isAdaptiveThinkingModel(model) {
|
|
|
3884
4009
|
}
|
|
3885
4010
|
|
|
3886
4011
|
/**
|
|
3887
|
-
*
|
|
4012
|
+
* Check if a model is eligible for 1M context (can receive context-1m beta).
|
|
4013
|
+
* This includes models with explicit "1m" in the name AND Opus 4.6.
|
|
4014
|
+
* @param {string} model
|
|
3888
4015
|
* @returns {boolean}
|
|
3889
4016
|
*/
|
|
3890
|
-
function
|
|
3891
|
-
// Models with explicit 1m suffix, or Opus 4.6 (1M by default since v2.1.75).
|
|
4017
|
+
function isEligibleFor1MContext(model) {
|
|
3892
4018
|
return /(^|[-_ ])1m($|[-_ ])|context[-_]?1m/i.test(model) || isOpus46Model(model);
|
|
3893
4019
|
}
|
|
3894
4020
|
|
|
4021
|
+
/**
|
|
4022
|
+
* Check if a model should ALWAYS use 1M context (static mode, no adaptive gating).
|
|
4023
|
+
* Only models with explicit "1m" in the name — NOT bare Opus 4.6.
|
|
4024
|
+
* When adaptive_context is enabled, Opus 4.6 uses the adaptive decision instead.
|
|
4025
|
+
* @param {string} model
|
|
4026
|
+
* @returns {boolean}
|
|
4027
|
+
*/
|
|
4028
|
+
function hasOneMillionContext(model) {
|
|
4029
|
+
return /(^|[-_ ])1m($|[-_ ])|context[-_]?1m/i.test(model);
|
|
4030
|
+
}
|
|
4031
|
+
|
|
3895
4032
|
/**
|
|
3896
4033
|
* @param {string | undefined} body
|
|
3897
4034
|
* @returns {boolean}
|
|
@@ -4324,6 +4461,7 @@ function buildAnthropicBetaHeader(
|
|
|
4324
4461
|
requestPath,
|
|
4325
4462
|
hasFileReferences,
|
|
4326
4463
|
adaptiveOverride,
|
|
4464
|
+
tokenEconomy,
|
|
4327
4465
|
) {
|
|
4328
4466
|
const incomingBetasList = incomingBeta
|
|
4329
4467
|
.split(",")
|
|
@@ -4350,13 +4488,23 @@ function buildAnthropicBetaHeader(
|
|
|
4350
4488
|
const nonInteractive = isNonInteractiveMode();
|
|
4351
4489
|
const haiku = isHaikuModel(model);
|
|
4352
4490
|
const isRoundRobin = strategy === "round-robin";
|
|
4491
|
+
const te = tokenEconomy || {};
|
|
4353
4492
|
|
|
4354
4493
|
// === ALWAYS-ON BETAS (Claude Code v2.1.84 base set) ===
|
|
4355
4494
|
// These are ALWAYS included regardless of env vars or feature flags.
|
|
4356
4495
|
if (!haiku) {
|
|
4357
4496
|
betas.push(CLAUDE_CODE_BETA_FLAG); // "claude-code-20250219"
|
|
4358
4497
|
}
|
|
4359
|
-
|
|
4498
|
+
|
|
4499
|
+
// Tool search: use provider-aware header.
|
|
4500
|
+
// 1P/Foundry → advanced-tool-use-2025-11-20 (enables broader tool capabilities)
|
|
4501
|
+
// Vertex/Bedrock → tool-search-tool-2025-10-19 (3P-compatible subset)
|
|
4502
|
+
if (provider === "vertex" || provider === "bedrock") {
|
|
4503
|
+
betas.push("tool-search-tool-2025-10-19");
|
|
4504
|
+
} else {
|
|
4505
|
+
betas.push(ADVANCED_TOOL_USE_BETA_FLAG); // "advanced-tool-use-2025-11-20"
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4360
4508
|
betas.push(FAST_MODE_BETA_FLAG); // "fast-mode-2026-02-01"
|
|
4361
4509
|
betas.push(EFFORT_BETA_FLAG); // "effort-2025-11-24"
|
|
4362
4510
|
|
|
@@ -4386,8 +4534,14 @@ function buildAnthropicBetaHeader(
|
|
|
4386
4534
|
// Context management — always-on in Claude Code (not env-gated)
|
|
4387
4535
|
betas.push("context-management-2025-06-27");
|
|
4388
4536
|
|
|
4389
|
-
// Structured outputs
|
|
4390
|
-
|
|
4537
|
+
// Structured outputs vs token-efficient-tools: mutually exclusive.
|
|
4538
|
+
// token-efficient-tools gives ~4.5% output token reduction (JSON FC v3).
|
|
4539
|
+
// When both are configured, token-efficient-tools wins (per CC source:
|
|
4540
|
+
// "strict wins" internally, but we prefer token savings for proxy users).
|
|
4541
|
+
if (te.token_efficient_tools && !disableExperimentalBetas) {
|
|
4542
|
+
betas.push("token-efficient-tools-2026-03-28");
|
|
4543
|
+
// Skip structured-outputs — API rejects both together
|
|
4544
|
+
} else if (supportsStructuredOutputs(model)) {
|
|
4391
4545
|
betas.push("structured-outputs-2025-12-15");
|
|
4392
4546
|
}
|
|
4393
4547
|
|
|
@@ -4396,9 +4550,6 @@ function buildAnthropicBetaHeader(
|
|
|
4396
4550
|
betas.push("web-search-2025-03-05");
|
|
4397
4551
|
}
|
|
4398
4552
|
|
|
4399
|
-
// Tool search tool
|
|
4400
|
-
betas.push("tool-search-tool-2025-10-19");
|
|
4401
|
-
|
|
4402
4553
|
// Files API — scoped to file endpoints/references
|
|
4403
4554
|
if (isFilesEndpoint || hasFileReferences) {
|
|
4404
4555
|
betas.push("files-api-2025-04-14");
|
|
@@ -4409,10 +4560,20 @@ function buildAnthropicBetaHeader(
|
|
|
4409
4560
|
betas.push(TOKEN_COUNTING_BETA_FLAG);
|
|
4410
4561
|
}
|
|
4411
4562
|
|
|
4412
|
-
// ===
|
|
4563
|
+
// === TOKEN ECONOMY BETAS (config-controlled) ===
|
|
4564
|
+
|
|
4565
|
+
// redact-thinking: suppresses thinking summaries server-side.
|
|
4566
|
+
// When enabled, API returns redacted_thinking blocks instead of summaries.
|
|
4567
|
+
// Saves bandwidth and tokens for users who don't inspect thinking.
|
|
4568
|
+
if (te.redact_thinking && !disableExperimentalBetas) {
|
|
4569
|
+
betas.push("redact-thinking-2026-02-12");
|
|
4570
|
+
}
|
|
4413
4571
|
|
|
4414
|
-
//
|
|
4415
|
-
//
|
|
4572
|
+
// summarize-connector-text: API summarizes assistant text between tool calls.
|
|
4573
|
+
// Anti-distillation measure — same mechanism as thinking blocks (summary + signature).
|
|
4574
|
+
if (te.connector_text_summarization && !disableExperimentalBetas) {
|
|
4575
|
+
betas.push("summarize-connector-text-2026-03-13");
|
|
4576
|
+
}
|
|
4416
4577
|
|
|
4417
4578
|
// afk-mode — NOT auto-included (requires user opt-in)
|
|
4418
4579
|
// Available via: /anthropic betas add afk-mode-2026-01-31
|
|
@@ -4559,7 +4720,16 @@ async function fetchLatestClaudeCodeVersion(timeoutMs = 1200) {
|
|
|
4559
4720
|
* @param {{enabled: boolean, claudeCliVersion: string, strategy?: import('./lib/config.mjs').AccountSelectionStrategy, customBetas?: string[]}} signature
|
|
4560
4721
|
* @returns {Headers}
|
|
4561
4722
|
*/
|
|
4562
|
-
function buildRequestHeaders(
|
|
4723
|
+
function buildRequestHeaders(
|
|
4724
|
+
input,
|
|
4725
|
+
requestInit,
|
|
4726
|
+
accessToken,
|
|
4727
|
+
requestBody,
|
|
4728
|
+
requestUrl,
|
|
4729
|
+
signature,
|
|
4730
|
+
adaptiveOverride,
|
|
4731
|
+
tokenEconomy,
|
|
4732
|
+
) {
|
|
4563
4733
|
const requestHeaders = new Headers();
|
|
4564
4734
|
if (input instanceof Request) {
|
|
4565
4735
|
input.headers.forEach((value, key) => {
|
|
@@ -4600,6 +4770,7 @@ function buildRequestHeaders(input, requestInit, accessToken, requestBody, reque
|
|
|
4600
4770
|
requestUrl?.pathname,
|
|
4601
4771
|
hasFileReferences,
|
|
4602
4772
|
adaptiveOverride,
|
|
4773
|
+
tokenEconomy,
|
|
4603
4774
|
);
|
|
4604
4775
|
|
|
4605
4776
|
const authTokenOverride = process.env.ANTHROPIC_AUTH_TOKEN?.trim();
|
|
@@ -4753,7 +4924,15 @@ function transformRequestBody(body, signature, runtime, betaHeader) {
|
|
|
4753
4924
|
// Cache breakpoint optimization: add cache_control to the last content block
|
|
4754
4925
|
// of each user/assistant message for maximum prefix caching.
|
|
4755
4926
|
// Skip for round-robin strategy (cache defeated by account rotation).
|
|
4756
|
-
|
|
4927
|
+
// Skip for title generators / fire-and-forget queries: these are one-shot
|
|
4928
|
+
// requests that don't benefit from caching and would pollute the cache pool.
|
|
4929
|
+
const isTitleGen = isTitleGeneratorSystemBlocks(parsed.system || []);
|
|
4930
|
+
if (
|
|
4931
|
+
signature.enabled &&
|
|
4932
|
+
signature.cachePolicy?.ttl !== "off" &&
|
|
4933
|
+
signature.cachePolicy?.ttl_supported !== false &&
|
|
4934
|
+
!isTitleGen
|
|
4935
|
+
) {
|
|
4757
4936
|
const strategy = signature.strategy || "sticky";
|
|
4758
4937
|
if (strategy !== "round-robin" && Array.isArray(parsed.messages)) {
|
|
4759
4938
|
const cacheControl = { type: "ephemeral", ttl: signature.cachePolicy?.ttl || "1h" };
|
package/lib/config.mjs
CHANGED
|
@@ -166,6 +166,19 @@ export const DEFAULT_CONFIG = {
|
|
|
166
166
|
* (no point suggesting reset at the start of a session). */
|
|
167
167
|
min_turns_before_suggest: 3,
|
|
168
168
|
},
|
|
169
|
+
/** Token economy optimizations. */
|
|
170
|
+
token_economy: {
|
|
171
|
+
/** Enable token-efficient-tools-2026-03-28 beta (~4.5% output token reduction).
|
|
172
|
+
* Mutually exclusive with structured-outputs — this flag takes precedence. */
|
|
173
|
+
token_efficient_tools: true,
|
|
174
|
+
/** Enable redact-thinking-2026-02-12 beta (suppresses thinking summaries
|
|
175
|
+
* server-side for lower bandwidth). Off by default — set to true if you
|
|
176
|
+
* don't need to inspect thinking blocks. */
|
|
177
|
+
redact_thinking: false,
|
|
178
|
+
/** Enable summarize-connector-text-2026-03-13 beta (API summarizes assistant
|
|
179
|
+
* text between tool calls — anti-distillation measure). */
|
|
180
|
+
connector_text_summarization: true,
|
|
181
|
+
},
|
|
169
182
|
};
|
|
170
183
|
|
|
171
184
|
export const VALID_STRATEGIES = ["sticky", "round-robin", "hybrid"];
|
|
@@ -194,6 +207,7 @@ function createDefaultConfig() {
|
|
|
194
207
|
cc_credential_reuse: { ...DEFAULT_CONFIG.cc_credential_reuse },
|
|
195
208
|
adaptive_context: { ...DEFAULT_CONFIG.adaptive_context },
|
|
196
209
|
willow_mode: { ...DEFAULT_CONFIG.willow_mode },
|
|
210
|
+
token_economy: { ...DEFAULT_CONFIG.token_economy },
|
|
197
211
|
};
|
|
198
212
|
}
|
|
199
213
|
|