@rynfar/meridian 1.61.0 → 1.62.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.
- package/README.md +2 -1
- package/dist/{cli-tbhba0cv.js → cli-fyhd8np7.js} +399 -72
- package/dist/cli.js +1 -1
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/adapters/prime.d.ts +99 -0
- package/dist/proxy/adapters/prime.d.ts.map +1 -0
- package/dist/proxy/errors.d.ts +65 -5
- package/dist/proxy/errors.d.ts.map +1 -1
- package/dist/proxy/messages.d.ts.map +1 -1
- package/dist/proxy/plugins/validation.d.ts.map +1 -1
- package/dist/proxy/sdkFeatures.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/session/cache.d.ts.map +1 -1
- package/dist/proxy/session/lineage.d.ts +58 -0
- package/dist/proxy/session/lineage.d.ts.map +1 -1
- package/dist/proxy/transform.d.ts +33 -0
- package/dist/proxy/transform.d.ts.map +1 -1
- package/dist/proxy/transforms/prime.d.ts +3 -0
- package/dist/proxy/transforms/prime.d.ts.map +1 -0
- package/dist/proxy/transforms/registry.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/telemetry/landing.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2020,6 +2020,12 @@ function consolidateMultimodalOntoLastUser(structured) {
|
|
|
2020
2020
|
};
|
|
2021
2021
|
return result;
|
|
2022
2022
|
}
|
|
2023
|
+
function normalizeTarget(value) {
|
|
2024
|
+
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
2025
|
+
if (!collapsed)
|
|
2026
|
+
return;
|
|
2027
|
+
return collapsed.length > TOOL_TARGET_MAX ? collapsed.slice(0, TOOL_TARGET_MAX - 3) + "..." : collapsed;
|
|
2028
|
+
}
|
|
2023
2029
|
function summarizeContent(value) {
|
|
2024
2030
|
if (typeof value !== "string" || value.length === 0)
|
|
2025
2031
|
return;
|
|
@@ -2028,13 +2034,18 @@ function summarizeContent(value) {
|
|
|
2028
2034
|
return;
|
|
2029
2035
|
return collapsed.length > CONTENT_SUMMARY_MAX ? collapsed.slice(0, CONTENT_SUMMARY_MAX) + "..." : collapsed;
|
|
2030
2036
|
}
|
|
2037
|
+
function editReplacementText(rec) {
|
|
2038
|
+
if (!rec)
|
|
2039
|
+
return;
|
|
2040
|
+
return rec.new_string ?? rec.newString ?? rec.new_text ?? rec.newText;
|
|
2041
|
+
}
|
|
2031
2042
|
function extractContentSummary(name, input) {
|
|
2032
2043
|
if (!input || typeof input !== "object")
|
|
2033
2044
|
return;
|
|
2034
2045
|
const rec = input;
|
|
2035
2046
|
switch (name.toLowerCase()) {
|
|
2036
2047
|
case "edit":
|
|
2037
|
-
return summarizeContent(rec
|
|
2048
|
+
return summarizeContent(editReplacementText(rec));
|
|
2038
2049
|
case "write":
|
|
2039
2050
|
return summarizeContent(rec.content);
|
|
2040
2051
|
case "multiedit": {
|
|
@@ -2042,7 +2053,7 @@ function extractContentSummary(name, input) {
|
|
|
2042
2053
|
if (!Array.isArray(edits) || edits.length === 0)
|
|
2043
2054
|
return;
|
|
2044
2055
|
const first = edits[0];
|
|
2045
|
-
const firstSummary = summarizeContent(first
|
|
2056
|
+
const firstSummary = summarizeContent(editReplacementText(first));
|
|
2046
2057
|
if (!firstSummary)
|
|
2047
2058
|
return;
|
|
2048
2059
|
return edits.length > 1 ? `${edits.length} edits; first: ${firstSummary}` : firstSummary;
|
|
@@ -2065,8 +2076,9 @@ function buildToolUseIndex(messages) {
|
|
|
2065
2076
|
for (const key of TOOL_TARGET_KEYS) {
|
|
2066
2077
|
const v = input[key];
|
|
2067
2078
|
if (typeof v === "string" && v) {
|
|
2068
|
-
target = v
|
|
2069
|
-
|
|
2079
|
+
target = normalizeTarget(v);
|
|
2080
|
+
if (target)
|
|
2081
|
+
break;
|
|
2070
2082
|
}
|
|
2071
2083
|
}
|
|
2072
2084
|
}
|
|
@@ -2091,7 +2103,7 @@ function extractSystemText(system) {
|
|
|
2091
2103
|
return system.filter((b) => b?.type === "text" && typeof b.text === "string" && b.text).map((b) => b.text).filter((text) => !TRANSPORT_HEADER_BLOCK.test(text)).join(`
|
|
2092
2104
|
`);
|
|
2093
2105
|
}
|
|
2094
|
-
var HASH_IGNORED_BLOCK_TYPES, HASH_HANDLED_BLOCK_TYPES, HASH_SERIALIZED_BLOCK_TYPES, PASSTHROUGH_CONTINUATION_LEAD_IN, MULTIMODAL_TYPES, TOOL_TARGET_KEYS, CONTENT_SUMMARY_MAX = 120, TRANSPORT_HEADER_BLOCK;
|
|
2106
|
+
var HASH_IGNORED_BLOCK_TYPES, HASH_HANDLED_BLOCK_TYPES, HASH_SERIALIZED_BLOCK_TYPES, PASSTHROUGH_CONTINUATION_LEAD_IN, MULTIMODAL_TYPES, TOOL_TARGET_KEYS, TOOL_TARGET_MAX = 80, CONTENT_SUMMARY_MAX = 120, TRANSPORT_HEADER_BLOCK;
|
|
2095
2107
|
var init_messages = __esm(() => {
|
|
2096
2108
|
HASH_IGNORED_BLOCK_TYPES = new Set(["thinking", "redacted_thinking"]);
|
|
2097
2109
|
HASH_HANDLED_BLOCK_TYPES = new Set(["text", "tool_use", "tool_result"]);
|
|
@@ -2110,7 +2122,7 @@ var init_messages = __esm(() => {
|
|
|
2110
2122
|
]);
|
|
2111
2123
|
PASSTHROUGH_CONTINUATION_LEAD_IN = "The tool calls from your previous turn were forwarded to the client, which has now executed them — " + "their results follow. The instruction to end that turn without further text applied to it alone and " + "is now discharged: continue the work and respond.";
|
|
2112
2124
|
MULTIMODAL_TYPES = new Set(["image", "document", "file"]);
|
|
2113
|
-
TOOL_TARGET_KEYS = ["filePath", "file_path", "path", "command", "pattern", "query", "url"];
|
|
2125
|
+
TOOL_TARGET_KEYS = ["filePath", "file_path", "path", "command", "code", "pattern", "query", "url"];
|
|
2114
2126
|
TRANSPORT_HEADER_BLOCK = /^\s*x-anthropic-[a-z0-9-]*header\s*:/i;
|
|
2115
2127
|
});
|
|
2116
2128
|
|
|
@@ -2942,6 +2954,124 @@ var init_pi2 = __esm(() => {
|
|
|
2942
2954
|
};
|
|
2943
2955
|
});
|
|
2944
2956
|
|
|
2957
|
+
// src/proxy/adapters/prime.ts
|
|
2958
|
+
function extractPrimeCwd(body) {
|
|
2959
|
+
let systemText = "";
|
|
2960
|
+
if (typeof body?.system === "string") {
|
|
2961
|
+
systemText = body.system;
|
|
2962
|
+
} else if (Array.isArray(body?.system)) {
|
|
2963
|
+
systemText = body.system.filter((b) => b?.type === "text" && b.text).map((b) => b.text).join(`
|
|
2964
|
+
`);
|
|
2965
|
+
}
|
|
2966
|
+
if (!systemText)
|
|
2967
|
+
return;
|
|
2968
|
+
const match2 = systemText.match(RLM_CWD_LINE) ?? systemText.match(CUSTOM_PROMPT_CWD_LINE);
|
|
2969
|
+
return match2?.[1]?.trim() || undefined;
|
|
2970
|
+
}
|
|
2971
|
+
function extractFileChangesFromIpythonCell(code) {
|
|
2972
|
+
const lines = code.split(`
|
|
2973
|
+
`);
|
|
2974
|
+
const firstMeaningful = lines.find((l) => l.trim().length > 0) ?? "";
|
|
2975
|
+
if (/^[ \t]*%%bash\b/.test(firstMeaningful)) {
|
|
2976
|
+
const bodyStart = lines.indexOf(firstMeaningful) + 1;
|
|
2977
|
+
return extractFileChangesFromBash(lines.slice(bodyStart).join(`
|
|
2978
|
+
`));
|
|
2979
|
+
}
|
|
2980
|
+
const changes = [];
|
|
2981
|
+
for (const line of lines) {
|
|
2982
|
+
const escaped = line.match(SHELL_ESCAPE_LINE);
|
|
2983
|
+
if (escaped?.[1])
|
|
2984
|
+
changes.push(...extractFileChangesFromBash(escaped[1]));
|
|
2985
|
+
}
|
|
2986
|
+
for (const m of code.matchAll(EDIT_SKILL_CALL)) {
|
|
2987
|
+
if (m[2])
|
|
2988
|
+
changes.push({ operation: "edited", path: m[2] });
|
|
2989
|
+
}
|
|
2990
|
+
return changes;
|
|
2991
|
+
}
|
|
2992
|
+
function extractPrimeFileChanges(toolName, toolInput) {
|
|
2993
|
+
const input = toolInput;
|
|
2994
|
+
if (toolName === "ipython" && typeof input?.code === "string") {
|
|
2995
|
+
return extractFileChangesFromIpythonCell(input.code);
|
|
2996
|
+
}
|
|
2997
|
+
const filePath = input?.path ?? input?.file_path ?? input?.filePath;
|
|
2998
|
+
if (toolName === "edit" && filePath) {
|
|
2999
|
+
return [{ operation: "edited", path: String(filePath) }];
|
|
3000
|
+
}
|
|
3001
|
+
if (toolName === "write" && filePath) {
|
|
3002
|
+
return [{ operation: "wrote", path: String(filePath) }];
|
|
3003
|
+
}
|
|
3004
|
+
if (toolName === "bash" && input?.command) {
|
|
3005
|
+
return extractFileChangesFromBash(String(input.command));
|
|
3006
|
+
}
|
|
3007
|
+
return [];
|
|
3008
|
+
}
|
|
3009
|
+
var PRIME_MCP_SERVER_NAME = "prime", PRIME_ALLOWED_MCP_TOOLS, RLM_CWD_LINE, CUSTOM_PROMPT_CWD_LINE, EDIT_SKILL_CALL, SHELL_ESCAPE_LINE, primeAdapter;
|
|
3010
|
+
var init_prime = __esm(() => {
|
|
3011
|
+
init_fileChanges();
|
|
3012
|
+
init_messages();
|
|
3013
|
+
init_tools();
|
|
3014
|
+
init_env();
|
|
3015
|
+
init_claudecode();
|
|
3016
|
+
PRIME_ALLOWED_MCP_TOOLS = [
|
|
3017
|
+
`mcp__${PRIME_MCP_SERVER_NAME}__read`,
|
|
3018
|
+
`mcp__${PRIME_MCP_SERVER_NAME}__write`,
|
|
3019
|
+
`mcp__${PRIME_MCP_SERVER_NAME}__edit`,
|
|
3020
|
+
`mcp__${PRIME_MCP_SERVER_NAME}__bash`,
|
|
3021
|
+
`mcp__${PRIME_MCP_SERVER_NAME}__glob`,
|
|
3022
|
+
`mcp__${PRIME_MCP_SERVER_NAME}__grep`
|
|
3023
|
+
];
|
|
3024
|
+
RLM_CWD_LINE = /^Working directory:[ \t]*(.+)$/m;
|
|
3025
|
+
CUSTOM_PROMPT_CWD_LINE = /^Current working directory:[ \t]*(.+)$/m;
|
|
3026
|
+
EDIT_SKILL_CALL = /\bedit\s*\(\s*path\s*=\s*(['"])(.+?)\1/g;
|
|
3027
|
+
SHELL_ESCAPE_LINE = /^[ \t]*!(.+)$/;
|
|
3028
|
+
primeAdapter = {
|
|
3029
|
+
name: "prime",
|
|
3030
|
+
getSessionId(c, body) {
|
|
3031
|
+
return c.req.header("x-session-affinity") ?? extractClaudeCodeSessionId(body);
|
|
3032
|
+
},
|
|
3033
|
+
extractWorkingDirectory(body) {
|
|
3034
|
+
return extractPrimeCwd(body);
|
|
3035
|
+
},
|
|
3036
|
+
extractClientWorkingDirectory(body) {
|
|
3037
|
+
return extractPrimeCwd(body);
|
|
3038
|
+
},
|
|
3039
|
+
normalizeContent(content) {
|
|
3040
|
+
return normalizeContent(content);
|
|
3041
|
+
},
|
|
3042
|
+
getBlockedBuiltinTools() {
|
|
3043
|
+
return BLOCKED_BUILTIN_TOOLS;
|
|
3044
|
+
},
|
|
3045
|
+
getAgentIncompatibleTools() {
|
|
3046
|
+
return CLAUDE_CODE_ONLY_TOOLS;
|
|
3047
|
+
},
|
|
3048
|
+
getMcpServerName() {
|
|
3049
|
+
return PRIME_MCP_SERVER_NAME;
|
|
3050
|
+
},
|
|
3051
|
+
getAllowedMcpTools() {
|
|
3052
|
+
return PRIME_ALLOWED_MCP_TOOLS;
|
|
3053
|
+
},
|
|
3054
|
+
buildSdkAgents(_body, _mcpToolNames) {
|
|
3055
|
+
return {};
|
|
3056
|
+
},
|
|
3057
|
+
supportsThinking() {
|
|
3058
|
+
return true;
|
|
3059
|
+
},
|
|
3060
|
+
buildSdkHooks(_body, _sdkAgents) {
|
|
3061
|
+
return;
|
|
3062
|
+
},
|
|
3063
|
+
buildSystemContextAddendum(_body, _sdkAgents) {
|
|
3064
|
+
return "";
|
|
3065
|
+
},
|
|
3066
|
+
usesPassthrough() {
|
|
3067
|
+
return resolvePassthrough(true);
|
|
3068
|
+
},
|
|
3069
|
+
extractFileChangesFromToolUse(toolName, toolInput) {
|
|
3070
|
+
return extractPrimeFileChanges(toolName, toolInput);
|
|
3071
|
+
}
|
|
3072
|
+
};
|
|
3073
|
+
});
|
|
3074
|
+
|
|
2945
3075
|
// src/proxy/transforms/forgecode.ts
|
|
2946
3076
|
var FORGECODE_MCP_SERVER_NAME = "forgecode", FORGECODE_ALLOWED_MCP_TOOLS, forgeCodeTransforms;
|
|
2947
3077
|
var init_forgecode = __esm(() => {
|
|
@@ -3279,6 +3409,7 @@ var init_detect = __esm(() => {
|
|
|
3279
3409
|
init_crush2();
|
|
3280
3410
|
init_passthrough2();
|
|
3281
3411
|
init_pi2();
|
|
3412
|
+
init_prime();
|
|
3282
3413
|
init_forgecode2();
|
|
3283
3414
|
init_claudecode();
|
|
3284
3415
|
init_openai();
|
|
@@ -3292,6 +3423,8 @@ var init_detect = __esm(() => {
|
|
|
3292
3423
|
crush: crushAdapter,
|
|
3293
3424
|
passthrough: passthroughAdapter,
|
|
3294
3425
|
pi: piAdapter,
|
|
3426
|
+
prime: primeAdapter,
|
|
3427
|
+
"prime-agent": primeAdapter,
|
|
3295
3428
|
forgecode: forgeCodeAdapter,
|
|
3296
3429
|
"claude-code": claudeCodeAdapter,
|
|
3297
3430
|
claudecode: claudeCodeAdapter,
|
|
@@ -3455,6 +3588,9 @@ var init_sdkFeatures = __esm(() => {
|
|
|
3455
3588
|
cherry: {
|
|
3456
3589
|
codeSystemPrompt: false
|
|
3457
3590
|
},
|
|
3591
|
+
prime: {
|
|
3592
|
+
codeSystemPrompt: false
|
|
3593
|
+
},
|
|
3458
3594
|
codex: {
|
|
3459
3595
|
codeSystemPrompt: false
|
|
3460
3596
|
}
|
|
@@ -6201,6 +6337,7 @@ var serve = (options, listeningListener) => {
|
|
|
6201
6337
|
};
|
|
6202
6338
|
|
|
6203
6339
|
// src/proxy/server.ts
|
|
6340
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6204
6341
|
import { homedir as homedir7 } from "node:os";
|
|
6205
6342
|
import { join as join8 } from "node:path";
|
|
6206
6343
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -11768,7 +11905,14 @@ function profileSection(q,s,pl,h){
|
|
|
11768
11905
|
var orderIdx=(pl.profileOrder||[]).indexOf(p.id);
|
|
11769
11906
|
if(orderIdx>=0)badge+='<span class="pool-chip">#'+(orderIdx+1)+' in pool</span>';
|
|
11770
11907
|
var exh=(pl.exhausted||[]).filter(function(e){return e.id===p.id})[0];
|
|
11771
|
-
if(exh)
|
|
11908
|
+
if(exh){
|
|
11909
|
+
// A billing refusal has no reset to wait for — the pool re-probes on the
|
|
11910
|
+
// same timer, but nothing changes until a human fixes the account.
|
|
11911
|
+
// Showing it as 'resets in 9m' promises a recovery that never comes.
|
|
11912
|
+
badge+=exh.reason==='billing_error'
|
|
11913
|
+
?' <span class="pool-chip exhausted" title="Subscription or payment refused — this does not clear on its own">subscription refused</span>'
|
|
11914
|
+
:' <span class="pool-chip exhausted">exhausted · resets '+resetIn(exh.until)+'</span>';
|
|
11915
|
+
}
|
|
11772
11916
|
}
|
|
11773
11917
|
cards+='<div class="profile-card'+(p.isActive?' active':'')+(switchable?' switchable':'')+'"'+(switchable?' data-profile="'+esc(p.id)+'" role="button" tabindex="0"':'')+'>'
|
|
11774
11918
|
+'<div class="profile-head"><span class="profile-name"><span class="prof-dot"></span>'+esc(p.label||p.id)+' '+badge+'</span>'
|
|
@@ -11954,6 +12098,16 @@ function extendedContextHint(model) {
|
|
|
11954
12098
|
return advise("MERIDIAN_SONNET_MODEL=sonnet");
|
|
11955
12099
|
return advise("MERIDIAN_1M_CONTEXT_SUPPORT=0");
|
|
11956
12100
|
}
|
|
12101
|
+
var BILLING_SIGNALS = [
|
|
12102
|
+
/\b402\b(?!:\d)/,
|
|
12103
|
+
/billing[_ ](?:error|issue|problem|failure)/,
|
|
12104
|
+
/subscription (?:is |has )?(?:inactive|expired|lapsed|cancell?ed|ended|invalid|not active)/,
|
|
12105
|
+
/(?:expired|inactive|lapsed|invalid|no active|cancell?ed) subscription/,
|
|
12106
|
+
/payment (?:method|required|failed|declined|details|info)/,
|
|
12107
|
+
/update your payment/,
|
|
12108
|
+
/(?:out of|draw from|draws from) extra usage/,
|
|
12109
|
+
/insufficient (?:credit|funds|balance)/
|
|
12110
|
+
];
|
|
11957
12111
|
var HIT_YOUR_LIMIT = /hit your (?:[\w-]+ )?limit/;
|
|
11958
12112
|
function classifyError(errMsg, model) {
|
|
11959
12113
|
const lower = errMsg.toLowerCase();
|
|
@@ -11979,7 +12133,7 @@ function classifyError(errMsg, model) {
|
|
|
11979
12133
|
message: `Claude Max rate limit reached. Wait a moment and try again.${hint}`
|
|
11980
12134
|
};
|
|
11981
12135
|
}
|
|
11982
|
-
if (
|
|
12136
|
+
if (BILLING_SIGNALS.some((rx) => rx.test(lower))) {
|
|
11983
12137
|
return {
|
|
11984
12138
|
status: 402,
|
|
11985
12139
|
type: "billing_error",
|
|
@@ -12049,11 +12203,18 @@ function isExpiredTokenError(errMsg) {
|
|
|
12049
12203
|
return true;
|
|
12050
12204
|
return false;
|
|
12051
12205
|
}
|
|
12052
|
-
function
|
|
12206
|
+
function classifyResumeRefusal(error, stderr) {
|
|
12207
|
+
if (isBusySessionError(error, stderr))
|
|
12208
|
+
return "busy";
|
|
12053
12209
|
if (!(error instanceof Error))
|
|
12054
|
-
return
|
|
12210
|
+
return;
|
|
12055
12211
|
const msg = error.message;
|
|
12056
|
-
|
|
12212
|
+
if (msg.includes("No message found with message.uuid"))
|
|
12213
|
+
return "missing-message";
|
|
12214
|
+
if (msg.includes("No conversation found with session ID") || msg.includes("No conversation found to continue") || msg.includes("No conversations found to resume")) {
|
|
12215
|
+
return "unresumable";
|
|
12216
|
+
}
|
|
12217
|
+
return;
|
|
12057
12218
|
}
|
|
12058
12219
|
function isBusySessionError(error, stderr) {
|
|
12059
12220
|
const needle = "is currently running as a background agent";
|
|
@@ -12064,6 +12225,16 @@ function isRateLimitError(errMsg) {
|
|
|
12064
12225
|
const lower = errMsg.toLowerCase();
|
|
12065
12226
|
return lower.includes("429") || lower.includes("rate limit") || lower.includes("too many requests");
|
|
12066
12227
|
}
|
|
12228
|
+
var ACCOUNT_FAILOVER_ERROR_TYPES = new Set([
|
|
12229
|
+
"rate_limit_error",
|
|
12230
|
+
"billing_error"
|
|
12231
|
+
]);
|
|
12232
|
+
function isAccountFailoverError(errorType) {
|
|
12233
|
+
return typeof errorType === "string" && ACCOUNT_FAILOVER_ERROR_TYPES.has(errorType);
|
|
12234
|
+
}
|
|
12235
|
+
function isQuotaRefusal(errorType) {
|
|
12236
|
+
return errorType === "rate_limit_error";
|
|
12237
|
+
}
|
|
12067
12238
|
function isExtraUsageRequiredError(errMsg) {
|
|
12068
12239
|
const lower = errMsg.toLowerCase();
|
|
12069
12240
|
return lower.includes("extra usage") && lower.includes("1m") || lower.includes("out of extra usage");
|
|
@@ -12088,11 +12259,34 @@ function makeRawTail(errMsg) {
|
|
|
12088
12259
|
return;
|
|
12089
12260
|
return head.length > RAW_TAIL_MAX ? head.slice(0, RAW_TAIL_MAX) : head;
|
|
12090
12261
|
}
|
|
12262
|
+
function canRecoverCapturedToolUses(input) {
|
|
12263
|
+
if (!input.passthrough)
|
|
12264
|
+
return false;
|
|
12265
|
+
if (input.capturedToolUses <= 0)
|
|
12266
|
+
return false;
|
|
12267
|
+
switch (input.reason) {
|
|
12268
|
+
case "max_turns":
|
|
12269
|
+
case "upstream_idle":
|
|
12270
|
+
return true;
|
|
12271
|
+
case "aborted":
|
|
12272
|
+
return input.abortIsOurs;
|
|
12273
|
+
default:
|
|
12274
|
+
return false;
|
|
12275
|
+
}
|
|
12276
|
+
}
|
|
12091
12277
|
function extractSdkTermination(errMsg) {
|
|
12092
12278
|
const stderrTail = extractStderrTail(errMsg);
|
|
12093
12279
|
const haystack = `${errMsg}
|
|
12094
12280
|
${stderrTail ?? ""}`;
|
|
12095
12281
|
const lower = haystack.toLowerCase();
|
|
12282
|
+
if (lower.includes("upstream idle for")) {
|
|
12283
|
+
const m = haystack.match(/upstream idle for (\d+)ms/i);
|
|
12284
|
+
return {
|
|
12285
|
+
reason: "upstream_idle",
|
|
12286
|
+
...m ? { idleMs: Number(m[1]) } : {},
|
|
12287
|
+
...stderrTail ? { stderrTail } : {}
|
|
12288
|
+
};
|
|
12289
|
+
}
|
|
12096
12290
|
if (lower.includes("reached maximum number of turns")) {
|
|
12097
12291
|
const m = haystack.match(/Reached maximum number of turns \((\d+)\)/i);
|
|
12098
12292
|
return {
|
|
@@ -19168,6 +19362,34 @@ init_opencode();
|
|
|
19168
19362
|
init_crush();
|
|
19169
19363
|
init_droid();
|
|
19170
19364
|
init_pi();
|
|
19365
|
+
|
|
19366
|
+
// src/proxy/transforms/prime.ts
|
|
19367
|
+
init_tools();
|
|
19368
|
+
init_env();
|
|
19369
|
+
init_prime();
|
|
19370
|
+
function resolvePrimePassthrough() {
|
|
19371
|
+
return resolvePassthrough(true);
|
|
19372
|
+
}
|
|
19373
|
+
var primeTransforms = [
|
|
19374
|
+
{
|
|
19375
|
+
name: "prime-core",
|
|
19376
|
+
adapters: ["prime"],
|
|
19377
|
+
onRequest(ctx) {
|
|
19378
|
+
return {
|
|
19379
|
+
...ctx,
|
|
19380
|
+
blockedTools: BLOCKED_BUILTIN_TOOLS,
|
|
19381
|
+
incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
|
|
19382
|
+
allowedMcpTools: PRIME_ALLOWED_MCP_TOOLS,
|
|
19383
|
+
sdkAgents: {},
|
|
19384
|
+
passthrough: resolvePrimePassthrough(),
|
|
19385
|
+
supportsThinking: true,
|
|
19386
|
+
extractFileChangesFromToolUse: extractPrimeFileChanges
|
|
19387
|
+
};
|
|
19388
|
+
}
|
|
19389
|
+
}
|
|
19390
|
+
];
|
|
19391
|
+
|
|
19392
|
+
// src/proxy/transforms/registry.ts
|
|
19171
19393
|
init_forgecode();
|
|
19172
19394
|
init_passthrough();
|
|
19173
19395
|
|
|
@@ -19249,6 +19471,7 @@ var ADAPTER_TRANSFORMS = {
|
|
|
19249
19471
|
crush: crushTransforms,
|
|
19250
19472
|
droid: droidTransforms,
|
|
19251
19473
|
pi: piTransforms,
|
|
19474
|
+
prime: primeTransforms,
|
|
19252
19475
|
forgecode: forgeCodeTransforms,
|
|
19253
19476
|
passthrough: passthroughTransforms,
|
|
19254
19477
|
cherry: cherryTransforms,
|
|
@@ -19267,7 +19490,7 @@ import { join as join5, isAbsolute as isAbsolute2, extname } from "path";
|
|
|
19267
19490
|
import { pathToFileURL } from "url";
|
|
19268
19491
|
|
|
19269
19492
|
// src/proxy/plugins/validation.ts
|
|
19270
|
-
|
|
19493
|
+
init_detect();
|
|
19271
19494
|
var KNOWN_HOOKS = ["onRequest", "onResponse", "onTelemetry", "onSession", "onToolUse", "onToolResult", "onError"];
|
|
19272
19495
|
function validateTransform(exported) {
|
|
19273
19496
|
if (exported == null || typeof exported !== "object") {
|
|
@@ -19288,8 +19511,9 @@ function validateTransform(exported) {
|
|
|
19288
19511
|
}
|
|
19289
19512
|
const warnings = [];
|
|
19290
19513
|
if (Array.isArray(obj.adapters)) {
|
|
19514
|
+
const known = listAdapterNames();
|
|
19291
19515
|
for (const adapter of obj.adapters) {
|
|
19292
|
-
if (typeof adapter === "string" && !
|
|
19516
|
+
if (typeof adapter === "string" && !known.includes(adapter)) {
|
|
19293
19517
|
warnings.push(adapter);
|
|
19294
19518
|
}
|
|
19295
19519
|
}
|
|
@@ -19622,6 +19846,48 @@ function computeLineageHash(messages) {
|
|
|
19622
19846
|
function hashMessage(message) {
|
|
19623
19847
|
return createHash2("sha256").update(`${message.role}:${normalizeContent(message.content)}`).digest("hex").slice(0, 32);
|
|
19624
19848
|
}
|
|
19849
|
+
function describeShape(message) {
|
|
19850
|
+
const normalized = normalizeContent(message.content);
|
|
19851
|
+
return {
|
|
19852
|
+
role: message.role,
|
|
19853
|
+
blocks: Array.isArray(message.content) ? message.content.map((b) => String(b?.type ?? "unknown")).join(",") : typeof message.content === "string" ? "string" : "unknown",
|
|
19854
|
+
bytes: Buffer.byteLength(normalized, "utf8")
|
|
19855
|
+
};
|
|
19856
|
+
}
|
|
19857
|
+
function describeLineageMismatch(cached, messages, precomputedIncomingHashes) {
|
|
19858
|
+
const storedHashes = cached.messageHashes ?? [];
|
|
19859
|
+
const incomingHashes = precomputedIncomingHashes ?? computeMessageHashes(messages);
|
|
19860
|
+
const limit = Math.min(storedHashes.length, incomingHashes.length);
|
|
19861
|
+
let index = -1;
|
|
19862
|
+
for (let i = 0;i < limit; i++) {
|
|
19863
|
+
if (storedHashes[i] !== incomingHashes[i]) {
|
|
19864
|
+
index = i;
|
|
19865
|
+
break;
|
|
19866
|
+
}
|
|
19867
|
+
}
|
|
19868
|
+
const base = {
|
|
19869
|
+
index,
|
|
19870
|
+
storedCount: cached.messageCount,
|
|
19871
|
+
incomingCount: messages.length
|
|
19872
|
+
};
|
|
19873
|
+
if (index < 0)
|
|
19874
|
+
return base;
|
|
19875
|
+
return {
|
|
19876
|
+
...base,
|
|
19877
|
+
storedDigest: storedHashes[index],
|
|
19878
|
+
incomingDigest: incomingHashes[index],
|
|
19879
|
+
incomingShape: messages[index] ? describeShape(messages[index]) : undefined,
|
|
19880
|
+
previousDigest: index > 0 ? storedHashes[index - 1] : undefined
|
|
19881
|
+
};
|
|
19882
|
+
}
|
|
19883
|
+
function formatLineageMismatch(mismatch) {
|
|
19884
|
+
if (mismatch.index < 0)
|
|
19885
|
+
return;
|
|
19886
|
+
const short = (digest) => digest ? digest.slice(0, 12) : "—";
|
|
19887
|
+
const trailing = mismatch.index === mismatch.storedCount - 1 ? " (trailing message only — the rest of the history matched)" : "";
|
|
19888
|
+
const shape = mismatch.incomingShape ? `${mismatch.incomingShape.role}[${mismatch.incomingShape.blocks}] ${mismatch.incomingShape.bytes}B` : "unknown";
|
|
19889
|
+
return `first mismatch at index ${mismatch.index}${trailing}: ` + `stored=${short(mismatch.storedDigest)} incoming=${short(mismatch.incomingDigest)}, ` + `incoming now ${shape}`;
|
|
19890
|
+
}
|
|
19625
19891
|
function computeMessageHashes(messages) {
|
|
19626
19892
|
if (!messages || messages.length === 0)
|
|
19627
19893
|
return [];
|
|
@@ -19763,7 +20029,12 @@ function verifyLineage(cached, messages) {
|
|
|
19763
20029
|
return { type: "undo", session: cached, prefixOverlap, rollbackUuid };
|
|
19764
20030
|
}
|
|
19765
20031
|
if (prefixOverlap > 0 && messages.length > cached.messageCount) {
|
|
19766
|
-
return {
|
|
20032
|
+
return {
|
|
20033
|
+
type: "diverged",
|
|
20034
|
+
reason: "modified-history",
|
|
20035
|
+
prefixOverlap,
|
|
20036
|
+
mismatch: describeLineageMismatch(cached, messages, incomingHashes)
|
|
20037
|
+
};
|
|
19767
20038
|
}
|
|
19768
20039
|
return { type: "diverged", reason: "unrelated-history", prefixOverlap };
|
|
19769
20040
|
}
|
|
@@ -20087,7 +20358,9 @@ function classifyLineage(state, messages, cacheKey2) {
|
|
|
20087
20358
|
console.error(`[PROXY] ${msg}`);
|
|
20088
20359
|
diagnosticLog2.lineage(msg);
|
|
20089
20360
|
} else if (result.type === "diverged" && result.reason === "modified-history") {
|
|
20090
|
-
const
|
|
20361
|
+
const detail = result.mismatch ? formatLineageMismatch(result.mismatch) : undefined;
|
|
20362
|
+
const msg = `Stale session detected (key=${cacheKey2.slice(0, 8)}…): prefix overlap ${result.prefixOverlap || 0}/${state.messageCount}, incoming ${messages.length} msgs. Starting fresh replay.` + (detail ? `
|
|
20363
|
+
${detail}` : "");
|
|
20091
20364
|
console.error(`[PROXY] ${msg}`);
|
|
20092
20365
|
diagnosticLog2.lineage(msg);
|
|
20093
20366
|
}
|
|
@@ -20422,8 +20695,8 @@ function createProxyServer(config = {}) {
|
|
|
20422
20695
|
const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
|
|
20423
20696
|
const PENDING_STORE_WAIT_MS = 3000;
|
|
20424
20697
|
const PENDING_STORE_AUTO_RESOLVE_MS = 1e4;
|
|
20425
|
-
const
|
|
20426
|
-
const
|
|
20698
|
+
const RESUME_REFUSAL_MAX_RETRIES = 3;
|
|
20699
|
+
const RESUME_REFUSAL_RETRY_DELAY_MS = parseInt(process.env.MERIDIAN_BUSY_RETRY_DELAY_MS ?? "500", 10);
|
|
20427
20700
|
const pendingSessionStores = new Map;
|
|
20428
20701
|
const registerPendingStore = (key) => {
|
|
20429
20702
|
let resolveFn = () => {};
|
|
@@ -20501,23 +20774,25 @@ function createProxyServer(config = {}) {
|
|
|
20501
20774
|
});
|
|
20502
20775
|
});
|
|
20503
20776
|
}
|
|
20504
|
-
async function
|
|
20777
|
+
async function sniffAccountFailure(res) {
|
|
20505
20778
|
const contentType = res.headers.get("content-type") ?? "";
|
|
20506
20779
|
if (!contentType.includes("text/event-stream")) {
|
|
20507
|
-
if (res.
|
|
20780
|
+
if (!res.ok) {
|
|
20508
20781
|
const body = await res.clone().json().catch(() => null);
|
|
20509
|
-
|
|
20510
|
-
|
|
20782
|
+
const errorType = body?.error?.type;
|
|
20783
|
+
if (isAccountFailoverError(errorType)) {
|
|
20784
|
+
return { failed: true, errorPayload: body, errorType, response: res };
|
|
20785
|
+
}
|
|
20511
20786
|
}
|
|
20512
|
-
return { failed: false, errorPayload: null, response: res };
|
|
20787
|
+
return { failed: false, errorPayload: null, errorType: null, response: res };
|
|
20513
20788
|
}
|
|
20514
20789
|
const reader = res.body?.getReader();
|
|
20515
20790
|
if (!reader)
|
|
20516
|
-
return { failed: false, errorPayload: null, response: res };
|
|
20791
|
+
return { failed: false, errorPayload: null, errorType: null, response: res };
|
|
20517
20792
|
const decoder = new TextDecoder;
|
|
20518
20793
|
const consumed = [];
|
|
20519
20794
|
let text = "";
|
|
20520
|
-
let
|
|
20795
|
+
let failure = null;
|
|
20521
20796
|
while (true) {
|
|
20522
20797
|
const { done, value } = await reader.read();
|
|
20523
20798
|
if (done)
|
|
@@ -20535,16 +20810,17 @@ function createProxyServer(config = {}) {
|
|
|
20535
20810
|
`).find((l) => l.startsWith("data: "));
|
|
20536
20811
|
try {
|
|
20537
20812
|
const parsed = dataLine ? JSON.parse(dataLine.slice(6)) : null;
|
|
20538
|
-
|
|
20539
|
-
|
|
20813
|
+
const parsedType = parsed?.error?.type;
|
|
20814
|
+
if (isAccountFailoverError(parsedType)) {
|
|
20815
|
+
failure = { payload: parsed, type: parsedType };
|
|
20540
20816
|
}
|
|
20541
20817
|
} catch {}
|
|
20542
20818
|
}
|
|
20543
20819
|
break;
|
|
20544
20820
|
}
|
|
20545
|
-
if (
|
|
20821
|
+
if (failure) {
|
|
20546
20822
|
await reader.cancel().catch(() => {});
|
|
20547
|
-
return { failed: true, errorPayload:
|
|
20823
|
+
return { failed: true, errorPayload: failure.payload, errorType: failure.type, response: res };
|
|
20548
20824
|
}
|
|
20549
20825
|
const rest = new ReadableStream({
|
|
20550
20826
|
start(ctrl) {
|
|
@@ -20562,33 +20838,40 @@ function createProxyServer(config = {}) {
|
|
|
20562
20838
|
reader.cancel(reason).catch(() => {});
|
|
20563
20839
|
}
|
|
20564
20840
|
});
|
|
20565
|
-
return { failed: false, errorPayload: null, response: new Response(rest, { status: res.status, headers: res.headers }) };
|
|
20841
|
+
return { failed: false, errorPayload: null, errorType: null, response: new Response(rest, { status: res.status, headers: res.headers }) };
|
|
20566
20842
|
}
|
|
20567
20843
|
async function dispatchPriority(c, orderedCandidateIds, sessionKey, wantsStream) {
|
|
20568
20844
|
const bodyBuf = await c.req.arrayBuffer();
|
|
20569
20845
|
let lastError = null;
|
|
20846
|
+
let lastStatus = 429;
|
|
20570
20847
|
let previous = null;
|
|
20848
|
+
let previousReason = "rate_limit_error";
|
|
20571
20849
|
for (const candidate of orderedCandidateIds) {
|
|
20572
20850
|
const headers = new Headers(c.req.raw.headers);
|
|
20573
20851
|
headers.set("x-meridian-profile", candidate);
|
|
20574
20852
|
headers.set("x-meridian-priority-dispatch", "1");
|
|
20575
20853
|
const inner = await app.fetch(new Request(c.req.url, { method: "POST", headers, body: bodyBuf }));
|
|
20576
|
-
const
|
|
20577
|
-
if (!failed) {
|
|
20854
|
+
const sniffed = await sniffAccountFailure(inner);
|
|
20855
|
+
if (!sniffed.failed) {
|
|
20578
20856
|
if (sessionKey)
|
|
20579
20857
|
priorityAssignments.set(sessionKey, candidate);
|
|
20580
20858
|
if (previous) {
|
|
20581
|
-
claudeLog("profile.failover", { from: previous, to: candidate, reason:
|
|
20582
|
-
plog(`[PROXY] PRIORITY failover ${previous} -> ${candidate}`);
|
|
20583
|
-
}
|
|
20584
|
-
return response;
|
|
20585
|
-
}
|
|
20586
|
-
const
|
|
20587
|
-
|
|
20588
|
-
|
|
20589
|
-
|
|
20590
|
-
|
|
20859
|
+
claudeLog("profile.failover", { from: previous, to: candidate, reason: previousReason, sessionKey });
|
|
20860
|
+
plog(`[PROXY] PRIORITY failover ${previous} -> ${candidate} (${previousReason})`);
|
|
20861
|
+
}
|
|
20862
|
+
return sniffed.response;
|
|
20863
|
+
}
|
|
20864
|
+
const reason = sniffed.errorType;
|
|
20865
|
+
const quotaRefusal = isQuotaRefusal(reason);
|
|
20866
|
+
const cooldownUntil = quotaRefusal ? priorityCooldownUntil(candidate, Date.now()) : Date.now() + PRIORITY_DEFAULT_COOLDOWN_MS;
|
|
20867
|
+
priorityExhaustion.mark(candidate, cooldownUntil, reason);
|
|
20868
|
+
claudeLog("priority.exhausted", { profile: candidate, until: cooldownUntil, reason });
|
|
20869
|
+
if (quotaRefusal)
|
|
20870
|
+
refinePriorityCooldown(candidate);
|
|
20871
|
+
lastError = sniffed.errorPayload;
|
|
20872
|
+
lastStatus = inner.status;
|
|
20591
20873
|
previous = candidate;
|
|
20874
|
+
previousReason = reason;
|
|
20592
20875
|
}
|
|
20593
20876
|
if (wantsStream) {
|
|
20594
20877
|
return new Response(`event: error
|
|
@@ -20599,7 +20882,7 @@ data: ${JSON.stringify(lastError)}
|
|
|
20599
20882
|
headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache" }
|
|
20600
20883
|
});
|
|
20601
20884
|
}
|
|
20602
|
-
return new Response(JSON.stringify(lastError), { status:
|
|
20885
|
+
return new Response(JSON.stringify(lastError), { status: lastStatus, headers: { "content-type": "application/json" } });
|
|
20603
20886
|
}
|
|
20604
20887
|
app.use("/auth/*", requireAuth);
|
|
20605
20888
|
app.get("/", (c) => {
|
|
@@ -20617,6 +20900,7 @@ data: ${JSON.stringify(lastError)}
|
|
|
20617
20900
|
const MAX_CONCURRENT_SESSIONS = parseInt((process.env.MERIDIAN_MAX_CONCURRENT ?? process.env.CLAUDE_PROXY_MAX_CONCURRENT) || "10", 10);
|
|
20618
20901
|
let activeSessions = 0;
|
|
20619
20902
|
const sessionQueue = [];
|
|
20903
|
+
const insideSessionSlot = new AsyncLocalStorage;
|
|
20620
20904
|
async function acquireSession() {
|
|
20621
20905
|
if (activeSessions < MAX_CONCURRENT_SESSIONS) {
|
|
20622
20906
|
activeSessions++;
|
|
@@ -20797,6 +21081,25 @@ data: ${JSON.stringify(lastError)}
|
|
|
20797
21081
|
if (lineageResult.type === "undo" && adapterBase === "opencode" && !agentSessionId) {
|
|
20798
21082
|
lineageResult = { type: "diverged", reason: "missing-session-header" };
|
|
20799
21083
|
}
|
|
21084
|
+
if (pipeline.some((t) => t.onSession)) {
|
|
21085
|
+
const mismatch = lineageResult.type === "diverged" ? lineageResult.mismatch : undefined;
|
|
21086
|
+
runTransformHook(pipeline, "onSession", {
|
|
21087
|
+
adapter: adapterBase,
|
|
21088
|
+
lineage: lineageResult.type,
|
|
21089
|
+
reason: lineageResult.type === "diverged" ? lineageResult.reason : undefined,
|
|
21090
|
+
sessionKey: profileSessionId,
|
|
21091
|
+
storedCount: mismatch?.storedCount,
|
|
21092
|
+
incomingCount: (body.messages || []).length,
|
|
21093
|
+
prefixOverlap: lineageResult.type === "diverged" ? lineageResult.prefixOverlap : undefined,
|
|
21094
|
+
mismatch: mismatch && mismatch.index >= 0 ? {
|
|
21095
|
+
index: mismatch.index,
|
|
21096
|
+
storedDigest: mismatch.storedDigest,
|
|
21097
|
+
incomingDigest: mismatch.incomingDigest,
|
|
21098
|
+
previousDigest: mismatch.previousDigest,
|
|
21099
|
+
incomingShape: mismatch.incomingShape
|
|
21100
|
+
} : undefined
|
|
21101
|
+
}, adapterBase);
|
|
21102
|
+
}
|
|
20800
21103
|
const isResume = lineageResult.type === "continuation" || lineageResult.type === "compaction";
|
|
20801
21104
|
const isUndo = lineageResult.type === "undo";
|
|
20802
21105
|
const cachedSession = lineageResult.type !== "diverged" ? lineageResult.session : undefined;
|
|
@@ -21110,8 +21413,9 @@ data: ${JSON.stringify(lastError)}
|
|
|
21110
21413
|
}
|
|
21111
21414
|
let tokenRefreshed = false;
|
|
21112
21415
|
let didFreshBaseRetry = false;
|
|
21113
|
-
let
|
|
21416
|
+
let resumeRefusalRetries = 0;
|
|
21114
21417
|
let busySessionFork = false;
|
|
21418
|
+
let sawUnresumableRefusal = false;
|
|
21115
21419
|
while (true) {
|
|
21116
21420
|
let didYieldContent = false;
|
|
21117
21421
|
const attemptStderrStart = stderrLines.length;
|
|
@@ -21174,29 +21478,33 @@ data: ${JSON.stringify(lastError)}
|
|
|
21174
21478
|
releaseHeldDenies("non_stream_attempt_error");
|
|
21175
21479
|
if (didYieldContent)
|
|
21176
21480
|
throw error;
|
|
21177
|
-
|
|
21178
|
-
`))
|
|
21179
|
-
|
|
21180
|
-
|
|
21181
|
-
|
|
21182
|
-
|
|
21183
|
-
|
|
21481
|
+
const refusal = classifyResumeRefusal(error, resumeSessionId ? stderrLines.slice(attemptStderrStart).join(`
|
|
21482
|
+
`) : undefined);
|
|
21483
|
+
if (refusal === "unresumable")
|
|
21484
|
+
sawUnresumableRefusal = true;
|
|
21485
|
+
if (resumeSessionId && (refusal === "busy" || refusal === "unresumable")) {
|
|
21486
|
+
if (resumeRefusalRetries < RESUME_REFUSAL_MAX_RETRIES) {
|
|
21487
|
+
resumeRefusalRetries++;
|
|
21488
|
+
claudeLog("session.resume_retry", { mode: "non_stream", refusal, attempt: resumeRefusalRetries, resumeSessionId });
|
|
21489
|
+
plog(`[PROXY] ${requestMeta.requestId} resume refused (${refusal}), retrying ${resumeRefusalRetries}/${RESUME_REFUSAL_MAX_RETRIES}`);
|
|
21490
|
+
await new Promise((resolve3) => setTimeout(resolve3, RESUME_REFUSAL_RETRY_DELAY_MS * resumeRefusalRetries));
|
|
21184
21491
|
continue;
|
|
21185
21492
|
}
|
|
21186
|
-
if (!busySessionFork) {
|
|
21493
|
+
if (refusal === "busy" && !busySessionFork) {
|
|
21187
21494
|
busySessionFork = true;
|
|
21188
21495
|
claudeLog("session.busy_fork", { mode: "non_stream", resumeSessionId });
|
|
21189
|
-
plog(`[PROXY] ${requestMeta.requestId} session still busy after ${
|
|
21496
|
+
plog(`[PROXY] ${requestMeta.requestId} session still busy after ${RESUME_REFUSAL_MAX_RETRIES} retries — forking session`);
|
|
21190
21497
|
continue;
|
|
21191
21498
|
}
|
|
21192
21499
|
}
|
|
21193
|
-
if (
|
|
21194
|
-
claudeLog("session.
|
|
21500
|
+
if (refusal === "missing-message" || sawUnresumableRefusal) {
|
|
21501
|
+
claudeLog("session.resume_replay", {
|
|
21195
21502
|
mode: "non_stream",
|
|
21503
|
+
refusal,
|
|
21196
21504
|
rollbackUuid: undoRollbackUuid,
|
|
21197
21505
|
resumeSessionId
|
|
21198
21506
|
});
|
|
21199
|
-
plog(`[PROXY]
|
|
21507
|
+
plog(`[PROXY] ${requestMeta.requestId} session unusable (${refusal}), evicting and replaying as fresh session`);
|
|
21200
21508
|
evictSession(profileSessionId, profileScopedCwd, allMessages);
|
|
21201
21509
|
sdkUuidMap.length = 0;
|
|
21202
21510
|
for (let i = 0;i < allMessages.length; i++)
|
|
@@ -21467,7 +21775,12 @@ data: ${JSON.stringify(lastError)}
|
|
|
21467
21775
|
Subprocess stderr: ${stderrOutput}`;
|
|
21468
21776
|
}
|
|
21469
21777
|
const sdkTerm = extractSdkTermination(error instanceof Error ? error.message : String(error));
|
|
21470
|
-
const canRecoverAsToolUse =
|
|
21778
|
+
const canRecoverAsToolUse = canRecoverCapturedToolUses({
|
|
21779
|
+
reason: sdkTerm.reason,
|
|
21780
|
+
passthrough,
|
|
21781
|
+
capturedToolUses: capturedToolUses.length,
|
|
21782
|
+
abortIsOurs: true
|
|
21783
|
+
});
|
|
21471
21784
|
if (canRecoverAsToolUse) {
|
|
21472
21785
|
diagnosticLog2.session(`${requestMeta.requestId} sdk_termination_recovered ${formatSdkTermination(sdkTerm, {
|
|
21473
21786
|
model,
|
|
@@ -21751,8 +22064,9 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
|
|
|
21751
22064
|
}
|
|
21752
22065
|
let tokenRefreshed = false;
|
|
21753
22066
|
let didFreshBaseRetry = false;
|
|
21754
|
-
let
|
|
22067
|
+
let resumeRefusalRetries = 0;
|
|
21755
22068
|
let busySessionFork = false;
|
|
22069
|
+
let sawUnresumableRefusal = false;
|
|
21756
22070
|
while (true) {
|
|
21757
22071
|
let didYieldClientEvent = false;
|
|
21758
22072
|
const attemptStderrStart = stderrLines.length;
|
|
@@ -21813,29 +22127,33 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
|
|
|
21813
22127
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
21814
22128
|
if (didYieldClientEvent)
|
|
21815
22129
|
throw error;
|
|
21816
|
-
|
|
21817
|
-
`))
|
|
21818
|
-
|
|
21819
|
-
|
|
21820
|
-
|
|
21821
|
-
|
|
21822
|
-
|
|
22130
|
+
const refusal = classifyResumeRefusal(error, resumeSessionId ? stderrLines.slice(attemptStderrStart).join(`
|
|
22131
|
+
`) : undefined);
|
|
22132
|
+
if (refusal === "unresumable")
|
|
22133
|
+
sawUnresumableRefusal = true;
|
|
22134
|
+
if (resumeSessionId && (refusal === "busy" || refusal === "unresumable")) {
|
|
22135
|
+
if (resumeRefusalRetries < RESUME_REFUSAL_MAX_RETRIES) {
|
|
22136
|
+
resumeRefusalRetries++;
|
|
22137
|
+
claudeLog("session.resume_retry", { mode: "stream", refusal, attempt: resumeRefusalRetries, resumeSessionId });
|
|
22138
|
+
plog(`[PROXY] ${requestMeta.requestId} resume refused (${refusal}), retrying ${resumeRefusalRetries}/${RESUME_REFUSAL_MAX_RETRIES}`);
|
|
22139
|
+
await new Promise((resolve3) => setTimeout(resolve3, RESUME_REFUSAL_RETRY_DELAY_MS * resumeRefusalRetries));
|
|
21823
22140
|
continue;
|
|
21824
22141
|
}
|
|
21825
|
-
if (!busySessionFork) {
|
|
22142
|
+
if (refusal === "busy" && !busySessionFork) {
|
|
21826
22143
|
busySessionFork = true;
|
|
21827
22144
|
claudeLog("session.busy_fork", { mode: "stream", resumeSessionId });
|
|
21828
|
-
plog(`[PROXY] ${requestMeta.requestId} session still busy after ${
|
|
22145
|
+
plog(`[PROXY] ${requestMeta.requestId} session still busy after ${RESUME_REFUSAL_MAX_RETRIES} retries — forking session`);
|
|
21829
22146
|
continue;
|
|
21830
22147
|
}
|
|
21831
22148
|
}
|
|
21832
|
-
if (
|
|
21833
|
-
claudeLog("session.
|
|
22149
|
+
if (refusal === "missing-message" || sawUnresumableRefusal) {
|
|
22150
|
+
claudeLog("session.resume_replay", {
|
|
21834
22151
|
mode: "stream",
|
|
22152
|
+
refusal,
|
|
21835
22153
|
rollbackUuid: undoRollbackUuid,
|
|
21836
22154
|
resumeSessionId
|
|
21837
22155
|
});
|
|
21838
|
-
plog(`[PROXY]
|
|
22156
|
+
plog(`[PROXY] ${requestMeta.requestId} session unusable (${refusal}), evicting and replaying as fresh session`);
|
|
21839
22157
|
evictSession(profileSessionId, profileScopedCwd, allMessages);
|
|
21840
22158
|
sdkUuidMap.length = 0;
|
|
21841
22159
|
for (let i = 0;i < allMessages.length; i++)
|
|
@@ -22673,7 +22991,12 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
22673
22991
|
} : classifyError(errMsg, model);
|
|
22674
22992
|
claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
|
|
22675
22993
|
const sdkTerm = extractSdkTermination(errMsg);
|
|
22676
|
-
const canRecoverAsToolUse = (
|
|
22994
|
+
const canRecoverAsToolUse = canRecoverCapturedToolUses({
|
|
22995
|
+
reason: sdkTerm.reason,
|
|
22996
|
+
passthrough,
|
|
22997
|
+
capturedToolUses: capturedToolUses.length,
|
|
22998
|
+
abortIsOurs: sawDuplicateToolUse || earlyStopFired
|
|
22999
|
+
}) && messageStartEmitted;
|
|
22677
23000
|
if (canRecoverAsToolUse) {
|
|
22678
23001
|
diagnosticLog2.session(`${requestMeta.requestId} sdk_termination_recovered ${formatSdkTermination(sdkTerm, {
|
|
22679
23002
|
model,
|
|
@@ -22800,7 +23123,7 @@ data: {"type":"message_stop"}
|
|
|
22800
23123
|
error: streamErr.type
|
|
22801
23124
|
});
|
|
22802
23125
|
if (messageStartEmitted) {
|
|
22803
|
-
const errorStopReason =
|
|
23126
|
+
const errorStopReason = "max_tokens";
|
|
22804
23127
|
claudeLog("response.error_envelope", {
|
|
22805
23128
|
mode: "stream",
|
|
22806
23129
|
stopReason: errorStopReason,
|
|
@@ -22909,10 +23232,14 @@ data: ${JSON.stringify({
|
|
|
22909
23232
|
const requestId = c.req.header("x-request-id") || randomUUID();
|
|
22910
23233
|
const queueEnteredAt = Date.now();
|
|
22911
23234
|
claudeLog("request.enter", { requestId, endpoint });
|
|
23235
|
+
const held = insideSessionSlot.getStore();
|
|
23236
|
+
if (held) {
|
|
23237
|
+
return handleMessages(c, { requestId, endpoint, ...held });
|
|
23238
|
+
}
|
|
22912
23239
|
await acquireSession();
|
|
22913
23240
|
const queueStartedAt = Date.now();
|
|
22914
23241
|
try {
|
|
22915
|
-
return await handleMessages(c, { requestId, endpoint, queueEnteredAt, queueStartedAt });
|
|
23242
|
+
return await insideSessionSlot.run({ queueEnteredAt, queueStartedAt }, () => handleMessages(c, { requestId, endpoint, queueEnteredAt, queueStartedAt }));
|
|
22916
23243
|
} finally {
|
|
22917
23244
|
releaseSession();
|
|
22918
23245
|
}
|