@rynfar/meridian 1.50.0 → 1.52.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 (49) hide show
  1. package/README.md +59 -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-yw8cx02m.js → cli-tq3vtp0q.js} +935 -189
  11. package/dist/{cli-q6dz9r5p.js → cli-vjeftz4z.js} +16 -2
  12. package/dist/cli-xmweegb1.js +179 -0
  13. package/dist/cli.js +9 -5
  14. package/dist/{pluginPage-85s6t6k8.js → pluginPage-03q5j753.js} +4 -10
  15. package/dist/{profileCli-fbhe9p5z.js → profileCli-s1h4vh8w.js} +1 -1
  16. package/dist/{profilePage-txxpbc44.js → profilePage-naychnb8.js} +6 -5
  17. package/dist/proxy/errors.d.ts +9 -0
  18. package/dist/proxy/errors.d.ts.map +1 -1
  19. package/dist/proxy/messages.d.ts +20 -0
  20. package/dist/proxy/messages.d.ts.map +1 -1
  21. package/dist/proxy/models.d.ts +16 -1
  22. package/dist/proxy/models.d.ts.map +1 -1
  23. package/dist/proxy/oauthUsage.d.ts.map +1 -1
  24. package/dist/proxy/openai.d.ts.map +1 -1
  25. package/dist/proxy/plugins/pluginPage.d.ts.map +1 -1
  26. package/dist/proxy/query.d.ts +4 -0
  27. package/dist/proxy/query.d.ts.map +1 -1
  28. package/dist/proxy/server.d.ts.map +1 -1
  29. package/dist/server.js +3 -3
  30. package/dist/telemetry/dashboard.d.ts.map +1 -1
  31. package/dist/telemetry/landing.d.ts +6 -2
  32. package/dist/telemetry/landing.d.ts.map +1 -1
  33. package/dist/telemetry/percentiles.d.ts +5 -1
  34. package/dist/telemetry/percentiles.d.ts.map +1 -1
  35. package/dist/telemetry/pricing.d.ts +63 -0
  36. package/dist/telemetry/pricing.d.ts.map +1 -0
  37. package/dist/telemetry/pricingStore.d.ts +32 -0
  38. package/dist/telemetry/pricingStore.d.ts.map +1 -0
  39. package/dist/telemetry/profileBar.d.ts +19 -9
  40. package/dist/telemetry/profileBar.d.ts.map +1 -1
  41. package/dist/telemetry/profileUsage.d.ts.map +1 -1
  42. package/dist/telemetry/settingsPage.d.ts +1 -1
  43. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  44. package/dist/telemetry/sqlite.d.ts.map +1 -1
  45. package/dist/telemetry/store.d.ts.map +1 -1
  46. package/dist/telemetry/types.d.ts +31 -0
  47. package/dist/telemetry/types.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/dist/cli-4rqtm83g.js +0 -148
@@ -20,15 +20,18 @@ import {
20
20
  profileBarHtml,
21
21
  profileBarJs,
22
22
  themeCss
23
- } from "./cli-4rqtm83g.js";
23
+ } from "./cli-xmweegb1.js";
24
24
  import {
25
+ CANONICAL_SONNET_MODEL,
25
26
  env,
26
27
  envBool,
27
28
  envInt,
29
+ explicitModelPin,
28
30
  getAuthCacheInfo,
29
31
  getClaudeAuthStatusAsync,
30
32
  getResolvedClaudeExecutableInfo,
31
33
  hasExtendedContext,
34
+ init_env,
32
35
  isClosedControllerError,
33
36
  mapModelToClaudeModel,
34
37
  recordExtendedContextUnavailable,
@@ -36,7 +39,7 @@ import {
36
39
  resolvePassthrough,
37
40
  resolveSdkModelDefaults,
38
41
  stripExtendedContext
39
- } from "./cli-q6dz9r5p.js";
42
+ } from "./cli-vjeftz4z.js";
40
43
  import {
41
44
  getSetting
42
45
  } from "./cli-340h1chz.js";
@@ -61,6 +64,137 @@ import {
61
64
  __toESM
62
65
  } from "./cli-p9swy5t3.js";
63
66
 
67
+ // src/telemetry/pricing.ts
68
+ var exports_pricing = {};
69
+ __export(exports_pricing, {
70
+ resolveModelPricing: () => resolveModelPricing,
71
+ normalizeModelKey: () => normalizeModelKey,
72
+ estimateRequestCostUsd: () => estimateRequestCostUsd,
73
+ computeCostEstimate: () => computeCostEstimate,
74
+ CACHE_WRITE_MULTIPLIER: () => CACHE_WRITE_MULTIPLIER,
75
+ CACHE_READ_MULTIPLIER: () => CACHE_READ_MULTIPLIER,
76
+ BUILTIN_MODEL_PRICING: () => BUILTIN_MODEL_PRICING
77
+ });
78
+ function normalizeModelKey(model) {
79
+ return model.trim().toLowerCase().replace(/\[1m\]$/, "");
80
+ }
81
+ function rates(inputPerMTok, outputPerMTok) {
82
+ return {
83
+ inputPerMTok,
84
+ outputPerMTok,
85
+ cacheReadPerMTok: inputPerMTok * CACHE_READ_MULTIPLIER,
86
+ cacheWritePerMTok: inputPerMTok * CACHE_WRITE_MULTIPLIER
87
+ };
88
+ }
89
+ function resolveModelPricing(model, overrides) {
90
+ const normalized = normalizeModelKey(model);
91
+ if (overrides) {
92
+ const override = overrides[normalized];
93
+ if (override)
94
+ return override;
95
+ }
96
+ const exact = BUILTIN_MODEL_PRICING[normalized];
97
+ if (exact)
98
+ return exact;
99
+ if (normalized.includes("fable") || normalized.includes("mythos"))
100
+ return FABLE;
101
+ if (normalized.includes("haiku")) {
102
+ if (normalized.includes("3-5") || normalized.includes("3.5"))
103
+ return HAIKU_35;
104
+ if (/haiku-3\b|3-haiku/.test(normalized))
105
+ return HAIKU_3;
106
+ return HAIKU;
107
+ }
108
+ if (normalized.includes("opus")) {
109
+ if (/opus-4-1\b|opus-4-1-|opus-4-0|opus-4-2025|3-opus/.test(normalized))
110
+ return OPUS_LEGACY;
111
+ return OPUS;
112
+ }
113
+ if (normalized.includes("sonnet"))
114
+ return SONNET;
115
+ return null;
116
+ }
117
+ function estimateRequestCostUsd(metric, pricing) {
118
+ 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;
119
+ }
120
+ function roundUsd(value) {
121
+ return Math.round(value * 1e6) / 1e6;
122
+ }
123
+ function computeCostEstimate(metrics, overrides) {
124
+ const byModel = {};
125
+ const byProfile = {};
126
+ let totalUsd = 0;
127
+ let unpricedRequestCount = 0;
128
+ for (const metric of metrics) {
129
+ const modelKey = metric.requestModel || metric.model;
130
+ const pricing = resolveModelPricing(modelKey, overrides);
131
+ const profileEntry = byProfile[metric.profileId ?? "default"] ??= { requests: 0, estimatedUsd: 0 };
132
+ profileEntry.requests++;
133
+ const entry = byModel[modelKey] ??= {
134
+ requests: 0,
135
+ inputTokens: 0,
136
+ outputTokens: 0,
137
+ cacheReadTokens: 0,
138
+ cacheCreationTokens: 0,
139
+ estimatedUsd: pricing === null ? null : 0
140
+ };
141
+ entry.requests++;
142
+ entry.inputTokens += metric.inputTokens ?? 0;
143
+ entry.outputTokens += metric.outputTokens ?? 0;
144
+ entry.cacheReadTokens += metric.cacheReadInputTokens ?? 0;
145
+ entry.cacheCreationTokens += metric.cacheCreationInputTokens ?? 0;
146
+ if (pricing === null) {
147
+ unpricedRequestCount++;
148
+ continue;
149
+ }
150
+ const cost = estimateRequestCostUsd(metric, pricing);
151
+ entry.estimatedUsd = (entry.estimatedUsd ?? 0) + cost;
152
+ profileEntry.estimatedUsd += cost;
153
+ totalUsd += cost;
154
+ }
155
+ for (const entry of Object.values(byProfile)) {
156
+ entry.estimatedUsd = roundUsd(entry.estimatedUsd);
157
+ }
158
+ for (const entry of Object.values(byModel)) {
159
+ if (entry.estimatedUsd !== null)
160
+ entry.estimatedUsd = roundUsd(entry.estimatedUsd);
161
+ }
162
+ return { totalUsd: roundUsd(totalUsd), byModel, unpricedRequestCount, byProfile };
163
+ }
164
+ 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;
165
+ var init_pricing = __esm(() => {
166
+ FABLE = rates(10, 50);
167
+ OPUS = rates(5, 25);
168
+ OPUS_LEGACY = rates(15, 75);
169
+ SONNET = rates(3, 15);
170
+ SONNET_5_INTRO = rates(2, 10);
171
+ HAIKU = rates(1, 5);
172
+ HAIKU_35 = rates(0.8, 4);
173
+ HAIKU_3 = rates(0.25, 1.25);
174
+ BUILTIN_MODEL_PRICING = {
175
+ fable: FABLE,
176
+ "claude-fable-5": FABLE,
177
+ "claude-mythos-5": FABLE,
178
+ opus: OPUS,
179
+ "claude-opus-4-8": OPUS,
180
+ "claude-opus-4-7": OPUS,
181
+ "claude-opus-4-6": OPUS,
182
+ "claude-opus-4-5": OPUS,
183
+ "claude-opus-4-1": OPUS_LEGACY,
184
+ "claude-opus-4-0": OPUS_LEGACY,
185
+ "claude-opus-4-20250514": OPUS_LEGACY,
186
+ "claude-3-opus-20240229": OPUS_LEGACY,
187
+ sonnet: SONNET,
188
+ "claude-sonnet-5": SONNET_5_INTRO,
189
+ "claude-sonnet-4-6": SONNET,
190
+ "claude-sonnet-4-5": SONNET,
191
+ haiku: HAIKU,
192
+ "claude-haiku-4-5": HAIKU,
193
+ "claude-3-5-haiku-20241022": HAIKU_35,
194
+ "claude-3-haiku-20240307": HAIKU_3
195
+ };
196
+ });
197
+
64
198
  // src/telemetry/percentiles.ts
65
199
  function computePercentiles(values) {
66
200
  if (values.length === 0)
@@ -76,7 +210,7 @@ function computePercentiles(values) {
76
210
  avg: Math.round(sum / sorted.length)
77
211
  };
78
212
  }
