@rynfar/meridian 1.62.1 → 1.62.3

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,185 @@ 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
+ function assertPositiveLimit(limit) {
6520
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
6521
+ throw new RangeError("Semaphore limit must be a positive integer");
6522
+ }
6523
+ return limit;
6524
+ }
6525
+
6526
+ class AbortableSemaphore {
6527
+ activeCount = 0;
6528
+ currentLimit;
6529
+ waiters = [];
6530
+ constructor(limit) {
6531
+ this.currentLimit = assertPositiveLimit(limit);
6532
+ }
6533
+ get limit() {
6534
+ return this.currentLimit;
6535
+ }
6536
+ setLimit(next) {
6537
+ const limit = assertPositiveLimit(next);
6538
+ if (limit === this.currentLimit)
6539
+ return;
6540
+ const raised = limit > this.currentLimit;
6541
+ this.currentLimit = limit;
6542
+ if (raised)
6543
+ this.grantNext();
6544
+ }
6545
+ get snapshot() {
6546
+ return { active: this.activeCount, queued: this.waiters.length, limit: this.currentLimit };
6547
+ }
6548
+ acquire(signal) {
6549
+ if (signal?.aborted)
6550
+ return Promise.reject(requestCancelledError(signal.reason));
6551
+ const enqueuedAt = Date.now();
6552
+ if (this.activeCount < this.limit && this.waiters.length === 0) {
6553
+ this.activeCount++;
6554
+ return Promise.resolve(this.createLease(enqueuedAt));
6555
+ }
6556
+ return new Promise((resolve, reject) => {
6557
+ const waiter = { enqueuedAt, resolve, reject, signal };
6558
+ if (signal) {
6559
+ waiter.abortListener = () => {
6560
+ const index = this.waiters.indexOf(waiter);
6561
+ if (index === -1)
6562
+ return;
6563
+ this.waiters.splice(index, 1);
6564
+ reject(requestCancelledError(signal.reason));
6565
+ };
6566
+ signal.addEventListener("abort", waiter.abortListener, { once: true });
6567
+ }
6568
+ this.waiters.push(waiter);
6569
+ });
6570
+ }
6571
+ createLease(enqueuedAt) {
6572
+ let released = false;
6573
+ return {
6574
+ waitedMs: Date.now() - enqueuedAt,
6575
+ release: () => {
6576
+ if (released)
6577
+ return;
6578
+ released = true;
6579
+ this.activeCount--;
6580
+ this.grantNext();
6581
+ }
6582
+ };
6583
+ }
6584
+ grantNext() {
6585
+ while (this.activeCount < this.limit) {
6586
+ const waiter = this.waiters.shift();
6587
+ if (!waiter)
6588
+ return;
6589
+ if (waiter.abortListener && waiter.signal) {
6590
+ waiter.signal.removeEventListener("abort", waiter.abortListener);
6591
+ }
6592
+ if (waiter.signal?.aborted) {
6593
+ waiter.reject(requestCancelledError(waiter.signal.reason));
6594
+ continue;
6595
+ }
6596
+ this.activeCount++;
6597
+ waiter.resolve(this.createLease(waiter.enqueuedAt));
6598
+ }
6599
+ }
6600
+ }
6601
+ function getProcessSdkSemaphore() {
6602
+ const limit = resolveMaxConcurrent();
6603
+ if (!processSdkSemaphore) {
6604
+ processSdkSemaphore = new AbortableSemaphore(limit);
6605
+ return processSdkSemaphore;
6606
+ }
6607
+ processSdkSemaphore.setLimit(limit);
6608
+ return processSdkSemaphore;
6609
+ }
6610
+
6611
+ // src/proxy/shutdown.ts
6612
+ function trackServerConnections(server) {
6613
+ const sockets = new Set;
6614
+ const onConnection = (socket) => {
6615
+ sockets.add(socket);
6616
+ socket.once("close", () => sockets.delete(socket));
6617
+ };
6618
+ server.on("connection", onConnection);
6619
+ return {
6620
+ forceCloseAll() {
6621
+ server.closeAllConnections?.();
6622
+ for (const socket of sockets)
6623
+ socket.destroy();
6624
+ },
6625
+ dispose() {
6626
+ server.off("connection", onConnection);
6627
+ sockets.clear();
6628
+ }
6629
+ };
6630
+ }
6631
+ async function closeServerWithGracePeriod(server, options) {
6632
+ const graceMs = Math.max(0, options.graceMs);
6633
+ const deadlineAt = Date.now() + graceMs;
6634
+ while (options.getInFlightCount() > 0) {
6635
+ const remainingMs = deadlineAt - Date.now();
6636
+ if (remainingMs <= 0)
6637
+ break;
6638
+ await new Promise((resolve) => {
6639
+ const timer = setTimeout(resolve, Math.min(50, remainingMs));
6640
+ timer.unref?.();
6641
+ });
6642
+ }
6643
+ const closePromise = new Promise((resolve, reject) => {
6644
+ server.close((error) => error ? reject(error) : resolve());
6645
+ });
6646
+ const remainingGraceMs = Math.max(0, deadlineAt - Date.now());
6647
+ if (remainingGraceMs > 0) {
6648
+ let timeout;
6649
+ const deadline = new Promise((resolve) => {
6650
+ timeout = setTimeout(() => resolve("timeout"), remainingGraceMs);
6651
+ timeout.unref?.();
6652
+ });
6653
+ try {
6654
+ const outcome = await Promise.race([
6655
+ closePromise.then(() => "closed"),
6656
+ deadline
6657
+ ]);
6658
+ if (outcome === "closed")
6659
+ return;
6660
+ } finally {
6661
+ if (timeout)
6662
+ clearTimeout(timeout);
6663
+ }
6664
+ }
6665
+ const remaining = options.getInFlightCount();
6666
+ options.warn?.(`[PROXY] Grace period elapsed with ${remaining} request(s) still in flight after ${graceMs}ms; forcing remaining HTTP connections closed.`);
6667
+ if (options.forceCloseConnections)
6668
+ options.forceCloseConnections();
6669
+ else
6670
+ server.closeAllConnections?.();
6671
+ await closePromise;
6672
+ }
6673
+
6480
6674
  // src/proxy/oauthUsage.ts
6481
6675
  var OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
6482
6676
  var OAUTH_BETA_HEADER = "oauth-2025-04-20";
