@rynfar/meridian 1.49.1 → 1.51.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.
Files changed (45) hide show
  1. package/README.md +52 -19
  2. package/assets/banner.svg +28 -21
  3. package/assets/bot-icon-512.png +0 -0
  4. package/assets/bot-icon.svg +49 -43
  5. package/assets/how-it-works.svg +40 -40
  6. package/assets/icon-512.png +0 -0
  7. package/assets/icon.svg +16 -13
  8. package/assets/logo-512.png +0 -0
  9. package/assets/logo.svg +14 -12
  10. package/dist/{cli-mjrt119g.js → cli-7df5eg5d.js} +987 -176
  11. package/dist/{cli-q6dz9r5p.js → cli-aybvv9nb.js} +7 -1
  12. package/dist/cli-xmweegb1.js +179 -0
  13. package/dist/cli.js +4 -4
  14. package/dist/{pluginPage-85s6t6k8.js → pluginPage-03q5j753.js} +4 -10
  15. package/dist/{profileCli-fbhe9p5z.js → profileCli-j01rr99j.js} +1 -1
  16. package/dist/{profilePage-txxpbc44.js → profilePage-naychnb8.js} +6 -5
  17. package/dist/proxy/envelopeIntegrity.d.ts +50 -0
  18. package/dist/proxy/envelopeIntegrity.d.ts.map +1 -0
  19. package/dist/proxy/oauthUsage.d.ts.map +1 -1
  20. package/dist/proxy/passthroughEarlyStop.d.ts +8 -2
  21. package/dist/proxy/passthroughEarlyStop.d.ts.map +1 -1
  22. package/dist/proxy/plugins/pluginPage.d.ts.map +1 -1
  23. package/dist/proxy/query.d.ts.map +1 -1
  24. package/dist/proxy/server.d.ts.map +1 -1
  25. package/dist/server.js +3 -3
  26. package/dist/telemetry/dashboard.d.ts.map +1 -1
  27. package/dist/telemetry/landing.d.ts +6 -2
  28. package/dist/telemetry/landing.d.ts.map +1 -1
  29. package/dist/telemetry/percentiles.d.ts +5 -1
  30. package/dist/telemetry/percentiles.d.ts.map +1 -1
  31. package/dist/telemetry/pricing.d.ts +63 -0
  32. package/dist/telemetry/pricing.d.ts.map +1 -0
  33. package/dist/telemetry/pricingStore.d.ts +32 -0
  34. package/dist/telemetry/pricingStore.d.ts.map +1 -0
  35. package/dist/telemetry/profileBar.d.ts +19 -9
  36. package/dist/telemetry/profileBar.d.ts.map +1 -1
  37. package/dist/telemetry/profileUsage.d.ts.map +1 -1
  38. package/dist/telemetry/settingsPage.d.ts +1 -1
  39. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  40. package/dist/telemetry/sqlite.d.ts.map +1 -1
  41. package/dist/telemetry/store.d.ts.map +1 -1
  42. package/dist/telemetry/types.d.ts +38 -0
  43. package/dist/telemetry/types.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/cli-4rqtm83g.js +0 -148
