@rynfar/meridian 1.62.0 → 1.62.2

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