@@ -11300,7 +11494,6 @@ var dashboardHtml = `<!DOCTYPE html>
11300
11494
  .waterfall-seg { height: 100%; border-radius: 2px; min-width: 2px; }
11301
11495
  .waterfall-seg.queue { background: var(--queue); }
11302
11496
  .waterfall-seg.overhead { background: var(--yellow); }
11303
- .waterfall-seg.ttfb { background: var(--ttfb); }
11304
11497
  .waterfall-seg.response { background: var(--upstream); }
11305
11498
  .legend { display: flex; gap: 16px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }
11306
11499
  .legend-dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; vertical-align: middle; }
@@ -11556,6 +11749,8 @@ function render(s, reqs, logs) {
11556
11749
  html += '<div class="section"><div class="section-title">Percentiles</div>'
11557
11750
  + '<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
11751
  + pctRow('Queue Wait', 'var(--queue)', s.queueWait)
11752
+ + pctRow('Session Queue', 'var(--queue)', s.sessionQueueWait)
11753
+ + pctRow('SDK Queue', 'var(--queue)', s.sdkQueueWait)
11559
11754
  + pctRow('Proxy Overhead', 'var(--yellow)', s.proxyOverhead)
11560
11755
  + pctRow('TTFB', 'var(--ttfb)', s.ttfb)
11561
11756
  + pctRow('Upstream', 'var(--upstream)', s.upstreamDuration)
@@ -11570,7 +11765,6 @@ function render(s, reqs, logs) {
11570
11765
  html += '<div class="legend">'
11571
11766
  + '<span><span class="legend-dot" style="background:var(--queue)"></span>Queue</span>'
11572
11767
  + '<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
11768
  + '<span><span class="legend-dot" style="background:var(--upstream)"></span>Response</span>'
11575
11769
  + '</div>'
11576
11770
  + '<table><thead><tr><th>Time</th><th>Adapter</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'
@@ -11582,10 +11776,11 @@ function render(s, reqs, logs) {
11582
11776
  const statusClass = r.error ? 'status-err' : 'status-ok';
11583
11777
  const statusText = r.error ? r.error : r.status;
11584
11778
  const scale = 280 / maxTotal;
11779
+ const sessionQW = r.sessionQueueWaitMs || 0;
11780
+ const sdkQW = r.sdkQueueWaitMs || 0;
11585
11781
  const qW = Math.max(r.queueWaitMs * scale, 2);
11586
11782
  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);
11783
+ const respW = Math.max(r.upstreamDurationMs * scale, 2);
11589
11784
 
11590
11785
  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
11786
  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 +11796,15 @@ function render(s, reqs, logs) {
11601
11796
  + '<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
11797
  + '<td class="mono">' + sessionShort + ' ' + lineageBadge + envBadge + '<br><span style="font-size:10px;color:var(--muted)">' + msgCount + ' msgs</span></td>'
11603
11798
  + '<td class="' + statusClass + '">' + statusText + '</td>'
11604
- + '<td class="mono">' + ms(r.queueWaitMs) + '</td>'
11799
+ + '<td class="mono">' + ms(r.queueWaitMs) + '<br><span style="font-size:9px;color:var(--muted)">session ' + ms(sessionQW) + ' / sdk ' + ms(sdkQW) + '</span></td>'
11605
11800
  + '<td class="mono">' + ms(r.proxyOverheadMs) + '</td>'
11606
11801
  + '<td class="mono">' + ms(r.ttfbMs) + '</td>'
11607
11802
  + '<td class="mono">' + ms(r.totalDurationMs) + '</td>'
11608
11803
  + '<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
11804
  + '<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">'
11805
+ + '<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
11806
  + '<div class="waterfall-seg queue" style="width:' + qW + 'px"></div>'
11612
11807
  + '<div class="waterfall-seg overhead" style="width:' + ohW + 'px"></div>'
11613
- + '<div class="waterfall-seg ttfb" style="width:' + ttfbW + 'px"></div>'
11614
11808
  + '<div class="waterfall-seg response" style="width:' + respW + 'px"></div>'
11615
11809
  + '</div></td>'
11616
11810
  + '</tr>';
@@ -12001,6 +12195,8 @@ init_percentiles();
12001
12195
  var DURATION_BUCKETS = [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 1e4, 30000];
12002
12196
  var PHASES = [
12003
12197
  { key: "queue_wait", extract: (m) => m.queueWaitMs },
12198
+ { key: "session_queue_wait", extract: (m) => m.sessionQueueWaitMs ?? 0 },
12199
+ { key: "sdk_queue_wait", extract: (m) => m.sdkQueueWaitMs ?? 0 },
12004
12200
  { key: "proxy_overhead", extract: (m) => m.proxyOverheadMs },
12005
12201
  { key: "ttfb", extract: (m) => m.ttfbMs },
12006
12202
  { key: "upstream", extract: (m) => m.upstreamDurationMs },
@@ -12958,7 +13154,7 @@ var FULL_CAPABILITIES = Object.freeze({
12958
13154
  structured_outputs: yes,
12959
13155
  thinking: { supported: true, types: { adaptive: yes, enabled: yes } }
12960
13156
  });
12961
- function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000)) {
13157
+ function buildModelList(extendedContextIncluded, now = Math.floor(Date.now() / 1000)) {
12962
13158
  return [
12963
13159
  {
12964
13160
  id: "claude-sonnet-5",
@@ -12984,7 +13180,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
12984
13180
  created: now,
12985
13181
  owned_by: "anthropic",
12986
13182
  display_name: "Claude Opus 5",
12987
- context_window: isMaxSubscription ? 1e6 : 200000,
13183
+ context_window: extendedContextIncluded ? 1e6 : 200000,
12988
13184
  capabilities: FULL_CAPABILITIES
12989
13185
  },
12990
13186
  {
@@ -12993,7 +13189,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
12993
13189
  created: now,
12994
13190
  owned_by: "anthropic",
12995
13191
  display_name: "Claude Opus 4.6",
12996
- context_window: isMaxSubscription ? 1e6 : 200000,
13192
+ context_window: extendedContextIncluded ? 1e6 : 200000,
12997
13193
  capabilities: FULL_CAPABILITIES
12998
13194
  },
12999
13195
  {
@@ -13002,7 +13198,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
13002
13198
  created: now,
13003
13199
  owned_by: "anthropic",
13004
13200
  display_name: "Claude Opus 4.7",
13005
- context_window: isMaxSubscription ? 1e6 : 200000,
13201
+ context_window: extendedContextIncluded ? 1e6 : 200000,
13006
13202
  capabilities: FULL_CAPABILITIES
13007
13203
  },
13008
13204
  {
@@ -13011,7 +13207,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
13011
13207
  created: now,
13012
13208
  owned_by: "anthropic",
13013
13209
  display_name: "Claude Opus 4.8",
13014
- context_window: isMaxSubscription ? 1e6 : 200000,
13210
+ context_window: extendedContextIncluded ? 1e6 : 200000,
13015
13211
  capabilities: FULL_CAPABILITIES
13016
13212
  },
13017
13213
  {
@@ -13020,7 +13216,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
13020
13216
  created: now,
13021
13217
  owned_by: "anthropic",
13022
13218
  display_name: "Claude Fable 5",
13023
- context_window: isMaxSubscription ? 1e6 : 200000,
13219
+ context_window: extendedContextIncluded ? 1e6 : 200000,
13024
13220
  capabilities: FULL_CAPABILITIES
13025
13221
  },
13026
13222
  {
@@ -20484,10 +20680,117 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
20484
20680
  }
20485
20681
  }
20486
20682
 
20683
+ // src/proxy/session/turnCoordinator.ts
20684
+ function cancellationError(reason) {
20685
+ if (reason instanceof Error)
20686
+ return reason;
20687
+ return new DOMException(typeof reason === "string" && reason ? reason : "The request was cancelled", "AbortError");
20688
+ }
20689
+
20690
+ class SessionTurnCoordinator {
20691
+ turns = new Map;
20692
+ get size() {
20693
+ return this.turns.size;
20694
+ }
20695
+ acquire(key, signal) {
20696
+ if (signal?.aborted)
20697
+ return Promise.reject(cancellationError(signal.reason));
20698
+ let state = this.turns.get(key);
20699
+ if (!state) {
20700
+ state = { held: false, versions: new Map, waiters: [] };
20701
+ this.turns.set(key, state);
20702
+ }
20703
+ const arrivedAt = Date.now();
20704
+ const arrivedVersions = new Map(state.versions);
20705
+ if (!state.held && state.waiters.length === 0) {
20706
+ state.held = true;
20707
+ return Promise.resolve(this.createLease(key, state, arrivedAt, arrivedVersions));
20708
+ }
20709
+ return new Promise((resolve3, reject) => {
20710
+ const waiter = {
20711
+ arrivedAt,
20712
+ versions: arrivedVersions,
20713
+ resolve: resolve3,
20714
+ reject,
20715
+ signal
20716
+ };
20717
+ if (signal) {
20718
+ waiter.abortListener = () => {
20719
+ const index = state.waiters.indexOf(waiter);
20720
+ if (index === -1)
20721
+ return;
20722
+ state.waiters.splice(index, 1);
20723
+ reject(cancellationError(signal.reason));
20724
+ this.cleanup(key, state);
20725
+ };
20726
+ signal.addEventListener("abort", waiter.abortListener, { once: true });
20727
+ }
20728
+ state.waiters.push(waiter);
20729
+ });
20730
+ }
20731
+ createLease(key, state, arrivedAt, arrivedVersions) {
20732
+ let released = false;
20733
+ const committedScopes = new Set;
20734
+ return {
20735
+ waitedMs: Date.now() - arrivedAt,
20736
+ advancedWhileWaiting: (scopeKey) => (state.versions.get(scopeKey) ?? 0) > (arrivedVersions.get(scopeKey) ?? 0),
20737
+ markCommitted: (scopeKey) => {
20738
+ if (released || committedScopes.has(scopeKey))
20739
+ return;
20740
+ committedScopes.add(scopeKey);
20741
+ state.versions.set(scopeKey, (state.versions.get(scopeKey) ?? 0) + 1);
20742
+ },
20743
+ release: () => {
20744
+ if (released)
20745
+ return;
20746
+ released = true;
20747
+ state.held = false;
20748
+ while (state.waiters.length > 0) {
20749
+ const waiter = state.waiters.shift();
20750
+ if (waiter.abortListener && waiter.signal) {
20751
+ waiter.signal.removeEventListener("abort", waiter.abortListener);
20752
+ }
20753
+ if (waiter.signal?.aborted) {
20754
+ waiter.reject(cancellationError(waiter.signal.reason));
20755
+ continue;
20756
+ }
20757
+ state.held = true;
20758
+ waiter.resolve(this.createLease(key, state, waiter.arrivedAt, waiter.versions));
20759
+ return;
20760
+ }
20761
+ this.cleanup(key, state);
20762
+ }
20763
+ };
20764
+ }
20765
+ cleanup(key, state) {
20766
+ if (!state.held && state.waiters.length === 0 && this.turns.get(key) === state) {
20767
+ this.turns.delete(key);
20768
+ }
20769
+ }
20770
+ }
20771
+ var processSessionTurns = new SessionTurnCoordinator;
20772
+
20487
20773
  // src/proxy/server.ts
20488
20774
  var exec2 = promisify3(execCallback);
20489
20775
  var claudeExecutable = "";
20490
20776
  var UPSTREAM_IDLE_MS = envInt("UPSTREAM_IDLE_MS", 90000);
20777
+ var SHUTDOWN_GRACE_MS = envInt("SHUTDOWN_GRACE_MS", 30000);
20778
+ function totalQueueWaitMs(meta) {
20779
+ return meta.sessionQueueWaitMs + meta.sdkQueueWaitMs;
20780
+ }
20781
+ function forkAttemptMeta(meta, attempt) {
20782
+ if (attempt === 0)
20783
+ return meta;
20784
+ return {
20785
+ ...meta,
20786
+ queueEnteredAt: Date.now(),
20787
+ sessionQueueWaitMs: 0,
20788
+ sdkQueueWaitMs: 0,
20789
+ sdkActiveDurationMs: 0,
20790
+ currentSdkStartedAt: undefined,
20791
+ ttfbMs: undefined
20792
+ };
20793
+ }
20491
20794
  function credentialStoreForProfile(profile) {
20492
20795
  if (profile.type !== "claude-max")
20493
20796
  return;
@@ -20693,28 +20996,62 @@ function createProxyServer(config = {}) {
20693
20996
  const sessionDiscoveredTools = new Map;
20694
20997
  const sessionToolCache = new Map;
20695
20998
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
20696
- const PENDING_STORE_WAIT_MS = 3000;
20697
- const PENDING_STORE_AUTO_RESOLVE_MS = 1e4;
20698
20999
  const RESUME_REFUSAL_MAX_RETRIES = 3;
20699
21000
  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
- };
21001
+ const SESSION_TURN_MAX_HOLD_MS = envInt("SESSION_TURN_MAX_HOLD_MS", 600000);
21002
+ const sdkSemaphore = finalConfig.maxConcurrent !== undefined ? new AbortableSemaphore(finalConfig.maxConcurrent) : getProcessSdkSemaphore();
21003
+ const responseCompletions = new WeakMap;
21004
+ let draining = false;
21005
+ let inFlightRequests = 0;
21006
+ const internalHopToken = randomUUID();
21007
+ const errorEnvelope = (shape, type, message) => shape === "anthropic" ? { type: "error", error: { type, message } } : { error: { type, message, code: null } };
21008
+ const DRAIN_MESSAGE = "Meridian is shutting down and is not accepting new requests. Retry against another instance.";
21009
+ const drainingResponse = (shape = "anthropic") => new Response(JSON.stringify(errorEnvelope(shape, "overloaded_error", DRAIN_MESSAGE)), {
21010
+ status: 503,
21011
+ headers: { "Content-Type": "application/json", "x-meridian-draining": "1" }
21012
+ });
21013
+ async function relayInnerError(internalRes, shape) {
21014
+ const errBody = await internalRes.text();
21015
+ let innerType;
21016
+ let innerMessage;
21017
+ try {
21018
+ const parsed = JSON.parse(errBody);
21019
+ innerType = parsed?.error?.type;
21020
+ innerMessage = parsed?.error?.message;
21021
+ } catch {}
21022
+ const payload = errorEnvelope(shape, innerType ?? "upstream_error", innerMessage ?? errBody);
21023
+ const headers = { "Content-Type": "application/json" };
21024
+ const drainingHeader = internalRes.headers.get("x-meridian-draining");
21025
+ if (drainingHeader)
21026
+ headers["x-meridian-draining"] = drainingHeader;
21027
+ return new Response(JSON.stringify(payload), { status: internalRes.status, headers });
21028
+ }
21029
+ async function* runSdkQueryAttempt(params, signal, requestMeta, mode) {
21030
+ const acquireStartedAt = Date.now();
21031
+ let lease;
21032
+ try {
21033
+ lease = await sdkSemaphore.acquire(signal);
21034
+ } catch (error) {
21035
+ requestMeta.sdkQueueWaitMs += Date.now() - acquireStartedAt;
21036
+ throw error;
21037
+ }
21038
+ requestMeta.sdkQueueWaitMs += lease.waitedMs;
21039
+ const startedAt = Date.now();
21040
+ requestMeta.currentSdkStartedAt = startedAt;
21041
+ let sdkQuery;
21042
+ try {
21043
+ sdkQuery = query(params);
21044
+ yield* guardUpstreamIdle(sdkQuery, UPSTREAM_IDLE_MS, (sinceLastMs) => claudeLog("upstream.stalled", { mode, sinceLastMs }));
21045
+ } finally {
21046
+ try {
21047
+ if (typeof sdkQuery?.close === "function")
21048
+ sdkQuery.close();
21049
+ } finally {
21050
+ requestMeta.sdkActiveDurationMs += Date.now() - startedAt;
21051
+ lease.release();
21052
+ }
21053
+ }
21054
+ }
20718
21055
  const pluginDir = finalConfig.pluginDir ?? join8(homedir7(), ".config", "meridian", "plugins");
20719
21056
  const pluginConfigPath = finalConfig.pluginConfigPath ?? join8(homedir7(), ".config", "meridian", "plugins.json");
20720
21057
  let loadedPlugins = [];
@@ -20737,7 +21074,6 @@ function createProxyServer(config = {}) {
20737
21074
  const PRIORITY_ASSIGNMENTS_MAX = 5000;
20738
21075
  const priorityAssignments = new AssignmentStore(PRIORITY_ASSIGNMENTS_MAX);
20739
21076
  const PRIORITY_DEFAULT_COOLDOWN_MS = 10 * 60000;
20740
- const PRIORITY_COOLDOWN_CAP_MS = 6 * 60 * 60000;
20741
21077
  function priorityProfileOrderSetting() {
20742
21078
  const env2 = process.env.MERIDIAN_PROFILE_ORDER;
20743
21079
  if (env2 && env2.trim())
@@ -20746,9 +21082,12 @@ function createProxyServer(config = {}) {
20746
21082
  return Array.isArray(setting) && setting.length > 0 ? setting : undefined;
20747
21083
  }
20748
21084
  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);
21085
+ const windows = rateLimitStore.getAll(profileId).map((e) => ({
21086
+ type: e.rateLimitType ?? "",
21087
+ resetsAt: e.resetsAt,
21088
+ exhausted: e.status === "rejected" || (e.utilization ?? 0) >= 1
21089
+ }));
21090
+ return resolveCooldownUntil(windows, now, PRIORITY_DEFAULT_COOLDOWN_MS);
20752
21091
  }
20753
21092
  function refinePriorityCooldown(profileId) {
20754
21093
  const target = getEffectiveProfiles(finalConfig.profiles).find((p) => p.id === profileId);
@@ -20757,14 +21096,15 @@ function createProxyServer(config = {}) {
20757
21096
  fetchOAuthUsage({ profileId, claudeConfigDir: target?.claudeConfigDir, force: true }).then((usage) => {
20758
21097
  if (!usage || usage.stale)
20759
21098
  return;
20760
- const fiveHour = usage.windows.find((w) => w.type === "five_hour");
20761
- if (!fiveHour || (fiveHour.utilization ?? 0) < 1)
20762
- return;
20763
21099
  const now = Date.now();
20764
- const resetsAt = fiveHour.resetsAt;
20765
- if (!resetsAt || resetsAt <= now)
21100
+ const windows = usage.windows.map((w) => ({
21101
+ type: w.type,
21102
+ resetsAt: w.resetsAt,
21103
+ exhausted: (w.utilization ?? 0) >= 1
21104
+ }));
21105
+ const until = resolveCooldownUntil(windows, now, 0);
21106
+ if (until <= now)
20766
21107
  return;
20767
- const until = Math.min(resetsAt, now + PRIORITY_COOLDOWN_CAP_MS);
20768
21108
  priorityExhaustion.mark(profileId, until, "rate_limit_error");
20769
21109
  claudeLog("priority.cooldown_refined", { profile: profileId, until, source: "oauth_usage" });
20770
21110
  }).catch((err) => {
@@ -20838,19 +21178,19 @@ function createProxyServer(config = {}) {
20838
21178
  reader.cancel(reason).catch(() => {});
20839
21179
  }
20840
21180
  });
20841
- return { failed: false, errorPayload: null, errorType: null, response: new Response(rest, { status: res.status, headers: res.headers }) };
21181
+ const response = new Response(rest, { status: res.status, headers: res.headers });
21182
+ const completion = responseCompletions.get(res);
21183
+ if (completion)
21184
+ responseCompletions.set(response, completion);
21185
+ return { failed: false, errorPayload: null, errorType: null, response };
20842
21186
  }
20843
- async function dispatchPriority(c, orderedCandidateIds, sessionKey, wantsStream) {
20844
- const bodyBuf = await c.req.arrayBuffer();
21187
+ async function dispatchPriority(c, body, requestMeta, orderedCandidateIds, sessionKey, wantsStream) {
20845
21188
  let lastError = null;
20846
21189
  let lastStatus = 429;
20847
21190
  let previous = null;
20848
21191
  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 }));
21192
+ for (const [attempt, candidate] of orderedCandidateIds.entries()) {
21193
+ const inner = await handleMessages(c, forkAttemptMeta(requestMeta, attempt), { body, forcedProfileId: candidate });
20854
21194
  const sniffed = await sniffAccountFailure(inner);
20855
21195
  if (!sniffed.failed) {
20856
21196
  if (sessionKey)
@@ -20861,6 +21201,7 @@ function createProxyServer(config = {}) {
20861
21201
  }
20862
21202
  return sniffed.response;
20863
21203
  }
21204
+ await responseCompletions.get(inner)?.catch(() => {});
20864
21205
  const reason = sniffed.errorType;
20865
21206
  const quotaRefusal = isQuotaRefusal(reason);
20866
21207
  const cooldownUntil = quotaRefusal ? priorityCooldownUntil(candidate, Date.now()) : Date.now() + PRIORITY_DEFAULT_COOLDOWN_MS;
@@ -20897,29 +21238,8 @@ data: ${JSON.stringify(lastError)}
20897
21238
  }
20898
21239
  return c.html(landingHtml);
20899
21240
  });
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();
21241
+ const handleMessages = async (c, requestMeta, options) => {
21242
+ const requestStartAt = requestMeta.queueEnteredAt;
20923
21243
  const requestAbort = linkRequestAbort(c.req.raw.signal);
20924
21244
  let streamOwnsAbortLink = false;
20925
21245
  return withClaudeLogContext({ requestId: requestMeta.requestId, endpoint: requestMeta.endpoint }, async () => {
@@ -20935,7 +21255,7 @@ data: ${JSON.stringify(lastError)}
20935
21255
  }
20936
21256
  return textPrompt;
20937
21257
  };
20938
- const body = await c.req.json();
21258
+ const body = options.body;
20939
21259
  if (!Array.isArray(body.messages)) {
20940
21260
  return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
20941
21261
  }
@@ -20952,7 +21272,7 @@ data: ${JSON.stringify(lastError)}
20952
21272
  }
20953
21273
  const outputFormat = parsedOutputFormat.value;
20954
21274
  const routingMode = getRoutingMode(process.env.MERIDIAN_ROUTING ?? getSetting("routing"));
20955
- if (routingMode === "priority" && !c.req.header("x-meridian-profile")) {
21275
+ if (routingMode === "priority" && !options.forcedProfileId && !c.req.header("x-meridian-profile")) {
20956
21276
  const effectivePool = getEffectiveProfiles(finalConfig.profiles);
20957
21277
  if (effectivePool.length > 1) {
20958
21278
  const { order, unknown } = resolvePriorityOrder(effectivePool.map((p) => p.id), priorityProfileOrderSetting());
@@ -20969,10 +21289,10 @@ data: ${JSON.stringify(lastError)}
20969
21289
  first = pick?.id ?? order[0];
20970
21290
  }
20971
21291
  const candidates = [first, ...order.filter((id) => id !== first && !priorityExhaustion.isExhausted(id))];
20972
- return dispatchPriority(c, candidates, sessionKey, body.stream === true);
21292
+ return dispatchPriority(c, body, requestMeta, candidates, sessionKey, body.stream === true);
20973
21293
  }
20974
21294
  }
20975
- const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined, routingMode === "sticky" ? { routingMode, stickySessionKey: adapter.getSessionId(c, body) } : undefined);
21295
+ 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
21296
  const authStatus = await getClaudeAuthStatusAsync(profile.id !== "default" ? profile.id : undefined, Object.keys(profile.env).length > 0 ? profile.env : undefined);
20977
21297
  const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
20978
21298
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
@@ -21061,26 +21381,63 @@ data: ${JSON.stringify(lastError)}
21061
21381
  const betas = betaFilter.forwarded;
21062
21382
  const agentSessionId = adapter.getSessionId(c, body);
21063
21383
  const profileSessionId = profile.id !== "default" && agentSessionId ? `${profile.id}:${agentSessionId}` : agentSessionId;
21384
+ const commitSessionTurn = () => {
21385
+ if (profileSessionId)
21386
+ requestMeta.sessionTurnLease?.markCommitted(profileSessionId);
21387
+ };
21064
21388
  const profileScopedCwd = profile.id !== "default" ? `${clientWorkingDirectory}::profile=${profile.id}` : clientWorkingDirectory;
21065
21389
  const lastMessage = Array.isArray(body.messages) ? body.messages[body.messages.length - 1] : undefined;
21066
21390
  const lastIsToolResult = Array.isArray(lastMessage?.content) && lastMessage.content.some((b) => b?.type === "tool_result");
21067
21391
  const isClientDrivenLoop = adapterBase !== "claude-code" && !agentSessionId && lastIsToolResult;
21068
21392
  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
21393
  let lineageResult = isIndependentSession ? { type: "diverged", reason: "independent-request" } : lookupSession(profileSessionId, body.messages || [], profileScopedCwd);
21081
21394
  if (lineageResult.type === "undo" && adapterBase === "opencode" && !agentSessionId) {
21082
21395
  lineageResult = { type: "diverged", reason: "missing-session-header" };
21083
21396
  }
21397
+ const declaresConcurrentFlow = requestSource?.startsWith("fork-") === true || requestSource?.startsWith("subagent-") === true;
21398
+ if (profileSessionId && !declaresConcurrentFlow && requestMeta.sessionTurnLease?.advancedWhileWaiting(profileSessionId) && lineageResult.type !== "continuation" && lineageResult.type !== "compaction") {
21399
+ const reason = lineageResult.type === "diverged" ? lineageResult.reason : lineageResult.type;
21400
+ const message = "This session advanced while the request was waiting. Retry with the latest conversation history or use a distinct session ID.";
21401
+ claudeLog("session.concurrent_conflict", {
21402
+ reason,
21403
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs
21404
+ });
21405
+ diagnosticLog2.session(`${requestMeta.requestId} session.concurrent_conflict reason=${reason} wait=${requestMeta.sessionQueueWaitMs}ms`, requestMeta.requestId);
21406
+ const conflictTotalMs = Date.now() - requestStartAt;
21407
+ const conflictQueueWaitMs = totalQueueWaitMs(requestMeta);
21408
+ telemetryStore2.record({
21409
+ requestId: requestMeta.requestId,
21410
+ timestamp: Date.now(),
21411
+ adapter: adapter.name,
21412
+ model,
21413
+ requestModel: requestedModel,
21414
+ mode: stream3 ? "stream" : "non-stream",
21415
+ isResume: false,
21416
+ isPassthrough: envBool("PASSTHROUGH"),
21417
+ hasDeferredTools: undefined,
21418
+ deferredToolCount: undefined,
21419
+ toolCount: body.tools?.length ?? 0,
21420
+ lineageType: lineageResult.type,
21421
+ messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
21422
+ sdkSessionId: undefined,
21423
+ status: 400,
21424
+ queueWaitMs: conflictQueueWaitMs,
21425
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
21426
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
21427
+ proxyOverheadMs: Math.max(0, conflictTotalMs - conflictQueueWaitMs),
21428
+ ttfbMs: null,
21429
+ upstreamDurationMs: 0,
21430
+ totalDurationMs: conflictTotalMs,
21431
+ contentBlocks: 0,
21432
+ textEvents: 0,
21433
+ error: "session_turn_conflict",
21434
+ profileId: profile.id
21435
+ });
21436
+ return new Response(JSON.stringify({ type: "error", error: { type: "invalid_request_error", message } }), {
21437
+ status: 400,
21438
+ headers: { "Content-Type": "application/json" }
21439
+ });
21440
+ }
21084
21441
  if (pipeline.some((t) => t.onSession)) {
21085
21442
  const mismatch = lineageResult.type === "diverged" ? lineageResult.mismatch : undefined;
21086
21443
  runTransformHook(pipeline, "onSession", {
@@ -21116,7 +21473,8 @@ data: ${JSON.stringify(lastError)}
21116
21473
  const lineageType = lineageResult.type === "diverged" && !cachedSession ? "new" : lineageResult.type;
21117
21474
  const msgCount = Array.isArray(body.messages) ? body.messages.length : 0;
21118
21475
  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}`;
