@rynfar/meridian 1.29.2 → 1.30.0
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/README.md +2 -2
- package/assets/bot-icon-512.png +0 -0
- package/assets/bot-icon.svg +54 -0
- package/assets/icon-512.png +0 -0
- package/assets/logo-512.png +0 -0
- package/dist/{cli-trtwsfge.js → cli-b2n69dg7.js} +204 -16
- package/dist/cli.js +2 -2
- package/dist/{profileCli-pdqrpw0m.js → profileCli-5e3p99k0.js} +1 -1
- package/dist/proxy/adapter.d.ts +9 -0
- package/dist/proxy/adapter.d.ts.map +1 -1
- package/dist/proxy/adapters/opencode.d.ts.map +1 -1
- package/dist/proxy/passthroughTools.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/tokenHealth.d.ts +32 -0
- package/dist/proxy/tokenHealth.d.ts.map +1 -0
- package/dist/server.js +1 -1
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/logStore.d.ts +1 -1
- package/dist/telemetry/logStore.d.ts.map +1 -1
- package/dist/telemetry/store.d.ts +3 -0
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +21 -0
- package/dist/telemetry/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ The Claude Code SDK provides programmatic access to Claude. But your favorite co
|
|
|
80
80
|
- **Passthrough mode** — forward tool calls to the client instead of executing internally
|
|
81
81
|
- **Multimodal** — images, documents, and file attachments pass through to Claude
|
|
82
82
|
- **Multi-profile** — switch between Claude accounts instantly, no restart needed
|
|
83
|
-
- **Telemetry dashboard** — real-time performance metrics at `/telemetry`
|
|
83
|
+
- **Telemetry dashboard** — real-time performance metrics at `/telemetry`, including token usage and prompt cache efficiency ([`MONITORING.md`](MONITORING.md))
|
|
84
84
|
|
|
85
85
|
## Multi-Profile Support
|
|
86
86
|
|
|
@@ -508,7 +508,7 @@ You haven't run `meridian setup`. Without the plugin, OpenCode requests won't ha
|
|
|
508
508
|
|
|
509
509
|
## Contributing
|
|
510
510
|
|
|
511
|
-
Issues and PRs welcome. Join the [Discord](https://discord.gg/7vNVFYBz) to discuss ideas before opening issues. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for module structure and dependency rules, [`CLAUDE.md`](CLAUDE.md) for coding guidelines,
|
|
511
|
+
Issues and PRs welcome. Join the [Discord](https://discord.gg/7vNVFYBz) to discuss ideas before opening issues. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for module structure and dependency rules, [`CLAUDE.md`](CLAUDE.md) for coding guidelines, [`E2E.md`](E2E.md) for end-to-end test procedures, and [`MONITORING.md`](MONITORING.md) for understanding token usage and prompt cache health.
|
|
512
512
|
|
|
513
513
|
## License
|
|
514
514
|
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
2
|
+
<rect width="512" height="512" rx="112" fill="#1C1830"/>
|
|
3
|
+
|
|
4
|
+
<!-- Antenna -->
|
|
5
|
+
<line x1="256" y1="72" x2="256" y2="115" stroke="#A78BFA" stroke-width="8" stroke-linecap="round"/>
|
|
6
|
+
<circle cx="256" cy="64" r="14" fill="#C4B5FD"/>
|
|
7
|
+
<!-- Antenna signal arcs -->
|
|
8
|
+
<path d="M228 52 A32 32 0 0 1 284 52" fill="none" stroke="#C4B5FD" stroke-width="3" opacity="0.4" stroke-linecap="round"/>
|
|
9
|
+
<path d="M216 40 A44 44 0 0 1 296 40" fill="none" stroke="#C4B5FD" stroke-width="2.5" opacity="0.25" stroke-linecap="round"/>
|
|
10
|
+
|
|
11
|
+
<!-- Robot head -->
|
|
12
|
+
<rect x="128" y="115" width="256" height="200" rx="40" fill="#2D2750"/>
|
|
13
|
+
<rect x="128" y="115" width="256" height="200" rx="40" fill="none" stroke="#8B7CF6" stroke-width="4"/>
|
|
14
|
+
|
|
15
|
+
<!-- Eyes — screens with Meridian purple glow -->
|
|
16
|
+
<rect x="170" y="165" width="68" height="52" rx="14" fill="#1C1830" stroke="#8B7CF6" stroke-width="3"/>
|
|
17
|
+
<rect x="274" y="165" width="68" height="52" rx="14" fill="#1C1830" stroke="#8B7CF6" stroke-width="3"/>
|
|
18
|
+
<!-- Eye glow -->
|
|
19
|
+
<circle cx="204" cy="191" r="16" fill="#8B7CF6" opacity="0.8"/>
|
|
20
|
+
<circle cx="308" cy="191" r="16" fill="#8B7CF6" opacity="0.8"/>
|
|
21
|
+
<!-- Eye highlights -->
|
|
22
|
+
<circle cx="210" cy="185" r="5" fill="#C4B5FD" opacity="0.6"/>
|
|
23
|
+
<circle cx="314" cy="185" r="5" fill="#C4B5FD" opacity="0.6"/>
|
|
24
|
+
|
|
25
|
+
<!-- Mouth — friendly LED strip -->
|
|
26
|
+
<rect x="196" y="248" width="120" height="24" rx="12" fill="#1C1830" stroke="#8B7CF6" stroke-width="2"/>
|
|
27
|
+
<circle cx="220" cy="260" r="5" fill="#C4B5FD" opacity="0.7"/>
|
|
28
|
+
<circle cx="244" cy="260" r="5" fill="#8B7CF6"/>
|
|
29
|
+
<circle cx="268" cy="260" r="5" fill="#C4B5FD" opacity="0.7"/>
|
|
30
|
+
<circle cx="292" cy="260" r="5" fill="#8B7CF6" opacity="0.5"/>
|
|
31
|
+
|
|
32
|
+
<!-- Ear bolts -->
|
|
33
|
+
<circle cx="118" cy="200" r="16" fill="#2D2750" stroke="#8B7CF6" stroke-width="3"/>
|
|
34
|
+
<circle cx="118" cy="200" r="6" fill="#8B7CF6"/>
|
|
35
|
+
<circle cx="394" cy="200" r="16" fill="#2D2750" stroke="#8B7CF6" stroke-width="3"/>
|
|
36
|
+
<circle cx="394" cy="200" r="6" fill="#8B7CF6"/>
|
|
37
|
+
|
|
38
|
+
<!-- Neck -->
|
|
39
|
+
<rect x="228" y="315" width="56" height="28" rx="6" fill="#2D2750" stroke="#8B7CF6" stroke-width="2"/>
|
|
40
|
+
|
|
41
|
+
<!-- Body / chest plate -->
|
|
42
|
+
<rect x="148" y="343" width="216" height="120" rx="30" fill="#2D2750" stroke="#8B7CF6" stroke-width="4"/>
|
|
43
|
+
|
|
44
|
+
<!-- Meridian logo on chest — the globe axis + arcs -->
|
|
45
|
+
<line x1="256" y1="365" x2="256" y2="443" stroke="#8B7CF6" stroke-width="6" stroke-linecap="round"/>
|
|
46
|
+
<!-- Chest latitude arcs -->
|
|
47
|
+
<path d="M212 382 A48 48 0 0 1 300 382" fill="none" stroke="#C4B5FD" stroke-width="3" opacity="0.5"/>
|
|
48
|
+
<path d="M212 426 A48 48 0 0 0 300 426" fill="none" stroke="#C4B5FD" stroke-width="3" opacity="0.5"/>
|
|
49
|
+
<!-- Chest poles -->
|
|
50
|
+
<circle cx="256" cy="365" r="8" fill="#C4B5FD"/>
|
|
51
|
+
<circle cx="256" cy="443" r="8" fill="#C4B5FD"/>
|
|
52
|
+
<!-- Chest center node -->
|
|
53
|
+
<circle cx="256" cy="404" r="6" fill="#8B7CF6"/>
|
|
54
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
@@ -6220,7 +6220,8 @@ function jsonSchemaToZod(schema) {
|
|
|
6220
6220
|
function createPassthroughMcpServer(tools) {
|
|
6221
6221
|
const server = createSdkMcpServer({ name: PASSTHROUGH_MCP_NAME });
|
|
6222
6222
|
const toolNames = [];
|
|
6223
|
-
|
|
6223
|
+
const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
|
|
6224
|
+
for (const tool of sortedTools) {
|
|
6224
6225
|
try {
|
|
6225
6226
|
const zodSchema = tool.input_schema?.properties ? jsonSchemaToZod(tool.input_schema) : exports_external.object({});
|
|
6226
6227
|
const shape = zodSchema instanceof exports_external.ZodObject ? zodSchema.shape : { input: exports_external.any() };
|
|
@@ -6286,6 +6287,16 @@ class TelemetryStore {
|
|
|
6286
6287
|
}
|
|
6287
6288
|
return results;
|
|
6288
6289
|
}
|
|
6290
|
+
getLastForSession(sdkSessionId) {
|
|
6291
|
+
for (let i = 0;i < this.count; i++) {
|
|
6292
|
+
const idx = (this.head - 1 - i + this.capacity) % this.capacity;
|
|
6293
|
+
const metric = this.buffer[idx];
|
|
6294
|
+
if (metric && metric.sdkSessionId === sdkSessionId && metric.error === null) {
|
|
6295
|
+
return metric;
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6289
6300
|
summarize(windowMs = 60 * 60 * 1000) {
|
|
6290
6301
|
const since = Date.now() - windowMs;
|
|
6291
6302
|
const metrics = this.getRecent({ limit: this.capacity, since });
|
|
@@ -6302,7 +6313,15 @@ class TelemetryStore {
|
|
|
6302
6313
|
upstreamDuration: emptyPhase,
|
|
6303
6314
|
totalDuration: emptyPhase,
|
|
6304
6315
|
byModel: {},
|
|
6305
|
-
byMode: {}
|
|
6316
|
+
byMode: {},
|
|
6317
|
+
tokenUsage: {
|
|
6318
|
+
totalInputTokens: 0,
|
|
6319
|
+
totalOutputTokens: 0,
|
|
6320
|
+
totalCacheReadTokens: 0,
|
|
6321
|
+
totalCacheCreationTokens: 0,
|
|
6322
|
+
avgCacheHitRate: 0,
|
|
6323
|
+
cacheMissOnResumeCount: 0
|
|
6324
|
+
}
|
|
6306
6325
|
};
|
|
6307
6326
|
}
|
|
6308
6327
|
const errorCount = metrics.filter((m) => m.error !== null).length;
|
|
@@ -6328,6 +6347,26 @@ class TelemetryStore {
|
|
|
6328
6347
|
entry.count++;
|
|
6329
6348
|
entry.totalMs += m.totalDurationMs;
|
|
6330
6349
|
}
|
|
6350
|
+
let totalInputTokens = 0;
|
|
6351
|
+
let totalOutputTokens = 0;
|
|
6352
|
+
let totalCacheReadTokens = 0;
|
|
6353
|
+
let totalCacheCreationTokens = 0;
|
|
6354
|
+
let cacheHitRateSum = 0;
|
|
6355
|
+
let cacheHitRateCount = 0;
|
|
6356
|
+
let cacheMissOnResumeCount = 0;
|
|
6357
|
+
for (const m of metrics) {
|
|
6358
|
+
totalInputTokens += m.inputTokens ?? 0;
|
|
6359
|
+
totalOutputTokens += m.outputTokens ?? 0;
|
|
6360
|
+
totalCacheReadTokens += m.cacheReadInputTokens ?? 0;
|
|
6361
|
+
totalCacheCreationTokens += m.cacheCreationInputTokens ?? 0;
|
|
6362
|
+
if (m.cacheHitRate !== undefined) {
|
|
6363
|
+
cacheHitRateSum += m.cacheHitRate;
|
|
6364
|
+
cacheHitRateCount++;
|
|
6365
|
+
}
|
|
6366
|
+
if (m.isResume && m.cacheHitRate !== undefined && m.cacheHitRate === 0) {
|
|
6367
|
+
cacheMissOnResumeCount++;
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6331
6370
|
return {
|
|
6332
6371
|
windowMs,
|
|
6333
6372
|
totalRequests: metrics.length,
|
|
@@ -6339,7 +6378,15 @@ class TelemetryStore {
|
|
|
6339
6378
|
upstreamDuration: computePercentiles(upstreams),
|
|
6340
6379
|
totalDuration: computePercentiles(totals),
|
|
6341
6380
|
byModel: Object.fromEntries(Object.entries(byModel).map(([k, v]) => [k, { count: v.count, avgTotalMs: Math.round(v.totalMs / v.count) }])),
|
|
6342
|
-
byMode: Object.fromEntries(Object.entries(byMode).map(([k, v]) => [k, { count: v.count, avgTotalMs: Math.round(v.totalMs / v.count) }]))
|
|
6381
|
+
byMode: Object.fromEntries(Object.entries(byMode).map(([k, v]) => [k, { count: v.count, avgTotalMs: Math.round(v.totalMs / v.count) }])),
|
|
6382
|
+
tokenUsage: {
|
|
6383
|
+
totalInputTokens,
|
|
6384
|
+
totalOutputTokens,
|
|
6385
|
+
totalCacheReadTokens,
|
|
6386
|
+
totalCacheCreationTokens,
|
|
6387
|
+
avgCacheHitRate: cacheHitRateCount > 0 ? Math.round(cacheHitRateSum / cacheHitRateCount * 100) / 100 : 0,
|
|
6388
|
+
cacheMissOnResumeCount
|
|
6389
|
+
}
|
|
6343
6390
|
};
|
|
6344
6391
|
}
|
|
6345
6392
|
clear() {
|
|
@@ -6590,7 +6637,7 @@ function render(s, reqs, logs) {
|
|
|
6590
6637
|
// Count lineage types for badges
|
|
6591
6638
|
const lineageCounts = {};
|
|
6592
6639
|
for (const r of reqs) { const t = r.lineageType || 'unknown'; lineageCounts[t] = (lineageCounts[t] || 0) + 1; }
|
|
6593
|
-
const logCounts = { session: 0, lineage: 0, error: 0 };
|
|
6640
|
+
const logCounts = { session: 0, lineage: 0, error: 0, token: 0 };
|
|
6594
6641
|
for (const l of logs) { if (logCounts[l.category] !== undefined) logCounts[l.category]++; }
|
|
6595
6642
|
|
|
6596
6643
|
// Tabs
|
|
@@ -6615,6 +6662,20 @@ function render(s, reqs, logs) {
|
|
|
6615
6662
|
+ card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))
|
|
6616
6663
|
+ '</div>';
|
|
6617
6664
|
|
|
6665
|
+
// Token usage cards
|
|
6666
|
+
if (s.tokenUsage) {
|
|
6667
|
+
const t = s.tokenUsage;
|
|
6668
|
+
const fmtTok = n => n > 1000000 ? (n/1000000).toFixed(1) + 'M' : n > 1000 ? Math.round(n/1000) + 'k' : String(n);
|
|
6669
|
+
html += '<div class="section"><div class="section-title">Token Usage</div></div>';
|
|
6670
|
+
html += '<div class="cards">'
|
|
6671
|
+
+ card('Input Tokens', fmtTok(t.totalInputTokens), '')
|
|
6672
|
+
+ card('Output Tokens', fmtTok(t.totalOutputTokens), '')
|
|
6673
|
+
+ card('Cache Read', fmtTok(t.totalCacheReadTokens), '')
|
|
6674
|
+
+ card('Cache Write', fmtTok(t.totalCacheCreationTokens), '')
|
|
6675
|
+
+ card('Avg Cache Hit', (t.avgCacheHitRate * 100).toFixed(0) + '%', t.cacheMissOnResumeCount > 0 ? t.cacheMissOnResumeCount + ' cache miss on resume' : '')
|
|
6676
|
+
+ '</div>';
|
|
6677
|
+
}
|
|
6678
|
+
|
|
6618
6679
|
// Model breakdown
|
|
6619
6680
|
const models = Object.entries(s.byModel);
|
|
6620
6681
|
if (models.length > 0) {
|
|
@@ -6658,7 +6719,7 @@ function render(s, reqs, logs) {
|
|
|
6658
6719
|
+ '<span><span class="legend-dot" style="background:var(--upstream)"></span>Response</span>'
|
|
6659
6720
|
+ '</div>'
|
|
6660
6721
|
+ '<table><thead><tr><th>Time</th><th>Adapter</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'
|
|
6661
|
-
+ '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';
|
|
6722
|
+
+ '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Tokens</th><th>Cache</th><th>Waterfall</th></tr></thead><tbody>';
|
|
6662
6723
|
|
|
6663
6724
|
const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);
|
|
6664
6725
|
|
|
@@ -6686,6 +6747,8 @@ function render(s, reqs, logs) {
|
|
|
6686
6747
|
+ '<td class="mono">' + ms(r.proxyOverheadMs) + '</td>'
|
|
6687
6748
|
+ '<td class="mono">' + ms(r.ttfbMs) + '</td>'
|
|
6688
6749
|
+ '<td class="mono">' + ms(r.totalDurationMs) + '</td>'
|
|
6750
|
+
+ '<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>'
|
|
6751
|
+
+ '<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>'
|
|
6689
6752
|
+ '<td><div class="waterfall">'
|
|
6690
6753
|
+ '<div class="waterfall-seg queue" style="width:' + qW + 'px"></div>'
|
|
6691
6754
|
+ '<div class="waterfall-seg overhead" style="width:' + ohW + 'px"></div>'
|
|
@@ -6706,6 +6769,7 @@ function render(s, reqs, logs) {
|
|
|
6706
6769
|
+ '<span class="log-filter' + (activeLogFilter === 'session' ? ' active' : '') + '" data-filter="session" onclick="setLogFilter('session')" style="--accent:var(--blue)">Session<span class="tab-badge">' + logCounts.session + '</span></span>'
|
|
6707
6770
|
+ '<span class="log-filter' + (activeLogFilter === 'lineage' ? ' active' : '') + '" data-filter="lineage" onclick="setLogFilter('lineage')" style="--accent:var(--purple)">Lineage<span class="tab-badge">' + logCounts.lineage + '</span></span>'
|
|
6708
6771
|
+ '<span class="log-filter' + (activeLogFilter === 'error' ? ' active' : '') + '" data-filter="error" onclick="setLogFilter('error')" style="--accent:var(--red)">Error<span class="tab-badge">' + logCounts.error + '</span></span>'
|
|
6772
|
+
+ '<span class="log-filter' + (activeLogFilter === 'token' ? ' active' : '') + '" data-filter="token" onclick="setLogFilter('token')" style="--accent:var(--yellow)">Token<span class="tab-badge">' + logCounts.token + '</span></span>'
|
|
6709
6773
|
+ '</div>';
|
|
6710
6774
|
|
|
6711
6775
|
if (logs.length === 0) {
|
|
@@ -6717,7 +6781,7 @@ function render(s, reqs, logs) {
|
|
|
6717
6781
|
|
|
6718
6782
|
for (const log of logs) {
|
|
6719
6783
|
const levelColor = {info:'var(--green)',warn:'var(--yellow)',error:'var(--red)'}[log.level] || 'var(--muted)';
|
|
6720
|
-
const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)'}[log.category] || 'var(--muted)';
|
|
6784
|
+
const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)',token:'var(--yellow)'}[log.category] || 'var(--muted)';
|
|
6721
6785
|
const display = (activeLogFilter === 'all' || log.category === activeLogFilter) ? '' : 'display:none';
|
|
6722
6786
|
html += '<tr class="log-row" data-category="' + log.category + '" style="' + display + '">'
|
|
6723
6787
|
+ '<td class="mono">' + ago(log.timestamp) + '</td>'
|
|
@@ -7702,6 +7766,16 @@ var openCodeAdapter = {
|
|
|
7702
7766
|
getAllowedMcpTools() {
|
|
7703
7767
|
return ALLOWED_MCP_TOOLS;
|
|
7704
7768
|
},
|
|
7769
|
+
usesPassthrough() {
|
|
7770
|
+
const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
|
|
7771
|
+
if (envVal === "0" || envVal === "false" || envVal === "no") {
|
|
7772
|
+
return false;
|
|
7773
|
+
}
|
|
7774
|
+
return true;
|
|
7775
|
+
},
|
|
7776
|
+
supportsThinking() {
|
|
7777
|
+
return true;
|
|
7778
|
+
},
|
|
7705
7779
|
buildSdkAgents(body, mcpToolNames) {
|
|
7706
7780
|
if (!Array.isArray(body.tools))
|
|
7707
7781
|
return {};
|
|
@@ -13877,6 +13951,50 @@ function filterBetasForProfile(rawBetaHeader, profileType, policy = DEFAULT_BETA
|
|
|
13877
13951
|
};
|
|
13878
13952
|
}
|
|
13879
13953
|
|
|
13954
|
+
// src/proxy/tokenHealth.ts
|
|
13955
|
+
var CONTEXT_SPIKE_THRESHOLD = 0.6;
|
|
13956
|
+
var CACHE_MISS_THRESHOLD = 0.05;
|
|
13957
|
+
var OUTPUT_EXPLOSION_RATIO = 2;
|
|
13958
|
+
var fmt = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
|
|
13959
|
+
function detectTokenAnomalies(current, previous) {
|
|
13960
|
+
const anomalies = [];
|
|
13961
|
+
if (previous && previous.inputTokens > 0) {
|
|
13962
|
+
const growth = (current.inputTokens - previous.inputTokens) / previous.inputTokens;
|
|
13963
|
+
if (growth > CONTEXT_SPIKE_THRESHOLD) {
|
|
13964
|
+
const pct = Math.round(growth * 100);
|
|
13965
|
+
anomalies.push({
|
|
13966
|
+
type: "context_spike",
|
|
13967
|
+
severity: growth > 2 ? "critical" : "warn",
|
|
13968
|
+
detail: `Input tokens grew ${pct}% in one turn (${fmt(previous.inputTokens)} -> ${fmt(current.inputTokens)}). Possible context leak or full replay.`
|
|
13969
|
+
});
|
|
13970
|
+
}
|
|
13971
|
+
}
|
|
13972
|
+
if (current.isResume && current.cacheHitRate <= CACHE_MISS_THRESHOLD && current.inputTokens > 0) {
|
|
13973
|
+
anomalies.push({
|
|
13974
|
+
type: "cache_miss",
|
|
13975
|
+
severity: "critical",
|
|
13976
|
+
detail: `Cache hit rate ${Math.round(current.cacheHitRate * 100)}% on resume (expected >50%). Prompt caching likely invalidated — check tool ordering or system prompt changes.`
|
|
13977
|
+
});
|
|
13978
|
+
}
|
|
13979
|
+
if (previous && previous.outputTokens > 0 && current.outputTokens > 0) {
|
|
13980
|
+
const ratio = current.outputTokens / previous.outputTokens;
|
|
13981
|
+
if (ratio > OUTPUT_EXPLOSION_RATIO && current.outputTokens > 2000) {
|
|
13982
|
+
anomalies.push({
|
|
13983
|
+
type: "output_explosion",
|
|
13984
|
+
severity: "warn",
|
|
13985
|
+
detail: `Output tokens ${fmt(current.outputTokens)} are ${ratio.toFixed(1)}x the previous turn (${fmt(previous.outputTokens)}).`
|
|
13986
|
+
});
|
|
13987
|
+
}
|
|
13988
|
+
}
|
|
13989
|
+
return anomalies;
|
|
13990
|
+
}
|
|
13991
|
+
function formatAnomalyAlerts(requestId, anomalies) {
|
|
13992
|
+
return anomalies.map((a) => {
|
|
13993
|
+
const icon = a.severity === "critical" ? "TOKEN ALERT" : "TOKEN WARN";
|
|
13994
|
+
return `[PROXY] ${requestId} ${icon}: ${a.detail}`;
|
|
13995
|
+
});
|
|
13996
|
+
}
|
|
13997
|
+
|
|
13880
13998
|
// src/proxy/session/lineage.ts
|
|
13881
13999
|
import { createHash as createHash2 } from "crypto";
|
|
13882
14000
|
var MIN_SUFFIX_FOR_COMPACTION = 2;
|
|
@@ -14555,14 +14673,72 @@ function buildFreshPrompt(messages, stripCacheControl) {
|
|
|
14555
14673
|
`) || "";
|
|
14556
14674
|
}
|
|
14557
14675
|
function logUsage(requestId, usage) {
|
|
14558
|
-
const
|
|
14676
|
+
const fmt2 = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
|
|
14677
|
+
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
14678
|
+
const totalInput = usage.input_tokens ?? 0;
|
|
14679
|
+
const cacheRate = totalInput > 0 ? Math.round(cacheRead / totalInput * 100) : 0;
|
|
14680
|
+
const cacheTag = totalInput > 0 ? ` cache=${cacheRate}%` : "";
|
|
14559
14681
|
const parts = [
|
|
14560
|
-
`input=${
|
|
14561
|
-
`output=${
|
|
14562
|
-
...usage.cache_read_input_tokens ? [`cache_read=${
|
|
14563
|
-
...usage.cache_creation_input_tokens ? [`cache_write=${
|
|
14682
|
+
`input=${fmt2(usage.input_tokens ?? 0)}`,
|
|
14683
|
+
`output=${fmt2(usage.output_tokens ?? 0)}`,
|
|
14684
|
+
...usage.cache_read_input_tokens ? [`cache_read=${fmt2(usage.cache_read_input_tokens)}`] : [],
|
|
14685
|
+
...usage.cache_creation_input_tokens ? [`cache_write=${fmt2(usage.cache_creation_input_tokens)}`] : []
|
|
14564
14686
|
];
|
|
14565
|
-
console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}`);
|
|
14687
|
+
console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}${cacheTag}`);
|
|
14688
|
+
}
|
|
14689
|
+
function computeCacheHitRate(usage) {
|
|
14690
|
+
if (!usage)
|
|
14691
|
+
return;
|
|
14692
|
+
const read = usage.cache_read_input_tokens ?? 0;
|
|
14693
|
+
const creation = usage.cache_creation_input_tokens ?? 0;
|
|
14694
|
+
const uncached = usage.input_tokens ?? 0;
|
|
14695
|
+
const total = uncached + read + creation;
|
|
14696
|
+
if (total === 0)
|
|
14697
|
+
return;
|
|
14698
|
+
return read / total;
|
|
14699
|
+
}
|
|
14700
|
+
function checkTokenHealth(requestId, sdkSessionId, usage, turnNumber, isResume, isPassthrough) {
|
|
14701
|
+
if (!usage || !sdkSessionId)
|
|
14702
|
+
return;
|
|
14703
|
+
const cacheHitRate = computeCacheHitRate(usage) ?? 0;
|
|
14704
|
+
const current = {
|
|
14705
|
+
requestId,
|
|
14706
|
+
turnNumber,
|
|
14707
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
14708
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
14709
|
+
cacheReadInputTokens: usage.cache_read_input_tokens ?? 0,
|
|
14710
|
+
cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
|
|
14711
|
+
cacheHitRate,
|
|
14712
|
+
isResume,
|
|
14713
|
+
isPassthrough
|
|
14714
|
+
};
|
|
14715
|
+
const prevMetric = telemetryStore.getLastForSession(sdkSessionId);
|
|
14716
|
+
const previous = prevMetric ? {
|
|
14717
|
+
requestId: prevMetric.requestId,
|
|
14718
|
+
turnNumber: turnNumber - 1,
|
|
14719
|
+
inputTokens: prevMetric.inputTokens ?? 0,
|
|
14720
|
+
outputTokens: prevMetric.outputTokens ?? 0,
|
|
14721
|
+
cacheReadInputTokens: prevMetric.cacheReadInputTokens ?? 0,
|
|
14722
|
+
cacheCreationInputTokens: prevMetric.cacheCreationInputTokens ?? 0,
|
|
14723
|
+
cacheHitRate: prevMetric.cacheHitRate ?? 0,
|
|
14724
|
+
isResume: prevMetric.isResume,
|
|
14725
|
+
isPassthrough: prevMetric.isPassthrough
|
|
14726
|
+
} : undefined;
|
|
14727
|
+
const anomalies = detectTokenAnomalies(current, previous);
|
|
14728
|
+
if (anomalies.length > 0) {
|
|
14729
|
+
const alerts = formatAnomalyAlerts(requestId, anomalies);
|
|
14730
|
+
for (const line of alerts) {
|
|
14731
|
+
console.error(line);
|
|
14732
|
+
}
|
|
14733
|
+
for (const a of anomalies) {
|
|
14734
|
+
diagnosticLog.log({
|
|
14735
|
+
level: a.severity === "critical" ? "error" : "warn",
|
|
14736
|
+
category: "token",
|
|
14737
|
+
message: `${requestId} ${a.type}: ${a.detail}`,
|
|
14738
|
+
requestId
|
|
14739
|
+
});
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14566
14742
|
}
|
|
14567
14743
|
function createProxyServer(config = {}) {
|
|
14568
14744
|
const finalConfig = { ...DEFAULT_PROXY_CONFIG, ...config };
|
|
@@ -15015,7 +15191,7 @@ function createProxyServer(config = {}) {
|
|
|
15015
15191
|
} else {
|
|
15016
15192
|
for (const block of message.message.content) {
|
|
15017
15193
|
const b = block;
|
|
15018
|
-
if (passthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
|
|
15194
|
+
if (passthrough && !adapter.supportsThinking?.() && (b.type === "thinking" || b.type === "redacted_thinking")) {
|
|
15019
15195
|
claudeLog("passthrough.thinking_stripped", { mode: "non_stream", type: b.type });
|
|
15020
15196
|
continue;
|
|
15021
15197
|
}
|
|
@@ -15100,6 +15276,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
15100
15276
|
hasToolUse
|
|
15101
15277
|
});
|
|
15102
15278
|
const nonStreamQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
|
|
15279
|
+
checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
|
|
15103
15280
|
telemetryStore.record({
|
|
15104
15281
|
requestId: requestMeta.requestId,
|
|
15105
15282
|
timestamp: Date.now(),
|
|
@@ -15120,7 +15297,12 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
15120
15297
|
totalDurationMs,
|
|
15121
15298
|
contentBlocks: contentBlocks.length,
|
|
15122
15299
|
textEvents: 0,
|
|
15123
|
-
error: null
|
|
15300
|
+
error: null,
|
|
15301
|
+
inputTokens: lastUsage?.input_tokens,
|
|
15302
|
+
outputTokens: lastUsage?.output_tokens,
|
|
15303
|
+
cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
|
|
15304
|
+
cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
|
|
15305
|
+
cacheHitRate: computeCacheHitRate(lastUsage)
|
|
15124
15306
|
});
|
|
15125
15307
|
if (currentSessionId) {
|
|
15126
15308
|
storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage);
|
|
@@ -15388,7 +15570,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
|
|
|
15388
15570
|
}
|
|
15389
15571
|
if (eventType === "content_block_start") {
|
|
15390
15572
|
const block = event.content_block;
|
|
15391
|
-
if (passthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
|
|
15573
|
+
if (passthrough && !adapter.supportsThinking?.() && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
|
|
15392
15574
|
if (eventIndex !== undefined)
|
|
15393
15575
|
skipBlockIndices.add(eventIndex);
|
|
15394
15576
|
claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
|
|
@@ -15570,6 +15752,7 @@ data: {"type":"message_stop"}
|
|
|
15570
15752
|
textEventsForwarded
|
|
15571
15753
|
});
|
|
15572
15754
|
const streamQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
|
|
15755
|
+
checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
|
|
15573
15756
|
telemetryStore.record({
|
|
15574
15757
|
requestId: requestMeta.requestId,
|
|
15575
15758
|
timestamp: Date.now(),
|
|
@@ -15590,7 +15773,12 @@ data: {"type":"message_stop"}
|
|
|
15590
15773
|
totalDurationMs: streamTotalDurationMs,
|
|
15591
15774
|
contentBlocks: eventsForwarded,
|
|
15592
15775
|
textEvents: textEventsForwarded,
|
|
15593
|
-
error: null
|
|
15776
|
+
error: null,
|
|
15777
|
+
inputTokens: lastUsage?.input_tokens,
|
|
15778
|
+
outputTokens: lastUsage?.output_tokens,
|
|
15779
|
+
cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
|
|
15780
|
+
cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
|
|
15781
|
+
cacheHitRate: computeCacheHitRate(lastUsage)
|
|
15594
15782
|
});
|
|
15595
15783
|
if (textEventsForwarded === 0) {
|
|
15596
15784
|
claudeLog("response.empty_stream", {
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startProxyServer
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-b2n69dg7.js";
|
|
5
5
|
import"./cli-g9ypdz51.js";
|
|
6
6
|
import"./cli-rtab0qa6.js";
|
|
7
7
|
import"./cli-m9pfb7h9.js";
|
|
@@ -49,7 +49,7 @@ See https://github.com/rynfar/meridian for full documentation.`);
|
|
|
49
49
|
process.exit(0);
|
|
50
50
|
}
|
|
51
51
|
if (args[0] === "profile") {
|
|
52
|
-
const { profileAdd, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-
|
|
52
|
+
const { profileAdd, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-5e3p99k0.js");
|
|
53
53
|
const subcommand = args[1];
|
|
54
54
|
const profileId = args[2];
|
|
55
55
|
if (subcommand === "add" && profileId)
|
|
@@ -199,8 +199,8 @@ function profileLogin(id) {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
function promptYesNo(question) {
|
|
202
|
+
process.stderr.write(`${question} [Y/n] `);
|
|
202
203
|
const result = spawnSync("node", ["-e", [
|
|
203
|
-
`process.stdout.write(${JSON.stringify(`${question} [Y/n] `)});`,
|
|
204
204
|
`const rl = require("readline").createInterface({ input: process.stdin });`,
|
|
205
205
|
`rl.once("line", (a) => { process.stdout.write(a); rl.close(); });`,
|
|
206
206
|
`rl.once("close", () => process.exit(0));`
|
package/dist/proxy/adapter.d.ts
CHANGED
|
@@ -82,6 +82,15 @@ export interface AgentAdapter {
|
|
|
82
82
|
* When defined, takes precedence over the env var for this agent.
|
|
83
83
|
*/
|
|
84
84
|
usesPassthrough?(): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Whether this agent's client can render thinking blocks.
|
|
87
|
+
*
|
|
88
|
+
* When true, thinking/redacted_thinking blocks are forwarded in
|
|
89
|
+
* passthrough mode instead of being stripped.
|
|
90
|
+
* When false or undefined, they are stripped (safe default for
|
|
91
|
+
* clients that may choke on the encrypted signature field).
|
|
92
|
+
*/
|
|
93
|
+
supportsThinking?(): boolean;
|
|
85
94
|
/**
|
|
86
95
|
* Map a client-side tool_use block to file changes (passthrough mode).
|
|
87
96
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5C;;;OAGG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;IAE1B;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5C;;;OAGG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;IAE1B;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,OAAO,CAAA;IAE5B;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/opencode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAQ9C,eAAO,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/opencode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAQ9C,eAAO,MAAM,eAAe,EAAE,YAiH7B,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passthroughTools.d.ts","sourceRoot":"","sources":["../../src/proxy/passthroughTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,eAAO,MAAM,oBAAoB,OAAO,CAAA;AACxC,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AAsCtE;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;;;
|
|
1
|
+
{"version":3,"file":"passthroughTools.d.ts","sourceRoot":"","sources":["../../src/proxy/passthroughTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,eAAO,MAAM,oBAAoB,OAAO,CAAA;AACxC,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AAsCtE;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;;;EA2CzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAuBvD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAyK7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAyrDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token health anomaly detection.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that compare consecutive turn token snapshots
|
|
5
|
+
* and classify abnormal patterns. No I/O, no imports from server.ts.
|
|
6
|
+
*/
|
|
7
|
+
export interface TokenSnapshot {
|
|
8
|
+
requestId: string;
|
|
9
|
+
turnNumber: number;
|
|
10
|
+
inputTokens: number;
|
|
11
|
+
outputTokens: number;
|
|
12
|
+
cacheReadInputTokens: number;
|
|
13
|
+
cacheCreationInputTokens: number;
|
|
14
|
+
cacheHitRate: number;
|
|
15
|
+
isResume: boolean;
|
|
16
|
+
isPassthrough: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface TokenAnomaly {
|
|
19
|
+
type: "context_spike" | "cache_miss" | "output_explosion";
|
|
20
|
+
severity: "warn" | "critical";
|
|
21
|
+
detail: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compare a current turn's token snapshot against the previous turn.
|
|
25
|
+
* Returns an array of detected anomalies (empty if everything looks normal).
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectTokenAnomalies(current: TokenSnapshot, previous: TokenSnapshot | undefined): TokenAnomaly[];
|
|
28
|
+
/**
|
|
29
|
+
* Format anomalies as stderr log lines.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatAnomalyAlerts(requestId: string, anomalies: TokenAnomaly[]): string[];
|
|
32
|
+
//# sourceMappingURL=tokenHealth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenHealth.d.ts","sourceRoot":"","sources":["../../src/proxy/tokenHealth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,wBAAwB,EAAE,MAAM,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,eAAe,GAAG,YAAY,GAAG,kBAAkB,CAAA;IACzD,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;IAC7B,MAAM,EAAE,MAAM,CAAA;CACf;AAaD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,YAAY,EAAE,CAsChB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CAK1F"}
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,aAAa,QA6VlB,CAAA"}
|
|
@@ -11,7 +11,7 @@ export interface DiagnosticLog {
|
|
|
11
11
|
/** Log level */
|
|
12
12
|
level: "info" | "warn" | "error";
|
|
13
13
|
/** Log category for filtering */
|
|
14
|
-
category: "session" | "lineage" | "error" | "lifecycle";
|
|
14
|
+
category: "session" | "lineage" | "error" | "lifecycle" | "token";
|
|
15
15
|
/** Request ID (if associated with a request) */
|
|
16
16
|
requestId?: string;
|
|
17
17
|
/** Human-readable message */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logStore.d.ts","sourceRoot":"","sources":["../../src/telemetry/logStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB;IAChB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAChC,iCAAiC;IACjC,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"logStore.d.ts","sourceRoot":"","sources":["../../src/telemetry/logStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB;IAChB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAChC,iCAAiC;IACjC,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,CAAA;IACjE,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAA;CAChB;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,0BAA0B;IAC1B,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,GAAG,IAAI;IAMlD,wCAAwC;IACxC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,qEAAqE;IACrE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,iCAAiC;IACjC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIhD;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAgB/F,6BAA6B;IAC7B,KAAK,IAAI,IAAI;CAKd;AAED,4CAA4C;AAC5C,eAAO,MAAM,aAAa,oBAA2B,CAAA"}
|
|
@@ -26,6 +26,9 @@ export declare class TelemetryStore {
|
|
|
26
26
|
since?: number;
|
|
27
27
|
model?: string;
|
|
28
28
|
}): RequestMetric[];
|
|
29
|
+
/** Find the most recent successful metric for a given SDK session ID.
|
|
30
|
+
* Used by anomaly detection to compare consecutive turns. */
|
|
31
|
+
getLastForSession(sdkSessionId: string): RequestMetric | undefined;
|
|
29
32
|
/**
|
|
30
33
|
* Compute aggregate statistics over a time window.
|
|
31
34
|
* @param windowMs - Time window in ms (default: 1 hour)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAY3E,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,yCAAyC;IACzC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMnC,8CAA8C;IAC9C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAiB5F;;;OAGG;IACH,SAAS,CAAC,QAAQ,GAAE,MAAuB,GAAG,gBAAgB;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAY3E,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,yCAAyC;IACzC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMnC,8CAA8C;IAC9C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAiB5F;kEAC8D;IAC9D,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAWlE;;;OAGG;IACH,SAAS,CAAC,QAAQ,GAAE,MAAuB,GAAG,gBAAgB;IAiH9D,gCAAgC;IAChC,KAAK,IAAI,IAAI;CAKd;AAkBD,kDAAkD;AAClD,eAAO,MAAM,cAAc,gBAAuB,CAAA"}
|
|
@@ -57,6 +57,17 @@ export interface RequestMetric {
|
|
|
57
57
|
textEvents: number;
|
|
58
58
|
/** Error type if the request failed, null if successful */
|
|
59
59
|
error: string | null;
|
|
60
|
+
/** Input tokens consumed (from SDK usage) */
|
|
61
|
+
inputTokens?: number;
|
|
62
|
+
/** Output tokens generated */
|
|
63
|
+
outputTokens?: number;
|
|
64
|
+
/** Input tokens read from prompt cache (lower cost) */
|
|
65
|
+
cacheReadInputTokens?: number;
|
|
66
|
+
/** Input tokens written to prompt cache (one-time cost) */
|
|
67
|
+
cacheCreationInputTokens?: number;
|
|
68
|
+
/** Cache hit ratio: cacheRead / (cacheRead + cacheCreation + uncached).
|
|
69
|
+
* 1.0 = perfect caching, 0.0 = no caching. undefined when no token data. */
|
|
70
|
+
cacheHitRate?: number;
|
|
60
71
|
}
|
|
61
72
|
export interface PhaseTiming {
|
|
62
73
|
p50: number;
|
|
@@ -91,5 +102,15 @@ export interface TelemetrySummary {
|
|
|
91
102
|
count: number;
|
|
92
103
|
avgTotalMs: number;
|
|
93
104
|
}>;
|
|
105
|
+
/** Aggregate token usage across all requests in the window */
|
|
106
|
+
tokenUsage: {
|
|
107
|
+
totalInputTokens: number;
|
|
108
|
+
totalOutputTokens: number;
|
|
109
|
+
totalCacheReadTokens: number;
|
|
110
|
+
totalCacheCreationTokens: number;
|
|
111
|
+
avgCacheHitRate: number;
|
|
112
|
+
/** Requests where cache hit rate was 0 despite being a resume */
|
|
113
|
+
cacheMissOnResumeCount: number;
|
|
114
|
+
};
|
|
94
115
|
}
|
|
95
116
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IAEjB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IAEjB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IAEb,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,iCAAiC;IACjC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAA;IAE7B,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAA;IAEjB,0CAA0C;IAC1C,aAAa,EAAE,OAAO,CAAA;IAEtB;;;;;sEAKkE;IAClE,WAAW,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,CAAA;IAEzE,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IAEd,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IAEnB;;8CAE0C;IAC1C,eAAe,EAAE,MAAM,CAAA;IAEvB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAErB,yCAAyC;IACzC,kBAAkB,EAAE,MAAM,CAAA;IAE1B,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAA;IAEvB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAA;IAErB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAElB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IAEjB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IAEjB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IAEb,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,iCAAiC;IACjC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAA;IAE7B,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAA;IAEjB,0CAA0C;IAC1C,aAAa,EAAE,OAAO,CAAA;IAEtB;;;;;sEAKkE;IAClE,WAAW,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,CAAA;IAEzE,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IAEd,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IAEnB;;8CAE0C;IAC1C,eAAe,EAAE,MAAM,CAAA;IAEvB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAErB,yCAAyC;IACzC,kBAAkB,EAAE,MAAM,CAAA;IAE1B,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAA;IAEvB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAA;IAErB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAElB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,8BAA8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAE7B,2DAA2D;IAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAA;IAEjC;iFAC6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAA;IACrB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IAEzB,iCAAiC;IACjC,SAAS,EAAE,WAAW,CAAA;IACtB,aAAa,EAAE,WAAW,CAAA;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,gBAAgB,EAAE,WAAW,CAAA;IAC7B,aAAa,EAAE,WAAW,CAAA;IAE1B,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9D,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAE7D,8DAA8D;IAC9D,UAAU,EAAE;QACV,gBAAgB,EAAE,MAAM,CAAA;QACxB,iBAAiB,EAAE,MAAM,CAAA;QACzB,oBAAoB,EAAE,MAAM,CAAA;QAC5B,wBAAwB,EAAE,MAAM,CAAA;QAChC,eAAe,EAAE,MAAM,CAAA;QACvB,iEAAiE;QACjE,sBAAsB,EAAE,MAAM,CAAA;KAC/B,CAAA;CACF"}
|
package/package.json
CHANGED