opencode-anthropic-fix 0.0.36 → 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 +209 -29
- 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 = {
|
|
@@ -3556,17 +3674,18 @@ process.once("beforeExit", () => {
|
|
|
3556
3674
|
// Request building helpers (extracted from original fetch interceptor)
|
|
3557
3675
|
// ---------------------------------------------------------------------------
|
|
3558
3676
|
|
|
3559
|
-
const FALLBACK_CLAUDE_CLI_VERSION = "2.1.
|
|
3677
|
+
const FALLBACK_CLAUDE_CLI_VERSION = "2.1.88";
|
|
3560
3678
|
const CLAUDE_CODE_NPM_LATEST_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
|
|
3561
|
-
const CLAUDE_CODE_BUILD_TIME = "2026-03-
|
|
3679
|
+
const CLAUDE_CODE_BUILD_TIME = "2026-03-30T21:59:52Z";
|
|
3562
3680
|
|
|
3563
|
-
// The @anthropic-ai/sdk version bundled with Claude Code v2.1.
|
|
3681
|
+
// The @anthropic-ai/sdk version bundled with Claude Code v2.1.88.
|
|
3564
3682
|
// This is distinct from the CLI version and goes in X-Stainless-Package-Version.
|
|
3565
|
-
// Verified by extracting VERSION="0.208.0" from the bundled cli.js of all versions .83-.
|
|
3683
|
+
// Verified by extracting VERSION="0.208.0" from the bundled cli.js of all versions .83-.88.
|
|
3566
3684
|
const ANTHROPIC_SDK_VERSION = "0.208.0";
|
|
3567
3685
|
|
|
3568
3686
|
// Map of CLI version → bundled SDK version (update when CLI version changes)
|
|
3569
3687
|
const CLI_TO_SDK_VERSION = new Map([
|
|
3688
|
+
["2.1.88", "0.208.0"],
|
|
3570
3689
|
["2.1.87", "0.208.0"],
|
|
3571
3690
|
["2.1.86", "0.208.0"],
|
|
3572
3691
|
["2.1.85", "0.208.0"],
|
|
@@ -3655,6 +3774,8 @@ const EXPERIMENTAL_BETA_FLAGS = new Set([
|
|
|
3655
3774
|
"prompt-caching-scope-2026-01-05",
|
|
3656
3775
|
"redact-thinking-2026-02-12",
|
|
3657
3776
|
"structured-outputs-2025-12-15",
|
|
3777
|
+
"summarize-connector-text-2026-03-13",
|
|
3778
|
+
"token-efficient-tools-2026-03-28",
|
|
3658
3779
|
"tool-examples-2025-10-29",
|
|
3659
3780
|
"tool-search-tool-2025-10-19",
|
|
3660
3781
|
"web-search-2025-03-05",
|
|
@@ -3668,6 +3789,11 @@ const BETA_SHORTCUTS = new Map([
|
|
|
3668
3789
|
["opus-fast", "fast-mode-2026-02-01"],
|
|
3669
3790
|
["task-budgets", "task-budgets-2026-03-13"],
|
|
3670
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"],
|
|
3671
3797
|
]);
|
|
3672
3798
|
const STAINLESS_HELPER_KEYS = [
|
|
3673
3799
|
"x_stainless_helper",
|
|
@@ -3883,14 +4009,26 @@ function isAdaptiveThinkingModel(model) {
|
|
|
3883
4009
|
}
|
|
3884
4010
|
|
|
3885
4011
|
/**
|
|
3886
|
-
*
|
|
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
|
|
3887
4015
|
* @returns {boolean}
|
|
3888
4016
|
*/
|
|
3889
|
-
function
|
|
3890
|
-
// Models with explicit 1m suffix, or Opus 4.6 (1M by default since v2.1.75).
|
|
4017
|
+
function isEligibleFor1MContext(model) {
|
|
3891
4018
|
return /(^|[-_ ])1m($|[-_ ])|context[-_]?1m/i.test(model) || isOpus46Model(model);
|
|
3892
4019
|
}
|
|
3893
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
|
+
|
|
3894
4032
|
/**
|
|
3895
4033
|
* @param {string | undefined} body
|
|
3896
4034
|
* @returns {boolean}
|
|
@@ -4323,6 +4461,7 @@ function buildAnthropicBetaHeader(
|
|
|
4323
4461
|
requestPath,
|
|
4324
4462
|
hasFileReferences,
|
|
4325
4463
|
adaptiveOverride,
|
|
4464
|
+
tokenEconomy,
|
|
4326
4465
|
) {
|
|
4327
4466
|
const incomingBetasList = incomingBeta
|
|
4328
4467
|
.split(",")
|
|
@@ -4349,13 +4488,23 @@ function buildAnthropicBetaHeader(
|
|
|
4349
4488
|
const nonInteractive = isNonInteractiveMode();
|
|
4350
4489
|
const haiku = isHaikuModel(model);
|
|
4351
4490
|
const isRoundRobin = strategy === "round-robin";
|
|
4491
|
+
const te = tokenEconomy || {};
|
|
4352
4492
|
|
|
4353
4493
|
// === ALWAYS-ON BETAS (Claude Code v2.1.84 base set) ===
|
|
4354
4494
|
// These are ALWAYS included regardless of env vars or feature flags.
|
|
4355
4495
|
if (!haiku) {
|
|
4356
4496
|
betas.push(CLAUDE_CODE_BETA_FLAG); // "claude-code-20250219"
|
|
4357
4497
|
}
|
|
4358
|
-
|
|
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
|
+
|
|
4359
4508
|
betas.push(FAST_MODE_BETA_FLAG); // "fast-mode-2026-02-01"
|
|
4360
4509
|
betas.push(EFFORT_BETA_FLAG); // "effort-2025-11-24"
|
|
4361
4510
|
|
|
@@ -4385,8 +4534,14 @@ function buildAnthropicBetaHeader(
|
|
|
4385
4534
|
// Context management — always-on in Claude Code (not env-gated)
|
|
4386
4535
|
betas.push("context-management-2025-06-27");
|
|
4387
4536
|
|
|
4388
|
-
// Structured outputs
|
|
4389
|
-
|
|
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)) {
|
|
4390
4545
|
betas.push("structured-outputs-2025-12-15");
|
|
4391
4546
|
}
|
|
4392
4547
|
|
|
@@ -4395,9 +4550,6 @@ function buildAnthropicBetaHeader(
|
|
|
4395
4550
|
betas.push("web-search-2025-03-05");
|
|
4396
4551
|
}
|
|
4397
4552
|
|
|
4398
|
-
// Tool search tool
|
|
4399
|
-
betas.push("tool-search-tool-2025-10-19");
|
|
4400
|
-
|
|
4401
4553
|
// Files API — scoped to file endpoints/references
|
|
4402
4554
|
if (isFilesEndpoint || hasFileReferences) {
|
|
4403
4555
|
betas.push("files-api-2025-04-14");
|
|
@@ -4408,10 +4560,20 @@ function buildAnthropicBetaHeader(
|
|
|
4408
4560
|
betas.push(TOKEN_COUNTING_BETA_FLAG);
|
|
4409
4561
|
}
|
|
4410
4562
|
|
|
4411
|
-
// ===
|
|
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
|
+
}
|
|
4412
4571
|
|
|
4413
|
-
//
|
|
4414
|
-
//
|
|
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
|
+
}
|
|
4415
4577
|
|
|
4416
4578
|
// afk-mode — NOT auto-included (requires user opt-in)
|
|
4417
4579
|
// Available via: /anthropic betas add afk-mode-2026-01-31
|
|
@@ -4558,7 +4720,16 @@ async function fetchLatestClaudeCodeVersion(timeoutMs = 1200) {
|
|
|
4558
4720
|
* @param {{enabled: boolean, claudeCliVersion: string, strategy?: import('./lib/config.mjs').AccountSelectionStrategy, customBetas?: string[]}} signature
|
|
4559
4721
|
* @returns {Headers}
|
|
4560
4722
|
*/
|
|
4561
|
-
function buildRequestHeaders(
|
|
4723
|
+
function buildRequestHeaders(
|
|
4724
|
+
input,
|
|
4725
|
+
requestInit,
|
|
4726
|
+
accessToken,
|
|
4727
|
+
requestBody,
|
|
4728
|
+
requestUrl,
|
|
4729
|
+
signature,
|
|
4730
|
+
adaptiveOverride,
|
|
4731
|
+
tokenEconomy,
|
|
4732
|
+
) {
|
|
4562
4733
|
const requestHeaders = new Headers();
|
|
4563
4734
|
if (input instanceof Request) {
|
|
4564
4735
|
input.headers.forEach((value, key) => {
|
|
@@ -4599,6 +4770,7 @@ function buildRequestHeaders(input, requestInit, accessToken, requestBody, reque
|
|
|
4599
4770
|
requestUrl?.pathname,
|
|
4600
4771
|
hasFileReferences,
|
|
4601
4772
|
adaptiveOverride,
|
|
4773
|
+
tokenEconomy,
|
|
4602
4774
|
);
|
|
4603
4775
|
|
|
4604
4776
|
const authTokenOverride = process.env.ANTHROPIC_AUTH_TOKEN?.trim();
|
|
@@ -4752,7 +4924,15 @@ function transformRequestBody(body, signature, runtime, betaHeader) {
|
|
|
4752
4924
|
// Cache breakpoint optimization: add cache_control to the last content block
|
|
4753
4925
|
// of each user/assistant message for maximum prefix caching.
|
|
4754
4926
|
// Skip for round-robin strategy (cache defeated by account rotation).
|
|
4755
|
-
|
|
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
|
+
) {
|
|
4756
4936
|
const strategy = signature.strategy || "sticky";
|
|
4757
4937
|
if (strategy !== "round-robin" && Array.isArray(parsed.messages)) {
|
|
4758
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
|
|