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