@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.
- package/dist/{cli-k36dddkm.js → cli-7jgse65q.js} +1728 -1163
- package/dist/{cli-p3ggjwgn.js → cli-d45dq9gf.js} +10 -1
- package/dist/{cli-h6hfkg3s.js → cli-m0p2bc8v.js} +18 -1
- package/dist/cli.js +22 -5
- package/dist/{profileCli-c8bejc7w.js → profileCli-7f6yhakj.js} +1 -1
- package/dist/{profiles-3rg4x7cz.js → profiles-0pyeqayw.js} +1 -1
- package/dist/proxy/concurrency.d.ts +30 -0
- package/dist/proxy/concurrency.d.ts.map +1 -0
- package/dist/proxy/errors.d.ts +51 -5
- package/dist/proxy/errors.d.ts.map +1 -1
- package/dist/proxy/models.d.ts +13 -0
- package/dist/proxy/models.d.ts.map +1 -1
- package/dist/proxy/openai.d.ts +8 -2
- package/dist/proxy/openai.d.ts.map +1 -1
- package/dist/proxy/plugins/validation.d.ts.map +1 -1
- package/dist/proxy/routing.d.ts +41 -0
- package/dist/proxy/routing.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/session/turnCoordinator.d.ts +23 -0
- package/dist/proxy/session/turnCoordinator.d.ts.map +1 -0
- package/dist/proxy/shutdown.d.ts +19 -0
- package/dist/proxy/shutdown.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +17 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/server.js +3 -3
- package/dist/telemetry/percentiles.d.ts.map +1 -1
- package/dist/telemetry/prometheus.d.ts.map +1 -1
- package/dist/telemetry/sqlite.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +6 -0
- package/dist/telemetry/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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
|
-
|
|
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
|
|
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
|
|
12382
|
+
function classifyResumeRefusal(error, stderr) {
|
|
12383
|
+
if (isBusySessionError(error, stderr))
|
|
12384
|
+
return "busy";
|
|
12206
12385
|
if (!(error instanceof Error))
|
|
12207
|
-
return
|
|
12386
|
+
return;
|
|
12208
12387
|
const msg = error.message;
|
|
12209
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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" && !
|
|
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
|
|
20665
|
-
const
|
|
20666
|
-
const
|
|
20667
|
-
const
|
|
20668
|
-
const
|
|
20669
|
-
|
|
20670
|
-
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
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
|
|
20718
|
-
|
|
20719
|
-
|
|
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
|
|
20733
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
20869
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
21448
|
-
`))
|
|
21449
|
-
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
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 ${
|
|
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 (
|
|
21464
|
-
claudeLog("session.
|
|
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]
|
|
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*
|
|
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*
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
21859
|
-
|
|
21860
|
-
|
|
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
|
-
|
|
21906
|
-
|
|
21907
|
-
|
|
21908
|
-
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
21914
|
-
|
|
21915
|
-
|
|
21916
|
-
|
|
21917
|
-
|
|
21918
|
-
|
|
21919
|
-
|
|
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
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
|
|
21945
|
-
|
|
21946
|
-
|
|
21947
|
-
|
|
21948
|
-
|
|
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
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
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
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
21981
|
-
|
|
21982
|
-
|
|
21983
|
-
|
|
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
|
-
|
|
21989
|
-
|
|
21990
|
-
|
|
21991
|
-
|
|
21992
|
-
|
|
21993
|
-
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
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
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22025
|
-
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22034
|
-
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
|
|
22038
|
-
|
|
22039
|
-
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
|
|
22043
|
-
|
|
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
|
|
22128
|
-
isUndo
|
|
22129
|
-
resumeSessionAtUuid:
|
|
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
|
-
|
|
22158
|
-
|
|
22159
|
-
|
|
22160
|
-
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
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 (
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
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 (
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
|
|
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
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
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
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
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
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22785
|
+
claudeLog("passthrough.turn2_suppressed", { mode: "stream", toolUses: streamedToolUseIds.size });
|
|
22786
|
+
streamClosed = true;
|
|
22787
|
+
controller.close();
|
|
22788
|
+
break;
|
|
22789
|
+
}
|
|
22790
|
+
continue;
|
|
22392
22791
|
}
|
|
22393
|
-
|
|
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 (
|
|
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 (
|
|
22415
|
-
|
|
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 &&
|
|
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 (
|
|
22432
|
-
|
|
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
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
|
|
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 (
|
|
22464
|
-
|
|
22465
|
-
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
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
|
-
|
|
22478
|
-
|
|
22479
|
-
|
|
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
|
-
|
|
22880
|
+
taskToolJsonBuffer.delete(eventIndex);
|
|
22881
|
+
}
|
|
22484
22882
|
}
|
|
22485
22883
|
}
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
|
|
22494
|
-
|
|
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
|
-
|
|
22500
|
-
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
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
|
-
|
|
22506
|
-
|
|
22507
|
-
|
|
22508
|
-
|
|
22509
|
-
if (
|
|
22510
|
-
|
|
22511
|
-
|
|
22512
|
-
|
|
22513
|
-
|
|
22514
|
-
|
|
22515
|
-
|
|
22516
|
-
|
|
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
|
-
|
|
22521
|
-
|
|
22522
|
-
|
|
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
|
-
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
22924
|
+
streamClosed = true;
|
|
22925
|
+
controller.close();
|
|
22926
|
+
if (earlyStopEnabled) {
|
|
22927
|
+
awaitingEarlyStopDrain = true;
|
|
22928
|
+
continue;
|
|
22929
|
+
}
|
|
22930
|
+
break;
|
|
22532
22931
|
}
|
|
22533
|
-
|
|
22534
|
-
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
|
|
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
|
-
|
|
22546
|
-
|
|
22547
|
-
|
|
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
|
-
|
|
22968
|
+
safeEnqueue(encoder.encode(`event: content_block_start
|
|
22572
22969
|
data: ${JSON.stringify({
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
|
|
22970
|
+
type: "content_block_start",
|
|
22971
|
+
index: 0,
|
|
22972
|
+
content_block: { type: "text", text: "" }
|
|
22973
|
+
})}
|
|
22577
22974
|
|
|
22578
22975
|
`), "structured_block_start");
|
|
22579
|
-
|
|
22976
|
+
safeEnqueue(encoder.encode(`event: content_block_delta
|
|
22580
22977
|
data: ${JSON.stringify({
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
22978
|
+
type: "content_block_delta",
|
|
22979
|
+
index: 0,
|
|
22980
|
+
delta: { type: "text_delta", text }
|
|
22981
|
+
})}
|
|
22585
22982
|
|
|
22586
22983
|
`), "structured_text_delta");
|
|
22587
|
-
|
|
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
|
-
|
|
22988
|
+
safeEnqueue(encoder.encode(`event: message_delta
|
|
22592
22989
|
data: ${JSON.stringify({
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
|
|
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
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
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
|
-
|
|
22649
|
-
|
|
22650
|
-
|
|
23006
|
+
model,
|
|
23007
|
+
durationMs: Date.now() - upstreamStartAt,
|
|
23008
|
+
streamEventsSeen,
|
|
23009
|
+
eventsForwarded,
|
|
23010
|
+
textEventsForwarded
|
|
22651
23011
|
});
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
22671
|
-
|
|
22672
|
-
|
|
22673
|
-
|
|
22674
|
-
|
|
22675
|
-
|
|
22676
|
-
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
|
|
22697
|
-
|
|
22698
|
-
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
|
|
22702
|
-
|
|
22703
|
-
|
|
22704
|
-
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
|
|
22708
|
-
|
|
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
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
|
|
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
|
-
|
|
22721
|
-
|
|
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
|
-
|
|
23137
|
+
recovered: silentTurnRecovered,
|
|
23138
|
+
textEvents: textEventsForwarded,
|
|
23139
|
+
forkedSession: recoverySessionId ?? null
|
|
22724
23140
|
});
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
}
|
|
22729
|
-
if (
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
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
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
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
|
-
|
|
23160
|
+
safeEnqueue(encoder.encode(`event: content_block_delta
|
|
22763
23161
|
data: ${JSON.stringify({
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
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
|
-
|
|
23168
|
+
safeEnqueue(encoder.encode(`event: content_block_stop
|
|
22771
23169
|
data: ${JSON.stringify({
|
|
22772
|
-
|
|
22773
|
-
|
|
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
|
-
|
|
22779
|
-
|
|
22780
|
-
|
|
22781
|
-
|
|
22782
|
-
|
|
22783
|
-
|
|
22784
|
-
|
|
22785
|
-
|
|
22786
|
-
|
|
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
|
-
|
|
22791
|
-
|
|
22792
|
-
|
|
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
|
-
|
|
23194
|
+
safeEnqueue(encoder.encode(`event: content_block_delta
|
|
22797
23195
|
data: ${JSON.stringify({
|
|
22798
|
-
|
|
22799
|
-
|
|
22800
|
-
|
|
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
|
-
|
|
23202
|
+
safeEnqueue(encoder.encode(`event: content_block_stop
|
|
22805
23203
|
data: ${JSON.stringify({
|
|
22806
|
-
|
|
22807
|
-
|
|
22808
|
-
|
|
23204
|
+
type: "content_block_stop",
|
|
23205
|
+
index: fcBlockIndex
|
|
23206
|
+
})}
|
|
22809
23207
|
|
|
22810
23208
|
`), "file_changes_block_stop");
|
|
22811
|
-
|
|
23209
|
+
claudeLog("response.file_changes", { mode: "stream", count: fileChanges.length });
|
|
23210
|
+
}
|
|
22812
23211
|
}
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
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
|
-
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22890
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
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
|
-
|
|
22919
|
-
resolvePendingStore();
|
|
22920
|
-
return;
|
|
22921
|
-
}
|
|
22922
|
-
resolvePendingStore();
|
|
22923
|
-
const stderrOutput = stderrLines.join(`
|
|
23322
|
+
const stderrOutput = stderrLines.join(`
|
|
22924
23323
|
`).trim();
|
|
22925
|
-
|
|
22926
|
-
|
|
23324
|
+
if (stderrOutput && error instanceof Error && !error.message.includes(stderrOutput)) {
|
|
23325
|
+
error.message = `${error.message}
|
|
22927
23326
|
Subprocess stderr: ${stderrOutput}`;
|
|
22928
|
-
|
|
22929
|
-
|
|
22930
|
-
|
|
22931
|
-
|
|
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
|
-
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
|
|
22954
|
-
|
|
22955
|
-
|
|
22956
|
-
const
|
|
22957
|
-
|
|
22958
|
-
|
|
22959
|
-
|
|
22960
|
-
|
|
22961
|
-
|
|
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
|
-
|
|
22964
|
-
|
|
22965
|
-
|
|
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
|
-
|
|
23373
|
+
safeEnqueue(encoder.encode(`event: content_block_delta
|
|
22970
23374
|
data: ${JSON.stringify({
|
|
22971
|
-
|
|
22972
|
-
|
|
22973
|
-
|
|
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
|
-
|
|
23381
|
+
safeEnqueue(encoder.encode(`event: content_block_stop
|
|
22978
23382
|
data: ${JSON.stringify({
|
|
22979
|
-
|
|
22980
|
-
|
|
22981
|
-
|
|
23383
|
+
type: "content_block_stop",
|
|
23384
|
+
index: blockIndex
|
|
23385
|
+
})}
|
|
22982
23386
|
|
|
22983
23387
|
`), "recover_tool_block_stop");
|
|
22984
|
-
|
|
22985
|
-
|
|
23388
|
+
}
|
|
23389
|
+
safeEnqueue(encoder.encode(`event: message_delta
|
|
22986
23390
|
data: ${JSON.stringify({
|
|
22987
|
-
|
|
22988
|
-
|
|
22989
|
-
|
|
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
|
-
|
|
23397
|
+
safeEnqueue(encoder.encode(`event: message_stop
|
|
22994
23398
|
data: {"type":"message_stop"}
|
|
22995
23399
|
|
|
22996
23400
|
`), "recover_message_stop");
|
|
22997
|
-
|
|
22998
|
-
|
|
22999
|
-
|
|
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:
|
|
23018
|
-
queueWaitMs:
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
|
|
23024
|
-
|
|
23025
|
-
|
|
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
|
-
|
|
23478
|
+
error: streamErr.type
|
|
23079
23479
|
});
|
|
23080
|
-
|
|
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
|
-
|
|
23083
|
-
|
|
23084
|
-
|
|
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
|
-
|
|
23496
|
+
safeEnqueue(encoder.encode(`event: error
|
|
23089
23497
|
data: ${JSON.stringify({
|
|
23090
|
-
|
|
23091
|
-
|
|
23092
|
-
|
|
23498
|
+
type: "error",
|
|
23499
|
+
error: { type: streamErr.type, message: streamErr.message }
|
|
23500
|
+
})}
|
|
23093
23501
|
|
|
23094
23502
|
`), "error_event_before_stop");
|
|
23095
|
-
|
|
23503
|
+
safeEnqueue(encoder.encode(`event: message_stop
|
|
23096
23504
|
data: {"type":"message_stop"}
|
|
23097
23505
|
|
|
23098
23506
|
`), "error_message_stop");
|
|
23099
|
-
|
|
23100
|
-
|
|
23507
|
+
} else {
|
|
23508
|
+
safeEnqueue(encoder.encode(`event: error
|
|
23101
23509
|
data: ${JSON.stringify({
|
|
23102
|
-
|
|
23103
|
-
|
|
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
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
23577
|
+
sessionQueueWaitMs: requestMeta.sessionQueueWaitMs,
|
|
23578
|
+
sdkQueueWaitMs: requestMeta.sdkQueueWaitMs,
|
|
23579
|
+
proxyOverheadMs: Math.max(0, errorTotalMs - errorQueueWaitMs - requestMeta.sdkActiveDurationMs),
|
|
23164
23580
|
ttfbMs: null,
|
|
23165
|
-
upstreamDurationMs:
|
|
23166
|
-
totalDurationMs:
|
|
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
|
-
|
|
23183
|
-
|
|
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
|
-
|
|
23186
|
-
|
|
23187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
23672
|
-
return c.json({ object: "list", data: buildModelList(
|
|
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 {
|
|
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
|
-
|
|
23995
|
-
|
|
23996
|
-
|
|
23997
|
-
|
|
23998
|
-
|
|
23999
|
-
|
|
24000
|
-
|
|
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
|
}
|