21476
+ const sdkSnapshot = sdkSemaphore.snapshot;
21477
+ 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
21478
  plog(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
21121
21479
  diagnosticLog2.session(`${requestLogLine}`, requestMeta.requestId);
21122
21480
  if (lineageResult.type === "diverged" && profileSessionId && !isIndependentSession) {
@@ -21131,7 +21489,9 @@ data: ${JSON.stringify(lastError)}
21131
21489
  claudeLog("request.received", {
21132
21490
  model,
21133
21491
  stream: stream3,
21134
- queueWaitMs: requestMeta.queueStartedAt - requestMeta.queueEnteredAt,
21492
+ queueWaitMs: totalQueueWaitMs(requestMeta),
21493
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
21494
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
21135
21495
  messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
21136
21496
  hasSystemPrompt: Boolean(body.system)
21137
21497
  });
@@ -21421,7 +21781,7 @@ data: ${JSON.stringify(lastError)}
21421
21781
  const attemptStderrStart = stderrLines.length;
21422
21782
  turnGenerating = true;
21423
21783
  try {
21424
- for await (const event of query(buildQueryOptions({
21784
+ for await (const event of runSdkQueryAttempt(buildQueryOptions({
21425
21785
  prompt: makePrompt(),
21426
21786
  model,
21427
21787
  workingDirectory,
@@ -21463,7 +21823,7 @@ data: ${JSON.stringify(lastError)}
21463
21823
  sdkDebug: sdkFeatures.sdkDebug,
21464
21824
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
21465
21825
  advisorModel
21466
- }, requestAbort.controller))) {
21826
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "non_stream")) {
21467
21827
  if (event.type === "rate_limit_event") {
21468
21828
  rateLimitStore.record(profile.id, event.rate_limit_info);
21469
21829
  }
@@ -21509,7 +21869,7 @@ data: ${JSON.stringify(lastError)}
21509
21869
  sdkUuidMap.length = 0;
21510
21870
  for (let i = 0;i < allMessages.length; i++)
21511
21871
  sdkUuidMap.push(null);
21512
- yield* query(buildQueryOptions({
21872
+ yield* runSdkQueryAttempt(buildQueryOptions({
21513
21873
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
21514
21874
  model,
21515
21875
  workingDirectory,
@@ -21550,7 +21910,7 @@ data: ${JSON.stringify(lastError)}
21550
21910
  sdkDebug: sdkFeatures.sdkDebug,
21551
21911
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
21552
21912
  advisorModel
21553
- }, requestAbort.controller));
21913
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "non_stream_fresh");
21554
21914
  return;
21555
21915
  }
21556
21916
  if (isExtraUsageRequiredError(errMsg) && hasExtendedContext(model)) {
@@ -21578,7 +21938,7 @@ data: ${JSON.stringify(lastError)}
21578
21938
  sdkUuidMap.length = 0;
21579
21939
  for (let i = 0;i < allMessages.length; i++)
21580
21940
  sdkUuidMap.push(null);
21581
- yield* query(buildQueryOptions({
21941
+ yield* runSdkQueryAttempt(buildQueryOptions({
21582
21942
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
21583
21943
  model,
21584
21944
  workingDirectory,
@@ -21619,7 +21979,7 @@ data: ${JSON.stringify(lastError)}
21619
21979
  sdkDebug: sdkFeatures.sdkDebug,
21620
21980
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
21621
21981
  advisorModel
21622
- }, requestAbort.controller));
21982
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "non_stream_fresh");
21623
21983
  return;
21624
21984
  }
21625
21985
  if (isExpiredTokenError(errMsg) && !tokenRefreshed) {
@@ -21693,10 +22053,11 @@ data: ${JSON.stringify(lastError)}
21693
22053
  }
21694
22054
  if (!firstChunkAt) {
21695
22055
  firstChunkAt = Date.now();
22056
+ requestMeta.ttfbMs ??= firstChunkAt - (requestMeta.currentSdkStartedAt ?? firstChunkAt);
21696
22057
  claudeLog("upstream.first_chunk", {
21697
22058
  mode: "non_stream",
21698
22059
  model,
21699
- ttfbMs: firstChunkAt - upstreamStartAt
22060
+ ttfbMs: requestMeta.ttfbMs
21700
22061
  });
21701
22062
  }
21702
22063
  const isPassthroughTurn2 = passthrough && assistantMessages > 1 && contentBlocks.some((b) => b.type === "tool_use");
@@ -21875,7 +22236,7 @@ Subprocess stderr: ${stderrOutput}`;
21875
22236
  contentBlocks: contentBlocks.length,
21876
22237
  hasToolUse
21877
22238
  });
21878
- const nonStreamQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
22239
+ const nonStreamQueueWaitMs = totalQueueWaitMs(requestMeta);
21879
22240
  checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
21880
22241
  telemetryStore2.record({
21881
22242
  requestId: requestMeta.requestId,
@@ -21898,9 +22259,11 @@ Subprocess stderr: ${stderrOutput}`;
21898
22259
  sdkSessionId: currentSessionId || resumeSessionId,
21899
22260
  status: 200,
21900
22261
  queueWaitMs: nonStreamQueueWaitMs,
21901
- proxyOverheadMs: upstreamStartAt - requestStartAt - nonStreamQueueWaitMs,
21902
- ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
21903
- upstreamDurationMs: Date.now() - upstreamStartAt,
22262
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
22263
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
22264
+ proxyOverheadMs: Math.max(0, totalDurationMs - nonStreamQueueWaitMs - requestMeta.sdkActiveDurationMs),
22265
+ ttfbMs: requestMeta.ttfbMs ?? null,
22266
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
21904
22267
  totalDurationMs,
21905
22268
  contentBlocks: contentBlocks.length,
21906
22269
  textEvents: 0,
@@ -21921,6 +22284,7 @@ Subprocess stderr: ${stderrOutput}`;
21921
22284
  }
21922
22285
  if (currentSessionId && !isIndependentSession && !sawDuplicateToolUse) {
21923
22286
  storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, earlyStopFired ? nextPassthroughResumeUuid : null);
22287
+ commitSessionTurn();
21924
22288
  }
21925
22289
  const responseSessionId = currentSessionId || resumeSessionId || `session_${Date.now()}`;
21926
22290
  return new Response(JSON.stringify({
@@ -21944,222 +22308,139 @@ Subprocess stderr: ${stderrOutput}`;
21944
22308
  });
21945
22309
  }
21946
22310
  const encoder = new TextEncoder;
22311
+ let resolveStreamCompletion = () => {};
22312
+ const streamCompletion = new Promise((resolve3) => {
22313
+ resolveStreamCompletion = resolve3;
22314
+ });
21947
22315
  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 });
22316
+ start(controller) {
22317
+ return (async () => {
22318
+ const upstreamStartAt = Date.now();
22319
+ let firstChunkAt;
22320
+ let heartbeatCount = 0;
22321
+ let streamEventsSeen = 0;
22322
+ let eventsForwarded = 0;
22323
+ let textEventsForwarded = 0;
22324
+ let textCharsForwarded = 0;
22325
+ let bytesSent = 0;
22326
+ let streamClosed = false;
22327
+ let awaitingEarlyStopDrain = false;
22328
+ claudeLog("upstream.start", { mode: "stream", model });
22329
+ const safeEnqueue = (payload, source) => {
22330
+ if (streamClosed)
21971
22331
  return false;
22332
+ try {
22333
+ controller.enqueue(payload);
22334
+ bytesSent += payload.byteLength;
22335
+ return true;
22336
+ } catch (error) {
22337
+ if (isClosedControllerError(error)) {
22338
+ streamClosed = true;
22339
+ claudeLog("stream.client_closed", { source, streamEventsSeen, eventsForwarded });
22340
+ return false;
22341
+ }
22342
+ claudeLog("stream.enqueue_failed", {
22343
+ source,
22344
+ error: error instanceof Error ? error.message : String(error)
22345
+ });
22346
+ throw error;
21972
22347
  }
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
22348
+ };
22349
+ const sdkUuidMap = cachedSession?.sdkMessageUuids ? [...cachedSession.sdkMessageUuids] : [];
22350
+ while (sdkUuidMap.length < allMessages.length)
22351
+ sdkUuidMap.push(null);
22352
+ let messageStartEmitted = false;
22353
+ let lastUsage;
22354
+ let hasStructuredOutput = false;
22355
+ let structuredOutput;
22356
+ let nextPassthroughResumeUuid;
22357
+ const silentTurnRecoveryEnabled = env("SILENT_TURN_RECOVERY") !== "0";
22358
+ let silentTurnRecoveryAttempted = false;
22359
+ let silentTurnRecovered = false;
22360
+ const streamedToolUseIds = new Set;
22361
+ let pendingTerminalDelta = null;
22362
+ let terminalDeltaSent = false;
22363
+ const sendTerminalDelta = (stopReasonOverride) => {
22364
+ if (terminalDeltaSent)
22365
+ return;
22366
+ const payload = stopReasonOverride ? encoder.encode(`event: message_delta
21998
22367
  data: ${JSON.stringify({
21999
- type: "message_delta",
22000
- delta: { stop_reason: stopReasonOverride, stop_sequence: null },
22001
- usage: { output_tokens: lastUsage?.output_tokens ?? 0 }
22002
- })}
22368
+ type: "message_delta",
22369
+ delta: { stop_reason: stopReasonOverride, stop_sequence: null },
22370
+ usage: { output_tokens: lastUsage?.output_tokens ?? 0 }
22371
+ })}
22003
22372
 
22004
22373
  `) : 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
22374
+ if (!payload)
22375
+ return;
22376
+ terminalDeltaSent = true;
22377
+ if (safeEnqueue(payload, "terminal_message_delta"))
22378
+ eventsForwarded += 1;
22379
+ };
22380
+ const openClientBlocks = new Set;
22381
+ let pendingEarlyStop = false;
22382
+ let pendingEarlyStopAt = 0;
22383
+ const fireEarlyStop = (reason) => {
22384
+ earlyStopFired = true;
22385
+ claudeLog("passthrough.early_stop", {
22386
+ mode: "stream",
22387
+ captured: capturedToolUses.length,
22388
+ drained: awaitingEarlyStopDrain,
22389
+ reason,
22390
+ deferredMs: pendingEarlyStopAt ? Date.now() - pendingEarlyStopAt : 0
22391
+ });
22392
+ pendingEarlyStop = false;
22393
+ flushOpenClientBlocks("early_stop");
22394
+ sendTerminalDelta("tool_use");
22395
+ safeEnqueue(encoder.encode(`event: message_stop
22028
22396
  data: ${JSON.stringify({ type: "message_stop" })}
22029
22397
 
22030
22398
  `), "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
22399
+ requestAbort.abort("passthrough turn complete");
22400
+ awaitingEarlyStopDrain = false;
22401
+ if (!streamClosed) {
22402
+ streamClosed = true;
22403
+ try {
22404
+ controller.close();
22405
+ } catch {}
22406
+ }
22407
+ };
22408
+ const flushOpenClientBlocks = (source) => {
22409
+ if (openClientBlocks.size === 0)
22410
+ return;
22411
+ recordEnvelopeViolations([...openClientBlocks].map((idx) => ({
22412
+ type: "dangling_block",
22413
+ detail: `content block ${idx} still open at ${source} close`
22414
+ })));
22415
+ claudeLog("stream.dangling_blocks_closed", { source, count: openClientBlocks.size });
22416
+ for (const idx of openClientBlocks) {
22417
+ safeEnqueue(encoder.encode(`event: content_block_stop
22050
22418
  data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
22051
22419
 
22052
22420
  `), `${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
22421
  }
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),
22422
+ openClientBlocks.clear();
22423
+ };
22424
+ let currentSessionId;
22425
+ try {
22426
+ const MAX_RATE_LIMIT_RETRIES = 2;
22427
+ const RATE_LIMIT_BASE_DELAY_MS = 1000;
22428
+ const response = async function* () {
22429
+ let rateLimitRetries = 0;
22430
+ if (profileCredentialStore) {
22431
+ await ensureFreshToken(profileCredentialStore).catch(() => {});
22432
+ }
22433
+ let tokenRefreshed = false;
22434
+ let didFreshBaseRetry = false;
22435
+ let resumeRefusalRetries = 0;
22436
+ let busySessionFork = false;
22437
+ let sawUnresumableRefusal = false;
22438
+ while (true) {
22439
+ let didYieldClientEvent = false;
22440
+ const attemptStderrStart = stderrLines.length;
22441
+ try {
22442
+ for await (const event of runSdkQueryAttempt(buildQueryOptions({
22443
+ prompt: makePrompt(),
22163
22444
  model,
22164
22445
  workingDirectory,
22165
22446
  clientWorkingDirectory,
@@ -22172,9 +22453,10 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
22172
22453
  cleanEnv: profileEnv,
22173
22454
  envOverrides,
22174
22455
  hasDeferredTools,
22175
- resumeSessionId: undefined,
22176
- isUndo: false,
22177
- resumeSessionAtUuid: undefined,
22456
+ resumeSessionId,
22457
+ isUndo,
22458
+ resumeSessionAtUuid: undoRollbackUuid ?? passthroughResumeUuid,
22459
+ forkSession: busySessionFork || Boolean(passthroughResumeUuid) || undefined,
22178
22460
  sdkHooks,
22179
22461
  blockedTools: pipelineCtx.blockedTools,
22180
22462
  incompatibleTools: pipelineCtx.incompatibleTools,
@@ -22199,857 +22481,993 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
22199
22481
  sdkDebug: sdkFeatures.sdkDebug,
22200
22482
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22201
22483
  advisorModel
22202
- }, requestAbort.controller));
22484
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "stream")) {
22485
+ if (event.type === "rate_limit_event") {
22486
+ rateLimitStore.record(profile.id, event.rate_limit_info);
22487
+ }
22488
+ if (event.type === "stream_event") {
22489
+ didYieldClientEvent = true;
22490
+ }
22491
+ yield event;
22492
+ }
22203
22493
  return;
22494
+ } catch (error) {
22495
+ const errMsg = error instanceof Error ? error.message : String(error);
22496
+ if (didYieldClientEvent)
22497
+ throw error;
22498
+ const refusal = classifyResumeRefusal(error, resumeSessionId ? stderrLines.slice(attemptStderrStart).join(`
22499
+ `) : undefined);
22500
+ if (refusal === "unresumable")
22501
+ sawUnresumableRefusal = true;
22502
+ if (resumeSessionId && (refusal === "busy" || refusal === "unresumable")) {
22503
+ if (resumeRefusalRetries < RESUME_REFUSAL_MAX_RETRIES) {
22504
+ resumeRefusalRetries++;
22505
+ claudeLog("session.resume_retry", { mode: "stream", refusal, attempt: resumeRefusalRetries, resumeSessionId });
22506
+ plog(`[PROXY] ${requestMeta.requestId} resume refused (${refusal}), retrying ${resumeRefusalRetries}/${RESUME_REFUSAL_MAX_RETRIES}`);
22507
+ await new Promise((resolve3) => setTimeout(resolve3, RESUME_REFUSAL_RETRY_DELAY_MS * resumeRefusalRetries));
22508
+ continue;
22509
+ }
22510
+ if (refusal === "busy" && !busySessionFork) {
22511
+ busySessionFork = true;
22512
+ claudeLog("session.busy_fork", { mode: "stream", resumeSessionId });
22513
+ plog(`[PROXY] ${requestMeta.requestId} session still busy after ${RESUME_REFUSAL_MAX_RETRIES} retries — forking session`);
22514
+ continue;
22515
+ }
22516
+ }
22517
+ if (refusal === "missing-message" || sawUnresumableRefusal) {
22518
+ claudeLog("session.resume_replay", {
22519
+ mode: "stream",
22520
+ refusal,
22521
+ rollbackUuid: undoRollbackUuid,
22522
+ resumeSessionId
22523
+ });
22524
+ plog(`[PROXY] ${requestMeta.requestId} session unusable (${refusal}), evicting and replaying as fresh session`);
22525
+ evictSession(profileSessionId, profileScopedCwd, allMessages);
22526
+ sdkUuidMap.length = 0;
22527
+ for (let i = 0;i < allMessages.length; i++)
22528
+ sdkUuidMap.push(null);
22529
+ yield* runSdkQueryAttempt(buildQueryOptions({
22530
+ prompt: buildFreshPrompt(allMessages, sanitizeOpts),
22531
+ model,
22532
+ workingDirectory,
22533
+ clientWorkingDirectory,
22534
+ systemContext,
22535
+ claudeExecutable,
22536
+ passthrough,
22537
+ stream: true,
22538
+ sdkAgents,
22539
+ passthroughMcp,
22540
+ cleanEnv: profileEnv,
22541
+ envOverrides,
22542
+ hasDeferredTools,
22543
+ resumeSessionId: undefined,
22544
+ isUndo: false,
22545
+ resumeSessionAtUuid: undefined,
22546
+ sdkHooks,
22547
+ blockedTools: pipelineCtx.blockedTools,
22548
+ incompatibleTools: pipelineCtx.incompatibleTools,
22549
+ mcpServerName: adapter.getMcpServerName(),
22550
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
22551
+ onStderr,
22552
+ effort,
22553
+ thinking,
22554
+ taskBudget,
22555
+ outputFormat,
22556
+ betas,
22557
+ settingSources,
22558
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
22559
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
22560
+ memory: sdkFeatures.memory,
22561
+ dreaming: sdkFeatures.dreaming,
22562
+ sharedMemory: sdkFeatures.sharedMemory,
22563
+ webFetchPreflight: sdkFeatures.webFetchPreflight,
22564
+ claudeAiConnectors: sdkFeatures.claudeAiConnectors,
22565
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
22566
+ fallbackModel: sdkFeatures.fallbackModel,
22567
+ sdkDebug: sdkFeatures.sdkDebug,
22568
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22569
+ advisorModel
22570
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "stream_fresh");
22571
+ return;
22572
+ }
22573
+ if (isExtraUsageRequiredError(errMsg) && hasExtendedContext(model)) {
22574
+ const from = model;
22575
+ model = stripExtendedContext(model);
22576
+ recordExtendedContextUnavailable();
22577
+ claudeLog("upstream.context_fallback", {
22578
+ mode: "stream",
22579
+ from,
22580
+ to: model,
22581
+ reason: "extra_usage_required"
22582
+ });
22583
+ plog(`[PROXY] ${requestMeta.requestId} extra usage required for [1m], falling back to ${model} (skipping [1m] for 1h)`);
22584
+ continue;
22585
+ }
22586
+ if (isExtraUsageRequiredError(errMsg) && resumeSessionId && !didFreshBaseRetry) {
22587
+ didFreshBaseRetry = true;
22588
+ claudeLog("upstream.session_fallback", {
22589
+ mode: "stream",
22590
+ model,
22591
+ reason: "extra_usage_required_resume"
22592
+ });
22593
+ plog(`[PROXY] ${requestMeta.requestId} extra usage persisted on resumed ${model}, retrying as fresh session`);
22594
+ evictSession(profileSessionId, profileScopedCwd, allMessages);
22595
+ sdkUuidMap.length = 0;
22596
+ for (let i = 0;i < allMessages.length; i++)
22597
+ sdkUuidMap.push(null);
22598
+ yield* runSdkQueryAttempt(buildQueryOptions({
22599
+ prompt: buildFreshPrompt(allMessages, sanitizeOpts),
22600
+ model,
22601
+ workingDirectory,
22602
+ clientWorkingDirectory,
22603
+ systemContext,
22604
+ claudeExecutable,
22605
+ passthrough,
22606
+ stream: true,
22607
+ sdkAgents,
22608
+ passthroughMcp,
22609
+ cleanEnv: profileEnv,
22610
+ envOverrides,
22611
+ hasDeferredTools,
22612
+ resumeSessionId: undefined,
22613
+ isUndo: false,
22614
+ resumeSessionAtUuid: undefined,
22615
+ sdkHooks,
22616
+ blockedTools: pipelineCtx.blockedTools,
22617
+ incompatibleTools: pipelineCtx.incompatibleTools,
22618
+ mcpServerName: adapter.getMcpServerName(),
22619
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
22620
+ onStderr,
22621
+ effort,
22622
+ thinking,
22623
+ taskBudget,
22624
+ outputFormat,
22625
+ betas,
22626
+ settingSources,
22627
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
22628
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
22629
+ memory: sdkFeatures.memory,
22630
+ dreaming: sdkFeatures.dreaming,
22631
+ sharedMemory: sdkFeatures.sharedMemory,
22632
+ webFetchPreflight: sdkFeatures.webFetchPreflight,
22633
+ claudeAiConnectors: sdkFeatures.claudeAiConnectors,
22634
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
22635
+ fallbackModel: sdkFeatures.fallbackModel,
22636
+ sdkDebug: sdkFeatures.sdkDebug,
22637
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
22638
+ advisorModel
22639
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "stream_fresh");
22640
+ return;
22641
+ }
22642
+ if (isExpiredTokenError(errMsg) && !tokenRefreshed) {
22643
+ tokenRefreshed = true;
22644
+ const refreshed = profileCredentialStore ? await refreshOAuthToken(profileCredentialStore) : false;
22645
+ if (refreshed) {
22646
+ claudeLog("token_refresh.retrying", { mode: "stream" });
22647
+ plog(`[PROXY] ${requestMeta.requestId} OAuth token expired — refreshed, retrying`);
22648
+ continue;
22649
+ }
22650
+ }
22651
+ if (isRateLimitError(errMsg)) {
22652
+ if (hasExtendedContext(model)) {
22653
+ const from = model;
22654
+ model = stripExtendedContext(model);
22655
+ claudeLog("upstream.context_fallback", {
22656
+ mode: "stream",
22657
+ from,
22658
+ to: model,
22659
+ reason: "rate_limit"
22660
+ });
22661
+ plog(`[PROXY] ${requestMeta.requestId} rate-limited on [1m], retrying with ${model}`);
22662
+ continue;
22663
+ }
22664
+ if (rateLimitRetries < MAX_RATE_LIMIT_RETRIES) {
22665
+ rateLimitRetries++;
22666
+ const delay = RATE_LIMIT_BASE_DELAY_MS * Math.pow(2, rateLimitRetries - 1);
22667
+ claudeLog("upstream.rate_limit_backoff", {
22668
+ mode: "stream",
22669
+ model,
22670
+ attempt: rateLimitRetries,
22671
+ maxAttempts: MAX_RATE_LIMIT_RETRIES,
22672
+ delayMs: delay
22673
+ });
22674
+ plog(`[PROXY] ${requestMeta.requestId} rate-limited on ${model}, retry ${rateLimitRetries}/${MAX_RATE_LIMIT_RETRIES} in ${delay}ms`);
22675
+ await new Promise((r) => setTimeout(r, delay));
22676
+ continue;
22677
+ }
22678
+ }
22679
+ throw error;
22204
22680
  }
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;
22217
- }
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));
22681
+ }
22682
+ }();
22683
+ const heartbeat = setInterval(() => {
22684
+ heartbeatCount += 1;
22685
+ try {
22686
+ const payload = encoder.encode(`: ping
22687
+
22688
+ `);
22689
+ if (!safeEnqueue(payload, "heartbeat")) {
22690
+ clearInterval(heartbeat);
22272
22691
  return;
22273
22692
  }
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;
22693
+ if (heartbeatCount % 5 === 0) {
22694
+ claudeLog("stream.heartbeat", { count: heartbeatCount });
22695
+ }
22696
+ } catch (error) {
22697
+ claudeLog("stream.heartbeat_failed", {
22698
+ count: heartbeatCount,
22699
+ error: error instanceof Error ? error.message : String(error)
22700
+ });
22701
+ clearInterval(heartbeat);
22702
+ }
22703
+ }, 15000);
22704
+ const skipBlockIndices = new Set;
22705
+ const taskToolBlockIndices = new Set;
22706
+ const taskToolJsonBuffer = new Map;
22707
+ let nextClientBlockIndex = 0;
22708
+ const sdkToClientIndex = new Map;
22709
+ const guardedResponse = guardUpstreamIdle(response, UPSTREAM_IDLE_MS, (sinceLastMs) => claudeLog("upstream.stalled", {
22710
+ mode: "stream",
22711
+ model,
22712
+ sinceLastMs,
22713
+ streamEventsSeen,
22714
+ firstChunkAt: firstChunkAt ?? null
22715
+ }));
22716
+ try {
22717
+ for await (const message of guardedResponse) {
22718
+ if (streamClosed && !awaitingEarlyStopDrain) {
22719
+ break;
22720
+ }
22721
+ if (message.session_id) {
22722
+ currentSessionId = message.session_id;
22723
+ }
22724
+ if (message.type === "assistant" && message.uuid) {
22725
+ sdkUuidMap.push(message.uuid);
22726
+ }
22727
+ nextPassthroughResumeUuid = resumeBoundaryUuid(message) ?? nextPassthroughResumeUuid;
22728
+ if (earlyStopEnabled) {
22729
+ if (message.type === "assistant") {
22730
+ noteAssistantContent(earlyStop, message.message?.content);
22731
+ } else if (message.type === "user") {
22732
+ noteUserContent(earlyStop, message.message?.content);
22733
+ if (shouldEarlyStop(earlyStop) && streamedToolUseIds.size > 0) {
22734
+ if (openClientBlocks.size > 0) {
22735
+ if (!pendingEarlyStop) {
22736
+ pendingEarlyStop = true;
22737
+ pendingEarlyStopAt = Date.now();
22738
+ claudeLog("passthrough.early_stop_deferred", {
22739
+ openBlocks: openClientBlocks.size,
22740
+ captured: capturedToolUses.length
22741
+ });
22742
+ }
22743
+ } else {
22744
+ fireEarlyStop("immediate");
22745
+ break;
22746
+ }
22747
+ }
22281
22748
  }
22282
22749
  }
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;
22750
+ if (message.type === "result") {
22751
+ const resultUsage = message.usage;
22752
+ if (resultUsage)
22753
+ lastUsage = { ...lastUsage, ...resultUsage };
22754
+ if (outputFormat && "structured_output" in message) {
22755
+ hasStructuredOutput = true;
22756
+ structuredOutput = message.structured_output;
22295
22757
  }
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", {
22758
+ }
22759
+ if (message.type === "stream_event") {
22760
+ streamEventsSeen += 1;
22761
+ if (!firstChunkAt) {
22762
+ firstChunkAt = Date.now();
22763
+ requestMeta.ttfbMs ??= firstChunkAt - (requestMeta.currentSdkStartedAt ?? firstChunkAt);
22764
+ claudeLog("upstream.first_chunk", {
22300
22765
  mode: "stream",
22301
22766
  model,
22302
- attempt: rateLimitRetries,
22303
- maxAttempts: MAX_RATE_LIMIT_RETRIES,
22304
- delayMs: delay
22767
+ ttfbMs: requestMeta.ttfbMs
22305
22768
  });
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
22769
  }
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;
22770
+ const event = message.event;
22771
+ const eventType = event.type;
22772
+ const eventIndex = event.index;
22773
+ if (eventType === "message_delta" || eventType === "message_stop" || eventType === "message_start" && messageStartEmitted) {
22774
+ releaseHeldDenies(eventType);
22775
+ }
22776
+ if (eventType === "message_start") {
22777
+ turnGenerating = true;
22778
+ }
22779
+ if (outputFormat) {
22780
+ if (eventType === "message_start") {
22781
+ const startUsage = event.message?.usage;
22782
+ if (startUsage)
22783
+ lastUsage = { ...lastUsage, ...startUsage };
22784
+ } else if (eventType === "message_delta") {
22785
+ const deltaUsage = event.usage;
22786
+ if (deltaUsage)
22787
+ lastUsage = { ...lastUsage, ...deltaUsage };
22378
22788
  }
22789
+ continue;
22379
22790
  }
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
22791
  if (eventType === "message_start") {
22792
+ skipBlockIndices.clear();
22793
+ sdkToClientIndex.clear();
22412
22794
  const startUsage = event.message?.usage;
22413
22795
  if (startUsage)
22414
22796
  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
22797
+ if (messageStartEmitted) {
22798
+ if (passthrough && streamedToolUseIds.size > 0) {
22799
+ flushOpenClientBlocks("turn2_suppression");
22800
+ sendTerminalDelta("tool_use");
22801
+ safeEnqueue(encoder.encode(`event: message_stop
22433
22802
  data: ${JSON.stringify({ type: "message_stop" })}
22434
22803
 
22435
22804
  `), "passthrough_turn2_stop");
22436
- claudeLog("passthrough.turn2_suppressed", { mode: "stream", toolUses: streamedToolUseIds.size });
22437
- streamClosed = true;
22438
- controller.close();
22439
- break;
22805
+ claudeLog("passthrough.turn2_suppressed", { mode: "stream", toolUses: streamedToolUseIds.size });
22806
+ streamClosed = true;
22807
+ controller.close();
22808
+ break;
22809
+ }
22810
+ continue;
22440
22811
  }
22441
- continue;
22812
+ messageStartEmitted = true;
22442
22813
  }
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 });
22814
+ if (eventType === "message_stop") {
22454
22815
  continue;
22455
22816
  }
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") {
22817
+ if (eventType === "content_block_start") {
22818
+ const block = event.content_block;
22819
+ if (pipelineCtx.hidesInternalTools && (block?.type === "tool_use" || (block?.type === "thinking" || block?.type === "redacted_thinking") && !sdkFeatures.thinkingPassthrough)) {
22464
22820
  if (eventIndex !== undefined)
22465
22821
  skipBlockIndices.add(eventIndex);
22822
+ claudeLog("internal_tool.hidden", { mode: "stream", type: block?.type, name: block?.name, index: eventIndex });
22466
22823
  continue;
22467
22824
  }
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__")) {
22825
+ if (passthrough && !pipelineCtx.supportsThinking && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
22473
22826
  if (eventIndex !== undefined)
22474
22827
  skipBlockIndices.add(eventIndex);
22828
+ claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
22475
22829
  continue;
22476
- } else if (passthrough && block.id) {
22477
- streamedToolUseIds.add(block.id);
22478
22830
  }
22479
- if (passthrough && eventIndex !== undefined && block.name.toLowerCase() === "task") {
22480
- taskToolBlockIndices.add(eventIndex);
22831
+ if (block?.type === "tool_use" && typeof block.name === "string") {
22832
+ if (block.name === "ToolSearch") {
22833
+ if (eventIndex !== undefined)
22834
+ skipBlockIndices.add(eventIndex);
22835
+ continue;
22836
+ }
22837
+ if (passthrough && block.name.startsWith(PASSTHROUGH_MCP_PREFIX)) {
22838
+ block.name = stripMcpPrefix(block.name);
22839
+ if (block.id)
22840
+ streamedToolUseIds.add(block.id);
22841
+ } else if (block.name.startsWith("mcp__")) {
22842
+ if (eventIndex !== undefined)
22843
+ skipBlockIndices.add(eventIndex);
22844
+ continue;
22845
+ } else if (passthrough && block.id) {
22846
+ streamedToolUseIds.add(block.id);
22847
+ }
22848
+ if (passthrough && eventIndex !== undefined && block.name.toLowerCase() === "task") {
22849
+ taskToolBlockIndices.add(eventIndex);
22850
+ }
22851
+ }
22852
+ if (eventIndex !== undefined) {
22853
+ sdkToClientIndex.set(eventIndex, nextClientBlockIndex++);
22481
22854
  }
22482
22855
  }
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) {
22856
+ if (eventIndex !== undefined && skipBlockIndices.has(eventIndex)) {
22499
22857
  continue;
22500
22858
  }
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);
22859
+ if (eventIndex !== undefined && sdkToClientIndex.has(eventIndex)) {
22860
+ event.index = sdkToClientIndex.get(eventIndex);
22861
+ }
22862
+ if (eventType === "message_delta") {
22863
+ const deltaUsage = event.usage;
22864
+ if (deltaUsage)
22865
+ lastUsage = { ...lastUsage, ...deltaUsage };
22866
+ const stopReason = event.delta?.stop_reason;
22867
+ if (stopReason === "tool_use" && skipBlockIndices.size > 0) {
22508
22868
  continue;
22509
22869
  }
22510
22870
  }
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
22871
+ if (passthrough && eventIndex !== undefined && taskToolBlockIndices.has(eventIndex)) {
22872
+ if (eventType === "content_block_delta") {
22873
+ const delta = event.delta;
22874
+ if (delta?.type === "input_json_delta" && typeof delta.partial_json === "string") {
22875
+ const prev = taskToolJsonBuffer.get(eventIndex) ?? "";
22876
+ taskToolJsonBuffer.set(eventIndex, prev + delta.partial_json);
22877
+ continue;
22878
+ }
22879
+ }
22880
+ if (eventType === "content_block_stop") {
22881
+ const buffered = taskToolJsonBuffer.get(eventIndex);
22882
+ if (buffered) {
22883
+ let fixed = buffered;
22884
+ try {
22885
+ const parsed = JSON.parse(buffered);
22886
+ if (typeof parsed.subagent_type === "string") {
22887
+ parsed.subagent_type = resolveAgentAlias(parsed.subagent_type, validAgentNames);
22888
+ }
22889
+ fixed = JSON.stringify(parsed);
22890
+ } catch {}
22891
+ const clientIdx = sdkToClientIndex.get(eventIndex) ?? eventIndex;
22892
+ safeEnqueue(encoder.encode(`event: content_block_delta
22524
22893
  data: ${JSON.stringify({
22525
- type: "content_block_delta",
22526
- index: clientIdx,
22527
- delta: { type: "input_json_delta", partial_json: fixed }
22528
- })}
22894
+ type: "content_block_delta",
22895
+ index: clientIdx,
22896
+ delta: { type: "input_json_delta", partial_json: fixed }
22897
+ })}
22529
22898
 
22530
22899
  `), "task_tool_fixed_delta");
22531
- taskToolJsonBuffer.delete(eventIndex);
22900
+ taskToolJsonBuffer.delete(eventIndex);
22901
+ }
22532
22902
  }
22533
22903
  }
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}
22904
+ if (eventType === "content_block_delta" && event.delta?.type === "text_delta" && shouldInjectSilentTurn({
22905
+ raw: env("DEBUG_FORCE_SILENT_TURN"),
22906
+ sessionId: agentSessionId
22907
+ })) {
22908
+ claudeLog("debug.silent_turn_injected", { sessionId: agentSessionId });
22909
+ continue;
22910
+ }
22911
+ stripNonStandardStreamFields(event);
22912
+ const payload = encoder.encode(`event: ${eventType}
22544
22913
  data: ${JSON.stringify(event)}
22545
22914
 
22546
22915
  `);
22547
- if (eventType === "message_delta") {
22548
- pendingTerminalDelta = payload;
22549
- } else {
22550
- if (!safeEnqueue(payload, `stream_event:${eventType}`)) {
22551
- break;
22916
+ if (eventType === "message_delta") {
22917
+ pendingTerminalDelta = payload;
22918
+ } else {
22919
+ if (!safeEnqueue(payload, `stream_event:${eventType}`)) {
22920
+ break;
22921
+ }
22922
+ eventsForwarded += 1;
22552
22923
  }
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;
22924
+ if (eventType === "content_block_start") {
22925
+ const idx = event.index;
22926
+ if (typeof idx === "number")
22927
+ openClientBlocks.add(idx);
22928
+ } else if (eventType === "content_block_stop") {
22929
+ const idx = event.index;
22930
+ if (typeof idx === "number")
22931
+ openClientBlocks.delete(idx);
22932
+ if (pendingEarlyStop && openClientBlocks.size === 0) {
22933
+ fireEarlyStop("blocks_closed");
22934
+ break;
22935
+ }
22566
22936
  }
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
22937
+ if (passthrough && eventType === "message_delta" && event.delta?.stop_reason === "tool_use" && streamedToolUseIds.size > 0) {
22938
+ flushOpenClientBlocks("drain_close");
22939
+ sendTerminalDelta();
22940
+ safeEnqueue(encoder.encode(`event: message_stop
22572
22941
  data: ${JSON.stringify({ type: "message_stop" })}
22573
22942
 
22574
22943
  `), "passthrough_tool_stream_stop");
22575
- streamClosed = true;
22576
- controller.close();
22577
- if (earlyStopEnabled) {
22578
- awaitingEarlyStopDrain = true;
22579
- continue;
22944
+ streamClosed = true;
22945
+ controller.close();
22946
+ if (earlyStopEnabled) {
22947
+ awaitingEarlyStopDrain = true;
22948
+ continue;
22949
+ }
22950
+ break;
22580
22951
  }
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;
22952
+ if (eventType === "content_block_delta") {
22953
+ const delta = event.delta;
22954
+ if (delta?.type === "text_delta") {
22955
+ textEventsForwarded += 1;
22956
+ if (typeof delta.text === "string")
22957
+ textCharsForwarded += delta.text.length;
22958
+ }
22589
22959
  }
22590
22960
  }
22591
22961
  }
22962
+ } finally {
22963
+ clearInterval(heartbeat);
22964
+ releaseHeldDenies("stream_loop_exit");
22592
22965
  }
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 }
22966
+ if (outputFormat) {
22967
+ if (!hasStructuredOutput) {
22968
+ throw new Error("Structured output was requested but the SDK returned no structured_output result");
22615
22969
  }
22616
- })}
22970
+ const text = structuredOutputText(structuredOutput);
22971
+ const messageId = `msg_${Date.now()}`;
22972
+ safeEnqueue(encoder.encode(`event: message_start
22973
+ data: ${JSON.stringify({
22974
+ type: "message_start",
22975
+ message: {
22976
+ id: messageId,
22977
+ type: "message",
22978
+ role: "assistant",
22979
+ content: [],
22980
+ model: body.model,
22981
+ stop_reason: null,
22982
+ stop_sequence: null,
22983
+ usage: { input_tokens: lastUsage?.input_tokens ?? 0, output_tokens: 0 }
22984
+ }
22985
+ })}
22617
22986
 
22618
22987
  `), "structured_message_start");
22619
- safeEnqueue(encoder.encode(`event: content_block_start
22988
+ safeEnqueue(encoder.encode(`event: content_block_start
22620
22989
  data: ${JSON.stringify({
22621
- type: "content_block_start",
22622
- index: 0,
22623
- content_block: { type: "text", text: "" }
22624
- })}
22990
+ type: "content_block_start",
22991
+ index: 0,
22992
+ content_block: { type: "text", text: "" }
22993
+ })}
22625
22994
 
22626
22995
  `), "structured_block_start");
22627
- safeEnqueue(encoder.encode(`event: content_block_delta
22996
+ safeEnqueue(encoder.encode(`event: content_block_delta
22628
22997
  data: ${JSON.stringify({
22629
- type: "content_block_delta",
22630
- index: 0,
22631
- delta: { type: "text_delta", text }
22632
- })}
22998
+ type: "content_block_delta",
22999
+ index: 0,
23000
+ delta: { type: "text_delta", text }
23001
+ })}
22633
23002
 
22634
23003
  `), "structured_text_delta");
22635
- safeEnqueue(encoder.encode(`event: content_block_stop
23004
+ safeEnqueue(encoder.encode(`event: content_block_stop
22636
23005
  data: ${JSON.stringify({ type: "content_block_stop", index: 0 })}
22637
23006
 
22638
23007
  `), "structured_block_stop");
22639
- safeEnqueue(encoder.encode(`event: message_delta
23008
+ safeEnqueue(encoder.encode(`event: message_delta
22640
23009
  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
- })}
23010
+ type: "message_delta",
23011
+ delta: { stop_reason: "end_turn", stop_sequence: null },
23012
+ usage: { output_tokens: lastUsage?.output_tokens ?? 0 }
23013
+ })}
22645
23014
 
22646
23015
  `), "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", {
23016
+ messageStartEmitted = true;
23017
+ eventsForwarded += 5;
23018
+ textEventsForwarded += 1;
23019
+ textCharsForwarded += text.length;
23020
+ }
23021
+ if (passthrough) {
23022
+ recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
23023
+ }
23024
+ claudeLog("upstream.completed", {
22695
23025
  mode: "stream",
22696
- kind: preRecoveryOutcome.kind,
22697
- reason: preRecoveryOutcome.kind === "silent" ? preRecoveryOutcome.reason : undefined,
22698
- sdkSessionId: currentSessionId || resumeSessionId
23026
+ model,
23027
+ durationMs: Date.now() - upstreamStartAt,
23028
+ streamEventsSeen,
23029
+ eventsForwarded,
23030
+ textEventsForwarded
22699
23031
  });
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}
23032
+ if (lastUsage)
23033
+ logUsage(requestMeta.requestId, lastUsage);
23034
+ const sessId = currentSessionId || resumeSessionId;
23035
+ if (sessId && discoveredTools.size > 0) {
23036
+ if (!sessionDiscoveredTools.has(sessId))
23037
+ sessionDiscoveredTools.set(sessId, new Set);
23038
+ for (const t of discoveredTools)
23039
+ sessionDiscoveredTools.get(sessId).add(t);
23040
+ const newNames = [...discoveredTools].join(", ");
23041
+ const allNames = [...sessionDiscoveredTools.get(sessId)];
23042
+ plog(`[PROXY] ${requestMeta.requestId} discovered=${discoveredTools.size} (${newNames}) session_total=${allNames.length}`);
23043
+ }
23044
+ if (currentSessionId && !isIndependentSession && !sawDuplicateToolUse) {
23045
+ storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, earlyStopFired ? nextPassthroughResumeUuid : null);
23046
+ commitSessionTurn();
23047
+ }
23048
+ const classifyNow = () => classifyTurnOutcome({
23049
+ textEvents: textEventsForwarded,
23050
+ toolUses: streamedToolUseIds.size,
23051
+ blocksForwarded: eventsForwarded
23052
+ });
23053
+ const preRecoveryOutcome = classifyNow();
23054
+ if (!streamClosed && messageStartEmitted && shouldAttemptRecovery({
23055
+ outcome: preRecoveryOutcome,
23056
+ alreadyAttempted: silentTurnRecoveryAttempted,
23057
+ clientGone: streamClosed,
23058
+ sessionId: currentSessionId || resumeSessionId,
23059
+ enabled: silentTurnRecoveryEnabled
23060
+ })) {
23061
+ silentTurnRecoveryAttempted = true;
23062
+ const capturedBeforeRecovery = capturedToolUses.length;
23063
+ claudeLog("response.silent_turn_recovery", {
23064
+ mode: "stream",
23065
+ kind: preRecoveryOutcome.kind,
23066
+ reason: preRecoveryOutcome.kind === "silent" ? preRecoveryOutcome.reason : undefined,
23067
+ sdkSessionId: currentSessionId || resumeSessionId
23068
+ });
23069
+ const recoveryLifter = createRecoveryLifter(() => nextClientBlockIndex++);
23070
+ let recoverySessionId;
23071
+ let recoveryBoundaryUuid;
23072
+ try {
23073
+ for await (const event of runSdkQueryAttempt(buildQueryOptions({
23074
+ prompt: SILENT_TURN_NUDGE,
23075
+ model,
23076
+ workingDirectory,
23077
+ clientWorkingDirectory,
23078
+ systemContext,
23079
+ claudeExecutable,
23080
+ passthrough,
23081
+ stream: true,
23082
+ sdkAgents,
23083
+ passthroughMcp,
23084
+ cleanEnv: profileEnv,
23085
+ envOverrides,
23086
+ hasDeferredTools,
23087
+ resumeSessionId: currentSessionId || resumeSessionId,
23088
+ isUndo: false,
23089
+ resumeSessionAtUuid: nextPassthroughResumeUuid,
23090
+ forkSession: true,
23091
+ sdkHooks,
23092
+ blockedTools: pipelineCtx.blockedTools,
23093
+ incompatibleTools: pipelineCtx.incompatibleTools,
23094
+ mcpServerName: adapter.getMcpServerName(),
23095
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
23096
+ onStderr,
23097
+ effort,
23098
+ thinking,
23099
+ taskBudget,
23100
+ outputFormat,
23101
+ betas,
23102
+ settingSources,
23103
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
23104
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
23105
+ memory: sdkFeatures.memory,
23106
+ dreaming: sdkFeatures.dreaming,
23107
+ sharedMemory: sdkFeatures.sharedMemory,
23108
+ webFetchPreflight: sdkFeatures.webFetchPreflight,
23109
+ claudeAiConnectors: sdkFeatures.claudeAiConnectors,
23110
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
23111
+ fallbackModel: sdkFeatures.fallbackModel,
23112
+ sdkDebug: sdkFeatures.sdkDebug,
23113
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
23114
+ advisorModel
23115
+ }, requestAbort.controller), requestAbort.controller.signal, requestMeta, "silent_recovery")) {
23116
+ const recoveryMessage = event;
23117
+ if (recoveryMessage.session_id)
23118
+ recoverySessionId = recoveryMessage.session_id;
23119
+ recoveryBoundaryUuid = resumeBoundaryUuid(recoveryMessage) ?? recoveryBoundaryUuid;
23120
+ if (recoveryMessage.type !== "stream_event")
23121
+ continue;
23122
+ const lifted = recoveryLifter.lift(event.event);
23123
+ if (!lifted)
23124
+ continue;
23125
+ safeEnqueue(encoder.encode(`event: ${lifted.frame.type}
22757
23126
  data: ${JSON.stringify(lifted.frame)}
22758
23127
 
22759
23128
  `), `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;
23129
+ if (lifted.kind === "block_start") {
23130
+ eventsForwarded += 1;
23131
+ } else if (lifted.kind === "text_delta") {
23132
+ textEventsForwarded += 1;
23133
+ textCharsForwarded += lifted.textChars;
23134
+ silentTurnRecovered = true;
23135
+ }
22766
23136
  }
23137
+ } catch (recoveryError) {
23138
+ claudeLog("response.silent_turn_recovery_failed", {
23139
+ mode: "stream",
23140
+ error: recoveryError instanceof Error ? recoveryError.message : String(recoveryError)
23141
+ });
22767
23142
  }
22768
- } catch (recoveryError) {
22769
- claudeLog("response.silent_turn_recovery_failed", {
23143
+ if (capturedToolUses.length > capturedBeforeRecovery) {
23144
+ silentTurnRecovered = true;
23145
+ }
23146
+ if (silentTurnRecovered && recoverySessionId && !isIndependentSession && !sawDuplicateToolUse) {
23147
+ currentSessionId = recoverySessionId;
23148
+ nextPassthroughResumeUuid = recoveryBoundaryUuid;
23149
+ sdkUuidMap.length = 0;
23150
+ for (let i = 0;i < allMessages.length; i++)
23151
+ sdkUuidMap.push(null);
23152
+ storeSession(profileSessionId, body.messages || [], recoverySessionId, profileScopedCwd, sdkUuidMap, lastUsage, recoveryBoundaryUuid ?? null);
23153
+ commitSessionTurn();
23154
+ }
23155
+ claudeLog("response.silent_turn_recovery_result", {
22770
23156
  mode: "stream",
22771
- error: recoveryError instanceof Error ? recoveryError.message : String(recoveryError)
23157
+ recovered: silentTurnRecovered,
23158
+ textEvents: textEventsForwarded,
23159
+ forkedSession: recoverySessionId ?? null
22772
23160
  });
23161
+ if (silentTurnRecovered && preRecoveryOutcome.kind === "silent") {
23162
+ diagnosticLog2.session(`${requestMeta.requestId} silent_turn reason=${preRecoveryOutcome.reason} ` + `blocks=${eventsForwarded} out=${lastUsage?.output_tokens ?? 0} ` + `recovery=succeeded`, requestMeta.requestId);
23163
+ }
22773
23164
  }
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
23165
+ if (!streamClosed) {
23166
+ const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
23167
+ if (passthrough && unseenToolUses.length > 0 && messageStartEmitted) {
23168
+ for (let i = 0;i < unseenToolUses.length; i++) {
23169
+ const tu = unseenToolUses[i];
23170
+ const blockIndex = eventsForwarded + i;
23171
+ streamedToolUseIds.add(tu.id);
23172
+ safeEnqueue(encoder.encode(`event: content_block_start
22803
23173
  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
- })}
23174
+ type: "content_block_start",
23175
+ index: blockIndex,
23176
+ content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
23177
+ })}
22808
23178
 
22809
23179
  `), "passthrough_tool_block_start");
22810
- safeEnqueue(encoder.encode(`event: content_block_delta
23180
+ safeEnqueue(encoder.encode(`event: content_block_delta
22811
23181
  data: ${JSON.stringify({
22812
- type: "content_block_delta",
22813
- index: blockIndex,
22814
- delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
22815
- })}
23182
+ type: "content_block_delta",
23183
+ index: blockIndex,
23184
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
23185
+ })}
22816
23186
 
22817
23187
  `), "passthrough_tool_input");
22818
- safeEnqueue(encoder.encode(`event: content_block_stop
23188
+ safeEnqueue(encoder.encode(`event: content_block_stop
22819
23189
  data: ${JSON.stringify({
22820
- type: "content_block_stop",
22821
- index: blockIndex
22822
- })}
23190
+ type: "content_block_stop",
23191
+ index: blockIndex
23192
+ })}
22823
23193
 
22824
23194
  `), "passthrough_tool_block_stop");
23195
+ }
23196
+ sendTerminalDelta("tool_use");
22825
23197
  }
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
23198
+ if (trackFileChanges && passthrough && pipelineCtx.extractFileChangesFromToolUse) {
23199
+ const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
23200
+ fileChanges.push(...passthroughChanges);
23201
+ }
23202
+ if (trackFileChanges) {
23203
+ const streamFileChangeSummary = formatFileChangeSummary(fileChanges);
23204
+ if (streamFileChangeSummary && messageStartEmitted) {
23205
+ const fcBlockIndex = nextClientBlockIndex++;
23206
+ safeEnqueue(encoder.encode(`event: content_block_start
22837
23207
  data: ${JSON.stringify({
22838
- type: "content_block_start",
22839
- index: fcBlockIndex,
22840
- content_block: { type: "text", text: "" }
22841
- })}
23208
+ type: "content_block_start",
23209
+ index: fcBlockIndex,
23210
+ content_block: { type: "text", text: "" }
23211
+ })}
22842
23212
 
22843
23213
  `), "file_changes_block_start");
22844
- safeEnqueue(encoder.encode(`event: content_block_delta
23214
+ safeEnqueue(encoder.encode(`event: content_block_delta
22845
23215
  data: ${JSON.stringify({
22846
- type: "content_block_delta",
22847
- index: fcBlockIndex,
22848
- delta: { type: "text_delta", text: streamFileChangeSummary }
22849
- })}
23216
+ type: "content_block_delta",
23217
+ index: fcBlockIndex,
23218
+ delta: { type: "text_delta", text: streamFileChangeSummary }
23219
+ })}
22850
23220
 
22851
23221
  `), "file_changes_text_delta");
22852
- safeEnqueue(encoder.encode(`event: content_block_stop
23222
+ safeEnqueue(encoder.encode(`event: content_block_stop
22853
23223
  data: ${JSON.stringify({
22854
- type: "content_block_stop",
22855
- index: fcBlockIndex
22856
- })}
23224
+ type: "content_block_stop",
23225
+ index: fcBlockIndex
23226
+ })}
22857
23227
 
22858
23228
  `), "file_changes_block_stop");
22859
- claudeLog("response.file_changes", { mode: "stream", count: fileChanges.length });
23229
+ claudeLog("response.file_changes", { mode: "stream", count: fileChanges.length });
23230
+ }
22860
23231
  }
22861
- }
22862
- if (messageStartEmitted) {
22863
- sendTerminalDelta();
22864
- safeEnqueue(encoder.encode(`event: message_stop
23232
+ if (messageStartEmitted) {
23233
+ sendTerminalDelta();
23234
+ safeEnqueue(encoder.encode(`event: message_stop
22865
23235
  data: {"type":"message_stop"}
22866
23236
 
22867
23237
  `), "final_message_stop");
23238
+ }
23239
+ try {
23240
+ controller.close();
23241
+ } catch {}
23242
+ streamClosed = true;
23243
+ claudeLog("stream.ended", {
23244
+ model,
23245
+ streamEventsSeen,
23246
+ eventsForwarded,
23247
+ textEventsForwarded,
23248
+ bytesSent,
23249
+ durationMs: Date.now() - requestStartAt
23250
+ });
22868
23251
  }
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", {
23252
+ {
23253
+ const streamTotalDurationMs = Date.now() - requestStartAt;
23254
+ claudeLog("response.completed", {
23255
+ mode: "stream",
22932
23256
  model,
22933
- reason: turnOutcome.reason,
23257
+ durationMs: streamTotalDurationMs,
22934
23258
  streamEventsSeen,
22935
23259
  eventsForwarded,
23260
+ textEventsForwarded
23261
+ });
23262
+ const streamQueueWaitMs = totalQueueWaitMs(requestMeta);
23263
+ checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
23264
+ telemetryStore2.record({
23265
+ requestId: requestMeta.requestId,
23266
+ timestamp: Date.now(),
23267
+ adapter: adapter.name,
23268
+ profileId: profile.id,
23269
+ requestSource,
23270
+ model,
23271
+ requestModel: body.model || undefined,
23272
+ mode: "stream",
23273
+ isResume,
23274
+ isPassthrough: passthrough,
23275
+ hasDeferredTools,
23276
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
23277
+ toolCount,
23278
+ discoveredTools: discoveredTools.size > 0 ? [...discoveredTools] : undefined,
23279
+ sessionDiscoveredCount: sessionDiscoveredTools.get(currentSessionId || resumeSessionId || "")?.size,
23280
+ lineageType,
23281
+ messageCount: allMessages.length,
23282
+ sdkSessionId: currentSessionId || resumeSessionId,
23283
+ status: 200,
23284
+ queueWaitMs: streamQueueWaitMs,
23285
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23286
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23287
+ proxyOverheadMs: Math.max(0, streamTotalDurationMs - streamQueueWaitMs - requestMeta.sdkActiveDurationMs),
23288
+ ttfbMs: requestMeta.ttfbMs ?? null,
23289
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23290
+ totalDurationMs: streamTotalDurationMs,
23291
+ contentBlocks: eventsForwarded,
23292
+ textEvents: textEventsForwarded,
23293
+ error: null,
23294
+ inputTokens: lastUsage?.input_tokens,
22936
23295
  outputTokens: lastUsage?.output_tokens,
22937
- recovered: silentTurnRecovered,
22938
- recoveryAttempted: silentTurnRecoveryAttempted
23296
+ cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
23297
+ cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
23298
+ cacheHitRate: computeCacheHitRate(lastUsage),
23299
+ ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
22939
23300
  });
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);
23301
+ const turnOutcome = classifyNow();
23302
+ if (turnOutcome.kind === "silent") {
23303
+ claudeLog("response.silent_turn", {
23304
+ model,
23305
+ reason: turnOutcome.reason,
23306
+ streamEventsSeen,
23307
+ eventsForwarded,
23308
+ outputTokens: lastUsage?.output_tokens,
23309
+ recovered: silentTurnRecovered,
23310
+ recoveryAttempted: silentTurnRecoveryAttempted
23311
+ });
23312
+ diagnosticLog2.session(`${requestMeta.requestId} silent_turn reason=${turnOutcome.reason} ` + `blocks=${eventsForwarded} out=${lastUsage?.output_tokens ?? 0} ` + `recovery=${silentTurnRecoveryAttempted ? silentTurnRecovered ? "succeeded" : "failed" : "off"}`, requestMeta.requestId);
23313
+ }
22941
23314
  }
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 || []);
23315
+ } catch (error) {
23316
+ if (isClosedControllerError(error)) {
23317
+ streamClosed = true;
23318
+ claudeLog("stream.client_closed", {
23319
+ source: "stream_catch",
23320
+ streamEventsSeen,
23321
+ eventsForwarded,
23322
+ textEventsForwarded,
23323
+ durationMs: Date.now() - requestStartAt
23324
+ });
23325
+ const disposition = clientAbortDisposition({
23326
+ isIndependentSession,
23327
+ profileSessionId,
23328
+ currentSessionId,
23329
+ sawDuplicateToolUse,
23330
+ resumeBoundaryUuid: nextPassthroughResumeUuid,
23331
+ passthrough
23332
+ });
23333
+ if (disposition.action === "store" && currentSessionId) {
23334
+ storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage, disposition.resumeUuid);
23335
+ commitSessionTurn();
23336
+ } else if (disposition.action === "evict") {
23337
+ evictSession(profileSessionId, profileScopedCwd, body.messages || []);
23338
+ }
23339
+ claudeLog("passthrough.client_abort_settled", { action: disposition.action });
23340
+ return;
22965
23341
  }
22966
- claudeLog("passthrough.client_abort_settled", { action: disposition.action });
22967
- resolvePendingStore();
22968
- return;
22969
- }
22970
- resolvePendingStore();
22971
- const stderrOutput = stderrLines.join(`
23342
+ const stderrOutput = stderrLines.join(`
22972
23343
  `).trim();
22973
- if (stderrOutput && error instanceof Error && !error.message.includes(stderrOutput)) {
22974
- error.message = `${error.message}
23344
+ if (stderrOutput && error instanceof Error && !error.message.includes(stderrOutput)) {
23345
+ error.message = `${error.message}
22975
23346
  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, {
23347
+ }
23348
+ const errMsg = error instanceof Error ? error.message : String(error);
23349
+ claudeLog("upstream.failed", {
23350
+ mode: "stream",
23002
23351
  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
23352
+ durationMs: Date.now() - upstreamStartAt,
23353
+ streamEventsSeen,
23354
+ textEventsForwarded,
23355
+ error: errMsg,
23356
+ ...stderrOutput ? { stderr: stderrOutput } : {}
23357
+ });
23358
+ const streamErr = error instanceof UpstreamIdleError ? {
23359
+ status: 504,
23360
+ type: "upstream_timeout",
23361
+ message: `Upstream stalled: no data for ${error.sinceLastMs}ms`
23362
+ } : classifyError(errMsg, model);
23363
+ claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
23364
+ const sdkTerm = extractSdkTermination(errMsg);
23365
+ const canRecoverAsToolUse = canRecoverCapturedToolUses({
23366
+ reason: sdkTerm.reason,
23367
+ passthrough,
23368
+ capturedToolUses: capturedToolUses.length,
23369
+ abortIsOurs: sawDuplicateToolUse || earlyStopFired
23370
+ }) && messageStartEmitted;
23371
+ if (canRecoverAsToolUse) {
23372
+ diagnosticLog2.session(`${requestMeta.requestId} sdk_termination_recovered ${formatSdkTermination(sdkTerm, {
23373
+ model,
23374
+ requestSource,
23375
+ isResume,
23376
+ hasDeferredTools,
23377
+ sdkSessionId: resumeSessionId
23378
+ })} captured=${capturedToolUses.length}`, requestMeta.requestId);
23379
+ flushOpenClientBlocks("recovery");
23380
+ const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
23381
+ for (let i = 0;i < unseenToolUses.length; i++) {
23382
+ const tu = unseenToolUses[i];
23383
+ const blockIndex = eventsForwarded + i;
23384
+ streamedToolUseIds.add(tu.id);
23385
+ safeEnqueue(encoder.encode(`event: content_block_start
23015
23386
  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
- })}
23387
+ type: "content_block_start",
23388
+ index: blockIndex,
23389
+ content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
23390
+ })}
23020
23391
 
23021
23392
  `), "recover_tool_block_start");
23022
- safeEnqueue(encoder.encode(`event: content_block_delta
23393
+ safeEnqueue(encoder.encode(`event: content_block_delta
23023
23394
  data: ${JSON.stringify({
23024
- type: "content_block_delta",
23025
- index: blockIndex,
23026
- delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
23027
- })}
23395
+ type: "content_block_delta",
23396
+ index: blockIndex,
23397
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
23398
+ })}
23028
23399
 
23029
23400
  `), "recover_tool_input");
23030
- safeEnqueue(encoder.encode(`event: content_block_stop
23401
+ safeEnqueue(encoder.encode(`event: content_block_stop
23031
23402
  data: ${JSON.stringify({
23032
- type: "content_block_stop",
23033
- index: blockIndex
23034
- })}
23403
+ type: "content_block_stop",
23404
+ index: blockIndex
23405
+ })}
23035
23406
 
23036
23407
  `), "recover_tool_block_stop");
23037
- }
23038
- safeEnqueue(encoder.encode(`event: message_delta
23408
+ }
23409
+ safeEnqueue(encoder.encode(`event: message_delta
23039
23410
  data: ${JSON.stringify({
23040
- type: "message_delta",
23041
- delta: { stop_reason: "tool_use", stop_sequence: null },
23042
- usage: { output_tokens: 0 }
23043
- })}
23411
+ type: "message_delta",
23412
+ delta: { stop_reason: "tool_use", stop_sequence: null },
23413
+ usage: { output_tokens: 0 }
23414
+ })}
23044
23415
 
23045
23416
  `), "recover_message_delta");
23046
- safeEnqueue(encoder.encode(`event: message_stop
23417
+ safeEnqueue(encoder.encode(`event: message_stop
23047
23418
  data: {"type":"message_stop"}
23048
23419
 
23049
23420
  `), "recover_message_stop");
23050
- recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
23051
- const recoverTotalMs = Date.now() - requestStartAt;
23052
- const recoverQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
23421
+ recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
23422
+ const recoverTotalMs = Date.now() - requestStartAt;
23423
+ const recoverQueueWaitMs = totalQueueWaitMs(requestMeta);
23424
+ telemetryStore2.record({
23425
+ requestId: requestMeta.requestId,
23426
+ timestamp: Date.now(),
23427
+ adapter: adapter.name,
23428
+ profileId: profile.id,
23429
+ requestSource,
23430
+ model,
23431
+ requestModel: body.model || undefined,
23432
+ mode: "stream",
23433
+ isResume,
23434
+ isPassthrough: passthrough,
23435
+ hasDeferredTools,
23436
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
23437
+ toolCount,
23438
+ lineageType,
23439
+ messageCount: allMessages.length,
23440
+ sdkSessionId: resumeSessionId,
23441
+ status: 200,
23442
+ queueWaitMs: recoverQueueWaitMs,
23443
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23444
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23445
+ proxyOverheadMs: Math.max(0, recoverTotalMs - recoverQueueWaitMs - requestMeta.sdkActiveDurationMs),
23446
+ ttfbMs: requestMeta.ttfbMs ?? null,
23447
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23448
+ totalDurationMs: recoverTotalMs,
23449
+ contentBlocks: eventsForwarded + unseenToolUses.length,
23450
+ textEvents: textEventsForwarded,
23451
+ error: null,
23452
+ ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
23453
+ });
23454
+ if (!streamClosed) {
23455
+ try {
23456
+ controller.close();
23457
+ } catch {}
23458
+ streamClosed = true;
23459
+ }
23460
+ return;
23461
+ }
23462
+ diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
23463
+ model,
23464
+ requestSource,
23465
+ isResume,
23466
+ hasDeferredTools,
23467
+ sdkSessionId: resumeSessionId
23468
+ })}`, requestMeta.requestId);
23469
+ const streamErrTotalMs = Date.now() - requestStartAt;
23470
+ const streamErrQueueWaitMs = totalQueueWaitMs(requestMeta);
23053
23471
  telemetryStore2.record({
23054
23472
  requestId: requestMeta.requestId,
23055
23473
  timestamp: Date.now(),
@@ -23067,106 +23485,66 @@ data: {"type":"message_stop"}
23067
23485
  lineageType,
23068
23486
  messageCount: allMessages.length,
23069
23487
  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,
23488
+ status: streamErr.status,
23489
+ queueWaitMs: streamErrQueueWaitMs,
23490
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23491
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23492
+ proxyOverheadMs: Math.max(0, streamErrTotalMs - streamErrQueueWaitMs - requestMeta.sdkActiveDurationMs),
23493
+ ttfbMs: requestMeta.ttfbMs ?? null,
23494
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23495
+ totalDurationMs: streamErrTotalMs,
23496
+ contentBlocks: eventsForwarded,
23130
23497
  textEvents: textEventsForwarded,
23131
- classified: streamErr.type
23498
+ error: streamErr.type
23132
23499
  });
23133
- safeEnqueue(encoder.encode(`event: message_delta
23500
+ if (messageStartEmitted) {
23501
+ const errorStopReason = "max_tokens";
23502
+ claudeLog("response.error_envelope", {
23503
+ mode: "stream",
23504
+ stopReason: errorStopReason,
23505
+ textEvents: textEventsForwarded,
23506
+ classified: streamErr.type
23507
+ });
23508
+ safeEnqueue(encoder.encode(`event: message_delta
23134
23509
  data: ${JSON.stringify({
23135
- type: "message_delta",
23136
- delta: { stop_reason: errorStopReason, stop_sequence: null },
23137
- usage: { output_tokens: 0 }
23138
- })}
23510
+ type: "message_delta",
23511
+ delta: { stop_reason: errorStopReason, stop_sequence: null },
23512
+ usage: { output_tokens: 0 }
23513
+ })}
23139
23514
 
23140
23515
  `), "error_message_delta");
23141
- safeEnqueue(encoder.encode(`event: error
23516
+ safeEnqueue(encoder.encode(`event: error
23142
23517
  data: ${JSON.stringify({
23143
- type: "error",
23144
- error: { type: streamErr.type, message: streamErr.message }
23145
- })}
23518
+ type: "error",
23519
+ error: { type: streamErr.type, message: streamErr.message }
23520
+ })}
23146
23521
 
