@rynfar/meridian 1.29.1 → 1.30.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 +18 -39
- package/assets/bot-icon-512.png +0 -0
- package/assets/bot-icon.svg +54 -0
- package/assets/icon-512.png +0 -0
- package/assets/logo-512.png +0 -0
- package/dist/{cli-msyx6dnk.js → cli-b2n69dg7.js} +254 -21
- package/dist/cli.js +2 -2
- package/dist/{profileCli-pdqrpw0m.js → profileCli-5e3p99k0.js} +1 -1
- package/dist/proxy/adapter.d.ts +9 -0
- package/dist/proxy/adapter.d.ts.map +1 -1
- package/dist/proxy/adapters/opencode.d.ts.map +1 -1
- package/dist/proxy/betas.d.ts +70 -0
- package/dist/proxy/betas.d.ts.map +1 -0
- package/dist/proxy/passthroughTools.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/tokenHealth.d.ts +32 -0
- package/dist/proxy/tokenHealth.d.ts.map +1 -0
- package/dist/server.js +1 -1
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/logStore.d.ts +1 -1
- package/dist/telemetry/logStore.d.ts.map +1 -1
- package/dist/telemetry/store.d.ts +3 -0
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +21 -0
- package/dist/telemetry/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,31 +7,34 @@
|
|
|
7
7
|
<a href="https://www.npmjs.com/package/@rynfar/meridian"><img src="https://img.shields.io/npm/v/@rynfar/meridian?style=flat-square&color=8b5cf6&label=npm" alt="npm"></a>
|
|
8
8
|
<a href="#"><img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-a78bfa?style=flat-square" alt="Platform"></a>
|
|
9
9
|
<a href="#"><img src="https://img.shields.io/badge/license-MIT-c4b5fd?style=flat-square" alt="License"></a>
|
|
10
|
+
<a href="https://discord.gg/7vNVFYBz"><img src="https://img.shields.io/badge/discord-join-5865F2?style=flat-square&logo=discord&logoColor=white" alt="Discord"></a>
|
|
10
11
|
</p>
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
|
-
Meridian bridges the Claude Code SDK to the standard Anthropic API. No OAuth interception. No binary patches. No hacks. Just pure, documented SDK calls. Any tool that speaks the Anthropic or OpenAI protocol — OpenCode,
|
|
15
|
+
Meridian bridges the Claude Code SDK to the standard Anthropic API. No OAuth interception. No binary patches. No hacks. Just pure, documented SDK calls. Any tool that speaks the Anthropic or OpenAI protocol — OpenCode, Crush, Cline, Aider, Pi, Droid, Open WebUI — connects to Meridian and gets Claude, with session management, streaming, and prompt caching handled natively by the SDK.
|
|
15
16
|
|
|
16
|
-
> [!
|
|
17
|
-
> ### Meridian
|
|
17
|
+
> [!NOTE]
|
|
18
|
+
> ### How Meridian works with Anthropic
|
|
18
19
|
>
|
|
19
|
-
>
|
|
20
|
+
> Meridian is built entirely on the [Claude Code SDK](https://docs.anthropic.com/en/docs/claude-code/sdk). Every request flows through `query()` — the same documented function Anthropic provides for programmatic access. No OAuth tokens are extracted, no binaries are patched, nothing is reverse-engineered.
|
|
20
21
|
>
|
|
21
|
-
>
|
|
22
|
+
> Because we use the SDK, Anthropic remains in full control of prompt caching, context window management, compaction, rate limiting, and authentication. Meridian doesn't bypass these mechanisms — it depends on them. Max subscription tokens flow through the correct channel, governed by the same guardrails Anthropic built into Claude Code.
|
|
22
23
|
>
|
|
23
|
-
>
|
|
24
|
-
> - **Real Claude Code sessions.** The SDK spawns the actual Claude Code process, manages its own authentication, and handles all communication with Anthropic's servers. Meridian's traffic doesn't *look like* Claude Code — it *is* Claude Code.
|
|
25
|
-
> - **Documented API surface only.** Session resume, MCP tool servers, agent definitions, thinking configuration, permission modes, tool blocking — every feature Meridian uses is a published, documented SDK option. Nothing is reverse-engineered or patched.
|
|
26
|
-
> - **Native benefits and controls preserved.** Prompt caching, conversation persistence, context window management, and compaction all function exactly as they do in Claude Code — because the SDK manages them directly. This means Anthropic's engineering investments in efficiency and their rate-limiting controls work as designed. Max subscription tokens flow through the correct channel, governed by the same guardrails Anthropic built into Claude Code. Meridian doesn't bypass these mechanisms; it depends on them.
|
|
24
|
+
> What Meridian adds is a **presentation and interoperability layer**. We translate Claude Code's output into the standard Anthropic API format so developers can connect the editors, terminals, and workflows they prefer. The SDK does the work; Meridian formats the result.
|
|
27
25
|
>
|
|
28
|
-
>
|
|
26
|
+
> If you're looking for a tool that circumvents usage limits or bypasses Anthropic's controls, this project is not for you. We play nice with the SDK because we believe that's how developers can continue to choose their own frontends while respecting Anthropic's platform.
|
|
27
|
+
|
|
28
|
+
> [!WARNING]
|
|
29
|
+
> ### Why Meridian does not support OpenClaw
|
|
30
|
+
>
|
|
31
|
+
> There is technically a way to make Meridian work with OpenClaw, but we're not interested in pursuing it.
|
|
29
32
|
>
|
|
30
|
-
>
|
|
33
|
+
> The reason Claude Max offers generous usage limits is because Anthropic can justify it through Claude Code — their harness, their optimizations, their control. OpenClaw blows through that with autonomous workflows that Anthropic has little ability to manage or optimize. Using Opus to check an email when a local model would handle it fine isn't efficient use — it's waste that degrades the plan for everyone.
|
|
31
34
|
>
|
|
32
|
-
>
|
|
35
|
+
> I built Meridian because I believe developers should have the right to use the frontend of their choice. But that right comes with a responsibility: don't wreck the subscription for the rest of us. Sloppy autonomous agents that burn through Claude Max tokens are directly counter-productive to developers like me who depend on the plan being sustainable.
|
|
33
36
|
>
|
|
34
|
-
>
|
|
37
|
+
> Meridian's philosophy is simple — play nice with the SDK, let Anthropic optimize how they see fit, and use the frontend you want within the constraints of Claude Code. OpenClaw is not just a frontend; it's an autonomous system that abuses the Max plan. We won't be supporting it.
|
|
35
38
|
|
|
36
39
|
## Quick Start
|
|
37
40
|
|
|
@@ -77,7 +80,7 @@ The Claude Code SDK provides programmatic access to Claude. But your favorite co
|
|
|
77
80
|
- **Passthrough mode** — forward tool calls to the client instead of executing internally
|
|
78
81
|
- **Multimodal** — images, documents, and file attachments pass through to Claude
|
|
79
82
|
- **Multi-profile** — switch between Claude accounts instantly, no restart needed
|
|
80
|
-
- **Telemetry dashboard** — real-time performance metrics at `/telemetry`
|
|
83
|
+
- **Telemetry dashboard** — real-time performance metrics at `/telemetry`, including token usage and prompt cache efficiency ([`MONITORING.md`](MONITORING.md))
|
|
81
84
|
|
|
82
85
|
## Multi-Profile Support
|
|
83
86
|
|
|
@@ -290,29 +293,6 @@ MERIDIAN_DEFAULT_AGENT=pi meridian
|
|
|
290
293
|
|
|
291
294
|
Pi mimics Claude Code's User-Agent, so automatic detection isn't possible. The `MERIDIAN_DEFAULT_AGENT` env var tells Meridian to use the pi adapter for all unrecognized requests. If you run other agents alongside pi, use the `x-meridian-agent: pi` header instead (requires pi-ai support for custom headers).
|
|
292
295
|
|
|
293
|
-
### OpenClaw
|
|
294
|
-
|
|
295
|
-
OpenClaw uses `@mariozechner/pi-ai` under the hood, so the pi adapter handles it with no additional code. Add a provider override in `~/.openclaw/openclaw.json`:
|
|
296
|
-
|
|
297
|
-
```json
|
|
298
|
-
{
|
|
299
|
-
"models": {
|
|
300
|
-
"providers": {
|
|
301
|
-
"anthropic": {
|
|
302
|
-
"baseUrl": "http://127.0.0.1:3456",
|
|
303
|
-
"apiKey": "dummy",
|
|
304
|
-
"models": [
|
|
305
|
-
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6 (Meridian)" },
|
|
306
|
-
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6 (Meridian)" }
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
Then start Meridian with the pi adapter: `MERIDIAN_DEFAULT_AGENT=pi meridian`
|
|
315
|
-
|
|
316
296
|
### Any Anthropic-compatible tool
|
|
317
297
|
|
|
318
298
|
```bash
|
|
@@ -331,7 +311,6 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:3456
|
|
|
331
311
|
| [Aider](https://github.com/paul-gauthier/aider) | ✅ Verified | Env vars — file editing, streaming; `--no-stream` broken (litellm bug) |
|
|
332
312
|
| [Open WebUI](https://github.com/open-webui/open-webui) | ✅ Verified | OpenAI-compatible endpoints — set base URL to `http://127.0.0.1:3456` |
|
|
333
313
|
| [Pi](https://github.com/mariozechner/pi-coding-agent) | ✅ Verified | models.json config (see above) — requires `MERIDIAN_DEFAULT_AGENT=pi` |
|
|
334
|
-
| [OpenClaw](https://github.com/openclaw/openclaw) | ✅ Verified | Provider config (see above) — uses pi adapter via `MERIDIAN_DEFAULT_AGENT=pi` |
|
|
335
314
|
| [Continue](https://github.com/continuedev/continue) | 🔲 Untested | OpenAI-compatible endpoints should work — set `apiBase` to `http://127.0.0.1:3456` |
|
|
336
315
|
|
|
337
316
|
Tested an agent or built a plugin? [Open an issue](https://github.com/rynfar/meridian/issues) and we'll add it.
|
|
@@ -529,7 +508,7 @@ You haven't run `meridian setup`. Without the plugin, OpenCode requests won't ha
|
|
|
529
508
|
|
|
530
509
|
## Contributing
|
|
531
510
|
|
|
532
|
-
Issues and PRs welcome. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for module structure and dependency rules, [`CLAUDE.md`](CLAUDE.md) for coding guidelines,
|
|
511
|
+
Issues and PRs welcome. Join the [Discord](https://discord.gg/7vNVFYBz) to discuss ideas before opening issues. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for module structure and dependency rules, [`CLAUDE.md`](CLAUDE.md) for coding guidelines, [`E2E.md`](E2E.md) for end-to-end test procedures, and [`MONITORING.md`](MONITORING.md) for understanding token usage and prompt cache health.
|
|
533
512
|
|
|
534
513
|
## License
|
|
535
514
|
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
2
|
+
<rect width="512" height="512" rx="112" fill="#1C1830"/>
|
|
3
|
+
|
|
4
|
+
<!-- Antenna -->
|
|
5
|
+
<line x1="256" y1="72" x2="256" y2="115" stroke="#A78BFA" stroke-width="8" stroke-linecap="round"/>
|
|
6
|
+
<circle cx="256" cy="64" r="14" fill="#C4B5FD"/>
|
|
7
|
+
<!-- Antenna signal arcs -->
|
|
8
|
+
<path d="M228 52 A32 32 0 0 1 284 52" fill="none" stroke="#C4B5FD" stroke-width="3" opacity="0.4" stroke-linecap="round"/>
|
|
9
|
+
<path d="M216 40 A44 44 0 0 1 296 40" fill="none" stroke="#C4B5FD" stroke-width="2.5" opacity="0.25" stroke-linecap="round"/>
|
|
10
|
+
|
|
11
|
+
<!-- Robot head -->
|
|
12
|
+
<rect x="128" y="115" width="256" height="200" rx="40" fill="#2D2750"/>
|
|
13
|
+
<rect x="128" y="115" width="256" height="200" rx="40" fill="none" stroke="#8B7CF6" stroke-width="4"/>
|
|
14
|
+
|
|
15
|
+
<!-- Eyes — screens with Meridian purple glow -->
|
|
16
|
+
<rect x="170" y="165" width="68" height="52" rx="14" fill="#1C1830" stroke="#8B7CF6" stroke-width="3"/>
|
|
17
|
+
<rect x="274" y="165" width="68" height="52" rx="14" fill="#1C1830" stroke="#8B7CF6" stroke-width="3"/>
|
|
18
|
+
<!-- Eye glow -->
|
|
19
|
+
<circle cx="204" cy="191" r="16" fill="#8B7CF6" opacity="0.8"/>
|
|
20
|
+
<circle cx="308" cy="191" r="16" fill="#8B7CF6" opacity="0.8"/>
|
|
21
|
+
<!-- Eye highlights -->
|
|
22
|
+
<circle cx="210" cy="185" r="5" fill="#C4B5FD" opacity="0.6"/>
|
|
23
|
+
<circle cx="314" cy="185" r="5" fill="#C4B5FD" opacity="0.6"/>
|
|
24
|
+
|
|
25
|
+
<!-- Mouth — friendly LED strip -->
|
|
26
|
+
<rect x="196" y="248" width="120" height="24" rx="12" fill="#1C1830" stroke="#8B7CF6" stroke-width="2"/>
|
|
27
|
+
<circle cx="220" cy="260" r="5" fill="#C4B5FD" opacity="0.7"/>
|
|
28
|
+
<circle cx="244" cy="260" r="5" fill="#8B7CF6"/>
|
|
29
|
+
<circle cx="268" cy="260" r="5" fill="#C4B5FD" opacity="0.7"/>
|
|
30
|
+
<circle cx="292" cy="260" r="5" fill="#8B7CF6" opacity="0.5"/>
|
|
31
|
+
|
|
32
|
+
<!-- Ear bolts -->
|
|
33
|
+
<circle cx="118" cy="200" r="16" fill="#2D2750" stroke="#8B7CF6" stroke-width="3"/>
|
|
34
|
+
<circle cx="118" cy="200" r="6" fill="#8B7CF6"/>
|
|
35
|
+
<circle cx="394" cy="200" r="16" fill="#2D2750" stroke="#8B7CF6" stroke-width="3"/>
|
|
36
|
+
<circle cx="394" cy="200" r="6" fill="#8B7CF6"/>
|
|
37
|
+
|
|
38
|
+
<!-- Neck -->
|
|
39
|
+
<rect x="228" y="315" width="56" height="28" rx="6" fill="#2D2750" stroke="#8B7CF6" stroke-width="2"/>
|
|
40
|
+
|
|
41
|
+
<!-- Body / chest plate -->
|
|
42
|
+
<rect x="148" y="343" width="216" height="120" rx="30" fill="#2D2750" stroke="#8B7CF6" stroke-width="4"/>
|
|
43
|
+
|
|
44
|
+
<!-- Meridian logo on chest — the globe axis + arcs -->
|
|
45
|
+
<line x1="256" y1="365" x2="256" y2="443" stroke="#8B7CF6" stroke-width="6" stroke-linecap="round"/>
|
|
46
|
+
<!-- Chest latitude arcs -->
|
|
47
|
+
<path d="M212 382 A48 48 0 0 1 300 382" fill="none" stroke="#C4B5FD" stroke-width="3" opacity="0.5"/>
|
|
48
|
+
<path d="M212 426 A48 48 0 0 0 300 426" fill="none" stroke="#C4B5FD" stroke-width="3" opacity="0.5"/>
|
|
49
|
+
<!-- Chest poles -->
|
|
50
|
+
<circle cx="256" cy="365" r="8" fill="#C4B5FD"/>
|
|
51
|
+
<circle cx="256" cy="443" r="8" fill="#C4B5FD"/>
|
|
52
|
+
<!-- Chest center node -->
|
|
53
|
+
<circle cx="256" cy="404" r="6" fill="#8B7CF6"/>
|
|
54
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
@@ -6220,7 +6220,8 @@ function jsonSchemaToZod(schema) {
|
|
|
6220
6220
|
function createPassthroughMcpServer(tools) {
|
|
6221
6221
|
const server = createSdkMcpServer({ name: PASSTHROUGH_MCP_NAME });
|
|
6222
6222
|
const toolNames = [];
|
|
6223
|
-
|
|
6223
|
+
const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
|
|
6224
|
+
for (const tool of sortedTools) {
|
|
6224
6225
|
try {
|
|
6225
6226
|
const zodSchema = tool.input_schema?.properties ? jsonSchemaToZod(tool.input_schema) : exports_external.object({});
|
|
6226
6227
|
const shape = zodSchema instanceof exports_external.ZodObject ? zodSchema.shape : { input: exports_external.any() };
|
|
@@ -6286,6 +6287,16 @@ class TelemetryStore {
|
|
|
6286
6287
|
}
|
|
6287
6288
|
return results;
|
|
6288
6289
|
}
|
|
6290
|
+
getLastForSession(sdkSessionId) {
|
|
6291
|
+
for (let i = 0;i < this.count; i++) {
|
|
6292
|
+
const idx = (this.head - 1 - i + this.capacity) % this.capacity;
|
|
6293
|
+
const metric = this.buffer[idx];
|
|
6294
|
+
if (metric && metric.sdkSessionId === sdkSessionId && metric.error === null) {
|
|
6295
|
+
return metric;
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6289
6300
|
summarize(windowMs = 60 * 60 * 1000) {
|
|
6290
6301
|
const since = Date.now() - windowMs;
|
|
6291
6302
|
const metrics = this.getRecent({ limit: this.capacity, since });
|
|
@@ -6302,7 +6313,15 @@ class TelemetryStore {
|
|
|
6302
6313
|
upstreamDuration: emptyPhase,
|
|
6303
6314
|
totalDuration: emptyPhase,
|
|
6304
6315
|
byModel: {},
|
|
6305
|
-
byMode: {}
|
|
6316
|
+
byMode: {},
|
|
6317
|
+
tokenUsage: {
|
|
6318
|
+
totalInputTokens: 0,
|
|
6319
|
+
totalOutputTokens: 0,
|
|
6320
|
+
totalCacheReadTokens: 0,
|
|
6321
|
+
totalCacheCreationTokens: 0,
|
|
6322
|
+
avgCacheHitRate: 0,
|
|
6323
|
+
cacheMissOnResumeCount: 0
|
|
6324
|
+
}
|
|
6306
6325
|
};
|
|
6307
6326
|
}
|
|
6308
6327
|
const errorCount = metrics.filter((m) => m.error !== null).length;
|
|
@@ -6328,6 +6347,26 @@ class TelemetryStore {
|
|
|
6328
6347
|
entry.count++;
|
|
6329
6348
|
entry.totalMs += m.totalDurationMs;
|
|
6330
6349
|
}
|
|
6350
|
+
let totalInputTokens = 0;
|
|
6351
|
+
let totalOutputTokens = 0;
|
|
6352
|
+
let totalCacheReadTokens = 0;
|
|
6353
|
+
let totalCacheCreationTokens = 0;
|
|
6354
|
+
let cacheHitRateSum = 0;
|
|
6355
|
+
let cacheHitRateCount = 0;
|
|
6356
|
+
let cacheMissOnResumeCount = 0;
|
|
6357
|
+
for (const m of metrics) {
|
|
6358
|
+
totalInputTokens += m.inputTokens ?? 0;
|
|
6359
|
+
totalOutputTokens += m.outputTokens ?? 0;
|
|
6360
|
+
totalCacheReadTokens += m.cacheReadInputTokens ?? 0;
|
|
6361
|
+
totalCacheCreationTokens += m.cacheCreationInputTokens ?? 0;
|
|
6362
|
+
if (m.cacheHitRate !== undefined) {
|
|
6363
|
+
cacheHitRateSum += m.cacheHitRate;
|
|
6364
|
+
cacheHitRateCount++;
|
|
6365
|
+
}
|
|
6366
|
+
if (m.isResume && m.cacheHitRate !== undefined && m.cacheHitRate === 0) {
|
|
6367
|
+
cacheMissOnResumeCount++;
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6331
6370
|
return {
|
|
6332
6371
|
windowMs,
|
|
6333
6372
|
totalRequests: metrics.length,
|
|
@@ -6339,7 +6378,15 @@ class TelemetryStore {
|
|
|
6339
6378
|
upstreamDuration: computePercentiles(upstreams),
|
|
6340
6379
|
totalDuration: computePercentiles(totals),
|
|
6341
6380
|
byModel: Object.fromEntries(Object.entries(byModel).map(([k, v]) => [k, { count: v.count, avgTotalMs: Math.round(v.totalMs / v.count) }])),
|
|
6342
|
-
byMode: Object.fromEntries(Object.entries(byMode).map(([k, v]) => [k, { count: v.count, avgTotalMs: Math.round(v.totalMs / v.count) }]))
|
|
6381
|
+
byMode: Object.fromEntries(Object.entries(byMode).map(([k, v]) => [k, { count: v.count, avgTotalMs: Math.round(v.totalMs / v.count) }])),
|
|
6382
|
+
tokenUsage: {
|
|
6383
|
+
totalInputTokens,
|
|
6384
|
+
totalOutputTokens,
|
|
6385
|
+
totalCacheReadTokens,
|
|
6386
|
+
totalCacheCreationTokens,
|
|
6387
|
+
avgCacheHitRate: cacheHitRateCount > 0 ? Math.round(cacheHitRateSum / cacheHitRateCount * 100) / 100 : 0,
|
|
6388
|
+
cacheMissOnResumeCount
|
|
6389
|
+
}
|
|
6343
6390
|
};
|
|
6344
6391
|
}
|
|
6345
6392
|
clear() {
|
|
@@ -6590,7 +6637,7 @@ function render(s, reqs, logs) {
|
|
|
6590
6637
|
// Count lineage types for badges
|
|
6591
6638
|
const lineageCounts = {};
|
|
6592
6639
|
for (const r of reqs) { const t = r.lineageType || 'unknown'; lineageCounts[t] = (lineageCounts[t] || 0) + 1; }
|
|
6593
|
-
const logCounts = { session: 0, lineage: 0, error: 0 };
|
|
6640
|
+
const logCounts = { session: 0, lineage: 0, error: 0, token: 0 };
|
|
6594
6641
|
for (const l of logs) { if (logCounts[l.category] !== undefined) logCounts[l.category]++; }
|
|
6595
6642
|
|
|
6596
6643
|
// Tabs
|
|
@@ -6615,6 +6662,20 @@ function render(s, reqs, logs) {
|
|
|
6615
6662
|
+ card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))
|
|
6616
6663
|
+ '</div>';
|
|
6617
6664
|
|
|
6665
|
+
// Token usage cards
|
|
6666
|
+
if (s.tokenUsage) {
|
|
6667
|
+
const t = s.tokenUsage;
|
|
6668
|
+
const fmtTok = n => n > 1000000 ? (n/1000000).toFixed(1) + 'M' : n > 1000 ? Math.round(n/1000) + 'k' : String(n);
|
|
6669
|
+
html += '<div class="section"><div class="section-title">Token Usage</div></div>';
|
|
6670
|
+
html += '<div class="cards">'
|
|
6671
|
+
+ card('Input Tokens', fmtTok(t.totalInputTokens), '')
|
|
6672
|
+
+ card('Output Tokens', fmtTok(t.totalOutputTokens), '')
|
|
6673
|
+
+ card('Cache Read', fmtTok(t.totalCacheReadTokens), '')
|
|
6674
|
+
+ card('Cache Write', fmtTok(t.totalCacheCreationTokens), '')
|
|
6675
|
+
+ card('Avg Cache Hit', (t.avgCacheHitRate * 100).toFixed(0) + '%', t.cacheMissOnResumeCount > 0 ? t.cacheMissOnResumeCount + ' cache miss on resume' : '')
|
|
6676
|
+
+ '</div>';
|
|
6677
|
+
}
|
|
6678
|
+
|
|
6618
6679
|
// Model breakdown
|
|
6619
6680
|
const models = Object.entries(s.byModel);
|
|
6620
6681
|
if (models.length > 0) {
|
|
@@ -6658,7 +6719,7 @@ function render(s, reqs, logs) {
|
|
|
6658
6719
|
+ '<span><span class="legend-dot" style="background:var(--upstream)"></span>Response</span>'
|
|
6659
6720
|
+ '</div>'
|
|
6660
6721
|
+ '<table><thead><tr><th>Time</th><th>Adapter</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'
|
|
6661
|
-
+ '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';
|
|
6722
|
+
+ '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Tokens</th><th>Cache</th><th>Waterfall</th></tr></thead><tbody>';
|
|
6662
6723
|
|
|
6663
6724
|
const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);
|
|
6664
6725
|
|
|
@@ -6686,6 +6747,8 @@ function render(s, reqs, logs) {
|
|
|
6686
6747
|
+ '<td class="mono">' + ms(r.proxyOverheadMs) + '</td>'
|
|
6687
6748
|
+ '<td class="mono">' + ms(r.ttfbMs) + '</td>'
|
|
6688
6749
|
+ '<td class="mono">' + ms(r.totalDurationMs) + '</td>'
|
|
6750
|
+
+ '<td class="mono">' + (r.inputTokens != null ? (r.inputTokens > 1000 ? Math.round(r.inputTokens/1000) + 'k' : r.inputTokens) + ' in<br>' + (r.outputTokens > 1000 ? Math.round(r.outputTokens/1000) + 'k' : r.outputTokens || 0) + ' out' : '—') + '</td>'
|
|
6751
|
+
+ '<td class="mono">' + (r.cacheHitRate != null ? '<span style="color:' + (r.cacheHitRate > 0.5 ? 'var(--green)' : r.cacheHitRate > 0 ? 'var(--yellow)' : 'var(--red)') + '">' + Math.round(r.cacheHitRate * 100) + '%</span>' : '—') + '</td>'
|
|
6689
6752
|
+ '<td><div class="waterfall">'
|
|
6690
6753
|
+ '<div class="waterfall-seg queue" style="width:' + qW + 'px"></div>'
|
|
6691
6754
|
+ '<div class="waterfall-seg overhead" style="width:' + ohW + 'px"></div>'
|
|
@@ -6706,6 +6769,7 @@ function render(s, reqs, logs) {
|
|
|
6706
6769
|
+ '<span class="log-filter' + (activeLogFilter === 'session' ? ' active' : '') + '" data-filter="session" onclick="setLogFilter('session')" style="--accent:var(--blue)">Session<span class="tab-badge">' + logCounts.session + '</span></span>'
|
|
6707
6770
|
+ '<span class="log-filter' + (activeLogFilter === 'lineage' ? ' active' : '') + '" data-filter="lineage" onclick="setLogFilter('lineage')" style="--accent:var(--purple)">Lineage<span class="tab-badge">' + logCounts.lineage + '</span></span>'
|
|
6708
6771
|
+ '<span class="log-filter' + (activeLogFilter === 'error' ? ' active' : '') + '" data-filter="error" onclick="setLogFilter('error')" style="--accent:var(--red)">Error<span class="tab-badge">' + logCounts.error + '</span></span>'
|
|
6772
|
+
+ '<span class="log-filter' + (activeLogFilter === 'token' ? ' active' : '') + '" data-filter="token" onclick="setLogFilter('token')" style="--accent:var(--yellow)">Token<span class="tab-badge">' + logCounts.token + '</span></span>'
|
|
6709
6773
|
+ '</div>';
|
|
6710
6774
|
|
|
6711
6775
|
if (logs.length === 0) {
|
|
@@ -6717,7 +6781,7 @@ function render(s, reqs, logs) {
|
|
|
6717
6781
|
|
|
6718
6782
|
for (const log of logs) {
|
|
6719
6783
|
const levelColor = {info:'var(--green)',warn:'var(--yellow)',error:'var(--red)'}[log.level] || 'var(--muted)';
|
|
6720
|
-
const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)'}[log.category] || 'var(--muted)';
|
|
6784
|
+
const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)',token:'var(--yellow)'}[log.category] || 'var(--muted)';
|
|
6721
6785
|
const display = (activeLogFilter === 'all' || log.category === activeLogFilter) ? '' : 'display:none';
|
|
6722
6786
|
html += '<tr class="log-row" data-category="' + log.category + '" style="' + display + '">'
|
|
6723
6787
|
+ '<td class="mono">' + ago(log.timestamp) + '</td>'
|
|
@@ -7702,6 +7766,16 @@ var openCodeAdapter = {
|
|
|
7702
7766
|
getAllowedMcpTools() {
|
|
7703
7767
|
return ALLOWED_MCP_TOOLS;
|
|
7704
7768
|
},
|
|
7769
|
+
usesPassthrough() {
|
|
7770
|
+
const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
|
|
7771
|
+
if (envVal === "0" || envVal === "false" || envVal === "no") {
|
|
7772
|
+
return false;
|
|
7773
|
+
}
|
|
7774
|
+
return true;
|
|
7775
|
+
},
|
|
7776
|
+
supportsThinking() {
|
|
7777
|
+
return true;
|
|
7778
|
+
},
|
|
7705
7779
|
buildSdkAgents(body, mcpToolNames) {
|
|
7706
7780
|
if (!Array.isArray(body.tools))
|
|
7707
7781
|
return {};
|
|
@@ -13833,6 +13907,94 @@ function buildQueryOptions(ctx) {
|
|
|
13833
13907
|
};
|
|
13834
13908
|
}
|
|
13835
13909
|
|
|
13910
|
+
// src/proxy/betas.ts
|
|
13911
|
+
var BILLABLE_BETA_PREFIXES_ON_MAX = [
|
|
13912
|
+
"extended-cache-ttl-"
|
|
13913
|
+
];
|
|
13914
|
+
var DEFAULT_BETA_POLICY = "allow-safe";
|
|
13915
|
+
function getBetaPolicyFromEnv() {
|
|
13916
|
+
const raw2 = process.env.MERIDIAN_BETA_POLICY;
|
|
13917
|
+
if (raw2 === "allow-safe" || raw2 === "strip-all" || raw2 === "allow-all") {
|
|
13918
|
+
return raw2;
|
|
13919
|
+
}
|
|
13920
|
+
return DEFAULT_BETA_POLICY;
|
|
13921
|
+
}
|
|
13922
|
+
function filterBetasForProfile(rawBetaHeader, profileType, policy = DEFAULT_BETA_POLICY) {
|
|
13923
|
+
if (!rawBetaHeader) {
|
|
13924
|
+
return { forwarded: undefined, stripped: [] };
|
|
13925
|
+
}
|
|
13926
|
+
const parsed = rawBetaHeader.split(",").map((b) => b.trim()).filter(Boolean);
|
|
13927
|
+
if (parsed.length === 0) {
|
|
13928
|
+
return { forwarded: undefined, stripped: [] };
|
|
13929
|
+
}
|
|
13930
|
+
if (profileType === "api") {
|
|
13931
|
+
return { forwarded: parsed, stripped: [] };
|
|
13932
|
+
}
|
|
13933
|
+
if (policy === "allow-all") {
|
|
13934
|
+
return { forwarded: parsed, stripped: [] };
|
|
13935
|
+
}
|
|
13936
|
+
if (policy === "strip-all") {
|
|
13937
|
+
return { forwarded: undefined, stripped: parsed };
|
|
13938
|
+
}
|
|
13939
|
+
const forwarded = [];
|
|
13940
|
+
const stripped = [];
|
|
13941
|
+
for (const beta of parsed) {
|
|
13942
|
+
if (BILLABLE_BETA_PREFIXES_ON_MAX.some((prefix) => beta.startsWith(prefix))) {
|
|
13943
|
+
stripped.push(beta);
|
|
13944
|
+
} else {
|
|
13945
|
+
forwarded.push(beta);
|
|
13946
|
+
}
|
|
13947
|
+
}
|
|
13948
|
+
return {
|
|
13949
|
+
forwarded: forwarded.length > 0 ? forwarded : undefined,
|
|
13950
|
+
stripped
|
|
13951
|
+
};
|
|
13952
|
+
}
|
|
13953
|
+
|
|
13954
|
+
// src/proxy/tokenHealth.ts
|
|
13955
|
+
var CONTEXT_SPIKE_THRESHOLD = 0.6;
|
|
13956
|
+
var CACHE_MISS_THRESHOLD = 0.05;
|
|
13957
|
+
var OUTPUT_EXPLOSION_RATIO = 2;
|
|
13958
|
+
var fmt = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
|
|
13959
|
+
function detectTokenAnomalies(current, previous) {
|
|
13960
|
+
const anomalies = [];
|
|
13961
|
+
if (previous && previous.inputTokens > 0) {
|
|
13962
|
+
const growth = (current.inputTokens - previous.inputTokens) / previous.inputTokens;
|
|
13963
|
+
if (growth > CONTEXT_SPIKE_THRESHOLD) {
|
|
13964
|
+
const pct = Math.round(growth * 100);
|
|
13965
|
+
anomalies.push({
|
|
13966
|
+
type: "context_spike",
|
|
13967
|
+
severity: growth > 2 ? "critical" : "warn",
|
|
13968
|
+
detail: `Input tokens grew ${pct}% in one turn (${fmt(previous.inputTokens)} -> ${fmt(current.inputTokens)}). Possible context leak or full replay.`
|
|
13969
|
+
});
|
|
13970
|
+
}
|
|
13971
|
+
}
|
|
13972
|
+
if (current.isResume && current.cacheHitRate <= CACHE_MISS_THRESHOLD && current.inputTokens > 0) {
|
|
13973
|
+
anomalies.push({
|
|
13974
|
+
type: "cache_miss",
|
|
13975
|
+
severity: "critical",
|
|
13976
|
+
detail: `Cache hit rate ${Math.round(current.cacheHitRate * 100)}% on resume (expected >50%). Prompt caching likely invalidated — check tool ordering or system prompt changes.`
|
|
13977
|
+
});
|
|
13978
|
+
}
|
|
13979
|
+
if (previous && previous.outputTokens > 0 && current.outputTokens > 0) {
|
|
13980
|
+
const ratio = current.outputTokens / previous.outputTokens;
|
|
13981
|
+
if (ratio > OUTPUT_EXPLOSION_RATIO && current.outputTokens > 2000) {
|
|
13982
|
+
anomalies.push({
|
|
13983
|
+
type: "output_explosion",
|
|
13984
|
+
severity: "warn",
|
|
13985
|
+
detail: `Output tokens ${fmt(current.outputTokens)} are ${ratio.toFixed(1)}x the previous turn (${fmt(previous.outputTokens)}).`
|
|
13986
|
+
});
|
|
13987
|
+
}
|
|
13988
|
+
}
|
|
13989
|
+
return anomalies;
|
|
13990
|
+
}
|
|
13991
|
+
function formatAnomalyAlerts(requestId, anomalies) {
|
|
13992
|
+
return anomalies.map((a) => {
|
|
13993
|
+
const icon = a.severity === "critical" ? "TOKEN ALERT" : "TOKEN WARN";
|
|
13994
|
+
return `[PROXY] ${requestId} ${icon}: ${a.detail}`;
|
|
13995
|
+
});
|
|
13996
|
+
}
|
|
13997
|
+
|
|
13836
13998
|
// src/proxy/session/lineage.ts
|
|
13837
13999
|
import { createHash as createHash2 } from "crypto";
|
|
13838
14000
|
var MIN_SUFFIX_FOR_COMPACTION = 2;
|
|
@@ -14511,14 +14673,72 @@ function buildFreshPrompt(messages, stripCacheControl) {
|
|
|
14511
14673
|
`) || "";
|
|
14512
14674
|
}
|
|
14513
14675
|
function logUsage(requestId, usage) {
|
|
14514
|
-
const
|
|
14676
|
+
const fmt2 = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
|
|
14677
|
+
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
14678
|
+
const totalInput = usage.input_tokens ?? 0;
|
|
14679
|
+
const cacheRate = totalInput > 0 ? Math.round(cacheRead / totalInput * 100) : 0;
|
|
14680
|
+
const cacheTag = totalInput > 0 ? ` cache=${cacheRate}%` : "";
|
|
14515
14681
|
const parts = [
|
|
14516
|
-
`input=${
|
|
14517
|
-
`output=${
|
|
14518
|
-
...usage.cache_read_input_tokens ? [`cache_read=${
|
|
14519
|
-
...usage.cache_creation_input_tokens ? [`cache_write=${
|
|
14682
|
+
`input=${fmt2(usage.input_tokens ?? 0)}`,
|
|
14683
|
+
`output=${fmt2(usage.output_tokens ?? 0)}`,
|
|
14684
|
+
...usage.cache_read_input_tokens ? [`cache_read=${fmt2(usage.cache_read_input_tokens)}`] : [],
|
|
14685
|
+
...usage.cache_creation_input_tokens ? [`cache_write=${fmt2(usage.cache_creation_input_tokens)}`] : []
|
|
14520
14686
|
];
|
|
14521
|
-
console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}`);
|
|
14687
|
+
console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}${cacheTag}`);
|
|
14688
|
+
}
|
|
14689
|
+
function computeCacheHitRate(usage) {
|
|
14690
|
+
if (!usage)
|
|
14691
|
+
return;
|
|
14692
|
+
const read = usage.cache_read_input_tokens ?? 0;
|
|
14693
|
+
const creation = usage.cache_creation_input_tokens ?? 0;
|
|
14694
|
+
const uncached = usage.input_tokens ?? 0;
|
|
14695
|
+
const total = uncached + read + creation;
|
|
14696
|
+
if (total === 0)
|
|
14697
|
+
return;
|
|
14698
|
+
return read / total;
|
|
14699
|
+
}
|
|
14700
|
+
function checkTokenHealth(requestId, sdkSessionId, usage, turnNumber, isResume, isPassthrough) {
|
|
14701
|
+
if (!usage || !sdkSessionId)
|
|
14702
|
+
return;
|
|
14703
|
+
const cacheHitRate = computeCacheHitRate(usage) ?? 0;
|
|
14704
|
+
const current = {
|
|
14705
|
+
requestId,
|
|
14706
|
+
turnNumber,
|
|
14707
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
14708
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
14709
|
+
cacheReadInputTokens: usage.cache_read_input_tokens ?? 0,
|
|
14710
|
+
cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
|
|
14711
|
+
cacheHitRate,
|
|
14712
|
+
isResume,
|
|
14713
|
+
isPassthrough
|
|
14714
|
+
};
|
|
14715
|
+
const prevMetric = telemetryStore.getLastForSession(sdkSessionId);
|
|
14716
|
+
const previous = prevMetric ? {
|
|
14717
|
+
requestId: prevMetric.requestId,
|
|
14718
|
+
turnNumber: turnNumber - 1,
|
|
14719
|
+
inputTokens: prevMetric.inputTokens ?? 0,
|
|
14720
|
+
outputTokens: prevMetric.outputTokens ?? 0,
|
|
14721
|
+
cacheReadInputTokens: prevMetric.cacheReadInputTokens ?? 0,
|
|
14722
|
+
cacheCreationInputTokens: prevMetric.cacheCreationInputTokens ?? 0,
|
|
14723
|
+
cacheHitRate: prevMetric.cacheHitRate ?? 0,
|
|
14724
|
+
isResume: prevMetric.isResume,
|
|
14725
|
+
isPassthrough: prevMetric.isPassthrough
|
|
14726
|
+
} : undefined;
|
|
14727
|
+
const anomalies = detectTokenAnomalies(current, previous);
|
|
14728
|
+
if (anomalies.length > 0) {
|
|
14729
|
+
const alerts = formatAnomalyAlerts(requestId, anomalies);
|
|
14730
|
+
for (const line of alerts) {
|
|
14731
|
+
console.error(line);
|
|
14732
|
+
}
|
|
14733
|
+
for (const a of anomalies) {
|
|
14734
|
+
diagnosticLog.log({
|
|
14735
|
+
level: a.severity === "critical" ? "error" : "warn",
|
|
14736
|
+
category: "token",
|
|
14737
|
+
message: `${requestId} ${a.type}: ${a.detail}`,
|
|
14738
|
+
requestId
|
|
14739
|
+
});
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14522
14742
|
}
|
|
14523
14743
|
function createProxyServer(config = {}) {
|
|
14524
14744
|
const finalConfig = { ...DEFAULT_PROXY_CONFIG, ...config };
|
|
@@ -14613,7 +14833,11 @@ function createProxyServer(config = {}) {
|
|
|
14613
14833
|
const effortHeader = c.req.header("x-opencode-effort");
|
|
14614
14834
|
const thinkingHeader = c.req.header("x-opencode-thinking");
|
|
14615
14835
|
const taskBudgetHeader = c.req.header("x-opencode-task-budget");
|
|
14616
|
-
const
|
|
14836
|
+
const rawBetaHeader = c.req.header("anthropic-beta");
|
|
14837
|
+
const betaFilter = filterBetasForProfile(rawBetaHeader, profile.type, getBetaPolicyFromEnv());
|
|
14838
|
+
if (betaFilter.stripped.length > 0) {
|
|
14839
|
+
console.error(`[PROXY] ${requestMeta.requestId} stripped anthropic-beta(s) for Max profile: ${betaFilter.stripped.join(", ")}`);
|
|
14840
|
+
}
|
|
14617
14841
|
const effort = effortHeader || body.effort || undefined;
|
|
14618
14842
|
let thinking = body.thinking || undefined;
|
|
14619
14843
|
if (thinkingHeader !== undefined) {
|
|
@@ -14625,10 +14849,7 @@ function createProxyServer(config = {}) {
|
|
|
14625
14849
|
}
|
|
14626
14850
|
const parsedBudget = taskBudgetHeader ? Number.parseInt(taskBudgetHeader, 10) : NaN;
|
|
14627
14851
|
const taskBudget = Number.isFinite(parsedBudget) ? { total: parsedBudget } : body.task_budget ? { total: body.task_budget.total ?? body.task_budget } : undefined;
|
|
14628
|
-
const betas =
|
|
14629
|
-
if (!betaHeader && c.req.header("anthropic-beta")) {
|
|
14630
|
-
console.error(`[PROXY] ${requestMeta.requestId} stripped anthropic-beta header (Max subscription — betas trigger extra usage billing)`);
|
|
14631
|
-
}
|
|
14852
|
+
const betas = betaFilter.forwarded;
|
|
14632
14853
|
const agentSessionId = adapter.getSessionId(c);
|
|
14633
14854
|
const profileSessionId = profile.id !== "default" && agentSessionId ? `${profile.id}:${agentSessionId}` : agentSessionId;
|
|
14634
14855
|
const profileScopedCwd = profile.id !== "default" ? `${workingDirectory}::profile=${profile.id}` : workingDirectory;
|
|
@@ -14970,7 +15191,7 @@ function createProxyServer(config = {}) {
|
|
|
14970
15191
|
} else {
|
|
14971
15192
|
for (const block of message.message.content) {
|
|
14972
15193
|
const b = block;
|
|
14973
|
-
if (passthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
|
|
15194
|
+
if (passthrough && !adapter.supportsThinking?.() && (b.type === "thinking" || b.type === "redacted_thinking")) {
|
|
14974
15195
|
claudeLog("passthrough.thinking_stripped", { mode: "non_stream", type: b.type });
|
|
14975
15196
|
continue;
|
|
14976
15197
|
}
|
|
@@ -15055,6 +15276,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
15055
15276
|
hasToolUse
|
|
15056
15277
|
});
|
|
15057
15278
|
const nonStreamQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
|
|
15279
|
+
checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
|
|
15058
15280
|
telemetryStore.record({
|
|
15059
15281
|
requestId: requestMeta.requestId,
|
|
15060
15282
|
timestamp: Date.now(),
|
|
@@ -15075,7 +15297,12 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
15075
15297
|
totalDurationMs,
|
|
15076
15298
|
contentBlocks: contentBlocks.length,
|
|
15077
15299
|
textEvents: 0,
|
|
15078
|
-
error: null
|
|
15300
|
+
error: null,
|
|
15301
|
+
inputTokens: lastUsage?.input_tokens,
|
|
15302
|
+
outputTokens: lastUsage?.output_tokens,
|
|
15303
|
+
cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
|
|
15304
|
+
cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
|
|
15305
|
+
cacheHitRate: computeCacheHitRate(lastUsage)
|
|
15079
15306
|
});
|
|
15080
15307
|
if (currentSessionId) {
|
|
15081
15308
|
storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage);
|
|
@@ -15343,7 +15570,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
|
|
|
15343
15570
|
}
|
|
15344
15571
|
if (eventType === "content_block_start") {
|
|
15345
15572
|
const block = event.content_block;
|
|
15346
|
-
if (passthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
|
|
15573
|
+
if (passthrough && !adapter.supportsThinking?.() && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
|
|
15347
15574
|
if (eventIndex !== undefined)
|
|
15348
15575
|
skipBlockIndices.add(eventIndex);
|
|
15349
15576
|
claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
|
|
@@ -15525,6 +15752,7 @@ data: {"type":"message_stop"}
|
|
|
15525
15752
|
textEventsForwarded
|
|
15526
15753
|
});
|
|
15527
15754
|
const streamQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
|
|
15755
|
+
checkTokenHealth(requestMeta.requestId, currentSessionId || resumeSessionId, lastUsage, allMessages.length, isResume, passthrough);
|
|
15528
15756
|
telemetryStore.record({
|
|
15529
15757
|
requestId: requestMeta.requestId,
|
|
15530
15758
|
timestamp: Date.now(),
|
|
@@ -15545,7 +15773,12 @@ data: {"type":"message_stop"}
|
|
|
15545
15773
|
totalDurationMs: streamTotalDurationMs,
|
|
15546
15774
|
contentBlocks: eventsForwarded,
|
|
15547
15775
|
textEvents: textEventsForwarded,
|
|
15548
|
-
error: null
|
|
15776
|
+
error: null,
|
|
15777
|
+
inputTokens: lastUsage?.input_tokens,
|
|
15778
|
+
outputTokens: lastUsage?.output_tokens,
|
|
15779
|
+
cacheReadInputTokens: lastUsage?.cache_read_input_tokens,
|
|
15780
|
+
cacheCreationInputTokens: lastUsage?.cache_creation_input_tokens,
|
|
15781
|
+
cacheHitRate: computeCacheHitRate(lastUsage)
|
|
15549
15782
|
});
|
|
15550
15783
|
if (textEventsForwarded === 0) {
|
|
15551
15784
|
claudeLog("response.empty_stream", {
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startProxyServer
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-b2n69dg7.js";
|
|
5
5
|
import"./cli-g9ypdz51.js";
|
|
6
6
|
import"./cli-rtab0qa6.js";
|
|
7
7
|
import"./cli-m9pfb7h9.js";
|
|
@@ -49,7 +49,7 @@ See https://github.com/rynfar/meridian for full documentation.`);
|
|
|
49
49
|
process.exit(0);
|
|
50
50
|
}
|
|
51
51
|
if (args[0] === "profile") {
|
|
52
|
-
const { profileAdd, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-
|
|
52
|
+
const { profileAdd, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-5e3p99k0.js");
|
|
53
53
|
const subcommand = args[1];
|
|
54
54
|
const profileId = args[2];
|
|
55
55
|
if (subcommand === "add" && profileId)
|
|
@@ -199,8 +199,8 @@ function profileLogin(id) {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
function promptYesNo(question) {
|
|
202
|
+
process.stderr.write(`${question} [Y/n] `);
|
|
202
203
|
const result = spawnSync("node", ["-e", [
|
|
203
|
-
`process.stdout.write(${JSON.stringify(`${question} [Y/n] `)});`,
|
|
204
204
|
`const rl = require("readline").createInterface({ input: process.stdin });`,
|
|
205
205
|
`rl.once("line", (a) => { process.stdout.write(a); rl.close(); });`,
|
|
206
206
|
`rl.once("close", () => process.exit(0));`
|
package/dist/proxy/adapter.d.ts
CHANGED
|
@@ -82,6 +82,15 @@ export interface AgentAdapter {
|
|
|
82
82
|
* When defined, takes precedence over the env var for this agent.
|
|
83
83
|
*/
|
|
84
84
|
usesPassthrough?(): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Whether this agent's client can render thinking blocks.
|
|
87
|
+
*
|
|
88
|
+
* When true, thinking/redacted_thinking blocks are forwarded in
|
|
89
|
+
* passthrough mode instead of being stripped.
|
|
90
|
+
* When false or undefined, they are stripped (safe default for
|
|
91
|
+
* clients that may choke on the encrypted signature field).
|
|
92
|
+
*/
|
|
93
|
+
supportsThinking?(): boolean;
|
|
85
94
|
/**
|
|
86
95
|
* Map a client-side tool_use block to file changes (passthrough mode).
|
|
87
96
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5C;;;OAGG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;IAE1B;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5C;;;OAGG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;IAE1B;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,OAAO,CAAA;IAE5B;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/opencode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAQ9C,eAAO,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/opencode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAQ9C,eAAO,MAAM,eAAe,EAAE,YAiH7B,CAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anthropic-beta header filtering for Max vs API profiles.
|
|
3
|
+
*
|
|
4
|
+
* Some betas (e.g. `extended-cache-ttl-*`) trigger Extra-Usage billing on
|
|
5
|
+
* Claude Max subscriptions. The default `allow-safe` policy strips only those
|
|
6
|
+
* for claude-max profiles while forwarding everything else so that prompt
|
|
7
|
+
* caching, 1M context, fine-grained tool streaming, and interleaved thinking
|
|
8
|
+
* continue to work as the SDK expects.
|
|
9
|
+
*
|
|
10
|
+
* Unconditional stripping (the previous behaviour) caused cache misses on
|
|
11
|
+
* every turn, which tripled TTFB and inflated token consumption roughly 3x on
|
|
12
|
+
* long conversations. See issue #278 for the original context.
|
|
13
|
+
*
|
|
14
|
+
* An operator can override the policy at runtime via the `MERIDIAN_BETA_POLICY`
|
|
15
|
+
* env var to force `strip-all` (safest — old behaviour) or `allow-all`
|
|
16
|
+
* (most permissive — matches api-profile behaviour) without a rebuild.
|
|
17
|
+
*
|
|
18
|
+
* This module is pure — no I/O, no imports from server.ts or session/.
|
|
19
|
+
*/
|
|
20
|
+
import type { ProfileType } from "./profiles";
|
|
21
|
+
/**
|
|
22
|
+
* Beta prefixes that are known to trigger Extra-Usage billing on Max accounts.
|
|
23
|
+
*
|
|
24
|
+
* A beta is considered billable if its name starts with any of these strings.
|
|
25
|
+
* Keep this list conservative — prefer allowing unknown betas through over
|
|
26
|
+
* silently stripping something the SDK needs for normal operation.
|
|
27
|
+
*/
|
|
28
|
+
export declare const BILLABLE_BETA_PREFIXES_ON_MAX: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Runtime policy for `anthropic-beta` header handling on claude-max profiles.
|
|
31
|
+
*
|
|
32
|
+
* - `allow-safe` (default): forward all betas except those matching
|
|
33
|
+
* {@link BILLABLE_BETA_PREFIXES_ON_MAX}. Restores prompt caching + 1M
|
|
34
|
+
* context while keeping the original billing-safety intent.
|
|
35
|
+
* - `strip-all`: the pre-fix (1.28.0 – 1.29.x) behaviour. Drops every beta
|
|
36
|
+
* for claude-max profiles. Use this as a kill switch if the allow-safe
|
|
37
|
+
* policy ever causes quota surprises.
|
|
38
|
+
* - `allow-all`: forward every beta unconditionally, same as api profiles.
|
|
39
|
+
* Use only if you've verified your Max tier treats all betas as free.
|
|
40
|
+
*/
|
|
41
|
+
export type BetaPolicy = "allow-safe" | "strip-all" | "allow-all";
|
|
42
|
+
export declare const DEFAULT_BETA_POLICY: BetaPolicy;
|
|
43
|
+
export interface BetaFilterResult {
|
|
44
|
+
/** Betas to forward upstream. `undefined` means no header should be sent. */
|
|
45
|
+
forwarded: string[] | undefined;
|
|
46
|
+
/** Betas that were removed. Empty for api-type profiles. */
|
|
47
|
+
stripped: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read the beta policy from the `MERIDIAN_BETA_POLICY` env var.
|
|
51
|
+
*
|
|
52
|
+
* Falls back to {@link DEFAULT_BETA_POLICY} for missing or invalid values.
|
|
53
|
+
* Invalid values are silently ignored rather than crashing the proxy.
|
|
54
|
+
*/
|
|
55
|
+
export declare function getBetaPolicyFromEnv(): BetaPolicy;
|
|
56
|
+
/**
|
|
57
|
+
* Filter an `anthropic-beta` header value for the given profile type.
|
|
58
|
+
*
|
|
59
|
+
* - For `api` profiles, all betas pass through unchanged regardless of policy.
|
|
60
|
+
* - For `claude-max` profiles, behaviour depends on `policy`:
|
|
61
|
+
* - `allow-safe` (default): strip only billable betas
|
|
62
|
+
* (see {@link BILLABLE_BETA_PREFIXES_ON_MAX}).
|
|
63
|
+
* - `strip-all`: strip every beta.
|
|
64
|
+
* - `allow-all`: forward every beta unchanged.
|
|
65
|
+
* - Whitespace and empty entries are trimmed.
|
|
66
|
+
* - Returns `forwarded: undefined` when the result would be an empty list so
|
|
67
|
+
* callers can omit the header entirely.
|
|
68
|
+
*/
|
|
69
|
+
export declare function filterBetasForProfile(rawBetaHeader: string | undefined, profileType: ProfileType, policy?: BetaPolicy): BetaFilterResult;
|
|
70
|
+
//# sourceMappingURL=betas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"betas.d.ts","sourceRoot":"","sources":["../../src/proxy/betas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAE1D,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,CAAA;AAEjE,eAAO,MAAM,mBAAmB,EAAE,UAAyB,CAAA;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAC/B,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,CAMjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,UAAgC,GACvC,gBAAgB,CA0ClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passthroughTools.d.ts","sourceRoot":"","sources":["../../src/proxy/passthroughTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,eAAO,MAAM,oBAAoB,OAAO,CAAA;AACxC,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AAsCtE;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;;;
|
|
1
|
+
{"version":3,"file":"passthroughTools.d.ts","sourceRoot":"","sources":["../../src/proxy/passthroughTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,eAAO,MAAM,oBAAoB,OAAO,CAAA;AACxC,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AAsCtE;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;;;EA2CzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAuBvD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAyK7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAyrDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token health anomaly detection.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that compare consecutive turn token snapshots
|
|
5
|
+
* and classify abnormal patterns. No I/O, no imports from server.ts.
|
|
6
|
+
*/
|
|
7
|
+
export interface TokenSnapshot {
|
|
8
|
+
requestId: string;
|
|
9
|
+
turnNumber: number;
|
|
10
|
+
inputTokens: number;
|
|
11
|
+
outputTokens: number;
|
|
12
|
+
cacheReadInputTokens: number;
|
|
13
|
+
cacheCreationInputTokens: number;
|
|
14
|
+
cacheHitRate: number;
|
|
15
|
+
isResume: boolean;
|
|
16
|
+
isPassthrough: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface TokenAnomaly {
|
|
19
|
+
type: "context_spike" | "cache_miss" | "output_explosion";
|
|
20
|
+
severity: "warn" | "critical";
|
|
21
|
+
detail: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compare a current turn's token snapshot against the previous turn.
|
|
25
|
+
* Returns an array of detected anomalies (empty if everything looks normal).
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectTokenAnomalies(current: TokenSnapshot, previous: TokenSnapshot | undefined): TokenAnomaly[];
|
|
28
|
+
/**
|
|
29
|
+
* Format anomalies as stderr log lines.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatAnomalyAlerts(requestId: string, anomalies: TokenAnomaly[]): string[];
|
|
32
|
+
//# sourceMappingURL=tokenHealth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenHealth.d.ts","sourceRoot":"","sources":["../../src/proxy/tokenHealth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,wBAAwB,EAAE,MAAM,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,eAAe,GAAG,YAAY,GAAG,kBAAkB,CAAA;IACzD,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;IAC7B,MAAM,EAAE,MAAM,CAAA;CACf;AAaD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,YAAY,EAAE,CAsChB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CAK1F"}
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,aAAa,QA6VlB,CAAA"}
|
|
@@ -11,7 +11,7 @@ export interface DiagnosticLog {
|
|
|
11
11
|
/** Log level */
|
|
12
12
|
level: "info" | "warn" | "error";
|
|
13
13
|
/** Log category for filtering */
|
|
14
|
-
category: "session" | "lineage" | "error" | "lifecycle";
|
|
14
|
+
category: "session" | "lineage" | "error" | "lifecycle" | "token";
|
|
15
15
|
/** Request ID (if associated with a request) */
|
|
16
16
|
requestId?: string;
|
|
17
17
|
/** Human-readable message */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logStore.d.ts","sourceRoot":"","sources":["../../src/telemetry/logStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB;IAChB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAChC,iCAAiC;IACjC,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"logStore.d.ts","sourceRoot":"","sources":["../../src/telemetry/logStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB;IAChB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAChC,iCAAiC;IACjC,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,CAAA;IACjE,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAA;CAChB;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,0BAA0B;IAC1B,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,GAAG,IAAI;IAMlD,wCAAwC;IACxC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,qEAAqE;IACrE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,iCAAiC;IACjC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIhD;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAgB/F,6BAA6B;IAC7B,KAAK,IAAI,IAAI;CAKd;AAED,4CAA4C;AAC5C,eAAO,MAAM,aAAa,oBAA2B,CAAA"}
|
|
@@ -26,6 +26,9 @@ export declare class TelemetryStore {
|
|
|
26
26
|
since?: number;
|
|
27
27
|
model?: string;
|
|
28
28
|
}): RequestMetric[];
|
|
29
|
+
/** Find the most recent successful metric for a given SDK session ID.
|
|
30
|
+
* Used by anomaly detection to compare consecutive turns. */
|
|
31
|
+
getLastForSession(sdkSessionId: string): RequestMetric | undefined;
|
|
29
32
|
/**
|
|
30
33
|
* Compute aggregate statistics over a time window.
|
|
31
34
|
* @param windowMs - Time window in ms (default: 1 hour)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAY3E,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,yCAAyC;IACzC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMnC,8CAA8C;IAC9C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAiB5F;;;OAGG;IACH,SAAS,CAAC,QAAQ,GAAE,MAAuB,GAAG,gBAAgB;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAY3E,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,yCAAyC;IACzC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMnC,8CAA8C;IAC9C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAiB5F;kEAC8D;IAC9D,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAWlE;;;OAGG;IACH,SAAS,CAAC,QAAQ,GAAE,MAAuB,GAAG,gBAAgB;IAiH9D,gCAAgC;IAChC,KAAK,IAAI,IAAI;CAKd;AAkBD,kDAAkD;AAClD,eAAO,MAAM,cAAc,gBAAuB,CAAA"}
|
|
@@ -57,6 +57,17 @@ export interface RequestMetric {
|
|
|
57
57
|
textEvents: number;
|
|
58
58
|
/** Error type if the request failed, null if successful */
|
|
59
59
|
error: string | null;
|
|
60
|
+
/** Input tokens consumed (from SDK usage) */
|
|
61
|
+
inputTokens?: number;
|
|
62
|
+
/** Output tokens generated */
|
|
63
|
+
outputTokens?: number;
|
|
64
|
+
/** Input tokens read from prompt cache (lower cost) */
|
|
65
|
+
cacheReadInputTokens?: number;
|
|
66
|
+
/** Input tokens written to prompt cache (one-time cost) */
|
|
67
|
+
cacheCreationInputTokens?: number;
|
|
68
|
+
/** Cache hit ratio: cacheRead / (cacheRead + cacheCreation + uncached).
|
|
69
|
+
* 1.0 = perfect caching, 0.0 = no caching. undefined when no token data. */
|
|
70
|
+
cacheHitRate?: number;
|
|
60
71
|
}
|
|
61
72
|
export interface PhaseTiming {
|
|
62
73
|
p50: number;
|
|
@@ -91,5 +102,15 @@ export interface TelemetrySummary {
|
|
|
91
102
|
count: number;
|
|
92
103
|
avgTotalMs: number;
|
|
93
104
|
}>;
|
|
105
|
+
/** Aggregate token usage across all requests in the window */
|
|
106
|
+
tokenUsage: {
|
|
107
|
+
totalInputTokens: number;
|
|
108
|
+
totalOutputTokens: number;
|
|
109
|
+
totalCacheReadTokens: number;
|
|
110
|
+
totalCacheCreationTokens: number;
|
|
111
|
+
avgCacheHitRate: number;
|
|
112
|
+
/** Requests where cache hit rate was 0 despite being a resume */
|
|
113
|
+
cacheMissOnResumeCount: number;
|
|
114
|
+
};
|
|
94
115
|
}
|
|
95
116
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IAEjB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IAEjB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IAEb,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,iCAAiC;IACjC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAA;IAE7B,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAA;IAEjB,0CAA0C;IAC1C,aAAa,EAAE,OAAO,CAAA;IAEtB;;;;;sEAKkE;IAClE,WAAW,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,CAAA;IAEzE,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IAEd,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IAEnB;;8CAE0C;IAC1C,eAAe,EAAE,MAAM,CAAA;IAEvB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAErB,yCAAyC;IACzC,kBAAkB,EAAE,MAAM,CAAA;IAE1B,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAA;IAEvB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAA;IAErB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAElB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IAEjB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IAEjB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IAEb,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,iCAAiC;IACjC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAA;IAE7B,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAA;IAEjB,0CAA0C;IAC1C,aAAa,EAAE,OAAO,CAAA;IAEtB;;;;;sEAKkE;IAClE,WAAW,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,CAAA;IAEzE,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IAEd,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IAEnB;;8CAE0C;IAC1C,eAAe,EAAE,MAAM,CAAA;IAEvB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAErB,yCAAyC;IACzC,kBAAkB,EAAE,MAAM,CAAA;IAE1B,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAA;IAEvB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAA;IAErB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAElB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,8BAA8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAE7B,2DAA2D;IAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAA;IAEjC;iFAC6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAA;IACrB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IAEzB,iCAAiC;IACjC,SAAS,EAAE,WAAW,CAAA;IACtB,aAAa,EAAE,WAAW,CAAA;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,gBAAgB,EAAE,WAAW,CAAA;IAC7B,aAAa,EAAE,WAAW,CAAA;IAE1B,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9D,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAE7D,8DAA8D;IAC9D,UAAU,EAAE;QACV,gBAAgB,EAAE,MAAM,CAAA;QACxB,iBAAiB,EAAE,MAAM,CAAA;QACzB,oBAAoB,EAAE,MAAM,CAAA;QAC5B,wBAAwB,EAAE,MAAM,CAAA;QAChC,eAAe,EAAE,MAAM,CAAA;QACvB,iEAAiE;QACjE,sBAAsB,EAAE,MAAM,CAAA;KAC/B,CAAA;CACF"}
|
package/package.json
CHANGED