@@ -20,7 +20,7 @@ import {
20
20
  profileBarHtml,
21
21
  profileBarJs,
22
22
  themeCss
23
- } from "./cli-4rqtm83g.js";
23
+ } from "./cli-xmweegb1.js";
24
24
  import {
25
25
  env,
26
26
  envBool,
@@ -29,6 +29,7 @@ import {
29
29
  getClaudeAuthStatusAsync,
30
30
  getResolvedClaudeExecutableInfo,
31
31
  hasExtendedContext,
32
+ init_env,
32
33
  isClosedControllerError,
33
34
  mapModelToClaudeModel,
34
35
  recordExtendedContextUnavailable,
@@ -36,7 +37,7 @@ import {
36
37
  resolvePassthrough,
37
38
  resolveSdkModelDefaults,
38
39
  stripExtendedContext
39
- } from "./cli-q6dz9r5p.js";
40
+ } from "./cli-aybvv9nb.js";
40
41
  import {
41
42
  getSetting
42
43
  } from "./cli-340h1chz.js";
@@ -61,6 +62,137 @@ import {
61
62
  __toESM
62
63
  } from "./cli-p9swy5t3.js";
63
64
 
65
+ // src/telemetry/pricing.ts
66
+ var exports_pricing = {};
67
+ __export(exports_pricing, {
68
+ resolveModelPricing: () => resolveModelPricing,
69
+ normalizeModelKey: () => normalizeModelKey,
70
+ estimateRequestCostUsd: () => estimateRequestCostUsd,
71
+ computeCostEstimate: () => computeCostEstimate,
72
+ CACHE_WRITE_MULTIPLIER: () => CACHE_WRITE_MULTIPLIER,
73
+ CACHE_READ_MULTIPLIER: () => CACHE_READ_MULTIPLIER,
74
+ BUILTIN_MODEL_PRICING: () => BUILTIN_MODEL_PRICING
75
+ });
76
+ function normalizeModelKey(model) {
77
+ return model.trim().toLowerCase().replace(/\[1m\]$/, "");
78
+ }
79
+ function rates(inputPerMTok, outputPerMTok) {
80
+ return {
81
+ inputPerMTok,
82
+ outputPerMTok,
83
+ cacheReadPerMTok: inputPerMTok * CACHE_READ_MULTIPLIER,
84
+ cacheWritePerMTok: inputPerMTok * CACHE_WRITE_MULTIPLIER
85
+ };
86
+ }
87
+ function resolveModelPricing(model, overrides) {
88
+ const normalized = normalizeModelKey(model);
89
+ if (overrides) {
90
+ const override = overrides[normalized];
91
+ if (override)
92
+ return override;
93
+ }
94
+ const exact = BUILTIN_MODEL_PRICING[normalized];
95
+ if (exact)
96
+ return exact;
97
+ if (normalized.includes("fable") || normalized.includes("mythos"))
98
+ return FABLE;
99
+ if (normalized.includes("haiku")) {
100
+ if (normalized.includes("3-5") || normalized.includes("3.5"))
101
+ return HAIKU_35;
102
+ if (/haiku-3\b|3-haiku/.test(normalized))
103
+ return HAIKU_3;
104
+ return HAIKU;
105
+ }
106
+ if (normalized.includes("opus")) {
107
+ if (/opus-4-1\b|opus-4-1-|opus-4-0|opus-4-2025|3-opus/.test(normalized))
108
+ return OPUS_LEGACY;
109
+ return OPUS;
110
+ }
111
+ if (normalized.includes("sonnet"))
112
+ return SONNET;
113
+ return null;
114
+ }
115
+ function estimateRequestCostUsd(metric, pricing) {
116
+ return (metric.inputTokens ?? 0) / 1e6 * pricing.inputPerMTok + (metric.outputTokens ?? 0) / 1e6 * pricing.outputPerMTok + (metric.cacheReadInputTokens ?? 0) / 1e6 * pricing.cacheReadPerMTok + (metric.cacheCreationInputTokens ?? 0) / 1e6 * pricing.cacheWritePerMTok;
117
+ }
118
+ function roundUsd(value) {
119
+ return Math.round(value * 1e6) / 1e6;
120
+ }
121
+ function computeCostEstimate(metrics, overrides) {
122
+ const byModel = {};
123
+ const byProfile = {};
124
+ let totalUsd = 0;
125
+ let unpricedRequestCount = 0;
126
+ for (const metric of metrics) {
127
+ const modelKey = metric.requestModel || metric.model;
128
+ const pricing = resolveModelPricing(modelKey, overrides);
129
+ const profileEntry = byProfile[metric.profileId ?? "default"] ??= { requests: 0, estimatedUsd: 0 };
130
+ profileEntry.requests++;
131
+ const entry = byModel[modelKey] ??= {
132
+ requests: 0,
133
+ inputTokens: 0,
134
+ outputTokens: 0,
135
+ cacheReadTokens: 0,
136
+ cacheCreationTokens: 0,
137
+ estimatedUsd: pricing === null ? null : 0
138
+ };
139
+ entry.requests++;
140
+ entry.inputTokens += metric.inputTokens ?? 0;
141
+ entry.outputTokens += metric.outputTokens ?? 0;
142
+ entry.cacheReadTokens += metric.cacheReadInputTokens ?? 0;
143
+ entry.cacheCreationTokens += metric.cacheCreationInputTokens ?? 0;
144
+ if (pricing === null) {
145
+ unpricedRequestCount++;
146
+ continue;
147
+ }
148
+ const cost = estimateRequestCostUsd(metric, pricing);
149
+ entry.estimatedUsd = (entry.estimatedUsd ?? 0) + cost;
150
+ profileEntry.estimatedUsd += cost;
151
+ totalUsd += cost;
152
+ }
153
+ for (const entry of Object.values(byProfile)) {
154
+ entry.estimatedUsd = roundUsd(entry.estimatedUsd);
155
+ }
156
+ for (const entry of Object.values(byModel)) {
157
+ if (entry.estimatedUsd !== null)
158
+ entry.estimatedUsd = roundUsd(entry.estimatedUsd);
159
+ }
160
+ return { totalUsd: roundUsd(totalUsd), byModel, unpricedRequestCount, byProfile };
161
+ }
162
+ var CACHE_READ_MULTIPLIER = 0.1, CACHE_WRITE_MULTIPLIER = 1.25, FABLE, OPUS, OPUS_LEGACY, SONNET, SONNET_5_INTRO, HAIKU, HAIKU_35, HAIKU_3, BUILTIN_MODEL_PRICING;
163
+ var init_pricing = __esm(() => {
164
+ FABLE = rates(10, 50);
165
+ OPUS = rates(5, 25);
166
+ OPUS_LEGACY = rates(15, 75);
167
+ SONNET = rates(3, 15);
168
+ SONNET_5_INTRO = rates(2, 10);
169
+ HAIKU = rates(1, 5);
170
+ HAIKU_35 = rates(0.8, 4);
171
+ HAIKU_3 = rates(0.25, 1.25);
172
+ BUILTIN_MODEL_PRICING = {
173
+ fable: FABLE,
174
+ "claude-fable-5": FABLE,
175
+ "claude-mythos-5": FABLE,
176
+ opus: OPUS,
177
+ "claude-opus-4-8": OPUS,
178
+ "claude-opus-4-7": OPUS,
179
+ "claude-opus-4-6": OPUS,
180
+ "claude-opus-4-5": OPUS,
181
+ "claude-opus-4-1": OPUS_LEGACY,
182
+ "claude-opus-4-0": OPUS_LEGACY,
183
+ "claude-opus-4-20250514": OPUS_LEGACY,
184
+ "claude-3-opus-20240229": OPUS_LEGACY,
185
+ sonnet: SONNET,
186
+ "claude-sonnet-5": SONNET_5_INTRO,
187
+ "claude-sonnet-4-6": SONNET,
188
+ "claude-sonnet-4-5": SONNET,
189
+ haiku: HAIKU,
190
+ "claude-haiku-4-5": HAIKU,
191
+ "claude-3-5-haiku-20241022": HAIKU_35,
192
+ "claude-3-haiku-20240307": HAIKU_3
193
+ };
194
+ });
195
+
64
196
  // src/telemetry/percentiles.ts
65
197
  function computePercentiles(values) {
66
198
  if (values.length === 0)
@@ -76,13 +208,14 @@ function computePercentiles(values) {
76
208
  avg: Math.round(sum / sorted.length)
77
209
  };
78
210
  }
79
- function computeSummary(metrics, windowMs) {
211
+ function computeSummary(metrics, windowMs, pricingOverrides) {
80
212
  if (metrics.length === 0) {
81
213
  const emptyPhase = { p50: 0, p95: 0, p99: 0, min: 0, max: 0, avg: 0 };
82
214
  return {
83
215
  windowMs,
84
216
  totalRequests: 0,
85
217
  errorCount: 0,
218
+ envelopeViolationCount: 0,
86
219
  requestsPerMinute: 0,
87
220
  queueWait: emptyPhase,
88
221
  proxyOverhead: emptyPhase,
@@ -98,10 +231,12 @@ function computeSummary(metrics, windowMs) {
98
231
  totalCacheCreationTokens: 0,
99
232
  avgCacheHitRate: 0,
100
233
  cacheMissOnResumeCount: 0
101
- }
234
+ },
235
+ costEstimate: { totalUsd: 0, byModel: {}, unpricedRequestCount: 0, byProfile: {} }
102
236
  };
103
237
  }
104
238
  const errorCount = metrics.filter((m) => m.error !== null).length;
239
+ const envelopeViolationCount = metrics.reduce((sum, m) => sum + (m.envelopeViolations?.length ?? 0), 0);
105
240
  const oldest = metrics[metrics.length - 1].timestamp;
106
241
  const newest = metrics[0].timestamp;
107
242
  const spanMs = Math.max(newest - oldest, 1);
@@ -148,6 +283,7 @@ function computeSummary(metrics, windowMs) {
148
283
  windowMs,
149
284
  totalRequests: metrics.length,
150
285
  errorCount,
286
+ envelopeViolationCount,
151
287
  requestsPerMinute: Math.round(requestsPerMinute * 100) / 100,
152
288
  queueWait: computePercentiles(queueWaits),
153
289
  proxyOverhead: computePercentiles(overheads),
@@ -163,10 +299,132 @@ function computeSummary(metrics, windowMs) {
163
299
  totalCacheCreationTokens,
164
300
  avgCacheHitRate: cacheHitRateCount > 0 ? Math.round(cacheHitRateSum / cacheHitRateCount * 100) / 100 : 0,
165
301
  cacheMissOnResumeCount
302
+ },
303
+ costEstimate: computeCostEstimate(metrics, pricingOverrides)
304
+ };
305
+ }
306
+ var init_percentiles = __esm(() => {
307
+ init_pricing();
308
+ });
309
+
310
+ // src/telemetry/pricingStore.ts
311
+ var exports_pricingStore = {};
312
+ __export(exports_pricingStore, {
313
+ validatePricingUpdate: () => validatePricingUpdate,
314
+ validateModelKey: () => validateModelKey,
315
+ setPricingOverride: () => setPricingOverride,
316
+ resetPricingOverridesCache: () => resetPricingOverridesCache,
317
+ getPricingOverrides: () => getPricingOverrides,
318
+ deletePricingOverride: () => deletePricingOverride
319
+ });
320
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
321
+ import { join, dirname } from "node:path";
322
+ import { homedir } from "node:os";
323
+ function getConfigPath() {
324
+ const explicit = env("PRICING_CONFIG");
325
+ if (explicit)
326
+ return explicit;
327
+ const dir = join(homedir(), ".config", "meridian");
328
+ return join(dir, "model-pricing.json");
329
+ }
330
+ function readOverrides() {
331
+ const path = getConfigPath();
332
+ const now = Date.now();
333
+ if (cachedOverrides && cachedPath === path && now - lastReadTime < CACHE_TTL_MS) {
334
+ return cachedOverrides;
335
+ }
336
+ try {
337
+ if (existsSync2(path)) {
338
+ const raw2 = JSON.parse(readFileSync(path, "utf-8"));
339
+ const result = {};
340
+ for (const [model, value] of Object.entries(raw2)) {
341
+ try {
342
+ result[normalizeModelKey(model)] = validatePricingUpdate(value);
343
+ } catch {}
344
+ }
345
+ cachedOverrides = result;
346
+ } else {
347
+ cachedOverrides = {};
348
+ }
349
+ } catch {
350
+ cachedOverrides = {};
351
+ }
352
+ cachedPath = path;
353
+ lastReadTime = now;
354
+ return cachedOverrides;
355
+ }
356
+ function writeOverrides(overrides) {
357
+ const path = getConfigPath();
358
+ const tmp = `${path}.tmp`;
359
+ try {
360
+ const dir = dirname(path);
361
+ if (!existsSync2(dir))
362
+ mkdirSync(dir, { recursive: true });
363
+ writeFileSync(tmp, JSON.stringify(overrides, null, 2));
364
+ renameSync(tmp, path);
365
+ cachedOverrides = overrides;
366
+ cachedPath = path;
367
+ lastReadTime = Date.now();
368
+ } catch (e) {
369
+ console.error("[pricing] write failed:", e.message);
370
+ }
371
+ }
372
+ function getPricingOverrides() {
373
+ return readOverrides();
374
+ }
375
+ function validatePricingUpdate(raw2) {
376
+ if (raw2 === null || typeof raw2 !== "object" || Array.isArray(raw2)) {
377
+ throw new Error("body must be a JSON object");
378
+ }
379
+ const input = raw2;
380
+ const readRate = (key, required) => {
381
+ const value = input[key];
382
+ if (value === undefined || value === null || value === "") {
383
+ if (required)
384
+ throw new Error(`${key} is required`);
385
+ return;
386
+ }
387
+ if (typeof value !== "number" || !isFinite(value) || value < 0) {
388
+ throw new Error(`${key} must be a non-negative finite number`);
166
389
  }
390
+ return value;
167
391
  };
392
+ const inputPerMTok = readRate("inputPerMTok", true);
393
+ const outputPerMTok = readRate("outputPerMTok", true);
394
+ const cacheReadPerMTok = readRate("cacheReadPerMTok", false) ?? inputPerMTok * CACHE_READ_MULTIPLIER;
395
+ const cacheWritePerMTok = readRate("cacheWritePerMTok", false) ?? inputPerMTok * CACHE_WRITE_MULTIPLIER;
396
+ return { inputPerMTok, outputPerMTok, cacheReadPerMTok, cacheWritePerMTok };
397
+ }
398
+ function validateModelKey(model) {
399
+ const normalized = normalizeModelKey(model);
400
+ if (normalized.length === 0)
401
+ throw new Error("model must be a non-empty string");
402
+ if (normalized.length > MAX_MODEL_KEY_LENGTH) {
403
+ throw new Error(`model must be at most ${MAX_MODEL_KEY_LENGTH} characters`);
404
+ }
405
+ return normalized;
168
406
  }
169
- var init_percentiles = () => {};
407
+ function setPricingOverride(model, pricing) {
408
+ const key = validateModelKey(model);
409
+ const overrides = { ...readOverrides(), [key]: pricing };
410
+ writeOverrides(overrides);
411
+ }
412
+ function deletePricingOverride(model) {
413
+ const key = validateModelKey(model);
414
+ const overrides = { ...readOverrides() };
415
+ delete overrides[key];
416
+ writeOverrides(overrides);
417
+ }
418
+ function resetPricingOverridesCache() {
419
+ cachedOverrides = null;
420
+ cachedPath = null;
421
+ lastReadTime = 0;
422
+ }
423
+ var MAX_MODEL_KEY_LENGTH = 200, cachedOverrides = null, cachedPath = null, lastReadTime = 0, CACHE_TTL_MS = 5000;
424
+ var init_pricingStore = __esm(() => {
425
+ init_env();
426
+ init_pricing();
427
+ });
170
428
 
171
429
  // node_modules/@neon-rs/load/dist/index.js
172
430
  var require_dist = __commonJS((exports) => {
@@ -269,8 +527,8 @@ var require_dist = __commonJS((exports) => {
269
527
  const header = report.header;
270
528
  return typeof header === "object" && !!header && "glibcVersionRuntime" in header;
271
529
  }
272
- function load(dirname2) {
273
- const m = path.join(dirname2, "index.node");
530
+ function load(dirname3) {
531
+ const m = path.join(dirname3, "index.node");
274
532
  return fs.existsSync(m) ? __require(m) : null;
275
533
  }
276
534
  exports.load = load;
@@ -293,7 +551,7 @@ var require_process = __commonJS((exports, module) => {
293
551
  var require_filesystem = __commonJS((exports, module) => {
294
552
  var fs = __require("fs");
295
553
  var LDD_PATH = "/usr/bin/ldd";
296
- var readFileSync2 = (path) => fs.readFileSync(path, "utf-8");
554
+ var readFileSync3 = (path) => fs.readFileSync(path, "utf-8");
297
555
  var readFile = (path) => new Promise((resolve2, reject) => {
298
556
  fs.readFile(path, "utf-8", (err, data) => {
299
557
  if (err) {
@@ -305,7 +563,7 @@ var require_filesystem = __commonJS((exports, module) => {
305
563
  });
306
564
  module.exports = {
307
565
  LDD_PATH,
308
- readFileSync: readFileSync2,
566
+ readFileSync: readFileSync3,
309
567
  readFile
310
568
  };
311
569
  });
@@ -314,7 +572,7 @@ var require_filesystem = __commonJS((exports, module) => {
314
572
  var require_detect_libc = __commonJS((exports, module) => {
315
573
  var childProcess = __require("child_process");
316
574
  var { isLinux, getReport } = require_process();
317
- var { LDD_PATH, readFile, readFileSync: readFileSync2 } = require_filesystem();
575
+ var { LDD_PATH, readFile, readFileSync: readFileSync3 } = require_filesystem();
318
576
  var cachedFamilyFilesystem;
319
577
  var cachedVersionFilesystem;
320
578
  var command = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
@@ -394,7 +652,7 @@ var require_detect_libc = __commonJS((exports, module) => {
394
652
  }
395
653
  cachedFamilyFilesystem = null;
396
654
  try {
397
- const lddContent = readFileSync2(LDD_PATH);
655
+ const lddContent = readFileSync3(LDD_PATH);
398
656
  cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
399
657
  } catch (e) {}
400
658
  return cachedFamilyFilesystem;
@@ -449,7 +707,7 @@ var require_detect_libc = __commonJS((exports, module) => {
449
707
  }
450
708
  cachedVersionFilesystem = null;
451
709
  try {
452
- const lddContent = readFileSync2(LDD_PATH);
710
+ const lddContent = readFileSync3(LDD_PATH);
453
711
  const versionMatch = lddContent.match(RE_GLIBC_VERSION);
454
712
  if (versionMatch) {
455
713
  cachedVersionFilesystem = versionMatch[1];
@@ -907,7 +1165,7 @@ class SqliteTelemetryStore {
907
1165
  status, queue_wait_ms, proxy_overhead_ms, ttfb_ms,
908
1166
  upstream_duration_ms, total_duration_ms, content_blocks, text_events, error,
909
1167
  input_tokens, output_tokens, cache_read_input_tokens,
910
- cache_creation_input_tokens, cache_hit_rate
1168
+ cache_creation_input_tokens, cache_hit_rate, profile_id, envelope_violations
911
1169
  ) VALUES (
912
1170
  @requestId, @timestamp, @adapter, @requestSource, @model, @requestModel, @mode,
913
1171
  @isResume, @isPassthrough, @lineageType,
@@ -916,7 +1174,7 @@ class SqliteTelemetryStore {
916
1174
  @status, @queueWaitMs, @proxyOverheadMs, @ttfbMs,
917
1175
  @upstreamDurationMs, @totalDurationMs, @contentBlocks, @textEvents, @error,
918
1176
  @inputTokens, @outputTokens, @cacheReadInputTokens,
919
- @cacheCreationInputTokens, @cacheHitRate
1177
+ @cacheCreationInputTokens, @cacheHitRate, @profileId, @envelopeViolations
920
1178
  )
921
1179
  `);
922
1180
  this.countStmt = db.prepare("SELECT COUNT(*) as cnt FROM metrics");
@@ -954,7 +1212,9 @@ class SqliteTelemetryStore {
954
1212
  outputTokens: metric.outputTokens ?? null,
955
1213
  cacheReadInputTokens: metric.cacheReadInputTokens ?? null,
956
1214
  cacheCreationInputTokens: metric.cacheCreationInputTokens ?? null,
957
- cacheHitRate: metric.cacheHitRate ?? null
1215
+ cacheHitRate: metric.cacheHitRate ?? null,
1216
+ profileId: metric.profileId ?? null,
1217
+ envelopeViolations: metric.envelopeViolations && metric.envelopeViolations.length > 0 ? JSON.stringify(metric.envelopeViolations) : null
958
1218
  });
959
1219
  } catch (err) {
960
1220
  console.error("[telemetry] SQLite write failed, skipping:", err);
@@ -1003,7 +1263,7 @@ class SqliteTelemetryStore {
1003
1263
  summarize(windowMs = 60 * 60 * 1000) {
1004
1264
  const since = Date.now() - windowMs;
1005
1265
  const metrics = this.getRecent({ limit: 1e5, since });
1006
- return computeSummary(metrics, windowMs);
1266
+ return computeSummary(metrics, windowMs, getPricingOverrides());
1007
1267
  }
1008
1268
  clear() {
1009
1269
  try {
@@ -1087,6 +1347,16 @@ class SqliteDiagnosticLogStore {
1087
1347
  } catch {}
1088
1348
  }
1089
1349
  }
1350
+ function parseEnvelopeViolations(raw2) {
1351
+ if (typeof raw2 !== "string" || raw2.length === 0)
1352
+ return;
1353
+ try {
1354
+ const parsed = JSON.parse(raw2);
1355
+ return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string") : undefined;
1356
+ } catch {
1357
+ return;
1358
+ }
1359
+ }
1090
1360
  function rowToMetric(r) {
1091
1361
  return {
1092
1362
  requestId: r.request_id,
@@ -1119,7 +1389,9 @@ function rowToMetric(r) {
1119
1389
  outputTokens: r.output_tokens ?? undefined,
1120
1390
  cacheReadInputTokens: r.cache_read_input_tokens ?? undefined,
1121
1391
  cacheCreationInputTokens: r.cache_creation_input_tokens ?? undefined,
1122
- cacheHitRate: r.cache_hit_rate ?? undefined
1392
+ cacheHitRate: r.cache_hit_rate ?? undefined,
1393
+ profileId: r.profile_id ?? undefined,
1394
+ envelopeViolations: parseEnvelopeViolations(r.envelope_violations)
1123
1395
  };
1124
1396
  }
1125
1397
  function createSqliteStores(dbPath, retentionDays) {
@@ -1167,7 +1439,9 @@ CREATE TABLE IF NOT EXISTS metrics (
1167
1439
  output_tokens INTEGER,
1168
1440
  cache_read_input_tokens INTEGER,
1169
1441
  cache_creation_input_tokens INTEGER,
1170
- cache_hit_rate REAL
1442
+ cache_hit_rate REAL,
1443
+ profile_id TEXT,
1444
+ envelope_violations TEXT
1171
1445
  );
1172
1446
  CREATE INDEX IF NOT EXISTS idx_metrics_ts ON metrics(timestamp);
1173
1447
  CREATE INDEX IF NOT EXISTS idx_metrics_model ON metrics(model);
@@ -1186,9 +1460,12 @@ CREATE INDEX IF NOT EXISTS idx_logs_cat ON diagnostic_logs(category);
1186
1460
  `, CLEANUP_INTERVAL = 1000;
1187
1461
  var init_sqlite = __esm(() => {
1188
1462
  init_percentiles();
1463
+ init_pricingStore();
1189
1464
  import_libsql = __toESM(require_libsql(), 1);
1190
1465
  METRICS_MIGRATIONS = [
1191
- "ALTER TABLE metrics ADD COLUMN request_source TEXT"
1466
+ "ALTER TABLE metrics ADD COLUMN request_source TEXT",
1467
+ "ALTER TABLE metrics ADD COLUMN profile_id TEXT",
1468
+ "ALTER TABLE metrics ADD COLUMN envelope_violations TEXT"
1192
1469
  ];
1193
1470
  });
1194
1471
 
@@ -1202,40 +1479,40 @@ __export(exports_sdkFeatures, {
1202
1479
  getExplicitThinking: () => getExplicitThinking,
1203
1480
  getAllFeatureConfigs: () => getAllFeatureConfigs
1204
1481
  });
1205
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
1206
- import { join as join5 } from "node:path";
1207
- import { homedir as homedir4 } from "node:os";
1208
- function getConfigPath() {
1209
- const dir = join5(homedir4(), ".config", "meridian");
1210
- if (!existsSync6(dir))
1211
- mkdirSync2(dir, { recursive: true });
1212
- return join5(dir, "sdk-features.json");
1482
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3 } from "node:fs";
1483
+ import { join as join6 } from "node:path";
1484
+ import { homedir as homedir5 } from "node:os";
1485
+ function getConfigPath2() {
1486
+ const dir = join6(homedir5(), ".config", "meridian");
1487
+ if (!existsSync7(dir))
1488
+ mkdirSync3(dir, { recursive: true });
1489
+ return join6(dir, "sdk-features.json");
1213
1490
  }
1214
1491
  function readConfig() {
1215
1492
  const now = Date.now();
1216
- if (cachedConfig && now - lastReadTime < CACHE_TTL_MS)
1493
+ if (cachedConfig && now - lastReadTime2 < CACHE_TTL_MS2)
1217
1494
  return cachedConfig;
1218
- const path3 = getConfigPath();
1495
+ const path3 = getConfigPath2();
1219
1496
  try {
1220
- if (existsSync6(path3)) {
1221
- cachedConfig = JSON.parse(readFileSync5(path3, "utf-8"));
1497
+ if (existsSync7(path3)) {
1498
+ cachedConfig = JSON.parse(readFileSync6(path3, "utf-8"));
1222
1499
  } else {
1223
1500
  cachedConfig = {};
1224
1501
  }
1225
1502
  } catch {
1226
1503
  cachedConfig = {};
1227
1504
  }
1228
- lastReadTime = now;
1505
+ lastReadTime2 = now;
1229
1506
  return cachedConfig;
1230
1507
  }
1231
1508
  function writeConfig(config) {
1232
- const path3 = getConfigPath();
1509
+ const path3 = getConfigPath2();
1233
1510
  const tmp = `${path3}.tmp`;
1234
1511
  try {
1235
- writeFileSync2(tmp, JSON.stringify(config, null, 2));
1236
- renameSync2(tmp, path3);
1512
+ writeFileSync3(tmp, JSON.stringify(config, null, 2));
1513
+ renameSync3(tmp, path3);
1237
1514
  cachedConfig = config;
1238
- lastReadTime = Date.now();
1515
+ lastReadTime2 = Date.now();
1239
1516
  } catch (e) {
1240
1517
  console.error("[sdk-features] write failed:", e.message);
1241
1518
  }
@@ -1308,7 +1585,7 @@ function resetAdapterFeatures(adapterName) {
1308
1585
  delete config[adapterName];
1309
1586
  writeConfig(config);
1310
1587
  }
1311
- var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null, lastReadTime = 0, CACHE_TTL_MS = 5000, VALID_CLAUDE_MD_VALUES, VALID_THINKING_VALUES;
1588
+ var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null, lastReadTime2 = 0, CACHE_TTL_MS2 = 5000, VALID_CLAUDE_MD_VALUES, VALID_THINKING_VALUES;
1312
1589
  var init_sdkFeatures = __esm(() => {
1313
1590
  DEFAULT_FEATURES = {
1314
1591
  codeSystemPrompt: true,
@@ -1358,7 +1635,7 @@ var init_settingsPage = __esm(() => {
1358
1635
  ${themeCss}
1359
1636
  * { box-sizing: border-box; margin: 0; padding: 0; }
1360
1637
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
1361
- background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
1638
+ color: var(--text); padding: 0; line-height: 1.5; }
1362
1639
  ${profileBarCss}
1363
1640
  .content { max-width: 900px; margin: 0 auto; padding: 24px; }
1364
1641
  h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
@@ -1432,6 +1709,28 @@ var init_settingsPage = __esm(() => {
1432
1709
  border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer;
1433
1710
  }
1434
1711
  .reset-btn:hover { border-color: var(--red); color: var(--red); }
1712
+
1713
+ /* Model pricing */
1714
+ .pricing-table { width: 100%; border-collapse: collapse; font-size: 12px; }
1715
+ .pricing-table th { text-align: left; padding: 8px 10px; color: var(--muted); font-weight: 500;
1716
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
1717
+ .pricing-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); }
1718
+ .pricing-table tr:last-child td { border-bottom: none; }
1719
+ .pricing-model { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; word-break: break-all; }
1720
+ .pricing-input { background: var(--bg); color: var(--text); border: 1px solid var(--border);
1721
+ border-radius: 6px; padding: 4px 8px; font-size: 12px; width: 84px; text-align: right;
1722
+ font-variant-numeric: tabular-nums; }
1723
+ .pricing-input:focus { border-color: var(--accent); outline: none; }
1724
+ .pricing-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px;
1725
+ text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }
1726
+ .badge-override { background: rgba(210, 153, 34, 0.15); color: var(--yellow); }
1727
+ .badge-builtin { background: rgba(139, 148, 158, 0.15); color: var(--muted); }
1728
+ .pricing-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px;
1729
+ padding-top: 14px; border-top: 1px solid var(--border); }
1730
+ .pricing-add input[type="text"] { width: 240px; text-align: left; }
1731
+ .add-btn { background: var(--accent); border: none; color: #fff; border-radius: 6px;
1732
+ padding: 5px 14px; font-size: 12px; font-weight: 500; cursor: pointer; }
1733
+ .pricing-note { font-size: 11px; color: var(--muted); margin-top: 12px; line-height: 1.6; }
1435
1734
  </style>
1436
1735
  </head>
1437
1736
  <body>
@@ -1448,6 +1747,32 @@ ${profileBarHtml}
1448
1747
  </p>
1449
1748
 
1450
1749
  <div id="adapters"></div>
1750
+
1751
+ <h1 style="margin-top:40px">Model Pricing</h1>
1752
+ <p class="subtitle" style="max-width:720px;line-height:1.6">
1753
+ Rates used by the telemetry cost estimate, in USD per million tokens. Edit a value to override the
1754
+ built-in rate, or add models the built-in table doesn't know about (they show as "no pricing" on the
1755
+ dashboard until defined here). Changes apply on the next dashboard refresh.
1756
+ </p>
1757
+ <div class="adapter-card">
1758
+ <table class="pricing-table">
1759
+ <thead><tr><th>Model</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cache Write</th><th>Source</th><th></th></tr></thead>
1760
+ <tbody id="pricingRows"></tbody>
1761
+ </table>
1762
+ <div class="pricing-add">
1763
+ <input type="text" class="pricing-input" id="newModelName" placeholder="model id (e.g. claude-opus-9)">
1764
+ <input type="number" class="pricing-input" id="newModelInput" placeholder="input" min="0" step="0.01">
1765
+ <input type="number" class="pricing-input" id="newModelOutput" placeholder="output" min="0" step="0.01">
1766
+ <input type="number" class="pricing-input" id="newModelCacheRead" placeholder="cache read" min="0" step="0.01">
1767
+ <input type="number" class="pricing-input" id="newModelCacheWrite" placeholder="cache write" min="0" step="0.01">
1768
+ <button class="add-btn" onclick="addPricingModel()">Add Model</button>
1769
+ </div>
1770
+ <div class="pricing-note">
1771
+ Cache read and cache write are optional; when left blank they default to 0.1x and 1.25x of the
1772
+ input rate (the 5-minute cache TTL multipliers). Verify current list prices at
1773
+ <a href="https://claude.com/pricing" target="_blank" rel="noreferrer" style="color:var(--accent)">claude.com/pricing</a>.
1774
+ </div>
1775
+ </div>
1451
1776
  </div>
1452
1777
 
1453
1778
  <div class="save-indicator" id="saveIndicator">Saved</div>
@@ -1582,7 +1907,127 @@ function render() {
1582
1907
  }
1583
1908
  }
1584
1909
 
1910
+ // ---- Model pricing (telemetry cost estimate) ----
1911
+ let pricingData = { builtin: {}, overrides: {} };
1912
+
1913
+ function fmtRate(v) { return String(Math.round(v * 10000) / 10000); }
1914
+
1915
+ async function loadPricing() {
1916
+ const res = await fetch('/settings/api/pricing');
1917
+ pricingData = await res.json();
1918
+ renderPricing();
1919
+ }
1920
+
1921
+ async function putPricing(model, rates) {
1922
+ const res = await fetch('/settings/api/pricing/' + encodeURIComponent(model), {
1923
+ method: 'PUT',
1924
+ headers: { 'Content-Type': 'application/json' },
1925
+ body: JSON.stringify(rates),
1926
+ });
1927
+ if (!res.ok) {
1928
+ const err = await res.json().catch(function () { return {}; });
1929
+ alert('Could not save pricing: ' + (err.error || ('HTTP ' + res.status)));
1930
+ return false;
1931
+ }
1932
+ showSaved();
1933
+ return true;
1934
+ }
1935
+
1936
+ async function removePricing(model) {
1937
+ await fetch('/settings/api/pricing/' + encodeURIComponent(model), { method: 'DELETE' });
1938
+ showSaved();
1939
+ await loadPricing();
1940
+ }
1941
+
1942
+ function rateCell(value, onCommit) {
1943
+ const td = document.createElement('td');
1944
+ const input = document.createElement('input');
1945
+ input.type = 'number';
1946
+ input.min = '0';
1947
+ input.step = '0.01';
1948
+ input.className = 'pricing-input';
1949
+ input.value = fmtRate(value);
1950
+ input.addEventListener('change', onCommit);
1951
+ td.appendChild(input);
1952
+ return { td: td, input: input };
1953
+ }
1954
+
1955
+ function renderPricing() {
1956
+ const tbody = document.getElementById('pricingRows');
1957
+ tbody.innerHTML = '';
1958
+ const models = Array.from(new Set(
1959
+ Object.keys(pricingData.builtin).concat(Object.keys(pricingData.overrides))
1960
+ )).sort();
1961
+
1962
+ for (const model of models) {
1963
+ const override = pricingData.overrides[model];
1964
+ const effective = override || pricingData.builtin[model];
1965
+ const tr = document.createElement('tr');
1966
+
1967
+ const nameTd = document.createElement('td');
1968
+ nameTd.className = 'pricing-model';
1969
+ nameTd.textContent = model;
1970
+ tr.appendChild(nameTd);
1971
+
1972
+ const cells = [];
1973
+ const commit = async function () {
1974
+ const rates = {
1975
+ inputPerMTok: parseFloat(cells[0].input.value),
1976
+ outputPerMTok: parseFloat(cells[1].input.value),
1977
+ cacheReadPerMTok: parseFloat(cells[2].input.value),
1978
+ cacheWritePerMTok: parseFloat(cells[3].input.value),
1979
+ };
1980
+ for (const k in rates) { if (!isFinite(rates[k]) || rates[k] < 0) return; }
1981
+ if (await putPricing(model, rates)) await loadPricing();
1982
+ };
1983
+ ['inputPerMTok', 'outputPerMTok', 'cacheReadPerMTok', 'cacheWritePerMTok'].forEach(function (key) {
1984
+ const cell = rateCell(effective[key], commit);
1985
+ cells.push(cell);
1986
+ tr.appendChild(cell.td);
1987
+ });
1988
+
1989
+ const badgeTd = document.createElement('td');
1990
+ const badge = document.createElement('span');
1991
+ badge.className = 'pricing-badge ' + (override ? 'badge-override' : 'badge-builtin');
1992
+ badge.textContent = override ? 'Override' : 'Built-in';
1993
+ badgeTd.appendChild(badge);
1994
+ tr.appendChild(badgeTd);
1995
+
1996
+ const actionTd = document.createElement('td');
1997
+ if (override) {
1998
+ const btn = document.createElement('button');
1999
+ btn.className = 'reset-btn';
2000
+ btn.textContent = pricingData.builtin[model] ? 'Reset' : 'Remove';
2001
+ btn.addEventListener('click', function () { removePricing(model); });
2002
+ actionTd.appendChild(btn);
2003
+ }
2004
+ tr.appendChild(actionTd);
2005
+
2006
+ tbody.appendChild(tr);
2007
+ }
2008
+ }
2009
+
2010
+ async function addPricingModel() {
2011
+ const name = document.getElementById('newModelName').value.trim();
2012
+ const inputRate = parseFloat(document.getElementById('newModelInput').value);
2013
+ const outputRate = parseFloat(document.getElementById('newModelOutput').value);
2014
+ if (!name) { alert('Enter a model id'); return; }
2015
+ if (!isFinite(inputRate) || !isFinite(outputRate)) { alert('Enter input and output rates (USD per million tokens)'); return; }
2016
+ const rates = { inputPerMTok: inputRate, outputPerMTok: outputRate };
2017
+ const cacheRead = parseFloat(document.getElementById('newModelCacheRead').value);
2018
+ const cacheWrite = parseFloat(document.getElementById('newModelCacheWrite').value);
2019
+ if (isFinite(cacheRead)) rates.cacheReadPerMTok = cacheRead;
2020
+ if (isFinite(cacheWrite)) rates.cacheWritePerMTok = cacheWrite;
2021
+ if (await putPricing(name, rates)) {
2022
+ ['newModelName', 'newModelInput', 'newModelOutput', 'newModelCacheRead', 'newModelCacheWrite'].forEach(function (id) {
2023
+ document.getElementById(id).value = '';
2024
+ });
2025
+ await loadPricing();
2026
+ }
2027
+ }
2028
+
1585
2029
  loadConfig();
2030
+ loadPricing();
1586
2031
  ${profileBarJs}
1587
2032
  </script>
1588
2033
  </body>
@@ -3742,8 +4187,8 @@ var serve = (options, listeningListener) => {
3742
4187
  };
3743
4188
 
3744
4189
  // src/proxy/server.ts
3745
- import { homedir as homedir5 } from "node:os";
3746
- import { join as join6 } from "node:path";
4190
+ import { homedir as homedir6 } from "node:os";
4191
+ import { join as join7 } from "node:path";
3747
4192
  import { query } from "@anthropic-ai/claude-agent-sdk";
3748
4193
 
3749
4194
  // src/proxy/rateLimitStore.ts
@@ -3880,6 +4325,16 @@ function normalizeUtilization(raw2) {
3880
4325
  return null;
3881
4326
  return Math.max(0, raw2 / 100);
3882
4327
  }
4328
+ function modelScopedWindowType(limit) {
4329
+ if (limit.kind !== "weekly_scoped")
4330
+ return null;
4331
+ const model = limit.scope?.model;
4332
+ const name = model?.display_name?.trim() || model?.id?.trim();
4333
+ if (!name)
4334
+ return null;
4335
+ const slug = name.toLowerCase().replace(/^claude[\s_-]+/, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
4336
+ return slug ? `seven_day_${slug}` : null;
4337
+ }
3883
4338
  function buildSnapshot(raw2) {
3884
4339
  const windows = [];
3885
4340
  for (const key of WINDOW_TYPES) {
@@ -3892,6 +4347,18 @@ function buildSnapshot(raw2) {
3892
4347
  continue;
3893
4348
  windows.push({ type: key, utilization, resetsAt });
3894
4349
  }
4350
+ const windowTypes = new Set(windows.map((window) => window.type));
4351
+ for (const limit of raw2.limits ?? []) {
4352
+ const type = modelScopedWindowType(limit);
4353
+ if (!type || windowTypes.has(type))
4354
+ continue;
4355
+ const utilization = normalizeUtilization(limit.percent);
4356
+ const resetsAt = parseIsoToMs(limit.resets_at);
4357
+ if (utilization === null && resetsAt === null)
4358
+ continue;
4359
+ windows.push({ type, utilization, resetsAt });
4360
+ windowTypes.add(type);
4361
+ }
3895
4362
  const extra = raw2.extra_usage;
3896
4363
  const extraUsage = extra ? {
3897
4364
  isEnabled: !!extra.is_enabled,
@@ -3998,6 +4465,9 @@ var DEFAULT_PROXY_CONFIG = {
3998
4465
  version: undefined
3999
4466
  };
4000
4467
 
4468
+ // src/proxy/server.ts
4469
+ init_env();
4470
+
4001
4471
  // src/proxy/transform.ts
4002
4472
  function runTransformHook(transforms, hook, ctx, adapterName) {
4003
4473
  return transforms.reduce((acc, transform) => {
@@ -8283,7 +8753,7 @@ function noteUserContent(tracker, content) {
8283
8753
  return;
8284
8754
  for (const block of content) {
8285
8755
  const b = block;
8286
- if (b?.type === "tool_result" && typeof b.tool_use_id === "string" && tracker.expected.has(b.tool_use_id)) {
8756
+ if (b?.type === "tool_result" && typeof b.tool_use_id === "string") {
8287
8757
  tracker.resolved.add(b.tool_use_id);
8288
8758
  }
8289
8759
  }
@@ -8301,6 +8771,45 @@ function shouldEarlyStop(tracker) {
8301
8771
  return true;
8302
8772
  }
8303
8773
 
8774
+ // src/proxy/envelopeIntegrity.ts
8775
+ function checkEmptyToolInputs(contentBlocks, tools) {
8776
+ if (!tools || tools.length === 0 || contentBlocks.length === 0)
8777
+ return [];
8778
+ const requiredByName = new Map;
8779
+ for (const t of tools) {
8780
+ const req = t.input_schema?.required;
8781
+ requiredByName.set(t.name, Array.isArray(req) && req.length > 0);
8782
+ }
8783
+ const violations = [];
8784
+ for (const b of contentBlocks) {
8785
+ if (b.type !== "tool_use" || typeof b.name !== "string")
8786
+ continue;
8787
+ if (!requiredByName.get(b.name))
8788
+ continue;
8789
+ const input = b.input;
8790
+ const empty = input == null || typeof input === "object" && Object.keys(input).length === 0;
8791
+ if (empty) {
8792
+ violations.push({
8793
+ type: "empty_tool_input",
8794
+ detail: `tool_use ${b.name} (${String(b.id ?? "?")}) delivered with empty input but schema requires arguments`
8795
+ });
8796
+ }
8797
+ }
8798
+ return violations;
8799
+ }
8800
+ function checkUndeliveredToolUses(captured, deliveredIds) {
8801
+ const violations = [];
8802
+ for (const c of captured) {
8803
+ if (!deliveredIds.has(c.id)) {
8804
+ violations.push({
8805
+ type: "undelivered_tool_use",
8806
+ detail: `captured tool_use ${c.name} (${c.id}) was never delivered to the client`
8807
+ });
8808
+ }
8809
+ }
8810
+ return violations;
8811
+ }
8812
+
8304
8813
  // src/proxy/agentDefs.ts
8305
8814
  var FALLBACK_AGENT_NAME = "general";
8306
8815
  var DEFAULT_AGENT_TYPES = {
@@ -8560,11 +9069,13 @@ class LRUMap {
8560
9069
  }
8561
9070
 
8562
9071
  // src/telemetry/index.ts
8563
- import { join } from "node:path";
8564
- import { homedir } from "node:os";
9072
+ init_env();
9073
+ import { join as join2 } from "node:path";
9074
+ import { homedir as homedir2 } from "node:os";
8565
9075
 
8566
9076
  // src/telemetry/store.ts
8567
9077
  init_percentiles();
9078
+ init_pricingStore();
8568
9079
  var DEFAULT_CAPACITY = 1000;
8569
9080
  function getCapacity() {
8570
9081
  const raw2 = process.env.MERIDIAN_TELEMETRY_SIZE ?? process.env.CLAUDE_PROXY_TELEMETRY_SIZE;
@@ -8623,7 +9134,7 @@ class MemoryTelemetryStore {
8623
9134
  summarize(windowMs = 60 * 60 * 1000) {
8624
9135
  const since = Date.now() - windowMs;
8625
9136
  const metrics = this.getRecent({ limit: this.capacity, since });
8626
- return computeSummary(metrics, windowMs);
9137
+ return computeSummary(metrics, windowMs, getPricingOverrides());
8627
9138
  }
8628
9139
  clear() {
8629
9140
  this.buffer = new Array(this.capacity).fill(null);
@@ -8684,8 +9195,8 @@ class MemoryDiagnosticLogStore {
8684
9195
  }
8685
9196
  var diagnosticLog = new MemoryDiagnosticLogStore;
8686
9197
  // src/telemetry/routes.ts
8687
- import { existsSync as existsSync2, readFileSync } from "node:fs";
8688
- import { resolve, dirname } from "node:path";
9198
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
9199
+ import { resolve, dirname as dirname2 } from "node:path";
8689
9200
  import { fileURLToPath } from "node:url";
8690
9201
 
8691
9202
  // src/telemetry/dashboard.ts
@@ -8702,7 +9213,7 @@ var dashboardHtml = `<!DOCTYPE html>
8702
9213
  :root { --total: var(--accent); }
8703
9214
  * { box-sizing: border-box; margin: 0; padding: 0; }
8704
9215
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
8705
- background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
9216
+ color: var(--text); padding: 0; line-height: 1.5; }
8706
9217
  h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
8707
9218
  .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }
8708
9219
  .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
@@ -8788,8 +9299,8 @@ var dashboardHtml = `<!DOCTYPE html>
8788
9299
  <body>
8789
9300
  ` + profileBarHtml + `
8790
9301
  <div style="padding:24px">
8791
- <h1>Meridian</h1>
8792
- <div class="subtitle">Request Performance Telemetry</div>
9302
+ <h1>Telemetry</h1>
9303
+ <div class="subtitle">Request performance, cost, and wire-contract integrity</div>
8793
9304
 
8794
9305
  <div class="refresh-bar">
8795
9306
  <select id="window">
@@ -8825,6 +9336,26 @@ function ago(ts) {
8825
9336
  return Math.floor(s/3600) + 'h ago';
8826
9337
  }
8827
9338
 
9339
+ function fmtTok(n) {
9340
+ return n > 1000000 ? (n/1000000).toFixed(1) + 'M' : n > 1000 ? Math.round(n/1000) + 'k' : String(n);
9341
+ }
9342
+
9343
+ // Model names come from client-supplied request bodies (requestModel) — escape
9344
+ // before concatenating into innerHTML so a quirky/malicious client can't
9345
+ // script the dashboard.
9346
+ function esc(s) {
9347
+ return String(s).replace(/[&<>"']/g, function (ch) {
9348
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ch];
9349
+ });
9350
+ }
9351
+
9352
+ function usd(v) {
9353
+ if (v == null) return '—';
9354
+ if (v > 0 && v < 0.01) return '$' + v.toFixed(4);
9355
+ if (v < 100) return '$' + v.toFixed(2);
9356
+ return '$' + Math.round(v).toLocaleString();
9357
+ }
9358
+
8828
9359
  function pctRow(label, color, phase) {
8829
9360
  return '<tr>'
8830
9361
  + '<td><span class="phase-dot" style="background:' + color + '"></span>' + label + '</td>'
@@ -8897,6 +9428,7 @@ function render(s, reqs, logs, quota) {
8897
9428
  html += '<div class="cards">'
8898
9429
  + card('Requests', s.totalRequests, s.requestsPerMinute.toFixed(1) + ' req/min')
8899
9430
  + card('Errors', s.errorCount, s.totalRequests > 0 ? ((s.errorCount/s.totalRequests)*100).toFixed(1) + '% error rate' : '')
9431
+ + '<div class="card"><div class="card-label">Envelope</div><div class="card-value" style="color:' + ((s.envelopeViolationCount || 0) > 0 ? 'var(--red)' : 'var(--green)') + '">' + (s.envelopeViolationCount || 0) + '</div><div class="card-detail">' + ((s.envelopeViolationCount || 0) > 0 ? 'wire-contract violations — check logs' : 'wire contract clean') + '</div></div>'
8900
9432
  + card('Median Total', ms(s.totalDuration.p50), 'p95: ' + ms(s.totalDuration.p95))
8901
9433
  + card('Median TTFB', ms(s.ttfb.p50), 'p95: ' + ms(s.ttfb.p95))
8902
9434
  + card('Proxy Overhead', ms(s.proxyOverhead.p50), 'p95: ' + ms(s.proxyOverhead.p95))
@@ -8906,7 +9438,6 @@ function render(s, reqs, logs, quota) {
8906
9438
  // Token usage cards
8907
9439
  if (s.tokenUsage) {
8908
9440
  const t = s.tokenUsage;
8909
- const fmtTok = n => n > 1000000 ? (n/1000000).toFixed(1) + 'M' : n > 1000 ? Math.round(n/1000) + 'k' : String(n);
8910
9441
  html += '<div class="section"><div class="section-title">Token Usage</div></div>';
8911
9442
  html += '<div class="cards">'
8912
9443
  + card('Input Tokens', fmtTok(t.totalInputTokens), '')
@@ -8917,12 +9448,51 @@ function render(s, reqs, logs, quota) {
8917
9448
  + '</div>';
8918
9449
  }
8919
9450
 
9451
+ // Estimated cost: static API list pricing applied to the window's token usage
9452
+ if (s.costEstimate && Object.keys(s.costEstimate.byModel).length > 0) {
9453
+ const ce = s.costEstimate;
9454
+ const costRows = Object.entries(ce.byModel)
9455
+ .sort((a, b) => (b[1].estimatedUsd || 0) - (a[1].estimatedUsd || 0));
9456
+
9457
+ html += '<div class="section"><div class="section-title">Estimated Cost</div></div>';
9458
+ html += '<div class="cards">'
9459
+ + card('Est. API Cost', usd(ce.totalUsd), 'window total at API list prices');
9460
+ for (const [model, m] of costRows) {
9461
+ html += card(esc(model), usd(m.estimatedUsd), m.requests + ' req' + (m.requests === 1 ? '' : 's'));
9462
+ }
9463
+ html += '</div>';
9464
+
9465
+ html += '<div class="section">'
9466
+ + '<table><thead><tr><th>Model</th><th>Requests</th><th>Input</th><th>Output</th>'
9467
+ + '<th>Cache Read</th><th>Cache Write</th><th>Est. Cost</th></tr></thead><tbody>';
9468
+ for (const [model, m] of costRows) {
9469
+ html += '<tr>'
9470
+ + '<td>' + esc(model) + (m.estimatedUsd == null ? ' <span style="font-size:10px;color:var(--yellow)">no pricing</span>' : '') + '</td>'
9471
+ + '<td class="mono">' + m.requests + '</td>'
9472
+ + '<td class="mono">' + fmtTok(m.inputTokens) + '</td>'
9473
+ + '<td class="mono">' + fmtTok(m.outputTokens) + '</td>'
9474
+ + '<td class="mono">' + fmtTok(m.cacheReadTokens) + '</td>'
9475
+ + '<td class="mono">' + fmtTok(m.cacheCreationTokens) + '</td>'
9476
+ + '<td class="mono">' + usd(m.estimatedUsd) + '</td>'
9477
+ + '</tr>';
9478
+ }
9479
+ html += '</tbody></table>'
9480
+ + '<div class="usage-note" style="margin-top:8px">Estimated at static Anthropic API list prices'
9481
+ + ' (cache writes at the 5-minute TTL rate). Claude Max usage is covered by your subscription'
9482
+ + ' (equivalent API cost, not a charge).'
9483
+ + (ce.unpricedRequestCount > 0
9484
+ ? ' ' + ce.unpricedRequestCount + ' request' + (ce.unpricedRequestCount === 1 ? '' : 's') + ' from unrecognized models excluded.'
9485
+ : '')
9486
+ + ' Rates are editable in <a href="/settings" style="color:var(--accent)">Settings</a>.'
9487
+ + '</div></div>';
9488
+ }
9489
+
8920
9490
  // Model breakdown
8921
9491
  const models = Object.entries(s.byModel);
8922
9492
  if (models.length > 0) {
8923
9493
  html += '<div class="cards">';
8924
9494
  for (const [name, data] of models) {
8925
- html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));
9495
+ html += card(esc(name), data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));
8926
9496
  }
8927
9497
  html += '</div>';
8928
9498
  }
@@ -8974,6 +9544,7 @@ function render(s, reqs, logs, quota) {
8974
9544
  const respW = Math.max((r.upstreamDurationMs - (r.ttfbMs || 0)) * scale, 2);
8975
9545
 
8976
9546
  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>' : '';
9547
+ 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>' : '';
8977
9548
  const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '—';
8978
9549
  const msgCount = r.messageCount != null ? r.messageCount : '?';
8979
9550
 
@@ -8984,7 +9555,7 @@ function render(s, reqs, logs, quota) {
8984
9555
  + '<td>' + (r.adapter || '—') + sourceBadge + '</td>'
8985
9556
  + '<td>' + (r.requestModel || r.model) + '<br><span style="font-size:10px;color:var(--muted)">' + r.model + '</span></td>'
8986
9557
  + '<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>'
8987
- + '<td class="mono">' + sessionShort + ' ' + lineageBadge + '<br><span style="font-size:10px;color:var(--muted)">' + msgCount + ' msgs</span></td>'
9558
+ + '<td class="mono">' + sessionShort + ' ' + lineageBadge + envBadge + '<br><span style="font-size:10px;color:var(--muted)">' + msgCount + ' msgs</span></td>'
8988
9559
  + '<td class="' + statusClass + '">' + statusText + '</td>'
8989
9560
  + '<td class="mono">' + ms(r.queueWaitMs) + '</td>'
8990
9561
  + '<td class="mono">' + ms(r.proxyOverheadMs) + '</td>'
@@ -9161,8 +9732,8 @@ timer = setInterval(refresh, 5000);
9161
9732
  </html>`;
9162
9733
 
9163
9734
  // src/telemetry/routes.ts
9164
- var _iconPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
9165
- var _iconSvg = existsSync2(_iconPath) ? readFileSync(_iconPath, "utf-8") : null;
9735
+ var _iconPath = resolve(dirname2(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
9736
+ var _iconSvg = existsSync3(_iconPath) ? readFileSync2(_iconPath, "utf-8") : null;
9166
9737
  function createTelemetryRoutes() {
9167
9738
  const routes = new Hono2;
9168
9739
  routes.get("/", (c) => {
@@ -9217,108 +9788,202 @@ var landingHtml = `<!DOCTYPE html>
9217
9788
  ${themeCss}
9218
9789
  * { box-sizing: border-box; margin: 0; padding: 0; }
9219
9790
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
9220
- background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }
9221
- .container { max-width: 960px; margin: 0 auto; padding: 32px 24px; }
9222
-
9223
- .header { display: flex; align-items: center; gap: 16px; margin-bottom: 6px; }
9224
- .header h1 { font-size: 28px; font-weight: 700; letter-spacing: 3px; }
9225
- .tagline { color: var(--muted); font-size: 14px; margin-bottom: 32px; letter-spacing: 0.5px; }
9226
-
9227
- .status-banner { display: flex; align-items: center; gap: 12px; padding: 16px 20px;
9228
- background: var(--surface); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 24px; }
9229
- .status-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
9230
- .status-dot.healthy { background: var(--green); box-shadow: 0 0 8px rgba(63,185,80,0.4); }
9231
- .status-dot.degraded { background: var(--yellow); }
9232
- .status-dot.unhealthy { background: var(--red); }
9233
- .status-text { font-size: 14px; font-weight: 500; }
9234
- .status-detail { font-size: 12px; color: var(--muted); margin-left: auto; }
9235
-
9236
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
9237
- .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
9238
- .card-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; font-weight: 500; }
9239
- .card-value { font-size: 32px; font-weight: 700; margin-top: 4px; font-variant-numeric: tabular-nums; }
9240
- .card-value.green { color: var(--green); }
9241
- .card-value.violet { color: var(--violet); }
9242
- .card-detail { font-size: 12px; color: var(--muted); margin-top: 4px; }
9791
+ color: var(--text); line-height: 1.6; min-height: 100vh; }
9792
+ .container { max-width: 960px; margin: 0 auto; padding: 28px 24px; }
9793
+
9794
+ /* Intro friendly one-paragraph overview of how Meridian works */
9795
+ .intro { margin-bottom: 28px; }
9796
+ .intro h2 { font-size: 20px; font-weight: 700; margin-bottom: 6px; }
9797
+ .intro p { font-size: 13px; color: var(--muted); max-width: 640px; }
9798
+ .intro code { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px;
9799
+ background: var(--surface); border: 1px solid var(--border); border-radius: 5px;
9800
+ padding: 1px 6px; color: var(--accent2); white-space: nowrap; }
9801
+ .intro a { color: var(--accent); text-decoration: none; }
9802
+ .intro a:hover { text-decoration: underline; }
9803
+ .intro-meta { font-size: 12px; color: var(--muted); margin-top: 8px; }
9804
+
9805
+ /* Profile cards the centerpiece: usage + cost per account, click to switch */
9806
+ .profile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; margin-bottom: 24px; }
9807
+ .profile-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
9808
+ padding: 18px 20px; position: relative; transition: border-color 0.15s; }
9809
+ .profile-card.switchable { cursor: pointer; }
9810
+ .profile-card.switchable:hover { border-color: var(--accent); }
9811
+ .profile-card.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
9812
+ .profile-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 4px; }
9813
+ .profile-name { font-size: 13px; font-weight: 600; letter-spacing: 0.5px; display: flex; align-items: center; gap: 8px; }
9814
+ .profile-name .prof-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border); }
9815
+ .profile-card.active .prof-dot { background: var(--accent); box-shadow: 0 0 6px rgba(88,166,255,0.5); }
9816
+ .active-pill { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
9817
+ color: var(--accent); background: rgba(88,166,255,0.12); border: 1px solid rgba(88,166,255,0.35);
9818
+ border-radius: 10px; padding: 1px 8px; }
9819
+ .switch-hint { font-size: 9px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;
9820
+ color: var(--muted); opacity: 0; transition: opacity 0.15s; }
9821
+ .profile-card.switchable:hover .switch-hint { opacity: 1; }
9822
+ .profile-cost { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
9823
+ .profile-sub { font-size: 11px; color: var(--muted); text-align: right; margin-bottom: 12px; }
9824
+ .usage-row { display: flex; align-items: center; gap: 10px; font-size: 12px; padding: 4px 0; }
9825
+ .usage-row .w-label { color: var(--muted); width: 64px; flex-shrink: 0; }
9826
+ .usage-row .w-bar { flex: 1; height: 6px; background: var(--surface2); border-radius: 3px; overflow: hidden; }
9827
+ .usage-row .w-fill { height: 100%; border-radius: 3px; }
9828
+ .usage-row .w-pct { width: 38px; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
9829
+ .usage-row .w-reset { color: var(--muted); font-size: 11px; width: 76px; text-align: right; }
9830
+ .no-usage { font-size: 12px; color: var(--muted); padding: 4px 0; }
9831
+
9832
+ /* Traffic strip — one compact surface */
9833
+ .strip { display: flex; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--border);
9834
+ border-radius: 12px; padding: 14px 4px; margin-bottom: 24px; }
9835
+ .strip-item { flex: 1; min-width: 120px; padding: 2px 18px; border-right: 1px solid var(--border); }
9836
+ .strip-item:last-child { border-right: none; }
9837
+ .strip-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; }
9838
+ .strip-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
9839
+ .strip-value.green { color: var(--green); }
9840
+ .strip-value.red { color: var(--red); }
9841
+ .strip-detail { font-size: 11px; color: var(--muted); }
9243
9842
 
9244
9843
  .section { margin-bottom: 24px; }
9245
9844
  .section-title { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase;
9246
9845
  letter-spacing: 1px; margin-bottom: 12px; }
9247
- .info-grid { display: grid; grid-template-columns: 120px 1fr; gap: 8px 16px; font-size: 13px;
9248
- background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 20px; }
9249
- .info-label { color: var(--muted); }
9250
- .info-value { color: var(--text); font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }
9251
-
9252
- .snippet { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
9253
- padding: 16px 20px; margin-top: 12px; }
9254
- .snippet code { display: block; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
9255
- font-size: 12px; color: var(--lavender); line-height: 1.8; white-space: pre-wrap; word-break: break-all; }
9256
- .snippet-tabs { display: flex; gap: 0; margin-bottom: 12px; }
9257
- .snippet-tab { padding: 6px 14px; font-size: 11px; font-weight: 500; cursor: pointer;
9258
- color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-bottom: none; }
9259
- .snippet-tab:first-child { border-radius: 8px 0 0 0; }
9260
- .snippet-tab:last-child { border-radius: 0 8px 0 0; }
9261
- .snippet-tab.active { color: var(--violet); background: var(--surface2); border-color: var(--accent); }
9262
-
9263
- .links { display: flex; gap: 12px; margin-top: 32px; flex-wrap: wrap; }
9264
- .link { padding: 10px 20px; background: var(--surface2); border: 1px solid var(--border);
9265
- border-radius: 8px; color: var(--violet); text-decoration: none; font-size: 13px; font-weight: 500;
9266
- transition: border-color 0.2s; }
9267
- .link:hover { border-color: var(--accent); }
9268
9846
 
9269
9847
  .footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border);
9270
9848
  font-size: 11px; color: var(--muted); text-align: center; }
9271
- .footer a { color: var(--violet); text-decoration: none; }
9849
+ .footer a { color: var(--accent); text-decoration: none; }
9272
9850
  ` + profileBarCss + `
9273
9851
  </style>
9274
9852
  </head>
9275
9853
  <body>
9276
9854
  ` + profileBarHtml + `
9277
9855
  <div class="container">
9278
- <div class="header">
9279
- <svg width="40" height="40" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
9280
- <rect width="64" height="64" rx="14" fill="#1C1830"/>
9281
- <line x1="32" y1="10" x2="32" y2="54" stroke="#8B7CF6" stroke-width="2.5" stroke-linecap="round"/>
9282
- <path d="M16 20 A18 18 0 0 1 48 20" fill="none" stroke="#C4B5FD" stroke-width="1.2" opacity="0.4"/>
9283
- <path d="M16 44 A18 18 0 0 0 48 44" fill="none" stroke="#C4B5FD" stroke-width="1.2" opacity="0.4"/>
9284
- <path d="M20 30 A14 14 0 0 1 44 30" fill="none" stroke="#C4B5FD" stroke-width="0.8" opacity="0.2"/>
9285
- <path d="M20 34 A14 14 0 0 0 44 34" fill="none" stroke="#C4B5FD" stroke-width="0.8" opacity="0.2"/>
9286
- <circle cx="32" cy="10" r="3.5" fill="#C4B5FD"/><circle cx="32" cy="54" r="3.5" fill="#C4B5FD"/>
9287
- <circle cx="32" cy="32" r="3" fill="#8B7CF6"/>
9288
- </svg>
9289
- <h1>MERIDIAN</h1>
9290
- </div>
9291
- <div class="tagline">Harness Claude, your way.</div>
9292
9856
  <div id="content"><div style="color:var(--muted);padding:40px;text-align:center">Loading…</div></div>
9293
9857
  </div>
9294
9858
  <script>
9295
9859
  function ms(v){if(v==null||v===0)return '—';return v<1000?v+'ms':(v/1000).toFixed(1)+'s'}
9296
- function card(l,v,d,c){return '<div class="card"><div class="card-label">'+l+'</div><div class="card-value '+(c||'')+'">'+v+'</div>'+(d?'<div class="card-detail">'+d+'</div>':'')+'</div>'}
9860
+ function esc(s){return String(s).replace(/[&<>"']/g,function(ch){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[ch]})}
9861
+ function usd(v){if(v==null)return '—';if(v>0&&v<0.01)return '$'+v.toFixed(4);if(v<100)return '$'+v.toFixed(2);return '$'+Math.round(v).toLocaleString()}
9862
+
9863
+ var WIN_LABELS={five_hour:'5h',seven_day:'7d',seven_day_opus:'7d Opus',seven_day_sonnet:'7d Sonnet',seven_day_fable:'7d Fable',seven_day_oauth_apps:'7d Apps',seven_day_cowork:'7d Cowork',seven_day_omelette:'7d Omelette'};
9864
+ function winLabel(t){if(WIN_LABELS[t])return WIN_LABELS[t];return t.replace(/^seven_day_/,'7d ').replace(/_/g,' ').replace(/\bw/g,function(c){return c.toUpperCase()})}
9865
+ function utilColor(u){return u>=0.85?'var(--red)':u>=0.6?'var(--yellow)':'var(--green)'}
9866
+ function resetIn(ts){if(ts==null)return '';var d=ts-Date.now();if(d<=0)return 'resetting…';var m=Math.ceil(d/60000);if(m<60)return 'in '+m+'m';var h=Math.floor(m/60);if(h<24)return 'in '+h+'h'+(m%60?' '+(m%60)+'m':'');var days=Math.floor(h/24);return 'in '+days+'d'+(h%24?' '+(h%24)+'h':'')}
9867
+
9868
+ function introSection(h){
9869
+ var meta=[];
9870
+ if(h.auth&&h.auth.loggedIn)meta.push(esc(h.auth.email||'')+(h.auth.subscriptionType?' ('+esc(h.auth.subscriptionType)+')':''));
9871
+ meta.push(h.mode||'internal');
9872
+ meta.push('port '+location.port);
9873
+ return '<div class="intro">'
9874
+ +'<h2>Harness Claude, your way.</h2>'
9875
+ +'<p>Meridian bridges any Anthropic-API agent to your Claude subscription — point the agent’s <code>ANTHROPIC_BASE_URL</code> at <code>http://'+esc(location.host)+'</code> and every request routes through the active account below. Setup guides for each agent live in the <a href="https://github.com/rynfar/meridian#readme">README</a>.</p>'
9876
+ +'<div class="intro-meta">'+meta.join(' · ')+'</div>'
9877
+ +'</div>';
9878
+ }
9879
+
9880
+ function profileSection(q,s,pl,h){
9881
+ var byProfile=(s&&s.costEstimate&&s.costEstimate.byProfile)||{};
9882
+ var quotaByProfile={};
9883
+ if(q&&Array.isArray(q.profiles))for(var i=0;i<q.profiles.length;i++){var qid=q.profiles[i].id||q.profiles[i].profile||'default';quotaByProfile[qid]=q.profiles[i].windows||[]}
9884
+ var profs=[];var seen={};
9885
+ var configured=(pl&&Array.isArray(pl.profiles))?pl.profiles:[];
9886
+ var multi=configured.length>1;
9887
+ if(configured.length>0){
9888
+ // Real profiles exist: show exactly those. Traffic that predates
9889
+ // per-profile attribution (the synthetic "default" bucket) still
9890
+ // counts in the totals strip but doesn't render as a fake account.
9891
+ for(var i=0;i<configured.length;i++){var p=configured[i];profs.push({id:p.id,label:p.id,type:p.type,isActive:!!p.isActive,configured:true});seen[p.id]=1}
9892
+ }else{
9893
+ // Single-account setup: one card, labeled with the logged-in email.
9894
+ var email=(h&&h.auth&&h.auth.loggedIn&&h.auth.email)||'';
9895
+ for(var k in quotaByProfile){profs.push({id:k,label:k==='default'?(email||'account'):k,configured:false});seen[k]=1}
9896
+ for(var k in byProfile){if(!seen[k])profs.push({id:k,label:k==='default'?(email||'account'):k,configured:false});seen[k]=1}
9897
+ }
9898
+ if(profs.length===0)return '';
9899
+ var cards='';
9900
+ for(var i=0;i<profs.length;i++){
9901
+ var p=profs[i];var cost=byProfile[p.id];
9902
+ var wins=(quotaByProfile[p.id]||[]).filter(function(w){return w.utilization!=null});
9903
+ if(!p.configured&&wins.length===0&&!cost)continue;
9904
+ var rows='';
9905
+ for(var j=0;j<wins.length;j++){
9906
+ var w=wins[j];var pct=Math.round(w.utilization*100);
9907
+ rows+='<div class="usage-row"><span class="w-label">'+esc(winLabel(w.type))+'</span>'
9908
+ +'<div class="w-bar"><div class="w-fill" style="width:'+Math.min(pct,100)+'%;background:'+utilColor(w.utilization)+'"></div></div>'
9909
+ +'<span class="w-pct" style="color:'+utilColor(w.utilization)+'">'+pct+'%</span>'
9910
+ +'<span class="w-reset">'+resetIn(w.resetsAt)+'</span></div>';
9911
+ }
9912
+ if(!rows)rows='<div class="no-usage">no usage data yet</div>';
9913
+ var switchable=multi&&p.configured&&!p.isActive;
9914
+ var badge=p.isActive?'<span class="active-pill">Active</span>':switchable?'<span class="switch-hint">Click to activate</span>':'';
9915
+ cards+='<div class="profile-card'+(p.isActive?' active':'')+(switchable?' switchable':'')+'"'+(switchable?' data-profile="'+esc(p.id)+'" role="button" tabindex="0"':'')+'>'
9916
+ +'<div class="profile-head"><span class="profile-name"><span class="prof-dot"></span>'+esc(p.label||p.id)+' '+badge+'</span>'
9917
+ +'<span class="profile-cost">'+usd(cost?cost.estimatedUsd:0)+'</span></div>'
9918
+ +'<div class="profile-sub">'+(cost?cost.requests+' request'+(cost.requests===1?'':'s')+' · est. API value · 24h':'no traffic · 24h')+'</div>'
9919
+ +rows+'</div>';
9920
+ }
9921
+ if(!cards)return '';
9922
+ return '<div class="section"><div class="section-title">'+(profs.length===1?'Account':'Accounts')+'</div><div class="profile-grid">'+cards+'</div></div>';
9923
+ }
9924
+
9925
+ function strip(items){
9926
+ var o='<div class="strip">';
9927
+ for(var i=0;i<items.length;i++){var it=items[i];
9928
+ o+='<div class="strip-item"><div class="strip-label">'+it[0]+'</div><div class="strip-value '+(it[2]||'')+'">'+it[1]+'</div>'+(it[3]?'<div class="strip-detail">'+it[3]+'</div>':'')+'</div>';
9929
+ }
9930
+ return o+'</div>';
9931
+ }
9297
9932
 
9298
9933
  async function refresh(){
9299
9934
  try{
9300
- const [health,stats]=await Promise.all([fetch('/health').then(r=>r.json()),fetch('/telemetry/summary?window=86400000').then(r=>r.json())]);
9301
- render(health,stats);
9935
+ const [health,stats,quota,profiles]=await Promise.all([
9936
+ fetch('/health').then(r=>r.json()),
9937
+ fetch('/telemetry/summary?window=86400000').then(r=>r.json()),
9938
+ fetch('/v1/usage/quota/all').then(r=>r.json()).catch(function(){return null}),
9939
+ fetch('/profiles/list').then(r=>r.json()).catch(function(){return null})
9940
+ ]);
9941
+ render(health,stats,quota,profiles);
9302
9942
  }catch(e){document.getElementById('content').innerHTML='<div style="color:var(--red);padding:40px;text-align:center">Could not connect</div>'}
9303
9943
  }
9304
9944
 
9305
- function render(h,s){
9306
- const st=h.status||'unknown',dot=st==='healthy'?'healthy':st==='degraded'?'degraded':'unhealthy';
9945
+ function tokens(v){if(v==null)return '—';if(v>=1e6)return (v/1e6).toFixed(1)+'M';if(v>=1e3)return (v/1e3).toFixed(1)+'k';return String(v)}
9946
+
9947
+ function render(h,s,q,pl){
9307
9948
  let o='';
9308
- o+='<div class="status-banner"><div class="status-dot '+dot+'"></div><span class="status-text">'+(st==='healthy'?'Operational':st==='degraded'?'Degraded':'Offline')+'</span><span class="status-detail">Port '+location.port+' · '+(h.mode||'internal')+' mode</span></div>';
9309
- const er=s.totalRequests>0?((s.errorCount/s.totalRequests)*100).toFixed(1):'0';
9310
- o+='<div class="grid">'+card('Requests (24h)',s.totalRequests,'','violet')+card('Median Response',ms(s.totalDuration?.p50),'p95: '+ms(s.totalDuration?.p95),'')+card('Median TTFB',ms(s.ttfb?.p50),'p95: '+ms(s.ttfb?.p95),'')+card('Error Rate',er+'%',s.errorCount+' errors',parseFloat(er)>5?'':'green')+'</div>';
9311
- o+='<div class="section"><div class="section-title">Account</div>';
9312
- if(h.auth?.loggedIn){o+='<div class="info-grid"><span class="info-label">Email</span><span class="info-value">'+(h.auth.email||'—')+'</span><span class="info-label">Subscription</span><span class="info-value">'+(h.auth.subscriptionType||'—')+'</span><span class="info-label">Mode</span><span class="info-value">'+(h.mode||'internal')+'</span><span class="info-label">Endpoint</span><span class="info-value">http://'+location.host+'</span></div>'}
9313
- else{o+='<div class="info-grid"><span class="info-label">Status</span><span class="info-value" style="color:var(--yellow)">'+(h.error||'Not authenticated')+'</span></div>'}
9314
- o+='</div>';
9315
- if(s.byModel&&Object.keys(s.byModel).length>0){o+='<div class="section"><div class="section-title">Models (24h)</div><div class="grid">';for(const[n,d]of Object.entries(s.byModel))o+=card(n,d.count,'avg '+ms(d.avgTotalMs),'');o+='</div></div>'}
9316
- o+='<div class="section"><div class="section-title">Connect an Agent</div><div class="snippet"><div class="snippet-tabs"><div class="snippet-tab active" onclick="showTab(this,&apos;opencode&apos;)">OpenCode</div><div class="snippet-tab" onclick="showTab(this,&apos;crush&apos;)">Crush</div><div class="snippet-tab" onclick="showTab(this,&apos;generic&apos;)">Any Tool</div></div><div id="tab-opencode"><code>ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://'+location.host+' opencode</code></div><div id="tab-crush" style="display:none"><code>'+JSON.stringify({providers:{meridian:{type:"anthropic",base_url:"http://"+location.host,api_key:"x",models:[{id:"claude-sonnet-4-5-20250514",name:"Sonnet 4.5"}]}}},null,2)+'</code></div><div id="tab-generic" style="display:none"><code>export ANTHROPIC_API_KEY=x\\nexport ANTHROPIC_BASE_URL=http://'+location.host+'</code></div></div></div>';
9317
- o+='<div class="links"><a href="/telemetry" class="link">\uD83D\uDCCA Telemetry</a><a href="/settings" class="link">\uD83D\uDD27 Settings</a><a href="/profiles" class="link">\uD83D\uDC64 Profiles</a><a href="/health" class="link">\uD83E\uDE7A Health</a><a href="/telemetry/summary" class="link">\uD83D\uDCC8 Stats API</a><a href="https://github.com/rynfar/meridian" class="link">⚙️ GitHub</a></div>';
9318
- o+='<div class="footer">Meridian · Built on the <a href="https://github.com/anthropics/claude-code-sdk-js">Claude Code SDK</a></div>';
9949
+ o+=introSection(h);
9950
+
9951
+ // Accounts per-profile usage + est cost; click a card to switch
9952
+ o+=profileSection(q,s,pl,h);
9953
+
9954
+ // Last 24 hours — meaningful signals only. Errors and envelope
9955
+ // violations appear only when there is something to report.
9956
+ var tu=s.tokenUsage||{};
9957
+ var cache=tu.avgCacheHitRate!=null?Math.round(tu.avgCacheHitRate*100)+'%':'';
9958
+ var items=[
9959
+ ['Requests',String(s.totalRequests),s.errorCount>0?'red':'',s.errorCount>0?s.errorCount+' error'+(s.errorCount===1?'':'s'):'no errors'],
9960
+ ['Tokens Out',tokens(tu.totalOutputTokens),'',tokens(tu.totalInputTokens)+' in'],
9961
+ ['Cache Hit',cache,tu.avgCacheHitRate>=0.5?'green':'','prompt cache'],
9962
+ ['Est. API Value',usd(s.costEstimate?.totalUsd),'','list prices'],
9963
+ ['Median Response',ms(s.totalDuration?.p50),'','p95 '+ms(s.totalDuration?.p95)]
9964
+ ];
9965
+ if(s.envelopeViolationCount>0)items.push(['Envelope',String(s.envelopeViolationCount),'red','wire-contract violations']);
9966
+ o+='<div class="section"><div class="section-title">Last 24 Hours</div>'+strip(items)+'</div>';
9967
+
9968
+ o+='<div class="footer">Meridian · <a href="https://github.com/rynfar/meridian">GitHub</a> · Built on the <a href="https://github.com/anthropics/claude-agent-sdk-typescript">Claude Agent SDK</a></div>';
9319
9969
  document.getElementById('content').innerHTML=o;
9320
9970
  }
9321
- function showTab(el,id){document.querySelectorAll('.snippet-tab').forEach(t=>t.classList.remove('active'));el.classList.add('active');document.querySelectorAll('[id^="tab-"]').forEach(t=>t.style.display='none');document.getElementById('tab-'+id).style.display='block'}
9971
+
9972
+ function switchProfile(id){
9973
+ fetch('/profiles/active',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({profile:id})})
9974
+ .then(function(r){return r.json()})
9975
+ .then(function(data){if(data.success){refresh();if(window.meridianHeaderRefresh)window.meridianHeaderRefresh()}})
9976
+ .catch(function(){});
9977
+ }
9978
+ document.getElementById('content').addEventListener('click',function(e){
9979
+ var card=e.target.closest('.profile-card.switchable');
9980
+ if(card&&card.dataset.profile)switchProfile(card.dataset.profile);
9981
+ });
9982
+ document.getElementById('content').addEventListener('keydown',function(e){
9983
+ if(e.key!=='Enter'&&e.key!==' ')return;
9984
+ var card=e.target.closest('.profile-card.switchable');
9985
+ if(card&&card.dataset.profile){e.preventDefault();switchProfile(card.dataset.profile)}
9986
+ });
9322
9987
  refresh();setInterval(refresh,10000);
9323
9988
  ` + profileBarJs + `
9324
9989
  </script>
@@ -9385,7 +10050,7 @@ function renderPrometheusMetrics(store) {
9385
10050
  // src/telemetry/index.ts
9386
10051
  init_sqlite();
9387
10052
  function getDefaultDbPath() {
9388
- return join(homedir(), ".config", "meridian", "telemetry.db");
10053
+ return join2(homedir2(), ".config", "meridian", "telemetry.db");
9389
10054
  }
9390
10055
  function createStores() {
9391
10056
  if (!envBool("TELEMETRY_PERSIST")) {
@@ -10426,7 +11091,11 @@ ${text.slice(0, 2000)}` : text.slice(0, 2000);
10426
11091
  return createHash("sha256").update(seed).digest("hex").slice(0, 16);
10427
11092
  }
10428
11093
 
11094
+ // src/proxy/adapters/opencode.ts
11095
+ init_env();
11096
+
10429
11097
  // src/proxy/transforms/opencode.ts
11098
+ init_env();
10430
11099
  var openCodeTransforms = [
10431
11100
  {
10432
11101
  name: "opencode-core",
@@ -10592,7 +11261,11 @@ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be on
10592
11261
  }
10593
11262
  };
10594
11263
 
11264
+ // src/proxy/adapters/droid.ts
11265
+ init_env();
11266
+
10595
11267
  // src/proxy/transforms/droid.ts
11268
+ init_env();
10596
11269
  var DROID_MCP_SERVER_NAME = "droid";
10597
11270
  var DROID_ALLOWED_MCP_TOOLS = [
10598
11271
  `mcp__${DROID_MCP_SERVER_NAME}__read`,
@@ -10898,7 +11571,11 @@ var passthroughAdapter = {
10898
11571
  }
10899
11572
  };
10900
11573
 
11574
+ // src/proxy/adapters/pi.ts
11575
+ init_env();
11576
+
10901
11577
  // src/proxy/transforms/pi.ts
11578
+ init_env();
10902
11579
  var PI_MCP_SERVER_NAME = "pi";
10903
11580
  var PI_ALLOWED_MCP_TOOLS = [
10904
11581
  `mcp__${PI_MCP_SERVER_NAME}__read`,
@@ -11131,6 +11808,7 @@ var forgeCodeAdapter = {
11131
11808
  };
11132
11809
 
11133
11810
  // src/proxy/adapters/claudecode.ts
11811
+ init_env();
11134
11812
  function extractClaudeCodeClientCwd(body) {
11135
11813
  let systemText = "";
11136
11814
  if (typeof body.system === "string") {
@@ -11254,10 +11932,10 @@ var cherryAdapter = {
11254
11932
  };
11255
11933
 
11256
11934
  // src/proxy/adapterInstances.ts
11257
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
11258
- import { join as join2 } from "node:path";
11259
- import { homedir as homedir2 } from "node:os";
11260
- var CONFIG_FILE = join2(homedir2(), ".config", "meridian", "adapter-instances.json");
11935
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
11936
+ import { join as join3 } from "node:path";
11937
+ import { homedir as homedir3 } from "node:os";
11938
+ var CONFIG_FILE = join3(homedir3(), ".config", "meridian", "adapter-instances.json");
11261
11939
  function parseAdapterInstances(raw2) {
11262
11940
  if (!raw2)
11263
11941
  return {};
@@ -11304,7 +11982,7 @@ function loadAdapterInstances() {
11304
11982
  if (diskCacheAt > 0 && Date.now() - diskCacheAt < DISK_CACHE_TTL_MS)
11305
11983
  return diskCache;
11306
11984
  try {
11307
- diskCache = existsSync3(CONFIG_FILE) ? parseAdapterInstances(readFileSync2(CONFIG_FILE, "utf-8")) : {};
11985
+ diskCache = existsSync4(CONFIG_FILE) ? parseAdapterInstances(readFileSync3(CONFIG_FILE, "utf-8")) : {};
11308
11986
  } catch (err) {
11309
11987
  console.warn(`[meridian] Failed to read ${CONFIG_FILE}: ${err instanceof Error ? err.message : err}`);
11310
11988
  diskCache = {};
@@ -17112,6 +17790,7 @@ function createOpencodeMcpServer() {
17112
17790
  }
17113
17791
 
17114
17792
  // src/proxy/query.ts
17793
+ init_env();
17115
17794
  function stripConfigDir(env2) {
17116
17795
  if (!("CLAUDE_CONFIG_DIR" in env2))
17117
17796
  return env2;
@@ -17236,6 +17915,7 @@ function buildQueryOptions(ctx, abortController) {
17236
17915
  ...sharedMemory ? stripConfigDir(cleanEnv) : cleanEnv,
17237
17916
  ENABLE_TOOL_SEARCH: hasDeferredTools ? "true" : "false",
17238
17917
  ...passthrough ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {},
17918
+ ...passthrough && process.env.MERIDIAN_SUPPRESS_SCRATCHPAD !== "0" ? { CLAUDE_CODE_SESSION_KIND: "bg" } : {},
17239
17919
  ...process.getuid?.() === 0 ? { IS_SANDBOX: "1" } : {},
17240
17920
  ...ctx.envOverrides
17241
17921
  },
@@ -17334,8 +18014,8 @@ function getAdapterTransforms(adapterName) {
17334
18014
  }
17335
18015
 
17336
18016
  // src/proxy/plugins/loader.ts
17337
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
17338
- import { join as join3, isAbsolute as isAbsolute2, extname } from "path";
18017
+ import { readdirSync as readdirSync2, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
18018
+ import { join as join4, isAbsolute as isAbsolute2, extname } from "path";
17339
18019
  import { pathToFileURL } from "url";
17340
18020
 
17341
18021
  // src/proxy/plugins/validation.ts
@@ -17376,10 +18056,10 @@ function validateTransform(exported) {
17376
18056
  // src/proxy/plugins/loader.ts
17377
18057
  var loadCounter = 0;
17378
18058
  function parsePluginConfig(configPath) {
17379
- if (!existsSync4(configPath))
18059
+ if (!existsSync5(configPath))
17380
18060
  return [];
17381
18061
  try {
17382
- const raw2 = readFileSync3(configPath, "utf-8");
18062
+ const raw2 = readFileSync4(configPath, "utf-8");
17383
18063
  const parsed = JSON.parse(raw2);
17384
18064
  return Array.isArray(parsed.plugins) ? parsed.plugins : [];
17385
18065
  } catch {
@@ -17389,7 +18069,7 @@ function parsePluginConfig(configPath) {
17389
18069
  async function loadPlugins(pluginDir, configPath) {
17390
18070
  resetAllPluginStats();
17391
18071
  const config = configPath ? parsePluginConfig(configPath) : [];
17392
- const pluginDirExists = existsSync4(pluginDir);
18072
+ const pluginDirExists = existsSync5(pluginDir);
17393
18073
  let filenames = [];
17394
18074
  if (pluginDirExists) {
17395
18075
  try {
@@ -17420,7 +18100,7 @@ async function loadPlugins(pluginDir, configPath) {
17420
18100
  const loaded = [];
17421
18101
  const seenNames = new Set;
17422
18102
  for (const { filename, entry } of ordered) {
17423
- const filePath = isAbsolute2(filename) ? filename : join3(pluginDir, filename);
18103
+ const filePath = isAbsolute2(filename) ? filename : join4(pluginDir, filename);
17424
18104
  if (entry && !entry.enabled) {
17425
18105
  loaded.push({
17426
18106
  name: filename,
@@ -17793,17 +18473,17 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
17793
18473
  // src/proxy/sessionStore.ts
17794
18474
  import {
17795
18475
  closeSync,
17796
- existsSync as existsSync5,
17797
- mkdirSync,
18476
+ existsSync as existsSync6,
18477
+ mkdirSync as mkdirSync2,
17798
18478
  openSync,
17799
- readFileSync as readFileSync4,
17800
- renameSync,
18479
+ readFileSync as readFileSync5,
18480
+ renameSync as renameSync2,
17801
18481
  statSync,
17802
18482
  unlinkSync,
17803
- writeFileSync
18483
+ writeFileSync as writeFileSync2
17804
18484
  } from "node:fs";
17805
- import { homedir as homedir3 } from "node:os";
17806
- import { join as join4 } from "node:path";
18485
+ import { homedir as homedir4 } from "node:os";
18486
+ import { join as join5 } from "node:path";
17807
18487
  var DEFAULT_MAX_STORED_SESSIONS = 1e4;
17808
18488
  var STALE_LOCK_THRESHOLD_MS = 30000;
17809
18489
  function getMaxStoredSessions() {
@@ -17851,17 +18531,17 @@ var sessionDirOverride = null;
17851
18531
  var skipLocking = false;
17852
18532
  function getStorePath() {
17853
18533
  const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
17854
- if (!existsSync5(dir)) {
17855
- mkdirSync(dir, { recursive: true });
18534
+ if (!existsSync6(dir)) {
18535
+ mkdirSync2(dir, { recursive: true });
17856
18536
  }
17857
- return join4(dir, "sessions.json");
18537
+ return join5(dir, "sessions.json");
17858
18538
  }
17859
18539
  function getDefaultCacheDir() {
17860
- const newDir = join4(homedir3(), ".cache", "meridian");
17861
- const oldDir = join4(homedir3(), ".cache", "opencode-claude-max-proxy");
17862
- if (existsSync5(newDir))
18540
+ const newDir = join5(homedir4(), ".cache", "meridian");
18541
+ const oldDir = join5(homedir4(), ".cache", "opencode-claude-max-proxy");
18542
+ if (existsSync6(newDir))
17863
18543
  return newDir;
17864
- if (existsSync5(oldDir)) {
18544
+ if (existsSync6(oldDir)) {
17865
18545
  try {
17866
18546
  const { symlinkSync } = __require("fs");
17867
18547
  symlinkSync(oldDir, newDir);
@@ -17874,10 +18554,10 @@ function getDefaultCacheDir() {
17874
18554
  }
17875
18555
  function readStore() {
17876
18556
  const path3 = getStorePath();
17877
- if (!existsSync5(path3))
18557
+ if (!existsSync6(path3))
17878
18558
  return {};
17879
18559
  try {
17880
- const data = readFileSync4(path3, "utf-8");
18560
+ const data = readFileSync5(path3, "utf-8");
17881
18561
  return JSON.parse(data);
17882
18562
  } catch (e) {
17883
18563
  console.error("[sessionStore] read failed:", e.message);
@@ -17888,12 +18568,12 @@ function writeStore(store) {
17888
18568
  const path3 = getStorePath();
17889
18569
  const tmp = `${path3}.tmp`;
17890
18570
  try {
17891
- writeFileSync(tmp, JSON.stringify(store, null, 2));
17892
- renameSync(tmp, path3);
18571
+ writeFileSync2(tmp, JSON.stringify(store, null, 2));
18572
+ renameSync2(tmp, path3);
17893
18573
  } catch (e) {
17894
18574
  console.error("[sessionStore] write failed:", e.message);
17895
18575
  try {
17896
- writeFileSync(path3, JSON.stringify(store, null, 2));
18576
+ writeFileSync2(path3, JSON.stringify(store, null, 2));
17897
18577
  } catch (directWriteError) {
17898
18578
  console.error("[sessionStore] write failed:", directWriteError.message);
17899
18579
  }
@@ -17999,7 +18679,7 @@ function listStoredSessions() {
17999
18679
  function clearSharedSessions() {
18000
18680
  const path3 = getStorePath();
18001
18681
  try {
18002
- writeFileSync(path3, "{}");
18682
+ writeFileSync2(path3, "{}");
18003
18683
  } catch (e) {
18004
18684
  console.error("[sessionStore] clear failed:", e.message);
18005
18685
  }
@@ -18408,8 +19088,28 @@ function createProxyServer(config = {}) {
18408
19088
  const sessionDiscoveredTools = new Map;
18409
19089
  const sessionToolCache = new Map;
18410
19090
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
18411
- const pluginDir = finalConfig.pluginDir ?? join6(homedir5(), ".config", "meridian", "plugins");
18412
- const pluginConfigPath = finalConfig.pluginConfigPath ?? join6(homedir5(), ".config", "meridian", "plugins.json");
19091
+ const PENDING_STORE_WAIT_MS = 3000;
19092
+ const PENDING_STORE_AUTO_RESOLVE_MS = 1e4;
19093
+ const pendingSessionStores = new Map;
19094
+ const registerPendingStore = (key) => {
19095
+ let resolveFn = () => {};
19096
+ const promise = new Promise((resolve3) => {
19097
+ const timer = setTimeout(resolve3, PENDING_STORE_AUTO_RESOLVE_MS);
19098
+ resolveFn = () => {
19099
+ clearTimeout(timer);
19100
+ resolve3();
19101
+ };
19102
+ });
19103
+ const entry = { promise, resolve: resolveFn };
19104
+ pendingSessionStores.set(key, entry);
19105
+ return () => {
19106
+ entry.resolve();
19107
+ if (pendingSessionStores.get(key) === entry)
19108
+ pendingSessionStores.delete(key);
19109
+ };
19110
+ };
19111
+ const pluginDir = finalConfig.pluginDir ?? join7(homedir6(), ".config", "meridian", "plugins");
19112
+ const pluginConfigPath = finalConfig.pluginConfigPath ?? join7(homedir6(), ".config", "meridian", "plugins.json");
18413
19113
  let loadedPlugins = [];
18414
19114
  let pluginTransforms = [];
18415
19115
  const app = new Hono2;
@@ -18594,6 +19294,17 @@ function createProxyServer(config = {}) {
18594
19294
  const lastIsToolResult = Array.isArray(lastMessage?.content) && lastMessage.content.some((b) => b?.type === "tool_result");
18595
19295
  const isClientDrivenLoop = adapterBase !== "claude-code" && !agentSessionId && lastIsToolResult;
18596
19296
  const isIndependentSession = requestSource?.startsWith("fork-") || requestSource?.startsWith("subagent-") || isClientDrivenLoop || false;
19297
+ if (!isIndependentSession && profileSessionId) {
19298
+ const pendingStore = pendingSessionStores.get(profileSessionId);
19299
+ if (pendingStore) {
19300
+ const waitStart = Date.now();
19301
+ await Promise.race([
19302
+ pendingStore.promise,
19303
+ new Promise((resolve3) => setTimeout(resolve3, PENDING_STORE_WAIT_MS))
19304
+ ]);
19305
+ claudeLog("session.pending_store_awaited", { waitedMs: Date.now() - waitStart });
19306
+ }
19307
+ }
18597
19308
  let lineageResult = isIndependentSession ? { type: "diverged" } : lookupSession(profileSessionId, body.messages || [], profileScopedCwd);
18598
19309
  if (lineageResult.type === "undo" && adapterBase === "opencode" && !agentSessionId) {
18599
19310
  lineageResult = { type: "diverged" };
@@ -18718,6 +19429,35 @@ function createProxyServer(config = {}) {
18718
19429
  const earlyStopEnabled = passthrough && process.env.MERIDIAN_PASSTHROUGH_EARLY_STOP !== "0";
18719
19430
  const earlyStop = createEarlyStopTracker();
18720
19431
  let earlyStopFired = false;
19432
+ const envelopeViolations = [];
19433
+ const recordEnvelopeViolations = (violations) => {
19434
+ for (const v of violations) {
19435
+ envelopeViolations.push(v.type);
19436
+ claudeLog("envelope.violation", { type: v.type, detail: v.detail });
19437
+ diagnosticLog2.error(`${requestMeta.requestId} ENVELOPE VIOLATION [${v.type}] ${v.detail}`, requestMeta.requestId);
19438
+ }
19439
+ };
19440
+ const DENY_HOLD_TIMEOUT_MS = 8000;
19441
+ const pendingDenyReleases = [];
19442
+ let turnGenerating = false;
19443
+ const releaseHeldDenies = (reason) => {
19444
+ turnGenerating = false;
19445
+ if (pendingDenyReleases.length === 0)
19446
+ return;
19447
+ claudeLog("passthrough.deny_hold_released", { reason, count: pendingDenyReleases.length });
19448
+ for (const release of pendingDenyReleases.splice(0))
19449
+ release();
19450
+ };
19451
+ const holdDenyUntilTurnEnd = () => new Promise((resolve3) => {
19452
+ const timer = setTimeout(() => {
19453
+ claudeLog("passthrough.deny_hold_timeout", { afterMs: DENY_HOLD_TIMEOUT_MS });
19454
+ resolve3();
19455
+ }, DENY_HOLD_TIMEOUT_MS);
19456
+ pendingDenyReleases.push(() => {
19457
+ clearTimeout(timer);
19458
+ resolve3();
19459
+ });
19460
+ });
18721
19461
  const toolChoice = body.tool_choice;
18722
19462
  const forceSingleToolUse = !!toolChoice && (toolChoice.type === "tool" || toolChoice.disable_parallel_tool_use === true);
18723
19463
  const fileChanges = [];
@@ -18803,6 +19543,9 @@ function createProxyServer(config = {}) {
18803
19543
  input: toolInput
18804
19544
  });
18805
19545
  }
19546
+ if (stream2 && earlyStopEnabled && turnGenerating && !requestAbort.controller.signal.aborted) {
19547
+ await holdDenyUntilTurnEnd();
19548
+ }
18806
19549
  if (isExactDuplicate) {
18807
19550
  return {
18808
19551
  decision: "block",
@@ -19283,6 +20026,7 @@ Subprocess stderr: ${stderrOutput}`;
19283
20026
  requestId: requestMeta.requestId,
19284
20027
  timestamp: Date.now(),
19285
20028
  adapter: adapter.name,
20029
+ profileId: profile.id,
19286
20030
  requestSource,
19287
20031
  model,
19288
20032
  requestModel: body.model || undefined,
@@ -19310,8 +20054,16 @@ Subprocess stderr: ${stderrOutput}`;
19310
20054
  outputTokens: lastUsage?.output_tokens,
19311
20055
  cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
19312
20056
  cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
19313
- cacheHitRate: computeCacheHitRate(lastUsage)
20057
+ cacheHitRate: computeCacheHitRate(lastUsage),
20058
+ ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
19314
20059
  });
20060
+ if (passthrough) {
20061
+ const deliveredIds = new Set(contentBlocks.filter((b) => b.type === "tool_use" && typeof b.id === "string").map((b) => b.id));
20062
+ recordEnvelopeViolations([
20063
+ ...checkEmptyToolInputs(contentBlocks, requestTools),
20064
+ ...checkUndeliveredToolUses(capturedToolUses, deliveredIds)
20065
+ ]);
20066
+ }
19315
20067
  if (currentSessionId && !isIndependentSession && !sawDuplicateToolUse) {
19316
20068
  storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage);
19317
20069
  }
@@ -19378,6 +20130,23 @@ Subprocess stderr: ${stderrOutput}`;
19378
20130
  let structuredOutput;
19379
20131
  const streamedToolUseIds = new Set;
19380
20132
  const openClientBlocks = new Set;
20133
+ const resolvePendingStore = passthrough && earlyStopEnabled && !isIndependentSession && profileSessionId ? registerPendingStore(profileSessionId) : () => {};
20134
+ const flushOpenClientBlocks = (source) => {
20135
+ if (openClientBlocks.size === 0)
20136
+ return;
20137
+ recordEnvelopeViolations([...openClientBlocks].map((idx) => ({
20138
+ type: "dangling_block",
20139
+ detail: `content block ${idx} still open at ${source} close`
20140
+ })));
20141
+ claudeLog("stream.dangling_blocks_closed", { source, count: openClientBlocks.size });
20142
+ for (const idx of openClientBlocks) {
20143
+ safeEnqueue(encoder.encode(`event: content_block_stop
20144
+ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
20145
+
20146
+ `), `${source}_close_dangling`);
20147
+ }
20148
+ openClientBlocks.clear();
20149
+ };
19381
20150
  try {
19382
20151
  let currentSessionId;
19383
20152
  const MAX_RATE_LIMIT_RETRIES = 2;
@@ -19658,6 +20427,7 @@ Subprocess stderr: ${stderrOutput}`;
19658
20427
  if (shouldEarlyStop(earlyStop) && streamedToolUseIds.size > 0) {
19659
20428
  earlyStopFired = true;
19660
20429
  claudeLog("passthrough.early_stop", { mode: "stream", captured: capturedToolUses.length, drained: awaitingEarlyStopDrain });
20430
+ flushOpenClientBlocks("early_stop");
19661
20431
  safeEnqueue(encoder.encode(`event: message_delta
19662
20432
  data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: "tool_use", stop_sequence: null }, usage: { output_tokens: lastUsage?.output_tokens ?? 0 } })}
19663
20433
 
@@ -19700,6 +20470,12 @@ data: ${JSON.stringify({ type: "message_stop" })}
19700
20470
  const event = message.event;
19701
20471
  const eventType = event.type;
19702
20472
  const eventIndex = event.index;
20473
+ if (eventType === "message_delta" || eventType === "message_stop" || eventType === "message_start" && messageStartEmitted) {
20474
+ releaseHeldDenies(eventType);
20475
+ }
20476
+ if (eventType === "message_start") {
20477
+ turnGenerating = true;
20478
+ }
19703
20479
  if (outputFormat) {
19704
20480
  if (eventType === "message_start") {
19705
20481
  const startUsage = event.message?.usage;
@@ -19720,6 +20496,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
19720
20496
  lastUsage = { ...lastUsage, ...startUsage };
19721
20497
  if (messageStartEmitted) {
19722
20498
  if (passthrough && streamedToolUseIds.size > 0) {
20499
+ flushOpenClientBlocks("turn2_suppression");
19723
20500
  safeEnqueue(encoder.encode(`event: message_delta
19724
20501
  data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: "tool_use", stop_sequence: null }, usage: { output_tokens: lastUsage?.output_tokens ?? 0 } })}
19725
20502
 
@@ -19846,6 +20623,7 @@ data: ${JSON.stringify(event)}
19846
20623
  openClientBlocks.delete(idx);
19847
20624
  }
19848
20625
  if (passthrough && eventType === "message_delta" && event.delta?.stop_reason === "tool_use" && streamedToolUseIds.size > 0) {
20626
+ flushOpenClientBlocks("drain_close");
19849
20627
  safeEnqueue(encoder.encode(`event: message_stop
19850
20628
  data: ${JSON.stringify({ type: "message_stop" })}
19851
20629
 
@@ -19868,6 +20646,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
19868
20646
  }
19869
20647
  } finally {
19870
20648
  clearInterval(heartbeat);
20649
+ releaseHeldDenies("stream_loop_exit");
19871
20650
  }
19872
20651
  if (outputFormat) {
19873
20652
  if (!hasStructuredOutput) {
@@ -19923,6 +20702,9 @@ data: ${JSON.stringify({
19923
20702
  eventsForwarded += 5;
19924
20703
  textEventsForwarded += 1;
19925
20704
  }
20705
+ if (passthrough) {
20706
+ recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
20707
+ }
19926
20708
  claudeLog("upstream.completed", {
19927
20709
  mode: "stream",
19928
20710
  model,
@@ -19946,12 +20728,14 @@ data: ${JSON.stringify({
19946
20728
  if (currentSessionId && !isIndependentSession && !sawDuplicateToolUse) {
19947
20729
  storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage);
19948
20730
  }
20731
+ resolvePendingStore();
19949
20732
  if (!streamClosed) {
19950
20733
  const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
19951
20734
  if (passthrough && unseenToolUses.length > 0 && messageStartEmitted) {
19952
20735
  for (let i = 0;i < unseenToolUses.length; i++) {
19953
20736
  const tu = unseenToolUses[i];
19954
20737
  const blockIndex = eventsForwarded + i;
20738
+ streamedToolUseIds.add(tu.id);
19955
20739
  safeEnqueue(encoder.encode(`event: content_block_start
19956
20740
  data: ${JSON.stringify({
19957
20741
  type: "content_block_start",
@@ -20054,6 +20838,7 @@ data: {"type":"message_stop"}
20054
20838
  requestId: requestMeta.requestId,
20055
20839
  timestamp: Date.now(),
20056
20840
  adapter: adapter.name,
20841
+ profileId: profile.id,
20057
20842
  requestSource,
20058
20843
  model,
20059
20844
  requestModel: body.model || undefined,
@@ -20081,7 +20866,8 @@ data: {"type":"message_stop"}
20081
20866
  outputTokens: lastUsage?.output_tokens,
20082
20867
  cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
20083
20868
  cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
20084
- cacheHitRate: computeCacheHitRate(lastUsage)
20869
+ cacheHitRate: computeCacheHitRate(lastUsage),
20870
+ ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
20085
20871
  });
20086
20872
  if (textEventsForwarded === 0) {
20087
20873
  claudeLog("response.empty_stream", {
@@ -20104,6 +20890,7 @@ data: {"type":"message_stop"}
20104
20890
  });
20105
20891
  return;
20106
20892
  }
20893
+ resolvePendingStore();
20107
20894
  const stderrOutput = stderrLines.join(`
20108
20895
  `).trim();
20109
20896
  if (stderrOutput && error instanceof Error && !error.message.includes(stderrOutput)) {
@@ -20136,17 +20923,12 @@ Subprocess stderr: ${stderrOutput}`;
20136
20923
  hasDeferredTools,
20137
20924
  sdkSessionId: resumeSessionId
20138
20925
  })} captured=${capturedToolUses.length}`, requestMeta.requestId);
20139
- for (const idx of openClientBlocks) {
20140
- safeEnqueue(encoder.encode(`event: content_block_stop
20141
- data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
20142
-
20143
- `), "recover_close_dangling_block");
20144
- }
20145
- openClientBlocks.clear();
20926
+ flushOpenClientBlocks("recovery");
20146
20927
  const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
20147
20928
  for (let i = 0;i < unseenToolUses.length; i++) {
20148
20929
  const tu = unseenToolUses[i];
20149
20930
  const blockIndex = eventsForwarded + i;
20931
+ streamedToolUseIds.add(tu.id);
20150
20932
  safeEnqueue(encoder.encode(`event: content_block_start
20151
20933
  data: ${JSON.stringify({
20152
20934
  type: "content_block_start",
@@ -20183,12 +20965,14 @@ data: ${JSON.stringify({
20183
20965
  data: {"type":"message_stop"}
20184
20966
 
20185
20967
  `), "recover_message_stop");
20968
+ recordEnvelopeViolations(checkUndeliveredToolUses(capturedToolUses, streamedToolUseIds));
20186
20969
  const recoverTotalMs = Date.now() - requestStartAt;
20187
20970
  const recoverQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
20188
20971
  telemetryStore2.record({
20189
20972
  requestId: requestMeta.requestId,
20190
20973
  timestamp: Date.now(),
20191
20974
  adapter: adapter.name,
20975
+ profileId: profile.id,
20192
20976
  requestSource,
20193
20977
  model,
20194
20978
  requestModel: body.model || undefined,
@@ -20209,7 +20993,8 @@ data: {"type":"message_stop"}
20209
20993
  totalDurationMs: recoverTotalMs,
20210
20994
  contentBlocks: eventsForwarded + unseenToolUses.length,
20211
20995
  textEvents: textEventsForwarded,
20212
- error: null
20996
+ error: null,
20997
+ ...envelopeViolations.length > 0 ? { envelopeViolations: [...envelopeViolations] } : {}
20213
20998
  });
20214
20999
  if (!streamClosed) {
20215
21000
  try {
@@ -20232,6 +21017,7 @@ data: {"type":"message_stop"}
20232
21017
  requestId: requestMeta.requestId,
20233
21018
  timestamp: Date.now(),
20234
21019
  adapter: adapter.name,
21020
+ profileId: profile.id,
20235
21021
  requestSource,
20236
21022
  model,
20237
21023
  requestModel: body.model || undefined,
@@ -20387,6 +21173,31 @@ data: ${JSON.stringify({
20387
21173
  resetAdapterFeatures2(adapter);
20388
21174
  return c.json({ ok: true });
20389
21175
  });
21176
+ app.get("/settings/api/pricing", (c) => {
21177
+ const { BUILTIN_MODEL_PRICING: BUILTIN_MODEL_PRICING2 } = (init_pricing(), __toCommonJS(exports_pricing));
21178
+ const { getPricingOverrides: getPricingOverrides2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
21179
+ return c.json({ builtin: BUILTIN_MODEL_PRICING2, overrides: getPricingOverrides2() });
21180
+ });
21181
+ app.put("/settings/api/pricing/:model", async (c) => {
21182
+ const { validatePricingUpdate: validatePricingUpdate2, setPricingOverride: setPricingOverride2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
21183
+ const model = c.req.param("model");
21184
+ try {
21185
+ const body = await c.req.json();
21186
+ setPricingOverride2(model, validatePricingUpdate2(body));
21187
+ } catch (e) {
21188
+ return c.json({ error: e.message }, 400);
21189
+ }
21190
+ return c.json({ ok: true });
21191
+ });
21192
+ app.delete("/settings/api/pricing/:model", (c) => {
21193
+ const { deletePricingOverride: deletePricingOverride2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
21194
+ try {
21195
+ deletePricingOverride2(c.req.param("model"));
21196
+ } catch (e) {
21197
+ return c.json({ error: e.message }, 400);
21198
+ }
21199
+ return c.json({ ok: true });
21200
+ });
20390
21201
  app.get("/metrics", (c) => {
20391
21202
  const body = renderPrometheusMetrics(telemetryStore2);
20392
21203
  return c.body(body, 200, {
@@ -20459,7 +21270,7 @@ data: ${JSON.stringify({
20459
21270
  });
20460
21271
  });
20461
21272
  app.get("/profiles", async (c) => {
20462
- const { profilePageHtml } = await import("./profilePage-txxpbc44.js");
21273
+ const { profilePageHtml } = await import("./profilePage-naychnb8.js");
20463
21274
  return c.html(profilePageHtml);
20464
21275
  });
20465
21276
  app.post("/profiles/active", async (c) => {
@@ -20521,7 +21332,7 @@ data: ${JSON.stringify({
20521
21332
  }
20522
21333
  });
20523
21334
  app.get("/plugins", async (c) => {
20524
- const { pluginPageHtml } = await import("./pluginPage-85s6t6k8.js");
21335
+ const { pluginPageHtml } = await import("./pluginPage-03q5j753.js");
20525
21336
  return c.html(pluginPageHtml);
20526
21337
  });
20527
21338
  app.post("/auth/refresh", async (c) => {