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