opencode-anthropic-fix 0.0.42 → 0.0.43
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 +100 -69
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -2354,28 +2354,35 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2354
2354
|
// pin the first attempt to that account so files are accessible.
|
|
2355
2355
|
// Without this, round-robin could route to an account that doesn't
|
|
2356
2356
|
// have the referenced files, causing file_not_found errors.
|
|
2357
|
-
|
|
2358
|
-
|
|
2357
|
+
// Parse body ONCE before the retry loop and reuse for all downstream logic.
|
|
2358
|
+
// This eliminates 4-5 redundant JSON.parse calls per turn (file pinning,
|
|
2359
|
+
// parseRequestBodyMetadata, adaptive context, toast, microcompact).
|
|
2360
|
+
let _parsedBodyOnce = null;
|
|
2361
|
+
if (typeof requestInit.body === "string") {
|
|
2359
2362
|
try {
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2363
|
+
_parsedBodyOnce = JSON.parse(requestInit.body);
|
|
2364
|
+
} catch {
|
|
2365
|
+
// Non-JSON body — downstream code handles gracefully
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
let pinnedAccount = null;
|
|
2370
|
+
if (_parsedBodyOnce && fileAccountMap.size > 0) {
|
|
2371
|
+
const fileIds = extractFileIds(_parsedBodyOnce);
|
|
2372
|
+
for (const fid of fileIds) {
|
|
2373
|
+
const pinnedIndex = fileAccountMap.get(fid);
|
|
2374
|
+
if (pinnedIndex !== undefined) {
|
|
2375
|
+
const candidates = accountManager.getEnabledAccounts();
|
|
2376
|
+
pinnedAccount = candidates.find((a) => a.index === pinnedIndex) ?? null;
|
|
2377
|
+
if (pinnedAccount) {
|
|
2378
|
+
debugLog("file-id pinning: routing to account", {
|
|
2379
|
+
fileId: fid,
|
|
2380
|
+
accountIndex: pinnedIndex,
|
|
2381
|
+
email: pinnedAccount.email,
|
|
2382
|
+
});
|
|
2383
|
+
break;
|
|
2375
2384
|
}
|
|
2376
2385
|
}
|
|
2377
|
-
} catch {
|
|
2378
|
-
// Non-JSON body or parse error — skip pinning
|
|
2379
2386
|
}
|
|
2380
2387
|
}
|
|
2381
2388
|
|
|
@@ -2538,8 +2545,10 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2538
2545
|
|
|
2539
2546
|
// Pre-compute the beta header so it can be injected into both the
|
|
2540
2547
|
// request body (betas field) and the anthropic-beta header.
|
|
2548
|
+
// Reuse _parsedBodyOnce to avoid redundant JSON.parse.
|
|
2541
2549
|
const { model: _reqModel, hasFileReferences: _reqHasFileRefs } = parseRequestBodyMetadata(
|
|
2542
2550
|
requestInit.body,
|
|
2551
|
+
_parsedBodyOnce,
|
|
2543
2552
|
);
|
|
2544
2553
|
const _reqProvider = detectProvider(requestUrl);
|
|
2545
2554
|
|
|
@@ -2555,12 +2564,15 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2555
2564
|
escalation_threshold: 150_000,
|
|
2556
2565
|
deescalation_threshold: 100_000,
|
|
2557
2566
|
},
|
|
2567
|
+
_parsedBodyOnce,
|
|
2558
2568
|
);
|
|
2559
2569
|
// Emit visual cue on state transitions (only when adaptive mode is on)
|
|
2560
2570
|
if (config.adaptive_context?.enabled && _prevAdaptiveState !== adaptiveContextState.active) {
|
|
2561
2571
|
const label = adaptiveContextState.active ? "1M context ON" : "1M context OFF";
|
|
2562
2572
|
const variant = adaptiveContextState.active ? "info" : "success";
|
|
2563
|
-
const est =
|
|
2573
|
+
const est = _parsedBodyOnce
|
|
2574
|
+
? estimatePromptTokensFromParsed(_parsedBodyOnce)
|
|
2575
|
+
: estimatePromptTokens(requestInit.body);
|
|
2564
2576
|
toast(`⬡ ${label} (est. ${Math.round(est / 1000)}K tokens)`, variant, {
|
|
2565
2577
|
debounceKey: "adaptive-ctx",
|
|
2566
2578
|
}).catch(() => {});
|
|
@@ -2578,7 +2590,9 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
2578
2590
|
// Microcompact: inject clear betas at high context utilization
|
|
2579
2591
|
let _microcompactBetas = null;
|
|
2580
2592
|
if (requestInit.body) {
|
|
2581
|
-
const estimatedTokens =
|
|
2593
|
+
const estimatedTokens = _parsedBodyOnce
|
|
2594
|
+
? estimatePromptTokensFromParsed(_parsedBodyOnce)
|
|
2595
|
+
: estimatePromptTokens(requestInit.body);
|
|
2582
2596
|
if (shouldMicrocompact(estimatedTokens, config)) {
|
|
2583
2597
|
_microcompactBetas = buildMicrocompactBetas();
|
|
2584
2598
|
if (!microcompactState.active) {
|
|
@@ -3144,7 +3158,7 @@ export async function AnthropicAuthPlugin({ client, project, directory, worktree
|
|
|
3144
3158
|
consecutive529Count = 0;
|
|
3145
3159
|
}
|
|
3146
3160
|
|
|
3147
|
-
const baseDelay = Math.min(0.5 * Math.pow(2, serviceWideRetryCount),
|
|
3161
|
+
const baseDelay = Math.min(0.5 * Math.pow(2, serviceWideRetryCount), 3);
|
|
3148
3162
|
const jitter = 1 - Math.random() * 0.25;
|
|
3149
3163
|
const sleepMs = Math.round(baseDelay * jitter * 1000);
|
|
3150
3164
|
const retryLabel = response.status === 529 ? "overloaded" : "unavailable";
|
|
@@ -4120,48 +4134,59 @@ function estimatePromptTokens(bodyString) {
|
|
|
4120
4134
|
if (!bodyString || typeof bodyString !== "string") return 0;
|
|
4121
4135
|
try {
|
|
4122
4136
|
const parsed = JSON.parse(bodyString);
|
|
4123
|
-
|
|
4137
|
+
return estimatePromptTokensFromParsed(parsed);
|
|
4138
|
+
} catch {
|
|
4139
|
+
// Fallback: raw body length / 4 if JSON parsing fails
|
|
4140
|
+
return Math.ceil(bodyString.length / 4);
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4124
4143
|
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4144
|
+
/**
|
|
4145
|
+
* Estimate prompt tokens from an already-parsed request body object.
|
|
4146
|
+
* Avoids redundant JSON.parse when the caller already has the parsed object.
|
|
4147
|
+
* @param {object} parsed - The parsed request body
|
|
4148
|
+
* @returns {number} Estimated token count
|
|
4149
|
+
*/
|
|
4150
|
+
function estimatePromptTokensFromParsed(parsed) {
|
|
4151
|
+
if (!parsed || typeof parsed !== "object") return 0;
|
|
4152
|
+
let charCount = 0;
|
|
4153
|
+
|
|
4154
|
+
// Count system prompt text
|
|
4155
|
+
if (Array.isArray(parsed.system)) {
|
|
4156
|
+
for (const block of parsed.system) {
|
|
4157
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
4158
|
+
charCount += block.text.length;
|
|
4131
4159
|
}
|
|
4132
|
-
} else if (typeof parsed.system === "string") {
|
|
4133
|
-
charCount += parsed.system.length;
|
|
4134
4160
|
}
|
|
4161
|
+
} else if (typeof parsed.system === "string") {
|
|
4162
|
+
charCount += parsed.system.length;
|
|
4163
|
+
}
|
|
4135
4164
|
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
}
|
|
4165
|
+
// Count messages content (text blocks, tool results) — skip tool definitions
|
|
4166
|
+
if (Array.isArray(parsed.messages)) {
|
|
4167
|
+
for (const msg of parsed.messages) {
|
|
4168
|
+
if (typeof msg.content === "string") {
|
|
4169
|
+
charCount += msg.content.length;
|
|
4170
|
+
} else if (Array.isArray(msg.content)) {
|
|
4171
|
+
for (const block of msg.content) {
|
|
4172
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
4173
|
+
charCount += block.text.length;
|
|
4174
|
+
} else if (block.type === "tool_result" && typeof block.content === "string") {
|
|
4175
|
+
charCount += block.content.length;
|
|
4176
|
+
} else if (block.type === "tool_use") {
|
|
4177
|
+
// Count serialized input as tokens
|
|
4178
|
+
charCount += JSON.stringify(block.input || {}).length;
|
|
4179
|
+
} else if (block.type === "image" || block.type === "image_url") {
|
|
4180
|
+
// Images: ~2000 tokens per image (Anthropic tile-based counting)
|
|
4181
|
+
charCount += 8000; // 2000 tokens * 4 chars/token
|
|
4154
4182
|
}
|
|
4155
4183
|
}
|
|
4156
4184
|
}
|
|
4157
4185
|
}
|
|
4158
|
-
|
|
4159
|
-
// 4 chars/token heuristic for text content (reasonable for English + code + JSON)
|
|
4160
|
-
return Math.ceil(charCount / 4);
|
|
4161
|
-
} catch {
|
|
4162
|
-
// Fallback: raw body length / 4 if JSON parsing fails
|
|
4163
|
-
return Math.ceil(bodyString.length / 4);
|
|
4164
4186
|
}
|
|
4187
|
+
|
|
4188
|
+
// 4 chars/token heuristic for text content (reasonable for English + code + JSON)
|
|
4189
|
+
return Math.ceil(charCount / 4);
|
|
4165
4190
|
}
|
|
4166
4191
|
|
|
4167
4192
|
/**
|
|
@@ -4284,7 +4309,7 @@ function analyzeRequestContext(bodyStr) {
|
|
|
4284
4309
|
* @param {import('./lib/config.mjs').AdaptiveContextConfig} adaptiveConfig
|
|
4285
4310
|
* @returns {boolean}
|
|
4286
4311
|
*/
|
|
4287
|
-
function resolveAdaptiveContext(bodyString, model, adaptiveConfig) {
|
|
4312
|
+
function resolveAdaptiveContext(bodyString, model, adaptiveConfig, parsedBody) {
|
|
4288
4313
|
// Non-adaptive: use static check (only explicit "1m" models)
|
|
4289
4314
|
if (!adaptiveConfig.enabled) {
|
|
4290
4315
|
return hasOneMillionContext(model);
|
|
@@ -4300,7 +4325,7 @@ function resolveAdaptiveContext(bodyString, model, adaptiveConfig) {
|
|
|
4300
4325
|
return false;
|
|
4301
4326
|
}
|
|
4302
4327
|
|
|
4303
|
-
const estimatedTokens = estimatePromptTokens(bodyString);
|
|
4328
|
+
const estimatedTokens = parsedBody ? estimatePromptTokensFromParsed(parsedBody) : estimatePromptTokens(bodyString);
|
|
4304
4329
|
const turnsSinceTransition = sessionMetrics.turns - adaptiveContextState.lastTransitionTurn;
|
|
4305
4330
|
|
|
4306
4331
|
if (adaptiveContextState.active) {
|
|
@@ -5231,21 +5256,27 @@ function detectProvider(requestUrl) {
|
|
|
5231
5256
|
* @param {any} body
|
|
5232
5257
|
* @returns {{model: string, tools: any[], messages: any[], hasFileReferences: boolean}}
|
|
5233
5258
|
*/
|
|
5234
|
-
function parseRequestBodyMetadata(body) {
|
|
5235
|
-
|
|
5259
|
+
function parseRequestBodyMetadata(body, parsedBody) {
|
|
5260
|
+
const parsed =
|
|
5261
|
+
parsedBody ||
|
|
5262
|
+
(typeof body === "string"
|
|
5263
|
+
? (() => {
|
|
5264
|
+
try {
|
|
5265
|
+
return JSON.parse(body);
|
|
5266
|
+
} catch {
|
|
5267
|
+
return null;
|
|
5268
|
+
}
|
|
5269
|
+
})()
|
|
5270
|
+
: null);
|
|
5271
|
+
if (!parsed) {
|
|
5236
5272
|
return { model: "", tools: [], messages: [], hasFileReferences: false };
|
|
5237
5273
|
}
|
|
5238
5274
|
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
const hasFileReferences = extractFileIds(parsed).length > 0;
|
|
5245
|
-
return { model, tools, messages, hasFileReferences };
|
|
5246
|
-
} catch {
|
|
5247
|
-
return { model: "", tools: [], messages: [], hasFileReferences: false };
|
|
5248
|
-
}
|
|
5275
|
+
const model = typeof parsed?.model === "string" ? parsed.model : "";
|
|
5276
|
+
const tools = Array.isArray(parsed?.tools) ? parsed.tools : [];
|
|
5277
|
+
const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
|
|
5278
|
+
const hasFileReferences = extractFileIds(parsed).length > 0;
|
|
5279
|
+
return { model, tools, messages, hasFileReferences };
|
|
5249
5280
|
}
|
|
5250
5281
|
|
|
5251
5282
|
/**
|