79
- function computeSummary(metrics, windowMs) {
213
+ function computeSummary(metrics, windowMs, pricingOverrides) {
80
214
  if (metrics.length === 0) {
81
215
  const emptyPhase = { p50: 0, p95: 0, p99: 0, min: 0, max: 0, avg: 0 };
82
216
  return {
@@ -99,7 +233,8 @@ function computeSummary(metrics, windowMs) {
99
233
  totalCacheCreationTokens: 0,
100
234
  avgCacheHitRate: 0,
101
235
  cacheMissOnResumeCount: 0
102
- }
236
+ },
237
+ costEstimate: { totalUsd: 0, byModel: {}, unpricedRequestCount: 0, byProfile: {} }
103
238
  };
104
239
  }
105
240
  const errorCount = metrics.filter((m) => m.error !== null).length;
@@ -166,10 +301,132 @@ function computeSummary(metrics, windowMs) {
166
301
  totalCacheCreationTokens,
167
302
  avgCacheHitRate: cacheHitRateCount > 0 ? Math.round(cacheHitRateSum / cacheHitRateCount * 100) / 100 : 0,
168
303
  cacheMissOnResumeCount
304
+ },
305
+ costEstimate: computeCostEstimate(metrics, pricingOverrides)
306
+ };
307
+ }
308
+ var init_percentiles = __esm(() => {
309
+ init_pricing();
310
+ });
311
+
312
+ // src/telemetry/pricingStore.ts
313
+ var exports_pricingStore = {};
314
+ __export(exports_pricingStore, {
315
+ validatePricingUpdate: () => validatePricingUpdate,
316
+ validateModelKey: () => validateModelKey,
317
+ setPricingOverride: () => setPricingOverride,
318
+ resetPricingOverridesCache: () => resetPricingOverridesCache,
319
+ getPricingOverrides: () => getPricingOverrides,
320
+ deletePricingOverride: () => deletePricingOverride
321
+ });
322
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
323
+ import { join, dirname } from "node:path";
324
+ import { homedir } from "node:os";
325
+ function getConfigPath() {
326
+ const explicit = env("PRICING_CONFIG");
327
+ if (explicit)
328
+ return explicit;
329
+ const dir = join(homedir(), ".config", "meridian");
330
+ return join(dir, "model-pricing.json");
331
+ }
332
+ function readOverrides() {
333
+ const path = getConfigPath();
334
+ const now = Date.now();
335
+ if (cachedOverrides && cachedPath === path && now - lastReadTime < CACHE_TTL_MS) {
336
+ return cachedOverrides;
337
+ }
338
+ try {
339
+ if (existsSync2(path)) {
340
+ const raw2 = JSON.parse(readFileSync(path, "utf-8"));
341
+ const result = {};
342
+ for (const [model, value] of Object.entries(raw2)) {
343
+ try {
344
+ result[normalizeModelKey(model)] = validatePricingUpdate(value);
345
+ } catch {}
346
+ }
347
+ cachedOverrides = result;
348
+ } else {
349
+ cachedOverrides = {};
350
+ }
351
+ } catch {
352
+ cachedOverrides = {};
353
+ }
354
+ cachedPath = path;
355
+ lastReadTime = now;
356
+ return cachedOverrides;
357
+ }
358
+ function writeOverrides(overrides) {
359
+ const path = getConfigPath();
360
+ const tmp = `${path}.tmp`;
361
+ try {
362
+ const dir = dirname(path);
363
+ if (!existsSync2(dir))
364
+ mkdirSync(dir, { recursive: true });
365
+ writeFileSync(tmp, JSON.stringify(overrides, null, 2));
366
+ renameSync(tmp, path);
367
+ cachedOverrides = overrides;
368
+ cachedPath = path;
369
+ lastReadTime = Date.now();
370
+ } catch (e) {
371
+ console.error("[pricing] write failed:", e.message);
372
+ }
373
+ }
374
+ function getPricingOverrides() {
375
+ return readOverrides();
376
+ }
377
+ function validatePricingUpdate(raw2) {
378
+ if (raw2 === null || typeof raw2 !== "object" || Array.isArray(raw2)) {
379
+ throw new Error("body must be a JSON object");
380
+ }
381
+ const input = raw2;
382
+ const readRate = (key, required) => {
383
+ const value = input[key];
384
+ if (value === undefined || value === null || value === "") {
385
+ if (required)
386
+ throw new Error(`${key} is required`);
387
+ return;
169
388
  }
389
+ if (typeof value !== "number" || !isFinite(value) || value < 0) {
390
+ throw new Error(`${key} must be a non-negative finite number`);
391
+ }
392
+ return value;
170
393
  };
394
+ const inputPerMTok = readRate("inputPerMTok", true);
395
+ const outputPerMTok = readRate("outputPerMTok", true);
396
+ const cacheReadPerMTok = readRate("cacheReadPerMTok", false) ?? inputPerMTok * CACHE_READ_MULTIPLIER;
397
+ const cacheWritePerMTok = readRate("cacheWritePerMTok", false) ?? inputPerMTok * CACHE_WRITE_MULTIPLIER;
398
+ return { inputPerMTok, outputPerMTok, cacheReadPerMTok, cacheWritePerMTok };
399
+ }
400
+ function validateModelKey(model) {
401
+ const normalized = normalizeModelKey(model);
402
+ if (normalized.length === 0)
403
+ throw new Error("model must be a non-empty string");
404
+ if (normalized.length > MAX_MODEL_KEY_LENGTH) {
405
+ throw new Error(`model must be at most ${MAX_MODEL_KEY_LENGTH} characters`);
406
+ }
407
+ return normalized;
171
408
  }
172
- var init_percentiles = () => {};
409
+ function setPricingOverride(model, pricing) {
410
+ const key = validateModelKey(model);
411
+ const overrides = { ...readOverrides(), [key]: pricing };
412
+ writeOverrides(overrides);
413
+ }
414
+ function deletePricingOverride(model) {
415
+ const key = validateModelKey(model);
416
+ const overrides = { ...readOverrides() };
417
+ delete overrides[key];
418
+ writeOverrides(overrides);
419
+ }
420
+ function resetPricingOverridesCache() {
421
+ cachedOverrides = null;
422
+ cachedPath = null;
423
+ lastReadTime = 0;
424
+ }
425
+ var MAX_MODEL_KEY_LENGTH = 200, cachedOverrides = null, cachedPath = null, lastReadTime = 0, CACHE_TTL_MS = 5000;
426
+ var init_pricingStore = __esm(() => {
427
+ init_env();
428
+ init_pricing();
429
+ });
173
430
 
174
431
  // node_modules/@neon-rs/load/dist/index.js
