@rynfar/meridian 1.65.2 → 1.67.0

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.
Files changed (36) hide show
  1. package/README.md +9 -3
  2. package/dist/{cli-syjqjygv.js → cli-35jx6vmy.js} +447 -56
  3. package/dist/{cli-pdpry6q0.js → cli-5jxyma6z.js} +5 -2
  4. package/dist/{cli-0ed6j0vk.js → cli-9e5cxp89.js} +51 -17
  5. package/dist/cli.js +5 -5
  6. package/dist/{profileCli-ap7eg985.js → profileCli-1eecbmgk.js} +1 -1
  7. package/dist/{profiles-4ajzjqhm.js → profiles-wch9h234.js} +1 -1
  8. package/dist/proxy/adapter.d.ts +14 -0
  9. package/dist/proxy/adapter.d.ts.map +1 -1
  10. package/dist/proxy/adapters/claudecode.d.ts +29 -0
  11. package/dist/proxy/adapters/claudecode.d.ts.map +1 -1
  12. package/dist/proxy/adapters/prime.d.ts.map +1 -1
  13. package/dist/proxy/errors.d.ts +1 -1
  14. package/dist/proxy/errors.d.ts.map +1 -1
  15. package/dist/proxy/models.d.ts +29 -11
  16. package/dist/proxy/models.d.ts.map +1 -1
  17. package/dist/proxy/oauthUsage.d.ts.map +1 -1
  18. package/dist/proxy/openai.d.ts +15 -0
  19. package/dist/proxy/openai.d.ts.map +1 -1
  20. package/dist/proxy/openaiResponses.d.ts +4 -11
  21. package/dist/proxy/openaiResponses.d.ts.map +1 -1
  22. package/dist/proxy/retryAfter.d.ts +90 -0
  23. package/dist/proxy/retryAfter.d.ts.map +1 -0
  24. package/dist/proxy/routing.d.ts +11 -0
  25. package/dist/proxy/routing.d.ts.map +1 -1
  26. package/dist/proxy/server.d.ts.map +1 -1
  27. package/dist/proxy/sessionTree.d.ts +113 -0
  28. package/dist/proxy/sessionTree.d.ts.map +1 -0
  29. package/dist/server.js +3 -3
  30. package/dist/telemetry/dashboard.d.ts.map +1 -1
  31. package/dist/telemetry/pricing.d.ts.map +1 -1
  32. package/dist/telemetry/routes.d.ts +10 -1
  33. package/dist/telemetry/routes.d.ts.map +1 -1
  34. package/dist/telemetry/types.d.ts +16 -0
  35. package/dist/telemetry/types.d.ts.map +1 -1
  36. package/package.json +2 -2