23147
23522
  `), "error_event_before_stop");
23148
- safeEnqueue(encoder.encode(`event: message_stop
23523
+ safeEnqueue(encoder.encode(`event: message_stop
23149
23524
  data: {"type":"message_stop"}
23150
23525
 
23151
23526
  `), "error_message_stop");
23152
- } else {
23153
- safeEnqueue(encoder.encode(`event: error
23527
+ } else {
23528
+ safeEnqueue(encoder.encode(`event: error
23154
23529
  data: ${JSON.stringify({
23155
- type: "error",
23156
- error: { type: streamErr.type, message: streamErr.message }
23157
- })}
23530
+ type: "error",
23531
+ error: { type: streamErr.type, message: streamErr.message }
23532
+ })}
23158
23533
 
23159
23534
  `), "error_event");
23535
+ }
23536
+ if (!streamClosed) {
23537
+ try {
23538
+ controller.close();
23539
+ } catch {}
23540
+ streamClosed = true;
23541
+ }
23542
+ } finally {
23543
+ requestAbort.detach();
23160
23544
  }
23161
- if (!streamClosed) {
23162
- try {
23163
- controller.close();
23164
- } catch {}
23165
- streamClosed = true;
23166
- }
23167
- } finally {
23168
- requestAbort.detach();
23169
- }
23545
+ })().finally(() => {
23546
+ resolveStreamCompletion();
23547
+ });
23170
23548
  },
23171
23549
  cancel(reason) {
23172
23550
  requestAbort.abort(reason);
@@ -23175,7 +23553,7 @@ data: ${JSON.stringify({
23175
23553
  });
23176
23554
  const streamSessionId = resumeSessionId || `session_${Date.now()}`;
23177
23555
  streamOwnsAbortLink = true;
23178
- return new Response(readable, {
23556
+ const streamResponse = new Response(readable, {
23179
23557
  headers: {
23180
23558
  "Content-Type": "text/event-stream",
23181
23559
  "Cache-Control": "no-cache",
@@ -23183,19 +23561,22 @@ data: ${JSON.stringify({
23183
23561
  "X-Claude-Session-ID": streamSessionId
23184
23562
  }
23185
23563
  });
23564
+ responseCompletions.set(streamResponse, streamCompletion);
23565
+ return streamResponse;
23186
23566
  } catch (error) {
23187
23567
  const errMsg = error instanceof Error ? error.message : String(error);
23188
23568
  claudeLog("error.unhandled", {
23189
23569
  durationMs: Date.now() - requestStartAt,
23190
23570
  error: errMsg
23191
23571
  });
23192
- const classified = classifyError(errMsg);
23572
+ const classified = requestAbort.controller.signal.aborted ? { status: 499, type: "request_cancelled", message: "The request was cancelled" } : classifyError(errMsg);
23193
23573
  claudeLog("proxy.error", { error: errMsg, classified: classified.type });
23194
23574
  const sdkTerm = extractSdkTermination(errMsg);
23195
23575
  diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
23196
23576
  requestSource: c.req.header("x-meridian-source")?.slice(0, 64) || undefined
23197
23577
  })}`, requestMeta.requestId);