175
432
  var require_dist = __commonJS((exports) => {
@@ -272,8 +529,8 @@ var require_dist = __commonJS((exports) => {
272
529
  const header = report.header;
273
530
  return typeof header === "object" && !!header && "glibcVersionRuntime" in header;
274
531
  }
275
- function load(dirname2) {
276
- const m = path.join(dirname2, "index.node");
532
+ function load(dirname3) {
533
+ const m = path.join(dirname3, "index.node");
277
534
  return fs.existsSync(m) ? __require(m) : null;
278
535
  }
279
536
  exports.load = load;
@@ -296,7 +553,7 @@ var require_process = __commonJS((exports, module) => {
296
553
  var require_filesystem = __commonJS((exports, module) => {
297
554
  var fs = __require("fs");
298
555
  var LDD_PATH = "/usr/bin/ldd";
299
- var readFileSync2 = (path) => fs.readFileSync(path, "utf-8");
556
+ var readFileSync3 = (path) => fs.readFileSync(path, "utf-8");
300
557
  var readFile = (path) => new Promise((resolve2, reject) => {
301
558
  fs.readFile(path, "utf-8", (err, data) => {
302
559
  if (err) {
@@ -308,7 +565,7 @@ var require_filesystem = __commonJS((exports, module) => {
308
565
  });
309
566
  module.exports = {
310
567
  LDD_PATH,
311
- readFileSync: readFileSync2,
568
+ readFileSync: readFileSync3,
312
569
  readFile
313
570
  };
314
571
  });
@@ -317,7 +574,7 @@ var require_filesystem = __commonJS((exports, module) => {
317
574
  var require_detect_libc = __commonJS((exports, module) => {
318
575
  var childProcess = __require("child_process");
319
576
  var { isLinux, getReport } = require_process();
320
- var { LDD_PATH, readFile, readFileSync: readFileSync2 } = require_filesystem();
577
+ var { LDD_PATH, readFile, readFileSync: readFileSync3 } = require_filesystem();
321
578
  var cachedFamilyFilesystem;
322
579
  var cachedVersionFilesystem;
323
580
  var command = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
@@ -397,7 +654,7 @@ var require_detect_libc = __commonJS((exports, module) => {
397
654
  }
398
655
  cachedFamilyFilesystem = null;
399
656
  try {
400
- const lddContent = readFileSync2(LDD_PATH);
657
+ const lddContent = readFileSync3(LDD_PATH);
401
658
  cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
402
659
  } catch (e) {}
403
660
  return cachedFamilyFilesystem;
@@ -452,7 +709,7 @@ var require_detect_libc = __commonJS((exports, module) => {
452
709
  }
453
710
  cachedVersionFilesystem = null;
454
711
  try {
455
- const lddContent = readFileSync2(LDD_PATH);
712
+ const lddContent = readFileSync3(LDD_PATH);
456
713
  const versionMatch = lddContent.match(RE_GLIBC_VERSION);
457
714
  if (versionMatch) {
458
715
  cachedVersionFilesystem = versionMatch[1];
@@ -910,7 +1167,7 @@ class SqliteTelemetryStore {
910
1167
  status, queue_wait_ms, proxy_overhead_ms, ttfb_ms,
911
1168
  upstream_duration_ms, total_duration_ms, content_blocks, text_events, error,
912
1169
  input_tokens, output_tokens, cache_read_input_tokens,
913
- cache_creation_input_tokens, cache_hit_rate
1170
+ cache_creation_input_tokens, cache_hit_rate, profile_id, envelope_violations
914
1171
  ) VALUES (
915
1172
  @requestId, @timestamp, @adapter, @requestSource, @model, @requestModel, @mode,
916
1173
  @isResume, @isPassthrough, @lineageType,
@@ -919,7 +1176,7 @@ class SqliteTelemetryStore {
919
1176
  @status, @queueWaitMs, @proxyOverheadMs, @ttfbMs,
920
1177
  @upstreamDurationMs, @totalDurationMs, @contentBlocks, @textEvents, @error,
921
1178
  @inputTokens, @outputTokens, @cacheReadInputTokens,
922
- @cacheCreationInputTokens, @cacheHitRate
1179
+ @cacheCreationInputTokens, @cacheHitRate, @profileId, @envelopeViolations
923
1180
  )
924
1181
  `);
925
1182
  this.countStmt = db.prepare("SELECT COUNT(*) as cnt FROM metrics");
@@ -957,7 +1214,9 @@ class SqliteTelemetryStore {
957
1214
  outputTokens: metric.outputTokens ?? null,
958
1215
  cacheReadInputTokens: metric.cacheReadInputTokens ?? null,
959
1216
  cacheCreationInputTokens: metric.cacheCreationInputTokens ?? null,
960
- cacheHitRate: metric.cacheHitRate ?? null
1217
+ cacheHitRate: metric.cacheHitRate ?? null,
1218
+ profileId: metric.profileId ?? null,
1219
+ envelopeViolations: metric.envelopeViolations && metric.envelopeViolations.length > 0 ? JSON.stringify(metric.envelopeViolations) : null
961
1220
  });
962
1221
  } catch (err) {
963
1222
  console.error("[telemetry] SQLite write failed, skipping:", err);
@@ -1006,7 +1265,7 @@ class SqliteTelemetryStore {
1006
1265
  summarize(windowMs = 60 * 60 * 1000) {
1007
1266
  const since = Date.now() - windowMs;
1008
1267
  const metrics = this.getRecent({ limit: 1e5, since });
1009
- return computeSummary(metrics, windowMs);
1268
+ return computeSummary(metrics, windowMs, getPricingOverrides());
1010
1269
  }
1011
1270
  clear() {
1012
1271
  try {
@@ -1090,6 +1349,16 @@ class SqliteDiagnosticLogStore {
1090
1349
  } catch {}
1091
1350
  }
1092
1351
  }
1352
+ function parseEnvelopeViolations(raw2) {
1353
+ if (typeof raw2 !== "string" || raw2.length === 0)
1354
+ return;
1355
+ try {
1356
+ const parsed = JSON.parse(raw2);
1357
+ return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string") : undefined;
1358
+ } catch {
1359
+ return;
1360
+ }
1361
+ }
1093
1362
  function rowToMetric(r) {
1094
1363
  return {
1095
1364
  requestId: r.request_id,
@@ -1122,7 +1391,9 @@ function rowToMetric(r) {
1122
1391
  outputTokens: r.output_tokens ?? undefined,
1123
1392
  cacheReadInputTokens: r.cache_read_input_tokens ?? undefined,
1124
1393
  cacheCreationInputTokens: r.cache_creation_input_tokens ?? undefined,
1125
- cacheHitRate: r.cache_hit_rate ?? undefined
1394
+ cacheHitRate: r.cache_hit_rate ?? undefined,
1395
+ profileId: r.profile_id ?? undefined,
1396
+ envelopeViolations: parseEnvelopeViolations(r.envelope_violations)
1126
1397
  };
1127
1398
  }
1128
1399
  function createSqliteStores(dbPath, retentionDays) {
@@ -1170,7 +1441,9 @@ CREATE TABLE IF NOT EXISTS metrics (
1170
1441
  output_tokens INTEGER,
1171
1442
  cache_read_input_tokens INTEGER,
1172
1443
  cache_creation_input_tokens INTEGER,
1173
- cache_hit_rate REAL
1444
+ cache_hit_rate REAL,
1445
+ profile_id TEXT,
1446
+ envelope_violations TEXT
1174
1447
  );
1175
1448
  CREATE INDEX IF NOT EXISTS idx_metrics_ts ON metrics(timestamp);
1176
1449
  CREATE INDEX IF NOT EXISTS idx_metrics_model ON metrics(model);
@@ -1189,9 +1462,12 @@ CREATE INDEX IF NOT EXISTS idx_logs_cat ON diagnostic_logs(category);
1189
1462
  `, CLEANUP_INTERVAL = 1000;
1190
1463
  var init_sqlite = __esm(() => {
1191
1464
  init_percentiles();
1465
+ init_pricingStore();
1192
1466
  import_libsql = __toESM(require_libsql(), 1);
1193
1467
  METRICS_MIGRATIONS = [
1194
- "ALTER TABLE metrics ADD COLUMN request_source TEXT"
1468
+ "ALTER TABLE metrics ADD COLUMN request_source TEXT",
1469
+ "ALTER TABLE metrics ADD COLUMN profile_id TEXT",
1470
+ "ALTER TABLE metrics ADD COLUMN envelope_violations TEXT"
1195
1471
  ];
1196
1472
  });
1197
1473
 
@@ -1205,40 +1481,40 @@ __export(exports_sdkFeatures, {
1205
1481
  getExplicitThinking: () => getExplicitThinking,
1206
1482
  getAllFeatureConfigs: () => getAllFeatureConfigs
1207
1483
  });
1208
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
1209
- import { join as join5 } from "node:path";
1210
- import { homedir as homedir4 } from "node:os";
1211
- function getConfigPath() {
1212
- const dir = join5(homedir4(), ".config", "meridian");
1213
- if (!existsSync6(dir))
1214
- mkdirSync2(dir, { recursive: true });
1215
- return join5(dir, "sdk-features.json");
1484
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3 } from "node:fs";
1485
+ import { join as join6 } from "node:path";
1486
+ import { homedir as homedir5 } from "node:os";
1487
+ function getConfigPath2() {
1488
+ const dir = join6(homedir5(), ".config", "meridian");
1489
+ if (!existsSync7(dir))
1490
+ mkdirSync3(dir, { recursive: true });
1491
+ return join6(dir, "sdk-features.json");
1216
1492
  }
1217
1493
  function readConfig() {
1218
1494
  const now = Date.now();
1219
- if (cachedConfig && now - lastReadTime < CACHE_TTL_MS)
1495
+ if (cachedConfig && now - lastReadTime2 < CACHE_TTL_MS2)
1220
1496
  return cachedConfig;
1221
- const path3 = getConfigPath();
1497
+ const path3 = getConfigPath2();
1222
1498
  try {
1223
- if (existsSync6(path3)) {
1224
- cachedConfig = JSON.parse(readFileSync5(path3, "utf-8"));
1499
+ if (existsSync7(path3)) {
1500
+ cachedConfig = JSON.parse(readFileSync6(path3, "utf-8"));
1225
1501
  } else {
1226
1502
  cachedConfig = {};
1227
1503
  }
1228
1504
  } catch {
1229
1505
  cachedConfig = {};
1230
1506
  }
1231
- lastReadTime = now;
1507
+ lastReadTime2 = now;
1232
1508
  return cachedConfig;
1233
1509
  }
1234
1510
  function writeConfig(config) {
1235
- const path3 = getConfigPath();
1511
+ const path3 = getConfigPath2();
1236
1512
  const tmp = `${path3}.tmp`;
1237
1513
  try {
1238
- writeFileSync2(tmp, JSON.stringify(config, null, 2));
1239
- renameSync2(tmp, path3);
1514
+ writeFileSync3(tmp, JSON.stringify(config, null, 2));
1515
+ renameSync3(tmp, path3);
1240
1516
  cachedConfig = config;
1241
- lastReadTime = Date.now();
1517
+ lastReadTime2 = Date.now();
1242
1518
  } catch (e) {
1243
1519
  console.error("[sdk-features] write failed:", e.message);
1244
1520
  }
@@ -1311,7 +1587,7 @@ function resetAdapterFeatures(adapterName) {
1311
1587
  delete config[adapterName];
1312
1588
  writeConfig(config);
1313
1589
  }
1314
- var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null, lastReadTime = 0, CACHE_TTL_MS = 5000, VALID_CLAUDE_MD_VALUES, VALID_THINKING_VALUES;
1590
+ var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null, lastReadTime2 = 0, CACHE_TTL_MS2 = 5000, VALID_CLAUDE_MD_VALUES, VALID_THINKING_VALUES;
1315
1591
  var init_sdkFeatures = __esm(() => {
1316
1592
  DEFAULT_FEATURES = {
1317
1593
  codeSystemPrompt: true,
@@ -1361,7 +1637,7 @@ var init_settingsPage = __esm(() => {
1361
1637
  ${themeCss}
1362
1638
  * { box-sizing: border-box; margin: 0; padding: 0; }
1363
1639
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
1364
- background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
1640
+ color: var(--text); padding: 0; line-height: 1.5; }
1365
1641
  ${profileBarCss}
1366
1642
  .content { max-width: 900px; margin: 0 auto; padding: 24px; }
1367
1643
  h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
@@ -1435,6 +1711,28 @@ var init_settingsPage = __esm(() => {
1435
1711
  border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer;
1436
1712
  }
1437
1713
  .reset-btn:hover { border-color: var(--red); color: var(--red); }
1714
+
1715
+ /* Model pricing */
1716
+ .pricing-table { width: 100%; border-collapse: collapse; font-size: 12px; }
1717
+ .pricing-table th { text-align: left; padding: 8px 10px; color: var(--muted); font-weight: 500;
1718
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
1719
+ .pricing-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); }
1720
+ .pricing-table tr:last-child td { border-bottom: none; }
1721
+ .pricing-model { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; word-break: break-all; }
1722
+ .pricing-input { background: var(--bg); color: var(--text); border: 1px solid var(--border);
1723
+ border-radius: 6px; padding: 4px 8px; font-size: 12px; width: 84px; text-align: right;
1724
+ font-variant-numeric: tabular-nums; }
1725
+ .pricing-input:focus { border-color: var(--accent); outline: none; }
1726
+ .pricing-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px;
1727
+ text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }
1728
+ .badge-override { background: rgba(210, 153, 34, 0.15); color: var(--yellow); }
1729
+ .badge-builtin { background: rgba(139, 148, 158, 0.15); color: var(--muted); }
1730
+ .pricing-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px;
1731
+ padding-top: 14px; border-top: 1px solid var(--border); }
1732
+ .pricing-add input[type="text"] { width: 240px; text-align: left; }
1733
+ .add-btn { background: var(--accent); border: none; color: #fff; border-radius: 6px;
1734
+ padding: 5px 14px; font-size: 12px; font-weight: 500; cursor: pointer; }
1735
+ .pricing-note { font-size: 11px; color: var(--muted); margin-top: 12px; line-height: 1.6; }
1438
1736
  </style>
1439
1737
  </head>
1440
1738
  <body>
@@ -1451,6 +1749,32 @@ ${profileBarHtml}
1451
1749
  </p>
1452
1750
 
1453
1751
  <div id="adapters"></div>
1752
+
1753
+ <h1 style="margin-top:40px">Model Pricing</h1>
1754
+ <p class="subtitle" style="max-width:720px;line-height:1.6">
1755
+ Rates used by the telemetry cost estimate, in USD per million tokens. Edit a value to override the
1756
+ built-in rate, or add models the built-in table doesn't know about (they show as "no pricing" on the
1757
+ dashboard until defined here). Changes apply on the next dashboard refresh.
1758
+ </p>
1759
+ <div class="adapter-card">
1760
+ <table class="pricing-table">
1761
+ <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>
1762
+ <tbody id="pricingRows"></tbody>
1763
+ </table>
1764
+ <div class="pricing-add">
1765
+ <input type="text" class="pricing-input" id="newModelName" placeholder="model id (e.g. claude-opus-9)">
1766
+ <input type="number" class="pricing-input" id="newModelInput" placeholder="input" min="0" step="0.01">
1767
+ <input type="number" class="pricing-input" id="newModelOutput" placeholder="output" min="0" step="0.01">
1768
+ <input type="number" class="pricing-input" id="newModelCacheRead" placeholder="cache read" min="0" step="0.01">
1769
+ <input type="number" class="pricing-input" id="newModelCacheWrite" placeholder="cache write" min="0" step="0.01">
1770
+ <button class="add-btn" onclick="addPricingModel()">Add Model</button>
1771
+ </div>
1772
+ <div class="pricing-note">
1773
+ Cache read and cache write are optional; when left blank they default to 0.1x and 1.25x of the
1774
+ input rate (the 5-minute cache TTL multipliers). Verify current list prices at
1775
+ <a href="https://claude.com/pricing" target="_blank" rel="noreferrer" style="color:var(--accent)">claude.com/pricing</a>.
1776
+ </div>
1777
+ </div>
1454
1778
  </div>
1455
1779
 
1456
1780
  <div class="save-indicator" id="saveIndicator">Saved</div>
@@ -1585,7 +1909,127 @@ function render() {
1585
1909
  }
1586
1910
  }
1587
1911
 
1912
+ // ---- Model pricing (telemetry cost estimate) ----
1913
+ let pricingData = { builtin: {}, overrides: {} };
1914
+
1915
+ function fmtRate(v) { return String(Math.round(v * 10000) / 10000); }
1916
+
1917
+ async function loadPricing() {
1918
+ const res = await fetch('/settings/api/pricing');
1919
+ pricingData = await res.json();
1920
+ renderPricing();
1921
+ }
1922
+
1923
+ async function putPricing(model, rates) {
1924
+ const res = await fetch('/settings/api/pricing/' + encodeURIComponent(model), {
1925
+ method: 'PUT',
1926
+ headers: { 'Content-Type': 'application/json' },
1927
+ body: JSON.stringify(rates),
1928
+ });
1929
+ if (!res.ok) {
1930
+ const err = await res.json().catch(function () { return {}; });
1931
+ alert('Could not save pricing: ' + (err.error || ('HTTP ' + res.status)));
1932
+ return false;
1933
+ }
1934
+ showSaved();
1935
+ return true;
1936
+ }
1937
+
1938
+ async function removePricing(model) {
1939
+ await fetch('/settings/api/pricing/' + encodeURIComponent(model), { method: 'DELETE' });
1940
+ showSaved();
1941
+ await loadPricing();
1942
+ }
1943
+
1944
+ function rateCell(value, onCommit) {
1945
+ const td = document.createElement('td');
1946
+ const input = document.createElement('input');
1947
+ input.type = 'number';
1948
+ input.min = '0';
1949
+ input.step = '0.01';
1950
+ input.className = 'pricing-input';
1951
+ input.value = fmtRate(value);
1952
+ input.addEventListener('change', onCommit);
1953
+ td.appendChild(input);
1954
+ return { td: td, input: input };
1955
+ }
1956
+
1957
+ function renderPricing() {
1958
+ const tbody = document.getElementById('pricingRows');
1959
+ tbody.innerHTML = '';
1960
+ const models = Array.from(new Set(
1961
+ Object.keys(pricingData.builtin).concat(Object.keys(pricingData.overrides))
1962
+ )).sort();
1963
+
1964
+ for (const model of models) {
1965
+ const override = pricingData.overrides[model];
1966
+ const effective = override || pricingData.builtin[model];
1967
+ const tr = document.createElement('tr');
1968
+
1969
+ const nameTd = document.createElement('td');
1970
+ nameTd.className = 'pricing-model';
1971
+ nameTd.textContent = model;
1972
+ tr.appendChild(nameTd);
1973
+
1974
+ const cells = [];
1975
+ const commit = async function () {
1976
+ const rates = {
1977
+ inputPerMTok: parseFloat(cells[0].input.value),
1978
+ outputPerMTok: parseFloat(cells[1].input.value),
1979
+ cacheReadPerMTok: parseFloat(cells[2].input.value),
1980
+ cacheWritePerMTok: parseFloat(cells[3].input.value),
1981
+ };
1982
+ for (const k in rates) { if (!isFinite(rates[k]) || rates[k] < 0) return; }
1983
+ if (await putPricing(model, rates)) await loadPricing();
1984
+ };
1985
+ ['inputPerMTok', 'outputPerMTok', 'cacheReadPerMTok', 'cacheWritePerMTok'].forEach(function (key) {
1986
+ const cell = rateCell(effective[key], commit);
1987
+ cells.push(cell);
1988
+ tr.appendChild(cell.td);
1989
+ });
1990
+
1991
+ const badgeTd = document.createElement('td');
1992
+ const badge = document.createElement('span');
1993
+ badge.className = 'pricing-badge ' + (override ? 'badge-override' : 'badge-builtin');
1994
+ badge.textContent = override ? 'Override' : 'Built-in';
1995
+ badgeTd.appendChild(badge);
1996
+ tr.appendChild(badgeTd);
1997
+
1998
+ const actionTd = document.createElement('td');
1999
+ if (override) {
2000
+ const btn = document.createElement('button');
2001
+ btn.className = 'reset-btn';
2002
+ btn.textContent = pricingData.builtin[model] ? 'Reset' : 'Remove';
2003
+ btn.addEventListener('click', function () { removePricing(model); });
2004
+ actionTd.appendChild(btn);
2005
+ }
2006
+ tr.appendChild(actionTd);
2007
+
2008
+ tbody.appendChild(tr);
2009
+ }
2010
+ }
2011
+
2012
+ async function addPricingModel() {
2013
+ const name = document.getElementById('newModelName').value.trim();
2014
+ const inputRate = parseFloat(document.getElementById('newModelInput').value);
2015
+ const outputRate = parseFloat(document.getElementById('newModelOutput').value);
2016
+ if (!name) { alert('Enter a model id'); return; }
2017
+ if (!isFinite(inputRate) || !isFinite(outputRate)) { alert('Enter input and output rates (USD per million tokens)'); return; }
2018
+ const rates = { inputPerMTok: inputRate, outputPerMTok: outputRate };
2019
+ const cacheRead = parseFloat(document.getElementById('newModelCacheRead').value);
2020
+ const cacheWrite = parseFloat(document.getElementById('newModelCacheWrite').value);
2021
+ if (isFinite(cacheRead)) rates.cacheReadPerMTok = cacheRead;
2022
+ if (isFinite(cacheWrite)) rates.cacheWritePerMTok = cacheWrite;
2023
+ if (await putPricing(name, rates)) {
2024
+ ['newModelName', 'newModelInput', 'newModelOutput', 'newModelCacheRead', 'newModelCacheWrite'].forEach(function (id) {
2025
+ document.getElementById(id).value = '';
2026
+ });
2027
+ await loadPricing();
2028
+ }
2029
+ }
2030
+
1588
2031
  loadConfig();
2032
+ loadPricing();
1589
2033
  ${profileBarJs}
1590
2034
  </script>
1591
2035
  </body>
@@ -3745,8 +4189,8 @@ var serve = (options, listeningListener) => {
3745
4189
  };
3746
4190
 
3747
4191
  // src/proxy/server.ts
3748
- import { homedir as homedir5 } from "node:os";
3749
- import { join as join6 } from "node:path";
4192
+ import { homedir as homedir6 } from "node:os";
4193
+ import { join as join7 } from "node:path";
3750
4194
  import { query } from "@anthropic-ai/claude-agent-sdk";
3751
4195
 
3752
4196
  // src/proxy/rateLimitStore.ts
@@ -3883,6 +4327,16 @@ function normalizeUtilization(raw2) {
3883
4327
  return null;
3884
4328
  return Math.max(0, raw2 / 100);
3885
4329
  }
4330
+ function modelScopedWindowType(limit) {
4331
+ if (limit.kind !== "weekly_scoped")
4332
+ return null;
4333
+ const model = limit.scope?.model;
4334
+ const name = model?.display_name?.trim() || model?.id?.trim();
4335
+ if (!name)
4336
+ return null;
4337
+ const slug = name.toLowerCase().replace(/^claude[\s_-]+/, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
4338
+ return slug ? `seven_day_${slug}` : null;
4339
+ }
3886
4340
  function buildSnapshot(raw2) {
3887
4341
  const windows = [];
3888
4342
  for (const key of WINDOW_TYPES) {
@@ -3895,6 +4349,18 @@ function buildSnapshot(raw2) {
3895
4349
  continue;
3896
4350
  windows.push({ type: key, utilization, resetsAt });
3897
4351
  }
4352
+ const windowTypes = new Set(windows.map((window) => window.type));
4353
+ for (const limit of raw2.limits ?? []) {
4354
+ const type = modelScopedWindowType(limit);
4355
+ if (!type || windowTypes.has(type))
4356
+ continue;
4357
+ const utilization = normalizeUtilization(limit.percent);
4358
+ const resetsAt = parseIsoToMs(limit.resets_at);
4359
+ if (utilization === null && resetsAt === null)
4360
+ continue;
4361
+ windows.push({ type, utilization, resetsAt });
4362
+ windowTypes.add(type);
4363
+ }
3898
4364
  const extra = raw2.extra_usage;
3899
4365
  const extraUsage = extra ? {
3900
4366
  isEnabled: !!extra.is_enabled,
@@ -4001,6 +4467,9 @@ var DEFAULT_PROXY_CONFIG = {
4001
4467
  version: undefined
4002
4468
  };
4003
4469
 
4470
+ // src/proxy/server.ts
4471
+ init_env();
4472
+
4004
4473
  // src/proxy/transform.ts
4005
4474
  function runTransformHook(transforms, hook, ctx, adapterName) {
4006
4475
  return transforms.reduce((acc, transform) => {
@@ -8602,11 +9071,13 @@ class LRUMap {
8602
9071
  }
8603
9072
 
8604
9073
  // src/telemetry/index.ts
8605
- import { join } from "node:path";
8606
- import { homedir } from "node:os";
9074
+ init_env();
9075
+ import { join as join2 } from "node:path";
9076
+ import { homedir as homedir2 } from "node:os";
8607
9077
 
8608
9078
  // src/telemetry/store.ts
8609
9079
  init_percentiles();
9080
+ init_pricingStore();
8610
9081
  var DEFAULT_CAPACITY = 1000;
8611
9082
  function getCapacity() {
8612
9083
  const raw2 = process.env.MERIDIAN_TELEMETRY_SIZE ?? process.env.CLAUDE_PROXY_TELEMETRY_SIZE;
@@ -8665,7 +9136,7 @@ class MemoryTelemetryStore {
8665
9136
  summarize(windowMs = 60 * 60 * 1000) {
8666
9137
  const since = Date.now() - windowMs;
8667
9138
  const metrics = this.getRecent({ limit: this.capacity, since });
8668
- return computeSummary(metrics, windowMs);
9139
+ return computeSummary(metrics, windowMs, getPricingOverrides());
8669
9140
  }
8670
9141
  clear() {
8671
9142
  this.buffer = new Array(this.capacity).fill(null);
@@ -8726,8 +9197,8 @@ class MemoryDiagnosticLogStore {
8726
9197
  }
8727
9198
  var diagnosticLog = new MemoryDiagnosticLogStore;
8728
9199
  // src/telemetry/routes.ts
8729
- import { existsSync as existsSync2, readFileSync } from "node:fs";
8730
- import { resolve, dirname } from "node:path";
9200
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
9201
+ import { resolve, dirname as dirname2 } from "node:path";
8731
9202
  import { fileURLToPath } from "node:url";
8732
9203
 
8733
9204
  // src/telemetry/dashboard.ts
@@ -8744,7 +9215,7 @@ var dashboardHtml = `<!DOCTYPE html>
8744
9215
  :root { --total: var(--accent); }
8745
9216
  * { box-sizing: border-box; margin: 0; padding: 0; }
8746
9217
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
8747
- background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
9218
+ color: var(--text); padding: 0; line-height: 1.5; }
8748
9219
  h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
8749
9220
  .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }
8750
9221
  .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
@@ -8830,8 +9301,8 @@ var dashboardHtml = `<!DOCTYPE html>
8830
9301
  <body>
8831
9302
  ` + profileBarHtml + `
8832
9303
  <div style="padding:24px">
8833
- <h1>Meridian</h1>
8834
- <div class="subtitle">Request Performance Telemetry</div>
9304
+ <h1>Telemetry</h1>
9305
+ <div class="subtitle">Request performance, cost, and wire-contract integrity</div>
8835
9306
 
8836
9307
  <div class="refresh-bar">
8837
9308
  <select id="window">
@@ -8867,6 +9338,26 @@ function ago(ts) {
8867
9338
  return Math.floor(s/3600) + 'h ago';
8868
9339
  }
8869
9340
 
9341
+ function fmtTok(n) {
9342
+ return n > 1000000 ? (n/1000000).toFixed(1) + 'M' : n > 1000 ? Math.round(n/1000) + 'k' : String(n);
9343
+ }
9344
+
9345
+ // Model names come from client-supplied request bodies (requestModel) — escape
9346
+ // before concatenating into innerHTML so a quirky/malicious client can't
9347
+ // script the dashboard.
9348
+ function esc(s) {
9349
+ return String(s).replace(/[&<>"']/g, function (ch) {
9350
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ch];
9351
+ });
9352
+ }
9353
+
9354
+ function usd(v) {
9355
+ if (v == null) return '—';
9356
+ if (v > 0 && v < 0.01) return '$' + v.toFixed(4);
9357
+ if (v < 100) return '$' + v.toFixed(2);
9358
+ return '$' + Math.round(v).toLocaleString();
9359
+ }
9360
+
8870
9361
  function pctRow(label, color, phase) {
8871
9362
  return '<tr>'
8872
9363
  + '<td><span class="phase-dot" style="background:' + color + '"></span>' + label + '</td>'
@@ -8949,7 +9440,6 @@ function render(s, reqs, logs, quota) {
8949
9440
  // Token usage cards
8950
9441
  if (s.tokenUsage) {
8951
9442
  const t = s.tokenUsage;
8952
- const fmtTok = n => n > 1000000 ? (n/1000000).toFixed(1) + 'M' : n > 1000 ? Math.round(n/1000) + 'k' : String(n);
8953
9443
  html += '<div class="section"><div class="section-title">Token Usage</div></div>';
8954
9444
  html += '<div class="cards">'
8955
9445
  + card('Input Tokens', fmtTok(t.totalInputTokens), '')
@@ -8960,12 +9450,51 @@ function render(s, reqs, logs, quota) {
8960
9450
  + '</div>';
8961
9451
  }
8962
9452
 
9453
+ // Estimated cost: static API list pricing applied to the window's token usage
9454
+ if (s.costEstimate && Object.keys(s.costEstimate.byModel).length > 0) {
9455
+ const ce = s.costEstimate;
9456
+ const costRows = Object.entries(ce.byModel)
9457
+ .sort((a, b) => (b[1].estimatedUsd || 0) - (a[1].estimatedUsd || 0));
9458
+
9459
+ html += '<div class="section"><div class="section-title">Estimated Cost</div></div>';
9460
+ html += '<div class="cards">'
9461
+ + card('Est. API Cost', usd(ce.totalUsd), 'window total at API list prices');
9462
+ for (const [model, m] of costRows) {
9463
+ html += card(esc(model), usd(m.estimatedUsd), m.requests + ' req' + (m.requests === 1 ? '' : 's'));
9464
+ }
9465
+ html += '</div>';
9466
+
9467
+ html += '<div class="section">'
9468
+ + '<table><thead><tr><th>Model</th><th>Requests</th><th>Input</th><th>Output</th>'
9469
+ + '<th>Cache Read</th><th>Cache Write</th><th>Est. Cost</th></tr></thead><tbody>';
9470
+ for (const [model, m] of costRows) {
9471
+ html += '<tr>'
9472
+ + '<td>' + esc(model) + (m.estimatedUsd == null ? ' <span style="font-size:10px;color:var(--yellow)">no pricing</span>' : '') + '</td>'
9473
+ + '<td class="mono">' + m.requests + '</td>'
9474
+ + '<td class="mono">' + fmtTok(m.inputTokens) + '</td>'
9475
+ + '<td class="mono">' + fmtTok(m.outputTokens) + '</td>'
9476
+ + '<td class="mono">' + fmtTok(m.cacheReadTokens) + '</td>'
9477
+ + '<td class="mono">' + fmtTok(m.cacheCreationTokens) + '</td>'
9478
+ + '<td class="mono">' + usd(m.estimatedUsd) + '</td>'
9479
+ + '</tr>';
9480
+ }
9481
+ html += '</tbody></table>'
9482
+ + '<div class="usage-note" style="margin-top:8px">Estimated at static Anthropic API list prices'
9483
+ + ' (cache writes at the 5-minute TTL rate). Claude Max usage is covered by your subscription'
9484
+ + ' (equivalent API cost, not a charge).'
9485
+ + (ce.unpricedRequestCount > 0
9486
+ ? ' ' + ce.unpricedRequestCount + ' request' + (ce.unpricedRequestCount === 1 ? '' : 's') + ' from unrecognized models excluded.'
9487
+ : '')
9488
+ + ' Rates are editable in <a href="/settings" style="color:var(--accent)">Settings</a>.'
9489
+ + '</div></div>';
9490
+ }
9491
+
8963
9492
  // Model breakdown
8964
9493
  const models = Object.entries(s.byModel);
8965
9494
  if (models.length > 0) {
8966
9495
  html += '<div class="cards">';
8967
9496
  for (const [name, data] of models) {
8968
- html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));
9497
+ html += card(esc(name), data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));
8969
9498
  }
8970
9499
  html += '</div>';
8971
9500
  }
@@ -9205,8 +9734,8 @@ timer = setInterval(refresh, 5000);
9205
9734
  </html>`;
9206
9735
 
9207
9736
  // src/telemetry/routes.ts
9208
- var _iconPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
9209
- var _iconSvg = existsSync2(_iconPath) ? readFileSync(_iconPath, "utf-8") : null;
9737
+ var _iconPath = resolve(dirname2(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
9738
+ var _iconSvg = existsSync3(_iconPath) ? readFileSync2(_iconPath, "utf-8") : null;
9210
9739
  function createTelemetryRoutes() {
9211
9740
  const routes = new Hono2;
9212
9741
  routes.get("/", (c) => {
@@ -9261,108 +9790,202 @@ var landingHtml = `<!DOCTYPE html>
9261
9790
  ${themeCss}
9262
9791
  * { box-sizing: border-box; margin: 0; padding: 0; }
9263
9792
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
9264
- background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }
9265
- .container { max-width: 960px; margin: 0 auto; padding: 32px 24px; }
9266
-
9267
- .header { display: flex; align-items: center; gap: 16px; margin-bottom: 6px; }
9268
- .header h1 { font-size: 28px; font-weight: 700; letter-spacing: 3px; }
9269
- .tagline { color: var(--muted); font-size: 14px; margin-bottom: 32px; letter-spacing: 0.5px; }
9270
-
9271
- .status-banner { display: flex; align-items: center; gap: 12px; padding: 16px 20px;
9272
- background: var(--surface); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 24px; }
9273
- .status-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
9274
- .status-dot.healthy { background: var(--green); box-shadow: 0 0 8px rgba(63,185,80,0.4); }
9275
- .status-dot.degraded { background: var(--yellow); }
9276
- .status-dot.unhealthy { background: var(--red); }
9277
- .status-text { font-size: 14px; font-weight: 500; }
9278
- .status-detail { font-size: 12px; color: var(--muted); margin-left: auto; }
9279
-
9280
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
9281
- .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
9282
- .card-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; font-weight: 500; }
9283
- .card-value { font-size: 32px; font-weight: 700; margin-top: 4px; font-variant-numeric: tabular-nums; }
9284
- .card-value.green { color: var(--green); }
9285
- .card-value.violet { color: var(--violet); }
9286
- .card-detail { font-size: 12px; color: var(--muted); margin-top: 4px; }
9793
+ color: var(--text); line-height: 1.6; min-height: 100vh; }
9794
+ .container { max-width: 960px; margin: 0 auto; padding: 28px 24px; }
9795
+
9796
+ /* Intro friendly one-paragraph overview of how Meridian works */
9797
+ .intro { margin-bottom: 28px; }
9798
+ .intro h2 { font-size: 20px; font-weight: 700; margin-bottom: 6px; }
9799
+ .intro p { font-size: 13px; color: var(--muted); max-width: 640px; }
9800
+ .intro code { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px;
9801
+ background: var(--surface); border: 1px solid var(--border); border-radius: 5px;
9802
+ padding: 1px 6px; color: var(--accent2); white-space: nowrap; }
9803
+ .intro a { color: var(--accent); text-decoration: none; }
9804
+ .intro a:hover { text-decoration: underline; }
9805
+ .intro-meta { font-size: 12px; color: var(--muted); margin-top: 8px; }
9806
+
9807
+ /* Profile cards the centerpiece: usage + cost per account, click to switch */
9808
+ .profile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; margin-bottom: 24px; }
9809
+ .profile-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
9810
+ padding: 18px 20px; position: relative; transition: border-color 0.15s; }
9811
+ .profile-card.switchable { cursor: pointer; }
9812
+ .profile-card.switchable:hover { border-color: var(--accent); }
9813
+ .profile-card.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
9814
+ .profile-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 4px; }
9815
+ .profile-name { font-size: 13px; font-weight: 600; letter-spacing: 0.5px; display: flex; align-items: center; gap: 8px; }
9816
+ .profile-name .prof-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border); }
9817
+ .profile-card.active .prof-dot { background: var(--accent); box-shadow: 0 0 6px rgba(88,166,255,0.5); }
9818
+ .active-pill { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
9819
+ color: var(--accent); background: rgba(88,166,255,0.12); border: 1px solid rgba(88,166,255,0.35);
9820
+ border-radius: 10px; padding: 1px 8px; }
9821
+ .switch-hint { font-size: 9px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;
9822
+ color: var(--muted); opacity: 0; transition: opacity 0.15s; }
9823
+ .profile-card.switchable:hover .switch-hint { opacity: 1; }
9824
+ .profile-cost { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
9825
+ .profile-sub { font-size: 11px; color: var(--muted); text-align: right; margin-bottom: 12px; }
9826
+ .usage-row { display: flex; align-items: center; gap: 10px; font-size: 12px; padding: 4px 0; }
9827
+ .usage-row .w-label { color: var(--muted); width: 64px; flex-shrink: 0; }
9828
+ .usage-row .w-bar { flex: 1; height: 6px; background: var(--surface2); border-radius: 3px; overflow: hidden; }
9829
+ .usage-row .w-fill { height: 100%; border-radius: 3px; }
9830
+ .usage-row .w-pct { width: 38px; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
9831
+ .usage-row .w-reset { color: var(--muted); font-size: 11px; width: 76px; text-align: right; }
9832
+ .no-usage { font-size: 12px; color: var(--muted); padding: 4px 0; }
9833
+
9834
+ /* Traffic strip — one compact surface */
9835
+ .strip { display: flex; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--border);
9836
+ border-radius: 12px; padding: 14px 4px; margin-bottom: 24px; }
9837
+ .strip-item { flex: 1; min-width: 120px; padding: 2px 18px; border-right: 1px solid var(--border); }
9838
+ .strip-item:last-child { border-right: none; }
9839
+ .strip-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; }
9840
+ .strip-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
9841
+ .strip-value.green { color: var(--green); }
9842
+ .strip-value.red { color: var(--red); }
9843
+ .strip-detail { font-size: 11px; color: var(--muted); }
9287
9844
 
9288
9845
  .section { margin-bottom: 24px; }
9289
9846
  .section-title { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase;
9290
9847
  letter-spacing: 1px; margin-bottom: 12px; }
9291
- .info-grid { display: grid; grid-template-columns: 120px 1fr; gap: 8px 16px; font-size: 13px;
9292
- background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 20px; }
9293
- .info-label { color: var(--muted); }
9294
- .info-value { color: var(--text); font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }
9295
-
9296
- .snippet { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
9297
- padding: 16px 20px; margin-top: 12px; }
9298
- .snippet code { display: block; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
9299
- font-size: 12px; color: var(--lavender); line-height: 1.8; white-space: pre-wrap; word-break: break-all; }
9300
- .snippet-tabs { display: flex; gap: 0; margin-bottom: 12px; }
9301
- .snippet-tab { padding: 6px 14px; font-size: 11px; font-weight: 500; cursor: pointer;
9302
- color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-bottom: none; }
9303
- .snippet-tab:first-child { border-radius: 8px 0 0 0; }
9304
- .snippet-tab:last-child { border-radius: 0 8px 0 0; }
9305
- .snippet-tab.active { color: var(--violet); background: var(--surface2); border-color: var(--accent); }
9306
-
9307
- .links { display: flex; gap: 12px; margin-top: 32px; flex-wrap: wrap; }
9308
- .link { padding: 10px 20px; background: var(--surface2); border: 1px solid var(--border);
9309
- border-radius: 8px; color: var(--violet); text-decoration: none; font-size: 13px; font-weight: 500;
9310
- transition: border-color 0.2s; }
9311
- .link:hover { border-color: var(--accent); }
9312
9848
 
9313
9849
  .footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border);
9314
9850
  font-size: 11px; color: var(--muted); text-align: center; }
9315
- .footer a { color: var(--violet); text-decoration: none; }
9851
+ .footer a { color: var(--accent); text-decoration: none; }
9316
9852
  ` + profileBarCss + `
9317
9853
  </style>
9318
9854
  </head>
9319
9855
  <body>
9320
9856
  ` + profileBarHtml + `
9321
9857
  <div class="container">
9322
- <div class="header">
9323
- <svg width="40" height="40" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
9324
- <rect width="64" height="64" rx="14" fill="#1C1830"/>
9325
- <line x1="32" y1="10" x2="32" y2="54" stroke="#8B7CF6" stroke-width="2.5" stroke-linecap="round"/>
9326
- <path d="M16 20 A18 18 0 0 1 48 20" fill="none" stroke="#C4B5FD" stroke-width="1.2" opacity="0.4"/>
9327
- <path d="M16 44 A18 18 0 0 0 48 44" fill="none" stroke="#C4B5FD" stroke-width="1.2" opacity="0.4"/>
9328
- <path d="M20 30 A14 14 0 0 1 44 30" fill="none" stroke="#C4B5FD" stroke-width="0.8" opacity="0.2"/>
9329
- <path d="M20 34 A14 14 0 0 0 44 34" fill="none" stroke="#C4B5FD" stroke-width="0.8" opacity="0.2"/>
9330
- <circle cx="32" cy="10" r="3.5" fill="#C4B5FD"/><circle cx="32" cy="54" r="3.5" fill="#C4B5FD"/>
9331
- <circle cx="32" cy="32" r="3" fill="#8B7CF6"/>
9332
- </svg>
9333
- <h1>MERIDIAN</h1>
9334
- </div>
9335
- <div class="tagline">Harness Claude, your way.</div>
9336
9858
  <div id="content"><div style="color:var(--muted);padding:40px;text-align:center">Loading…</div></div>
9337
9859
  </div>
9338
9860
  <script>
9339
9861
  function ms(v){if(v==null||v===0)return '—';return v<1000?v+'ms':(v/1000).toFixed(1)+'s'}
9340
- 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>'}
9862
+ function esc(s){return String(s).replace(/[&<>"']/g,function(ch){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[ch]})}
9863
+ 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()}
9864
+
9865
+ 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'};
9866
+ 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()})}
9867
+ function utilColor(u){return u>=0.85?'var(--red)':u>=0.6?'var(--yellow)':'var(--green)'}
9868
+ 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':'')}
9869
+
9870
+ function introSection(h){
9871
+ var meta=[];
9872
+ if(h.auth&&h.auth.loggedIn)meta.push(esc(h.auth.email||'')+(h.auth.subscriptionType?' ('+esc(h.auth.subscriptionType)+')':''));
9873
+ meta.push(h.mode||'internal');
9874
+ meta.push('port '+location.port);
9875
+ return '<div class="intro">'
9876
+ +'<h2>Harness Claude, your way.</h2>'
9877
+ +'<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>'
9878
+ +'<div class="intro-meta">'+meta.join(' · ')+'</div>'
9879
+ +'</div>';
9880
+ }
9881
+
9882
+ function profileSection(q,s,pl,h){
9883
+ var byProfile=(s&&s.costEstimate&&s.costEstimate.byProfile)||{};
9884
+ var quotaByProfile={};
9885
+ 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||[]}
9886
+ var profs=[];var seen={};
9887
+ var configured=(pl&&Array.isArray(pl.profiles))?pl.profiles:[];
9888
+ var multi=configured.length>1;
9889
+ if(configured.length>0){
9890
+ // Real profiles exist: show exactly those. Traffic that predates
9891
+ // per-profile attribution (the synthetic "default" bucket) still
9892
+ // counts in the totals strip but doesn't render as a fake account.
9893
+ 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}
9894
+ }else{
9895
+ // Single-account setup: one card, labeled with the logged-in email.
9896
+ var email=(h&&h.auth&&h.auth.loggedIn&&h.auth.email)||'';
9897
+ for(var k in quotaByProfile){profs.push({id:k,label:k==='default'?(email||'account'):k,configured:false});seen[k]=1}
9898
+ for(var k in byProfile){if(!seen[k])profs.push({id:k,label:k==='default'?(email||'account'):k,configured:false});seen[k]=1}
9899
+ }
9900
+ if(profs.length===0)return '';
9901
+ var cards='';
9902
+ for(var i=0;i<profs.length;i++){
9903
+ var p=profs[i];var cost=byProfile[p.id];
9904
+ var wins=(quotaByProfile[p.id]||[]).filter(function(w){return w.utilization!=null});
9905
+ if(!p.configured&&wins.length===0&&!cost)continue;
9906
+ var rows='';
9907
+ for(var j=0;j<wins.length;j++){
9908
+ var w=wins[j];var pct=Math.round(w.utilization*100);
9909
+ rows+='<div class="usage-row"><span class="w-label">'+esc(winLabel(w.type))+'</span>'
9910
+ +'<div class="w-bar"><div class="w-fill" style="width:'+Math.min(pct,100)+'%;background:'+utilColor(w.utilization)+'"></div></div>'
9911
+ +'<span class="w-pct" style="color:'+utilColor(w.utilization)+'">'+pct+'%</span>'
9912
+ +'<span class="w-reset">'+resetIn(w.resetsAt)+'</span></div>';
9913
+ }
9914
+ if(!rows)rows='<div class="no-usage">no usage data yet</div>';
9915
+ var switchable=multi&&p.configured&&!p.isActive;
9916
+ var badge=p.isActive?'<span class="active-pill">Active</span>':switchable?'<span class="switch-hint">Click to activate</span>':'';
9917
+ cards+='<div class="profile-card'+(p.isActive?' active':'')+(switchable?' switchable':'')+'"'+(switchable?' data-profile="'+esc(p.id)+'" role="button" tabindex="0"':'')+'>'
9918
+ +'<div class="profile-head"><span class="profile-name"><span class="prof-dot"></span>'+esc(p.label||p.id)+' '+badge+'</span>'
9919
+ +'<span class="profile-cost">'+usd(cost?cost.estimatedUsd:0)+'</span></div>'
9920
+ +'<div class="profile-sub">'+(cost?cost.requests+' request'+(cost.requests===1?'':'s')+' · est. API value · 24h':'no traffic · 24h')+'</div>'
9921
+ +rows+'</div>';
9922
+ }
9923
+ if(!cards)return '';
9924
+ return '<div class="section"><div class="section-title">'+(profs.length===1?'Account':'Accounts')+'</div><div class="profile-grid">'+cards+'</div></div>';
9925
+ }
9926
+
9927
+ function strip(items){
9928
+ var o='<div class="strip">';
9929
+ for(var i=0;i<items.length;i++){var it=items[i];
9930
+ 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>';
9931
+ }
9932
+ return o+'</div>';
9933
+ }
9341
9934
 
9342
9935
  async function refresh(){
9343
9936
  try{
9344
- const [health,stats]=await Promise.all([fetch('/health').then(r=>r.json()),fetch('/telemetry/summary?window=86400000').then(r=>r.json())]);
9345
- render(health,stats);
9937
+ const [health,stats,quota,profiles]=await Promise.all([
9938
+ fetch('/health').then(r=>r.json()),
9939
+ fetch('/telemetry/summary?window=86400000').then(r=>r.json()),
9940
+ fetch('/v1/usage/quota/all').then(r=>r.json()).catch(function(){return null}),
9941
+ fetch('/profiles/list').then(r=>r.json()).catch(function(){return null})
9942
+ ]);
9943
+ render(health,stats,quota,profiles);
9346
9944
  }catch(e){document.getElementById('content').innerHTML='<div style="color:var(--red);padding:40px;text-align:center">Could not connect</div>'}
9347
9945
  }
9348
9946
 
9349
- function render(h,s){
9350
- const st=h.status||'unknown',dot=st==='healthy'?'healthy':st==='degraded'?'degraded':'unhealthy';
9947
+ 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)}
9948
+
9949
+ function render(h,s,q,pl){
9351
9950
  let o='';
9352
- 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>';
9353
- const er=s.totalRequests>0?((s.errorCount/s.totalRequests)*100).toFixed(1):'0';
9354
- 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>';
9355
- o+='<div class="section"><div class="section-title">Account</div>';
9356
- 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>'}
9357
- 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>'}
9358
- o+='</div>';
9359
- 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>'}
9360
- 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>';
9361
- 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>';
9362
- o+='<div class="footer">Meridian · Built on the <a href="https://github.com/anthropics/claude-code-sdk-js">Claude Code SDK</a></div>';
9951
+ o+=introSection(h);
9952
+
9953
+ // Accounts per-profile usage + est cost; click a card to switch
9954
+ o+=profileSection(q,s,pl,h);
9955
+
9956
+ // Last 24 hours — meaningful signals only. Errors and envelope
9957
+ // violations appear only when there is something to report.
9958
+ var tu=s.tokenUsage||{};
9959
+ var cache=tu.avgCacheHitRate!=null?Math.round(tu.avgCacheHitRate*100)+'%':'';
9960
+ var items=[
9961
+ ['Requests',String(s.totalRequests),s.errorCount>0?'red':'',s.errorCount>0?s.errorCount+' error'+(s.errorCount===1?'':'s'):'no errors'],
9962
+ ['Tokens Out',tokens(tu.totalOutputTokens),'',tokens(tu.totalInputTokens)+' in'],
9963
+ ['Cache Hit',cache,tu.avgCacheHitRate>=0.5?'green':'','prompt cache'],
9964
+ ['Est. API Value',usd(s.costEstimate?.totalUsd),'','list prices'],
9965
+ ['Median Response',ms(s.totalDuration?.p50),'','p95 '+ms(s.totalDuration?.p95)]
9966
+ ];
9967
+ if(s.envelopeViolationCount>0)items.push(['Envelope',String(s.envelopeViolationCount),'red','wire-contract violations']);
9968
+ o+='<div class="section"><div class="section-title">Last 24 Hours</div>'+strip(items)+'</div>';
9969
+
9970
+ 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>';
9363
9971
  document.getElementById('content').innerHTML=o;
9364
9972
  }
9365
- 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'}
9973
+
9974
+ function switchProfile(id){
9975
+ fetch('/profiles/active',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({profile:id})})
9976
+ .then(function(r){return r.json()})
9977
+ .then(function(data){if(data.success){refresh();if(window.meridianHeaderRefresh)window.meridianHeaderRefresh()}})
9978
+ .catch(function(){});
9979
+ }
9980
+ document.getElementById('content').addEventListener('click',function(e){
9981
+ var card=e.target.closest('.profile-card.switchable');
9982
+ if(card&&card.dataset.profile)switchProfile(card.dataset.profile);
9983
+ });
9984
+ document.getElementById('content').addEventListener('keydown',function(e){
9985
+ if(e.key!=='Enter'&&e.key!==' ')return;
9986
+ var card=e.target.closest('.profile-card.switchable');
9987
+ if(card&&card.dataset.profile){e.preventDefault();switchProfile(card.dataset.profile)}
9988
+ });
9366
9989
  refresh();setInterval(refresh,10000);
9367
9990
  ` + profileBarJs + `
9368
9991
  </script>
@@ -9429,7 +10052,7 @@ function renderPrometheusMetrics(store) {
9429
10052
  // src/telemetry/index.ts
9430
10053
  init_sqlite();
9431
10054
  function getDefaultDbPath() {
9432
- return join(homedir(), ".config", "meridian", "telemetry.db");
10055
+ return join2(homedir2(), ".config", "meridian", "telemetry.db");
9433
10056
  }
9434
10057
  function createStores() {
9435
10058
  if (!envBool("TELEMETRY_PERSIST")) {
@@ -9558,6 +10181,11 @@ function isStaleSessionError(error) {
9558
10181
  const msg = error.message;
9559
10182
  return msg.includes("No message found with message.uuid") || msg.includes("No conversation found with session ID") || msg.includes("No conversation found to continue") || msg.includes("No conversations found to resume");
9560
10183
  }
10184
+ function isBusySessionError(error, stderr) {
10185
+ const needle = "is currently running as a background agent";
10186
+ const msg = error instanceof Error ? error.message : String(error);
10187
+ return msg.includes(needle) || (stderr?.includes(needle) ?? false);
10188
+ }
9561
10189
  function isRateLimitError(errMsg) {
9562
10190
  const lower = errMsg.toLowerCase();
9563
10191
  return lower.includes("429") || lower.includes("rate limit") || lower.includes("too many requests");
@@ -9838,7 +10466,7 @@ ${historyBlock}` : historyBlock;
9838
10466
  messagesToSend = turns.slice(-1);
9839
10467
  }
9840
10468
  const result = {
9841
- model: body.model ?? "claude-sonnet-4-6",
10469
+ model: body.model ?? CANONICAL_SONNET_MODEL,
9842
10470
  messages: messagesToSend,
9843
10471
  max_tokens: body.max_tokens ?? body.max_completion_tokens ?? 8192,
9844
10472
  tools,
@@ -10044,6 +10672,15 @@ var FULL_CAPABILITIES = Object.freeze({
10044
10672
  });
10045
10673
  function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000)) {
10046
10674
  return [
10675
+ {
10676
+ id: "claude-sonnet-5",
10677
+ object: "model",
10678
+ created: now,
10679
+ owned_by: "anthropic",
10680
+ display_name: "Claude Sonnet 5",
10681
+ context_window: 200000,
10682
+ capabilities: FULL_CAPABILITIES
10683
+ },
10047
10684
  {
10048
10685
  id: "claude-sonnet-4-6",
10049
10686
  object: "model",
@@ -10159,6 +10796,27 @@ function getLastUserMessage(messages) {
10159
10796
  }
10160
10797
  return messages.slice(-1);
10161
10798
  }
10799
+ function frameReplayTurns(turns) {
10800
+ const nonEmpty = turns.filter((t) => t.text);
10801
+ const joined = nonEmpty.map((t) => t.text).join(`
10802
+
10803
+ `);
10804
+ if (nonEmpty.length < 2)
10805
+ return joined;
10806
+ const last = nonEmpty[nonEmpty.length - 1];
10807
+ if (last.role !== "user")
10808
+ return joined;
10809
+ const history = nonEmpty.slice(0, -1).map((t) => t.text).join(`
10810
+
10811
+ `);
10812
+ return `<conversation_history>
10813
+ ${history}
10814
+ </conversation_history>
10815
+
10816
+ ` + `The above is a replay of your prior conversation with this user — the original session could not be resumed. ` + `It is context only: do not continue or imitate its transcript format, do not write "[Assistant: ...]" markers, ` + `and never invent tool output — use your actual tools when action is needed. ` + `Respond only as the assistant to the user's message below.
10817
+
10818
+ ` + last.text;
10819
+ }
10162
10820
  function stripNonStandardStreamFields(event) {
10163
10821
  if (event && typeof event === "object") {
10164
10822
  const e = event;
@@ -10470,7 +11128,11 @@ ${text.slice(0, 2000)}` : text.slice(0, 2000);
10470
11128
  return createHash("sha256").update(seed).digest("hex").slice(0, 16);
10471
11129
  }
10472
11130
 
11131
+ // src/proxy/adapters/opencode.ts
11132
+ init_env();
11133
+
10473
11134
  // src/proxy/transforms/opencode.ts
11135
+ init_env();
10474
11136
  var openCodeTransforms = [
10475
11137
  {
10476
11138
  name: "opencode-core",
@@ -10636,7 +11298,11 @@ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be on
10636
11298
  }
10637
11299
  };
10638
11300
 
11301
+ // src/proxy/adapters/droid.ts
11302
+ init_env();
11303
+
10639
11304
  // src/proxy/transforms/droid.ts
11305
+ init_env();
10640
11306
  var DROID_MCP_SERVER_NAME = "droid";
10641
11307
  var DROID_ALLOWED_MCP_TOOLS = [
10642
11308
  `mcp__${DROID_MCP_SERVER_NAME}__read`,
@@ -10942,7 +11608,11 @@ var passthroughAdapter = {
10942
11608
  }
10943
11609
  };
10944
11610
 
11611
+ // src/proxy/adapters/pi.ts
11612
+ init_env();
11613
+
10945
11614
  // src/proxy/transforms/pi.ts
11615
+ init_env();
10946
11616
  var PI_MCP_SERVER_NAME = "pi";
10947
11617
  var PI_ALLOWED_MCP_TOOLS = [
10948
11618
  `mcp__${PI_MCP_SERVER_NAME}__read`,
@@ -11175,6 +11845,7 @@ var forgeCodeAdapter = {
11175
11845
  };
11176
11846
 
11177
11847
  // src/proxy/adapters/claudecode.ts
11848
+ init_env();
11178
11849
  function extractClaudeCodeClientCwd(body) {
11179
11850
  let systemText = "";
11180
11851
  if (typeof body.system === "string") {
@@ -11298,10 +11969,10 @@ var cherryAdapter = {
11298
11969
  };
11299
11970
 
11300
11971
  // src/proxy/adapterInstances.ts
11301
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
11302
- import { join as join2 } from "node:path";
11303
- import { homedir as homedir2 } from "node:os";
11304
- var CONFIG_FILE = join2(homedir2(), ".config", "meridian", "adapter-instances.json");
11972
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
11973
+ import { join as join3 } from "node:path";
11974
+ import { homedir as homedir3 } from "node:os";
11975
+ var CONFIG_FILE = join3(homedir3(), ".config", "meridian", "adapter-instances.json");
11305
11976
  function parseAdapterInstances(raw2) {
11306
11977
  if (!raw2)
11307
11978
  return {};
@@ -11348,7 +12019,7 @@ function loadAdapterInstances() {
11348
12019
  if (diskCacheAt > 0 && Date.now() - diskCacheAt < DISK_CACHE_TTL_MS)
11349
12020
  return diskCache;
11350
12021
  try {
11351
- diskCache = existsSync3(CONFIG_FILE) ? parseAdapterInstances(readFileSync2(CONFIG_FILE, "utf-8")) : {};
12022
+ diskCache = existsSync4(CONFIG_FILE) ? parseAdapterInstances(readFileSync3(CONFIG_FILE, "utf-8")) : {};
11352
12023
  } catch (err) {
11353
12024
  console.warn(`[meridian] Failed to read ${CONFIG_FILE}: ${err instanceof Error ? err.message : err}`);
11354
12025
  diskCache = {};
@@ -17156,6 +17827,7 @@ function createOpencodeMcpServer() {
17156
17827
  }
17157
17828
 
17158
17829
  // src/proxy/query.ts
17830
+ init_env();
17159
17831
  function stripConfigDir(env2) {
17160
17832
  if (!("CLAUDE_CONFIG_DIR" in env2))
17161
17833
  return env2;
@@ -17217,6 +17889,7 @@ function buildQueryOptions(ctx, abortController) {
17217
17889
  resumeSessionId,
17218
17890
  isUndo,
17219
17891
  undoRollbackUuid,
17892
+ forkSession,
17220
17893
  sdkHooks,
17221
17894
  blockedTools,
17222
17895
  incompatibleTools,
@@ -17256,7 +17929,6 @@ function buildQueryOptions(ctx, abortController) {
17256
17929
  ...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt, cwdNote),
17257
17930
  ...passthrough ? {
17258
17931
  tools: [],
17259
- settingSources: [],
17260
17932
  disallowedTools: [...allBlockedTools],
17261
17933
  ...passthroughMcp ? {
17262
17934
  allowedTools: [...passthroughMcp.toolNames],
@@ -17268,13 +17940,11 @@ function buildQueryOptions(ctx, abortController) {
17268
17940
  mcpServers: { [mcpServerName]: createOpencodeMcpServer() }
17269
17941
  },
17270
17942
  plugins: [],
17271
- ...settingSources && settingSources.length > 0 ? {
17272
- settingSources,
17273
- settings: {
17274
- autoMemoryEnabled: ctx.memory ?? true,
17275
- autoDreamEnabled: ctx.dreaming ?? false
17276
- }
17277
- } : {},
17943
+ settings: {
17944
+ autoMemoryEnabled: ctx.memory ?? true,
17945
+ autoDreamEnabled: ctx.dreaming ?? false
17946
+ },
17947
+ settingSources: settingSources ?? [],
17278
17948
  ...onStderr ? { stderr: onStderr } : {},
17279
17949
  env: {
17280
17950
  ...sharedMemory ? stripConfigDir(cleanEnv) : cleanEnv,
@@ -17286,7 +17956,8 @@ function buildQueryOptions(ctx, abortController) {
17286
17956
  },
17287
17957
  ...Object.keys(sdkAgents).length > 0 ? { agents: sdkAgents } : {},
17288
17958
  ...resumeSessionId ? { resume: resumeSessionId } : {},
17289
- ...isUndo ? { forkSession: true, ...undoRollbackUuid ? { resumeSessionAt: undoRollbackUuid } : {} } : {},
17959
+ ...isUndo || forkSession ? { forkSession: true } : {},
17960
+ ...isUndo && undoRollbackUuid ? { resumeSessionAt: undoRollbackUuid } : {},
17290
17961
  ...sdkHooks ? { hooks: sdkHooks } : {},
17291
17962
  ...effort ? { effort } : {},
17292
17963
  ...thinking ? { thinking } : {},
@@ -17379,8 +18050,8 @@ function getAdapterTransforms(adapterName) {
17379
18050
  }
17380
18051
 
17381
18052
  // src/proxy/plugins/loader.ts
17382
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
17383
- import { join as join3, isAbsolute as isAbsolute2, extname } from "path";
18053
+ import { readdirSync as readdirSync2, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
18054
+ import { join as join4, isAbsolute as isAbsolute2, extname } from "path";
17384
18055
  import { pathToFileURL } from "url";
17385
18056
 
17386
18057
  // src/proxy/plugins/validation.ts
@@ -17421,10 +18092,10 @@ function validateTransform(exported) {
17421
18092
  // src/proxy/plugins/loader.ts
17422
18093
  var loadCounter = 0;
17423
18094
  function parsePluginConfig(configPath) {
17424
- if (!existsSync4(configPath))
18095
+ if (!existsSync5(configPath))
17425
18096
  return [];
17426
18097
  try {
17427
- const raw2 = readFileSync3(configPath, "utf-8");
18098
+ const raw2 = readFileSync4(configPath, "utf-8");
17428
18099
  const parsed = JSON.parse(raw2);
17429
18100
  return Array.isArray(parsed.plugins) ? parsed.plugins : [];
17430
18101
  } catch {
@@ -17434,7 +18105,7 @@ function parsePluginConfig(configPath) {
17434
18105
  async function loadPlugins(pluginDir, configPath) {
17435
18106
  resetAllPluginStats();
17436
18107
  const config = configPath ? parsePluginConfig(configPath) : [];
17437
- const pluginDirExists = existsSync4(pluginDir);
18108
+ const pluginDirExists = existsSync5(pluginDir);
17438
18109
  let filenames = [];
17439
18110
  if (pluginDirExists) {
17440
18111
  try {
@@ -17465,7 +18136,7 @@ async function loadPlugins(pluginDir, configPath) {
17465
18136
  const loaded = [];
17466
18137
  const seenNames = new Set;
17467
18138
  for (const { filename, entry } of ordered) {
17468
- const filePath = isAbsolute2(filename) ? filename : join3(pluginDir, filename);
18139
+ const filePath = isAbsolute2(filename) ? filename : join4(pluginDir, filename);
17469
18140
  if (entry && !entry.enabled) {
17470
18141
  loaded.push({
17471
18142
  name: filename,
@@ -17838,17 +18509,17 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
17838
18509
  // src/proxy/sessionStore.ts
17839
18510
  import {
17840
18511
  closeSync,
17841
- existsSync as existsSync5,
17842
- mkdirSync,
18512
+ existsSync as existsSync6,
18513
+ mkdirSync as mkdirSync2,
17843
18514
  openSync,
17844
- readFileSync as readFileSync4,
17845
- renameSync,
18515
+ readFileSync as readFileSync5,
18516
+ renameSync as renameSync2,
17846
18517
  statSync,
17847
18518
  unlinkSync,
17848
- writeFileSync
18519
+ writeFileSync as writeFileSync2
17849
18520
  } from "node:fs";
17850
- import { homedir as homedir3 } from "node:os";
17851
- import { join as join4 } from "node:path";
18521
+ import { homedir as homedir4 } from "node:os";
18522
+ import { join as join5 } from "node:path";
17852
18523
  var DEFAULT_MAX_STORED_SESSIONS = 1e4;
17853
18524
  var STALE_LOCK_THRESHOLD_MS = 30000;
17854
18525
  function getMaxStoredSessions() {
@@ -17896,17 +18567,17 @@ var sessionDirOverride = null;
17896
18567
  var skipLocking = false;
17897
18568
  function getStorePath() {
17898
18569
  const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
17899
- if (!existsSync5(dir)) {
17900
- mkdirSync(dir, { recursive: true });
18570
+ if (!existsSync6(dir)) {
18571
+ mkdirSync2(dir, { recursive: true });
17901
18572
  }
17902
- return join4(dir, "sessions.json");
18573
+ return join5(dir, "sessions.json");
17903
18574
  }
17904
18575
  function getDefaultCacheDir() {
17905
- const newDir = join4(homedir3(), ".cache", "meridian");
17906
- const oldDir = join4(homedir3(), ".cache", "opencode-claude-max-proxy");
17907
- if (existsSync5(newDir))
18576
+ const newDir = join5(homedir4(), ".cache", "meridian");
18577
+ const oldDir = join5(homedir4(), ".cache", "opencode-claude-max-proxy");
18578
+ if (existsSync6(newDir))
17908
18579
  return newDir;
17909
- if (existsSync5(oldDir)) {
18580
+ if (existsSync6(oldDir)) {
17910
18581
  try {
17911
18582
  const { symlinkSync } = __require("fs");
17912
18583
  symlinkSync(oldDir, newDir);
@@ -17919,10 +18590,10 @@ function getDefaultCacheDir() {
17919
18590
  }
17920
18591
  function readStore() {
17921
18592
  const path3 = getStorePath();
17922
- if (!existsSync5(path3))
18593
+ if (!existsSync6(path3))
17923
18594
  return {};
17924
18595
  try {
17925
- const data = readFileSync4(path3, "utf-8");
18596
+ const data = readFileSync5(path3, "utf-8");
17926
18597
  return JSON.parse(data);
17927
18598
  } catch (e) {
17928
18599
  console.error("[sessionStore] read failed:", e.message);
@@ -17933,12 +18604,12 @@ function writeStore(store) {
17933
18604
  const path3 = getStorePath();
17934
18605
  const tmp = `${path3}.tmp`;
17935
18606
  try {
17936
- writeFileSync(tmp, JSON.stringify(store, null, 2));
17937
- renameSync(tmp, path3);
18607
+ writeFileSync2(tmp, JSON.stringify(store, null, 2));
18608
+ renameSync2(tmp, path3);
17938
18609
  } catch (e) {
17939
18610
  console.error("[sessionStore] write failed:", e.message);
17940
18611
  try {
17941
- writeFileSync(path3, JSON.stringify(store, null, 2));
18612
+ writeFileSync2(path3, JSON.stringify(store, null, 2));
17942
18613
  } catch (directWriteError) {
17943
18614
  console.error("[sessionStore] write failed:", directWriteError.message);
17944
18615
  }
@@ -18044,7 +18715,7 @@ function listStoredSessions() {
18044
18715
  function clearSharedSessions() {
18045
18716
  const path3 = getStorePath();
18046
18717
  try {
18047
- writeFileSync(path3, "{}");
18718
+ writeFileSync2(path3, "{}");
18048
18719
  } catch (e) {
18049
18720
  console.error("[sessionStore] clear failed:", e.message);
18050
18721
  }
@@ -18384,15 +19055,13 @@ function buildFreshPrompt(messages, sanitizeOpts = {}) {
18384
19055
  yield msg;
18385
19056
  }();
18386
19057
  }
18387
- return messages.map((m) => {
19058
+ return frameReplayTurns(messages.map((m) => {
18388
19059
  if (m.role === "assistant") {
18389
19060
  const assistantText = flattenAssistantContent(m.content);
18390
- return assistantText ? `[Assistant: ${assistantText}]` : "";
19061
+ return { role: "assistant", text: assistantText ? `[Assistant: ${assistantText}]` : "" };
18391
19062
  }
18392
- return flattenUserContent(m.content, sanitizeOpts, toolIndex);
18393
- }).filter(Boolean).join(`
18394
-
18395
- `) || "";
19063
+ return { role: "user", text: flattenUserContent(m.content, sanitizeOpts, toolIndex) };
19064
+ }));
18396
19065
  }
18397
19066
  var proxyLogSilent = false;
18398
19067
  function plog(message) {
@@ -18455,6 +19124,8 @@ function createProxyServer(config = {}) {
18455
19124
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
18456
19125
  const PENDING_STORE_WAIT_MS = 3000;
18457
19126
  const PENDING_STORE_AUTO_RESOLVE_MS = 1e4;
19127
+ const BUSY_SESSION_MAX_RETRIES = 3;
19128
+ const BUSY_SESSION_RETRY_DELAY_MS = parseInt(process.env.MERIDIAN_BUSY_RETRY_DELAY_MS ?? "500", 10);
18458
19129
  const pendingSessionStores = new Map;
18459
19130
  const registerPendingStore = (key) => {
18460
19131
  let resolveFn = () => {};
@@ -18473,8 +19144,8 @@ function createProxyServer(config = {}) {
18473
19144
  pendingSessionStores.delete(key);
18474
19145
  };
18475
19146
  };
18476
- const pluginDir = finalConfig.pluginDir ?? join6(homedir5(), ".config", "meridian", "plugins");
18477
- const pluginConfigPath = finalConfig.pluginConfigPath ?? join6(homedir5(), ".config", "meridian", "plugins.json");
19147
+ const pluginDir = finalConfig.pluginDir ?? join7(homedir6(), ".config", "meridian", "plugins");
19148
+ const pluginConfigPath = finalConfig.pluginConfigPath ?? join7(homedir6(), ".config", "meridian", "plugins.json");
18478
19149
  let loadedPlugins = [];
18479
19150
  let pluginTransforms = [];
18480
19151
  const app = new Hono2;
@@ -18563,7 +19234,7 @@ function createProxyServer(config = {}) {
18563
19234
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
18564
19235
  const requestedModel = typeof body.model === "string" ? body.model : "sonnet";
18565
19236
  let model = mapModelToClaudeModel(requestedModel, authStatus?.subscriptionType, agentMode);
18566
- const envOverrides = requestedModel.startsWith("claude-opus-") ? { ANTHROPIC_DEFAULT_OPUS_MODEL: requestedModel } : requestedModel.startsWith("claude-fable-") ? { ANTHROPIC_DEFAULT_FABLE_MODEL: requestedModel } : requestedModel.startsWith("claude-mythos-") ? { ANTHROPIC_DEFAULT_FABLE_MODEL: requestedModel } : requestedModel.startsWith("claude-sonnet-") ? { ANTHROPIC_DEFAULT_SONNET_MODEL: requestedModel } : requestedModel.startsWith("claude-haiku-") ? { ANTHROPIC_DEFAULT_HAIKU_MODEL: requestedModel } : undefined;
19237
+ const envOverrides = explicitModelPin(requestedModel);
18567
19238
  const cwdResolution = resolveSdkWorkingDirectory({
18568
19239
  envOverride: process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR,
18569
19240
  adapterCwd: adapter.extractWorkingDirectory(body),
@@ -18772,17 +19443,18 @@ function createProxyServer(config = {}) {
18772
19443
  }
18773
19444
  } else {
18774
19445
  const toolIndex = buildToolUseIndex(allMessages ?? messagesToConvert ?? []);
18775
- textPrompt = messagesToConvert?.map((m) => {
19446
+ const promptTurns = (messagesToConvert ?? []).map((m) => {
18776
19447
  if (m.role === "assistant") {
18777
19448
  if (isResume)
18778
- return "";
19449
+ return { role: "assistant", text: "" };
18779
19450
  const assistantText = flattenAssistantContent(m.content);
18780
- return assistantText ? `[Assistant: ${assistantText}]` : "";
19451
+ return { role: "assistant", text: assistantText ? `[Assistant: ${assistantText}]` : "" };
18781
19452
  }
18782
- return flattenUserContent(m.content, sanitizeOpts, toolIndex);
18783
- }).filter(Boolean).join(`
19453
+ return { role: "user", text: flattenUserContent(m.content, sanitizeOpts, toolIndex) };
19454
+ });
19455
+ textPrompt = isResume ? promptTurns.map((t) => t.text).filter(Boolean).join(`
18784
19456
 
18785
- `) || "";
19457
+ `) || "" : frameReplayTurns(promptTurns);
18786
19458
  }
18787
19459
  const passthrough = adapter.instancePassthrough !== undefined ? adapter.instancePassthrough : pipelineCtx.passthrough !== undefined ? pipelineCtx.passthrough : envBool("PASSTHROUGH");
18788
19460
  const settingSources = envBool("LOAD_CONTEXT") || sdkFeatures.claudeMd === "full" ? ["user", "project"] : sdkFeatures.claudeMd === "project" ? ["project"] : pipelineCtx.settingSources ?? [];
@@ -18908,7 +19580,7 @@ function createProxyServer(config = {}) {
18908
19580
  input: toolInput
18909
19581
  });
18910
19582
  }
18911
- if (stream2 && earlyStopEnabled && turnGenerating && !requestAbort.controller.signal.aborted) {
19583
+ if (earlyStopEnabled && turnGenerating && !requestAbort.controller.signal.aborted) {
18912
19584
  await holdDenyUntilTurnEnd();
18913
19585
  }
18914
19586
  if (isExactDuplicate) {
@@ -18965,8 +19637,12 @@ function createProxyServer(config = {}) {
18965
19637
  }
18966
19638
  let tokenRefreshed = false;
18967
19639
  let didFreshBaseRetry = false;
19640
+ let busySessionRetries = 0;
19641
+ let busySessionFork = false;
18968
19642
  while (true) {
18969
19643
  let didYieldContent = false;
19644
+ const attemptStderrStart = stderrLines.length;
19645
+ turnGenerating = true;
18970
19646
  try {
18971
19647
  for await (const event of query(buildQueryOptions({
18972
19648
  prompt: makePrompt(),
@@ -18985,6 +19661,7 @@ function createProxyServer(config = {}) {
18985
19661
  resumeSessionId,
18986
19662
  isUndo,
18987
19663
  undoRollbackUuid,
19664
+ forkSession: busySessionFork || undefined,
18988
19665
  sdkHooks,
18989
19666
  blockedTools: pipelineCtx.blockedTools,
18990
19667
  incompatibleTools: pipelineCtx.incompatibleTools,
@@ -19019,8 +19696,25 @@ function createProxyServer(config = {}) {
19019
19696
  return;
19020
19697
  } catch (error) {
19021
19698
  const errMsg = error instanceof Error ? error.message : String(error);
19699
+ releaseHeldDenies("non_stream_attempt_error");
19022
19700
  if (didYieldContent)
19023
19701
  throw error;
19702
+ if (resumeSessionId && isBusySessionError(error, stderrLines.slice(attemptStderrStart).join(`
19703
+ `))) {
19704
+ if (busySessionRetries < BUSY_SESSION_MAX_RETRIES) {
19705
+ busySessionRetries++;
19706
+ claudeLog("session.busy_retry", { mode: "non_stream", attempt: busySessionRetries, resumeSessionId });
19707
+ plog(`[PROXY] ${requestMeta.requestId} session busy (bg agent), retrying resume ${busySessionRetries}/${BUSY_SESSION_MAX_RETRIES}`);
19708
+ await new Promise((resolve3) => setTimeout(resolve3, BUSY_SESSION_RETRY_DELAY_MS * busySessionRetries));
19709
+ continue;
19710
+ }
19711
+ if (!busySessionFork) {
19712
+ busySessionFork = true;
19713
+ claudeLog("session.busy_fork", { mode: "non_stream", resumeSessionId });
19714
+ plog(`[PROXY] ${requestMeta.requestId} session still busy after ${BUSY_SESSION_MAX_RETRIES} retries — forking session`);
19715
+ continue;
19716
+ }
19717
+ }
19024
19718
  if (isStaleSessionError(error)) {
19025
19719
  claudeLog("session.stale_uuid_retry", {
19026
19720
  mode: "non_stream",
@@ -19204,6 +19898,7 @@ function createProxyServer(config = {}) {
19204
19898
  }
19205
19899
  }
19206
19900
  if (message.type === "assistant") {
19901
+ releaseHeldDenies("assistant_message");
19207
19902
  assistantMessages += 1;
19208
19903
  if (message.uuid) {
19209
19904
  sdkUuidMap.push(message.uuid);
@@ -19264,6 +19959,7 @@ function createProxyServer(config = {}) {
19264
19959
  }
19265
19960
  }
19266
19961
  }
19962
+ releaseHeldDenies("non_stream_loop_exit");
19267
19963
  claudeLog("upstream.completed", {
19268
19964
  mode: "non_stream",
19269
19965
  model,
@@ -19283,6 +19979,7 @@ function createProxyServer(config = {}) {
19283
19979
  plog(`[PROXY] ${requestMeta.requestId} discovered=${discoveredTools.size} (${newNames}) session_total=${allNames.length}`);
19284
19980
  }
19285
19981
  } catch (error) {
19982
+ releaseHeldDenies("non_stream_error");
19286
19983
  const stderrOutput = stderrLines.join(`
19287
19984
  `).trim();
19288
19985
  if (stderrOutput && error instanceof Error && !error.message.includes(stderrOutput)) {
@@ -19391,6 +20088,7 @@ Subprocess stderr: ${stderrOutput}`;
19391
20088
  requestId: requestMeta.requestId,
19392
20089
  timestamp: Date.now(),
19393
20090
  adapter: adapter.name,
20091
+ profileId: profile.id,
19394
20092
  requestSource,
19395
20093
  model,
19396
20094
  requestModel: body.model || undefined,
@@ -19522,8 +20220,11 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
19522
20220
  }
19523
20221
  let tokenRefreshed = false;
19524
20222
  let didFreshBaseRetry = false;
20223
+ let busySessionRetries = 0;
20224
+ let busySessionFork = false;
19525
20225
  while (true) {
19526
20226
  let didYieldClientEvent = false;
20227
+ const attemptStderrStart = stderrLines.length;
19527
20228
  try {
19528
20229
  for await (const event of query(buildQueryOptions({
19529
20230
  prompt: makePrompt(),
@@ -19542,6 +20243,7 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
19542
20243
  resumeSessionId,
19543
20244
  isUndo,
19544
20245
  undoRollbackUuid,
20246
+ forkSession: busySessionFork || undefined,
19545
20247
  sdkHooks,
19546
20248
  blockedTools: pipelineCtx.blockedTools,
19547
20249
  incompatibleTools: pipelineCtx.incompatibleTools,
@@ -19578,6 +20280,22 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
19578
20280
  const errMsg = error instanceof Error ? error.message : String(error);
19579
20281
  if (didYieldClientEvent)
19580
20282
  throw error;
20283
+ if (resumeSessionId && isBusySessionError(error, stderrLines.slice(attemptStderrStart).join(`
20284
+ `))) {
20285
+ if (busySessionRetries < BUSY_SESSION_MAX_RETRIES) {
20286
+ busySessionRetries++;
20287
+ claudeLog("session.busy_retry", { mode: "stream", attempt: busySessionRetries, resumeSessionId });
20288
+ plog(`[PROXY] ${requestMeta.requestId} session busy (bg agent), retrying resume ${busySessionRetries}/${BUSY_SESSION_MAX_RETRIES}`);
20289
+ await new Promise((resolve3) => setTimeout(resolve3, BUSY_SESSION_RETRY_DELAY_MS * busySessionRetries));
20290
+ continue;
20291
+ }
20292
+ if (!busySessionFork) {
20293
+ busySessionFork = true;
20294
+ claudeLog("session.busy_fork", { mode: "stream", resumeSessionId });
20295
+ plog(`[PROXY] ${requestMeta.requestId} session still busy after ${BUSY_SESSION_MAX_RETRIES} retries — forking session`);
20296
+ continue;
20297
+ }
20298
+ }
19581
20299
  if (isStaleSessionError(error)) {
19582
20300
  claudeLog("session.stale_uuid_retry", {
19583
20301
  mode: "stream",
@@ -20202,6 +20920,7 @@ data: {"type":"message_stop"}
20202
20920
  requestId: requestMeta.requestId,
20203
20921
  timestamp: Date.now(),
20204
20922
  adapter: adapter.name,
20923
+ profileId: profile.id,
20205
20924
  requestSource,
20206
20925
  model,
20207
20926
  requestModel: body.model || undefined,
@@ -20335,6 +21054,7 @@ data: {"type":"message_stop"}
20335
21054
  requestId: requestMeta.requestId,
20336
21055
  timestamp: Date.now(),
20337
21056
  adapter: adapter.name,
21057
+ profileId: profile.id,
20338
21058
  requestSource,
20339
21059
  model,
20340
21060
  requestModel: body.model || undefined,
@@ -20379,6 +21099,7 @@ data: {"type":"message_stop"}
20379
21099
  requestId: requestMeta.requestId,
20380
21100
  timestamp: Date.now(),
20381
21101
  adapter: adapter.name,
21102
+ profileId: profile.id,
20382
21103
  requestSource,
20383
21104
  model,
20384
21105
  requestModel: body.model || undefined,
@@ -20534,6 +21255,31 @@ data: ${JSON.stringify({
20534
21255
  resetAdapterFeatures2(adapter);
20535
21256
  return c.json({ ok: true });
20536
21257
  });
21258
+ app.get("/settings/api/pricing", (c) => {
21259
+ const { BUILTIN_MODEL_PRICING: BUILTIN_MODEL_PRICING2 } = (init_pricing(), __toCommonJS(exports_pricing));
21260
+ const { getPricingOverrides: getPricingOverrides2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
21261
+ return c.json({ builtin: BUILTIN_MODEL_PRICING2, overrides: getPricingOverrides2() });
21262
+ });
21263
+ app.put("/settings/api/pricing/:model", async (c) => {
21264
+ const { validatePricingUpdate: validatePricingUpdate2, setPricingOverride: setPricingOverride2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
21265
+ const model = c.req.param("model");
21266
+ try {
21267
+ const body = await c.req.json();
21268
+ setPricingOverride2(model, validatePricingUpdate2(body));
21269
+ } catch (e) {
21270
+ return c.json({ error: e.message }, 400);
21271
+ }
21272
+ return c.json({ ok: true });
21273
+ });
21274
+ app.delete("/settings/api/pricing/:model", (c) => {
21275
+ const { deletePricingOverride: deletePricingOverride2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
21276
+ try {
21277
+ deletePricingOverride2(c.req.param("model"));
21278
+ } catch (e) {
21279
+ return c.json({ error: e.message }, 400);
21280
+ }
21281
+ return c.json({ ok: true });
21282
+ });
20537
21283
  app.get("/metrics", (c) => {
20538
21284
  const body = renderPrometheusMetrics(telemetryStore2);
20539
21285
  return c.body(body, 200, {
@@ -20606,7 +21352,7 @@ data: ${JSON.stringify({
20606
21352
  });
20607
21353
  });
20608
21354
  app.get("/profiles", async (c) => {
20609
- const { profilePageHtml } = await import("./profilePage-txxpbc44.js");
21355
+ const { profilePageHtml } = await import("./profilePage-naychnb8.js");
20610
21356
  return c.html(profilePageHtml);
20611
21357
  });
20612
21358
  app.post("/profiles/active", async (c) => {
@@ -20668,7 +21414,7 @@ data: ${JSON.stringify({
20668
21414
  }
20669
21415
  });
20670
21416
  app.get("/plugins", async (c) => {
20671
- const { pluginPageHtml } = await import("./pluginPage-85s6t6k8.js");
21417
+ const { pluginPageHtml } = await import("./pluginPage-03q5j753.js");
20672
21418
  return c.html(pluginPageHtml);
20673
21419
  });
20674
21420
  app.post("/auth/refresh", async (c) => {
@@ -20709,7 +21455,7 @@ data: ${JSON.stringify({
20709
21455
  }
20710
21456
  const completionId = `chatcmpl-${randomUUID()}`;
20711
21457
  const created = Math.floor(Date.now() / 1000);
20712
- const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : "claude-sonnet-4-6";
21458
+ const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : CANONICAL_SONNET_MODEL;
20713
21459
  const { getFeaturesForAdapter: getFeaturesForAdapter2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
20714
21460
  const sdkFeatures = getFeaturesForAdapter2("openai");
20715
21461
  if (!anthropicBody.stream) {