@@ -2,6 +2,7 @@ import {
2
2
  AssignmentStore,
3
3
  ProfileExhaustion,
4
4
  choosePriorityProfile,
5
+ findCooldownReset,
5
6
  getActiveProfileId,
6
7
  getEffectiveProfiles,
7
8
  getPriorityFailbackPolicy,
@@ -13,7 +14,7 @@ import {
13
14
  restoreActiveProfile,
14
15
  setActiveProfile,
15
16
  shouldPromotePriorityAssignment
16
- } from "./cli-pdpry6q0.js";
17
+ } from "./cli-5jxyma6z.js";
17
18
  import {
18
19
  isTrackedPlugin,
19
20
  recordError,
@@ -53,7 +54,7 @@ import {
53
54
  resolveSdkModelDefaults,
54
55
  stripExtendedContext,
55
56
  subscriptionIncludesExtendedContext
56
- } from "./cli-0ed6j0vk.js";
57
+ } from "./cli-9e5cxp89.js";
57
58
  import {
58
59
  claudeLog,
59
60
  createPlatformCredentialStore,
@@ -477,7 +478,9 @@ var init_pricing = __esm(() => {
477
478
  HAIKU_3 = rates(0.25, 1.25);
478
479
  BUILTIN_MODEL_PRICING = {
479
480
  fable: FABLE,
481
+ "claude-fable-5-1": FABLE,
480
482
  "claude-fable-5": FABLE,
483
+ "claude-mythos-5-1": FABLE,
481
484
  "claude-mythos-5": FABLE,
482
485
  opus: OPUS,
483
486
  "claude-opus-5": OPUS,
@@ -2827,7 +2830,7 @@ function extractClaudeCodeClientCwd(body) {
2827
2830
  const match2 = systemText.match(/Primary working directory:\s*([^\n<]+)/i);
2828
2831
  return match2?.[1]?.trim() || undefined;
2829
2832
  }
2830
- function extractClaudeCodeSessionId(body) {
2833
+ function extractClaudeCodeSessionIdentity(body) {
2831
2834
  if (!body || typeof body !== "object")
2832
2835
  return;
2833
2836
  const metadata = body.metadata;
@@ -2845,7 +2848,17 @@ function extractClaudeCodeSessionId(body) {
2845
2848
  if (!userMetadata || typeof userMetadata !== "object")
2846
2849
  return;
2847
2850
  const sessionId = userMetadata.session_id;
2848
- return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : undefined;
2851
+ if (typeof sessionId !== "string" || sessionId.length === 0)
2852
+ return;
2853
+ const parentSessionId = userMetadata.parent_session_id;
2854
+ const parent = typeof parentSessionId === "string" && parentSessionId.length > 0 && parentSessionId !== sessionId ? parentSessionId : undefined;
2855
+ return parent ? { sessionId, parentSessionId: parent } : { sessionId };
2856
+ }
2857
+ function extractClaudeCodeSessionId(body) {
2858
+ return extractClaudeCodeSessionIdentity(body)?.sessionId;
2859
+ }
2860
+ function extractClaudeCodeParentSessionId(body) {
2861
+ return extractClaudeCodeSessionIdentity(body)?.parentSessionId;
2849
2862
  }
2850
2863
  var claudeCodeAdapter;
2851
2864
  var init_claudecode = __esm(() => {
@@ -2858,6 +2871,9 @@ var init_claudecode = __esm(() => {
2858
2871
  getSessionId(_c, body) {
2859
2872
  return extractClaudeCodeSessionId(body);
2860
2873
  },
2874
+ getParentSessionId(_c, body) {
2875
+ return extractClaudeCodeParentSessionId(body);
2876
+ },
2861
2877
  extractWorkingDirectory(_body) {
2862
2878
  return;
2863
2879
  },
@@ -3121,6 +3137,11 @@ var init_prime = __esm(() => {
3121
3137
  getSessionId(c, body) {
3122
3138
  return c.req.header("x-session-affinity") ?? extractClaudeCodeSessionId(body);
3123
3139
  },
3140
+ getParentSessionId(c, body) {
3141
+ if (c.req.header("x-session-affinity"))
3142
+ return;
3143
+ return extractClaudeCodeParentSessionId(body);
3144
+ },
3124
3145
  extractWorkingDirectory(body) {
3125
3146
  return extractPrimeCwd(body);
3126
3147
  },
@@ -6567,6 +6588,129 @@ function linkRequestAbort(signal) {
6567
6588
  };
6568
6589
  }
6569
6590
 
6591
+ // src/proxy/sessionTree.ts
6592
+ var EMPTY_CANCELLATION = { keys: [], requestIds: [] };
6593
+ var MAX_SUBTREE_DEPTH = 64;
6594
+ function truncateSessionKey(key, length = 8) {
6595
+ return key.length > length ? `${key.slice(0, length)}…` : key;
6596
+ }
6597
+
6598
+ class SessionTreeRegistry {
6599
+ nextToken = 1;
6600
+ entries = new Map;
6601
+ childrenByParent = new Map;
6602
+ propagations = 0;
6603
+ cancelledDescendants = 0;
6604
+ register(entry) {
6605
+ const token = this.nextToken++;
6606
+ this.entries.set(token, entry);
6607
+ const indexedParent = entry.parentKey && entry.parentKey !== entry.sessionKey ? entry.parentKey : undefined;
6608
+ if (indexedParent) {
6609
+ let siblings = this.childrenByParent.get(indexedParent);
6610
+ if (!siblings) {
6611
+ siblings = new Set;
6612
+ this.childrenByParent.set(indexedParent, siblings);
6613
+ }
6614
+ siblings.add(token);
6615
+ }
6616
+ let released = false;
6617
+ return {
6618
+ release: () => {
6619
+ if (released)
6620
+ return;
6621
+ released = true;
6622
+ this.entries.delete(token);
6623
+ if (!indexedParent)
6624
+ return;
6625
+ const siblings = this.childrenByParent.get(indexedParent);
6626
+ if (!siblings)
6627
+ return;
6628
+ siblings.delete(token);
6629
+ if (siblings.size === 0)
6630
+ this.childrenByParent.delete(indexedParent);
6631
+ }
6632
+ };
6633
+ }
6634
+ descendantsOf(sessionKey) {
6635
+ const visitedKeys = new Set([sessionKey]);
6636
+ let frontier = [sessionKey];
6637
+ const found = [];
6638
+ for (let depth = 0;depth < MAX_SUBTREE_DEPTH && frontier.length > 0; depth++) {
6639
+ const next = [];
6640
+ for (const parentKey of frontier) {
6641
+ const tokens = this.childrenByParent.get(parentKey);
6642
+ if (!tokens)
6643
+ continue;
6644
+ for (const token of tokens) {
6645
+ const entry = this.entries.get(token);
6646
+ if (!entry)
6647
+ continue;
6648
+ found.push(entry);
6649
+ if (visitedKeys.has(entry.sessionKey))
6650
+ continue;
6651
+ visitedKeys.add(entry.sessionKey);
6652
+ next.push(entry.sessionKey);
6653
+ }
6654
+ }
6655
+ frontier = next;
6656
+ }
6657
+ return found;
6658
+ }
6659
+ liveRequestsFor(sessionKey) {
6660
+ const found = [];
6661
+ for (const entry of this.entries.values()) {
6662
+ if (entry.sessionKey === sessionKey)
6663
+ found.push(entry);
6664
+ }
6665
+ return found;
6666
+ }
6667
+ cancelDescendants(sessionKey, reason) {
6668
+ return this.cancel(sessionKey, { reason });
6669
+ }
6670
+ cancelSubtree(sessionKey, reason) {
6671
+ return this.cancel(sessionKey, { reason, includeSelf: true });
6672
+ }
6673
+ cancel(sessionKey, options) {
6674
+ const descendants = this.descendantsOf(sessionKey);
6675
+ const targets = options.includeSelf ? [...this.liveRequestsFor(sessionKey), ...descendants] : descendants;
6676
+ if (targets.length === 0)
6677
+ return EMPTY_CANCELLATION;
6678
+ const keys = [];
6679
+ const requestIds = [];
6680
+ for (const entry of targets) {
6681
+ try {
6682
+ entry.abort(options.reason);
6683
+ } catch {}
6684
+ if (!keys.includes(entry.sessionKey))
6685
+ keys.push(entry.sessionKey);
6686
+ requestIds.push(entry.requestId);
6687
+ }
6688
+ this.propagations++;
6689
+ this.cancelledDescendants += descendants.length;
6690
+ return { keys, requestIds };
6691
+ }
6692
+ stats() {
6693
+ let linked = 0;
6694
+ for (const entry of this.entries.values()) {
6695
+ if (entry.parentKey)
6696
+ linked++;
6697
+ }
6698
+ return {
6699
+ tracked: this.entries.size,
6700
+ linked,
6701
+ propagations: this.propagations,
6702
+ cancelledDescendants: this.cancelledDescendants
6703
+ };
6704
+ }
6705
+ clear() {
6706
+ this.entries.clear();
6707
+ this.childrenByParent.clear();
6708
+ this.propagations = 0;
6709
+ this.cancelledDescendants = 0;
6710
+ }
6711
+ }
6712
+ var processSessionTree = new SessionTreeRegistry;
6713
+
6570
6714
  // src/proxy/concurrency.ts
6571
6715
  var DEFAULT_MAX_CONCURRENT = 10;
6572
6716
  var didWarnInvalidMaxConcurrent = false;
@@ -6746,6 +6890,57 @@ async function closeServerWithGracePeriod(server, options) {
6746
6890
  await closePromise;
6747
6891
  }
6748
6892
 
6893
+ // src/proxy/retryAfter.ts
6894
+ var RETRYABLE_STATUSES = new Set([429, 503, 529]);
6895
+ var OVERLOADED_RETRY_AFTER_SECONDS = 5;
6896
+ var RATE_LIMIT_DEFAULT_RETRY_AFTER_SECONDS = 60;
6897
+ var RETRY_AFTER_MIN_SECONDS = 1;
6898
+ var RETRY_AFTER_MAX_SECONDS = 24 * 60 * 60;
6899
+ function parseRetryAfterMs(raw2, now = Date.now()) {
6900
+ if (!raw2)
6901
+ return null;
6902
+ const seconds = Number(raw2);
6903
+ if (Number.isFinite(seconds))
6904
+ return Math.max(0, seconds * 1000);
6905
+ const retryAt = Date.parse(raw2);
6906
+ return Number.isFinite(retryAt) ? Math.max(0, retryAt - now) : null;
6907
+ }
6908
+ function extractRetryAfterSeconds(errMsg) {
6909
+ if (!errMsg)
6910
+ return null;
6911
+ const match2 = errMsg.match(/retry[-_ ]?after"?\s*[:=]\s*"?(\d+)/i);
6912
+ if (!match2?.[1])
6913
+ return null;
6914
+ const seconds = Number(match2[1]);
6915
+ return Number.isFinite(seconds) ? seconds : null;
6916
+ }
6917
+ function retryAfterSeconds(input) {
6918
+ if (!RETRYABLE_STATUSES.has(input.status))
6919
+ return null;
6920
+ const now = input.now ?? Date.now();
6921
+ const upstreamMs = parseRetryAfterMs(input.upstreamRetryAfter, now);
6922
+ if (upstreamMs !== null)
6923
+ return clamp(Math.ceil(upstreamMs / 1000));
6924
+ const embedded = extractRetryAfterSeconds(input.errorMessage);
6925
+ if (embedded !== null)
6926
+ return clamp(embedded);
6927
+ if (input.resetAtMs != null && input.resetAtMs > now) {
6928
+ return clamp(Math.ceil((input.resetAtMs - now) / 1000));
6929
+ }
6930
+ return input.status === 429 ? RATE_LIMIT_DEFAULT_RETRY_AFTER_SECONDS : OVERLOADED_RETRY_AFTER_SECONDS;
6931
+ }
6932
+ function retryAfterHeaders(seconds) {
6933
+ return seconds === null ? {} : { "Retry-After": String(seconds) };
6934
+ }
6935
+ function retryAfterBodyFields(seconds) {
6936
+ return seconds === null ? {} : { retry_after: seconds };
6937
+ }
6938
+ function clamp(seconds) {
6939
+ if (!Number.isFinite(seconds))
6940
+ return RATE_LIMIT_DEFAULT_RETRY_AFTER_SECONDS;
6941
+ return Math.min(RETRY_AFTER_MAX_SECONDS, Math.max(RETRY_AFTER_MIN_SECONDS, Math.round(seconds)));
6942
+ }
6943
+
6749
6944
  // src/proxy/oauthUsage.ts
6750
6945
  var OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
6751
6946
  var OAUTH_BETA_HEADER = "oauth-2025-04-20";
@@ -6776,15 +6971,6 @@ function normalizeUtilization(raw2) {
6776
6971
  return null;
6777
6972
  return Math.max(0, raw2 / 100);
6778
6973
  }
6779
- function parseRetryAfterMs(raw2) {
6780
- if (!raw2)
6781
- return null;
6782
- const seconds = Number(raw2);
6783
- if (Number.isFinite(seconds))
6784
- return Math.max(0, seconds * 1000);
6785
- const retryAt = Date.parse(raw2);
6786
- return Number.isFinite(retryAt) ? Math.max(0, retryAt - Date.now()) : null;
6787
- }
6788
6974
  function modelScopedWindowType(limit) {
6789
6975
  if (limit.kind !== "weekly_scoped")
6790
6976
  return null;
@@ -22081,6 +22267,13 @@ function render(s, reqs, logs) {
22081
22267
  + card('Median TTFB', ms(s.ttfb.p50), 'p95: ' + ms(s.ttfb.p95))
22082
22268
  + card('Proxy Overhead', ms(s.proxyOverhead.p50), 'p95: ' + ms(s.proxyOverhead.p95))
22083
22269
  + card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))
22270
+ // Only rendered when a subagent tree has actually been seen: for a
22271
+ // single-agent client these numbers are permanently zero and would just be
22272
+ // a dead tile. Counts are cumulative, not windowed.
22273
+ + ((s.sessionTree && (s.sessionTree.linked > 0 || s.sessionTree.cancelledDescendants > 0))
22274
+ ? card('Subtree Cancels', s.sessionTree.cancelledDescendants,
22275
+ s.sessionTree.linked + ' linked live / ' + s.sessionTree.propagations + ' propagations')
22276
+ : '')
22084
22277
  + '</div>';
22085
22278
 
22086
22279
  // Token usage cards
@@ -22283,7 +22476,7 @@ timer = setInterval(refresh, 5000);
22283
22476
  // src/telemetry/routes.ts
22284
22477
  var _iconPath = resolve2(dirname2(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
22285
22478
  var _iconSvg = existsSync3(_iconPath) ? readFileSync2(_iconPath, "utf-8") : null;
22286
- function createTelemetryRoutes() {
22479
+ function createTelemetryRoutes(deps = {}) {
22287
22480
  const routes = new Hono2;
22288
22481
  routes.get("/", (c) => {
22289
22482
  return c.html(dashboardHtml);
@@ -22310,7 +22503,8 @@ function createTelemetryRoutes() {
22310
22503
  routes.get("/summary", (c) => {
22311
22504
  const windowMs = Number.parseInt(c.req.query("window") || "3600000", 10);
22312
22505
  const summary = telemetryStore2.summarize(windowMs);
22313
- return c.json(summary);
22506
+ const sessionTree = deps.getSessionTree?.();
22507
+ return c.json(sessionTree ? { ...summary, sessionTree } : summary);
22314
22508
  });
22315
22509
  routes.get("/logs", (c) => {
22316
22510
  const limit = Number.parseInt(c.req.query("limit") || "100", 10);
@@ -22713,15 +22907,23 @@ var BILLING_SIGNALS = [
22713
22907
  /payment (?:method|required|failed|declined|details|info)/,
22714
22908
  /update your payment/,
22715
22909
  /(?:out of|draw from|draws from) extra usage/,
22716
- /insufficient (?:credit|funds|balance)/
22910
+ /insufficient (?:credit|funds|balance)/,
22911
+ /^\s*(?:(?:error|api error|claude code returned an error result|subprocess stderr):\s*)*your (?:group|organization|org)(?:'|’)s usage limit is set to \$\d/m
22717
22912
  ];
22718
22913
  var HIT_YOUR_LIMIT = /hit your (?:[\w-]+ )?limit/;
22719
22914
  var HIT_YOUR_SPEND_LIMIT = /^\s*(?:(?:error|api error|claude code returned an error result|subprocess stderr):\s*)*you(?:'|’)ve hit your (?:[\w'’-]+ ){0,4}(?:spend|usage) limit/m;
22915
+ var REACHED_YOUR_TIER_LIMIT = /^[ \t]*(?:(?:error|api error|claude code returned an error result|subprocess stderr):[ \t]*)*you(?:'|’)ve reached your (?:claude )?(?:fable|mythos|opus|sonnet|haiku)(?: \d+(?:\.\d+)*)? limit(?:(?:[.!][ \t]+|[ \t]+)(?:(?:run[ \t]+)?\/usage-credits(?:[ \t]+to[ \t]+continue)?(?:[ \t]+or[ \t]+switch[ \t]+models[ \t]+with[ \t]+\/model)?|\/model[ \t]+to[ \t]+switch[ \t]+models)\.?|[.!]?)[ \t\r]*$/m;
22720
22916
  var OUT_OF_USAGE_CREDITS = /^\s*(?:(?:error|api error|claude code returned an error result):\s*)*you(?:'|’)re out of usage credits(?:[.!]\s*)?(?:\/model to switch models\.?)?\s*$/;
22721
22917
  var HTTP_401 = /(?:^|[^0-9a-f])401(?![0-9a-f]|:\d)/;
22722
22918
  var HTTP_429 = /(?:^|[^0-9a-f])429(?![0-9a-f]|:\d)/;
22723
22919
  var HTTP_500 = /(?:^|[^0-9a-f])500(?![0-9a-f]|:\d)/;
22724
22920
  var HTTP_503 = /(?:^|[^0-9a-f])503(?![0-9a-f]|:\d)/;
22921
+ var OVERFLOW_PHRASES = [
22922
+ String.raw`prompt is too long`,
22923
+ String.raw`input length and .?max_tokens.? exceed context limit`,
22924
+ String.raw`context[_ ]length[_ ]exceeded`
22925
+ ];
22926
+ var CONTEXT_OVERFLOW_SIGNALS = OVERFLOW_PHRASES.map((phrase) => new RegExp(String.raw`(?:^\s*(?:(?:error|api error|claude code returned an error result|subprocess stderr):\s*)*|"message"\s*:\s*")` + phrase, "m"));
22725
22927
  function classifyError(errMsg, model) {
22726
22928
  const lower = errMsg.toLowerCase();
22727
22929
  if (lower.includes("oauth token has expired") || lower.includes("not logged in")) {
@@ -22738,7 +22940,7 @@ function classifyError(errMsg, model) {
22738
22940
  message: "Claude authentication expired or invalid. Run 'claude login' in your terminal to re-authenticate, then restart the proxy."
22739
22941
  };
22740
22942
  }
22741
- if (HTTP_429.test(lower) || lower.includes("rate limit") || lower.includes("too many requests") || HIT_YOUR_LIMIT.test(lower) || HIT_YOUR_SPEND_LIMIT.test(lower) || lower.includes("usage limit reached") || OUT_OF_USAGE_CREDITS.test(lower)) {
22943
+ if (HTTP_429.test(lower) || lower.includes("rate limit") || lower.includes("too many requests") || HIT_YOUR_LIMIT.test(lower) || HIT_YOUR_SPEND_LIMIT.test(lower) || REACHED_YOUR_TIER_LIMIT.test(lower) || lower.includes("usage limit reached") || OUT_OF_USAGE_CREDITS.test(lower)) {
22742
22944
  const hint = lower.includes("1m") || lower.includes("context") ? extendedContextHint(model) : "";
22743
22945
  return {
22744
22946
  status: 429,
@@ -22753,6 +22955,13 @@ function classifyError(errMsg, model) {
22753
22955
  message: "Claude Max subscription issue. Check your subscription status at https://claude.ai/settings/subscription"
22754
22956
  };
22755
22957
  }
22958
+ if (CONTEXT_OVERFLOW_SIGNALS.some((rx) => rx.test(lower))) {
22959
+ return {
22960
+ status: 400,
22961
+ type: "invalid_request_error",
22962
+ message: "Prompt exceeds the model's context window. Compact or trim the conversation before retrying — an identical retry fails the same way."
22963
+ };
22964
+ }
22756
22965
  if (lower.includes("exited with code") || lower.includes("process exited")) {
22757
22966
  const codeMatch = errMsg.match(/exited with code (\d+)/);
22758
22967
  const code = codeMatch ? codeMatch[1] : "unknown";
@@ -22908,6 +23117,12 @@ ${stderrTail ?? ""}`;
22908
23117
  ...stderrTail ? { stderrTail } : {}
22909
23118
  };
22910
23119
  }
23120
+ if (CONTEXT_OVERFLOW_SIGNALS.some((rx) => rx.test(lower))) {
23121
+ return {
23122
+ reason: "context_overflow",
23123
+ ...stderrTail ? { stderrTail } : {}
23124
+ };
23125
+ }
22911
23126
  if (lower.includes("exited with code") || lower.includes("process exited")) {
22912
23127
  const m = haystack.match(/exited with code (\d+)/i);
22913
23128
  return {
@@ -23355,6 +23570,23 @@ function stopUpdateCheck() {
23355
23570
  }
23356
23571
 
23357
23572
  // src/proxy/openai.ts
23573
+ var ANTHROPIC_USAGE_FIELDS = [
23574
+ "input_tokens",
23575
+ "output_tokens",
23576
+ "cache_read_input_tokens",
23577
+ "cache_creation_input_tokens"
23578
+ ];
23579
+ function mergeAnthropicUsage(current, update) {
23580
+ const merged = { ...current };
23581
+ for (const field of ANTHROPIC_USAGE_FIELDS) {
23582
+ if (typeof update[field] === "number")
23583
+ merged[field] = update[field];
23584
+ }
23585
+ return merged;
23586
+ }
23587
+ function totalAnthropicInputTokens(usage) {
23588
+ return (usage?.input_tokens ?? 0) + (usage?.cache_read_input_tokens ?? 0) + (usage?.cache_creation_input_tokens ?? 0);
23589
+ }
23358
23590
  function extractOpenAiContent(content) {
23359
23591
  if (typeof content === "string")
23360
23592
  return content;
@@ -23596,7 +23828,7 @@ function translateAnthropicToOpenAi(response, completionId, model, created, opti
23596
23828
  }));
23597
23829
  const thinkingPassthrough = options?.thinkingPassthrough;
23598
23830
  const thinking = thinkingPassthrough !== false ? contentBlocks.filter((b) => b.type === "thinking").map((b) => b.thinking).join("") : "";
23599
- const promptTokens = response.usage?.input_tokens ?? 0;
23831
+ const promptTokens = totalAnthropicInputTokens(response.usage);
23600
23832
  const completionTokens = response.usage?.output_tokens ?? 0;
23601
23833
  return {
23602
23834
  id: completionId,
@@ -23616,7 +23848,11 @@ function translateAnthropicToOpenAi(response, completionId, model, created, opti
23616
23848
  usage: {
23617
23849
  prompt_tokens: promptTokens,
23618
23850
  completion_tokens: completionTokens,
23619
- total_tokens: promptTokens + completionTokens
23851
+ total_tokens: promptTokens + completionTokens,
23852
+ prompt_tokens_details: {
23853
+ cached_tokens: response.usage?.cache_read_input_tokens ?? 0,
23854
+ cache_write_tokens: response.usage?.cache_creation_input_tokens ?? 0
23855
+ }
23620
23856
  }
23621
23857
  };
23622
23858
  }
@@ -23627,15 +23863,18 @@ function createSseTranslator(ctx) {
23627
23863
  if (event.type === "content_block_start" && event.content_block?.type === "tool_use" && typeof event.content_block.name === "string") {
23628
23864
  toolCallIndex++;
23629
23865
  }
23866
+ if (event.type === "message_start" && event.message?.usage) {
23867
+ lastUsage = mergeAnthropicUsage(lastUsage, event.message.usage);
23868
+ }
23630
23869
  if (event.type === "message_delta" && event.usage) {
23631
- lastUsage = event.usage;
23870
+ lastUsage = mergeAnthropicUsage(lastUsage, event.usage);
23632
23871
  }
23633
23872
  return translateAnthropicSseEvent(event, ctx.completionId, ctx.model, ctx.created, toolCallIndex, ctx.thinkingPassthrough);
23634
23873
  };
23635
23874
  translate.buildUsageChunk = () => {
23636
23875
  if (!ctx.includeUsage || !lastUsage)
23637
23876
  return null;
23638
- const promptTokens = lastUsage.input_tokens ?? 0;
23877
+ const promptTokens = totalAnthropicInputTokens(lastUsage);
23639
23878
  const completionTokens = lastUsage.output_tokens ?? 0;
23640
23879
  return {
23641
23880
  id: ctx.completionId,
@@ -23646,7 +23885,11 @@ function createSseTranslator(ctx) {
23646
23885
  usage: {
23647
23886
  prompt_tokens: promptTokens,
23648
23887
  completion_tokens: completionTokens,
23649
- total_tokens: promptTokens + completionTokens
23888
+ total_tokens: promptTokens + completionTokens,
23889
+ prompt_tokens_details: {
23890
+ cached_tokens: lastUsage.cache_read_input_tokens ?? 0,
23891
+ cache_write_tokens: lastUsage.cache_creation_input_tokens ?? 0
23892
+ }
23650
23893
  }
23651
23894
  };
23652
23895
  };
@@ -23818,6 +24061,15 @@ function buildModelList(extendedContextIncluded, now = Math.floor(Date.now() / 1
23818
24061
  context_window: extendedContextIncluded ? 1e6 : 200000,
23819
24062
  capabilities: FULL_CAPABILITIES
23820
24063
  },
24064
+ {
24065
+ id: "claude-fable-5-1",
24066
+ object: "model",
24067
+ created: now,
24068
+ owned_by: "anthropic",
24069
+ display_name: "Claude Fable 5.1",
24070
+ context_window: extendedContextIncluded ? 1e6 : 200000,
24071
+ capabilities: FULL_CAPABILITIES
24072
+ },
23821
24073
  {
23822
24074
  id: "claude-fable-5",
23823
24075
  object: "model",
@@ -23988,9 +24240,17 @@ function reasoningRequested(body) {
23988
24240
  return Array.isArray(include) && include.some((v) => typeof v === "string" && v.startsWith("reasoning"));
23989
24241
  }
23990
24242
  function mapUsage(usage) {
23991
- const input = usage?.input_tokens ?? 0;
24243
+ const input = totalAnthropicInputTokens(usage);
23992
24244
  const output = usage?.output_tokens ?? 0;
23993
- return { input_tokens: input, output_tokens: output, total_tokens: input + output };
24245
+ return {
24246
+ input_tokens: input,
24247
+ output_tokens: output,
24248
+ total_tokens: input + output,
24249
+ input_tokens_details: {
24250
+ cached_tokens: usage?.cache_read_input_tokens ?? 0,
24251
+ cache_write_tokens: usage?.cache_creation_input_tokens ?? 0
24252
+ }
24253
+ };
23994
24254
  }
23995
24255
  function toResponsesStatus(stopReason) {
23996
24256
  return stopReason === "max_tokens" ? "incomplete" : "completed";
@@ -24048,8 +24308,7 @@ function translateAnthropicToResponses(res, ctx) {
24048
24308
  function createResponsesSseTranslator(ctx) {
24049
24309
  let seq = 0;
24050
24310
  let outputIndex = 0;
24051
- let inputTokens = 0;
24052
- let outputTokens = 0;
24311
+ let usage;
24053
24312
  let createdEmitted = false;
24054
24313
  let stopReason;
24055
24314
  const blocks = new Map;
@@ -24070,7 +24329,8 @@ function createResponsesSseTranslator(ctx) {
24070
24329
  const out = [];
24071
24330
  switch (event.type) {
24072
24331
  case "message_start": {
24073
- inputTokens = event.message?.usage?.input_tokens ?? 0;
24332
+ if (event.message?.usage)
24333
+ usage = mergeAnthropicUsage(usage, event.message.usage);
24074
24334
  if (!createdEmitted) {
24075
24335
  createdEmitted = true;
24076
24336
  out.push(emit("response.created", { response: responseEnvelope("in_progress", { output: [] }) }));
@@ -24186,8 +24446,8 @@ function createResponsesSseTranslator(ctx) {
24186
24446
  break;
24187
24447
  }
24188
24448
  case "message_delta": {
24189
- if (typeof event.usage?.output_tokens === "number")
24190
- outputTokens = event.usage.output_tokens;
24449
+ if (event.usage)
24450
+ usage = mergeAnthropicUsage(usage, event.usage);
24191
24451
  if (typeof event.delta?.stop_reason === "string")
24192
24452
  stopReason = event.delta.stop_reason;
24193
24453
  break;
@@ -24197,7 +24457,7 @@ function createResponsesSseTranslator(ctx) {
24197
24457
  out.push(emit(status === "incomplete" ? "response.incomplete" : "response.completed", {
24198
24458
  response: responseEnvelope(status, {
24199
24459
  output: finalOutput,
24200
- usage: { input_tokens: inputTokens, output_tokens: outputTokens, total_tokens: inputTokens + outputTokens },
24460
+ usage: mapUsage(usage),
24201
24461
  parallel_tool_calls: true,
24202
24462
  tool_choice: "auto",
24203
24463
  tools: [],
@@ -35438,9 +35698,14 @@ function createProxyServer(config2 = {}) {
35438
35698
  const internalHopToken = randomUUID6();
35439
35699
  const errorEnvelope = (shape, type, message) => shape === "anthropic" ? { type: "error", error: { type, message } } : { error: { type, message, code: null } };
35440
35700
  const DRAIN_MESSAGE = "Meridian is shutting down and is not accepting new requests. Retry against another instance.";
35701
+ const TRANSIENT_RETRY_AFTER_HEADERS = retryAfterHeaders(OVERLOADED_RETRY_AFTER_SECONDS);
35441
35702
  const drainingResponse = (shape = "anthropic") => new Response(JSON.stringify(errorEnvelope(shape, "overloaded_error", DRAIN_MESSAGE)), {
35442
35703
  status: 503,
35443
- headers: { "Content-Type": "application/json", "x-meridian-draining": "1" }
35704
+ headers: {
35705
+ "Content-Type": "application/json",
35706
+ "x-meridian-draining": "1",
35707
+ ...TRANSIENT_RETRY_AFTER_HEADERS
35708
+ }
35444
35709
  });
35445
35710
  async function relayInnerError(internalRes, shape) {
35446
35711
  const errBody = await internalRes.text();
@@ -35456,6 +35721,9 @@ function createProxyServer(config2 = {}) {
35456
35721
  const drainingHeader = internalRes.headers.get("x-meridian-draining");
35457
35722
  if (drainingHeader)
35458
35723
  headers["x-meridian-draining"] = drainingHeader;
35724
+ const innerRetryAfter = internalRes.headers.get("retry-after");
35725
+ if (innerRetryAfter)
35726
+ headers["Retry-After"] = innerRetryAfter;
35459
35727
  return new Response(JSON.stringify(payload), { status: internalRes.status, headers });
35460
35728
  }
35461
35729
  async function* runSdkQueryAttempt(params, signal, requestMeta, mode, activeLocators) {
@@ -35533,13 +35801,20 @@ function createProxyServer(config2 = {}) {
35533
35801
  const setting = getSetting("profileOrder");
35534
35802
  return Array.isArray(setting) && setting.length > 0 ? setting : undefined;
35535
35803
  }
35536
- function priorityCooldownUntil(profileId, now) {
35537
- const windows = rateLimitStore.getAll(profileId).map((e) => ({
35804
+ function profileCooldownWindows(profileId) {
35805
+ return rateLimitStore.getAll(profileId).map((e) => ({
35538
35806
  type: e.rateLimitType ?? "",
35539
35807
  resetsAt: e.resetsAt,
35540
35808
  exhausted: e.status === "rejected" || (e.utilization ?? 0) >= 1
35541
35809
  }));
35542
- return resolveCooldownUntil(windows, now, PRIORITY_DEFAULT_COOLDOWN_MS);
35810
+ }
35811
+ function priorityCooldownUntil(profileId, now) {
35812
+ return resolveCooldownUntil(profileCooldownWindows(profileId), now, PRIORITY_DEFAULT_COOLDOWN_MS);
35813
+ }
35814
+ function observedResetAtMs(profileId, now) {
35815
+ if (!profileId)
35816
+ return null;
35817
+ return findCooldownReset(profileCooldownWindows(profileId), now);
35543
35818
  }
35544
35819
  function refinePriorityCooldown(profileId) {
35545
35820
  const target = getEffectiveProfiles(finalConfig.profiles).find((p) => p.id === profileId);
@@ -35660,7 +35935,7 @@ function createProxyServer(config2 = {}) {
35660
35935
  return options.context.json({
35661
35936
  type: "error",
35662
35937
  error: { type: "overloaded_error", message: "Durable priority attempt state is unavailable" }
35663
- }, 503);
35938
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
35664
35939
  }
35665
35940
  attemptOwnerToken = claim.ownerToken;
35666
35941
  } catch (error51) {
@@ -35671,7 +35946,7 @@ function createProxyServer(config2 = {}) {
35671
35946
  return options.context.json({
35672
35947
  type: "error",
35673
35948
  error: { type: "overloaded_error", message: "Durable priority attempt state is unavailable" }
35674
- }, 503);
35949
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
35675
35950
  }
35676
35951
  }
35677
35952
  const settleAttempt = (disposition) => {
@@ -35691,11 +35966,12 @@ function createProxyServer(config2 = {}) {
35691
35966
  const unavailableAttemptResponse = () => options.context.json({
35692
35967
  type: "error",
35693
35968
  error: { type: "overloaded_error", message: "Durable priority attempt state is unavailable" }
35694
- }, 503);
35969
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
35695
35970
  let lastError = null;
35696
35971
  let lastStatus = 429;
35697
35972
  let previous = null;
35698
35973
  let previousReason = "rate_limit_error";
35974
+ let earliestPoolReset = null;
35699
35975
  for (const [attempt, candidate] of options.candidateIds.entries()) {
35700
35976
  const exposure = { committed: false };
35701
35977
  const priorityPublication = options.durableRoute && options.publicationTurn ? {
@@ -35734,6 +36010,9 @@ function createProxyServer(config2 = {}) {
35734
36010
  const quotaRefusal = isQuotaRefusal(reason);
35735
36011
  const cooldownUntil = quotaRefusal ? priorityCooldownUntil(candidate, Date.now()) : Date.now() + PRIORITY_DEFAULT_COOLDOWN_MS;
35736
36012
  priorityExhaustion.mark(candidate, cooldownUntil, reason);
36013
+ if (earliestPoolReset === null || cooldownUntil < earliestPoolReset) {
36014
+ earliestPoolReset = cooldownUntil;
36015
+ }
35737
36016
  claudeLog("priority.exhausted", { profile: candidate, until: cooldownUntil, reason });
35738
36017
  if (quotaRefusal)
35739
36018
  refinePriorityCooldown(candidate);
@@ -35759,7 +36038,14 @@ data: ${JSON.stringify(lastError)}
35759
36038
  headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache" }
35760
36039
  });
35761
36040
  }
35762
- return new Response(JSON.stringify(lastError), { status: lastStatus, headers: { "content-type": "application/json" } });
36041
+ const poolRetryAfter = retryAfterSeconds({
36042
+ status: lastStatus,
36043
+ resetAtMs: earliestPoolReset
36044
+ });
36045
+ return new Response(JSON.stringify(lastError), {
36046
+ status: lastStatus,
36047
+ headers: { "content-type": "application/json", ...retryAfterHeaders(poolRetryAfter) }
36048
+ });
35763
36049
  }
35764
36050
  app.use("/auth/*", requireAuth);
35765
36051
  app.get("/", (c) => {
@@ -35769,7 +36055,7 @@ data: ${JSON.stringify(lastError)}
35769
36055
  status: "ok",
35770
36056
  service: "meridian",
35771
36057
  format: "anthropic",
35772
- endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/responses", "/v1/models", "/v1/design/*", "/design-login", "/telemetry", "/metrics", "/health"]
36058
+ endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/responses", "/v1/models", "/v1/sessions/:key/cancel", "/v1/design/*", "/design-login", "/telemetry", "/metrics", "/health"]
35773
36059
  });
35774
36060
  }
35775
36061
  return c.html(landingHtml);
@@ -35940,6 +36226,7 @@ data: ${JSON.stringify(lastError)}
35940
36226
  }
35941
36227
  priorityTerminalCommitted = true;
35942
36228
  };
36229
+ let resolvedProfileId;
35943
36230
  try {
35944
36231
  let makePrompt = function() {
35945
36232
  if (structuredMessages) {
@@ -36017,7 +36304,7 @@ data: ${JSON.stringify(lastError)}
36017
36304
  return c.json({
36018
36305
  type: "error",
36019
36306
  error: { type: "overloaded_error", message: "Durable priority routing state is unavailable" }
36020
- }, 503);
36307
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
36021
36308
  }
36022
36309
  if (routeResult.status === "found") {
36023
36310
  durableRoute = { routeKey, expectedGeneration: routeResult.generation };
@@ -36053,7 +36340,7 @@ data: ${JSON.stringify(lastError)}
36053
36340
  return c.json({
36054
36341
  type: "error",
36055
36342
  error: { type: "overloaded_error", message: "Durable priority session state is unavailable" }
36056
- }, 503);
36343
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
36057
36344
  }
36058
36345
  routeMappingIsCurrent = mapped.status === "found" && mapped.generation === routeResult.assignment.mappingGeneration;
36059
36346
  if (!routeMappingIsCurrent) {
@@ -36061,7 +36348,7 @@ data: ${JSON.stringify(lastError)}
36061
36348
  return c.json({
36062
36349
  type: "error",
36063
36350
  error: { type: "overloaded_error", message: "Durable priority session state is unavailable" }
36064
- }, 503);
36351
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
36065
36352
  }
36066
36353
  durableRoute = { ...durableRoute, forceFreshReplay: true };
36067
36354
  }
@@ -36069,7 +36356,7 @@ data: ${JSON.stringify(lastError)}
36069
36356
  return c.json({
36070
36357
  type: "error",
36071
36358
  error: { type: "overloaded_error", message: "Durable priority attempt state is unavailable" }
36072
- }, 503);
36359
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
36073
36360
  } else if (trustedTurn) {
36074
36361
  durableRoute = { routeKey, expectedGeneration: routeResult.generation };
36075
36362
  publicationTurn = trustedTurn;
@@ -36092,7 +36379,7 @@ data: ${JSON.stringify(lastError)}
36092
36379
  return c.json({
36093
36380
  type: "error",
36094
36381
  error: { type: "overloaded_error", message: "Durable priority routing state is unavailable" }
36095
- }, 503);
36382
+ }, 503, TRANSIENT_RETRY_AFTER_HEADERS);
36096
36383
  }
36097
36384
  const pick2 = choosePriorityProfile(order, (id) => priorityExhaustion.isExhausted(id));
36098
36385
  const first = retainOnlyProfile ?? (shouldPromote ? preferred : assignmentIsHealthy ? assignedProfile : pick2?.id ?? preferred);
@@ -36114,13 +36401,15 @@ data: ${JSON.stringify(lastError)}
36114
36401
  }
36115
36402
  }
36116
36403
  const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, options.forcedProfileId || c.req.header("x-meridian-profile") || undefined, routingMode === "sticky" ? { routingMode, stickySessionKey: adapter.getSessionId(c, body) } : undefined);
36404
+ resolvedProfileId = profile.id;
36117
36405
  const authStatus = await getClaudeAuthStatusAsync(profile.id !== "default" ? profile.id : undefined, Object.keys(profile.env).length > 0 ? profile.env : undefined);
36118
36406
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
36119
36407
  const declaredAgentMode = adapter.getAgentMode?.(c, body) ?? c.req.header("x-opencode-agent-mode") ?? null;
36120
36408
  const isSubagentRequest = declaredAgentMode === "subagent" || requestSource?.startsWith("subagent-") === true;
36121
36409
  const agentMode = isSubagentRequest ? "subagent" : declaredAgentMode;
36122
36410
  const requestedModel = typeof body.model === "string" ? body.model : "sonnet";
36123
- let model = mapModelToClaudeModel(requestedModel, authStatus?.subscriptionType, agentMode, profile.id);
36411
+ const benchSessionKey = adapter.getSessionId(c, body) || undefined;
36412
+ let model = mapModelToClaudeModel(requestedModel, authStatus?.subscriptionType, agentMode, profile.id, benchSessionKey);
36124
36413
  const envOverrides = explicitModelPin(requestedModel);
36125
36414
  const cwdResolution = resolveSdkWorkingDirectory({
36126
36415
  envOverride: process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR,
@@ -36825,7 +37114,7 @@ data: ${JSON.stringify(lastError)}
36825
37114
  claudeExecutable = await resolveClaudeExecutableAsync();
36826
37115
  }
36827
37116
  const MAX_RATE_LIMIT_RETRIES = 2;
36828
- const RATE_LIMIT_BASE_DELAY_MS = 1000;
37117
+ const RATE_LIMIT_BASE_DELAY_MS = envInt("RATE_LIMIT_BASE_DELAY_MS", 1000);
36829
37118
  const response = async function* () {
36830
37119
  let rateLimitRetries = 0;
36831
37120
  if (profileCredentialStore) {
@@ -37085,7 +37374,7 @@ data: ${JSON.stringify(lastError)}
37085
37374
  if (hasExtendedContext(model)) {
37086
37375
  const from = model;
37087
37376
  model = stripExtendedContext(model);
37088
- recordExtendedContextRateLimited(profile.id, priorityCooldownUntil(profile.id, Date.now()));
37377
+ recordExtendedContextRateLimited(profile.id, priorityCooldownUntil(profile.id, Date.now()), benchSessionKey);
37089
37378
  claudeLog("upstream.context_fallback", {
37090
37379
  mode: "non_stream",
37091
37380
  from,
@@ -37608,7 +37897,7 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
37608
37897
  let nextClientBlockIndex = 0;
37609
37898
  try {
37610
37899
  const MAX_RATE_LIMIT_RETRIES = 2;
37611
- const RATE_LIMIT_BASE_DELAY_MS = 1000;
37900
+ const RATE_LIMIT_BASE_DELAY_MS = envInt("RATE_LIMIT_BASE_DELAY_MS", 1000);
37612
37901
  const response = async function* () {
37613
37902
  let rateLimitRetries = 0;
37614
37903
  if (profileCredentialStore) {
@@ -37866,7 +38155,7 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
37866
38155
  if (hasExtendedContext(model)) {
37867
38156
  const from = model;
37868
38157
  model = stripExtendedContext(model);
37869
- recordExtendedContextRateLimited(profile.id, priorityCooldownUntil(profile.id, Date.now()));
38158
+ recordExtendedContextRateLimited(profile.id, priorityCooldownUntil(profile.id, Date.now()), benchSessionKey);
37870
38159
  claudeLog("upstream.context_fallback", {
37871
38160
  mode: "stream",
37872
38161
  from,
@@ -38798,6 +39087,11 @@ Subprocess stderr: ${stderrOutput}`;
38798
39087
  message: `Upstream stalled: no data for ${error51.sinceLastMs}ms`
38799
39088
  } : classifyError(errMsg, model);
38800
39089
  claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
39090
+ const streamRetryAfter = retryAfterSeconds({
39091
+ status: streamErr.status,
39092
+ errorMessage: errMsg,
39093
+ resetAtMs: observedResetAtMs(profile.id, Date.now())
39094
+ });
38801
39095
  const sdkTerm = extractSdkTermination(errMsg);
38802
39096
  const canRecoverAsToolUse = canRecoverCapturedToolUses({
38803
39097
  reason: sdkTerm.reason,
@@ -39010,7 +39304,7 @@ data: ${JSON.stringify({
39010
39304
  safeEnqueue(encoder.encode(`event: error
39011
39305
  data: ${JSON.stringify({
39012
39306
  type: "error",
39013
- error: { type: streamErr.type, message: streamErr.message }
39307
+ error: { type: streamErr.type, message: streamErr.message, ...retryAfterBodyFields(streamRetryAfter) }
39014
39308
  })}
39015
39309
 
39016
39310
  `), "error_event_before_stop");
@@ -39022,7 +39316,7 @@ data: {"type":"message_stop"}
39022
39316
  safeEnqueue(encoder.encode(`event: error
39023
39317
  data: ${JSON.stringify({
39024
39318
  type: "error",
39025
- error: { type: streamErr.type, message: streamErr.message }
39319
+ error: { type: streamErr.type, message: streamErr.message, ...retryAfterBodyFields(streamRetryAfter) }
39026
39320
  })}
39027
39321
 
39028
39322
  `), "error_event");
@@ -39046,6 +39340,7 @@ data: ${JSON.stringify({
39046
39340
  cancel(reason) {
39047
39341
  requestAbort.abort(reason);
39048
39342
  requestAbort.detach();
39343
+ requestMeta.cascadeSubtreeCancel?.("stream_cancel");
39049
39344
  if (!isIndependentSession && (!managedForkTarget || managedForkPublished || clientAssistantContentExposed)) {
39050
39345
  evictSession2(profileSessionId, profileScopedCwd, lineageMessages, mappingExpectedGeneration);
39051
39346
  claudeLog("passthrough.client_abort_settled", { action: "evict", source: "stream_cancel" });
@@ -39069,6 +39364,11 @@ data: ${JSON.stringify({
39069
39364
  error: errMsg
39070
39365
  });
39071
39366
  const classified = requestAbort.controller.signal.aborted ? { status: 499, type: "request_cancelled", message: "The request was cancelled" } : classifyError(errMsg);
39367
+ const retryAfter = retryAfterSeconds({
39368
+ status: classified.status,
39369
+ errorMessage: errMsg,
39370
+ resetAtMs: observedResetAtMs(resolvedProfileId, Date.now())
39371
+ });
39072
39372
  claudeLog("proxy.error", { error: errMsg, classified: classified.type });
39073
39373
  const sdkTerm = extractSdkTermination(errMsg);
39074
39374
  diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
@@ -39103,7 +39403,17 @@ data: ${JSON.stringify({
39103
39403
  textEvents: 0,
39104
39404
  error: classified.type
39105
39405
  });
39106
- return new Response(JSON.stringify({ type: "error", error: { type: classified.type, message: classified.message } }), { status: classified.status, headers: { "Content-Type": "application/json" } });
39406
+ return new Response(JSON.stringify({
39407
+ type: "error",
39408
+ error: {
39409
+ type: classified.type,
39410
+ message: classified.message,
39411
+ ...retryAfterBodyFields(retryAfter)
39412
+ }
39413
+ }), {
39414
+ status: classified.status,
39415
+ headers: { "Content-Type": "application/json", ...retryAfterHeaders(retryAfter) }
39416
+ });
39107
39417
  } finally {
39108
39418
  if (!streamOwnsAbortLink) {
39109
39419
  await abandonManagedFork("request_complete_without_commit");
@@ -39112,6 +39422,17 @@ data: ${JSON.stringify({
39112
39422
  }
39113
39423
  });
39114
39424
  };
39425
+ const logSubtreeCancel = (parentKey, cancelled, requestId, source) => {
39426
+ const children = cancelled.keys.map((key) => truncateSessionKey(key));
39427
+ claudeLog("session.tree_cancel_propagated", {
39428
+ requestId,
39429
+ source,
39430
+ parent: truncateSessionKey(parentKey),
39431
+ children,
39432
+ requests: cancelled.requestIds.length
39433
+ });
39434
+ diagnosticLog2.session(`${requestId} session_tree_cancel source=${source} parent=${truncateSessionKey(parentKey)} ` + `children=${children.join(",")} requests=${cancelled.requestIds.length}`, requestId);
39435
+ };
39115
39436
  const handleWithQueue = async (c, endpoint) => {
39116
39437
  if (draining && c.req.header("x-meridian-internal-hop") !== internalHopToken) {
39117
39438
  return drainingResponse();
@@ -39121,6 +39442,20 @@ data: ${JSON.stringify({
39121
39442
  claudeLog("request.enter", { requestId, endpoint });
39122
39443
  let sessionTurnLease;
39123
39444
  let crossProcessTurnLease;
39445
+ let sessionTreeRegistration;
39446
+ let detachSubtreeAbortWatch;
39447
+ let subtreeSessionKey;
39448
+ let subtreeCascaded = false;
39449
+ const cascadeSubtreeCancel = (source) => {
39450
+ const parentKey = subtreeSessionKey;
39451
+ if (subtreeCascaded || !parentKey)
39452
+ return;
39453
+ subtreeCascaded = true;
39454
+ const cancelled = processSessionTree.cancelDescendants(parentKey, new Error(`Parent session ${truncateSessionKey(parentKey)} was cancelled`));
39455
+ if (cancelled.requestIds.length === 0)
39456
+ return;
39457
+ logSubtreeCancel(parentKey, cancelled, requestId, source);
39458
+ };
39124
39459
  const turnWatchdogAbort = new AbortController;
39125
39460
  activeRequestAborts.add(turnWatchdogAbort);
39126
39461
  let finished = false;
@@ -39160,6 +39495,10 @@ data: ${JSON.stringify({
39160
39495
  } else {
39161
39496
  releaseSessionTurn(false);
39162
39497
  }
39498
+ detachSubtreeAbortWatch?.();
39499
+ detachSubtreeAbortWatch = undefined;
39500
+ sessionTreeRegistration?.release();
39501
+ sessionTreeRegistration = undefined;
39163
39502
  activeRequestAborts.delete(turnWatchdogAbort);
39164
39503
  inFlightRequests--;
39165
39504
  };
@@ -39188,6 +39527,21 @@ data: ${JSON.stringify({
39188
39527
  routingTurnIdentity = adapter.getRoutingTurnIdentity?.(c, body);
39189
39528
  const agentSessionId = adapter.getSessionId(c, body);
39190
39529
  if (agentSessionId) {
39530
+ sessionTreeRegistration = processSessionTree.register({
39531
+ requestId,
39532
+ sessionKey: agentSessionId,
39533
+ parentKey: adapter.getParentSessionId?.(c, body),
39534
+ abort: (reason) => turnWatchdogAbort.abort(reason)
39535
+ });
39536
+ subtreeSessionKey = agentSessionId;
39537
+ const clientSignal = c.req.raw.signal;
39538
+ if (clientSignal.aborted) {
39539
+ cascadeSubtreeCancel("client_abort");
39540
+ } else {
39541
+ const onClientAbort = () => cascadeSubtreeCancel("client_abort");
39542
+ clientSignal.addEventListener("abort", onClientAbort, { once: true });
39543
+ detachSubtreeAbortWatch = () => clientSignal.removeEventListener("abort", onClientAbort);
39544
+ }
39191
39545
  const arrivalProfileIds = new Set(getEffectiveProfiles(finalConfig.profiles).map((profile) => profile.id));
39192
39546
  const explicitlyRequestedProfile = c.req.header("x-meridian-profile")?.trim();
39193
39547
  if (explicitlyRequestedProfile)
@@ -39249,7 +39603,7 @@ data: ${JSON.stringify({
39249
39603
  type: "overloaded_error",
39250
39604
  message: "Timed out waiting for another process to finish this session turn"
39251
39605
  }
39252
- }), { status: 529, headers: { "Content-Type": "application/json" } });
39606
+ }), { status: 529, headers: { "Content-Type": "application/json", ...TRANSIENT_RETRY_AFTER_HEADERS } });
39253
39607
  }
39254
39608
  throw error51;
39255
39609
  }
@@ -39267,7 +39621,8 @@ data: ${JSON.stringify({
39267
39621
  routingTurnIdentity,
39268
39622
  retainSessionTurnFence: () => {
39269
39623
  retainSessionTurnFence = true;
39270
- }
39624
+ },
39625
+ cascadeSubtreeCancel
39271
39626
  };
39272
39627
  const response = await handleMessages(c, requestMeta, {
39273
39628
  body,
@@ -39287,7 +39642,29 @@ data: ${JSON.stringify({
39287
39642
  };
39288
39643
  app.post("/v1/messages", (c) => handleWithQueue(c, "/v1/messages"));
39289
39644
  app.post("/messages", (c) => handleWithQueue(c, "/messages"));
39290
- app.route("/telemetry", createTelemetryRoutes());
39645
+ app.post("/v1/sessions/:key/cancel", (c) => {
39646
+ const key = c.req.param("key");
39647
+ if (!key) {
39648
+ return c.json({ type: "error", error: { type: "invalid_request_error", message: "Session key is required" } }, 400);
39649
+ }
39650
+ const cancelled = processSessionTree.cancelSubtree(key, new Error("Session cancelled by request"));
39651
+ if (cancelled.requestIds.length > 0) {
39652
+ claudeLog("session.tree_cancel_requested", {
39653
+ session: truncateSessionKey(key),
39654
+ keys: cancelled.keys.map((cancelledKey) => truncateSessionKey(cancelledKey)),
39655
+ requests: cancelled.requestIds.length
39656
+ });
39657
+ diagnosticLog2.session(`session_tree_cancel_requested session=${truncateSessionKey(key)} requests=${cancelled.requestIds.length}`);
39658
+ }
39659
+ return c.json({
39660
+ session: key,
39661
+ cancelled: { sessions: cancelled.keys.length, requests: cancelled.requestIds.length },
39662
+ requestIds: cancelled.requestIds
39663
+ });
39664
+ });
39665
+ app.route("/telemetry", createTelemetryRoutes({
39666
+ getSessionTree: () => processSessionTree.stats()
39667
+ }));
39291
39668
  app.get("/settings", (c) => {
39292
39669
  const { settingsPageHtml: settingsPageHtml2 } = (init_settingsPage(), __toCommonJS(exports_settingsPage));
39293
39670
  return c.html(settingsPageHtml2);
@@ -39628,6 +40005,12 @@ data: ${JSON.stringify({
39628
40005
  `);
39629
40006
  buffer = lines.pop() ?? "";
39630
40007
  for (const line of lines) {
40008
+ if (line.startsWith(":")) {
40009
+ controller.enqueue(encoder.encode(`${line}
40010
+
40011
+ `));
40012
+ continue;
40013
+ }
39631
40014
  if (!line.startsWith("data: "))
39632
40015
  continue;
39633
40016
  const dataStr = line.slice(6).trim();
@@ -39746,6 +40129,12 @@ data: ${JSON.stringify({
39746
40129
  `);
39747
40130
  buffer = lines.pop() ?? "";
39748
40131
  for (const line of lines) {
40132
+ if (line.startsWith(":")) {
40133
+ controller.enqueue(encoder.encode(`${line}
40134
+
40135
+ `));
40136
+ continue;
40137
+ }
39749
40138
  if (!line.startsWith("data: "))
39750
40139
  continue;
39751
40140
  const dataStr = line.slice(6).trim();
@@ -39790,7 +40179,9 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
39790
40179
  });
39791
40180
  });
39792
40181
  app.get("/v1/models", async (c) => {
39793
- const authStatus = await getClaudeAuthStatusAsync();
40182
+ const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile);
40183
+ const profileEnvOverrides = Object.keys(profile.env).length > 0 ? profile.env : undefined;
40184
+ const authStatus = await getClaudeAuthStatusAsync(profile.id !== "default" ? profile.id : undefined, profileEnvOverrides);
39794
40185
  const extendedContext = subscriptionIncludesExtendedContext(authStatus?.subscriptionType);
39795
40186
  return c.json({ object: "list", data: buildModelList(extendedContext) });
39796
40187
  });