@rynfar/meridian 1.62.1 → 1.62.2

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.
@@ -6,11 +6,12 @@ import {
6
6
  getEffectiveProfiles,
7
7
  getRoutingMode,
8
8
  listProfiles,
9
+ resolveCooldownUntil,
9
10
  resolvePriorityOrder,
10
11
  resolveProfile,
11
12
  restoreActiveProfile,
12
13
  setActiveProfile
13
- } from "./cli-h6hfkg3s.js";
14
+ } from "./cli-m0p2bc8v.js";
14
15
  import {
15
16
  isTrackedPlugin,
16
17
  recordError,
@@ -47,8 +48,9 @@ import {
47
48
  resolveClaudeExecutableAsync,
48
49
  resolvePassthrough,
49
50
  resolveSdkModelDefaults,
50
- stripExtendedContext
51
- } from "./cli-p3ggjwgn.js";
51
+ stripExtendedContext,
52
+ subscriptionIncludesExtendedContext
53
+ } from "./cli-d45dq9gf.js";
52
54
  import {
53
55
  getSetting,
54
56
  setSetting
@@ -501,6 +503,8 @@ function computeSummary(metrics, windowMs, pricingOverrides) {
501
503
  envelopeViolationCount: 0,
502
504
  requestsPerMinute: 0,
503
505
  queueWait: emptyPhase,
506
+ sessionQueueWait: emptyPhase,
507
+ sdkQueueWait: emptyPhase,
504
508
  proxyOverhead: emptyPhase,
505
509
  ttfb: emptyPhase,
506
510
  upstreamDuration: emptyPhase,
@@ -525,6 +529,8 @@ function computeSummary(metrics, windowMs, pricingOverrides) {
525
529
  const spanMs = Math.max(newest - oldest, 1);
526
530
  const requestsPerMinute = metrics.length / spanMs * 60000;
527
531
  const queueWaits = metrics.map((m) => m.queueWaitMs);
532
+ const sessionQueueWaits = metrics.map((m) => m.sessionQueueWaitMs ?? 0);
533
+ const sdkQueueWaits = metrics.map((m) => m.sdkQueueWaitMs ?? 0);
528
534
  const overheads = metrics.map((m) => m.proxyOverheadMs);
529
535
  const ttfbs = metrics.filter((m) => m.ttfbMs !== null).map((m) => m.ttfbMs);
530
536
  const upstreams = metrics.map((m) => m.upstreamDurationMs);
@@ -569,6 +575,8 @@ function computeSummary(metrics, windowMs, pricingOverrides) {
569
575
  envelopeViolationCount,
570
576
  requestsPerMinute: Math.round(requestsPerMinute * 100) / 100,
571
577
  queueWait: computePercentiles(queueWaits),
578
+ sessionQueueWait: computePercentiles(sessionQueueWaits),
579
+ sdkQueueWait: computePercentiles(sdkQueueWaits),
572
580
  proxyOverhead: computePercentiles(overheads),
573
581
  ttfb: ttfbs.length > 0 ? computePercentiles(ttfbs) : { p50: 0, p95: 0, p99: 0, min: 0, max: 0, avg: 0 },
574
582
  upstreamDuration: computePercentiles(upstreams),
@@ -1445,7 +1453,7 @@ class SqliteTelemetryStore {
1445
1453
  is_resume, is_passthrough, lineage_type,
1446
1454
  has_deferred_tools, deferred_tool_count, tool_count, discovered_tools, session_discovered_count,
1447
1455
  message_count, sdk_session_id,
1448
- status, queue_wait_ms, proxy_overhead_ms, ttfb_ms,
1456
+ status, queue_wait_ms, session_queue_wait_ms, sdk_queue_wait_ms, proxy_overhead_ms, ttfb_ms,
1449
1457
  upstream_duration_ms, total_duration_ms, content_blocks, text_events, error,
1450
1458
  input_tokens, output_tokens, cache_read_input_tokens,
1451
1459
  cache_creation_input_tokens, cache_hit_rate, profile_id, envelope_violations
@@ -1454,7 +1462,7 @@ class SqliteTelemetryStore {
1454
1462
  @isResume, @isPassthrough, @lineageType,
1455
1463
  @hasDeferredTools, @deferredToolCount, @toolCount, @discoveredTools, @sessionDiscoveredCount,
1456
1464
  @messageCount, @sdkSessionId,
1457
- @status, @queueWaitMs, @proxyOverheadMs, @ttfbMs,
1465
+ @status, @queueWaitMs, @sessionQueueWaitMs, @sdkQueueWaitMs, @proxyOverheadMs, @ttfbMs,
1458
1466
  @upstreamDurationMs, @totalDurationMs, @contentBlocks, @textEvents, @error,
1459
1467
  @inputTokens, @outputTokens, @cacheReadInputTokens,
1460
1468
  @cacheCreationInputTokens, @cacheHitRate, @profileId, @envelopeViolations
@@ -1484,6 +1492,8 @@ class SqliteTelemetryStore {
1484
1492
  sdkSessionId: metric.sdkSessionId ?? null,
1485
1493
  status: metric.status,
1486
1494
  queueWaitMs: metric.queueWaitMs,
1495
+ sessionQueueWaitMs: metric.sessionQueueWaitMs ?? 0,
1496
+ sdkQueueWaitMs: metric.sdkQueueWaitMs ?? 0,
1487
1497
  proxyOverheadMs: metric.proxyOverheadMs,
1488
1498
  ttfbMs: metric.ttfbMs ?? null,
1489
1499
  upstreamDurationMs: metric.upstreamDurationMs,
@@ -1661,6 +1671,8 @@ function rowToMetric(r) {
1661
1671
  sdkSessionId: r.sdk_session_id ?? undefined,
1662
1672
  status: r.status,
1663
1673
  queueWaitMs: r.queue_wait_ms,
1674
+ sessionQueueWaitMs: r.session_queue_wait_ms ?? 0,
1675
+ sdkQueueWaitMs: r.sdk_queue_wait_ms ?? 0,
1664
1676
  proxyOverheadMs: r.proxy_overhead_ms,
1665
1677
  ttfbMs: r.ttfb_ms ?? null,
1666
1678
  upstreamDurationMs: r.upstream_duration_ms,
@@ -1711,6 +1723,8 @@ CREATE TABLE IF NOT EXISTS metrics (
1711
1723
  sdk_session_id TEXT,
1712
1724
  status INTEGER NOT NULL,
1713
1725
  queue_wait_ms REAL NOT NULL,
1726
+ session_queue_wait_ms REAL NOT NULL DEFAULT 0,
1727
+ sdk_queue_wait_ms REAL NOT NULL DEFAULT 0,
1714
1728
  proxy_overhead_ms REAL NOT NULL,
1715
1729
  ttfb_ms REAL,
1716
1730
  upstream_duration_ms REAL NOT NULL,
@@ -1748,7 +1762,9 @@ var init_sqlite = __esm(() => {
1748
1762
  METRICS_MIGRATIONS = [
1749
1763
  "ALTER TABLE metrics ADD COLUMN request_source TEXT",
1750
1764
  "ALTER TABLE metrics ADD COLUMN profile_id TEXT",
1751
- "ALTER TABLE metrics ADD COLUMN envelope_violations TEXT"
1765
+ "ALTER TABLE metrics ADD COLUMN envelope_violations TEXT",
1766
+ "ALTER TABLE metrics ADD COLUMN session_queue_wait_ms REAL NOT NULL DEFAULT 0",
1767
+ "ALTER TABLE metrics ADD COLUMN sdk_queue_wait_ms REAL NOT NULL DEFAULT 0"
1752
1768
  ];
1753
1769
  });
1754
1770
 
@@ -6337,7 +6353,6 @@ var serve = (options, listeningListener) => {
6337
6353
  };
6338
6354
 
6339
6355
  // src/proxy/server.ts
6340
- import { AsyncLocalStorage } from "node:async_hooks";
6341
6356
  import { homedir as homedir7 } from "node:os";
6342
6357
  import { join as join8 } from "node:path";
6343
6358
  import { query } from "@anthropic-ai/claude-agent-sdk";
@@ -6477,6 +6492,165 @@ function linkRequestAbort(signal) {
6477
6492
  };
6478
6493
  }
6479
6494
 
6495
+ // src/proxy/concurrency.ts
6496
+ var DEFAULT_MAX_CONCURRENT = 10;
6497
+ var didWarnInvalidMaxConcurrent = false;
6498
+ var processSdkSemaphore;
6499
+ function requestCancelledError(reason) {
6500
+ if (reason instanceof Error)
6501
+ return reason;
6502
+ return new DOMException(typeof reason === "string" && reason ? reason : "The request was cancelled", "AbortError");
6503
+ }
6504
+ function resolveMaxConcurrent(source = process.env, warn = console.warn) {
6505
+ const raw2 = source.MERIDIAN_MAX_CONCURRENT ?? source.CLAUDE_PROXY_MAX_CONCURRENT;
6506
+ if (raw2 === undefined)
6507
+ return DEFAULT_MAX_CONCURRENT;
6508
+ if (raw2 && /^\d+$/.test(raw2)) {
6509
+ const parsed = Number(raw2);
6510
+ if (Number.isSafeInteger(parsed) && parsed > 0)
6511
+ return parsed;
6512
+ }
6513
+ if (!didWarnInvalidMaxConcurrent) {
6514
+ didWarnInvalidMaxConcurrent = true;
6515
+ warn(`[PROXY] Invalid MERIDIAN_MAX_CONCURRENT value "${raw2}"; using default ${DEFAULT_MAX_CONCURRENT}`);
6516
+ }
6517
+ return DEFAULT_MAX_CONCURRENT;
6518
+ }
6519
+
6520
+ class AbortableSemaphore {
6521
+ limit;
6522
+ activeCount = 0;
6523
+ waiters = [];
6524
+ constructor(limit) {
6525
+ this.limit = limit;
6526
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
6527
+ throw new RangeError("Semaphore limit must be a positive integer");
6528
+ }
6529
+ }
6530
+ get snapshot() {
6531
+ return { active: this.activeCount, queued: this.waiters.length, limit: this.limit };
6532
+ }
6533
+ acquire(signal) {
6534
+ if (signal?.aborted)
6535
+ return Promise.reject(requestCancelledError(signal.reason));
6536
+ const enqueuedAt = Date.now();
6537
+ if (this.activeCount < this.limit && this.waiters.length === 0) {
6538
+ this.activeCount++;
6539
+ return Promise.resolve(this.createLease(enqueuedAt));
6540
+ }
6541
+ return new Promise((resolve, reject) => {
6542
+ const waiter = { enqueuedAt, resolve, reject, signal };
6543
+ if (signal) {
6544
+ waiter.abortListener = () => {
6545
+ const index = this.waiters.indexOf(waiter);
6546
+ if (index === -1)
6547
+ return;
6548
+ this.waiters.splice(index, 1);
6549
+ reject(requestCancelledError(signal.reason));
6550
+ };
6551
+ signal.addEventListener("abort", waiter.abortListener, { once: true });
6552
+ }
6553
+ this.waiters.push(waiter);
6554
+ });
6555
+ }
6556
+ createLease(enqueuedAt) {
6557
+ let released = false;
6558
+ return {
6559
+ waitedMs: Date.now() - enqueuedAt,
6560
+ release: () => {
6561
+ if (released)
6562
+ return;
6563
+ released = true;
6564
+ this.activeCount--;
6565
+ this.grantNext();
6566
+ }
6567
+ };
6568
+ }
6569
+ grantNext() {
6570
+ while (this.activeCount < this.limit) {
6571
+ const waiter = this.waiters.shift();
6572
+ if (!waiter)
6573
+ return;
6574
+ if (waiter.abortListener && waiter.signal) {
6575
+ waiter.signal.removeEventListener("abort", waiter.abortListener);
6576
+ }
6577
+ if (waiter.signal?.aborted) {
6578
+ waiter.reject(requestCancelledError(waiter.signal.reason));
6579
+ continue;
6580
+ }
6581
+ this.activeCount++;
6582
+ waiter.resolve(this.createLease(waiter.enqueuedAt));
6583
+ }
6584
+ }
6585
+ }
6586
+ function getProcessSdkSemaphore() {
6587
+ processSdkSemaphore ??= new AbortableSemaphore(resolveMaxConcurrent());
6588
+ return processSdkSemaphore;
6589
+ }
6590
+
6591
+ // src/proxy/shutdown.ts
6592
+ function trackServerConnections(server) {
6593
+ const sockets = new Set;
6594
+ const onConnection = (socket) => {
6595
+ sockets.add(socket);
6596
+ socket.once("close", () => sockets.delete(socket));
6597
+ };
6598
+ server.on("connection", onConnection);
6599
+ return {
6600
+ forceCloseAll() {
6601
+ server.closeAllConnections?.();
6602
+ for (const socket of sockets)
6603
+ socket.destroy();
6604
+ },
6605
+ dispose() {
6606
+ server.off("connection", onConnection);
6607
+ sockets.clear();
6608
+ }
6609
+ };
6610
+ }
6611
+ async function closeServerWithGracePeriod(server, options) {
6612
+ const graceMs = Math.max(0, options.graceMs);
6613
+ const deadlineAt = Date.now() + graceMs;
6614
+ while (options.getInFlightCount() > 0) {
6615
+ const remainingMs = deadlineAt - Date.now();
6616
+ if (remainingMs <= 0)
6617
+ break;
6618
+ await new Promise((resolve) => {
6619
+ const timer = setTimeout(resolve, Math.min(50, remainingMs));
6620
+ timer.unref?.();
6621
+ });
6622
+ }
6623
+ const closePromise = new Promise((resolve, reject) => {
6624
+ server.close((error) => error ? reject(error) : resolve());
6625
+ });
6626
+ const remainingGraceMs = Math.max(0, deadlineAt - Date.now());
6627
+ if (remainingGraceMs > 0) {
6628
+ let timeout;
6629
+ const deadline = new Promise((resolve) => {
6630
+ timeout = setTimeout(() => resolve("timeout"), remainingGraceMs);
6631
+ timeout.unref?.();
6632
+ });
6633
+ try {
6634
+ const outcome = await Promise.race([
6635
+ closePromise.then(() => "closed"),
6636
+ deadline
6637
+ ]);
6638
+ if (outcome === "closed")
6639
+ return;
6640
+ } finally {
6641
+ if (timeout)
6642
+ clearTimeout(timeout);
6643
+ }
6644
+ }
6645
+ const remaining = options.getInFlightCount();
6646
+ options.warn?.(`[PROXY] Grace period elapsed with ${remaining} request(s) still in flight after ${graceMs}ms; forcing remaining HTTP connections closed.`);
6647
+ if (options.forceCloseConnections)
6648
+ options.forceCloseConnections();
6649
+ else
6650
+ server.closeAllConnections?.();
6651
+ await closePromise;
6652
+ }
6653
+
6480
6654
  // src/proxy/oauthUsage.ts
6481
6655
  var OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
6482
6656
  var OAUTH_BETA_HEADER = "oauth-2025-04-20";
@@ -11300,7 +11474,6 @@ var dashboardHtml = `<!DOCTYPE html>
11300
11474
  .waterfall-seg { height: 100%; border-radius: 2px; min-width: 2px; }
11301
11475
  .waterfall-seg.queue { background: var(--queue); }
11302
11476
  .waterfall-seg.overhead { background: var(--yellow); }
11303
- .waterfall-seg.ttfb { background: var(--ttfb); }
11304
11477
  .waterfall-seg.response { background: var(--upstream); }
11305
11478
  .legend { display: flex; gap: 16px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }
11306
11479
  .legend-dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; vertical-align: middle; }
@@ -11556,6 +11729,8 @@ function render(s, reqs, logs) {
11556
11729
  html += '<div class="section"><div class="section-title">Percentiles</div>'
11557
11730
  + '<table class="pct-table"><thead><tr><th>Phase</th><th>p50</th><th>p95</th><th>p99</th><th>Min</th><th>Max</th><th>Avg</th></tr></thead><tbody>'
11558
11731
  + pctRow('Queue Wait', 'var(--queue)', s.queueWait)
11732
+ + pctRow('Session Queue', 'var(--queue)', s.sessionQueueWait)
11733
+ + pctRow('SDK Queue', 'var(--queue)', s.sdkQueueWait)
11559
11734
  + pctRow('Proxy Overhead', 'var(--yellow)', s.proxyOverhead)
11560
11735
  + pctRow('TTFB', 'var(--ttfb)', s.ttfb)
11561
11736
  + pctRow('Upstream', 'var(--upstream)', s.upstreamDuration)
@@ -11570,7 +11745,6 @@ function render(s, reqs, logs) {
11570
11745
  html += '<div class="legend">'
11571
11746
  + '<span><span class="legend-dot" style="background:var(--queue)"></span>Queue</span>'
11572
11747
  + '<span><span class="legend-dot" style="background:var(--yellow)"></span>Proxy</span>'
11573
- + '<span><span class="legend-dot" style="background:var(--ttfb)"></span>TTFB</span>'
11574
11748
  + '<span><span class="legend-dot" style="background:var(--upstream)"></span>Response</span>'
11575
11749
  + '</div>'
11576
11750
  + '<table><thead><tr><th>Time</th><th>Adapter</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'
@@ -11582,10 +11756,11 @@ function render(s, reqs, logs) {
11582
11756
  const statusClass = r.error ? 'status-err' : 'status-ok';
11583
11757
  const statusText = r.error ? r.error : r.status;
11584
11758
  const scale = 280 / maxTotal;
11759
+ const sessionQW = r.sessionQueueWaitMs || 0;
11760
+ const sdkQW = r.sdkQueueWaitMs || 0;
11585
11761
  const qW = Math.max(r.queueWaitMs * scale, 2);
11586
11762
  const ohW = Math.max((r.proxyOverheadMs || 0) * scale, 0);
11587
- const ttfbW = Math.max((r.ttfbMs || 0) * scale, 0);
11588
- const respW = Math.max((r.upstreamDurationMs - (r.ttfbMs || 0)) * scale, 2);
11763
+ const respW = Math.max(r.upstreamDurationMs * scale, 2);
11589
11764
 
11590
11765
  const lineageBadge = r.lineageType ? '<span style="font-size:10px;padding:1px 5px;border-radius:3px;background:' + ({continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'}[r.lineageType] || 'var(--muted)') + ';color:var(--bg)">' + r.lineageType + '</span>' : '';
11591
11766
  const envBadge = (r.envelopeViolations && r.envelopeViolations.length > 0) ? ' <span style="font-size:10px;padding:1px 5px;border-radius:3px;background:var(--red);color:var(--bg)" title="' + r.envelopeViolations.join(', ') + '">envelope×' + r.envelopeViolations.length + '</span>' : '';
@@ -11601,16 +11776,15 @@ function render(s, reqs, logs) {
11601
11776
  + '<td>' + r.mode + (r.hasDeferredTools ? (function() { var sessDisc = r.sessionDiscoveredCount || 0; var loaded = ((r.toolCount || 0) - (r.deferredToolCount || 0)) + sessDisc; var deferred = Math.max(0, (r.deferredToolCount || 0) - sessDisc); var newDisc = r.discoveredTools || []; return '<br><span style="font-size:10px;color:var(--purple)">loaded=' + loaded + ' deferred=' + deferred + '</span>' + (newDisc.length > 0 ? '<br><span style="font-size:10px;color:var(--green)">+' + newDisc.join(', +') + '</span>' : ''); })() : '') + '</td>'
11602
11777
  + '<td class="mono">' + sessionShort + ' ' + lineageBadge + envBadge + '<br><span style="font-size:10px;color:var(--muted)">' + msgCount + ' msgs</span></td>'
11603
11778
  + '<td class="' + statusClass + '">' + statusText + '</td>'
11604
- + '<td class="mono">' + ms(r.queueWaitMs) + '</td>'
11779
+ + '<td class="mono">' + ms(r.queueWaitMs) + '<br><span style="font-size:9px;color:var(--muted)">session ' + ms(sessionQW) + ' / sdk ' + ms(sdkQW) + '</span></td>'
11605
11780
  + '<td class="mono">' + ms(r.proxyOverheadMs) + '</td>'
11606
11781
  + '<td class="mono">' + ms(r.ttfbMs) + '</td>'
11607
11782
  + '<td class="mono">' + ms(r.totalDurationMs) + '</td>'
11608
11783
  + '<td class="mono">' + (r.inputTokens != null ? (r.inputTokens > 1000 ? Math.round(r.inputTokens/1000) + 'k' : r.inputTokens) + ' in<br>' + (r.outputTokens > 1000 ? Math.round(r.outputTokens/1000) + 'k' : r.outputTokens || 0) + ' out' : '—') + '</td>'
11609
11784
  + '<td class="mono">' + (r.cacheHitRate != null ? '<span style="color:' + (r.cacheHitRate > 0.5 ? 'var(--green)' : r.cacheHitRate > 0 ? 'var(--yellow)' : 'var(--red)') + '">' + Math.round(r.cacheHitRate * 100) + '%</span>' : '—') + '</td>'
11610
- + '<td><div class="waterfall">'
11785
+ + '<td><div class="waterfall" title="Queue, then proxy overhead, then upstream response. A request that replayed upstream sums every attempt into the response segment, while TTFB counts only the attempt that produced the first chunk.">'
11611
11786
  + '<div class="waterfall-seg queue" style="width:' + qW + 'px"></div>'
11612
11787
  + '<div class="waterfall-seg overhead" style="width:' + ohW + 'px"></div>'
11613
- + '<div class="waterfall-seg ttfb" style="width:' + ttfbW + 'px"></div>'
11614
11788
  + '<div class="waterfall-seg response" style="width:' + respW + 'px"></div>'
11615
11789
  + '</div></td>'
11616
11790
  + '</tr>';
@@ -12001,6 +12175,8 @@ init_percentiles();
12001
12175
  var DURATION_BUCKETS = [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 1e4, 30000];
12002
12176
  var PHASES = [
12003
12177
  { key: "queue_wait", extract: (m) => m.queueWaitMs },
12178
+ { key: "session_queue_wait", extract: (m) => m.sessionQueueWaitMs ?? 0 },
12179
+ { key: "sdk_queue_wait", extract: (m) => m.sdkQueueWaitMs ?? 0 },
12004
12180
  { key: "proxy_overhead", extract: (m) => m.proxyOverheadMs },
12005
12181
  { key: "ttfb", extract: (m) => m.ttfbMs },
12006
12182
  { key: "upstream", extract: (m) => m.upstreamDurationMs },
@@ -12958,7 +13134,7 @@ var FULL_CAPABILITIES = Object.freeze({
12958
13134
  structured_outputs: yes,
12959
13135
  thinking: { supported: true, types: { adaptive: yes, enabled: yes } }
12960
13136
  });
12961
- function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000)) {
13137
+ function buildModelList(extendedContextIncluded, now = Math.floor(Date.now() / 1000)) {
12962
13138
  return [
12963
13139
  {
12964
13140
  id: "claude-sonnet-5",
@@ -12984,7 +13160,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
12984
13160
  created: now,
12985
13161
  owned_by: "anthropic",
12986
13162
  display_name: "Claude Opus 5",
12987
- context_window: isMaxSubscription ? 1e6 : 200000,
13163
+ context_window: extendedContextIncluded ? 1e6 : 200000,
12988
13164
  capabilities: FULL_CAPABILITIES
12989
13165
  },
12990
13166
  {
@@ -12993,7 +13169,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
12993
13169
  created: now,
12994
13170
  owned_by: "anthropic",
12995
13171
  display_name: "Claude Opus 4.6",
12996
- context_window: isMaxSubscription ? 1e6 : 200000,
13172
+ context_window: extendedContextIncluded ? 1e6 : 200000,
12997
13173
  capabilities: FULL_CAPABILITIES
12998
13174
  },
12999
13175
  {
@@ -13002,7 +13178,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
13002
13178
  created: now,
13003
13179
  owned_by: "anthropic",
13004
13180
  display_name: "Claude Opus 4.7",
13005
- context_window: isMaxSubscription ? 1e6 : 200000,
13181
+ context_window: extendedContextIncluded ? 1e6 : 200000,
13006
13182
  capabilities: FULL_CAPABILITIES
13007
13183
  },
13008
13184
  {
@@ -13011,7 +13187,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
13011
13187
  created: now,
13012
13188
  owned_by: "anthropic",
13013
13189
  display_name: "Claude Opus 4.8",
13014
- context_window: isMaxSubscription ? 1e6 : 200000,
13190
+ context_window: extendedContextIncluded ? 1e6 : 200000,
13015
13191
  capabilities: FULL_CAPABILITIES
13016
13192
  },
13017
13193
  {
@@ -13020,7 +13196,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
13020
13196
  created: now,
13021
13197
  owned_by: "anthropic",
13022
13198
  display_name: "Claude Fable 5",
13023
- context_window: isMaxSubscription ? 1e6 : 200000,
13199
+ context_window: extendedContextIncluded ? 1e6 : 200000,
13024
13200
  capabilities: FULL_CAPABILITIES
13025
13201
  },
13026
13202
  {
@@ -20484,10 +20660,117 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
20484
20660
  }
20485
20661
  }
20486
20662
 
20663
+ // src/proxy/session/turnCoordinator.ts
20664
+ function cancellationError(reason) {
20665
+ if (reason instanceof Error)
20666
+ return reason;
20667
+ return new DOMException(typeof reason === "string" && reason ? reason : "The request was cancelled", "AbortError");
20668
+ }
20669
+
20670
+ class SessionTurnCoordinator {
20671
+ turns = new Map;
20672
+ get size() {
20673
+ return this.turns.size;
20674
+ }
20675
+ acquire(key, signal) {
20676
+ if (signal?.aborted)
20677
+ return Promise.reject(cancellationError(signal.reason));
20678
+ let state = this.turns.get(key);
20679
+ if (!state) {
20680
+ state = { held: false, versions: new Map, waiters: [] };
20681
+ this.turns.set(key, state);
20682
+ }
20683
+ const arrivedAt = Date.now();
20684
+ const arrivedVersions = new Map(state.versions);
20685
+ if (!state.held && state.waiters.length === 0) {
20686
+ state.held = true;
20687
+ return Promise.resolve(this.createLease(key, state, arrivedAt, arrivedVersions));
20688
+ }
20689
+ return new Promise((resolve3, reject) => {
20690
+ const waiter = {
20691
+ arrivedAt,
20692
+ versions: arrivedVersions,
20693
+ resolve: resolve3,
20694
+ reject,
20695
+ signal
20696
+ };
20697
+ if (signal) {
20698
+ waiter.abortListener = () => {
20699
+ const index = state.waiters.indexOf(waiter);
20700
+ if (index === -1)
20701
+ return;
20702
+ state.waiters.splice(index, 1);
20703
+ reject(cancellationError(signal.reason));
20704
+ this.cleanup(key, state);
20705
+ };
20706
+ signal.addEventListener("abort", waiter.abortListener, { once: true });
20707
+ }
20708
+ state.waiters.push(waiter);
20709
+ });
20710
+ }
20711
+ createLease(key, state, arrivedAt, arrivedVersions) {
20712
+ let released = false;
20713
+ const committedScopes = new Set;
20714
+ return {
20715
+ waitedMs: Date.now() - arrivedAt,
20716
+ advancedWhileWaiting: (scopeKey) => (state.versions.get(scopeKey) ?? 0) > (arrivedVersions.get(scopeKey) ?? 0),
20717
+ markCommitted: (scopeKey) => {
20718
+ if (released || committedScopes.has(scopeKey))
20719
+ return;
20720
+ committedScopes.add(scopeKey);
20721
+ state.versions.set(scopeKey, (state.versions.get(scopeKey) ?? 0) + 1);
20722
+ },
20723
+ release: () => {
20724
+ if (released)
20725
+ return;
20726
+ released = true;
20727
+ state.held = false;
20728
+ while (state.waiters.length > 0) {
20729
+ const waiter = state.waiters.shift();
20730
+ if (waiter.abortListener && waiter.signal) {
20731
+ waiter.signal.removeEventListener("abort", waiter.abortListener);
20732
+ }
20733
+ if (waiter.signal?.aborted) {
20734
+ waiter.reject(cancellationError(waiter.signal.reason));
20735
+ continue;
20736
+ }
20737
+ state.held = true;
20738
+ waiter.resolve(this.createLease(key, state, waiter.arrivedAt, waiter.versions));
20739
+ return;
20740
+ }
20741
+ this.cleanup(key, state);
20742
+ }
20743
+ };
20744
+ }
20745
+ cleanup(key, state) {
20746
+ if (!state.held && state.waiters.length === 0 && this.turns.get(key) === state) {
20747
+ this.turns.delete(key);
20748
+ }
20749
+ }
20750
+ }
20751
+ var processSessionTurns = new SessionTurnCoordinator;
20752
+
20487
20753
  // src/proxy/server.ts
20488
20754
  var exec2 = promisify3(execCallback);
20489
20755
  var claudeExecutable = "";
20490
20756
  var UPSTREAM_IDLE_MS = envInt("UPSTREAM_IDLE_MS", 90000);
20757
+ var SHUTDOWN_GRACE_MS = envInt("SHUTDOWN_GRACE_MS", 30000);
20758
+ function totalQueueWaitMs(meta) {
20759
+ return meta.sessionQueueWaitMs + meta.sdkQueueWaitMs;
20760
+ }
20761
+ function forkAttemptMeta(meta, attempt) {
20762
+ if (attempt === 0)
20763
+ return meta;
20764
+ return {
20765
+ ...meta,
20766
+ queueEnteredAt: Date.now(),
20767
+ sessionQueueWaitMs: 0,
20768
+ sdkQueueWaitMs: 0,
20769
+ sdkActiveDurationMs: 0,
20770
+ currentSdkStartedAt: undefined,
20771
+ ttfbMs: undefined
20772
+ };
20773
+ }
20491
20774
  function credentialStoreForProfile(profile) {
20492
20775
  if (profile.type !== "claude-max")
20493
20776
  return;
@@ -20693,28 +20976,62 @@ function createProxyServer(config = {}) {
20693
20976
  const sessionDiscoveredTools = new Map;
20694
20977
  const sessionToolCache = new Map;
20695
20978
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
20696
- const PENDING_STORE_WAIT_MS = 3000;
20697
- const PENDING_STORE_AUTO_RESOLVE_MS = 1e4;
20698
20979
  const RESUME_REFUSAL_MAX_RETRIES = 3;
20699
20980
  const RESUME_REFUSAL_RETRY_DELAY_MS = parseInt(process.env.MERIDIAN_BUSY_RETRY_DELAY_MS ?? "500", 10);
20700
- const pendingSessionStores = new Map;
20701
- const registerPendingStore = (key) => {
20702
- let resolveFn = () => {};
20703
- const promise = new Promise((resolve3) => {
20704
- const timer = setTimeout(resolve3, PENDING_STORE_AUTO_RESOLVE_MS);
20705
- resolveFn = () => {
20706
- clearTimeout(timer);
20707
- resolve3();
20708
- };
20709
- });
20710
- const entry = { promise, resolve: resolveFn };
20711
- pendingSessionStores.set(key, entry);
20712
- return () => {
20713
- entry.resolve();
20714
- if (pendingSessionStores.get(key) === entry)
20715
- pendingSessionStores.delete(key);
20716
- };
20717
- };
20981
+ const SESSION_TURN_MAX_HOLD_MS = envInt("SESSION_TURN_MAX_HOLD_MS", 600000);
20982
+ const sdkSemaphore = finalConfig.maxConcurrent !== undefined ? new AbortableSemaphore(finalConfig.maxConcurrent) : getProcessSdkSemaphore();
20983
+ const responseCompletions = new WeakMap;
20984
+ let draining = false;
20985
+ let inFlightRequests = 0;
20986
+ const internalHopToken = randomUUID();
20987
+ const errorEnvelope = (shape, type, message) => shape === "anthropic" ? { type: "error", error: { type, message } } : { error: { type, message, code: null } };
20988
+ const DRAIN_MESSAGE = "Meridian is shutting down and is not accepting new requests. Retry against another instance.";
20989
+ const drainingResponse = (shape = "anthropic") => new Response(JSON.stringify(errorEnvelope(shape, "overloaded_error", DRAIN_MESSAGE)), {
20990
+ status: 503,
20991
+ headers: { "Content-Type": "application/json", "x-meridian-draining": "1" }
20992
+ });
20993
+ async function relayInnerError(internalRes, shape) {
20994
+ const errBody = await internalRes.text();
20995
+ let innerType;
20996
+ let innerMessage;
20997
+ try {
20998
+ const parsed = JSON.parse(errBody);
20999
+ innerType = parsed?.error?.type;
21000
+ innerMessage = parsed?.error?.message;
21001
+ } catch {}
21002
+ const payload = errorEnvelope(shape, innerType ?? "upstream_error", innerMessage ?? errBody);
21003
+ const headers = { "Content-Type": "application/json" };
21004
+ const drainingHeader = internalRes.headers.get("x-meridian-draining");
21005
+ if (drainingHeader)
21006
+ headers["x-meridian-draining"] = drainingHeader;
21007
+ return new Response(JSON.stringify(payload), { status: internalRes.status, headers });
21008
+ }
21009
+ async function* runSdkQueryAttempt(params, signal, requestMeta, mode) {
21010
+ const acquireStartedAt = Date.now();
21011
+ let lease;
21012
+ try {
21013
+ lease = await sdkSemaphore.acquire(signal);
21014
+ } catch (error) {
21015
+ requestMeta.sdkQueueWaitMs += Date.now() - acquireStartedAt;
21016
+ throw error;
21017
+ }
21018
+ requestMeta.sdkQueueWaitMs += lease.waitedMs;
21019
+ const startedAt = Date.now();
21020
+ requestMeta.currentSdkStartedAt = startedAt;
21021
+ let sdkQuery;
21022
+ try {
21023
+ sdkQuery = query(params);
21024
+ yield* guardUpstreamIdle(sdkQuery, UPSTREAM_IDLE_MS, (sinceLastMs) => claudeLog("upstream.stalled", { mode, sinceLastMs }));
21025
+ } finally {
21026
+ try {
21027
+ if (typeof sdkQuery?.close === "function")
21028
+ sdkQuery.close();
21029
+ } finally {
21030
+ requestMeta.sdkActiveDurationMs += Date.now() - startedAt;
21031
+ lease.release();
21032
+ }
21033
+ }
21034
+ }
20718
21035
  const pluginDir = finalConfig.pluginDir ?? join8(homedir7(), ".config", "meridian", "plugins");
20719
21036
  const pluginConfigPath = finalConfig.pluginConfigPath ?? join8(homedir7(), ".config", "meridian", "plugins.json");
20720
21037
  let loadedPlugins = [];
@@ -20737,7 +21054,6 @@ function createProxyServer(config = {}) {
20737
21054
  const PRIORITY_ASSIGNMENTS_MAX = 5000;
20738
21055
  const priorityAssignments = new AssignmentStore(PRIORITY_ASSIGNMENTS_MAX);
20739
21056
  const PRIORITY_DEFAULT_COOLDOWN_MS = 10 * 60000;
20740
- const PRIORITY_COOLDOWN_CAP_MS = 6 * 60 * 60000;
20741
21057
  function priorityProfileOrderSetting() {
20742
21058
  const env2 = process.env.MERIDIAN_PROFILE_ORDER;
20743
21059
  if (env2 && env2.trim())
@@ -20746,9 +21062,12 @@ function createProxyServer(config = {}) {
20746
21062
  return Array.isArray(setting) && setting.length > 0 ? setting : undefined;
20747
21063
  }
20748
21064
  function priorityCooldownUntil(profileId, now) {
20749
- const fiveHour = rateLimitStore.getAll(profileId).find((e) => e.rateLimitType === "five_hour" && (e.resetsAt ?? 0) > now && (e.status === "rejected" || (e.utilization ?? 0) >= 1));
20750
- const until = fiveHour?.resetsAt ?? now + PRIORITY_DEFAULT_COOLDOWN_MS;
20751
- return Math.min(until, now + PRIORITY_COOLDOWN_CAP_MS);
21065
+ const windows = rateLimitStore.getAll(profileId).map((e) => ({
21066
+ type: e.rateLimitType ?? "",
21067
+ resetsAt: e.resetsAt,
21068
+ exhausted: e.status === "rejected" || (e.utilization ?? 0) >= 1
21069
+ }));
21070
+ return resolveCooldownUntil(windows, now, PRIORITY_DEFAULT_COOLDOWN_MS);
20752
21071
  }
20753
21072
  function refinePriorityCooldown(profileId) {
20754
21073
  const target = getEffectiveProfiles(finalConfig.profiles).find((p) => p.id === profileId);
@@ -20757,14 +21076,15 @@ function createProxyServer(config = {}) {
20757
21076
  fetchOAuthUsage({ profileId, claudeConfigDir: target?.claudeConfigDir, force: true }).then((usage) => {
20758
21077
  if (!usage || usage.stale)
20759
21078
  return;
20760
- const fiveHour = usage.windows.find((w) => w.type === "five_hour");
20761
- if (!fiveHour || (fiveHour.utilization ?? 0) < 1)
20762
- return;
20763
21079
  const now = Date.now();
20764
- const resetsAt = fiveHour.resetsAt;
20765
- if (!resetsAt || resetsAt <= now)
21080
+ const windows = usage.windows.map((w) => ({
21081
+ type: w.type,
21082
+ resetsAt: w.resetsAt,
21083
+ exhausted: (w.utilization ?? 0) >= 1
21084
+ }));
21085
+ const until = resolveCooldownUntil(windows, now, 0);
21086
+ if (until <= now)
20766
21087
  return;
20767
- const until = Math.min(resetsAt, now + PRIORITY_COOLDOWN_CAP_MS);
20768
21088
  priorityExhaustion.mark(profileId, until, "rate_limit_error");
20769
21089
  claudeLog("priority.cooldown_refined", { profile: profileId, until, source: "oauth_usage" });
20770
21090
  }).catch((err) => {
@@ -20838,19 +21158,19 @@ function createProxyServer(config = {}) {
20838
21158
  reader.cancel(reason).catch(() => {});
20839
21159
  }
20840
21160
  });
20841
- return { failed: false, errorPayload: null, errorType: null, response: new Response(rest, { status: res.status, headers: res.headers }) };
21161
+ const response = new Response(rest, { status: res.status, headers: res.headers });
21162
+ const completion = responseCompletions.get(res);
21163
+ if (completion)
21164
+ responseCompletions.set(response, completion);
21165
+ return { failed: false, errorPayload: null, errorType: null, response };
20842
21166
  }
20843
- async function dispatchPriority(c, orderedCandidateIds, sessionKey, wantsStream) {
20844
- const bodyBuf = await c.req.arrayBuffer();
21167
+ async function dispatchPriority(c, body, requestMeta, orderedCandidateIds, sessionKey, wantsStream) {
20845
21168
  let lastError = null;
20846
21169
  let lastStatus = 429;
20847
21170
  let previous = null;
20848
21171
  let previousReason = "rate_limit_error";
20849
- for (const candidate of orderedCandidateIds) {
20850
- const headers = new Headers(c.req.raw.headers);
20851
- headers.set("x-meridian-profile", candidate);
20852
- headers.set("x-meridian-priority-dispatch", "1");
20853
- const inner = await app.fetch(new Request(c.req.url, { method: "POST", headers, body: bodyBuf }));
21172
+ for (const [attempt, candidate] of orderedCandidateIds.entries()) {
21173
+ const inner = await handleMessages(c, forkAttemptMeta(requestMeta, attempt), { body, forcedProfileId: candidate });
20854
21174
  const sniffed = await sniffAccountFailure(inner);
20855
21175
  if (!sniffed.failed) {
20856
21176
  if (sessionKey)
@@ -20861,6 +21181,7 @@ function createProxyServer(config = {}) {
20861
21181
  }
20862
21182
  return sniffed.response;
20863
21183
  }
21184
+ await responseCompletions.get(inner)?.catch(() => {});
20864
21185
  const reason = sniffed.errorType;
20865
21186
  const quotaRefusal = isQuotaRefusal(reason);
20866
21187
  const cooldownUntil = quotaRefusal ? priorityCooldownUntil(candidate, Date.now()) : Date.now() + PRIORITY_DEFAULT_COOLDOWN_MS;
@@ -20897,29 +21218,8 @@ data: ${JSON.stringify(lastError)}
20897
21218
  }
20898
21219
  return c.html(landingHtml);
20899
21220
  });
20900
- const MAX_CONCURRENT_SESSIONS = parseInt((process.env.MERIDIAN_MAX_CONCURRENT ?? process.env.CLAUDE_PROXY_MAX_CONCURRENT) || "10", 10);
20901
- let activeSessions = 0;
20902
- const sessionQueue = [];
20903
- const insideSessionSlot = new AsyncLocalStorage;
20904
- async function acquireSession() {
20905
- if (activeSessions < MAX_CONCURRENT_SESSIONS) {
20906
- activeSessions++;
20907
- return;
20908
- }
20909
- return new Promise((resolve3) => {
20910
- sessionQueue.push({ resolve: resolve3 });
20911
- });
20912
- }
20913
- function releaseSession() {
20914
- activeSessions--;
20915
- const next = sessionQueue.shift();
20916
- if (next) {
20917
- activeSessions++;
20918
- next.resolve();
20919
- }
20920
- }
20921
- const handleMessages = async (c, requestMeta) => {
20922
- const requestStartAt = Date.now();
21221
+ const handleMessages = async (c, requestMeta, options) => {
21222
+ const requestStartAt = requestMeta.queueEnteredAt;
20923
21223
  const requestAbort = linkRequestAbort(c.req.raw.signal);
20924
21224
  let streamOwnsAbortLink = false;
20925
21225
  return withClaudeLogContext({ requestId: requestMeta.requestId, endpoint: requestMeta.endpoint }, async () => {
@@ -20935,7 +21235,7 @@ data: ${JSON.stringify(lastError)}
20935
21235
  }
20936
21236
  return textPrompt;
20937
21237
  };
20938
- const body = await c.req.json();
21238
+ const body = options.body;
20939
21239
  if (!Array.isArray(body.messages)) {
20940
21240
  return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
20941
21241
  }
@@ -20952,7 +21252,7 @@ data: ${JSON.stringify(lastError)}
20952
21252
  }
20953
21253
  const outputFormat = parsedOutputFormat.value;
20954
21254
  const routingMode = getRoutingMode(process.env.MERIDIAN_ROUTING ?? getSetting("routing"));
20955
- if (routingMode === "priority" && !c.req.header("x-meridian-profile")) {
21255
+ if (routingMode === "priority" && !options.forcedProfileId && !c.req.header("x-meridian-profile")) {
20956
21256
  const effectivePool = getEffectiveProfiles(finalConfig.profiles);
20957
21257
  if (effectivePool.length > 1) {
20958
21258
  const { order, unknown } = resolvePriorityOrder(effectivePool.map((p) => p.id), priorityProfileOrderSetting());
@@ -20969,10 +21269,10 @@ data: ${JSON.stringify(lastError)}
20969
21269
  first = pick?.id ?? order[0];
20970
21270
  }
20971
21271
  const candidates = [first, ...order.filter((id) => id !== first && !priorityExhaustion.isExhausted(id))];
20972
- return dispatchPriority(c, candidates, sessionKey, body.stream === true);
21272
+ return dispatchPriority(c, body, requestMeta, candidates, sessionKey, body.stream === true);
20973
21273
  }
20974
21274
  }
20975
- const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined, routingMode === "sticky" ? { routingMode, stickySessionKey: adapter.getSessionId(c, body) } : undefined);
21275
+ 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);
20976
21276
  const authStatus = await getClaudeAuthStatusAsync(profile.id !== "default" ? profile.id : undefined, Object.keys(profile.env).length > 0 ? profile.env : undefined);
20977
21277
  const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
20978
21278
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
@@ -21061,26 +21361,63 @@ data: ${JSON.stringify(lastError)}
21061
21361
  const betas = betaFilter.forwarded;
21062
21362
  const agentSessionId = adapter.getSessionId(c, body);
21063
21363
  const profileSessionId = profile.id !== "default" && agentSessionId ? `${profile.id}:${agentSessionId}` : agentSessionId;
21364
+ const commitSessionTurn = () => {
21365
+ if (profileSessionId)
21366
+ requestMeta.sessionTurnLease?.markCommitted(profileSessionId);
21367
+ };
21064
21368
  const profileScopedCwd = profile.id !== "default" ? `${clientWorkingDirectory}::profile=${profile.id}` : clientWorkingDirectory;
21065
21369
  const lastMessage = Array.isArray(body.messages) ? body.messages[body.messages.length - 1] : undefined;
21066
21370
  const lastIsToolResult = Array.isArray(lastMessage?.content) && lastMessage.content.some((b) => b?.type === "tool_result");
21067
21371
  const isClientDrivenLoop = adapterBase !== "claude-code" && !agentSessionId && lastIsToolResult;
21068
21372
  const isIndependentSession = !agentSessionId && (requestSource?.startsWith("fork-") || requestSource?.startsWith("subagent-")) || isClientDrivenLoop || false;
21069
- if (!isIndependentSession && profileSessionId) {
21070
- const pendingStore = pendingSessionStores.get(profileSessionId);
21071
- if (pendingStore) {
21072
- const waitStart = Date.now();
21073
- await Promise.race([
21074
- pendingStore.promise,
21075
- new Promise((resolve3) => setTimeout(resolve3, PENDING_STORE_WAIT_MS))
21076
- ]);
21077
- claudeLog("session.pending_store_awaited", { waitedMs: Date.now() - waitStart });
21078
- }
21079
- }
21080
21373
  let lineageResult = isIndependentSession ? { type: "diverged", reason: "independent-request" } : lookupSession(profileSessionId, body.messages || [], profileScopedCwd);
21081
21374
  if (lineageResult.type === "undo" && adapterBase === "opencode" && !agentSessionId) {
21082
21375
  lineageResult = { type: "diverged", reason: "missing-session-header" };
21083
21376
  }
21377
+ const declaresConcurrentFlow = requestSource?.startsWith("fork-") === true || requestSource?.startsWith("subagent-") === true;
21378
+ if (profileSessionId && !declaresConcurrentFlow && requestMeta.sessionTurnLease?.advancedWhileWaiting(profileSessionId) && lineageResult.type !== "continuation" && lineageResult.type !== "compaction") {
21379
+ const reason = lineageResult.type === "diverged" ? lineageResult.reason : lineageResult.type;
21380
+ const message = "This session advanced while the request was waiting. Retry with the latest conversation history or use a distinct session ID.";
21381
+ claudeLog("session.concurrent_conflict", {
21382
+ reason,
21383
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs
21384
+ });
21385
+ diagnosticLog2.session(`${requestMeta.requestId} session.concurrent_conflict reason=${reason} wait=${requestMeta.sessionQueueWaitMs}ms`, requestMeta.requestId);
21386
+ const conflictTotalMs = Date.now() - requestStartAt;
21387
+ const conflictQueueWaitMs = totalQueueWaitMs(requestMeta);
21388
+ telemetryStore2.record({
21389
+ requestId: requestMeta.requestId,
21390
+ timestamp: Date.now(),
21391
+ adapter: adapter.name,
21392
+ model,
21393
+ requestModel: requestedModel,
21394
+ mode: stream3 ? "stream" : "non-stream",
21395
+ isResume: false,
21396
+ isPassthrough: envBool("PASSTHROUGH"),
21397
+ hasDeferredTools: undefined,
21398
+ deferredToolCount: undefined,
21399
+ toolCount: body.tools?.length ?? 0,
21400
+ lineageType: lineageResult.type,
21401
+ messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
21402
+ sdkSessionId: undefined,
21403
+ status: 400,
21404
+ queueWaitMs: conflictQueueWaitMs,
21405
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
21406
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
21407
+ proxyOverheadMs: Math.max(0, conflictTotalMs - conflictQueueWaitMs),
21408
+ ttfbMs: null,
21409
+ upstreamDurationMs: 0,
21410
+ totalDurationMs: conflictTotalMs,
21411
+ contentBlocks: 0,
21412
+ textEvents: 0,
21413
+ error: "session_turn_conflict",
21414
+ profileId: profile.id
21415
+ });
21416
+ return new Response(JSON.stringify({ type: "error", error: { type: "invalid_request_error", message } }), {
21417
+ status: 400,
21418
+ headers: { "Content-Type": "application/json" }
21419
+ });
21420
+ }
21084
21421
  if (pipeline.some((t) => t.onSession)) {
21085
21422
  const mismatch = lineageResult.type === "diverged" ? lineageResult.mismatch : undefined;
21086
21423
  runTransformHook(pipeline, "onSession", {
@@ -21116,7 +21453,8 @@ data: ${JSON.stringify(lastError)}
21116
21453
  const lineageType = lineageResult.type === "diverged" && !cachedSession ? "new" : lineageResult.type;
21117
21454
  const msgCount = Array.isArray(body.messages) ? body.messages.length : 0;
21118
21455
  const toolCount = body.tools?.length ?? 0;
21119
- const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name}${requestSource ? ` source=${requestSource}` : ""}${profile.id !== "default" ? ` profile=${profile.id}${routingMode === "sticky" ? "(sticky)" : c.req.header("x-meridian-priority-dispatch") ? "(priority)" : ""}` : ""} model=${model} stream=${stream3} tools=${toolCount} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
21456
+ const sdkSnapshot = sdkSemaphore.snapshot;
21457
+ const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name}${requestSource ? ` source=${requestSource}` : ""}${profile.id !== "default" ? ` profile=${profile.id}${routingMode === "sticky" ? "(sticky)" : options.forcedProfileId ? "(priority)" : ""}` : ""} model=${model} stream=${stream3} tools=${toolCount} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} sdkActive=${sdkSnapshot.active}/${sdkSnapshot.limit} sdkQueued=${sdkSnapshot.queued} sessionWait=${requestMeta.sessionQueueWaitMs}ms msgCount=${msgCount}`;
21120
21458
  plog(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
21121
21459
  diagnosticLog2.session(`${requestLogLine}`, requestMeta.requestId);
21122
21460
  if (lineageResult.type === "diverged" && profileSessionId && !isIndependentSession) {
@@ -21131,7 +21469,9 @@ data: ${JSON.stringify(lastError)}
21131
21469
  claudeLog("request.received", {
21132
21470
  model,
21133
21471
  stream: stream3,
21134
- queueWaitMs: requestMeta.queueStartedAt - requestMeta.queueEnteredAt,
21472
+ queueWaitMs: totalQueueWaitMs(requestMeta),
21473
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
21474
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
21135
21475
  messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
21136
21476
  hasSystemPrompt: Boolean(body.system)
21137
21477
  });
@@ -21421,7 +21761,7 @@ data: ${JSON.stringify(lastError)}
21421
21761
  const attemptStderrStart = stderrLines.length;
21422
21762
  turnGenerating = true;
21423
21763
  try {
21424
- for await (const event of query(buildQueryOptions({
21764
+ for await (const event of runSdkQueryAttempt(buildQueryOptions({
21425
21765
  prompt: makePrompt(),
21426
21766
  model,
21427
21767
  workingDirectory,
@@ -21463,7 +21803,7 @@ data: ${JSON.stringify(lastError)}
21463
21803
  sdkDebug: sdkFeatures.sdkDebug,
21464
21804
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
21465
21805
  advisorModel
21466
- }, requestAbort.controller))) {
21806
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "non_stream")) {
21467
21807
  if (event.type === "rate_limit_event") {
21468
21808
  rateLimitStore.record(profile.id, event.rate_limit_info);
21469
21809
  }
@@ -21509,7 +21849,7 @@ data: ${JSON.stringify(lastError)}
21509
21849
  sdkUuidMap.length = 0;
21510
21850
  for (let i = 0;i < allMessages.length; i++)
21511
21851
  sdkUuidMap.push(null);
21512
- yield* query(buildQueryOptions({
21852
+ yield* runSdkQueryAttempt(buildQueryOptions({
21513
21853
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
21514
21854
  model,
21515
21855
  workingDirectory,
@@ -21550,7 +21890,7 @@ data: ${JSON.stringify(lastError)}
21550
21890
  sdkDebug: sdkFeatures.sdkDebug,
21551
21891
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
21552
21892
  advisorModel
21553
- }, requestAbort.controller));
21893
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "non_stream_fresh");
21554
21894
  return;
21555
21895
  }
21556
21896
  if (isExtraUsageRequiredError(errMsg) && hasExtendedContext(model)) {
@@ -21578,7 +21918,7 @@ data: ${JSON.stringify(lastError)}
21578
21918
  sdkUuidMap.length = 0;
21579
21919
  for (let i = 0;i < allMessages.length; i++)
21580
21920
  sdkUuidMap.push(null);
21581
- yield* query(buildQueryOptions({
21921
+ yield* runSdkQueryAttempt(buildQueryOptions({
21582
21922
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
21583
21923
  model,
21584
21924
  workingDirectory,
@@ -21619,7 +21959,7 @@ data: ${JSON.stringify(lastError)}
21619
21959
  sdkDebug: sdkFeatures.sdkDebug,
21620
21960
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
21621
21961
  advisorModel
21622
- }, requestAbort.controller));
21962
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "non_stream_fresh");
21623
21963
  return;
21624
21964
  }
21625
21965
  if (isExpiredTokenError(errMsg) && !tokenRefreshed) {
@@ -21693,10 +22033,11 @@ data: ${JSON.stringify(lastError)}
21693
22033
  }
21694
22034
  if (!firstChunkAt) {
21695
22035
  firstChunkAt = Date.now();
22036
+ requestMeta.ttfbMs ??= firstChunkAt - (requestMeta.currentSdkStartedAt ?? firstChunkAt);
21696
22037
  claudeLog("upstream.first_chunk", {
21697
22038
  mode: "non_stream",
21698
22039
  model,
21699
- ttfbMs: firstChunkAt - upstreamStartAt
22040
+ ttfbMs: requestMeta.ttfbMs
21700
22041
  });
21701
22042
  }
21702
22043
  const isPassthroughTurn2 = passthrough && assistantMessages > 1 && contentBlocks.some((b) => b.type === "tool_use");
@@ -21875,7 +22216,7 @@ Subprocess stderr: ${stderrOutput}`;
21875
22216
  contentBlocks: contentBlocks.length,
21876
22217
  hasToolUse
21877
22218
  });
21878
- const nonStreamQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
22219
+ const nonStreamQueueWaitMs = totalQueueWaitMs(requestMeta);
21879
22220
  checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
21880
22221
  telemetryStore2.record({
21881
22222
  requestId: requestMeta.requestId,
@@ -21898,9 +22239,11 @@ Subprocess stderr: ${stderrOutput}`;
21898
22239
  sdkSessionId: currentSessionId || resumeSessionId,
21899
22240
  status: 200,
21900
22241
  queueWaitMs: nonStreamQueueWaitMs,
21901
- proxyOverheadMs: upstreamStartAt - requestStartAt - nonStreamQueueWaitMs,
21902
- ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
21903
- upstreamDurationMs: Date.now() - upstreamStartAt,
22242
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
22243
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
22244
+ proxyOverheadMs: Math.max(0, totalDurationMs - nonStreamQueueWaitMs - requestMeta.sdkActiveDurationMs),
22245
+ ttfbMs: requestMeta.ttfbMs ?? null,
22246
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
21904
22247
  totalDurationMs,
21905
22248
  contentBlocks: contentBlocks.length,
21906
22249
  textEvents: 0,
@@ -21921,6 +22264,7 @@ Subprocess stderr: ${stderrOutput}`;
21921
22264
  }
21922
22265
  if (currentSessionId && !isIndependentSession && !sawDuplicateToolUse) {
21923
22266
  storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, earlyStopFired ? nextPassthroughResumeUuid : null);
22267
+ commitSessionTurn();
21924
22268
  }
21925
22269
  const responseSessionId = currentSessionId || resumeSessionId || `session_${Date.now()}`;
21926
22270
  return new Response(JSON.stringify({
@@ -21944,222 +22288,139 @@ Subprocess stderr: ${stderrOutput}`;
21944
22288
  });
21945
22289
  }
21946
22290
  const encoder = new TextEncoder;
22291
+ let resolveStreamCompletion = () => {};
22292
+ const streamCompletion = new Promise((resolve3) => {
22293
+ resolveStreamCompletion = resolve3;
22294
+ });
21947
22295
  const readable = new ReadableStream({
21948
- async start(controller) {
21949
- const upstreamStartAt = Date.now();
21950
- let firstChunkAt;
21951
- let heartbeatCount = 0;
21952
- let streamEventsSeen = 0;
21953
- let eventsForwarded = 0;
21954
- let textEventsForwarded = 0;
21955
- let textCharsForwarded = 0;
21956
- let bytesSent = 0;
21957
- let streamClosed = false;
21958
- let awaitingEarlyStopDrain = false;
21959
- claudeLog("upstream.start", { mode: "stream", model });
21960
- const safeEnqueue = (payload, source) => {
21961
- if (streamClosed)
21962
- return false;
21963
- try {
21964
- controller.enqueue(payload);
21965
- bytesSent += payload.byteLength;
21966
- return true;
21967
- } catch (error) {
21968
- if (isClosedControllerError(error)) {
21969
- streamClosed = true;
21970
- claudeLog("stream.client_closed", { source, streamEventsSeen, eventsForwarded });
22296
+ start(controller) {
22297
+ return (async () => {
22298
+ const upstreamStartAt = Date.now();
22299
+ let firstChunkAt;
22300
+ let heartbeatCount = 0;
22301
+ let streamEventsSeen = 0;
22302
+ let eventsForwarded = 0;
22303
+ let textEventsForwarded = 0;
22304
+ let textCharsForwarded = 0;
22305
+ let bytesSent = 0;
22306
+ let streamClosed = false;
22307
+ let awaitingEarlyStopDrain = false;
22308
+ claudeLog("upstream.start", { mode: "stream", model });
22309
+ const safeEnqueue = (payload, source) => {
22310
+ if (streamClosed)
21971
22311
  return false;
22312
+ try {
22313
+ controller.enqueue(payload);
22314
+ bytesSent += payload.byteLength;
22315
+ return true;
22316
+ } catch (error) {
22317
+ if (isClosedControllerError(error)) {
22318
+ streamClosed = true;
22319
+ claudeLog("stream.client_closed", { source, streamEventsSeen, eventsForwarded });
22320
+ return false;
22321
+ }
22322
+ claudeLog("stream.enqueue_failed", {
22323
+ source,
22324
+ error: error instanceof Error ? error.message : String(error)
22325
+ });
22326
+ throw error;
21972
22327
  }
21973
- claudeLog("stream.enqueue_failed", {
21974
- source,
21975
- error: error instanceof Error ? error.message : String(error)
21976
- });
21977
- throw error;
21978
- }
21979
- };
21980
- const sdkUuidMap = cachedSession?.sdkMessageUuids ? [...cachedSession.sdkMessageUuids] : [];
21981
- while (sdkUuidMap.length < allMessages.length)
21982
- sdkUuidMap.push(null);
21983
- let messageStartEmitted = false;
21984
- let lastUsage;
21985
- let hasStructuredOutput = false;
21986
- let structuredOutput;
21987
- let nextPassthroughResumeUuid;
21988
- const silentTurnRecoveryEnabled = env("SILENT_TURN_RECOVERY") !== "0";
21989
- let silentTurnRecoveryAttempted = false;
21990
- let silentTurnRecovered = false;
21991
- const streamedToolUseIds = new Set;
21992
- let pendingTerminalDelta = null;
21993
- let terminalDeltaSent = false;
21994
- const sendTerminalDelta = (stopReasonOverride) => {
21995
- if (terminalDeltaSent)
21996
- return;
21997
- const payload = stopReasonOverride ? encoder.encode(`event: message_delta
22328
+ };
22329
+ const sdkUuidMap = cachedSession?.sdkMessageUuids ? [...cachedSession.sdkMessageUuids] : [];
22330
+ while (sdkUuidMap.length < allMessages.length)
22331
+ sdkUuidMap.push(null);
22332
+ let messageStartEmitted = false;
22333
+ let lastUsage;
22334
+ let hasStructuredOutput = false;
22335
+ let structuredOutput;
22336
+ let nextPassthroughResumeUuid;
22337
+ const silentTurnRecoveryEnabled = env("SILENT_TURN_RECOVERY") !== "0";
22338
+ let silentTurnRecoveryAttempted = false;
22339
+ let silentTurnRecovered = false;
22340
+ const streamedToolUseIds = new Set;
22341
+ let pendingTerminalDelta = null;
22342
+ let terminalDeltaSent = false;
22343
+ const sendTerminalDelta = (stopReasonOverride) => {
22344
+ if (terminalDeltaSent)
22345
+ return;
22346
+ const payload = stopReasonOverride ? encoder.encode(`event: message_delta
21998
22347
  data: ${JSON.stringify({
21999
- type: "message_delta",
22000
- delta: { stop_reason: stopReasonOverride, stop_sequence: null },
22001
- usage: { output_tokens: lastUsage?.output_tokens ?? 0 }
22002
- })}
22348
+ type: "message_delta",
22349
+ delta: { stop_reason: stopReasonOverride, stop_sequence: null },
22350
+ usage: { output_tokens: lastUsage?.output_tokens ?? 0 }
22351
+ })}
22003
22352
 
22004
22353
  `) : pendingTerminalDelta;
22005
- if (!payload)
22006
- return;
22007
- terminalDeltaSent = true;
22008
- if (safeEnqueue(payload, "terminal_message_delta"))
22009
- eventsForwarded += 1;
22010
- };
22011
- const openClientBlocks = new Set;
22012
- const resolvePendingStore = passthrough && earlyStopEnabled && !isIndependentSession && profileSessionId ? registerPendingStore(profileSessionId) : () => {};
22013
- let pendingEarlyStop = false;
22014
- let pendingEarlyStopAt = 0;
22015
- const fireEarlyStop = (reason) => {
22016
- earlyStopFired = true;
22017
- claudeLog("passthrough.early_stop", {
22018
- mode: "stream",
22019
- captured: capturedToolUses.length,
22020
- drained: awaitingEarlyStopDrain,
22021
- reason,
22022
- deferredMs: pendingEarlyStopAt ? Date.now() - pendingEarlyStopAt : 0
22023
- });
22024
- pendingEarlyStop = false;
22025
- flushOpenClientBlocks("early_stop");
22026
- sendTerminalDelta("tool_use");
22027
- safeEnqueue(encoder.encode(`event: message_stop
22354
+ if (!payload)
22355
+ return;
22356
+ terminalDeltaSent = true;
22357
+ if (safeEnqueue(payload, "terminal_message_delta"))
22358
+ eventsForwarded += 1;
22359
+ };
22360
+ const openClientBlocks = new Set;
22361
+ let pendingEarlyStop = false;
22362
+ let pendingEarlyStopAt = 0;
22363
+ const fireEarlyStop = (reason) => {
22364
+ earlyStopFired = true;
22365
+ claudeLog("passthrough.early_stop", {
22366
+ mode: "stream",
22367
+ captured: capturedToolUses.length,
22368
+ drained: awaitingEarlyStopDrain,
22369
+ reason,
22370
+ deferredMs: pendingEarlyStopAt ? Date.now() - pendingEarlyStopAt : 0
22371
+ });
22372
+ pendingEarlyStop = false;
22373
+ flushOpenClientBlocks("early_stop");
22374
+ sendTerminalDelta("tool_use");
22375
+ safeEnqueue(encoder.encode(`event: message_stop
22028
22376
  data: ${JSON.stringify({ type: "message_stop" })}
22029
22377
 
22030
22378
  `), "early_stop");
22031
- requestAbort.abort("passthrough turn complete");
22032
- awaitingEarlyStopDrain = false;
22033
- if (!streamClosed) {
22034
- streamClosed = true;
22035
- try {
22036
- controller.close();
22037
- } catch {}
22038
- }
22039
- };
22040
- const flushOpenClientBlocks = (source) => {
22041
- if (openClientBlocks.size === 0)
22042
- return;
22043
- recordEnvelopeViolations([...openClientBlocks].map((idx) => ({
22044
- type: "dangling_block",
22045
- detail: `content block ${idx} still open at ${source} close`
22046
- })));
22047
- claudeLog("stream.dangling_blocks_closed", { source, count: openClientBlocks.size });
22048
- for (const idx of openClientBlocks) {
22049
- safeEnqueue(encoder.encode(`event: content_block_stop
22379
+ requestAbort.abort("passthrough turn complete");
22380
+ awaitingEarlyStopDrain = false;
22381
+ if (!streamClosed) {
22382
+ streamClosed = true;
22383
+ try {
22384
+ controller.close();
22385
+ } catch {}
22386
+ }
22387
+ };
22388
+ const flushOpenClientBlocks = (source) => {
22389
+ if (openClientBlocks.size === 0)
22390
+ return;
22391
+ recordEnvelopeViolations([...openClientBlocks].map((idx) => ({
22392
+ type: "dangling_block",
22393
+ detail: `content block ${idx} still open at ${source} close`
22394
+ })));
22395
+ claudeLog("stream.dangling_blocks_closed", { source, count: openClientBlocks.size });
22396
+ for (const idx of openClientBlocks) {
22397
+ safeEnqueue(encoder.encode(`event: content_block_stop
22050
22398
  data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
22051
22399
 
22052
22400
  `), `${source}_close_dangling`);
22053
- }
22054
- openClientBlocks.clear();
22055
- };
22056
- let currentSessionId;
22057
- try {
22058
- const MAX_RATE_LIMIT_RETRIES = 2;
22059
- const RATE_LIMIT_BASE_DELAY_MS = 1000;
22060
- const response = async function* () {
22061
- let rateLimitRetries = 0;
22062
- if (profileCredentialStore) {
22063
- await ensureFreshToken(profileCredentialStore).catch(() => {});
22064
22401
  }
22065
- let tokenRefreshed = false;
22066
- let didFreshBaseRetry = false;
22067
- let resumeRefusalRetries = 0;
22068
- let busySessionFork = false;
22069
- let sawUnresumableRefusal = false;
22070
- while (true) {
22071
- let didYieldClientEvent = false;
22072
- const attemptStderrStart = stderrLines.length;
22073
- try {
22074
- for await (const event of query(buildQueryOptions({
22075
- prompt: makePrompt(),
22076
- model,
22077
- workingDirectory,
22078
- clientWorkingDirectory,
22079
- systemContext,
22080
- claudeExecutable,
22081
- passthrough,
22082
- stream: true,
22083
- sdkAgents,
22084
- passthroughMcp,
22085
- cleanEnv: profileEnv,
22086
- envOverrides,
22087
- hasDeferredTools,
22088
- resumeSessionId,
22089
- isUndo,
22090
- resumeSessionAtUuid: undoRollbackUuid ?? passthroughResumeUuid,
22091
- forkSession: busySessionFork || Boolean(passthroughResumeUuid) || undefined,
22092
- sdkHooks,
22093
- blockedTools: pipelineCtx.blockedTools,
22094
- incompatibleTools: pipelineCtx.incompatibleTools,
22095
- mcpServerName: adapter.getMcpServerName(),
22096
- allowedMcpTools: pipelineCtx.allowedMcpTools,
22097
- onStderr,
22098
- effort,
22099
- thinking,
22100
- taskBudget,
22101
- outputFormat,
22102
- betas,
22103
- settingSources,
22104
- codeSystemPrompt: sdkFeatures.codeSystemPrompt,
22105
- clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
22106
- memory: sdkFeatures.memory,
22107
- dreaming: sdkFeatures.dreaming,
22108
- sharedMemory: sdkFeatures.sharedMemory,
22109
- webFetchPreflight: sdkFeatures.webFetchPreflight,
22110
- claudeAiConnectors: sdkFeatures.claudeAiConnectors,
22111
- maxBudgetUsd: sdkFeatures.maxBudgetUsd,
22112
- fallbackModel: sdkFeatures.fallbackModel,
22113
- sdkDebug: sdkFeatures.sdkDebug,
22114
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22115
- advisorModel
22116
- }, requestAbort.controller))) {
22117
- if (event.type === "rate_limit_event") {
22118
- rateLimitStore.record(profile.id, event.rate_limit_info);
22119
- }
22120
- if (event.type === "stream_event") {
22121
- didYieldClientEvent = true;
22122
- }
22123
- yield event;
22124
- }
22125
- return;
22126
- } catch (error) {
22127
- const errMsg = error instanceof Error ? error.message : String(error);
22128
- if (didYieldClientEvent)
22129
- throw error;
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));
22140
- continue;
22141
- }
22142
- if (refusal === "busy" && !busySessionFork) {
22143
- busySessionFork = true;
22144
- claudeLog("session.busy_fork", { mode: "stream", resumeSessionId });
22145
- plog(`[PROXY] ${requestMeta.requestId} session still busy after ${RESUME_REFUSAL_MAX_RETRIES} retries — forking session`);
22146
- continue;
22147
- }
22148
- }
22149
- if (refusal === "missing-message" || sawUnresumableRefusal) {
22150
- claudeLog("session.resume_replay", {
22151
- mode: "stream",
22152
- refusal,
22153
- rollbackUuid: undoRollbackUuid,
22154
- resumeSessionId
22155
- });
22156
- plog(`[PROXY] ${requestMeta.requestId} session unusable (${refusal}), evicting and replaying as fresh session`);
22157
- evictSession(profileSessionId, profileScopedCwd, allMessages);
22158
- sdkUuidMap.length = 0;
22159
- for (let i = 0;i < allMessages.length; i++)
22160
- sdkUuidMap.push(null);
22161
- yield* query(buildQueryOptions({
22162
- prompt: buildFreshPrompt(allMessages, sanitizeOpts),
22402
+ openClientBlocks.clear();
22403
+ };
22404
+ let currentSessionId;
22405
+ try {
22406
+ const MAX_RATE_LIMIT_RETRIES = 2;
22407
+ const RATE_LIMIT_BASE_DELAY_MS = 1000;
22408
+ const response = async function* () {
22409
+ let rateLimitRetries = 0;
22410
+ if (profileCredentialStore) {
22411
+ await ensureFreshToken(profileCredentialStore).catch(() => {});
22412
+ }
22413
+ let tokenRefreshed = false;
22414
+ let didFreshBaseRetry = false;
22415
+ let resumeRefusalRetries = 0;
22416
+ let busySessionFork = false;
22417
+ let sawUnresumableRefusal = false;
22418
+ while (true) {
22419
+ let didYieldClientEvent = false;
22420
+ const attemptStderrStart = stderrLines.length;
22421
+ try {
22422
+ for await (const event of runSdkQueryAttempt(buildQueryOptions({
22423
+ prompt: makePrompt(),
22163
22424
  model,
22164
22425
  workingDirectory,
22165
22426
  clientWorkingDirectory,
@@ -22172,9 +22433,10 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
22172
22433
  cleanEnv: profileEnv,
22173
22434
  envOverrides,
22174
22435
  hasDeferredTools,
22175
- resumeSessionId: undefined,
22176
- isUndo: false,
22177
- resumeSessionAtUuid: undefined,
22436
+ resumeSessionId,
22437
+ isUndo,
22438
+ resumeSessionAtUuid: undoRollbackUuid ?? passthroughResumeUuid,
22439
+ forkSession: busySessionFork || Boolean(passthroughResumeUuid) || undefined,
22178
22440
  sdkHooks,
22179
22441
  blockedTools: pipelineCtx.blockedTools,
22180
22442
  incompatibleTools: pipelineCtx.incompatibleTools,
@@ -22199,857 +22461,993 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
22199
22461
  sdkDebug: sdkFeatures.sdkDebug,
22200
22462
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22201
22463
  advisorModel
22202
- }, requestAbort.controller));
22464
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "stream")) {
22465
+ if (event.type === "rate_limit_event") {
22466
+ rateLimitStore.record(profile.id, event.rate_limit_info);
22467
+ }
22468
+ if (event.type === "stream_event") {
22469
+ didYieldClientEvent = true;
22470
+ }
22471
+ yield event;
22472
+ }
22203
22473
  return;
22474
+ } catch (error) {
22475
+ const errMsg = error instanceof Error ? error.message : String(error);
22476
+ if (didYieldClientEvent)
22477
+ throw error;
22478
+ const refusal = classifyResumeRefusal(error, resumeSessionId ? stderrLines.slice(attemptStderrStart).join(`
22479
+ `) : undefined);
22480
+ if (refusal === "unresumable")
22481
+ sawUnresumableRefusal = true;
22482
+ if (resumeSessionId && (refusal === "busy" || refusal === "unresumable")) {
22483
+ if (resumeRefusalRetries < RESUME_REFUSAL_MAX_RETRIES) {
22484
+ resumeRefusalRetries++;
22485
+ claudeLog("session.resume_retry", { mode: "stream", refusal, attempt: resumeRefusalRetries, resumeSessionId });
22486
+ plog(`[PROXY] ${requestMeta.requestId} resume refused (${refusal}), retrying ${resumeRefusalRetries}/${RESUME_REFUSAL_MAX_RETRIES}`);
22487
+ await new Promise((resolve3) => setTimeout(resolve3, RESUME_REFUSAL_RETRY_DELAY_MS * resumeRefusalRetries));
22488
+ continue;
22489
+ }
22490
+ if (refusal === "busy" && !busySessionFork) {
22491
+ busySessionFork = true;
22492
+ claudeLog("session.busy_fork", { mode: "stream", resumeSessionId });
22493
+ plog(`[PROXY] ${requestMeta.requestId} session still busy after ${RESUME_REFUSAL_MAX_RETRIES} retries — forking session`);
22494
+ continue;
22495
+ }
22496
+ }
22497
+ if (refusal === "missing-message" || sawUnresumableRefusal) {
22498
+ claudeLog("session.resume_replay", {
22499
+ mode: "stream",
22500
+ refusal,
22501
+ rollbackUuid: undoRollbackUuid,
22502
+ resumeSessionId
22503
+ });
22504
+ plog(`[PROXY] ${requestMeta.requestId} session unusable (${refusal}), evicting and replaying as fresh session`);
22505
+ evictSession(profileSessionId, profileScopedCwd, allMessages);
22506
+ sdkUuidMap.length = 0;
22507
+ for (let i = 0;i < allMessages.length; i++)
22508
+ sdkUuidMap.push(null);
22509
+ yield* runSdkQueryAttempt(buildQueryOptions({
22510
+ prompt: buildFreshPrompt(allMessages, sanitizeOpts),
22511
+ model,
22512
+ workingDirectory,
22513
+ clientWorkingDirectory,
22514
+ systemContext,
22515
+ claudeExecutable,
22516
+ passthrough,
22517
+ stream: true,
22518
+ sdkAgents,
22519
+ passthroughMcp,
22520
+ cleanEnv: profileEnv,
22521
+ envOverrides,
22522
+ hasDeferredTools,
22523
+ resumeSessionId: undefined,
22524
+ isUndo: false,
22525
+ resumeSessionAtUuid: undefined,
22526
+ sdkHooks,
22527
+ blockedTools: pipelineCtx.blockedTools,
22528
+ incompatibleTools: pipelineCtx.incompatibleTools,
22529
+ mcpServerName: adapter.getMcpServerName(),
22530
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
22531
+ onStderr,
22532
+ effort,
22533
+ thinking,
22534
+ taskBudget,
22535
+ outputFormat,
22536
+ betas,
22537
+ settingSources,
22538
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
22539
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
22540
+ memory: sdkFeatures.memory,
22541
+ dreaming: sdkFeatures.dreaming,
22542
+ sharedMemory: sdkFeatures.sharedMemory,
22543
+ webFetchPreflight: sdkFeatures.webFetchPreflight,
22544
+ claudeAiConnectors: sdkFeatures.claudeAiConnectors,
22545
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
22546
+ fallbackModel: sdkFeatures.fallbackModel,
22547
+ sdkDebug: sdkFeatures.sdkDebug,
22548
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22549
+ advisorModel
22550
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "stream_fresh");
22551
+ return;
22552
+ }
22553
+ if (isExtraUsageRequiredError(errMsg) && hasExtendedContext(model)) {
22554
+ const from = model;
22555
+ model = stripExtendedContext(model);
22556
+ recordExtendedContextUnavailable();
22557
+ claudeLog("upstream.context_fallback", {
22558
+ mode: "stream",
22559
+ from,
22560
+ to: model,
22561
+ reason: "extra_usage_required"
22562
+ });
22563
+ plog(`[PROXY] ${requestMeta.requestId} extra usage required for [1m], falling back to ${model} (skipping [1m] for 1h)`);
22564
+ continue;
22565
+ }
22566
+ if (isExtraUsageRequiredError(errMsg) && resumeSessionId && !didFreshBaseRetry) {
22567
+ didFreshBaseRetry = true;
22568
+ claudeLog("upstream.session_fallback", {
22569
+ mode: "stream",
22570
+ model,
22571
+ reason: "extra_usage_required_resume"
22572
+ });
22573
+ plog(`[PROXY] ${requestMeta.requestId} extra usage persisted on resumed ${model}, retrying as fresh session`);
22574
+ evictSession(profileSessionId, profileScopedCwd, allMessages);
22575
+ sdkUuidMap.length = 0;
22576
+ for (let i = 0;i < allMessages.length; i++)
22577
+ sdkUuidMap.push(null);
22578
+ yield* runSdkQueryAttempt(buildQueryOptions({
22579
+ prompt: buildFreshPrompt(allMessages, sanitizeOpts),
22580
+ model,
22581
+ workingDirectory,
22582
+ clientWorkingDirectory,
22583
+ systemContext,
22584
+ claudeExecutable,
22585
+ passthrough,
22586
+ stream: true,
22587
+ sdkAgents,
22588
+ passthroughMcp,
22589
+ cleanEnv: profileEnv,
22590
+ envOverrides,
22591
+ hasDeferredTools,
22592
+ resumeSessionId: undefined,
22593
+ isUndo: false,
22594
+ resumeSessionAtUuid: undefined,
22595
+ sdkHooks,
22596
+ blockedTools: pipelineCtx.blockedTools,
22597
+ incompatibleTools: pipelineCtx.incompatibleTools,
22598
+ mcpServerName: adapter.getMcpServerName(),
22599
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
22600
+ onStderr,
22601
+ effort,
22602
+ thinking,
22603
+ taskBudget,
22604
+ outputFormat,
22605
+ betas,
22606
+ settingSources,
22607
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
22608
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
22609
+ memory: sdkFeatures.memory,
22610
+ dreaming: sdkFeatures.dreaming,
22611
+ sharedMemory: sdkFeatures.sharedMemory,
22612
+ webFetchPreflight: sdkFeatures.webFetchPreflight,
22613
+ claudeAiConnectors: sdkFeatures.claudeAiConnectors,
22614
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
22615
+ fallbackModel: sdkFeatures.fallbackModel,
22616
+ sdkDebug: sdkFeatures.sdkDebug,
22617
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22618
+ advisorModel
22619
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "stream_fresh");
22620
+ return;
22621
+ }
22622
+ if (isExpiredTokenError(errMsg) && !tokenRefreshed) {
22623
+ tokenRefreshed = true;
22624
+ const refreshed = profileCredentialStore ? await refreshOAuthToken(profileCredentialStore) : false;
22625
+ if (refreshed) {
22626
+ claudeLog("token_refresh.retrying", { mode: "stream" });
22627
+ plog(`[PROXY] ${requestMeta.requestId} OAuth token expired — refreshed, retrying`);
22628
+ continue;
22629
+ }
22630
+ }
22631
+ if (isRateLimitError(errMsg)) {
22632
+ if (hasExtendedContext(model)) {
22633
+ const from = model;
22634
+ model = stripExtendedContext(model);
22635
+ claudeLog("upstream.context_fallback", {
22636
+ mode: "stream",
22637
+ from,
22638
+ to: model,
22639
+ reason: "rate_limit"
22640
+ });
22641
+ plog(`[PROXY] ${requestMeta.requestId} rate-limited on [1m], retrying with ${model}`);
22642
+ continue;
22643
+ }
22644
+ if (rateLimitRetries < MAX_RATE_LIMIT_RETRIES) {
22645
+ rateLimitRetries++;
22646
+ const delay = RATE_LIMIT_BASE_DELAY_MS * Math.pow(2, rateLimitRetries - 1);
22647
+ claudeLog("upstream.rate_limit_backoff", {
22648
+ mode: "stream",
22649
+ model,
22650
+ attempt: rateLimitRetries,
22651
+ maxAttempts: MAX_RATE_LIMIT_RETRIES,
22652
+ delayMs: delay
22653
+ });
22654
+ plog(`[PROXY] ${requestMeta.requestId} rate-limited on ${model}, retry ${rateLimitRetries}/${MAX_RATE_LIMIT_RETRIES} in ${delay}ms`);
22655
+ await new Promise((r) => setTimeout(r, delay));
22656
+ continue;
22657
+ }
22658
+ }
22659
+ throw error;
22204
22660
  }
22205
- if (isExtraUsageRequiredError(errMsg) && hasExtendedContext(model)) {
22206
- const from = model;
22207
- model = stripExtendedContext(model);
22208
- recordExtendedContextUnavailable();
22209
- claudeLog("upstream.context_fallback", {
22210
- mode: "stream",
22211
- from,
22212
- to: model,
22213
- reason: "extra_usage_required"
22214
- });
22215
- plog(`[PROXY] ${requestMeta.requestId} extra usage required for [1m], falling back to ${model} (skipping [1m] for 1h)`);
22216
- continue;
22661
+ }
22662
+ }();
22663
+ const heartbeat = setInterval(() => {
22664
+ heartbeatCount += 1;
22665
+ try {
22666
+ const payload = encoder.encode(`: ping
22667
+
22668
+ `);
22669
+ if (!safeEnqueue(payload, "heartbeat")) {
22670
+ clearInterval(heartbeat);
22671
+ return;
22217
22672
  }
22218
- if (isExtraUsageRequiredError(errMsg) && resumeSessionId && !didFreshBaseRetry) {
22219
- didFreshBaseRetry = true;
22220
- claudeLog("upstream.session_fallback", {
22221
- mode: "stream",
22222
- model,
22223
- reason: "extra_usage_required_resume"
22224
- });
22225
- plog(`[PROXY] ${requestMeta.requestId} extra usage persisted on resumed ${model}, retrying as fresh session`);
22226
- evictSession(profileSessionId, profileScopedCwd, allMessages);
22227
- sdkUuidMap.length = 0;
22228
- for (let i = 0;i < allMessages.length; i++)
22229
- sdkUuidMap.push(null);
22230
- yield* query(buildQueryOptions({
22231
- prompt: buildFreshPrompt(allMessages, sanitizeOpts),
22232
- model,
22233
- workingDirectory,
22234
- clientWorkingDirectory,
22235
- systemContext,
22236
- claudeExecutable,
22237
- passthrough,
22238
- stream: true,
22239
- sdkAgents,
22240
- passthroughMcp,
22241
- cleanEnv: profileEnv,
22242
- envOverrides,
22243
- hasDeferredTools,
22244
- resumeSessionId: undefined,
22245
- isUndo: false,
22246
- resumeSessionAtUuid: undefined,
22247
- sdkHooks,
22248
- blockedTools: pipelineCtx.blockedTools,
22249
- incompatibleTools: pipelineCtx.incompatibleTools,
22250
- mcpServerName: adapter.getMcpServerName(),
22251
- allowedMcpTools: pipelineCtx.allowedMcpTools,
22252
- onStderr,
22253
- effort,
22254
- thinking,
22255
- taskBudget,
22256
- outputFormat,
22257
- betas,
22258
- settingSources,
22259
- codeSystemPrompt: sdkFeatures.codeSystemPrompt,
22260
- clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
22261
- memory: sdkFeatures.memory,
22262
- dreaming: sdkFeatures.dreaming,
22263
- sharedMemory: sdkFeatures.sharedMemory,
22264
- webFetchPreflight: sdkFeatures.webFetchPreflight,
22265
- claudeAiConnectors: sdkFeatures.claudeAiConnectors,
22266
- maxBudgetUsd: sdkFeatures.maxBudgetUsd,
22267
- fallbackModel: sdkFeatures.fallbackModel,
22268
- sdkDebug: sdkFeatures.sdkDebug,
22269
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22270
- advisorModel
22271
- }, requestAbort.controller));
22272
- return;
22673
+ if (heartbeatCount % 5 === 0) {
22674
+ claudeLog("stream.heartbeat", { count: heartbeatCount });
22273
22675
  }
22274
- if (isExpiredTokenError(errMsg) && !tokenRefreshed) {
22275
- tokenRefreshed = true;
22276
- const refreshed = profileCredentialStore ? await refreshOAuthToken(profileCredentialStore) : false;
22277
- if (refreshed) {
22278
- claudeLog("token_refresh.retrying", { mode: "stream" });
22279
- plog(`[PROXY] ${requestMeta.requestId} OAuth token expired — refreshed, retrying`);
22280
- continue;
22676
+ } catch (error) {
22677
+ claudeLog("stream.heartbeat_failed", {
22678
+ count: heartbeatCount,
22679
+ error: error instanceof Error ? error.message : String(error)
22680
+ });
22681
+ clearInterval(heartbeat);
22682
+ }
22683
+ }, 15000);
22684
+ const skipBlockIndices = new Set;
22685
+ const taskToolBlockIndices = new Set;
22686
+ const taskToolJsonBuffer = new Map;
22687
+ let nextClientBlockIndex = 0;
22688
+ const sdkToClientIndex = new Map;
22689
+ const guardedResponse = guardUpstreamIdle(response, UPSTREAM_IDLE_MS, (sinceLastMs) => claudeLog("upstream.stalled", {
22690
+ mode: "stream",
22691
+ model,
22692
+ sinceLastMs,
22693
+ streamEventsSeen,
22694
+ firstChunkAt: firstChunkAt ?? null
22695
+ }));
22696
+ try {
22697
+ for await (const message of guardedResponse) {
22698
+ if (streamClosed && !awaitingEarlyStopDrain) {
22699
+ break;
22700
+ }
22701
+ if (message.session_id) {
22702
+ currentSessionId = message.session_id;
22703
+ }
22704
+ if (message.type === "assistant" && message.uuid) {
22705
+ sdkUuidMap.push(message.uuid);
22706
+ }
22707
+ nextPassthroughResumeUuid = resumeBoundaryUuid(message) ?? nextPassthroughResumeUuid;
22708
+ if (earlyStopEnabled) {
22709
+ if (message.type === "assistant") {
22710
+ noteAssistantContent(earlyStop, message.message?.content);
22711
+ } else if (message.type === "user") {
22712
+ noteUserContent(earlyStop, message.message?.content);
22713
+ if (shouldEarlyStop(earlyStop) && streamedToolUseIds.size > 0) {
22714
+ if (openClientBlocks.size > 0) {
22715
+ if (!pendingEarlyStop) {
22716
+ pendingEarlyStop = true;
22717
+ pendingEarlyStopAt = Date.now();
22718
+ claudeLog("passthrough.early_stop_deferred", {
22719
+ openBlocks: openClientBlocks.size,
22720
+ captured: capturedToolUses.length
22721
+ });
22722
+ }
22723
+ } else {
22724
+ fireEarlyStop("immediate");
22725
+ break;
22726
+ }
22727
+ }
22281
22728
  }
22282
22729
  }
22283
- if (isRateLimitError(errMsg)) {
22284
- if (hasExtendedContext(model)) {
22285
- const from = model;
22286
- model = stripExtendedContext(model);
22287
- claudeLog("upstream.context_fallback", {
22288
- mode: "stream",
22289
- from,
22290
- to: model,
22291
- reason: "rate_limit"
22292
- });
22293
- plog(`[PROXY] ${requestMeta.requestId} rate-limited on [1m], retrying with ${model}`);
22294
- continue;
22730
+ if (message.type === "result") {
22731
+ const resultUsage = message.usage;
22732
+ if (resultUsage)
22733
+ lastUsage = { ...lastUsage, ...resultUsage };
22734
+ if (outputFormat && "structured_output" in message) {
22735
+ hasStructuredOutput = true;
22736
+ structuredOutput = message.structured_output;
22295
22737
  }
22296
- if (rateLimitRetries < MAX_RATE_LIMIT_RETRIES) {
22297
- rateLimitRetries++;
22298
- const delay = RATE_LIMIT_BASE_DELAY_MS * Math.pow(2, rateLimitRetries - 1);
22299
- claudeLog("upstream.rate_limit_backoff", {
22738
+ }
22739
+ if (message.type === "stream_event") {
22740
+ streamEventsSeen += 1;
22741
+ if (!firstChunkAt) {
22742
+ firstChunkAt = Date.now();
22743
+ requestMeta.ttfbMs ??= firstChunkAt - (requestMeta.currentSdkStartedAt ?? firstChunkAt);
22744
+ claudeLog("upstream.first_chunk", {
22300
22745
  mode: "stream",
22301
22746
  model,
22302
- attempt: rateLimitRetries,
22303
- maxAttempts: MAX_RATE_LIMIT_RETRIES,
22304
- delayMs: delay
22747
+ ttfbMs: requestMeta.ttfbMs
22305
22748
  });
22306
- plog(`[PROXY] ${requestMeta.requestId} rate-limited on ${model}, retry ${rateLimitRetries}/${MAX_RATE_LIMIT_RETRIES} in ${delay}ms`);
22307
- await new Promise((r) => setTimeout(r, delay));
22308
- continue;
22309
22749
  }
22310
- }
22311
- throw error;
22312
- }
22313
- }
22314
- }();
22315
- const heartbeat = setInterval(() => {
22316
- heartbeatCount += 1;
22317
- try {
22318
- const payload = encoder.encode(`: ping
22319
-
22320
- `);
22321
- if (!safeEnqueue(payload, "heartbeat")) {
22322
- clearInterval(heartbeat);
22323
- return;
22324
- }
22325
- if (heartbeatCount % 5 === 0) {
22326
- claudeLog("stream.heartbeat", { count: heartbeatCount });
22327
- }
22328
- } catch (error) {
22329
- claudeLog("stream.heartbeat_failed", {
22330
- count: heartbeatCount,
22331
- error: error instanceof Error ? error.message : String(error)
22332
- });
22333
- clearInterval(heartbeat);
22334
- }
22335
- }, 15000);
22336
- const skipBlockIndices = new Set;
22337
- const taskToolBlockIndices = new Set;
22338
- const taskToolJsonBuffer = new Map;
22339
- let nextClientBlockIndex = 0;
22340
- const sdkToClientIndex = new Map;
22341
- const guardedResponse = guardUpstreamIdle(response, UPSTREAM_IDLE_MS, (sinceLastMs) => claudeLog("upstream.stalled", {
22342
- mode: "stream",
22343
- model,
22344
- sinceLastMs,
22345
- streamEventsSeen,
22346
- firstChunkAt: firstChunkAt ?? null
22347
- }));
22348
- try {
22349
- for await (const message of guardedResponse) {
22350
- if (streamClosed && !awaitingEarlyStopDrain) {
22351
- break;
22352
- }
22353
- if (message.session_id) {
22354
- currentSessionId = message.session_id;
22355
- }
22356
- if (message.type === "assistant" && message.uuid) {
22357
- sdkUuidMap.push(message.uuid);
22358
- }
22359
- nextPassthroughResumeUuid = resumeBoundaryUuid(message) ?? nextPassthroughResumeUuid;
22360
- if (earlyStopEnabled) {
22361
- if (message.type === "assistant") {
22362
- noteAssistantContent(earlyStop, message.message?.content);
22363
- } else if (message.type === "user") {
22364
- noteUserContent(earlyStop, message.message?.content);
22365
- if (shouldEarlyStop(earlyStop) && streamedToolUseIds.size > 0) {
22366
- if (openClientBlocks.size > 0) {
22367
- if (!pendingEarlyStop) {
22368
- pendingEarlyStop = true;
22369
- pendingEarlyStopAt = Date.now();
22370
- claudeLog("passthrough.early_stop_deferred", {
22371
- openBlocks: openClientBlocks.size,
22372
- captured: capturedToolUses.length
22373
- });
22374
- }
22375
- } else {
22376
- fireEarlyStop("immediate");
22377
- break;
22750
+ const event = message.event;
22751
+ const eventType = event.type;
22752
+ const eventIndex = event.index;
22753
+ if (eventType === "message_delta" || eventType === "message_stop" || eventType === "message_start" && messageStartEmitted) {
22754
+ releaseHeldDenies(eventType);
22755
+ }
22756
+ if (eventType === "message_start") {
22757
+ turnGenerating = true;
22758
+ }
22759
+ if (outputFormat) {
22760
+ if (eventType === "message_start") {
22761
+ const startUsage = event.message?.usage;
22762
+ if (startUsage)
22763
+ lastUsage = { ...lastUsage, ...startUsage };
22764
+ } else if (eventType === "message_delta") {
22765
+ const deltaUsage = event.usage;
22766
+ if (deltaUsage)
22767
+ lastUsage = { ...lastUsage, ...deltaUsage };
22378
22768
  }
22769
+ continue;
22379
22770
  }
22380
- }
22381
- }
22382
- if (message.type === "result") {
22383
- const resultUsage = message.usage;
22384
- if (resultUsage)
22385
- lastUsage = { ...lastUsage, ...resultUsage };
22386
- if (outputFormat && "structured_output" in message) {
22387
- hasStructuredOutput = true;
22388
- structuredOutput = message.structured_output;
22389
- }
22390
- }
22391
- if (message.type === "stream_event") {
22392
- streamEventsSeen += 1;
22393
- if (!firstChunkAt) {
22394
- firstChunkAt = Date.now();
22395
- claudeLog("upstream.first_chunk", {
22396
- mode: "stream",
22397
- model,
22398
- ttfbMs: firstChunkAt - upstreamStartAt
22399
- });
22400
- }
22401
- const event = message.event;
22402
- const eventType = event.type;
22403
- const eventIndex = event.index;
22404
- if (eventType === "message_delta" || eventType === "message_stop" || eventType === "message_start" && messageStartEmitted) {
22405
- releaseHeldDenies(eventType);
22406
- }
22407
- if (eventType === "message_start") {
22408
- turnGenerating = true;
22409
- }
22410
- if (outputFormat) {
22411
22771
  if (eventType === "message_start") {
22772
+ skipBlockIndices.clear();
22773
+ sdkToClientIndex.clear();
22412
22774
  const startUsage = event.message?.usage;
22413
22775
  if (startUsage)
22414
22776
  lastUsage = { ...lastUsage, ...startUsage };
22415
- } else if (eventType === "message_delta") {
22416
- const deltaUsage = event.usage;
22417
- if (deltaUsage)
22418
- lastUsage = { ...lastUsage, ...deltaUsage };
22419
- }
22420
- continue;
22421
- }
22422
- if (eventType === "message_start") {
22423
- skipBlockIndices.clear();
22424
- sdkToClientIndex.clear();
22425
- const startUsage = event.message?.usage;
22426
- if (startUsage)
22427
- lastUsage = { ...lastUsage, ...startUsage };
22428
- if (messageStartEmitted) {
22429
- if (passthrough && streamedToolUseIds.size > 0) {
22430
- flushOpenClientBlocks("turn2_suppression");
22431
- sendTerminalDelta("tool_use");
22432
- safeEnqueue(encoder.encode(`event: message_stop
22777
+ if (messageStartEmitted) {
22778
+ if (passthrough && streamedToolUseIds.size > 0) {
22779
+ flushOpenClientBlocks("turn2_suppression");
22780
+ sendTerminalDelta("tool_use");
22781
+ safeEnqueue(encoder.encode(`event: message_stop
22433
22782
  data: ${JSON.stringify({ type: "message_stop" })}
22434
22783
 
22435
22784
  `), "passthrough_turn2_stop");
22436
- claudeLog("passthrough.turn2_suppressed", { mode: "stream", toolUses: streamedToolUseIds.size });
22437
- streamClosed = true;
22438
- controller.close();
22439
- break;
22785
+ claudeLog("passthrough.turn2_suppressed", { mode: "stream", toolUses: streamedToolUseIds.size });
22786
+ streamClosed = true;
22787
+ controller.close();
22788
+ break;
22789
+ }
22790
+ continue;
22440
22791
  }
22441
- continue;
22792
+ messageStartEmitted = true;
22442
22793
  }
22443
- messageStartEmitted = true;
22444
- }
22445
- if (eventType === "message_stop") {
22446
- continue;
22447
- }
22448
- if (eventType === "content_block_start") {
22449
- const block = event.content_block;
22450
- if (pipelineCtx.hidesInternalTools && (block?.type === "tool_use" || (block?.type === "thinking" || block?.type === "redacted_thinking") && !sdkFeatures.thinkingPassthrough)) {
22451
- if (eventIndex !== undefined)
22452
- skipBlockIndices.add(eventIndex);
22453
- claudeLog("internal_tool.hidden", { mode: "stream", type: block?.type, name: block?.name, index: eventIndex });
22794
+ if (eventType === "message_stop") {
22454
22795
  continue;
22455
22796
  }
22456
- if (passthrough && !pipelineCtx.supportsThinking && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
22457
- if (eventIndex !== undefined)
22458
- skipBlockIndices.add(eventIndex);
22459
- claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
22460
- continue;
22461
- }
22462
- if (block?.type === "tool_use" && typeof block.name === "string") {
22463
- if (block.name === "ToolSearch") {
22797
+ if (eventType === "content_block_start") {
22798
+ const block = event.content_block;
22799
+ if (pipelineCtx.hidesInternalTools && (block?.type === "tool_use" || (block?.type === "thinking" || block?.type === "redacted_thinking") && !sdkFeatures.thinkingPassthrough)) {
22464
22800
  if (eventIndex !== undefined)
22465
22801
  skipBlockIndices.add(eventIndex);
22802
+ claudeLog("internal_tool.hidden", { mode: "stream", type: block?.type, name: block?.name, index: eventIndex });
22466
22803
  continue;
22467
22804
  }
22468
- if (passthrough && block.name.startsWith(PASSTHROUGH_MCP_PREFIX)) {
22469
- block.name = stripMcpPrefix(block.name);
22470
- if (block.id)
22471
- streamedToolUseIds.add(block.id);
22472
- } else if (block.name.startsWith("mcp__")) {
22805
+ if (passthrough && !pipelineCtx.supportsThinking && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
22473
22806
  if (eventIndex !== undefined)
22474
22807
  skipBlockIndices.add(eventIndex);
22808
+ claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
22475
22809
  continue;
22476
- } else if (passthrough && block.id) {
22477
- streamedToolUseIds.add(block.id);
22478
22810
  }
22479
- if (passthrough && eventIndex !== undefined && block.name.toLowerCase() === "task") {
22480
- taskToolBlockIndices.add(eventIndex);
22811
+ if (block?.type === "tool_use" && typeof block.name === "string") {
22812
+ if (block.name === "ToolSearch") {
22813
+ if (eventIndex !== undefined)
22814
+ skipBlockIndices.add(eventIndex);
22815
+ continue;
22816
+ }
22817
+ if (passthrough && block.name.startsWith(PASSTHROUGH_MCP_PREFIX)) {
22818
+ block.name = stripMcpPrefix(block.name);
22819
+ if (block.id)
22820
+ streamedToolUseIds.add(block.id);
22821
+ } else if (block.name.startsWith("mcp__")) {
22822
+ if (eventIndex !== undefined)
22823
+ skipBlockIndices.add(eventIndex);
22824
+ continue;
22825
+ } else if (passthrough && block.id) {
22826
+ streamedToolUseIds.add(block.id);
22827
+ }
22828
+ if (passthrough && eventIndex !== undefined && block.name.toLowerCase() === "task") {
22829
+ taskToolBlockIndices.add(eventIndex);
22830
+ }
22831
+ }
22832
+ if (eventIndex !== undefined) {
22833
+ sdkToClientIndex.set(eventIndex, nextClientBlockIndex++);
22481
22834
  }
22482
22835
  }
22483
- if (eventIndex !== undefined) {
22484
- sdkToClientIndex.set(eventIndex, nextClientBlockIndex++);
22485
- }
22486
- }
22487
- if (eventIndex !== undefined && skipBlockIndices.has(eventIndex)) {
22488
- continue;
22489
- }
22490
- if (eventIndex !== undefined && sdkToClientIndex.has(eventIndex)) {
22491
- event.index = sdkToClientIndex.get(eventIndex);
22492
- }
22493
- if (eventType === "message_delta") {
22494
- const deltaUsage = event.usage;
22495
- if (deltaUsage)
22496
- lastUsage = { ...lastUsage, ...deltaUsage };
22497
- const stopReason = event.delta?.stop_reason;
22498
- if (stopReason === "tool_use" && skipBlockIndices.size > 0) {
22836
+ if (eventIndex !== undefined && skipBlockIndices.has(eventIndex)) {
22499
22837
  continue;
22500
22838
  }
22501
- }
22502
- if (passthrough && eventIndex !== undefined && taskToolBlockIndices.has(eventIndex)) {
22503
- if (eventType === "content_block_delta") {
22504
- const delta = event.delta;
22505
- if (delta?.type === "input_json_delta" && typeof delta.partial_json === "string") {
22506
- const prev = taskToolJsonBuffer.get(eventIndex) ?? "";
22507
- taskToolJsonBuffer.set(eventIndex, prev + delta.partial_json);
22839
+ if (eventIndex !== undefined && sdkToClientIndex.has(eventIndex)) {
22840
+ event.index = sdkToClientIndex.get(eventIndex);
22841
+ }
22842
+ if (eventType === "message_delta") {
22843
+ const deltaUsage = event.usage;
22844
+ if (deltaUsage)
22845
+ lastUsage = { ...lastUsage, ...deltaUsage };
22846
+ const stopReason = event.delta?.stop_reason;
22847
+ if (stopReason === "tool_use" && skipBlockIndices.size > 0) {
22508
22848
  continue;
22509
22849
  }
22510
22850
  }
22511
- if (eventType === "content_block_stop") {
22512
- const buffered = taskToolJsonBuffer.get(eventIndex);
22513
- if (buffered) {
22514
- let fixed = buffered;
22515
- try {
22516
- const parsed = JSON.parse(buffered);
22517
- if (typeof parsed.subagent_type === "string") {
22518
- parsed.subagent_type = resolveAgentAlias(parsed.subagent_type, validAgentNames);
22519
- }
22520
- fixed = JSON.stringify(parsed);
22521
- } catch {}
22522
- const clientIdx = sdkToClientIndex.get(eventIndex) ?? eventIndex;
22523
- safeEnqueue(encoder.encode(`event: content_block_delta
22851
+ if (passthrough && eventIndex !== undefined && taskToolBlockIndices.has(eventIndex)) {
22852
+ if (eventType === "content_block_delta") {
22853
+ const delta = event.delta;
22854
+ if (delta?.type === "input_json_delta" && typeof delta.partial_json === "string") {
22855
+ const prev = taskToolJsonBuffer.get(eventIndex) ?? "";
22856
+ taskToolJsonBuffer.set(eventIndex, prev + delta.partial_json);
22857
+ continue;
22858
+ }
22859
+ }
22860
+ if (eventType === "content_block_stop") {
22861
+ const buffered = taskToolJsonBuffer.get(eventIndex);
22862
+ if (buffered) {
22863
+ let fixed = buffered;
22864
+ try {
22865
+ const parsed = JSON.parse(buffered);
22866
+ if (typeof parsed.subagent_type === "string") {
22867
+ parsed.subagent_type = resolveAgentAlias(parsed.subagent_type, validAgentNames);
22868
+ }
22869
+ fixed = JSON.stringify(parsed);
22870
+ } catch {}
22871
+ const clientIdx = sdkToClientIndex.get(eventIndex) ?? eventIndex;
22872
+ safeEnqueue(encoder.encode(`event: content_block_delta
22524
22873
  data: ${JSON.stringify({
22525
- type: "content_block_delta",
22526
- index: clientIdx,
22527
- delta: { type: "input_json_delta", partial_json: fixed }
22528
- })}
22874
+ type: "content_block_delta",
22875
+ index: clientIdx,
22876
+ delta: { type: "input_json_delta", partial_json: fixed }
22877
+ })}
22529
22878
 
22530
22879
  `), "task_tool_fixed_delta");
22531
- taskToolJsonBuffer.delete(eventIndex);
22880
+ taskToolJsonBuffer.delete(eventIndex);
22881
+ }
22532
22882
  }
22533
22883
  }
22534
- }
22535
- if (eventType === "content_block_delta" && event.delta?.type === "text_delta" && shouldInjectSilentTurn({
22536
- raw: env("DEBUG_FORCE_SILENT_TURN"),
22537
- sessionId: agentSessionId
22538
- })) {
22539
- claudeLog("debug.silent_turn_injected", { sessionId: agentSessionId });
22540
- continue;
22541
- }
22542
- stripNonStandardStreamFields(event);
22543
- const payload = encoder.encode(`event: ${eventType}
22884
+ if (eventType === "content_block_delta" && event.delta?.type === "text_delta" && shouldInjectSilentTurn({
22885
+ raw: env("DEBUG_FORCE_SILENT_TURN"),
22886
+ sessionId: agentSessionId
22887
+ })) {
22888
+ claudeLog("debug.silent_turn_injected", { sessionId: agentSessionId });
22889
+ continue;
22890
+ }
22891
+ stripNonStandardStreamFields(event);
22892
+ const payload = encoder.encode(`event: ${eventType}
22544
22893
  data: ${JSON.stringify(event)}
22545
22894
 
22546
22895
  `);
22547
- if (eventType === "message_delta") {
22548
- pendingTerminalDelta = payload;
22549
- } else {
22550
- if (!safeEnqueue(payload, `stream_event:${eventType}`)) {
22551
- break;
22896
+ if (eventType === "message_delta") {
22897
+ pendingTerminalDelta = payload;
22898
+ } else {
22899
+ if (!safeEnqueue(payload, `stream_event:${eventType}`)) {
22900
+ break;
22901
+ }
22902
+ eventsForwarded += 1;
22552
22903
  }
22553
- eventsForwarded += 1;
22554
- }
22555
- if (eventType === "content_block_start") {
22556
- const idx = event.index;
22557
- if (typeof idx === "number")
22558
- openClientBlocks.add(idx);
22559
- } else if (eventType === "content_block_stop") {
22560
- const idx = event.index;
22561
- if (typeof idx === "number")
22562
- openClientBlocks.delete(idx);
22563
- if (pendingEarlyStop && openClientBlocks.size === 0) {
22564
- fireEarlyStop("blocks_closed");
22565
- break;
22904
+ if (eventType === "content_block_start") {
22905
+ const idx = event.index;
22906
+ if (typeof idx === "number")
22907
+ openClientBlocks.add(idx);
22908
+ } else if (eventType === "content_block_stop") {
22909
+ const idx = event.index;
22910
+ if (typeof idx === "number")
22911
+ openClientBlocks.delete(idx);
22912
+ if (pendingEarlyStop && openClientBlocks.size === 0) {
22913
+ fireEarlyStop("blocks_closed");
22914
+ break;
22915
+ }
22566
22916
  }
22567
- }
22568
- if (passthrough && eventType === "message_delta" && event.delta?.stop_reason === "tool_use" && streamedToolUseIds.size > 0) {
22569
- flushOpenClientBlocks("drain_close");
22570
- sendTerminalDelta();
22571
- safeEnqueue(encoder.encode(`event: message_stop
22917
+ if (passthrough && eventType === "message_delta" && event.delta?.stop_reason === "tool_use" && streamedToolUseIds.size > 0) {
22918
+ flushOpenClientBlocks("drain_close");
22919
+ sendTerminalDelta();
22920
+ safeEnqueue(encoder.encode(`event: message_stop
22572
22921
  data: ${JSON.stringify({ type: "message_stop" })}
22573
22922
 
22574
22923
  `), "passthrough_tool_stream_stop");
22575
- streamClosed = true;
22576
- controller.close();
22577
- if (earlyStopEnabled) {
22578
- awaitingEarlyStopDrain = true;
22579
- continue;
22924
+ streamClosed = true;
22925
+ controller.close();
22926
+ if (earlyStopEnabled) {
22927
+ awaitingEarlyStopDrain = true;
22928
+ continue;
22929
+ }
22930
+ break;
22580
22931
  }
22581
- break;
22582
- }
22583
- if (eventType === "content_block_delta") {
22584
- const delta = event.delta;
22585
- if (delta?.type === "text_delta") {
22586
- textEventsForwarded += 1;
22587
- if (typeof delta.text === "string")
22588
- textCharsForwarded += delta.text.length;
22932
+ if (eventType === "content_block_delta") {
22933
+ const delta = event.delta;
22934
+ if (delta?.type === "text_delta") {
22935
+ textEventsForwarded += 1;
22936
+ if (typeof delta.text === "string")
22937
+ textCharsForwarded += delta.text.length;
22938
+ }
22589
22939
  }
22590
22940
  }
22591
22941
  }
22942
+ } finally {
22943
+ clearInterval(heartbeat);
22944
+ releaseHeldDenies("stream_loop_exit");
22592
22945
  }
22593
- } finally {
22594
- clearInterval(heartbeat);
22595
- releaseHeldDenies("stream_loop_exit");
22596
- }
22597
- if (outputFormat) {
22598
- if (!hasStructuredOutput) {
22599
- throw new Error("Structured output was requested but the SDK returned no structured_output result");
22600
- }
22601
- const text = structuredOutputText(structuredOutput);
22602
- const messageId = `msg_${Date.now()}`;
22603
- safeEnqueue(encoder.encode(`event: message_start
22604
- data: ${JSON.stringify({
22605
- type: "message_start",
22606
- message: {
22607
- id: messageId,
22608
- type: "message",
22609
- role: "assistant",
22610
- content: [],
22611
- model: body.model,
22612
- stop_reason: null,
22613
- stop_sequence: null,
22614
- usage: { input_tokens: lastUsage?.input_tokens ?? 0, output_tokens: 0 }
22946
+ if (outputFormat) {
22947
+ if (!hasStructuredOutput) {
22948
+ throw new Error("Structured output was requested but the SDK returned no structured_output result");
22615
22949
  }
22616
- })}
22950
+ const text = structuredOutputText(structuredOutput);
22951
+ const messageId = `msg_${Date.now()}`;
22952
+ safeEnqueue(encoder.encode(`event: message_start
22953
+ data: ${JSON.stringify({
22954
+ type: "message_start",
22955
+ message: {
22956
+ id: messageId,
22957
+ type: "message",
22958
+ role: "assistant",
22959
+ content: [],
22960
+ model: body.model,
22961
+ stop_reason: null,
22962
+ stop_sequence: null,
22963
+ usage: { input_tokens: lastUsage?.input_tokens ?? 0, output_tokens: 0 }
22964
+ }
22965
+ })}
22617
22966
 
22618
22967
  `), "structured_message_start");
22619
- safeEnqueue(encoder.encode(`event: content_block_start
22968
+ safeEnqueue(encoder.encode(`event: content_block_start
22620
22969
  data: ${JSON.stringify({
22621
- type: "content_block_start",
22622
- index: 0,
22623
- content_block: { type: "text", text: "" }
22624
- })}
22970
+ type: "content_block_start",
22971
+ index: 0,
22972
+ content_block: { type: "text", text: "" }
22973
+ })}
22625
22974
 
22626
22975
  `), "structured_block_start");
22627
- safeEnqueue(encoder.encode(`event: content_block_delta
22976
+ safeEnqueue(encoder.encode(`event: content_block_delta
22628
22977
  data: ${JSON.stringify({
22629
- type: "content_block_delta",
22630
- index: 0,
22631
- delta: { type: "text_delta", text }
22632
- })}
22978
+ type: "content_block_delta",
22979
+ index: 0,
22980
+ delta: { type: "text_delta", text }
22981
+ })}
22633
22982
 
22634
22983
  `), "structured_text_delta");
22635
- safeEnqueue(encoder.encode(`event: content_block_stop
22984
+ safeEnqueue(encoder.encode(`event: content_block_stop
22636
22985
  data: ${JSON.stringify({ type: "content_block_stop", index: 0 })}
22637
22986
 
22638
22987
  `), "structured_block_stop");
22639
- safeEnqueue(encoder.encode(`event: message_delta
22988
+ safeEnqueue(encoder.encode(`event: message_delta
22640
22989
  data: ${JSON.stringify({
22641
- type: "message_delta",
22642
- delta: { stop_reason: "end_turn", stop_sequence: null },
22643
- usage: { output_tokens: lastUsage?.output_tokens ?? 0 }
22644
- })}
22990
+ type: "message_delta",
22991
+ delta: { stop_reason: "end_turn", stop_sequence: null },
22992
+ usage: { output_tokens: lastUsage?.output_tokens ?? 0 }
22993
+ })}
22645
22994
 
22646
22995
  `), "structured_message_delta");
22647
- messageStartEmitted = true;
22648
- eventsForwarded += 5;
22649
- textEventsForwarded += 1;
22650
- textCharsForwarded += text.length;
22651
- }
22652
- if (passthrough) {
22653
- recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
22654
- }
22655
- claudeLog("upstream.completed", {
22656
- mode: "stream",
22657
- model,
22658
- durationMs: Date.now() - upstreamStartAt,
22659
- streamEventsSeen,
22660
- eventsForwarded,
22661
- textEventsForwarded
22662
- });
22663
- if (lastUsage)
22664
- logUsage(requestMeta.requestId, lastUsage);
22665
- const sessId = currentSessionId || resumeSessionId;
22666
- if (sessId && discoveredTools.size > 0) {
22667
- if (!sessionDiscoveredTools.has(sessId))
22668
- sessionDiscoveredTools.set(sessId, new Set);
22669
- for (const t of discoveredTools)
22670
- sessionDiscoveredTools.get(sessId).add(t);
22671
- const newNames = [...discoveredTools].join(", ");
22672
- const allNames = [...sessionDiscoveredTools.get(sessId)];
22673
- plog(`[PROXY] ${requestMeta.requestId} discovered=${discoveredTools.size} (${newNames}) session_total=${allNames.length}`);
22674
- }
22675
- if (currentSessionId && !isIndependentSession && !sawDuplicateToolUse) {
22676
- storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, earlyStopFired ? nextPassthroughResumeUuid : null);
22677
- }
22678
- resolvePendingStore();
22679
- const classifyNow = () => classifyTurnOutcome({
22680
- textEvents: textEventsForwarded,
22681
- toolUses: streamedToolUseIds.size,
22682
- blocksForwarded: eventsForwarded
22683
- });
22684
- const preRecoveryOutcome = classifyNow();
22685
- if (!streamClosed && messageStartEmitted && shouldAttemptRecovery({
22686
- outcome: preRecoveryOutcome,
22687
- alreadyAttempted: silentTurnRecoveryAttempted,
22688
- clientGone: streamClosed,
22689
- sessionId: currentSessionId || resumeSessionId,
22690
- enabled: silentTurnRecoveryEnabled
22691
- })) {
22692
- silentTurnRecoveryAttempted = true;
22693
- const capturedBeforeRecovery = capturedToolUses.length;
22694
- claudeLog("response.silent_turn_recovery", {
22996
+ messageStartEmitted = true;
22997
+ eventsForwarded += 5;
22998
+ textEventsForwarded += 1;
22999
+ textCharsForwarded += text.length;
23000
+ }
23001
+ if (passthrough) {
23002
+ recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
23003
+ }
23004
+ claudeLog("upstream.completed", {
22695
23005
  mode: "stream",
22696
- kind: preRecoveryOutcome.kind,
22697
- reason: preRecoveryOutcome.kind === "silent" ? preRecoveryOutcome.reason : undefined,
22698
- sdkSessionId: currentSessionId || resumeSessionId
23006
+ model,
23007
+ durationMs: Date.now() - upstreamStartAt,
23008
+ streamEventsSeen,
23009
+ eventsForwarded,
23010
+ textEventsForwarded
22699
23011
  });
22700
- const recoveryLifter = createRecoveryLifter(() => nextClientBlockIndex++);
22701
- let recoverySessionId;
22702
- let recoveryBoundaryUuid;
22703
- try {
22704
- for await (const event of guardUpstreamIdle(query(buildQueryOptions({
22705
- prompt: SILENT_TURN_NUDGE,
22706
- model,
22707
- workingDirectory,
22708
- clientWorkingDirectory,
22709
- systemContext,
22710
- claudeExecutable,
22711
- passthrough,
22712
- stream: true,
22713
- sdkAgents,
22714
- passthroughMcp,
22715
- cleanEnv: profileEnv,
22716
- envOverrides,
22717
- hasDeferredTools,
22718
- resumeSessionId: currentSessionId || resumeSessionId,
22719
- isUndo: false,
22720
- resumeSessionAtUuid: nextPassthroughResumeUuid,
22721
- forkSession: true,
22722
- sdkHooks,
22723
- blockedTools: pipelineCtx.blockedTools,
22724
- incompatibleTools: pipelineCtx.incompatibleTools,
22725
- mcpServerName: adapter.getMcpServerName(),
22726
- allowedMcpTools: pipelineCtx.allowedMcpTools,
22727
- onStderr,
22728
- effort,
22729
- thinking,
22730
- taskBudget,
22731
- outputFormat,
22732
- betas,
22733
- settingSources,
22734
- codeSystemPrompt: sdkFeatures.codeSystemPrompt,
22735
- clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
22736
- memory: sdkFeatures.memory,
22737
- dreaming: sdkFeatures.dreaming,
22738
- sharedMemory: sdkFeatures.sharedMemory,
22739
- webFetchPreflight: sdkFeatures.webFetchPreflight,
22740
- claudeAiConnectors: sdkFeatures.claudeAiConnectors,
22741
- maxBudgetUsd: sdkFeatures.maxBudgetUsd,
22742
- fallbackModel: sdkFeatures.fallbackModel,
22743
- sdkDebug: sdkFeatures.sdkDebug,
22744
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22745
- advisorModel
22746
- }, requestAbort.controller)), UPSTREAM_IDLE_MS, (sinceLastMs) => claudeLog("upstream.stalled", { mode: "silent_recovery", model, sinceLastMs }))) {
22747
- const recoveryMessage = event;
22748
- if (recoveryMessage.session_id)
22749
- recoverySessionId = recoveryMessage.session_id;
22750
- recoveryBoundaryUuid = resumeBoundaryUuid(recoveryMessage) ?? recoveryBoundaryUuid;
22751
- if (recoveryMessage.type !== "stream_event")
22752
- continue;
22753
- const lifted = recoveryLifter.lift(event.event);
22754
- if (!lifted)
22755
- continue;
22756
- safeEnqueue(encoder.encode(`event: ${lifted.frame.type}
23012
+ if (lastUsage)
23013
+ logUsage(requestMeta.requestId, lastUsage);
23014
+ const sessId = currentSessionId || resumeSessionId;
23015
+ if (sessId && discoveredTools.size > 0) {
23016
+ if (!sessionDiscoveredTools.has(sessId))
23017
+ sessionDiscoveredTools.set(sessId, new Set);
23018
+ for (const t of discoveredTools)
23019
+ sessionDiscoveredTools.get(sessId).add(t);
23020
+ const newNames = [...discoveredTools].join(", ");
23021
+ const allNames = [...sessionDiscoveredTools.get(sessId)];
23022
+ plog(`[PROXY] ${requestMeta.requestId} discovered=${discoveredTools.size} (${newNames}) session_total=${allNames.length}`);
23023
+ }
23024
+ if (currentSessionId && !isIndependentSession && !sawDuplicateToolUse) {
23025
+ storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, earlyStopFired ? nextPassthroughResumeUuid : null);
23026
+ commitSessionTurn();
23027
+ }
23028
+ const classifyNow = () => classifyTurnOutcome({
23029
+ textEvents: textEventsForwarded,
23030
+ toolUses: streamedToolUseIds.size,
23031
+ blocksForwarded: eventsForwarded
23032
+ });
23033
+ const preRecoveryOutcome = classifyNow();
23034
+ if (!streamClosed && messageStartEmitted && shouldAttemptRecovery({
23035
+ outcome: preRecoveryOutcome,
23036
+ alreadyAttempted: silentTurnRecoveryAttempted,
23037
+ clientGone: streamClosed,
23038
+ sessionId: currentSessionId || resumeSessionId,
23039
+ enabled: silentTurnRecoveryEnabled
23040
+ })) {
23041
+ silentTurnRecoveryAttempted = true;
23042
+ const capturedBeforeRecovery = capturedToolUses.length;
23043
+ claudeLog("response.silent_turn_recovery", {
23044
+ mode: "stream",
23045
+ kind: preRecoveryOutcome.kind,
23046
+ reason: preRecoveryOutcome.kind === "silent" ? preRecoveryOutcome.reason : undefined,
23047
+ sdkSessionId: currentSessionId || resumeSessionId
23048
+ });
23049
+ const recoveryLifter = createRecoveryLifter(() => nextClientBlockIndex++);
23050
+ let recoverySessionId;
23051
+ let recoveryBoundaryUuid;
23052
+ try {
23053
+ for await (const event of runSdkQueryAttempt(buildQueryOptions({
23054
+ prompt: SILENT_TURN_NUDGE,
23055
+ model,
23056
+ workingDirectory,
23057
+ clientWorkingDirectory,
23058
+ systemContext,
23059
+ claudeExecutable,
23060
+ passthrough,
23061
+ stream: true,
23062
+ sdkAgents,
23063
+ passthroughMcp,
23064
+ cleanEnv: profileEnv,
23065
+ envOverrides,
23066
+ hasDeferredTools,
23067
+ resumeSessionId: currentSessionId || resumeSessionId,
23068
+ isUndo: false,
23069
+ resumeSessionAtUuid: nextPassthroughResumeUuid,
23070
+ forkSession: true,
23071
+ sdkHooks,
23072
+ blockedTools: pipelineCtx.blockedTools,
23073
+ incompatibleTools: pipelineCtx.incompatibleTools,
23074
+ mcpServerName: adapter.getMcpServerName(),
23075
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
23076
+ onStderr,
23077
+ effort,
23078
+ thinking,
23079
+ taskBudget,
23080
+ outputFormat,
23081
+ betas,
23082
+ settingSources,
23083
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
23084
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
23085
+ memory: sdkFeatures.memory,
23086
+ dreaming: sdkFeatures.dreaming,
23087
+ sharedMemory: sdkFeatures.sharedMemory,
23088
+ webFetchPreflight: sdkFeatures.webFetchPreflight,
23089
+ claudeAiConnectors: sdkFeatures.claudeAiConnectors,
23090
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
23091
+ fallbackModel: sdkFeatures.fallbackModel,
23092
+ sdkDebug: sdkFeatures.sdkDebug,
23093
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
23094
+ advisorModel
23095
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "silent_recovery")) {
23096
+ const recoveryMessage = event;
23097
+ if (recoveryMessage.session_id)
23098
+ recoverySessionId = recoveryMessage.session_id;
23099
+ recoveryBoundaryUuid = resumeBoundaryUuid(recoveryMessage) ?? recoveryBoundaryUuid;
23100
+ if (recoveryMessage.type !== "stream_event")
23101
+ continue;
23102
+ const lifted = recoveryLifter.lift(event.event);
23103
+ if (!lifted)
23104
+ continue;
23105
+ safeEnqueue(encoder.encode(`event: ${lifted.frame.type}
22757
23106
  data: ${JSON.stringify(lifted.frame)}
22758
23107
 
22759
23108
  `), `silent_recovery_${lifted.kind}`);
22760
- if (lifted.kind === "block_start") {
22761
- eventsForwarded += 1;
22762
- } else if (lifted.kind === "text_delta") {
22763
- textEventsForwarded += 1;
22764
- textCharsForwarded += lifted.textChars;
22765
- silentTurnRecovered = true;
23109
+ if (lifted.kind === "block_start") {
23110
+ eventsForwarded += 1;
23111
+ } else if (lifted.kind === "text_delta") {
23112
+ textEventsForwarded += 1;
23113
+ textCharsForwarded += lifted.textChars;
23114
+ silentTurnRecovered = true;
23115
+ }
22766
23116
  }
23117
+ } catch (recoveryError) {
23118
+ claudeLog("response.silent_turn_recovery_failed", {
23119
+ mode: "stream",
23120
+ error: recoveryError instanceof Error ? recoveryError.message : String(recoveryError)
23121
+ });
23122
+ }
23123
+ if (capturedToolUses.length > capturedBeforeRecovery) {
23124
+ silentTurnRecovered = true;
22767
23125
  }
22768
- } catch (recoveryError) {
22769
- claudeLog("response.silent_turn_recovery_failed", {
23126
+ if (silentTurnRecovered && recoverySessionId && !isIndependentSession && !sawDuplicateToolUse) {
23127
+ currentSessionId = recoverySessionId;
23128
+ nextPassthroughResumeUuid = recoveryBoundaryUuid;
23129
+ sdkUuidMap.length = 0;
23130
+ for (let i = 0;i < allMessages.length; i++)
23131
+ sdkUuidMap.push(null);
23132
+ storeSession(profileSessionId, body.messages || [], recoverySessionId, profileScopedCwd, sdkUuidMap, lastUsage, recoveryBoundaryUuid ?? null);
23133
+ commitSessionTurn();
23134
+ }
23135
+ claudeLog("response.silent_turn_recovery_result", {
22770
23136
  mode: "stream",
22771
- error: recoveryError instanceof Error ? recoveryError.message : String(recoveryError)
23137
+ recovered: silentTurnRecovered,
23138
+ textEvents: textEventsForwarded,
23139
+ forkedSession: recoverySessionId ?? null
22772
23140
  });
23141
+ if (silentTurnRecovered && preRecoveryOutcome.kind === "silent") {
23142
+ diagnosticLog2.session(`${requestMeta.requestId} silent_turn reason=${preRecoveryOutcome.reason} ` + `blocks=${eventsForwarded} out=${lastUsage?.output_tokens ?? 0} ` + `recovery=succeeded`, requestMeta.requestId);
23143
+ }
22773
23144
  }
22774
- if (capturedToolUses.length > capturedBeforeRecovery) {
22775
- silentTurnRecovered = true;
22776
- }
22777
- if (silentTurnRecovered && recoverySessionId && !isIndependentSession && !sawDuplicateToolUse) {
22778
- currentSessionId = recoverySessionId;
22779
- nextPassthroughResumeUuid = recoveryBoundaryUuid;
22780
- sdkUuidMap.length = 0;
22781
- for (let i = 0;i < allMessages.length; i++)
22782
- sdkUuidMap.push(null);
22783
- storeSession(profileSessionId, body.messages || [], recoverySessionId, profileScopedCwd, sdkUuidMap, lastUsage, recoveryBoundaryUuid ?? null);
22784
- }
22785
- claudeLog("response.silent_turn_recovery_result", {
22786
- mode: "stream",
22787
- recovered: silentTurnRecovered,
22788
- textEvents: textEventsForwarded,
22789
- forkedSession: recoverySessionId ?? null
22790
- });
22791
- if (silentTurnRecovered && preRecoveryOutcome.kind === "silent") {
22792
- diagnosticLog2.session(`${requestMeta.requestId} silent_turn reason=${preRecoveryOutcome.reason} ` + `blocks=${eventsForwarded} out=${lastUsage?.output_tokens ?? 0} ` + `recovery=succeeded`, requestMeta.requestId);
22793
- }
22794
- }
22795
- if (!streamClosed) {
22796
- const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
22797
- if (passthrough && unseenToolUses.length > 0 && messageStartEmitted) {
22798
- for (let i = 0;i < unseenToolUses.length; i++) {
22799
- const tu = unseenToolUses[i];
22800
- const blockIndex = eventsForwarded + i;
22801
- streamedToolUseIds.add(tu.id);
22802
- safeEnqueue(encoder.encode(`event: content_block_start
23145
+ if (!streamClosed) {
23146
+ const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
23147
+ if (passthrough && unseenToolUses.length > 0 && messageStartEmitted) {
23148
+ for (let i = 0;i < unseenToolUses.length; i++) {
23149
+ const tu = unseenToolUses[i];
23150
+ const blockIndex = eventsForwarded + i;
23151
+ streamedToolUseIds.add(tu.id);
23152
+ safeEnqueue(encoder.encode(`event: content_block_start
22803
23153
  data: ${JSON.stringify({
22804
- type: "content_block_start",
22805
- index: blockIndex,
22806
- content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
22807
- })}
23154
+ type: "content_block_start",
23155
+ index: blockIndex,
23156
+ content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
23157
+ })}
22808
23158
 
22809
23159
  `), "passthrough_tool_block_start");
22810
- safeEnqueue(encoder.encode(`event: content_block_delta
23160
+ safeEnqueue(encoder.encode(`event: content_block_delta
22811
23161
  data: ${JSON.stringify({
22812
- type: "content_block_delta",
22813
- index: blockIndex,
22814
- delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
22815
- })}
23162
+ type: "content_block_delta",
23163
+ index: blockIndex,
23164
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
23165
+ })}
22816
23166
 
22817
23167
  `), "passthrough_tool_input");
22818
- safeEnqueue(encoder.encode(`event: content_block_stop
23168
+ safeEnqueue(encoder.encode(`event: content_block_stop
22819
23169
  data: ${JSON.stringify({
22820
- type: "content_block_stop",
22821
- index: blockIndex
22822
- })}
23170
+ type: "content_block_stop",
23171
+ index: blockIndex
23172
+ })}
22823
23173
 
22824
23174
  `), "passthrough_tool_block_stop");
23175
+ }
23176
+ sendTerminalDelta("tool_use");
22825
23177
  }
22826
- sendTerminalDelta("tool_use");
22827
- }
22828
- if (trackFileChanges && passthrough && pipelineCtx.extractFileChangesFromToolUse) {
22829
- const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
22830
- fileChanges.push(...passthroughChanges);
22831
- }
22832
- if (trackFileChanges) {
22833
- const streamFileChangeSummary = formatFileChangeSummary(fileChanges);
22834
- if (streamFileChangeSummary && messageStartEmitted) {
22835
- const fcBlockIndex = nextClientBlockIndex++;
22836
- safeEnqueue(encoder.encode(`event: content_block_start
23178
+ if (trackFileChanges && passthrough && pipelineCtx.extractFileChangesFromToolUse) {
23179
+ const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
23180
+ fileChanges.push(...passthroughChanges);
23181
+ }
23182
+ if (trackFileChanges) {
23183
+ const streamFileChangeSummary = formatFileChangeSummary(fileChanges);
23184
+ if (streamFileChangeSummary && messageStartEmitted) {
23185
+ const fcBlockIndex = nextClientBlockIndex++;
23186
+ safeEnqueue(encoder.encode(`event: content_block_start
22837
23187
  data: ${JSON.stringify({
22838
- type: "content_block_start",
22839
- index: fcBlockIndex,
22840
- content_block: { type: "text", text: "" }
22841
- })}
23188
+ type: "content_block_start",
23189
+ index: fcBlockIndex,
23190
+ content_block: { type: "text", text: "" }
23191
+ })}
22842
23192
 
22843
23193
  `), "file_changes_block_start");
22844
- safeEnqueue(encoder.encode(`event: content_block_delta
23194
+ safeEnqueue(encoder.encode(`event: content_block_delta
22845
23195
  data: ${JSON.stringify({
22846
- type: "content_block_delta",
22847
- index: fcBlockIndex,
22848
- delta: { type: "text_delta", text: streamFileChangeSummary }
22849
- })}
23196
+ type: "content_block_delta",
23197
+ index: fcBlockIndex,
23198
+ delta: { type: "text_delta", text: streamFileChangeSummary }
23199
+ })}
22850
23200
 
22851
23201
  `), "file_changes_text_delta");
22852
- safeEnqueue(encoder.encode(`event: content_block_stop
23202
+ safeEnqueue(encoder.encode(`event: content_block_stop
22853
23203
  data: ${JSON.stringify({
22854
- type: "content_block_stop",
22855
- index: fcBlockIndex
22856
- })}
23204
+ type: "content_block_stop",
23205
+ index: fcBlockIndex
23206
+ })}
22857
23207
 
22858
23208
  `), "file_changes_block_stop");
22859
- claudeLog("response.file_changes", { mode: "stream", count: fileChanges.length });
23209
+ claudeLog("response.file_changes", { mode: "stream", count: fileChanges.length });
23210
+ }
22860
23211
  }
22861
- }
22862
- if (messageStartEmitted) {
22863
- sendTerminalDelta();
22864
- safeEnqueue(encoder.encode(`event: message_stop
23212
+ if (messageStartEmitted) {
23213
+ sendTerminalDelta();
23214
+ safeEnqueue(encoder.encode(`event: message_stop
22865
23215
  data: {"type":"message_stop"}
22866
23216
 
22867
23217
  `), "final_message_stop");
23218
+ }
23219
+ try {
23220
+ controller.close();
23221
+ } catch {}
23222
+ streamClosed = true;
23223
+ claudeLog("stream.ended", {
23224
+ model,
23225
+ streamEventsSeen,
23226
+ eventsForwarded,
23227
+ textEventsForwarded,
23228
+ bytesSent,
23229
+ durationMs: Date.now() - requestStartAt
23230
+ });
22868
23231
  }
22869
- try {
22870
- controller.close();
22871
- } catch {}
22872
- streamClosed = true;
22873
- claudeLog("stream.ended", {
22874
- model,
22875
- streamEventsSeen,
22876
- eventsForwarded,
22877
- textEventsForwarded,
22878
- bytesSent,
22879
- durationMs: Date.now() - requestStartAt
22880
- });
22881
- }
22882
- {
22883
- const streamTotalDurationMs = Date.now() - requestStartAt;
22884
- claudeLog("response.completed", {
22885
- mode: "stream",
22886
- model,
22887
- durationMs: streamTotalDurationMs,
22888
- streamEventsSeen,
22889
- eventsForwarded,
22890
- textEventsForwarded
22891
- });
22892
- const streamQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
22893
- checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
22894
- telemetryStore2.record({
22895
- requestId: requestMeta.requestId,
22896
- timestamp: Date.now(),
22897
- adapter: adapter.name,
22898
- profileId: profile.id,
22899
- requestSource,
22900
- model,
22901
- requestModel: body.model || undefined,
22902
- mode: "stream",
22903
- isResume,
22904
- isPassthrough: passthrough,
22905
- hasDeferredTools,
22906
- deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
22907
- toolCount,
22908
- discoveredTools: discoveredTools.size > 0 ? [...discoveredTools] : undefined,
22909
- sessionDiscoveredCount: sessionDiscoveredTools.get(currentSessionId || resumeSessionId || "")?.size,
22910
- lineageType,
22911
- messageCount: allMessages.length,
22912
- sdkSessionId: currentSessionId || resumeSessionId,
22913
- status: 200,
22914
- queueWaitMs: streamQueueWaitMs,
22915
- proxyOverheadMs: upstreamStartAt - requestStartAt - streamQueueWaitMs,
22916
- ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
22917
- upstreamDurationMs: Date.now() - upstreamStartAt,
22918
- totalDurationMs: streamTotalDurationMs,
22919
- contentBlocks: eventsForwarded,
22920
- textEvents: textEventsForwarded,
22921
- error: null,
22922
- inputTokens: lastUsage?.input_tokens,
22923
- outputTokens: lastUsage?.output_tokens,
22924
- cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
22925
- cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
22926
- cacheHitRate: computeCacheHitRate(lastUsage),
22927
- ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
22928
- });
22929
- const turnOutcome = classifyNow();
22930
- if (turnOutcome.kind === "silent") {
22931
- claudeLog("response.silent_turn", {
23232
+ {
23233
+ const streamTotalDurationMs = Date.now() - requestStartAt;
23234
+ claudeLog("response.completed", {
23235
+ mode: "stream",
22932
23236
  model,
22933
- reason: turnOutcome.reason,
23237
+ durationMs: streamTotalDurationMs,
22934
23238
  streamEventsSeen,
22935
23239
  eventsForwarded,
23240
+ textEventsForwarded
23241
+ });
23242
+ const streamQueueWaitMs = totalQueueWaitMs(requestMeta);
23243
+ checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
23244
+ telemetryStore2.record({
23245
+ requestId: requestMeta.requestId,
23246
+ timestamp: Date.now(),
23247
+ adapter: adapter.name,
23248
+ profileId: profile.id,
23249
+ requestSource,
23250
+ model,
23251
+ requestModel: body.model || undefined,
23252
+ mode: "stream",
23253
+ isResume,
23254
+ isPassthrough: passthrough,
23255
+ hasDeferredTools,
23256
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
23257
+ toolCount,
23258
+ discoveredTools: discoveredTools.size > 0 ? [...discoveredTools] : undefined,
23259
+ sessionDiscoveredCount: sessionDiscoveredTools.get(currentSessionId || resumeSessionId || "")?.size,
23260
+ lineageType,
23261
+ messageCount: allMessages.length,
23262
+ sdkSessionId: currentSessionId || resumeSessionId,
23263
+ status: 200,
23264
+ queueWaitMs: streamQueueWaitMs,
23265
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23266
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23267
+ proxyOverheadMs: Math.max(0, streamTotalDurationMs - streamQueueWaitMs - requestMeta.sdkActiveDurationMs),
23268
+ ttfbMs: requestMeta.ttfbMs ?? null,
23269
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23270
+ totalDurationMs: streamTotalDurationMs,
23271
+ contentBlocks: eventsForwarded,
23272
+ textEvents: textEventsForwarded,
23273
+ error: null,
23274
+ inputTokens: lastUsage?.input_tokens,
22936
23275
  outputTokens: lastUsage?.output_tokens,
22937
- recovered: silentTurnRecovered,
22938
- recoveryAttempted: silentTurnRecoveryAttempted
23276
+ cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
23277
+ cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
23278
+ cacheHitRate: computeCacheHitRate(lastUsage),
23279
+ ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
22939
23280
  });
22940
- diagnosticLog2.session(`${requestMeta.requestId} silent_turn reason=${turnOutcome.reason} ` + `blocks=${eventsForwarded} out=${lastUsage?.output_tokens ?? 0} ` + `recovery=${silentTurnRecoveryAttempted ? silentTurnRecovered ? "succeeded" : "failed" : "off"}`, requestMeta.requestId);
23281
+ const turnOutcome = classifyNow();
23282
+ if (turnOutcome.kind === "silent") {
23283
+ claudeLog("response.silent_turn", {
23284
+ model,
23285
+ reason: turnOutcome.reason,
23286
+ streamEventsSeen,
23287
+ eventsForwarded,
23288
+ outputTokens: lastUsage?.output_tokens,
23289
+ recovered: silentTurnRecovered,
23290
+ recoveryAttempted: silentTurnRecoveryAttempted
23291
+ });
23292
+ diagnosticLog2.session(`${requestMeta.requestId} silent_turn reason=${turnOutcome.reason} ` + `blocks=${eventsForwarded} out=${lastUsage?.output_tokens ?? 0} ` + `recovery=${silentTurnRecoveryAttempted ? silentTurnRecovered ? "succeeded" : "failed" : "off"}`, requestMeta.requestId);
23293
+ }
22941
23294
  }
22942
- }
22943
- } catch (error) {
22944
- if (isClosedControllerError(error)) {
22945
- streamClosed = true;
22946
- claudeLog("stream.client_closed", {
22947
- source: "stream_catch",
22948
- streamEventsSeen,
22949
- eventsForwarded,
22950
- textEventsForwarded,
22951
- durationMs: Date.now() - requestStartAt
22952
- });
22953
- const disposition = clientAbortDisposition({
22954
- isIndependentSession,
22955
- profileSessionId,
22956
- currentSessionId,
22957
- sawDuplicateToolUse,
22958
- resumeBoundaryUuid: nextPassthroughResumeUuid,
22959
- passthrough
22960
- });
22961
- if (disposition.action === "store" && currentSessionId) {
22962
- storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, disposition.resumeUuid);
22963
- } else if (disposition.action === "evict") {
22964
- evictSession(profileSessionId, profileScopedCwd, body.messages || []);
23295
+ } catch (error) {
23296
+ if (isClosedControllerError(error)) {
23297
+ streamClosed = true;
23298
+ claudeLog("stream.client_closed", {
23299
+ source: "stream_catch",
23300
+ streamEventsSeen,
23301
+ eventsForwarded,
23302
+ textEventsForwarded,
23303
+ durationMs: Date.now() - requestStartAt
23304
+ });
23305
+ const disposition = clientAbortDisposition({
23306
+ isIndependentSession,
23307
+ profileSessionId,
23308
+ currentSessionId,
23309
+ sawDuplicateToolUse,
23310
+ resumeBoundaryUuid: nextPassthroughResumeUuid,
23311
+ passthrough
23312
+ });
23313
+ if (disposition.action === "store" && currentSessionId) {
23314
+ storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, disposition.resumeUuid);
23315
+ commitSessionTurn();
23316
+ } else if (disposition.action === "evict") {
23317
+ evictSession(profileSessionId, profileScopedCwd, body.messages || []);
23318
+ }
23319
+ claudeLog("passthrough.client_abort_settled", { action: disposition.action });
23320
+ return;
22965
23321
  }
22966
- claudeLog("passthrough.client_abort_settled", { action: disposition.action });
22967
- resolvePendingStore();
22968
- return;
22969
- }
22970
- resolvePendingStore();
22971
- const stderrOutput = stderrLines.join(`
23322
+ const stderrOutput = stderrLines.join(`
22972
23323
  `).trim();
22973
- if (stderrOutput && error instanceof Error && !error.message.includes(stderrOutput)) {
22974
- error.message = `${error.message}
23324
+ if (stderrOutput && error instanceof Error && !error.message.includes(stderrOutput)) {
23325
+ error.message = `${error.message}
22975
23326
  Subprocess stderr: ${stderrOutput}`;
22976
- }
22977
- const errMsg = error instanceof Error ? error.message : String(error);
22978
- claudeLog("upstream.failed", {
22979
- mode: "stream",
22980
- model,
22981
- durationMs: Date.now() - upstreamStartAt,
22982
- streamEventsSeen,
22983
- textEventsForwarded,
22984
- error: errMsg,
22985
- ...stderrOutput ? { stderr: stderrOutput } : {}
22986
- });
22987
- const streamErr = error instanceof UpstreamIdleError ? {
22988
- status: 504,
22989
- type: "upstream_timeout",
22990
- message: `Upstream stalled: no data for ${error.sinceLastMs}ms`
22991
- } : classifyError(errMsg, model);
22992
- claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
22993
- const sdkTerm = extractSdkTermination(errMsg);
22994
- const canRecoverAsToolUse = canRecoverCapturedToolUses({
22995
- reason: sdkTerm.reason,
22996
- passthrough,
22997
- capturedToolUses: capturedToolUses.length,
22998
- abortIsOurs: sawDuplicateToolUse || earlyStopFired
22999
- }) && messageStartEmitted;
23000
- if (canRecoverAsToolUse) {
23001
- diagnosticLog2.session(`${requestMeta.requestId} sdk_termination_recovered ${formatSdkTermination(sdkTerm, {
23327
+ }
23328
+ const errMsg = error instanceof Error ? error.message : String(error);
23329
+ claudeLog("upstream.failed", {
23330
+ mode: "stream",
23002
23331
  model,
23003
- requestSource,
23004
- isResume,
23005
- hasDeferredTools,
23006
- sdkSessionId: resumeSessionId
23007
- })} captured=${capturedToolUses.length}`, requestMeta.requestId);
23008
- flushOpenClientBlocks("recovery");
23009
- const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
23010
- for (let i = 0;i < unseenToolUses.length; i++) {
23011
- const tu = unseenToolUses[i];
23012
- const blockIndex = eventsForwarded + i;
23013
- streamedToolUseIds.add(tu.id);
23014
- safeEnqueue(encoder.encode(`event: content_block_start
23332
+ durationMs: Date.now() - upstreamStartAt,
23333
+ streamEventsSeen,
23334
+ textEventsForwarded,
23335
+ error: errMsg,
23336
+ ...stderrOutput ? { stderr: stderrOutput } : {}
23337
+ });
23338
+ const streamErr = error instanceof UpstreamIdleError ? {
23339
+ status: 504,
23340
+ type: "upstream_timeout",
23341
+ message: `Upstream stalled: no data for ${error.sinceLastMs}ms`
23342
+ } : classifyError(errMsg, model);
23343
+ claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
23344
+ const sdkTerm = extractSdkTermination(errMsg);
23345
+ const canRecoverAsToolUse = canRecoverCapturedToolUses({
23346
+ reason: sdkTerm.reason,
23347
+ passthrough,
23348
+ capturedToolUses: capturedToolUses.length,
23349
+ abortIsOurs: sawDuplicateToolUse || earlyStopFired
23350
+ }) && messageStartEmitted;
23351
+ if (canRecoverAsToolUse) {
23352
+ diagnosticLog2.session(`${requestMeta.requestId} sdk_termination_recovered ${formatSdkTermination(sdkTerm, {
23353
+ model,
23354
+ requestSource,
23355
+ isResume,
23356
+ hasDeferredTools,
23357
+ sdkSessionId: resumeSessionId
23358
+ })} captured=${capturedToolUses.length}`, requestMeta.requestId);
23359
+ flushOpenClientBlocks("recovery");
23360
+ const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
23361
+ for (let i = 0;i < unseenToolUses.length; i++) {
23362
+ const tu = unseenToolUses[i];
23363
+ const blockIndex = eventsForwarded + i;
23364
+ streamedToolUseIds.add(tu.id);
23365
+ safeEnqueue(encoder.encode(`event: content_block_start
23015
23366
  data: ${JSON.stringify({
23016
- type: "content_block_start",
23017
- index: blockIndex,
23018
- content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
23019
- })}
23367
+ type: "content_block_start",
23368
+ index: blockIndex,
23369
+ content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
23370
+ })}
23020
23371
 
23021
23372
  `), "recover_tool_block_start");
23022
- safeEnqueue(encoder.encode(`event: content_block_delta
23373
+ safeEnqueue(encoder.encode(`event: content_block_delta
23023
23374
  data: ${JSON.stringify({
23024
- type: "content_block_delta",
23025
- index: blockIndex,
23026
- delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
23027
- })}
23375
+ type: "content_block_delta",
23376
+ index: blockIndex,
23377
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
23378
+ })}
23028
23379
 
23029
23380
  `), "recover_tool_input");
23030
- safeEnqueue(encoder.encode(`event: content_block_stop
23381
+ safeEnqueue(encoder.encode(`event: content_block_stop
23031
23382
  data: ${JSON.stringify({
23032
- type: "content_block_stop",
23033
- index: blockIndex
23034
- })}
23383
+ type: "content_block_stop",
23384
+ index: blockIndex
23385
+ })}
23035
23386
 
23036
23387
  `), "recover_tool_block_stop");
23037
- }
23038
- safeEnqueue(encoder.encode(`event: message_delta
23388
+ }
23389
+ safeEnqueue(encoder.encode(`event: message_delta
23039
23390
  data: ${JSON.stringify({
23040
- type: "message_delta",
23041
- delta: { stop_reason: "tool_use", stop_sequence: null },
23042
- usage: { output_tokens: 0 }
23043
- })}
23391
+ type: "message_delta",
23392
+ delta: { stop_reason: "tool_use", stop_sequence: null },
23393
+ usage: { output_tokens: 0 }
23394
+ })}
23044
23395
 
23045
23396
  `), "recover_message_delta");
23046
- safeEnqueue(encoder.encode(`event: message_stop
23397
+ safeEnqueue(encoder.encode(`event: message_stop
23047
23398
  data: {"type":"message_stop"}
23048
23399
 
23049
23400
  `), "recover_message_stop");
23050
- recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
23051
- const recoverTotalMs = Date.now() - requestStartAt;
23052
- const recoverQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
23401
+ recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
23402
+ const recoverTotalMs = Date.now() - requestStartAt;
23403
+ const recoverQueueWaitMs = totalQueueWaitMs(requestMeta);
23404
+ telemetryStore2.record({
23405
+ requestId: requestMeta.requestId,
23406
+ timestamp: Date.now(),
23407
+ adapter: adapter.name,
23408
+ profileId: profile.id,
23409
+ requestSource,
23410
+ model,
23411
+ requestModel: body.model || undefined,
23412
+ mode: "stream",
23413
+ isResume,
23414
+ isPassthrough: passthrough,
23415
+ hasDeferredTools,
23416
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
23417
+ toolCount,
23418
+ lineageType,
23419
+ messageCount: allMessages.length,
23420
+ sdkSessionId: resumeSessionId,
23421
+ status: 200,
23422
+ queueWaitMs: recoverQueueWaitMs,
23423
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23424
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23425
+ proxyOverheadMs: Math.max(0, recoverTotalMs - recoverQueueWaitMs - requestMeta.sdkActiveDurationMs),
23426
+ ttfbMs: requestMeta.ttfbMs ?? null,
23427
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23428
+ totalDurationMs: recoverTotalMs,
23429
+ contentBlocks: eventsForwarded + unseenToolUses.length,
23430
+ textEvents: textEventsForwarded,
23431
+ error: null,
23432
+ ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
23433
+ });
23434
+ if (!streamClosed) {
23435
+ try {
23436
+ controller.close();
23437
+ } catch {}
23438
+ streamClosed = true;
23439
+ }
23440
+ return;
23441
+ }
23442
+ diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
23443
+ model,
23444
+ requestSource,
23445
+ isResume,
23446
+ hasDeferredTools,
23447
+ sdkSessionId: resumeSessionId
23448
+ })}`, requestMeta.requestId);
23449
+ const streamErrTotalMs = Date.now() - requestStartAt;
23450
+ const streamErrQueueWaitMs = totalQueueWaitMs(requestMeta);
23053
23451
  telemetryStore2.record({
23054
23452
  requestId: requestMeta.requestId,
23055
23453
  timestamp: Date.now(),
@@ -23067,106 +23465,66 @@ data: {"type":"message_stop"}
23067
23465
  lineageType,
23068
23466
  messageCount: allMessages.length,
23069
23467
  sdkSessionId: resumeSessionId,
23070
- status: 200,
23071
- queueWaitMs: recoverQueueWaitMs,
23072
- proxyOverheadMs: upstreamStartAt - requestStartAt - recoverQueueWaitMs,
23073
- ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
23074
- upstreamDurationMs: Date.now() - upstreamStartAt,
23075
- totalDurationMs: recoverTotalMs,
23076
- contentBlocks: eventsForwarded + unseenToolUses.length,
23077
- textEvents: textEventsForwarded,
23078
- error: null,
23079
- ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
23080
- });
23081
- if (!streamClosed) {
23082
- try {
23083
- controller.close();
23084
- } catch {}
23085
- streamClosed = true;
23086
- }
23087
- return;
23088
- }
23089
- diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
23090
- model,
23091
- requestSource,
23092
- isResume,
23093
- hasDeferredTools,
23094
- sdkSessionId: resumeSessionId
23095
- })}`, requestMeta.requestId);
23096
- const streamErrTotalMs = Date.now() - requestStartAt;
23097
- const streamErrQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
23098
- telemetryStore2.record({
23099
- requestId: requestMeta.requestId,
23100
- timestamp: Date.now(),
23101
- adapter: adapter.name,
23102
- profileId: profile.id,
23103
- requestSource,
23104
- model,
23105
- requestModel: body.model || undefined,
23106
- mode: "stream",
23107
- isResume,
23108
- isPassthrough: passthrough,
23109
- hasDeferredTools,
23110
- deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
23111
- toolCount,
23112
- lineageType,
23113
- messageCount: allMessages.length,
23114
- sdkSessionId: resumeSessionId,
23115
- status: streamErr.status,
23116
- queueWaitMs: streamErrQueueWaitMs,
23117
- proxyOverheadMs: upstreamStartAt - requestStartAt - streamErrQueueWaitMs,
23118
- ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
23119
- upstreamDurationMs: Date.now() - upstreamStartAt,
23120
- totalDurationMs: streamErrTotalMs,
23121
- contentBlocks: eventsForwarded,
23122
- textEvents: textEventsForwarded,
23123
- error: streamErr.type
23124
- });
23125
- if (messageStartEmitted) {
23126
- const errorStopReason = "max_tokens";
23127
- claudeLog("response.error_envelope", {
23128
- mode: "stream",
23129
- stopReason: errorStopReason,
23468
+ status: streamErr.status,
23469
+ queueWaitMs: streamErrQueueWaitMs,
23470
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23471
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23472
+ proxyOverheadMs: Math.max(0, streamErrTotalMs - streamErrQueueWaitMs - requestMeta.sdkActiveDurationMs),
23473
+ ttfbMs: requestMeta.ttfbMs ?? null,
23474
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23475
+ totalDurationMs: streamErrTotalMs,
23476
+ contentBlocks: eventsForwarded,
23130
23477
  textEvents: textEventsForwarded,
23131
- classified: streamErr.type
23478
+ error: streamErr.type
23132
23479
  });
23133
- safeEnqueue(encoder.encode(`event: message_delta
23480
+ if (messageStartEmitted) {
23481
+ const errorStopReason = "max_tokens";
23482
+ claudeLog("response.error_envelope", {
23483
+ mode: "stream",
23484
+ stopReason: errorStopReason,
23485
+ textEvents: textEventsForwarded,
23486
+ classified: streamErr.type
23487
+ });
23488
+ safeEnqueue(encoder.encode(`event: message_delta
23134
23489
  data: ${JSON.stringify({
23135
- type: "message_delta",
23136
- delta: { stop_reason: errorStopReason, stop_sequence: null },
23137
- usage: { output_tokens: 0 }
23138
- })}
23490
+ type: "message_delta",
23491
+ delta: { stop_reason: errorStopReason, stop_sequence: null },
23492
+ usage: { output_tokens: 0 }
23493
+ })}
23139
23494
 
23140
23495
  `), "error_message_delta");
23141
- safeEnqueue(encoder.encode(`event: error
23496
+ safeEnqueue(encoder.encode(`event: error
23142
23497
  data: ${JSON.stringify({
23143
- type: "error",
23144
- error: { type: streamErr.type, message: streamErr.message }
23145
- })}
23498
+ type: "error",
23499
+ error: { type: streamErr.type, message: streamErr.message }
23500
+ })}
23146
23501
 
23147
23502
  `), "error_event_before_stop");
23148
- safeEnqueue(encoder.encode(`event: message_stop
23503
+ safeEnqueue(encoder.encode(`event: message_stop
23149
23504
  data: {"type":"message_stop"}
23150
23505
 
23151
23506
  `), "error_message_stop");
23152
- } else {
23153
- safeEnqueue(encoder.encode(`event: error
23507
+ } else {
23508
+ safeEnqueue(encoder.encode(`event: error
23154
23509
  data: ${JSON.stringify({
23155
- type: "error",
23156
- error: { type: streamErr.type, message: streamErr.message }
23157
- })}
23510
+ type: "error",
23511
+ error: { type: streamErr.type, message: streamErr.message }
23512
+ })}
23158
23513
 
23159
23514
  `), "error_event");
23515
+ }
23516
+ if (!streamClosed) {
23517
+ try {
23518
+ controller.close();
23519
+ } catch {}
23520
+ streamClosed = true;
23521
+ }
23522
+ } finally {
23523
+ requestAbort.detach();
23160
23524
  }
23161
- if (!streamClosed) {
23162
- try {
23163
- controller.close();
23164
- } catch {}
23165
- streamClosed = true;
23166
- }
23167
- } finally {
23168
- requestAbort.detach();
23169
- }
23525
+ })().finally(() => {
23526
+ resolveStreamCompletion();
23527
+ });
23170
23528
  },
23171
23529
  cancel(reason) {
23172
23530
  requestAbort.abort(reason);
@@ -23175,7 +23533,7 @@ data: ${JSON.stringify({
23175
23533
  });
23176
23534
  const streamSessionId = resumeSessionId || `session_${Date.now()}`;
23177
23535
  streamOwnsAbortLink = true;
23178
- return new Response(readable, {
23536
+ const streamResponse = new Response(readable, {
23179
23537
  headers: {
23180
23538
  "Content-Type": "text/event-stream",
23181
23539
  "Cache-Control": "no-cache",
@@ -23183,19 +23541,22 @@ data: ${JSON.stringify({
23183
23541
  "X-Claude-Session-ID": streamSessionId
23184
23542
  }
23185
23543
  });
23544
+ responseCompletions.set(streamResponse, streamCompletion);
23545
+ return streamResponse;
23186
23546
  } catch (error) {
23187
23547
  const errMsg = error instanceof Error ? error.message : String(error);
23188
23548
  claudeLog("error.unhandled", {
23189
23549
  durationMs: Date.now() - requestStartAt,
23190
23550
  error: errMsg
23191
23551
  });
23192
- const classified = classifyError(errMsg);
23552
+ const classified = requestAbort.controller.signal.aborted ? { status: 499, type: "request_cancelled", message: "The request was cancelled" } : classifyError(errMsg);
23193
23553
  claudeLog("proxy.error", { error: errMsg, classified: classified.type });
23194
23554
  const sdkTerm = extractSdkTermination(errMsg);
23195
23555
  diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
23196
23556
  requestSource: c.req.header("x-meridian-source")?.slice(0, 64) || undefined
23197
23557
  })}`, requestMeta.requestId);
23198
- const errorQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
23558
+ const errorQueueWaitMs = totalQueueWaitMs(requestMeta);
23559
+ const errorTotalMs = Date.now() - requestStartAt;
23199
23560
  telemetryStore2.record({
23200
23561
  requestId: requestMeta.requestId,
23201
23562
  timestamp: Date.now(),
@@ -23213,10 +23574,12 @@ data: ${JSON.stringify({
23213
23574
  sdkSessionId: undefined,
23214
23575
  status: classified.status,
23215
23576
  queueWaitMs: errorQueueWaitMs,
23216
- proxyOverheadMs: Date.now() - requestStartAt - errorQueueWaitMs,
23577
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23578
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23579
+ proxyOverheadMs: Math.max(0, errorTotalMs - errorQueueWaitMs - requestMeta.sdkActiveDurationMs),
23217
23580
  ttfbMs: null,
23218
- upstreamDurationMs: Date.now() - requestStartAt,
23219
- totalDurationMs: Date.now() - requestStartAt,
23581
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23582
+ totalDurationMs: errorTotalMs,
23220
23583
  contentBlocks: 0,
23221
23584
  textEvents: 0,
23222
23585
  error: classified.type
@@ -23229,19 +23592,122 @@ data: ${JSON.stringify({
23229
23592
  });
23230
23593
  };
23231
23594
  const handleWithQueue = async (c, endpoint) => {
23595
+ if (draining && c.req.header("x-meridian-internal-hop") !== internalHopToken) {
23596
+ return drainingResponse();
23597
+ }
23232
23598
  const requestId = c.req.header("x-request-id") || randomUUID();
23233
23599
  const queueEnteredAt = Date.now();
23234
23600
  claudeLog("request.enter", { requestId, endpoint });
23235
- const held = insideSessionSlot.getStore();
23236
- if (held) {
23237
- return handleMessages(c, { requestId, endpoint, ...held });
23238
- }
23239
- await acquireSession();
23240
- const queueStartedAt = Date.now();
23601
+ let sessionTurnLease;
23602
+ let finished = false;
23603
+ let leaseReleased = false;
23604
+ let leaseWatchdog;
23605
+ inFlightRequests++;
23606
+ const releaseSessionTurn = (forced) => {
23607
+ if (leaseReleased || !sessionTurnLease)
23608
+ return;
23609
+ leaseReleased = true;
23610
+ if (leaseWatchdog)
23611
+ clearTimeout(leaseWatchdog);
23612
+ if (forced) {
23613
+ claudeLog("session.turn_lease_forced", { requestId, heldMs: SESSION_TURN_MAX_HOLD_MS });
23614
+ plog(`[PROXY] ${requestId} session turn lease force-released after ${SESSION_TURN_MAX_HOLD_MS}ms`);
23615
+ }
23616
+ sessionTurnLease.release();
23617
+ };
23618
+ const finishRequest = () => {
23619
+ if (finished)
23620
+ return;
23621
+ finished = true;
23622
+ releaseSessionTurn(false);
23623
+ inFlightRequests--;
23624
+ };
23625
+ let body;
23241
23626
  try {
23242
- return await insideSessionSlot.run({ queueEnteredAt, queueStartedAt }, () => handleMessages(c, { requestId, endpoint, queueEnteredAt, queueStartedAt }));
23243
- } finally {
23244
- releaseSession();
23627
+ try {
23628
+ body = await c.req.json();
23629
+ } catch (error) {
23630
+ if (c.req.raw.signal.aborted || error instanceof Error && error.name === "AbortError") {
23631
+ finishRequest();
23632
+ return new Response(JSON.stringify({
23633
+ type: "error",
23634
+ error: { type: "request_cancelled", message: "The request was cancelled" }
23635
+ }), { status: 499, headers: { "Content-Type": "application/json" } });
23636
+ }
23637
+ finishRequest();
23638
+ return new Response(JSON.stringify({
23639
+ type: "error",
23640
+ error: { type: "invalid_request_error", message: "Request body must be valid JSON" }
23641
+ }), { status: 400, headers: { "Content-Type": "application/json" } });
23642
+ }
23643
+ if (Array.isArray(body?.messages)) {
23644
+ const adapter = detectAdapter(c);
23645
+ const agentSessionId = adapter.getSessionId(c, body);
23646
+ if (agentSessionId) {
23647
+ try {
23648
+ sessionTurnLease = await processSessionTurns.acquire(`session:${agentSessionId}`, c.req.raw.signal);
23649
+ leaseWatchdog = setTimeout(() => releaseSessionTurn(true), SESSION_TURN_MAX_HOLD_MS);
23650
+ leaseWatchdog.unref?.();
23651
+ } catch (error) {
23652
+ if (c.req.raw.signal.aborted || error instanceof Error && error.name === "AbortError") {
23653
+ const cancelledWaitMs = Date.now() - queueEnteredAt;
23654
+ telemetryStore2.record({
23655
+ requestId,
23656
+ timestamp: Date.now(),
23657
+ adapter: adapter.name,
23658
+ model: "unknown",
23659
+ requestModel: undefined,
23660
+ mode: "non-stream",
23661
+ isResume: false,
23662
+ isPassthrough: envBool("PASSTHROUGH"),
23663
+ hasDeferredTools: undefined,
23664
+ deferredToolCount: undefined,
23665
+ toolCount: undefined,
23666
+ lineageType: undefined,
23667
+ messageCount: Array.isArray(body?.messages) ? body.messages.length : undefined,
23668
+ sdkSessionId: undefined,
23669
+ status: 499,
23670
+ queueWaitMs: cancelledWaitMs,
23671
+ sessionQueueWaitMs: cancelledWaitMs,
23672
+ sdkQueueWaitMs: 0,
23673
+ proxyOverheadMs: 0,
23674
+ ttfbMs: null,
23675
+ upstreamDurationMs: 0,
23676
+ totalDurationMs: cancelledWaitMs,
23677
+ contentBlocks: 0,
23678
+ textEvents: 0,
23679
+ error: "request_cancelled"
23680
+ });
23681
+ finishRequest();
23682
+ return new Response(JSON.stringify({
23683
+ type: "error",
23684
+ error: { type: "request_cancelled", message: "The request was cancelled" }
23685
+ }), { status: 499, headers: { "Content-Type": "application/json" } });
23686
+ }
23687
+ throw error;
23688
+ }
23689
+ }
23690
+ }
23691
+ const requestMeta = {
23692
+ requestId,
23693
+ endpoint,
23694
+ queueEnteredAt,
23695
+ sessionQueueWaitMs: sessionTurnLease?.waitedMs ?? 0,
23696
+ sdkQueueWaitMs: 0,
23697
+ sdkActiveDurationMs: 0,
23698
+ sessionTurnLease
23699
+ };
23700
+ const response = await handleMessages(c, requestMeta, { body });
23701
+ const completion = responseCompletions.get(response);
23702
+ if (completion) {
23703
+ completion.finally(finishRequest).catch(() => {});
23704
+ } else {
23705
+ finishRequest();
23706
+ }
23707
+ return response;
23708
+ } catch (error) {
23709
+ finishRequest();
23710
+ throw error;
23245
23711
  }
23246
23712
  };
23247
23713
  app.post("/v1/messages", (c) => handleWithQueue(c, "/v1/messages"));
@@ -23344,6 +23810,13 @@ data: ${JSON.stringify({
23344
23810
  });
23345
23811
  });
23346
23812
  app.get("/health", async (c) => {
23813
+ if (draining) {
23814
+ return c.json({
23815
+ status: "draining",
23816
+ version: serverVersion,
23817
+ message: "Meridian is shutting down; route new requests to another instance."
23818
+ }, 503);
23819
+ }
23347
23820
  try {
23348
23821
  const healthProfile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile);
23349
23822
  const profileEnvOverrides = Object.keys(healthProfile.env).length > 0 ? healthProfile.env : undefined;
@@ -23501,6 +23974,8 @@ data: ${JSON.stringify({
23501
23974
  return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
23502
23975
  });
23503
23976
  app.post("/v1/chat/completions", async (c) => {
23977
+ if (draining)
23978
+ return drainingResponse();
23504
23979
  const rawBody = await c.req.json();
23505
23980
  const userAgent = c.req.header("user-agent") ?? "";
23506
23981
  const jcodeSessionId = userAgent.startsWith("jcode/") ? normalizeJcodeSessionId(c.req.header("x-jcode-session")) : undefined;
@@ -23527,16 +24002,16 @@ data: ${JSON.stringify({
23527
24002
  const authz = c.req.header("authorization");
23528
24003
  if (authz)
23529
24004
  internalHeaders["authorization"] = authz;
24005
+ internalHeaders["x-meridian-internal-hop"] = internalHopToken;
23530
24006
  const internalReq = new Request("http://internal/v1/messages", {
23531
24007
  method: "POST",
23532
24008
  headers: internalHeaders,
23533
- body: JSON.stringify(anthropicBody)
24009
+ body: JSON.stringify(anthropicBody),
24010
+ signal: c.req.raw.signal
23534
24011
  });
23535
24012
  const internalRes = await app.fetch(internalReq);
23536
- if (!internalRes.ok) {
23537
- const errBody = await internalRes.text();
23538
- return c.json({ type: "error", error: { type: "upstream_error", message: errBody } }, internalRes.status);
23539
- }
24013
+ if (!internalRes.ok)
24014
+ return relayInnerError(internalRes, "anthropic");
23540
24015
  const completionId = `chatcmpl-${randomUUID()}`;
23541
24016
  const created = Math.floor(Date.now() / 1000);
23542
24017
  const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : CANONICAL_SONNET_MODEL;
@@ -23549,6 +24024,7 @@ data: ${JSON.stringify({
23549
24024
  }));
23550
24025
  }
23551
24026
  const encoder = new TextEncoder;
24027
+ let internalReader;
23552
24028
  const readable = new ReadableStream({
23553
24029
  async start(controller) {
23554
24030
  const reader = internalRes.body?.getReader();
@@ -23556,6 +24032,7 @@ data: ${JSON.stringify({
23556
24032
  controller.close();
23557
24033
  return;
23558
24034
  }
24035
+ internalReader = reader;
23559
24036
  const decoder = new TextDecoder;
23560
24037
  let buffer = "";
23561
24038
  let streamError = null;
@@ -23608,6 +24085,9 @@ data: ${JSON.stringify({
23608
24085
  }
23609
24086
  controller.close();
23610
24087
  }
24088
+ },
24089
+ cancel(reason) {
24090
+ return internalReader?.cancel(reason);
23611
24091
  }
23612
24092
  });
23613
24093
  return new Response(readable, {
@@ -23619,6 +24099,8 @@ data: ${JSON.stringify({
23619
24099
  });
23620
24100
  });
23621
24101
  app.post("/v1/responses", async (c) => {
24102
+ if (draining)
24103
+ return drainingResponse("openai");
23622
24104
  const rawBody = await c.req.json();
23623
24105
  const anthropicBody = translateResponsesToAnthropic(rawBody);
23624
24106
  if (!anthropicBody) {
@@ -23644,16 +24126,16 @@ data: ${JSON.stringify({
23644
24126
  const xProfile = c.req.header("x-meridian-profile");
23645
24127
  if (xProfile)
23646
24128
  internalHeaders["x-meridian-profile"] = xProfile;
24129
+ internalHeaders["x-meridian-internal-hop"] = internalHopToken;
23647
24130
  const internalReq = new Request("http://internal/v1/messages", {
23648
24131
  method: "POST",
23649
24132
  headers: internalHeaders,
23650
- body: JSON.stringify(anthropicBody)
24133
+ body: JSON.stringify(anthropicBody),
24134
+ signal: c.req.raw.signal
23651
24135
  });
23652
24136
  const internalRes = await app.fetch(internalReq);
23653
- if (!internalRes.ok) {
23654
- const errBody = await internalRes.text();
23655
- return c.json({ error: { type: "upstream_error", message: errBody, code: null } }, internalRes.status);
23656
- }
24137
+ if (!internalRes.ok)
24138
+ return relayInnerError(internalRes, "openai");
23657
24139
  const responseId = `resp_${randomUUID().replace(/-/g, "")}`;
23658
24140
  const created = Math.floor(Date.now() / 1000);
23659
24141
  const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : CANONICAL_SONNET_MODEL;
@@ -23663,6 +24145,7 @@ data: ${JSON.stringify({
23663
24145
  return c.json(translateAnthropicToResponses(anthropicRes, ctx));
23664
24146
  }
23665
24147
  const encoder = new TextEncoder;
24148
+ let internalReader;
23666
24149
  const readable = new ReadableStream({
23667
24150
  async start(controller) {
23668
24151
  const reader = internalRes.body?.getReader();
@@ -23670,6 +24153,7 @@ data: ${JSON.stringify({
23670
24153
  controller.close();
23671
24154
  return;
23672
24155
  }
24156
+ internalReader = reader;
23673
24157
  const decoder = new TextDecoder;
23674
24158
  let buffer = "";
23675
24159
  const translate = createResponsesSseTranslator(ctx);
@@ -23713,6 +24197,9 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
23713
24197
  } finally {
23714
24198
  controller.close();
23715
24199
  }
24200
+ },
24201
+ cancel(reason) {
24202
+ return internalReader?.cancel(reason);
23716
24203
  }
23717
24204
  });
23718
24205
  return new Response(readable, {
@@ -23725,8 +24212,8 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
23725
24212
  });
23726
24213
  app.get("/v1/models", async (c) => {
23727
24214
  const authStatus = await getClaudeAuthStatusAsync();
23728
- const isMax = authStatus?.subscriptionType === "max";
23729
- return c.json({ object: "list", data: buildModelList(isMax) });
24215
+ const extendedContext = subscriptionIncludesExtendedContext(authStatus?.subscriptionType);
24216
+ return c.json({ object: "list", data: buildModelList(extendedContext) });
23730
24217
  });
23731
24218
  app.get("/v1/usage/quota", async (c) => {
23732
24219
  const requestedProfile = c.req.query("profile");
@@ -23962,7 +24449,15 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
23962
24449
  plog(`[PROXY] Plugin loading failed: ${err instanceof Error ? err.message : String(err)}`);
23963
24450
  }
23964
24451
  }
23965
- return { app, config: finalConfig, initPlugins: initPluginsAsync };
24452
+ return {
24453
+ app,
24454
+ config: finalConfig,
24455
+ initPlugins: initPluginsAsync,
24456
+ beginDrain: () => {
24457
+ draining = true;
24458
+ },
24459
+ getInFlightCount: () => inFlightRequests
24460
+ };
23966
24461
  }
23967
24462
  var processErrorHandlersInstalled = false;
23968
24463
  function installProxyProcessErrorHandlers() {
@@ -23978,7 +24473,7 @@ function installProxyProcessErrorHandlers() {
23978
24473
  }
23979
24474
  async function startProxyServer(config = {}) {
23980
24475
  claudeExecutable = await resolveClaudeExecutableAsync();
23981
- const { app, config: finalConfig, initPlugins } = createProxyServer(config);
24476
+ const { app, config: finalConfig, initPlugins, beginDrain, getInFlightCount } = createProxyServer(config);
23982
24477
  if (initPlugins)
23983
24478
  await initPlugins();
23984
24479
  if (finalConfig.installProcessErrorHandlers) {
@@ -24007,6 +24502,7 @@ Point any Anthropic-compatible tool at this endpoint:`);
24007
24502
  const idleMs = finalConfig.idleTimeoutSeconds * 1000;
24008
24503
  server.keepAliveTimeout = idleMs;
24009
24504
  server.headersTimeout = idleMs + 1000;
24505
+ const connectionTracker = trackServerConnections(server);
24010
24506
  server.on("error", (error) => {
24011
24507
  if (error.code === "EADDRINUSE" && !finalConfig.silent) {
24012
24508
  console.error(`
@@ -24045,17 +24541,29 @@ Or use a different port:`);
24045
24541
  if (authKeepaliveInterval.unref)
24046
24542
  authKeepaliveInterval.unref();
24047
24543
  }
24544
+ let closePromise;
24048
24545
  return {
24049
24546
  server,
24050
24547
  config: finalConfig,
24051
- async close() {
24052
- clearInterval(profileTokenRefreshInterval);
24053
- if (authKeepaliveInterval)
24054
- clearInterval(authKeepaliveInterval);
24055
- stopBackgroundRefresh();
24056
- await new Promise((resolve3, reject) => {
24057
- server.close((err) => err ? reject(err) : resolve3());
24058
- });
24548
+ close() {
24549
+ closePromise ??= (async () => {
24550
+ clearInterval(profileTokenRefreshInterval);
24551
+ if (authKeepaliveInterval)
24552
+ clearInterval(authKeepaliveInterval);
24553
+ stopBackgroundRefresh();
24554
+ beginDrain?.();
24555
+ try {
24556
+ await closeServerWithGracePeriod(server, {
24557
+ graceMs: SHUTDOWN_GRACE_MS,
24558
+ getInFlightCount: () => getInFlightCount?.() ?? 0,
24559
+ warn: finalConfig.silent ? undefined : (message) => console.warn(message),
24560
+ forceCloseConnections: () => connectionTracker.forceCloseAll()
24561
+ });
24562
+ } finally {
24563
+ connectionTracker.dispose();
24564
+ }
24565
+ })();
24566
+ return closePromise;
24059
24567
  }
24060
24568
  };
24061
24569
  }