@rynfar/meridian 1.50.0 → 1.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -19
- package/assets/banner.svg +28 -21
- package/assets/bot-icon-512.png +0 -0
- package/assets/bot-icon.svg +49 -43
- package/assets/how-it-works.svg +40 -40
- package/assets/icon-512.png +0 -0
- package/assets/icon.svg +16 -13
- package/assets/logo-512.png +0 -0
- package/assets/logo.svg +14 -12
- package/dist/{cli-yw8cx02m.js → cli-7df5eg5d.js} +828 -164
- package/dist/{cli-q6dz9r5p.js → cli-aybvv9nb.js} +7 -1
- package/dist/cli-xmweegb1.js +179 -0
- package/dist/cli.js +4 -4
- package/dist/{pluginPage-85s6t6k8.js → pluginPage-03q5j753.js} +4 -10
- package/dist/{profileCli-fbhe9p5z.js → profileCli-j01rr99j.js} +1 -1
- package/dist/{profilePage-txxpbc44.js → profilePage-naychnb8.js} +6 -5
- package/dist/proxy/oauthUsage.d.ts.map +1 -1
- package/dist/proxy/plugins/pluginPage.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/server.js +3 -3
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/landing.d.ts +6 -2
- package/dist/telemetry/landing.d.ts.map +1 -1
- package/dist/telemetry/percentiles.d.ts +5 -1
- package/dist/telemetry/percentiles.d.ts.map +1 -1
- package/dist/telemetry/pricing.d.ts +63 -0
- package/dist/telemetry/pricing.d.ts.map +1 -0
- package/dist/telemetry/pricingStore.d.ts +32 -0
- package/dist/telemetry/pricingStore.d.ts.map +1 -0
- package/dist/telemetry/profileBar.d.ts +19 -9
- package/dist/telemetry/profileBar.d.ts.map +1 -1
- package/dist/telemetry/profileUsage.d.ts.map +1 -1
- package/dist/telemetry/settingsPage.d.ts +1 -1
- package/dist/telemetry/settingsPage.d.ts.map +1 -1
- package/dist/telemetry/sqlite.d.ts.map +1 -1
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +31 -0
- package/dist/telemetry/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/cli-4rqtm83g.js +0 -148
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
profileBarHtml,
|
|
21
21
|
profileBarJs,
|
|
22
22
|
themeCss
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-xmweegb1.js";
|
|
24
24
|
import {
|
|
25
25
|
env,
|
|
26
26
|
envBool,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
getClaudeAuthStatusAsync,
|
|
30
30
|
getResolvedClaudeExecutableInfo,
|
|
31
31
|
hasExtendedContext,
|
|
32
|
+
init_env,
|
|
32
33
|
isClosedControllerError,
|
|
33
34
|
mapModelToClaudeModel,
|
|
34
35
|
recordExtendedContextUnavailable,
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
resolvePassthrough,
|
|
37
38
|
resolveSdkModelDefaults,
|
|
38
39
|
stripExtendedContext
|
|
39
|
-
} from "./cli-
|
|
40
|
+
} from "./cli-aybvv9nb.js";
|
|
40
41
|
import {
|
|
41
42
|
getSetting
|
|
42
43
|
} from "./cli-340h1chz.js";
|
|
@@ -61,6 +62,137 @@ import {
|
|
|
61
62
|
__toESM
|
|
62
63
|
} from "./cli-p9swy5t3.js";
|
|
63
64
|
|
|
65
|
+
// src/telemetry/pricing.ts
|
|
66
|
+
var exports_pricing = {};
|
|
67
|
+
__export(exports_pricing, {
|
|
68
|
+
resolveModelPricing: () => resolveModelPricing,
|
|
69
|
+
normalizeModelKey: () => normalizeModelKey,
|
|
70
|
+
estimateRequestCostUsd: () => estimateRequestCostUsd,
|
|
71
|
+
computeCostEstimate: () => computeCostEstimate,
|
|
72
|
+
CACHE_WRITE_MULTIPLIER: () => CACHE_WRITE_MULTIPLIER,
|
|
73
|
+
CACHE_READ_MULTIPLIER: () => CACHE_READ_MULTIPLIER,
|
|
74
|
+
BUILTIN_MODEL_PRICING: () => BUILTIN_MODEL_PRICING
|
|
75
|
+
});
|
|
76
|
+
function normalizeModelKey(model) {
|
|
77
|
+
return model.trim().toLowerCase().replace(/\[1m\]$/, "");
|
|
78
|
+
}
|
|
79
|
+
function rates(inputPerMTok, outputPerMTok) {
|
|
80
|
+
return {
|
|
81
|
+
inputPerMTok,
|
|
82
|
+
outputPerMTok,
|
|
83
|
+
cacheReadPerMTok: inputPerMTok * CACHE_READ_MULTIPLIER,
|
|
84
|
+
cacheWritePerMTok: inputPerMTok * CACHE_WRITE_MULTIPLIER
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function resolveModelPricing(model, overrides) {
|
|
88
|
+
const normalized = normalizeModelKey(model);
|
|
89
|
+
if (overrides) {
|
|
90
|
+
const override = overrides[normalized];
|
|
91
|
+
if (override)
|
|
92
|
+
return override;
|
|
93
|
+
}
|
|
94
|
+
const exact = BUILTIN_MODEL_PRICING[normalized];
|
|
95
|
+
if (exact)
|
|
96
|
+
return exact;
|
|
97
|
+
if (normalized.includes("fable") || normalized.includes("mythos"))
|
|
98
|
+
return FABLE;
|
|
99
|
+
if (normalized.includes("haiku")) {
|
|
100
|
+
if (normalized.includes("3-5") || normalized.includes("3.5"))
|
|
101
|
+
return HAIKU_35;
|
|
102
|
+
if (/haiku-3\b|3-haiku/.test(normalized))
|
|
103
|
+
return HAIKU_3;
|
|
104
|
+
return HAIKU;
|
|
105
|
+
}
|
|
106
|
+
if (normalized.includes("opus")) {
|
|
107
|
+
if (/opus-4-1\b|opus-4-1-|opus-4-0|opus-4-2025|3-opus/.test(normalized))
|
|
108
|
+
return OPUS_LEGACY;
|
|
109
|
+
return OPUS;
|
|
110
|
+
}
|
|
111
|
+
if (normalized.includes("sonnet"))
|
|
112
|
+
return SONNET;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function estimateRequestCostUsd(metric, pricing) {
|
|
116
|
+
return (metric.inputTokens ?? 0) / 1e6 * pricing.inputPerMTok + (metric.outputTokens ?? 0) / 1e6 * pricing.outputPerMTok + (metric.cacheReadInputTokens ?? 0) / 1e6 * pricing.cacheReadPerMTok + (metric.cacheCreationInputTokens ?? 0) / 1e6 * pricing.cacheWritePerMTok;
|
|
117
|
+
}
|
|
118
|
+
function roundUsd(value) {
|
|
119
|
+
return Math.round(value * 1e6) / 1e6;
|
|
120
|
+
}
|
|
121
|
+
function computeCostEstimate(metrics, overrides) {
|
|
122
|
+
const byModel = {};
|
|
123
|
+
const byProfile = {};
|
|
124
|
+
let totalUsd = 0;
|
|
125
|
+
let unpricedRequestCount = 0;
|
|
126
|
+
for (const metric of metrics) {
|
|
127
|
+
const modelKey = metric.requestModel || metric.model;
|
|
128
|
+
const pricing = resolveModelPricing(modelKey, overrides);
|
|
129
|
+
const profileEntry = byProfile[metric.profileId ?? "default"] ??= { requests: 0, estimatedUsd: 0 };
|
|
130
|
+
profileEntry.requests++;
|
|
131
|
+
const entry = byModel[modelKey] ??= {
|
|
132
|
+
requests: 0,
|
|
133
|
+
inputTokens: 0,
|
|
134
|
+
outputTokens: 0,
|
|
135
|
+
cacheReadTokens: 0,
|
|
136
|
+
cacheCreationTokens: 0,
|
|
137
|
+
estimatedUsd: pricing === null ? null : 0
|
|
138
|
+
};
|
|
139
|
+
entry.requests++;
|
|
140
|
+
entry.inputTokens += metric.inputTokens ?? 0;
|
|
141
|
+
entry.outputTokens += metric.outputTokens ?? 0;
|
|
142
|
+
entry.cacheReadTokens += metric.cacheReadInputTokens ?? 0;
|
|
143
|
+
entry.cacheCreationTokens += metric.cacheCreationInputTokens ?? 0;
|
|
144
|
+
if (pricing === null) {
|
|
145
|
+
unpricedRequestCount++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const cost = estimateRequestCostUsd(metric, pricing);
|
|
149
|
+
entry.estimatedUsd = (entry.estimatedUsd ?? 0) + cost;
|
|
150
|
+
profileEntry.estimatedUsd += cost;
|
|
151
|
+
totalUsd += cost;
|
|
152
|
+
}
|
|
153
|
+
for (const entry of Object.values(byProfile)) {
|
|
154
|
+
entry.estimatedUsd = roundUsd(entry.estimatedUsd);
|
|
155
|
+
}
|
|
156
|
+
for (const entry of Object.values(byModel)) {
|
|
157
|
+
if (entry.estimatedUsd !== null)
|
|
158
|
+
entry.estimatedUsd = roundUsd(entry.estimatedUsd);
|
|
159
|
+
}
|
|
160
|
+
return { totalUsd: roundUsd(totalUsd), byModel, unpricedRequestCount, byProfile };
|
|
161
|
+
}
|
|
162
|
+
var CACHE_READ_MULTIPLIER = 0.1, CACHE_WRITE_MULTIPLIER = 1.25, FABLE, OPUS, OPUS_LEGACY, SONNET, SONNET_5_INTRO, HAIKU, HAIKU_35, HAIKU_3, BUILTIN_MODEL_PRICING;
|
|
163
|
+
var init_pricing = __esm(() => {
|
|
164
|
+
FABLE = rates(10, 50);
|
|
165
|
+
OPUS = rates(5, 25);
|
|
166
|
+
OPUS_LEGACY = rates(15, 75);
|
|
167
|
+
SONNET = rates(3, 15);
|
|
168
|
+
SONNET_5_INTRO = rates(2, 10);
|
|
169
|
+
HAIKU = rates(1, 5);
|
|
170
|
+
HAIKU_35 = rates(0.8, 4);
|
|
171
|
+
HAIKU_3 = rates(0.25, 1.25);
|
|
172
|
+
BUILTIN_MODEL_PRICING = {
|
|
173
|
+
fable: FABLE,
|
|
174
|
+
"claude-fable-5": FABLE,
|
|
175
|
+
"claude-mythos-5": FABLE,
|
|
176
|
+
opus: OPUS,
|
|
177
|
+
"claude-opus-4-8": OPUS,
|
|
178
|
+
"claude-opus-4-7": OPUS,
|
|
179
|
+
"claude-opus-4-6": OPUS,
|
|
180
|
+
"claude-opus-4-5": OPUS,
|
|
181
|
+
"claude-opus-4-1": OPUS_LEGACY,
|
|
182
|
+
"claude-opus-4-0": OPUS_LEGACY,
|
|
183
|
+
"claude-opus-4-20250514": OPUS_LEGACY,
|
|
184
|
+
"claude-3-opus-20240229": OPUS_LEGACY,
|
|
185
|
+
sonnet: SONNET,
|
|
186
|
+
"claude-sonnet-5": SONNET_5_INTRO,
|
|
187
|
+
"claude-sonnet-4-6": SONNET,
|
|
188
|
+
"claude-sonnet-4-5": SONNET,
|
|
189
|
+
haiku: HAIKU,
|
|
190
|
+
"claude-haiku-4-5": HAIKU,
|
|
191
|
+
"claude-3-5-haiku-20241022": HAIKU_35,
|
|
192
|
+
"claude-3-haiku-20240307": HAIKU_3
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
|
|
64
196
|
// src/telemetry/percentiles.ts
|
|
65
197
|
function computePercentiles(values) {
|
|
66
198
|
if (values.length === 0)
|
|
@@ -76,7 +208,7 @@ function computePercentiles(values) {
|
|
|
76
208
|
avg: Math.round(sum / sorted.length)
|
|
77
209
|
};
|
|
78
210
|
}
|
|
79
|
-
function computeSummary(metrics, windowMs) {
|
|
211
|
+
function computeSummary(metrics, windowMs, pricingOverrides) {
|
|
80
212
|
if (metrics.length === 0) {
|
|
81
213
|
const emptyPhase = { p50: 0, p95: 0, p99: 0, min: 0, max: 0, avg: 0 };
|
|
82
214
|
return {
|
|
@@ -99,7 +231,8 @@ function computeSummary(metrics, windowMs) {
|
|
|
99
231
|
totalCacheCreationTokens: 0,
|
|
100
232
|
avgCacheHitRate: 0,
|
|
101
233
|
cacheMissOnResumeCount: 0
|
|
102
|
-
}
|
|
234
|
+
},
|
|
235
|
+
costEstimate: { totalUsd: 0, byModel: {}, unpricedRequestCount: 0, byProfile: {} }
|
|
103
236
|
};
|
|
104
237
|
}
|
|
105
238
|
const errorCount = metrics.filter((m) => m.error !== null).length;
|
|
@@ -166,10 +299,132 @@ function computeSummary(metrics, windowMs) {
|
|
|
166
299
|
totalCacheCreationTokens,
|
|
167
300
|
avgCacheHitRate: cacheHitRateCount > 0 ? Math.round(cacheHitRateSum / cacheHitRateCount * 100) / 100 : 0,
|
|
168
301
|
cacheMissOnResumeCount
|
|
302
|
+
},
|
|
303
|
+
costEstimate: computeCostEstimate(metrics, pricingOverrides)
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
var init_percentiles = __esm(() => {
|
|
307
|
+
init_pricing();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// src/telemetry/pricingStore.ts
|
|
311
|
+
var exports_pricingStore = {};
|
|
312
|
+
__export(exports_pricingStore, {
|
|
313
|
+
validatePricingUpdate: () => validatePricingUpdate,
|
|
314
|
+
validateModelKey: () => validateModelKey,
|
|
315
|
+
setPricingOverride: () => setPricingOverride,
|
|
316
|
+
resetPricingOverridesCache: () => resetPricingOverridesCache,
|
|
317
|
+
getPricingOverrides: () => getPricingOverrides,
|
|
318
|
+
deletePricingOverride: () => deletePricingOverride
|
|
319
|
+
});
|
|
320
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
321
|
+
import { join, dirname } from "node:path";
|
|
322
|
+
import { homedir } from "node:os";
|
|
323
|
+
function getConfigPath() {
|
|
324
|
+
const explicit = env("PRICING_CONFIG");
|
|
325
|
+
if (explicit)
|
|
326
|
+
return explicit;
|
|
327
|
+
const dir = join(homedir(), ".config", "meridian");
|
|
328
|
+
return join(dir, "model-pricing.json");
|
|
329
|
+
}
|
|
330
|
+
function readOverrides() {
|
|
331
|
+
const path = getConfigPath();
|
|
332
|
+
const now = Date.now();
|
|
333
|
+
if (cachedOverrides && cachedPath === path && now - lastReadTime < CACHE_TTL_MS) {
|
|
334
|
+
return cachedOverrides;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
if (existsSync2(path)) {
|
|
338
|
+
const raw2 = JSON.parse(readFileSync(path, "utf-8"));
|
|
339
|
+
const result = {};
|
|
340
|
+
for (const [model, value] of Object.entries(raw2)) {
|
|
341
|
+
try {
|
|
342
|
+
result[normalizeModelKey(model)] = validatePricingUpdate(value);
|
|
343
|
+
} catch {}
|
|
344
|
+
}
|
|
345
|
+
cachedOverrides = result;
|
|
346
|
+
} else {
|
|
347
|
+
cachedOverrides = {};
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
cachedOverrides = {};
|
|
351
|
+
}
|
|
352
|
+
cachedPath = path;
|
|
353
|
+
lastReadTime = now;
|
|
354
|
+
return cachedOverrides;
|
|
355
|
+
}
|
|
356
|
+
function writeOverrides(overrides) {
|
|
357
|
+
const path = getConfigPath();
|
|
358
|
+
const tmp = `${path}.tmp`;
|
|
359
|
+
try {
|
|
360
|
+
const dir = dirname(path);
|
|
361
|
+
if (!existsSync2(dir))
|
|
362
|
+
mkdirSync(dir, { recursive: true });
|
|
363
|
+
writeFileSync(tmp, JSON.stringify(overrides, null, 2));
|
|
364
|
+
renameSync(tmp, path);
|
|
365
|
+
cachedOverrides = overrides;
|
|
366
|
+
cachedPath = path;
|
|
367
|
+
lastReadTime = Date.now();
|
|
368
|
+
} catch (e) {
|
|
369
|
+
console.error("[pricing] write failed:", e.message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function getPricingOverrides() {
|
|
373
|
+
return readOverrides();
|
|
374
|
+
}
|
|
375
|
+
function validatePricingUpdate(raw2) {
|
|
376
|
+
if (raw2 === null || typeof raw2 !== "object" || Array.isArray(raw2)) {
|
|
377
|
+
throw new Error("body must be a JSON object");
|
|
378
|
+
}
|
|
379
|
+
const input = raw2;
|
|
380
|
+
const readRate = (key, required) => {
|
|
381
|
+
const value = input[key];
|
|
382
|
+
if (value === undefined || value === null || value === "") {
|
|
383
|
+
if (required)
|
|
384
|
+
throw new Error(`${key} is required`);
|
|
385
|
+
return;
|
|
169
386
|
}
|
|
387
|
+
if (typeof value !== "number" || !isFinite(value) || value < 0) {
|
|
388
|
+
throw new Error(`${key} must be a non-negative finite number`);
|
|
389
|
+
}
|
|
390
|
+
return value;
|
|
170
391
|
};
|
|
392
|
+
const inputPerMTok = readRate("inputPerMTok", true);
|
|
393
|
+
const outputPerMTok = readRate("outputPerMTok", true);
|
|
394
|
+
const cacheReadPerMTok = readRate("cacheReadPerMTok", false) ?? inputPerMTok * CACHE_READ_MULTIPLIER;
|
|
395
|
+
const cacheWritePerMTok = readRate("cacheWritePerMTok", false) ?? inputPerMTok * CACHE_WRITE_MULTIPLIER;
|
|
396
|
+
return { inputPerMTok, outputPerMTok, cacheReadPerMTok, cacheWritePerMTok };
|
|
397
|
+
}
|
|
398
|
+
function validateModelKey(model) {
|
|
399
|
+
const normalized = normalizeModelKey(model);
|
|
400
|
+
if (normalized.length === 0)
|
|
401
|
+
throw new Error("model must be a non-empty string");
|
|
402
|
+
if (normalized.length > MAX_MODEL_KEY_LENGTH) {
|
|
403
|
+
throw new Error(`model must be at most ${MAX_MODEL_KEY_LENGTH} characters`);
|
|
404
|
+
}
|
|
405
|
+
return normalized;
|
|
171
406
|
}
|
|
172
|
-
|
|
407
|
+
function setPricingOverride(model, pricing) {
|
|
408
|
+
const key = validateModelKey(model);
|
|
409
|
+
const overrides = { ...readOverrides(), [key]: pricing };
|
|
410
|
+
writeOverrides(overrides);
|
|
411
|
+
}
|
|
412
|
+
function deletePricingOverride(model) {
|
|
413
|
+
const key = validateModelKey(model);
|
|
414
|
+
const overrides = { ...readOverrides() };
|
|
415
|
+
delete overrides[key];
|
|
416
|
+
writeOverrides(overrides);
|
|
417
|
+
}
|
|
418
|
+
function resetPricingOverridesCache() {
|
|
419
|
+
cachedOverrides = null;
|
|
420
|
+
cachedPath = null;
|
|
421
|
+
lastReadTime = 0;
|
|
422
|
+
}
|
|
423
|
+
var MAX_MODEL_KEY_LENGTH = 200, cachedOverrides = null, cachedPath = null, lastReadTime = 0, CACHE_TTL_MS = 5000;
|
|
424
|
+
var init_pricingStore = __esm(() => {
|
|
425
|
+
init_env();
|
|
426
|
+
init_pricing();
|
|
427
|
+
});
|
|
173
428
|
|
|
174
429
|
// node_modules/@neon-rs/load/dist/index.js
|
|
175
430
|
var require_dist = __commonJS((exports) => {
|
|
@@ -272,8 +527,8 @@ var require_dist = __commonJS((exports) => {
|
|
|
272
527
|
const header = report.header;
|
|
273
528
|
return typeof header === "object" && !!header && "glibcVersionRuntime" in header;
|
|
274
529
|
}
|
|
275
|
-
function load(
|
|
276
|
-
const m = path.join(
|
|
530
|
+
function load(dirname3) {
|
|
531
|
+
const m = path.join(dirname3, "index.node");
|
|
277
532
|
return fs.existsSync(m) ? __require(m) : null;
|
|
278
533
|
}
|
|
279
534
|
exports.load = load;
|
|
@@ -296,7 +551,7 @@ var require_process = __commonJS((exports, module) => {
|
|
|
296
551
|
var require_filesystem = __commonJS((exports, module) => {
|
|
297
552
|
var fs = __require("fs");
|
|
298
553
|
var LDD_PATH = "/usr/bin/ldd";
|
|
299
|
-
var
|
|
554
|
+
var readFileSync3 = (path) => fs.readFileSync(path, "utf-8");
|
|
300
555
|
var readFile = (path) => new Promise((resolve2, reject) => {
|
|
301
556
|
fs.readFile(path, "utf-8", (err, data) => {
|
|
302
557
|
if (err) {
|
|
@@ -308,7 +563,7 @@ var require_filesystem = __commonJS((exports, module) => {
|
|
|
308
563
|
});
|
|
309
564
|
module.exports = {
|
|
310
565
|
LDD_PATH,
|
|
311
|
-
readFileSync:
|
|
566
|
+
readFileSync: readFileSync3,
|
|
312
567
|
readFile
|
|
313
568
|
};
|
|
314
569
|
});
|
|
@@ -317,7 +572,7 @@ var require_filesystem = __commonJS((exports, module) => {
|
|
|
317
572
|
var require_detect_libc = __commonJS((exports, module) => {
|
|
318
573
|
var childProcess = __require("child_process");
|
|
319
574
|
var { isLinux, getReport } = require_process();
|
|
320
|
-
var { LDD_PATH, readFile, readFileSync:
|
|
575
|
+
var { LDD_PATH, readFile, readFileSync: readFileSync3 } = require_filesystem();
|
|
321
576
|
var cachedFamilyFilesystem;
|
|
322
577
|
var cachedVersionFilesystem;
|
|
323
578
|
var command = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
|
|
@@ -397,7 +652,7 @@ var require_detect_libc = __commonJS((exports, module) => {
|
|
|
397
652
|
}
|
|
398
653
|
cachedFamilyFilesystem = null;
|
|
399
654
|
try {
|
|
400
|
-
const lddContent =
|
|
655
|
+
const lddContent = readFileSync3(LDD_PATH);
|
|
401
656
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
|
402
657
|
} catch (e) {}
|
|
403
658
|
return cachedFamilyFilesystem;
|
|
@@ -452,7 +707,7 @@ var require_detect_libc = __commonJS((exports, module) => {
|
|
|
452
707
|
}
|
|
453
708
|
cachedVersionFilesystem = null;
|
|
454
709
|
try {
|
|
455
|
-
const lddContent =
|
|
710
|
+
const lddContent = readFileSync3(LDD_PATH);
|
|
456
711
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
|
457
712
|
if (versionMatch) {
|
|
458
713
|
cachedVersionFilesystem = versionMatch[1];
|
|
@@ -910,7 +1165,7 @@ class SqliteTelemetryStore {
|
|
|
910
1165
|
status, queue_wait_ms, proxy_overhead_ms, ttfb_ms,
|
|
911
1166
|
upstream_duration_ms, total_duration_ms, content_blocks, text_events, error,
|
|
912
1167
|
input_tokens, output_tokens, cache_read_input_tokens,
|
|
913
|
-
cache_creation_input_tokens, cache_hit_rate
|
|
1168
|
+
cache_creation_input_tokens, cache_hit_rate, profile_id, envelope_violations
|
|
914
1169
|
) VALUES (
|
|
915
1170
|
@requestId, @timestamp, @adapter, @requestSource, @model, @requestModel, @mode,
|
|
916
1171
|
@isResume, @isPassthrough, @lineageType,
|
|
@@ -919,7 +1174,7 @@ class SqliteTelemetryStore {
|
|
|
919
1174
|
@status, @queueWaitMs, @proxyOverheadMs, @ttfbMs,
|
|
920
1175
|
@upstreamDurationMs, @totalDurationMs, @contentBlocks, @textEvents, @error,
|
|
921
1176
|
@inputTokens, @outputTokens, @cacheReadInputTokens,
|
|
922
|
-
@cacheCreationInputTokens, @cacheHitRate
|
|
1177
|
+
@cacheCreationInputTokens, @cacheHitRate, @profileId, @envelopeViolations
|
|
923
1178
|
)
|
|
924
1179
|
`);
|
|
925
1180
|
this.countStmt = db.prepare("SELECT COUNT(*) as cnt FROM metrics");
|
|
@@ -957,7 +1212,9 @@ class SqliteTelemetryStore {
|
|
|
957
1212
|
outputTokens: metric.outputTokens ?? null,
|
|
958
1213
|
cacheReadInputTokens: metric.cacheReadInputTokens ?? null,
|
|
959
1214
|
cacheCreationInputTokens: metric.cacheCreationInputTokens ?? null,
|
|
960
|
-
cacheHitRate: metric.cacheHitRate ?? null
|
|
1215
|
+
cacheHitRate: metric.cacheHitRate ?? null,
|
|
1216
|
+
profileId: metric.profileId ?? null,
|
|
1217
|
+
envelopeViolations: metric.envelopeViolations && metric.envelopeViolations.length > 0 ? JSON.stringify(metric.envelopeViolations) : null
|
|
961
1218
|
});
|
|
962
1219
|
} catch (err) {
|
|
963
1220
|
console.error("[telemetry] SQLite write failed, skipping:", err);
|
|
@@ -1006,7 +1263,7 @@ class SqliteTelemetryStore {
|
|
|
1006
1263
|
summarize(windowMs = 60 * 60 * 1000) {
|
|
1007
1264
|
const since = Date.now() - windowMs;
|
|
1008
1265
|
const metrics = this.getRecent({ limit: 1e5, since });
|
|
1009
|
-
return computeSummary(metrics, windowMs);
|
|
1266
|
+
return computeSummary(metrics, windowMs, getPricingOverrides());
|
|
1010
1267
|
}
|
|
1011
1268
|
clear() {
|
|
1012
1269
|
try {
|
|
@@ -1090,6 +1347,16 @@ class SqliteDiagnosticLogStore {
|
|
|
1090
1347
|
} catch {}
|
|
1091
1348
|
}
|
|
1092
1349
|
}
|
|
1350
|
+
function parseEnvelopeViolations(raw2) {
|
|
1351
|
+
if (typeof raw2 !== "string" || raw2.length === 0)
|
|
1352
|
+
return;
|
|
1353
|
+
try {
|
|
1354
|
+
const parsed = JSON.parse(raw2);
|
|
1355
|
+
return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string") : undefined;
|
|
1356
|
+
} catch {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1093
1360
|
function rowToMetric(r) {
|
|
1094
1361
|
return {
|
|
1095
1362
|
requestId: r.request_id,
|
|
@@ -1122,7 +1389,9 @@ function rowToMetric(r) {
|
|
|
1122
1389
|
outputTokens: r.output_tokens ?? undefined,
|
|
1123
1390
|
cacheReadInputTokens: r.cache_read_input_tokens ?? undefined,
|
|
1124
1391
|
cacheCreationInputTokens: r.cache_creation_input_tokens ?? undefined,
|
|
1125
|
-
cacheHitRate: r.cache_hit_rate ?? undefined
|
|
1392
|
+
cacheHitRate: r.cache_hit_rate ?? undefined,
|
|
1393
|
+
profileId: r.profile_id ?? undefined,
|
|
1394
|
+
envelopeViolations: parseEnvelopeViolations(r.envelope_violations)
|
|
1126
1395
|
};
|
|
1127
1396
|
}
|
|
1128
1397
|
function createSqliteStores(dbPath, retentionDays) {
|
|
@@ -1170,7 +1439,9 @@ CREATE TABLE IF NOT EXISTS metrics (
|
|
|
1170
1439
|
output_tokens INTEGER,
|
|
1171
1440
|
cache_read_input_tokens INTEGER,
|
|
1172
1441
|
cache_creation_input_tokens INTEGER,
|
|
1173
|
-
cache_hit_rate REAL
|
|
1442
|
+
cache_hit_rate REAL,
|
|
1443
|
+
profile_id TEXT,
|
|
1444
|
+
envelope_violations TEXT
|
|
1174
1445
|
);
|
|
1175
1446
|
CREATE INDEX IF NOT EXISTS idx_metrics_ts ON metrics(timestamp);
|
|
1176
1447
|
CREATE INDEX IF NOT EXISTS idx_metrics_model ON metrics(model);
|
|
@@ -1189,9 +1460,12 @@ CREATE INDEX IF NOT EXISTS idx_logs_cat ON diagnostic_logs(category);
|
|
|
1189
1460
|
`, CLEANUP_INTERVAL = 1000;
|
|
1190
1461
|
var init_sqlite = __esm(() => {
|
|
1191
1462
|
init_percentiles();
|
|
1463
|
+
init_pricingStore();
|
|
1192
1464
|
import_libsql = __toESM(require_libsql(), 1);
|
|
1193
1465
|
METRICS_MIGRATIONS = [
|
|
1194
|
-
"ALTER TABLE metrics ADD COLUMN request_source TEXT"
|
|
1466
|
+
"ALTER TABLE metrics ADD COLUMN request_source TEXT",
|
|
1467
|
+
"ALTER TABLE metrics ADD COLUMN profile_id TEXT",
|
|
1468
|
+
"ALTER TABLE metrics ADD COLUMN envelope_violations TEXT"
|
|
1195
1469
|
];
|
|
1196
1470
|
});
|
|
1197
1471
|
|
|
@@ -1205,40 +1479,40 @@ __export(exports_sdkFeatures, {
|
|
|
1205
1479
|
getExplicitThinking: () => getExplicitThinking,
|
|
1206
1480
|
getAllFeatureConfigs: () => getAllFeatureConfigs
|
|
1207
1481
|
});
|
|
1208
|
-
import { existsSync as
|
|
1209
|
-
import { join as
|
|
1210
|
-
import { homedir as
|
|
1211
|
-
function
|
|
1212
|
-
const dir =
|
|
1213
|
-
if (!
|
|
1214
|
-
|
|
1215
|
-
return
|
|
1482
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3 } from "node:fs";
|
|
1483
|
+
import { join as join6 } from "node:path";
|
|
1484
|
+
import { homedir as homedir5 } from "node:os";
|
|
1485
|
+
function getConfigPath2() {
|
|
1486
|
+
const dir = join6(homedir5(), ".config", "meridian");
|
|
1487
|
+
if (!existsSync7(dir))
|
|
1488
|
+
mkdirSync3(dir, { recursive: true });
|
|
1489
|
+
return join6(dir, "sdk-features.json");
|
|
1216
1490
|
}
|
|
1217
1491
|
function readConfig() {
|
|
1218
1492
|
const now = Date.now();
|
|
1219
|
-
if (cachedConfig && now -
|
|
1493
|
+
if (cachedConfig && now - lastReadTime2 < CACHE_TTL_MS2)
|
|
1220
1494
|
return cachedConfig;
|
|
1221
|
-
const path3 =
|
|
1495
|
+
const path3 = getConfigPath2();
|
|
1222
1496
|
try {
|
|
1223
|
-
if (
|
|
1224
|
-
cachedConfig = JSON.parse(
|
|
1497
|
+
if (existsSync7(path3)) {
|
|
1498
|
+
cachedConfig = JSON.parse(readFileSync6(path3, "utf-8"));
|
|
1225
1499
|
} else {
|
|
1226
1500
|
cachedConfig = {};
|
|
1227
1501
|
}
|
|
1228
1502
|
} catch {
|
|
1229
1503
|
cachedConfig = {};
|
|
1230
1504
|
}
|
|
1231
|
-
|
|
1505
|
+
lastReadTime2 = now;
|
|
1232
1506
|
return cachedConfig;
|
|
1233
1507
|
}
|
|
1234
1508
|
function writeConfig(config) {
|
|
1235
|
-
const path3 =
|
|
1509
|
+
const path3 = getConfigPath2();
|
|
1236
1510
|
const tmp = `${path3}.tmp`;
|
|
1237
1511
|
try {
|
|
1238
|
-
|
|
1239
|
-
|
|
1512
|
+
writeFileSync3(tmp, JSON.stringify(config, null, 2));
|
|
1513
|
+
renameSync3(tmp, path3);
|
|
1240
1514
|
cachedConfig = config;
|
|
1241
|
-
|
|
1515
|
+
lastReadTime2 = Date.now();
|
|
1242
1516
|
} catch (e) {
|
|
1243
1517
|
console.error("[sdk-features] write failed:", e.message);
|
|
1244
1518
|
}
|
|
@@ -1311,7 +1585,7 @@ function resetAdapterFeatures(adapterName) {
|
|
|
1311
1585
|
delete config[adapterName];
|
|
1312
1586
|
writeConfig(config);
|
|
1313
1587
|
}
|
|
1314
|
-
var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null,
|
|
1588
|
+
var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null, lastReadTime2 = 0, CACHE_TTL_MS2 = 5000, VALID_CLAUDE_MD_VALUES, VALID_THINKING_VALUES;
|
|
1315
1589
|
var init_sdkFeatures = __esm(() => {
|
|
1316
1590
|
DEFAULT_FEATURES = {
|
|
1317
1591
|
codeSystemPrompt: true,
|
|
@@ -1361,7 +1635,7 @@ var init_settingsPage = __esm(() => {
|
|
|
1361
1635
|
${themeCss}
|
|
1362
1636
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1363
1637
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
1364
|
-
|
|
1638
|
+
color: var(--text); padding: 0; line-height: 1.5; }
|
|
1365
1639
|
${profileBarCss}
|
|
1366
1640
|
.content { max-width: 900px; margin: 0 auto; padding: 24px; }
|
|
1367
1641
|
h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
|
|
@@ -1435,6 +1709,28 @@ var init_settingsPage = __esm(() => {
|
|
|
1435
1709
|
border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer;
|
|
1436
1710
|
}
|
|
1437
1711
|
.reset-btn:hover { border-color: var(--red); color: var(--red); }
|
|
1712
|
+
|
|
1713
|
+
/* Model pricing */
|
|
1714
|
+
.pricing-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
1715
|
+
.pricing-table th { text-align: left; padding: 8px 10px; color: var(--muted); font-weight: 500;
|
|
1716
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
|
|
1717
|
+
.pricing-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); }
|
|
1718
|
+
.pricing-table tr:last-child td { border-bottom: none; }
|
|
1719
|
+
.pricing-model { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; word-break: break-all; }
|
|
1720
|
+
.pricing-input { background: var(--bg); color: var(--text); border: 1px solid var(--border);
|
|
1721
|
+
border-radius: 6px; padding: 4px 8px; font-size: 12px; width: 84px; text-align: right;
|
|
1722
|
+
font-variant-numeric: tabular-nums; }
|
|
1723
|
+
.pricing-input:focus { border-color: var(--accent); outline: none; }
|
|
1724
|
+
.pricing-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px;
|
|
1725
|
+
text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }
|
|
1726
|
+
.badge-override { background: rgba(210, 153, 34, 0.15); color: var(--yellow); }
|
|
1727
|
+
.badge-builtin { background: rgba(139, 148, 158, 0.15); color: var(--muted); }
|
|
1728
|
+
.pricing-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px;
|
|
1729
|
+
padding-top: 14px; border-top: 1px solid var(--border); }
|
|
1730
|
+
.pricing-add input[type="text"] { width: 240px; text-align: left; }
|
|
1731
|
+
.add-btn { background: var(--accent); border: none; color: #fff; border-radius: 6px;
|
|
1732
|
+
padding: 5px 14px; font-size: 12px; font-weight: 500; cursor: pointer; }
|
|
1733
|
+
.pricing-note { font-size: 11px; color: var(--muted); margin-top: 12px; line-height: 1.6; }
|
|
1438
1734
|
</style>
|
|
1439
1735
|
</head>
|
|
1440
1736
|
<body>
|
|
@@ -1451,6 +1747,32 @@ ${profileBarHtml}
|
|
|
1451
1747
|
</p>
|
|
1452
1748
|
|
|
1453
1749
|
<div id="adapters"></div>
|
|
1750
|
+
|
|
1751
|
+
<h1 style="margin-top:40px">Model Pricing</h1>
|
|
1752
|
+
<p class="subtitle" style="max-width:720px;line-height:1.6">
|
|
1753
|
+
Rates used by the telemetry cost estimate, in USD per million tokens. Edit a value to override the
|
|
1754
|
+
built-in rate, or add models the built-in table doesn't know about (they show as "no pricing" on the
|
|
1755
|
+
dashboard until defined here). Changes apply on the next dashboard refresh.
|
|
1756
|
+
</p>
|
|
1757
|
+
<div class="adapter-card">
|
|
1758
|
+
<table class="pricing-table">
|
|
1759
|
+
<thead><tr><th>Model</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cache Write</th><th>Source</th><th></th></tr></thead>
|
|
1760
|
+
<tbody id="pricingRows"></tbody>
|
|
1761
|
+
</table>
|
|
1762
|
+
<div class="pricing-add">
|
|
1763
|
+
<input type="text" class="pricing-input" id="newModelName" placeholder="model id (e.g. claude-opus-9)">
|
|
1764
|
+
<input type="number" class="pricing-input" id="newModelInput" placeholder="input" min="0" step="0.01">
|
|
1765
|
+
<input type="number" class="pricing-input" id="newModelOutput" placeholder="output" min="0" step="0.01">
|
|
1766
|
+
<input type="number" class="pricing-input" id="newModelCacheRead" placeholder="cache read" min="0" step="0.01">
|
|
1767
|
+
<input type="number" class="pricing-input" id="newModelCacheWrite" placeholder="cache write" min="0" step="0.01">
|
|
1768
|
+
<button class="add-btn" onclick="addPricingModel()">Add Model</button>
|
|
1769
|
+
</div>
|
|
1770
|
+
<div class="pricing-note">
|
|
1771
|
+
Cache read and cache write are optional; when left blank they default to 0.1x and 1.25x of the
|
|
1772
|
+
input rate (the 5-minute cache TTL multipliers). Verify current list prices at
|
|
1773
|
+
<a href="https://claude.com/pricing" target="_blank" rel="noreferrer" style="color:var(--accent)">claude.com/pricing</a>.
|
|
1774
|
+
</div>
|
|
1775
|
+
</div>
|
|
1454
1776
|
</div>
|
|
1455
1777
|
|
|
1456
1778
|
<div class="save-indicator" id="saveIndicator">Saved</div>
|
|
@@ -1585,7 +1907,127 @@ function render() {
|
|
|
1585
1907
|
}
|
|
1586
1908
|
}
|
|
1587
1909
|
|
|
1910
|
+
// ---- Model pricing (telemetry cost estimate) ----
|
|
1911
|
+
let pricingData = { builtin: {}, overrides: {} };
|
|
1912
|
+
|
|
1913
|
+
function fmtRate(v) { return String(Math.round(v * 10000) / 10000); }
|
|
1914
|
+
|
|
1915
|
+
async function loadPricing() {
|
|
1916
|
+
const res = await fetch('/settings/api/pricing');
|
|
1917
|
+
pricingData = await res.json();
|
|
1918
|
+
renderPricing();
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
async function putPricing(model, rates) {
|
|
1922
|
+
const res = await fetch('/settings/api/pricing/' + encodeURIComponent(model), {
|
|
1923
|
+
method: 'PUT',
|
|
1924
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1925
|
+
body: JSON.stringify(rates),
|
|
1926
|
+
});
|
|
1927
|
+
if (!res.ok) {
|
|
1928
|
+
const err = await res.json().catch(function () { return {}; });
|
|
1929
|
+
alert('Could not save pricing: ' + (err.error || ('HTTP ' + res.status)));
|
|
1930
|
+
return false;
|
|
1931
|
+
}
|
|
1932
|
+
showSaved();
|
|
1933
|
+
return true;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
async function removePricing(model) {
|
|
1937
|
+
await fetch('/settings/api/pricing/' + encodeURIComponent(model), { method: 'DELETE' });
|
|
1938
|
+
showSaved();
|
|
1939
|
+
await loadPricing();
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
function rateCell(value, onCommit) {
|
|
1943
|
+
const td = document.createElement('td');
|
|
1944
|
+
const input = document.createElement('input');
|
|
1945
|
+
input.type = 'number';
|
|
1946
|
+
input.min = '0';
|
|
1947
|
+
input.step = '0.01';
|
|
1948
|
+
input.className = 'pricing-input';
|
|
1949
|
+
input.value = fmtRate(value);
|
|
1950
|
+
input.addEventListener('change', onCommit);
|
|
1951
|
+
td.appendChild(input);
|
|
1952
|
+
return { td: td, input: input };
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
function renderPricing() {
|
|
1956
|
+
const tbody = document.getElementById('pricingRows');
|
|
1957
|
+
tbody.innerHTML = '';
|
|
1958
|
+
const models = Array.from(new Set(
|
|
1959
|
+
Object.keys(pricingData.builtin).concat(Object.keys(pricingData.overrides))
|
|
1960
|
+
)).sort();
|
|
1961
|
+
|
|
1962
|
+
for (const model of models) {
|
|
1963
|
+
const override = pricingData.overrides[model];
|
|
1964
|
+
const effective = override || pricingData.builtin[model];
|
|
1965
|
+
const tr = document.createElement('tr');
|
|
1966
|
+
|
|
1967
|
+
const nameTd = document.createElement('td');
|
|
1968
|
+
nameTd.className = 'pricing-model';
|
|
1969
|
+
nameTd.textContent = model;
|
|
1970
|
+
tr.appendChild(nameTd);
|
|
1971
|
+
|
|
1972
|
+
const cells = [];
|
|
1973
|
+
const commit = async function () {
|
|
1974
|
+
const rates = {
|
|
1975
|
+
inputPerMTok: parseFloat(cells[0].input.value),
|
|
1976
|
+
outputPerMTok: parseFloat(cells[1].input.value),
|
|
1977
|
+
cacheReadPerMTok: parseFloat(cells[2].input.value),
|
|
1978
|
+
cacheWritePerMTok: parseFloat(cells[3].input.value),
|
|
1979
|
+
};
|
|
1980
|
+
for (const k in rates) { if (!isFinite(rates[k]) || rates[k] < 0) return; }
|
|
1981
|
+
if (await putPricing(model, rates)) await loadPricing();
|
|
1982
|
+
};
|
|
1983
|
+
['inputPerMTok', 'outputPerMTok', 'cacheReadPerMTok', 'cacheWritePerMTok'].forEach(function (key) {
|
|
1984
|
+
const cell = rateCell(effective[key], commit);
|
|
1985
|
+
cells.push(cell);
|
|
1986
|
+
tr.appendChild(cell.td);
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
const badgeTd = document.createElement('td');
|
|
1990
|
+
const badge = document.createElement('span');
|
|
1991
|
+
badge.className = 'pricing-badge ' + (override ? 'badge-override' : 'badge-builtin');
|
|
1992
|
+
badge.textContent = override ? 'Override' : 'Built-in';
|
|
1993
|
+
badgeTd.appendChild(badge);
|
|
1994
|
+
tr.appendChild(badgeTd);
|
|
1995
|
+
|
|
1996
|
+
const actionTd = document.createElement('td');
|
|
1997
|
+
if (override) {
|
|
1998
|
+
const btn = document.createElement('button');
|
|
1999
|
+
btn.className = 'reset-btn';
|
|
2000
|
+
btn.textContent = pricingData.builtin[model] ? 'Reset' : 'Remove';
|
|
2001
|
+
btn.addEventListener('click', function () { removePricing(model); });
|
|
2002
|
+
actionTd.appendChild(btn);
|
|
2003
|
+
}
|
|
2004
|
+
tr.appendChild(actionTd);
|
|
2005
|
+
|
|
2006
|
+
tbody.appendChild(tr);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
async function addPricingModel() {
|
|
2011
|
+
const name = document.getElementById('newModelName').value.trim();
|
|
2012
|
+
const inputRate = parseFloat(document.getElementById('newModelInput').value);
|
|
2013
|
+
const outputRate = parseFloat(document.getElementById('newModelOutput').value);
|
|
2014
|
+
if (!name) { alert('Enter a model id'); return; }
|
|
2015
|
+
if (!isFinite(inputRate) || !isFinite(outputRate)) { alert('Enter input and output rates (USD per million tokens)'); return; }
|
|
2016
|
+
const rates = { inputPerMTok: inputRate, outputPerMTok: outputRate };
|
|
2017
|
+
const cacheRead = parseFloat(document.getElementById('newModelCacheRead').value);
|
|
2018
|
+
const cacheWrite = parseFloat(document.getElementById('newModelCacheWrite').value);
|
|
2019
|
+
if (isFinite(cacheRead)) rates.cacheReadPerMTok = cacheRead;
|
|
2020
|
+
if (isFinite(cacheWrite)) rates.cacheWritePerMTok = cacheWrite;
|
|
2021
|
+
if (await putPricing(name, rates)) {
|
|
2022
|
+
['newModelName', 'newModelInput', 'newModelOutput', 'newModelCacheRead', 'newModelCacheWrite'].forEach(function (id) {
|
|
2023
|
+
document.getElementById(id).value = '';
|
|
2024
|
+
});
|
|
2025
|
+
await loadPricing();
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
1588
2029
|
loadConfig();
|
|
2030
|
+
loadPricing();
|
|
1589
2031
|
${profileBarJs}
|
|
1590
2032
|
</script>
|
|
1591
2033
|
</body>
|
|
@@ -3745,8 +4187,8 @@ var serve = (options, listeningListener) => {
|
|
|
3745
4187
|
};
|
|
3746
4188
|
|
|
3747
4189
|
// src/proxy/server.ts
|
|
3748
|
-
import { homedir as
|
|
3749
|
-
import { join as
|
|
4190
|
+
import { homedir as homedir6 } from "node:os";
|
|
4191
|
+
import { join as join7 } from "node:path";
|
|
3750
4192
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
3751
4193
|
|
|
3752
4194
|
// src/proxy/rateLimitStore.ts
|
|
@@ -3883,6 +4325,16 @@ function normalizeUtilization(raw2) {
|
|
|
3883
4325
|
return null;
|
|
3884
4326
|
return Math.max(0, raw2 / 100);
|
|
3885
4327
|
}
|
|
4328
|
+
function modelScopedWindowType(limit) {
|
|
4329
|
+
if (limit.kind !== "weekly_scoped")
|
|
4330
|
+
return null;
|
|
4331
|
+
const model = limit.scope?.model;
|
|
4332
|
+
const name = model?.display_name?.trim() || model?.id?.trim();
|
|
4333
|
+
if (!name)
|
|
4334
|
+
return null;
|
|
4335
|
+
const slug = name.toLowerCase().replace(/^claude[\s_-]+/, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4336
|
+
return slug ? `seven_day_${slug}` : null;
|
|
4337
|
+
}
|
|
3886
4338
|
function buildSnapshot(raw2) {
|
|
3887
4339
|
const windows = [];
|
|
3888
4340
|
for (const key of WINDOW_TYPES) {
|
|
@@ -3895,6 +4347,18 @@ function buildSnapshot(raw2) {
|
|
|
3895
4347
|
continue;
|
|
3896
4348
|
windows.push({ type: key, utilization, resetsAt });
|
|
3897
4349
|
}
|
|
4350
|
+
const windowTypes = new Set(windows.map((window) => window.type));
|
|
4351
|
+
for (const limit of raw2.limits ?? []) {
|
|
4352
|
+
const type = modelScopedWindowType(limit);
|
|
4353
|
+
if (!type || windowTypes.has(type))
|
|
4354
|
+
continue;
|
|
4355
|
+
const utilization = normalizeUtilization(limit.percent);
|
|
4356
|
+
const resetsAt = parseIsoToMs(limit.resets_at);
|
|
4357
|
+
if (utilization === null && resetsAt === null)
|
|
4358
|
+
continue;
|
|
4359
|
+
windows.push({ type, utilization, resetsAt });
|
|
4360
|
+
windowTypes.add(type);
|
|
4361
|
+
}
|
|
3898
4362
|
const extra = raw2.extra_usage;
|
|
3899
4363
|
const extraUsage = extra ? {
|
|
3900
4364
|
isEnabled: !!extra.is_enabled,
|
|
@@ -4001,6 +4465,9 @@ var DEFAULT_PROXY_CONFIG = {
|
|
|
4001
4465
|
version: undefined
|
|
4002
4466
|
};
|
|
4003
4467
|
|
|
4468
|
+
// src/proxy/server.ts
|
|
4469
|
+
init_env();
|
|
4470
|
+
|
|
4004
4471
|
// src/proxy/transform.ts
|
|
4005
4472
|
function runTransformHook(transforms, hook, ctx, adapterName) {
|
|
4006
4473
|
return transforms.reduce((acc, transform) => {
|
|
@@ -8602,11 +9069,13 @@ class LRUMap {
|
|
|
8602
9069
|
}
|
|
8603
9070
|
|
|
8604
9071
|
// src/telemetry/index.ts
|
|
8605
|
-
|
|
8606
|
-
import {
|
|
9072
|
+
init_env();
|
|
9073
|
+
import { join as join2 } from "node:path";
|
|
9074
|
+
import { homedir as homedir2 } from "node:os";
|
|
8607
9075
|
|
|
8608
9076
|
// src/telemetry/store.ts
|
|
8609
9077
|
init_percentiles();
|
|
9078
|
+
init_pricingStore();
|
|
8610
9079
|
var DEFAULT_CAPACITY = 1000;
|
|
8611
9080
|
function getCapacity() {
|
|
8612
9081
|
const raw2 = process.env.MERIDIAN_TELEMETRY_SIZE ?? process.env.CLAUDE_PROXY_TELEMETRY_SIZE;
|
|
@@ -8665,7 +9134,7 @@ class MemoryTelemetryStore {
|
|
|
8665
9134
|
summarize(windowMs = 60 * 60 * 1000) {
|
|
8666
9135
|
const since = Date.now() - windowMs;
|
|
8667
9136
|
const metrics = this.getRecent({ limit: this.capacity, since });
|
|
8668
|
-
return computeSummary(metrics, windowMs);
|
|
9137
|
+
return computeSummary(metrics, windowMs, getPricingOverrides());
|
|
8669
9138
|
}
|
|
8670
9139
|
clear() {
|
|
8671
9140
|
this.buffer = new Array(this.capacity).fill(null);
|
|
@@ -8726,8 +9195,8 @@ class MemoryDiagnosticLogStore {
|
|
|
8726
9195
|
}
|
|
8727
9196
|
var diagnosticLog = new MemoryDiagnosticLogStore;
|
|
8728
9197
|
// src/telemetry/routes.ts
|
|
8729
|
-
import { existsSync as
|
|
8730
|
-
import { resolve, dirname } from "node:path";
|
|
9198
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
9199
|
+
import { resolve, dirname as dirname2 } from "node:path";
|
|
8731
9200
|
import { fileURLToPath } from "node:url";
|
|
8732
9201
|
|
|
8733
9202
|
// src/telemetry/dashboard.ts
|
|
@@ -8744,7 +9213,7 @@ var dashboardHtml = `<!DOCTYPE html>
|
|
|
8744
9213
|
:root { --total: var(--accent); }
|
|
8745
9214
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
8746
9215
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
8747
|
-
|
|
9216
|
+
color: var(--text); padding: 0; line-height: 1.5; }
|
|
8748
9217
|
h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
|
|
8749
9218
|
.subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }
|
|
8750
9219
|
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
|
@@ -8830,8 +9299,8 @@ var dashboardHtml = `<!DOCTYPE html>
|
|
|
8830
9299
|
<body>
|
|
8831
9300
|
` + profileBarHtml + `
|
|
8832
9301
|
<div style="padding:24px">
|
|
8833
|
-
<h1>
|
|
8834
|
-
<div class="subtitle">Request
|
|
9302
|
+
<h1>Telemetry</h1>
|
|
9303
|
+
<div class="subtitle">Request performance, cost, and wire-contract integrity</div>
|
|
8835
9304
|
|
|
8836
9305
|
<div class="refresh-bar">
|
|
8837
9306
|
<select id="window">
|
|
@@ -8867,6 +9336,26 @@ function ago(ts) {
|
|
|
8867
9336
|
return Math.floor(s/3600) + 'h ago';
|
|
8868
9337
|
}
|
|
8869
9338
|
|
|
9339
|
+
function fmtTok(n) {
|
|
9340
|
+
return n > 1000000 ? (n/1000000).toFixed(1) + 'M' : n > 1000 ? Math.round(n/1000) + 'k' : String(n);
|
|
9341
|
+
}
|
|
9342
|
+
|
|
9343
|
+
// Model names come from client-supplied request bodies (requestModel) — escape
|
|
9344
|
+
// before concatenating into innerHTML so a quirky/malicious client can't
|
|
9345
|
+
// script the dashboard.
|
|
9346
|
+
function esc(s) {
|
|
9347
|
+
return String(s).replace(/[&<>"']/g, function (ch) {
|
|
9348
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch];
|
|
9349
|
+
});
|
|
9350
|
+
}
|
|
9351
|
+
|
|
9352
|
+
function usd(v) {
|
|
9353
|
+
if (v == null) return '—';
|
|
9354
|
+
if (v > 0 && v < 0.01) return '$' + v.toFixed(4);
|
|
9355
|
+
if (v < 100) return '$' + v.toFixed(2);
|
|
9356
|
+
return '$' + Math.round(v).toLocaleString();
|
|
9357
|
+
}
|
|
9358
|
+
|
|
8870
9359
|
function pctRow(label, color, phase) {
|
|
8871
9360
|
return '<tr>'
|
|
8872
9361
|
+ '<td><span class="phase-dot" style="background:' + color + '"></span>' + label + '</td>'
|
|
@@ -8949,7 +9438,6 @@ function render(s, reqs, logs, quota) {
|
|
|
8949
9438
|
// Token usage cards
|
|
8950
9439
|
if (s.tokenUsage) {
|
|
8951
9440
|
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
9441
|
html += '<div class="section"><div class="section-title">Token Usage</div></div>';
|
|
8954
9442
|
html += '<div class="cards">'
|
|
8955
9443
|
+ card('Input Tokens', fmtTok(t.totalInputTokens), '')
|
|
@@ -8960,12 +9448,51 @@ function render(s, reqs, logs, quota) {
|
|
|
8960
9448
|
+ '</div>';
|
|
8961
9449
|
}
|
|
8962
9450
|
|
|
9451
|
+
// Estimated cost: static API list pricing applied to the window's token usage
|
|
9452
|
+
if (s.costEstimate && Object.keys(s.costEstimate.byModel).length > 0) {
|
|
9453
|
+
const ce = s.costEstimate;
|
|
9454
|
+
const costRows = Object.entries(ce.byModel)
|
|
9455
|
+
.sort((a, b) => (b[1].estimatedUsd || 0) - (a[1].estimatedUsd || 0));
|
|
9456
|
+
|
|
9457
|
+
html += '<div class="section"><div class="section-title">Estimated Cost</div></div>';
|
|
9458
|
+
html += '<div class="cards">'
|
|
9459
|
+
+ card('Est. API Cost', usd(ce.totalUsd), 'window total at API list prices');
|
|
9460
|
+
for (const [model, m] of costRows) {
|
|
9461
|
+
html += card(esc(model), usd(m.estimatedUsd), m.requests + ' req' + (m.requests === 1 ? '' : 's'));
|
|
9462
|
+
}
|
|
9463
|
+
html += '</div>';
|
|
9464
|
+
|
|
9465
|
+
html += '<div class="section">'
|
|
9466
|
+
+ '<table><thead><tr><th>Model</th><th>Requests</th><th>Input</th><th>Output</th>'
|
|
9467
|
+
+ '<th>Cache Read</th><th>Cache Write</th><th>Est. Cost</th></tr></thead><tbody>';
|
|
9468
|
+
for (const [model, m] of costRows) {
|
|
9469
|
+
html += '<tr>'
|
|
9470
|
+
+ '<td>' + esc(model) + (m.estimatedUsd == null ? ' <span style="font-size:10px;color:var(--yellow)">no pricing</span>' : '') + '</td>'
|
|
9471
|
+
+ '<td class="mono">' + m.requests + '</td>'
|
|
9472
|
+
+ '<td class="mono">' + fmtTok(m.inputTokens) + '</td>'
|
|
9473
|
+
+ '<td class="mono">' + fmtTok(m.outputTokens) + '</td>'
|
|
9474
|
+
+ '<td class="mono">' + fmtTok(m.cacheReadTokens) + '</td>'
|
|
9475
|
+
+ '<td class="mono">' + fmtTok(m.cacheCreationTokens) + '</td>'
|
|
9476
|
+
+ '<td class="mono">' + usd(m.estimatedUsd) + '</td>'
|
|
9477
|
+
+ '</tr>';
|
|
9478
|
+
}
|
|
9479
|
+
html += '</tbody></table>'
|
|
9480
|
+
+ '<div class="usage-note" style="margin-top:8px">Estimated at static Anthropic API list prices'
|
|
9481
|
+
+ ' (cache writes at the 5-minute TTL rate). Claude Max usage is covered by your subscription'
|
|
9482
|
+
+ ' (equivalent API cost, not a charge).'
|
|
9483
|
+
+ (ce.unpricedRequestCount > 0
|
|
9484
|
+
? ' ' + ce.unpricedRequestCount + ' request' + (ce.unpricedRequestCount === 1 ? '' : 's') + ' from unrecognized models excluded.'
|
|
9485
|
+
: '')
|
|
9486
|
+
+ ' Rates are editable in <a href="/settings" style="color:var(--accent)">Settings</a>.'
|
|
9487
|
+
+ '</div></div>';
|
|
9488
|
+
}
|
|
9489
|
+
|
|
8963
9490
|
// Model breakdown
|
|
8964
9491
|
const models = Object.entries(s.byModel);
|
|
8965
9492
|
if (models.length > 0) {
|
|
8966
9493
|
html += '<div class="cards">';
|
|
8967
9494
|
for (const [name, data] of models) {
|
|
8968
|
-
html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));
|
|
9495
|
+
html += card(esc(name), data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));
|
|
8969
9496
|
}
|
|
8970
9497
|
html += '</div>';
|
|
8971
9498
|
}
|
|
@@ -9205,8 +9732,8 @@ timer = setInterval(refresh, 5000);
|
|
|
9205
9732
|
</html>`;
|
|
9206
9733
|
|
|
9207
9734
|
// src/telemetry/routes.ts
|
|
9208
|
-
var _iconPath = resolve(
|
|
9209
|
-
var _iconSvg =
|
|
9735
|
+
var _iconPath = resolve(dirname2(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
|
|
9736
|
+
var _iconSvg = existsSync3(_iconPath) ? readFileSync2(_iconPath, "utf-8") : null;
|
|
9210
9737
|
function createTelemetryRoutes() {
|
|
9211
9738
|
const routes = new Hono2;
|
|
9212
9739
|
routes.get("/", (c) => {
|
|
@@ -9261,108 +9788,202 @@ var landingHtml = `<!DOCTYPE html>
|
|
|
9261
9788
|
${themeCss}
|
|
9262
9789
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9263
9790
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
9264
|
-
|
|
9265
|
-
.container { max-width: 960px; margin: 0 auto; padding:
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
.
|
|
9269
|
-
.
|
|
9270
|
-
|
|
9271
|
-
.
|
|
9272
|
-
background: var(--surface); border: 1px solid var(--border); border-radius:
|
|
9273
|
-
|
|
9274
|
-
.
|
|
9275
|
-
.
|
|
9276
|
-
.
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
.
|
|
9281
|
-
|
|
9282
|
-
.card
|
|
9283
|
-
.card
|
|
9284
|
-
.card
|
|
9285
|
-
.
|
|
9286
|
-
.
|
|
9791
|
+
color: var(--text); line-height: 1.6; min-height: 100vh; }
|
|
9792
|
+
.container { max-width: 960px; margin: 0 auto; padding: 28px 24px; }
|
|
9793
|
+
|
|
9794
|
+
/* Intro — friendly one-paragraph overview of how Meridian works */
|
|
9795
|
+
.intro { margin-bottom: 28px; }
|
|
9796
|
+
.intro h2 { font-size: 20px; font-weight: 700; margin-bottom: 6px; }
|
|
9797
|
+
.intro p { font-size: 13px; color: var(--muted); max-width: 640px; }
|
|
9798
|
+
.intro code { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px;
|
|
9799
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 5px;
|
|
9800
|
+
padding: 1px 6px; color: var(--accent2); white-space: nowrap; }
|
|
9801
|
+
.intro a { color: var(--accent); text-decoration: none; }
|
|
9802
|
+
.intro a:hover { text-decoration: underline; }
|
|
9803
|
+
.intro-meta { font-size: 12px; color: var(--muted); margin-top: 8px; }
|
|
9804
|
+
|
|
9805
|
+
/* Profile cards — the centerpiece: usage + cost per account, click to switch */
|
|
9806
|
+
.profile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
|
9807
|
+
.profile-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
9808
|
+
padding: 18px 20px; position: relative; transition: border-color 0.15s; }
|
|
9809
|
+
.profile-card.switchable { cursor: pointer; }
|
|
9810
|
+
.profile-card.switchable:hover { border-color: var(--accent); }
|
|
9811
|
+
.profile-card.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
|
9812
|
+
.profile-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 4px; }
|
|
9813
|
+
.profile-name { font-size: 13px; font-weight: 600; letter-spacing: 0.5px; display: flex; align-items: center; gap: 8px; }
|
|
9814
|
+
.profile-name .prof-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border); }
|
|
9815
|
+
.profile-card.active .prof-dot { background: var(--accent); box-shadow: 0 0 6px rgba(88,166,255,0.5); }
|
|
9816
|
+
.active-pill { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
|
|
9817
|
+
color: var(--accent); background: rgba(88,166,255,0.12); border: 1px solid rgba(88,166,255,0.35);
|
|
9818
|
+
border-radius: 10px; padding: 1px 8px; }
|
|
9819
|
+
.switch-hint { font-size: 9px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;
|
|
9820
|
+
color: var(--muted); opacity: 0; transition: opacity 0.15s; }
|
|
9821
|
+
.profile-card.switchable:hover .switch-hint { opacity: 1; }
|
|
9822
|
+
.profile-cost { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
|
|
9823
|
+
.profile-sub { font-size: 11px; color: var(--muted); text-align: right; margin-bottom: 12px; }
|
|
9824
|
+
.usage-row { display: flex; align-items: center; gap: 10px; font-size: 12px; padding: 4px 0; }
|
|
9825
|
+
.usage-row .w-label { color: var(--muted); width: 64px; flex-shrink: 0; }
|
|
9826
|
+
.usage-row .w-bar { flex: 1; height: 6px; background: var(--surface2); border-radius: 3px; overflow: hidden; }
|
|
9827
|
+
.usage-row .w-fill { height: 100%; border-radius: 3px; }
|
|
9828
|
+
.usage-row .w-pct { width: 38px; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
9829
|
+
.usage-row .w-reset { color: var(--muted); font-size: 11px; width: 76px; text-align: right; }
|
|
9830
|
+
.no-usage { font-size: 12px; color: var(--muted); padding: 4px 0; }
|
|
9831
|
+
|
|
9832
|
+
/* Traffic strip — one compact surface */
|
|
9833
|
+
.strip { display: flex; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--border);
|
|
9834
|
+
border-radius: 12px; padding: 14px 4px; margin-bottom: 24px; }
|
|
9835
|
+
.strip-item { flex: 1; min-width: 120px; padding: 2px 18px; border-right: 1px solid var(--border); }
|
|
9836
|
+
.strip-item:last-child { border-right: none; }
|
|
9837
|
+
.strip-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; }
|
|
9838
|
+
.strip-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
|
|
9839
|
+
.strip-value.green { color: var(--green); }
|
|
9840
|
+
.strip-value.red { color: var(--red); }
|
|
9841
|
+
.strip-detail { font-size: 11px; color: var(--muted); }
|
|
9287
9842
|
|
|
9288
9843
|
.section { margin-bottom: 24px; }
|
|
9289
9844
|
.section-title { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase;
|
|
9290
9845
|
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
9846
|
|
|
9313
9847
|
.footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border);
|
|
9314
9848
|
font-size: 11px; color: var(--muted); text-align: center; }
|
|
9315
|
-
.footer a { color: var(--
|
|
9849
|
+
.footer a { color: var(--accent); text-decoration: none; }
|
|
9316
9850
|
` + profileBarCss + `
|
|
9317
9851
|
</style>
|
|
9318
9852
|
</head>
|
|
9319
9853
|
<body>
|
|
9320
9854
|
` + profileBarHtml + `
|
|
9321
9855
|
<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
9856
|
<div id="content"><div style="color:var(--muted);padding:40px;text-align:center">Loading…</div></div>
|
|
9337
9857
|
</div>
|
|
9338
9858
|
<script>
|
|
9339
9859
|
function ms(v){if(v==null||v===0)return '—';return v<1000?v+'ms':(v/1000).toFixed(1)+'s'}
|
|
9340
|
-
function
|
|
9860
|
+
function esc(s){return String(s).replace(/[&<>"']/g,function(ch){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch]})}
|
|
9861
|
+
function usd(v){if(v==null)return '—';if(v>0&&v<0.01)return '$'+v.toFixed(4);if(v<100)return '$'+v.toFixed(2);return '$'+Math.round(v).toLocaleString()}
|
|
9862
|
+
|
|
9863
|
+
var WIN_LABELS={five_hour:'5h',seven_day:'7d',seven_day_opus:'7d Opus',seven_day_sonnet:'7d Sonnet',seven_day_fable:'7d Fable',seven_day_oauth_apps:'7d Apps',seven_day_cowork:'7d Cowork',seven_day_omelette:'7d Omelette'};
|
|
9864
|
+
function winLabel(t){if(WIN_LABELS[t])return WIN_LABELS[t];return t.replace(/^seven_day_/,'7d ').replace(/_/g,' ').replace(/\bw/g,function(c){return c.toUpperCase()})}
|
|
9865
|
+
function utilColor(u){return u>=0.85?'var(--red)':u>=0.6?'var(--yellow)':'var(--green)'}
|
|
9866
|
+
function resetIn(ts){if(ts==null)return '';var d=ts-Date.now();if(d<=0)return 'resetting…';var m=Math.ceil(d/60000);if(m<60)return 'in '+m+'m';var h=Math.floor(m/60);if(h<24)return 'in '+h+'h'+(m%60?' '+(m%60)+'m':'');var days=Math.floor(h/24);return 'in '+days+'d'+(h%24?' '+(h%24)+'h':'')}
|
|
9867
|
+
|
|
9868
|
+
function introSection(h){
|
|
9869
|
+
var meta=[];
|
|
9870
|
+
if(h.auth&&h.auth.loggedIn)meta.push(esc(h.auth.email||'')+(h.auth.subscriptionType?' ('+esc(h.auth.subscriptionType)+')':''));
|
|
9871
|
+
meta.push(h.mode||'internal');
|
|
9872
|
+
meta.push('port '+location.port);
|
|
9873
|
+
return '<div class="intro">'
|
|
9874
|
+
+'<h2>Harness Claude, your way.</h2>'
|
|
9875
|
+
+'<p>Meridian bridges any Anthropic-API agent to your Claude subscription — point the agent’s <code>ANTHROPIC_BASE_URL</code> at <code>http://'+esc(location.host)+'</code> and every request routes through the active account below. Setup guides for each agent live in the <a href="https://github.com/rynfar/meridian#readme">README</a>.</p>'
|
|
9876
|
+
+'<div class="intro-meta">'+meta.join(' · ')+'</div>'
|
|
9877
|
+
+'</div>';
|
|
9878
|
+
}
|
|
9879
|
+
|
|
9880
|
+
function profileSection(q,s,pl,h){
|
|
9881
|
+
var byProfile=(s&&s.costEstimate&&s.costEstimate.byProfile)||{};
|
|
9882
|
+
var quotaByProfile={};
|
|
9883
|
+
if(q&&Array.isArray(q.profiles))for(var i=0;i<q.profiles.length;i++){var qid=q.profiles[i].id||q.profiles[i].profile||'default';quotaByProfile[qid]=q.profiles[i].windows||[]}
|
|
9884
|
+
var profs=[];var seen={};
|
|
9885
|
+
var configured=(pl&&Array.isArray(pl.profiles))?pl.profiles:[];
|
|
9886
|
+
var multi=configured.length>1;
|
|
9887
|
+
if(configured.length>0){
|
|
9888
|
+
// Real profiles exist: show exactly those. Traffic that predates
|
|
9889
|
+
// per-profile attribution (the synthetic "default" bucket) still
|
|
9890
|
+
// counts in the totals strip but doesn't render as a fake account.
|
|
9891
|
+
for(var i=0;i<configured.length;i++){var p=configured[i];profs.push({id:p.id,label:p.id,type:p.type,isActive:!!p.isActive,configured:true});seen[p.id]=1}
|
|
9892
|
+
}else{
|
|
9893
|
+
// Single-account setup: one card, labeled with the logged-in email.
|
|
9894
|
+
var email=(h&&h.auth&&h.auth.loggedIn&&h.auth.email)||'';
|
|
9895
|
+
for(var k in quotaByProfile){profs.push({id:k,label:k==='default'?(email||'account'):k,configured:false});seen[k]=1}
|
|
9896
|
+
for(var k in byProfile){if(!seen[k])profs.push({id:k,label:k==='default'?(email||'account'):k,configured:false});seen[k]=1}
|
|
9897
|
+
}
|
|
9898
|
+
if(profs.length===0)return '';
|
|
9899
|
+
var cards='';
|
|
9900
|
+
for(var i=0;i<profs.length;i++){
|
|
9901
|
+
var p=profs[i];var cost=byProfile[p.id];
|
|
9902
|
+
var wins=(quotaByProfile[p.id]||[]).filter(function(w){return w.utilization!=null});
|
|
9903
|
+
if(!p.configured&&wins.length===0&&!cost)continue;
|
|
9904
|
+
var rows='';
|
|
9905
|
+
for(var j=0;j<wins.length;j++){
|
|
9906
|
+
var w=wins[j];var pct=Math.round(w.utilization*100);
|
|
9907
|
+
rows+='<div class="usage-row"><span class="w-label">'+esc(winLabel(w.type))+'</span>'
|
|
9908
|
+
+'<div class="w-bar"><div class="w-fill" style="width:'+Math.min(pct,100)+'%;background:'+utilColor(w.utilization)+'"></div></div>'
|
|
9909
|
+
+'<span class="w-pct" style="color:'+utilColor(w.utilization)+'">'+pct+'%</span>'
|
|
9910
|
+
+'<span class="w-reset">'+resetIn(w.resetsAt)+'</span></div>';
|
|
9911
|
+
}
|
|
9912
|
+
if(!rows)rows='<div class="no-usage">no usage data yet</div>';
|
|
9913
|
+
var switchable=multi&&p.configured&&!p.isActive;
|
|
9914
|
+
var badge=p.isActive?'<span class="active-pill">Active</span>':switchable?'<span class="switch-hint">Click to activate</span>':'';
|
|
9915
|
+
cards+='<div class="profile-card'+(p.isActive?' active':'')+(switchable?' switchable':'')+'"'+(switchable?' data-profile="'+esc(p.id)+'" role="button" tabindex="0"':'')+'>'
|
|
9916
|
+
+'<div class="profile-head"><span class="profile-name"><span class="prof-dot"></span>'+esc(p.label||p.id)+' '+badge+'</span>'
|
|
9917
|
+
+'<span class="profile-cost">'+usd(cost?cost.estimatedUsd:0)+'</span></div>'
|
|
9918
|
+
+'<div class="profile-sub">'+(cost?cost.requests+' request'+(cost.requests===1?'':'s')+' · est. API value · 24h':'no traffic · 24h')+'</div>'
|
|
9919
|
+
+rows+'</div>';
|
|
9920
|
+
}
|
|
9921
|
+
if(!cards)return '';
|
|
9922
|
+
return '<div class="section"><div class="section-title">'+(profs.length===1?'Account':'Accounts')+'</div><div class="profile-grid">'+cards+'</div></div>';
|
|
9923
|
+
}
|
|
9924
|
+
|
|
9925
|
+
function strip(items){
|
|
9926
|
+
var o='<div class="strip">';
|
|
9927
|
+
for(var i=0;i<items.length;i++){var it=items[i];
|
|
9928
|
+
o+='<div class="strip-item"><div class="strip-label">'+it[0]+'</div><div class="strip-value '+(it[2]||'')+'">'+it[1]+'</div>'+(it[3]?'<div class="strip-detail">'+it[3]+'</div>':'')+'</div>';
|
|
9929
|
+
}
|
|
9930
|
+
return o+'</div>';
|
|
9931
|
+
}
|
|
9341
9932
|
|
|
9342
9933
|
async function refresh(){
|
|
9343
9934
|
try{
|
|
9344
|
-
const [health,stats]=await Promise.all([
|
|
9345
|
-
|
|
9935
|
+
const [health,stats,quota,profiles]=await Promise.all([
|
|
9936
|
+
fetch('/health').then(r=>r.json()),
|
|
9937
|
+
fetch('/telemetry/summary?window=86400000').then(r=>r.json()),
|
|
9938
|
+
fetch('/v1/usage/quota/all').then(r=>r.json()).catch(function(){return null}),
|
|
9939
|
+
fetch('/profiles/list').then(r=>r.json()).catch(function(){return null})
|
|
9940
|
+
]);
|
|
9941
|
+
render(health,stats,quota,profiles);
|
|
9346
9942
|
}catch(e){document.getElementById('content').innerHTML='<div style="color:var(--red);padding:40px;text-align:center">Could not connect</div>'}
|
|
9347
9943
|
}
|
|
9348
9944
|
|
|
9349
|
-
function
|
|
9350
|
-
|
|
9945
|
+
function tokens(v){if(v==null)return '—';if(v>=1e6)return (v/1e6).toFixed(1)+'M';if(v>=1e3)return (v/1e3).toFixed(1)+'k';return String(v)}
|
|
9946
|
+
|
|
9947
|
+
function render(h,s,q,pl){
|
|
9351
9948
|
let o='';
|
|
9352
|
-
o+=
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
o+=
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9949
|
+
o+=introSection(h);
|
|
9950
|
+
|
|
9951
|
+
// Accounts — per-profile usage + est cost; click a card to switch
|
|
9952
|
+
o+=profileSection(q,s,pl,h);
|
|
9953
|
+
|
|
9954
|
+
// Last 24 hours — meaningful signals only. Errors and envelope
|
|
9955
|
+
// violations appear only when there is something to report.
|
|
9956
|
+
var tu=s.tokenUsage||{};
|
|
9957
|
+
var cache=tu.avgCacheHitRate!=null?Math.round(tu.avgCacheHitRate*100)+'%':'—';
|
|
9958
|
+
var items=[
|
|
9959
|
+
['Requests',String(s.totalRequests),s.errorCount>0?'red':'',s.errorCount>0?s.errorCount+' error'+(s.errorCount===1?'':'s'):'no errors'],
|
|
9960
|
+
['Tokens Out',tokens(tu.totalOutputTokens),'',tokens(tu.totalInputTokens)+' in'],
|
|
9961
|
+
['Cache Hit',cache,tu.avgCacheHitRate>=0.5?'green':'','prompt cache'],
|
|
9962
|
+
['Est. API Value',usd(s.costEstimate?.totalUsd),'','list prices'],
|
|
9963
|
+
['Median Response',ms(s.totalDuration?.p50),'','p95 '+ms(s.totalDuration?.p95)]
|
|
9964
|
+
];
|
|
9965
|
+
if(s.envelopeViolationCount>0)items.push(['Envelope',String(s.envelopeViolationCount),'red','wire-contract violations']);
|
|
9966
|
+
o+='<div class="section"><div class="section-title">Last 24 Hours</div>'+strip(items)+'</div>';
|
|
9967
|
+
|
|
9968
|
+
o+='<div class="footer">Meridian · <a href="https://github.com/rynfar/meridian">GitHub</a> · Built on the <a href="https://github.com/anthropics/claude-agent-sdk-typescript">Claude Agent SDK</a></div>';
|
|
9363
9969
|
document.getElementById('content').innerHTML=o;
|
|
9364
9970
|
}
|
|
9365
|
-
|
|
9971
|
+
|
|
9972
|
+
function switchProfile(id){
|
|
9973
|
+
fetch('/profiles/active',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({profile:id})})
|
|
9974
|
+
.then(function(r){return r.json()})
|
|
9975
|
+
.then(function(data){if(data.success){refresh();if(window.meridianHeaderRefresh)window.meridianHeaderRefresh()}})
|
|
9976
|
+
.catch(function(){});
|
|
9977
|
+
}
|
|
9978
|
+
document.getElementById('content').addEventListener('click',function(e){
|
|
9979
|
+
var card=e.target.closest('.profile-card.switchable');
|
|
9980
|
+
if(card&&card.dataset.profile)switchProfile(card.dataset.profile);
|
|
9981
|
+
});
|
|
9982
|
+
document.getElementById('content').addEventListener('keydown',function(e){
|
|
9983
|
+
if(e.key!=='Enter'&&e.key!==' ')return;
|
|
9984
|
+
var card=e.target.closest('.profile-card.switchable');
|
|
9985
|
+
if(card&&card.dataset.profile){e.preventDefault();switchProfile(card.dataset.profile)}
|
|
9986
|
+
});
|
|
9366
9987
|
refresh();setInterval(refresh,10000);
|
|
9367
9988
|
` + profileBarJs + `
|
|
9368
9989
|
</script>
|
|
@@ -9429,7 +10050,7 @@ function renderPrometheusMetrics(store) {
|
|
|
9429
10050
|
// src/telemetry/index.ts
|
|
9430
10051
|
init_sqlite();
|
|
9431
10052
|
function getDefaultDbPath() {
|
|
9432
|
-
return
|
|
10053
|
+
return join2(homedir2(), ".config", "meridian", "telemetry.db");
|
|
9433
10054
|
}
|
|
9434
10055
|
function createStores() {
|
|
9435
10056
|
if (!envBool("TELEMETRY_PERSIST")) {
|
|
@@ -10470,7 +11091,11 @@ ${text.slice(0, 2000)}` : text.slice(0, 2000);
|
|
|
10470
11091
|
return createHash("sha256").update(seed).digest("hex").slice(0, 16);
|
|
10471
11092
|
}
|
|
10472
11093
|
|
|
11094
|
+
// src/proxy/adapters/opencode.ts
|
|
11095
|
+
init_env();
|
|
11096
|
+
|
|
10473
11097
|
// src/proxy/transforms/opencode.ts
|
|
11098
|
+
init_env();
|
|
10474
11099
|
var openCodeTransforms = [
|
|
10475
11100
|
{
|
|
10476
11101
|
name: "opencode-core",
|
|
@@ -10636,7 +11261,11 @@ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be on
|
|
|
10636
11261
|
}
|
|
10637
11262
|
};
|
|
10638
11263
|
|
|
11264
|
+
// src/proxy/adapters/droid.ts
|
|
11265
|
+
init_env();
|
|
11266
|
+
|
|
10639
11267
|
// src/proxy/transforms/droid.ts
|
|
11268
|
+
init_env();
|
|
10640
11269
|
var DROID_MCP_SERVER_NAME = "droid";
|
|
10641
11270
|
var DROID_ALLOWED_MCP_TOOLS = [
|
|
10642
11271
|
`mcp__${DROID_MCP_SERVER_NAME}__read`,
|
|
@@ -10942,7 +11571,11 @@ var passthroughAdapter = {
|
|
|
10942
11571
|
}
|
|
10943
11572
|
};
|
|
10944
11573
|
|
|
11574
|
+
// src/proxy/adapters/pi.ts
|
|
11575
|
+
init_env();
|
|
11576
|
+
|
|
10945
11577
|
// src/proxy/transforms/pi.ts
|
|
11578
|
+
init_env();
|
|
10946
11579
|
var PI_MCP_SERVER_NAME = "pi";
|
|
10947
11580
|
var PI_ALLOWED_MCP_TOOLS = [
|
|
10948
11581
|
`mcp__${PI_MCP_SERVER_NAME}__read`,
|
|
@@ -11175,6 +11808,7 @@ var forgeCodeAdapter = {
|
|
|
11175
11808
|
};
|
|
11176
11809
|
|
|
11177
11810
|
// src/proxy/adapters/claudecode.ts
|
|
11811
|
+
init_env();
|
|
11178
11812
|
function extractClaudeCodeClientCwd(body) {
|
|
11179
11813
|
let systemText = "";
|
|
11180
11814
|
if (typeof body.system === "string") {
|
|
@@ -11298,10 +11932,10 @@ var cherryAdapter = {
|
|
|
11298
11932
|
};
|
|
11299
11933
|
|
|
11300
11934
|
// src/proxy/adapterInstances.ts
|
|
11301
|
-
import { existsSync as
|
|
11302
|
-
import { join as
|
|
11303
|
-
import { homedir as
|
|
11304
|
-
var CONFIG_FILE =
|
|
11935
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
11936
|
+
import { join as join3 } from "node:path";
|
|
11937
|
+
import { homedir as homedir3 } from "node:os";
|
|
11938
|
+
var CONFIG_FILE = join3(homedir3(), ".config", "meridian", "adapter-instances.json");
|
|
11305
11939
|
function parseAdapterInstances(raw2) {
|
|
11306
11940
|
if (!raw2)
|
|
11307
11941
|
return {};
|
|
@@ -11348,7 +11982,7 @@ function loadAdapterInstances() {
|
|
|
11348
11982
|
if (diskCacheAt > 0 && Date.now() - diskCacheAt < DISK_CACHE_TTL_MS)
|
|
11349
11983
|
return diskCache;
|
|
11350
11984
|
try {
|
|
11351
|
-
diskCache =
|
|
11985
|
+
diskCache = existsSync4(CONFIG_FILE) ? parseAdapterInstances(readFileSync3(CONFIG_FILE, "utf-8")) : {};
|
|
11352
11986
|
} catch (err) {
|
|
11353
11987
|
console.warn(`[meridian] Failed to read ${CONFIG_FILE}: ${err instanceof Error ? err.message : err}`);
|
|
11354
11988
|
diskCache = {};
|
|
@@ -17156,6 +17790,7 @@ function createOpencodeMcpServer() {
|
|
|
17156
17790
|
}
|
|
17157
17791
|
|
|
17158
17792
|
// src/proxy/query.ts
|
|
17793
|
+
init_env();
|
|
17159
17794
|
function stripConfigDir(env2) {
|
|
17160
17795
|
if (!("CLAUDE_CONFIG_DIR" in env2))
|
|
17161
17796
|
return env2;
|
|
@@ -17379,8 +18014,8 @@ function getAdapterTransforms(adapterName) {
|
|
|
17379
18014
|
}
|
|
17380
18015
|
|
|
17381
18016
|
// src/proxy/plugins/loader.ts
|
|
17382
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
17383
|
-
import { join as
|
|
18017
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
18018
|
+
import { join as join4, isAbsolute as isAbsolute2, extname } from "path";
|
|
17384
18019
|
import { pathToFileURL } from "url";
|
|
17385
18020
|
|
|
17386
18021
|
// src/proxy/plugins/validation.ts
|
|
@@ -17421,10 +18056,10 @@ function validateTransform(exported) {
|
|
|
17421
18056
|
// src/proxy/plugins/loader.ts
|
|
17422
18057
|
var loadCounter = 0;
|
|
17423
18058
|
function parsePluginConfig(configPath) {
|
|
17424
|
-
if (!
|
|
18059
|
+
if (!existsSync5(configPath))
|
|
17425
18060
|
return [];
|
|
17426
18061
|
try {
|
|
17427
|
-
const raw2 =
|
|
18062
|
+
const raw2 = readFileSync4(configPath, "utf-8");
|
|
17428
18063
|
const parsed = JSON.parse(raw2);
|
|
17429
18064
|
return Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
17430
18065
|
} catch {
|
|
@@ -17434,7 +18069,7 @@ function parsePluginConfig(configPath) {
|
|
|
17434
18069
|
async function loadPlugins(pluginDir, configPath) {
|
|
17435
18070
|
resetAllPluginStats();
|
|
17436
18071
|
const config = configPath ? parsePluginConfig(configPath) : [];
|
|
17437
|
-
const pluginDirExists =
|
|
18072
|
+
const pluginDirExists = existsSync5(pluginDir);
|
|
17438
18073
|
let filenames = [];
|
|
17439
18074
|
if (pluginDirExists) {
|
|
17440
18075
|
try {
|
|
@@ -17465,7 +18100,7 @@ async function loadPlugins(pluginDir, configPath) {
|
|
|
17465
18100
|
const loaded = [];
|
|
17466
18101
|
const seenNames = new Set;
|
|
17467
18102
|
for (const { filename, entry } of ordered) {
|
|
17468
|
-
const filePath = isAbsolute2(filename) ? filename :
|
|
18103
|
+
const filePath = isAbsolute2(filename) ? filename : join4(pluginDir, filename);
|
|
17469
18104
|
if (entry && !entry.enabled) {
|
|
17470
18105
|
loaded.push({
|
|
17471
18106
|
name: filename,
|
|
@@ -17838,17 +18473,17 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
|
|
|
17838
18473
|
// src/proxy/sessionStore.ts
|
|
17839
18474
|
import {
|
|
17840
18475
|
closeSync,
|
|
17841
|
-
existsSync as
|
|
17842
|
-
mkdirSync,
|
|
18476
|
+
existsSync as existsSync6,
|
|
18477
|
+
mkdirSync as mkdirSync2,
|
|
17843
18478
|
openSync,
|
|
17844
|
-
readFileSync as
|
|
17845
|
-
renameSync,
|
|
18479
|
+
readFileSync as readFileSync5,
|
|
18480
|
+
renameSync as renameSync2,
|
|
17846
18481
|
statSync,
|
|
17847
18482
|
unlinkSync,
|
|
17848
|
-
writeFileSync
|
|
18483
|
+
writeFileSync as writeFileSync2
|
|
17849
18484
|
} from "node:fs";
|
|
17850
|
-
import { homedir as
|
|
17851
|
-
import { join as
|
|
18485
|
+
import { homedir as homedir4 } from "node:os";
|
|
18486
|
+
import { join as join5 } from "node:path";
|
|
17852
18487
|
var DEFAULT_MAX_STORED_SESSIONS = 1e4;
|
|
17853
18488
|
var STALE_LOCK_THRESHOLD_MS = 30000;
|
|
17854
18489
|
function getMaxStoredSessions() {
|
|
@@ -17896,17 +18531,17 @@ var sessionDirOverride = null;
|
|
|
17896
18531
|
var skipLocking = false;
|
|
17897
18532
|
function getStorePath() {
|
|
17898
18533
|
const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
|
|
17899
|
-
if (!
|
|
17900
|
-
|
|
18534
|
+
if (!existsSync6(dir)) {
|
|
18535
|
+
mkdirSync2(dir, { recursive: true });
|
|
17901
18536
|
}
|
|
17902
|
-
return
|
|
18537
|
+
return join5(dir, "sessions.json");
|
|
17903
18538
|
}
|
|
17904
18539
|
function getDefaultCacheDir() {
|
|
17905
|
-
const newDir =
|
|
17906
|
-
const oldDir =
|
|
17907
|
-
if (
|
|
18540
|
+
const newDir = join5(homedir4(), ".cache", "meridian");
|
|
18541
|
+
const oldDir = join5(homedir4(), ".cache", "opencode-claude-max-proxy");
|
|
18542
|
+
if (existsSync6(newDir))
|
|
17908
18543
|
return newDir;
|
|
17909
|
-
if (
|
|
18544
|
+
if (existsSync6(oldDir)) {
|
|
17910
18545
|
try {
|
|
17911
18546
|
const { symlinkSync } = __require("fs");
|
|
17912
18547
|
symlinkSync(oldDir, newDir);
|
|
@@ -17919,10 +18554,10 @@ function getDefaultCacheDir() {
|
|
|
17919
18554
|
}
|
|
17920
18555
|
function readStore() {
|
|
17921
18556
|
const path3 = getStorePath();
|
|
17922
|
-
if (!
|
|
18557
|
+
if (!existsSync6(path3))
|
|
17923
18558
|
return {};
|
|
17924
18559
|
try {
|
|
17925
|
-
const data =
|
|
18560
|
+
const data = readFileSync5(path3, "utf-8");
|
|
17926
18561
|
return JSON.parse(data);
|
|
17927
18562
|
} catch (e) {
|
|
17928
18563
|
console.error("[sessionStore] read failed:", e.message);
|
|
@@ -17933,12 +18568,12 @@ function writeStore(store) {
|
|
|
17933
18568
|
const path3 = getStorePath();
|
|
17934
18569
|
const tmp = `${path3}.tmp`;
|
|
17935
18570
|
try {
|
|
17936
|
-
|
|
17937
|
-
|
|
18571
|
+
writeFileSync2(tmp, JSON.stringify(store, null, 2));
|
|
18572
|
+
renameSync2(tmp, path3);
|
|
17938
18573
|
} catch (e) {
|
|
17939
18574
|
console.error("[sessionStore] write failed:", e.message);
|
|
17940
18575
|
try {
|
|
17941
|
-
|
|
18576
|
+
writeFileSync2(path3, JSON.stringify(store, null, 2));
|
|
17942
18577
|
} catch (directWriteError) {
|
|
17943
18578
|
console.error("[sessionStore] write failed:", directWriteError.message);
|
|
17944
18579
|
}
|
|
@@ -18044,7 +18679,7 @@ function listStoredSessions() {
|
|
|
18044
18679
|
function clearSharedSessions() {
|
|
18045
18680
|
const path3 = getStorePath();
|
|
18046
18681
|
try {
|
|
18047
|
-
|
|
18682
|
+
writeFileSync2(path3, "{}");
|
|
18048
18683
|
} catch (e) {
|
|
18049
18684
|
console.error("[sessionStore] clear failed:", e.message);
|
|
18050
18685
|
}
|
|
@@ -18473,8 +19108,8 @@ function createProxyServer(config = {}) {
|
|
|
18473
19108
|
pendingSessionStores.delete(key);
|
|
18474
19109
|
};
|
|
18475
19110
|
};
|
|
18476
|
-
const pluginDir = finalConfig.pluginDir ??
|
|
18477
|
-
const pluginConfigPath = finalConfig.pluginConfigPath ??
|
|
19111
|
+
const pluginDir = finalConfig.pluginDir ?? join7(homedir6(), ".config", "meridian", "plugins");
|
|
19112
|
+
const pluginConfigPath = finalConfig.pluginConfigPath ?? join7(homedir6(), ".config", "meridian", "plugins.json");
|
|
18478
19113
|
let loadedPlugins = [];
|
|
18479
19114
|
let pluginTransforms = [];
|
|
18480
19115
|
const app = new Hono2;
|
|
@@ -19391,6 +20026,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
19391
20026
|
requestId: requestMeta.requestId,
|
|
19392
20027
|
timestamp: Date.now(),
|
|
19393
20028
|
adapter: adapter.name,
|
|
20029
|
+
profileId: profile.id,
|
|
19394
20030
|
requestSource,
|
|
19395
20031
|
model,
|
|
19396
20032
|
requestModel: body.model || undefined,
|
|
@@ -20202,6 +20838,7 @@ data: {"type":"message_stop"}
|
|
|
20202
20838
|
requestId: requestMeta.requestId,
|
|
20203
20839
|
timestamp: Date.now(),
|
|
20204
20840
|
adapter: adapter.name,
|
|
20841
|
+
profileId: profile.id,
|
|
20205
20842
|
requestSource,
|
|
20206
20843
|
model,
|
|
20207
20844
|
requestModel: body.model || undefined,
|
|
@@ -20335,6 +20972,7 @@ data: {"type":"message_stop"}
|
|
|
20335
20972
|
requestId: requestMeta.requestId,
|
|
20336
20973
|
timestamp: Date.now(),
|
|
20337
20974
|
adapter: adapter.name,
|
|
20975
|
+
profileId: profile.id,
|
|
20338
20976
|
requestSource,
|
|
20339
20977
|
model,
|
|
20340
20978
|
requestModel: body.model || undefined,
|
|
@@ -20379,6 +21017,7 @@ data: {"type":"message_stop"}
|
|
|
20379
21017
|
requestId: requestMeta.requestId,
|
|
20380
21018
|
timestamp: Date.now(),
|
|
20381
21019
|
adapter: adapter.name,
|
|
21020
|
+
profileId: profile.id,
|
|
20382
21021
|
requestSource,
|
|
20383
21022
|
model,
|
|
20384
21023
|
requestModel: body.model || undefined,
|
|
@@ -20534,6 +21173,31 @@ data: ${JSON.stringify({
|
|
|
20534
21173
|
resetAdapterFeatures2(adapter);
|
|
20535
21174
|
return c.json({ ok: true });
|
|
20536
21175
|
});
|
|
21176
|
+
app.get("/settings/api/pricing", (c) => {
|
|
21177
|
+
const { BUILTIN_MODEL_PRICING: BUILTIN_MODEL_PRICING2 } = (init_pricing(), __toCommonJS(exports_pricing));
|
|
21178
|
+
const { getPricingOverrides: getPricingOverrides2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
|
|
21179
|
+
return c.json({ builtin: BUILTIN_MODEL_PRICING2, overrides: getPricingOverrides2() });
|
|
21180
|
+
});
|
|
21181
|
+
app.put("/settings/api/pricing/:model", async (c) => {
|
|
21182
|
+
const { validatePricingUpdate: validatePricingUpdate2, setPricingOverride: setPricingOverride2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
|
|
21183
|
+
const model = c.req.param("model");
|
|
21184
|
+
try {
|
|
21185
|
+
const body = await c.req.json();
|
|
21186
|
+
setPricingOverride2(model, validatePricingUpdate2(body));
|
|
21187
|
+
} catch (e) {
|
|
21188
|
+
return c.json({ error: e.message }, 400);
|
|
21189
|
+
}
|
|
21190
|
+
return c.json({ ok: true });
|
|
21191
|
+
});
|
|
21192
|
+
app.delete("/settings/api/pricing/:model", (c) => {
|
|
21193
|
+
const { deletePricingOverride: deletePricingOverride2 } = (init_pricingStore(), __toCommonJS(exports_pricingStore));
|
|
21194
|
+
try {
|
|
21195
|
+
deletePricingOverride2(c.req.param("model"));
|
|
21196
|
+
} catch (e) {
|
|
21197
|
+
return c.json({ error: e.message }, 400);
|
|
21198
|
+
}
|
|
21199
|
+
return c.json({ ok: true });
|
|
21200
|
+
});
|
|
20537
21201
|
app.get("/metrics", (c) => {
|
|
20538
21202
|
const body = renderPrometheusMetrics(telemetryStore2);
|
|
20539
21203
|
return c.body(body, 200, {
|
|
@@ -20606,7 +21270,7 @@ data: ${JSON.stringify({
|
|
|
20606
21270
|
});
|
|
20607
21271
|
});
|
|
20608
21272
|
app.get("/profiles", async (c) => {
|
|
20609
|
-
const { profilePageHtml } = await import("./profilePage-
|
|
21273
|
+
const { profilePageHtml } = await import("./profilePage-naychnb8.js");
|
|
20610
21274
|
return c.html(profilePageHtml);
|
|
20611
21275
|
});
|
|
20612
21276
|
app.post("/profiles/active", async (c) => {
|
|
@@ -20668,7 +21332,7 @@ data: ${JSON.stringify({
|
|
|
20668
21332
|
}
|
|
20669
21333
|
});
|
|
20670
21334
|
app.get("/plugins", async (c) => {
|
|
20671
|
-
const { pluginPageHtml } = await import("./pluginPage-
|
|
21335
|
+
const { pluginPageHtml } = await import("./pluginPage-03q5j753.js");
|
|
20672
21336
|
return c.html(pluginPageHtml);
|
|
20673
21337
|
});
|
|
20674
21338
|
app.post("/auth/refresh", async (c) => {
|