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