@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 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, and [`E2E.md`](E2E.md) for end-to-end test procedures.
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
- for (const tool of tools) {
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(&apos;session&apos;)" 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(&apos;lineage&apos;)" 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(&apos;error&apos;)" 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(&apos;token&apos;)" 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 fmt = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
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=${fmt(usage.input_tokens ?? 0)}`,
14561
- `output=${fmt(usage.output_tokens ?? 0)}`,
14562
- ...usage.cache_read_input_tokens ? [`cache_read=${fmt(usage.cache_read_input_tokens)}`] : [],
14563
- ...usage.cache_creation_input_tokens ? [`cache_write=${fmt(usage.cache_creation_input_tokens)}`] : []
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-trtwsfge.js";
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-pdqrpw0m.js");
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));`
@@ -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,YAqG7B,CAAA"}
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;;;EAsCzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKvD"}
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;AAsBvD,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;AAoG7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA8pDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
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
@@ -6,7 +6,7 @@ import {
6
6
  getMaxSessionsLimit,
7
7
  hashMessage,
8
8
  startProxyServer
9
- } from "./cli-trtwsfge.js";
9
+ } from "./cli-b2n69dg7.js";
10
10
  import"./cli-g9ypdz51.js";
11
11
  import"./cli-rtab0qa6.js";
12
12
  import"./cli-m9pfb7h9.js";
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,aAAa,QA4UlB,CAAA"}
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;IACvD,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"}
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;IAwE9D,gCAAgC;IAChC,KAAK,IAAI,IAAI;CAKd;AAkBD,kDAAkD;AAClD,eAAO,MAAM,cAAc,gBAAuB,CAAA"}
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;CACrB;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;CAC9D"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.29.2",
3
+ "version": "1.30.0",
4
4
  "description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",