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