23198
- const errorQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
23578
+ const errorQueueWaitMs = totalQueueWaitMs(requestMeta);
23579
+ const errorTotalMs = Date.now() - requestStartAt;
23199
23580
  telemetryStore2.record({
23200
23581
  requestId: requestMeta.requestId,
23201
23582
  timestamp: Date.now(),
@@ -23213,10 +23594,12 @@ data: ${JSON.stringify({
23213
23594
  sdkSessionId: undefined,
23214
23595
  status: classified.status,
23215
23596
  queueWaitMs: errorQueueWaitMs,
23216
- proxyOverheadMs: Date.now() - requestStartAt - errorQueueWaitMs,
23597
+ sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
23598
+ sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
23599
+ proxyOverheadMs: Math.max(0, errorTotalMs - errorQueueWaitMs - requestMeta.sdkActiveDurationMs),
23217
23600
  ttfbMs: null,
23218
- upstreamDurationMs: Date.now() - requestStartAt,
23219
- totalDurationMs: Date.now() - requestStartAt,
23601
+ upstreamDurationMs: requestMeta.sdkActiveDurationMs,
23602
+ totalDurationMs: errorTotalMs,
23220
23603
  contentBlocks: 0,
23221
23604
  textEvents: 0,
23222
23605
  error: classified.type
@@ -23229,19 +23612,122 @@ data: ${JSON.stringify({
23229
23612
  });
23230
23613
  };
23231
23614
  const handleWithQueue = async (c, endpoint) => {
23615
+ if (draining && c.req.header("x-meridian-internal-hop") !== internalHopToken) {
23616
+ return drainingResponse();
23617
+ }
23232
23618
  const requestId = c.req.header("x-request-id") || randomUUID();
23233
23619
  const queueEnteredAt = Date.now();
23234
23620
  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();
23621
+ let sessionTurnLease;
23622
+ let finished = false;
23623
+ let leaseReleased = false;
23624
+ let leaseWatchdog;
23625
+ inFlightRequests++;
23626
+ const releaseSessionTurn = (forced) => {
23627
+ if (leaseReleased || !sessionTurnLease)
23628
+ return;
23629
+ leaseReleased = true;
23630
+ if (leaseWatchdog)
23631
+ clearTimeout(leaseWatchdog);
23632
+ if (forced) {
23633
+ claudeLog("session.turn_lease_forced", { requestId, heldMs: SESSION_TURN_MAX_HOLD_MS });
23634
+ plog(`[PROXY] ${requestId} session turn lease force-released after ${SESSION_TURN_MAX_HOLD_MS}ms`);
23635
+ }
23636
+ sessionTurnLease.release();
23637
+ };
23638
+ const finishRequest = () => {
23639
+ if (finished)
23640
+ return;
23641
+ finished = true;
23642
+ releaseSessionTurn(false);
23643
+ inFlightRequests--;
23644
+ };
23645
+ let body;
23241
23646
  try {
23242
- return await insideSessionSlot.run({ queueEnteredAt, queueStartedAt }, () => handleMessages(c, { requestId, endpoint, queueEnteredAt, queueStartedAt }));
23243
- } finally {
23244
- releaseSession();
23647
+ try {
23648
+ body = await c.req.json();
23649
+ } catch (error) {
23650
+ if (c.req.raw.signal.aborted || error instanceof Error && error.name === "AbortError") {
23651
+ finishRequest();
23652
+ return new Response(JSON.stringify({
23653
+ type: "error",
23654
+ error: { type: "request_cancelled", message: "The request was cancelled" }
23655
+ }), { status: 499, headers: { "Content-Type": "application/json" } });
23656
+ }
23657
+ finishRequest();
23658
+ return new Response(JSON.stringify({
23659
+ type: "error",
23660
+ error: { type: "invalid_request_error", message: "Request body must be valid JSON" }
23661
+ }), { status: 400, headers: { "Content-Type": "application/json" } });
23662
+ }
23663
+ if (Array.isArray(body?.messages)) {
23664
+ const adapter = detectAdapter(c);
23665
+ const agentSessionId = adapter.getSessionId(c, body);
23666
+ if (agentSessionId) {
23667
+ try {
23668
+ sessionTurnLease = await processSessionTurns.acquire(`session:${agentSessionId}`, c.req.raw.signal);
23669
+ leaseWatchdog = setTimeout(() => releaseSessionTurn(true), SESSION_TURN_MAX_HOLD_MS);
23670
+ leaseWatchdog.unref?.();
23671
+ } catch (error) {
23672
+ if (c.req.raw.signal.aborted || error instanceof Error && error.name === "AbortError") {
23673
+ const cancelledWaitMs = Date.now() - queueEnteredAt;
23674
+ telemetryStore2.record({
23675
+ requestId,
23676
+ timestamp: Date.now(),
23677
+ adapter: adapter.name,
23678
+ model: "unknown",
23679
+ requestModel: undefined,
23680
+ mode: "non-stream",
23681
+ isResume: false,
23682
+ isPassthrough: envBool("PASSTHROUGH"),
23683
+ hasDeferredTools: undefined,
23684
+ deferredToolCount: undefined,
23685
+ toolCount: undefined,
23686
+ lineageType: undefined,
23687
+ messageCount: Array.isArray(body?.messages) ? body.messages.length : undefined,
23688
+ sdkSessionId: undefined,
23689
+ status: 499,
23690
+ queueWaitMs: cancelledWaitMs,
23691
+ sessionQueueWaitMs: cancelledWaitMs,
23692
+ sdkQueueWaitMs: 0,
23693
+ proxyOverheadMs: 0,
23694
+ ttfbMs: null,
23695
+ upstreamDurationMs: 0,
23696
+ totalDurationMs: cancelledWaitMs,
23697
+ contentBlocks: 0,
23698
+ textEvents: 0,
23699
+ error: "request_cancelled"
23700
+ });
23701
+ finishRequest();
23702
+ return new Response(JSON.stringify({
23703
+ type: "error",
23704
+ error: { type: "request_cancelled", message: "The request was cancelled" }
23705
+ }), { status: 499, headers: { "Content-Type": "application/json" } });
23706
+ }
23707
+ throw error;
23708
+ }
23709
+ }
23710
+ }
23711
+ const requestMeta = {
23712
+ requestId,
23713
+ endpoint,
23714
+ queueEnteredAt,
23715
+ sessionQueueWaitMs: sessionTurnLease?.waitedMs ?? 0,
23716
+ sdkQueueWaitMs: 0,
23717
+ sdkActiveDurationMs: 0,
23718
+ sessionTurnLease
23719
+ };
23720
+ const response = await handleMessages(c, requestMeta, { body });
23721
+ const completion = responseCompletions.get(response);
23722
+ if (completion) {
23723
+ completion.finally(finishRequest).catch(() => {});
23724
+ } else {
23725
+ finishRequest();
23726
+ }
23727
+ return response;
23728
+ } catch (error) {
23729
+ finishRequest();
23730
+ throw error;
23245
23731
  }
23246
23732
  };
23247
23733
  app.post("/v1/messages", (c) => handleWithQueue(c, "/v1/messages"));
@@ -23344,6 +23830,13 @@ data: ${JSON.stringify({
23344
23830
  });
23345
23831
  });
23346
23832
  app.get("/health", async (c) => {
23833
+ if (draining) {
23834
+ return c.json({
23835
+ status: "draining",
23836
+ version: serverVersion,
23837
+ message: "Meridian is shutting down; route new requests to another instance."
23838
+ }, 503);
23839
+ }
23347
23840
  try {
23348
23841
  const healthProfile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile);
23349
23842
  const profileEnvOverrides = Object.keys(healthProfile.env).length > 0 ? healthProfile.env : undefined;
@@ -23501,6 +23994,8 @@ data: ${JSON.stringify({
23501
23994
  return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
23502
23995
  });
23503
23996
  app.post("/v1/chat/completions", async (c) => {
23997
+ if (draining)
23998
+ return drainingResponse();
23504
23999
  const rawBody = await c.req.json();
23505
24000
  const userAgent = c.req.header("user-agent") ?? "";
23506
24001
  const jcodeSessionId = userAgent.startsWith("jcode/") ? normalizeJcodeSessionId(c.req.header("x-jcode-session")) : undefined;
@@ -23527,16 +24022,16 @@ data: ${JSON.stringify({
23527
24022
  const authz = c.req.header("authorization");
23528
24023
  if (authz)
23529
24024
  internalHeaders["authorization"] = authz;
24025
+ internalHeaders["x-meridian-internal-hop"] = internalHopToken;
23530
24026
  const internalReq = new Request("http://internal/v1/messages", {
23531
24027
  method: "POST",
23532
24028
  headers: internalHeaders,
23533
- body: JSON.stringify(anthropicBody)
24029
+ body: JSON.stringify(anthropicBody),
24030
+ signal: c.req.raw.signal
23534
24031
  });
23535
24032
  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
- }
24033
+ if (!internalRes.ok)
24034
+ return relayInnerError(internalRes, "anthropic");
23540
24035
  const completionId = `chatcmpl-${randomUUID()}`;
23541
24036
  const created = Math.floor(Date.now() / 1000);
23542
24037
  const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : CANONICAL_SONNET_MODEL;
@@ -23549,6 +24044,7 @@ data: ${JSON.stringify({
23549
24044
  }));
23550
24045
  }
23551
24046
  const encoder = new TextEncoder;
24047
+ let internalReader;
23552
24048
  const readable = new ReadableStream({
23553
24049
  async start(controller) {
23554
24050
  const reader = internalRes.body?.getReader();
@@ -23556,6 +24052,7 @@ data: ${JSON.stringify({
23556
24052
  controller.close();
23557
24053
  return;
23558
24054
  }
24055
+ internalReader = reader;
23559
24056
  const decoder = new TextDecoder;
23560
24057
  let buffer = "";
23561
24058
  let streamError = null;
@@ -23608,6 +24105,9 @@ data: ${JSON.stringify({
23608
24105
  }
23609
24106
  controller.close();
23610
24107
  }
24108
+ },
24109
+ cancel(reason) {
24110
+ return internalReader?.cancel(reason);
23611
24111
  }
23612
24112
  });
23613
24113
  return new Response(readable, {
@@ -23619,6 +24119,8 @@ data: ${JSON.stringify({
23619
24119
  });
23620
24120
  });
23621
24121
  app.post("/v1/responses", async (c) => {
24122
+ if (draining)
24123
+ return drainingResponse("openai");
23622
24124
  const rawBody = await c.req.json();
23623
24125
  const anthropicBody = translateResponsesToAnthropic(rawBody);
23624
24126
  if (!anthropicBody) {
@@ -23644,16 +24146,16 @@ data: ${JSON.stringify({
23644
24146
  const xProfile = c.req.header("x-meridian-profile");
23645
24147
  if (xProfile)
23646
24148
  internalHeaders["x-meridian-profile"] = xProfile;
24149
+ internalHeaders["x-meridian-internal-hop"] = internalHopToken;
23647
24150
  const internalReq = new Request("http://internal/v1/messages", {
23648
24151
  method: "POST",
23649
24152
  headers: internalHeaders,
23650
- body: JSON.stringify(anthropicBody)
24153
+ body: JSON.stringify(anthropicBody),
24154
+ signal: c.req.raw.signal
23651
24155
  });
23652
24156
  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
- }
24157
+ if (!internalRes.ok)
24158
+ return relayInnerError(internalRes, "openai");
23657
24159
  const responseId = `resp_${randomUUID().replace(/-/g, "")}`;
23658
24160
  const created = Math.floor(Date.now() / 1000);
23659
24161
  const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : CANONICAL_SONNET_MODEL;
@@ -23663,6 +24165,7 @@ data: ${JSON.stringify({
23663
24165
  return c.json(translateAnthropicToResponses(anthropicRes, ctx));
23664
24166
  }
23665
24167
  const encoder = new TextEncoder;
24168
+ let internalReader;
23666
24169
  const readable = new ReadableStream({
23667
24170
  async start(controller) {
23668
24171
  const reader = internalRes.body?.getReader();
@@ -23670,6 +24173,7 @@ data: ${JSON.stringify({
23670
24173
  controller.close();
23671
24174
  return;
23672
24175
  }
24176
+ internalReader = reader;
23673
24177
  const decoder = new TextDecoder;
23674
24178
  let buffer = "";
23675
24179
  const translate = createResponsesSseTranslator(ctx);
@@ -23713,6 +24217,9 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
23713
24217
  } finally {
23714
24218
  controller.close();
23715
24219
  }
24220
+ },
24221
+ cancel(reason) {
24222
+ return internalReader?.cancel(reason);
23716
24223
  }
23717
24224
  });
23718
24225
  return new Response(readable, {
@@ -23725,8 +24232,8 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
23725
24232
  });
23726
24233
  app.get("/v1/models", async (c) => {
23727
24234
  const authStatus = await getClaudeAuthStatusAsync();
23728
- const isMax = authStatus?.subscriptionType === "max";
23729
- return c.json({ object: "list", data: buildModelList(isMax) });
24235
+ const extendedContext = subscriptionIncludesExtendedContext(authStatus?.subscriptionType);
24236
+ return c.json({ object: "list", data: buildModelList(extendedContext) });
23730
24237
  });
23731
24238
  app.get("/v1/usage/quota", async (c) => {
23732
24239
  const requestedProfile = c.req.query("profile");
@@ -23962,7 +24469,15 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
23962
24469
  plog(`[PROXY] Plugin loading failed: ${err instanceof Error ? err.message : String(err)}`);
23963
24470
  }
23964
24471
  }
23965
- return { app, config: finalConfig, initPlugins: initPluginsAsync };
24472
+ return {
24473
+ app,
24474
+ config: finalConfig,
24475
+ initPlugins: initPluginsAsync,
24476
+ beginDrain: () => {
24477
+ draining = true;
24478
+ },
24479
+ getInFlightCount: () => inFlightRequests
24480
+ };
23966
24481
  }
23967
24482
  var processErrorHandlersInstalled = false;
23968
24483
  function installProxyProcessErrorHandlers() {
@@ -23978,7 +24493,7 @@ function installProxyProcessErrorHandlers() {
23978
24493
  }
23979
24494
  async function startProxyServer(config = {}) {
23980
24495
  claudeExecutable = await resolveClaudeExecutableAsync();
23981
- const { app, config: finalConfig, initPlugins } = createProxyServer(config);
24496
+ const { app, config: finalConfig, initPlugins, beginDrain, getInFlightCount } = createProxyServer(config);
23982
24497
  if (initPlugins)
23983
24498
  await initPlugins();
23984
24499
  if (finalConfig.installProcessErrorHandlers) {
@@ -24007,6 +24522,7 @@ Point any Anthropic-compatible tool at this endpoint:`);
24007
24522
  const idleMs = finalConfig.idleTimeoutSeconds * 1000;
24008
24523
  server.keepAliveTimeout = idleMs;
24009
24524
  server.headersTimeout = idleMs + 1000;
24525
+ const connectionTracker = trackServerConnections(server);
24010
24526
  server.on("error", (error) => {
24011
24527
  if (error.code === "EADDRINUSE" && !finalConfig.silent) {
24012
24528
  console.error(`
@@ -24045,17 +24561,29 @@ Or use a different port:`);
24045
24561
  if (authKeepaliveInterval.unref)
24046
24562
  authKeepaliveInterval.unref();
24047
24563
  }
24564
+ let closePromise;
24048
24565
  return {
24049
24566
  server,
24050
24567
  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
- });
24568
+ close() {
24569
+ closePromise ??= (async () => {
24570
+ clearInterval(profileTokenRefreshInterval);
24571
+ if (authKeepaliveInterval)
24572
+ clearInterval(authKeepaliveInterval);
24573
+ stopBackgroundRefresh();
24574
+ beginDrain?.();
24575
+ try {
24576
+ await closeServerWithGracePeriod(server, {
24577
+ graceMs: SHUTDOWN_GRACE_MS,
24578
+ getInFlightCount: () => getInFlightCount?.() ?? 0,
24579
+ warn: finalConfig.silent ? undefined : (message) => console.warn(message),
24580
+ forceCloseConnections: () => connectionTracker.forceCloseAll()
24581
+ });
24582
+ } finally {
24583
+ connectionTracker.dispose();
24584
+ }
24585
+ })();
24586
+ return closePromise;
24059
24587
  }
24060
24588
  };
24061
24589
  }