@rynfar/meridian 1.27.0 → 1.27.2
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 +8 -7
- package/dist/{cli-6hehvt9f.js → cli-mx5hhmme.js} +64 -21
- package/dist/cli.js +1 -1
- package/dist/proxy/adapters/detect.d.ts +6 -4
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/adapters/passthrough.d.ts +1 -2
- package/dist/proxy/adapters/passthrough.d.ts.map +1 -1
- package/dist/proxy/adapters/pi.d.ts.map +1 -1
- package/dist/proxy/models.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/telemetry/dashboard.d.ts +1 -1
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +2 -0
- package/dist/telemetry/types.d.ts.map +1 -1
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
Meridian turns your Claude Max subscription into a local Anthropic API. Any tool that speaks the Anthropic or OpenAI protocol — OpenCode, Crush, Cline, Aider, Pi, Droid, Open WebUI — connects to Meridian and gets Claude, powered by your existing subscription through the official Claude Code SDK.
|
|
14
|
+
Meridian turns your Claude Max subscription into a local Anthropic API. Any tool that speaks the Anthropic or OpenAI protocol — OpenCode, OpenClaw, Crush, Cline, Aider, Pi, Droid, Open WebUI — connects to Meridian and gets Claude, powered by your existing subscription through the official Claude Code SDK.
|
|
15
15
|
|
|
16
|
-
> [!
|
|
17
|
-
> **
|
|
16
|
+
> [!IMPORTANT]
|
|
17
|
+
> **Extra Usage billing fix (v0.x.x):** Previous versions defaulted Sonnet to `sonnet[1m]` (1M context), which is [always billed as Extra Usage](https://code.claude.com/docs/en/model-config#extended-context) on Max plans — even when regular usage isn't exhausted. Sonnet now defaults to 200k. If you're on an older version, update or set `MERIDIAN_SONNET_MODEL=sonnet` as a workaround. See [#255](https://github.com/rynfar/meridian/issues/255) for details.
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
@@ -76,7 +76,8 @@ meridian setup
|
|
|
76
76
|
This adds the Meridian plugin to your OpenCode global config (`~/.config/opencode/opencode.json`). The plugin enables:
|
|
77
77
|
|
|
78
78
|
- **Session tracking** — reliable conversation continuity across requests
|
|
79
|
-
- **
|
|
79
|
+
- **Safe model defaults** — Opus uses 1M context (included with Max subscription); Sonnet uses 200k to avoid Extra Usage charges ([details](#extended-context-billing))
|
|
80
|
+
- **Subagent model selection** — subagents automatically use `sonnet`/`opus` (200k), preserving rate-limit budget
|
|
80
81
|
|
|
81
82
|
If the plugin is missing, Meridian warns at startup and reports `"plugin": "not-configured"` in the health endpoint.
|
|
82
83
|
|
|
@@ -330,10 +331,10 @@ Implement the `AgentAdapter` interface in `src/proxy/adapters/`. See [`adapters/
|
|
|
330
331
|
| `MERIDIAN_IDLE_TIMEOUT_SECONDS` | `CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS` | `120` | HTTP keep-alive timeout |
|
|
331
332
|
| `MERIDIAN_TELEMETRY_SIZE` | `CLAUDE_PROXY_TELEMETRY_SIZE` | `1000` | Telemetry ring buffer size |
|
|
332
333
|
| `MERIDIAN_NO_FILE_CHANGES` | `CLAUDE_PROXY_NO_FILE_CHANGES` | unset | Disable "Files changed" summary in responses |
|
|
333
|
-
| `MERIDIAN_SONNET_MODEL` | `CLAUDE_PROXY_SONNET_MODEL` | `sonnet
|
|
334
|
+
| `MERIDIAN_SONNET_MODEL` | `CLAUDE_PROXY_SONNET_MODEL` | `sonnet` | Sonnet context tier: `sonnet` (200k, default) or `sonnet[1m]` (1M, requires Extra Usage†) |
|
|
334
335
|
| `MERIDIAN_DEFAULT_AGENT` | — | `opencode` | Default adapter for unrecognized agents: `opencode`, `pi`, `crush`, `droid`, `passthrough`. Requires restart. |
|
|
335
336
|
|
|
336
|
-
|
|
337
|
+
†Sonnet 1M requires Extra Usage on all plans including Max ([docs](https://code.claude.com/docs/en/model-config#extended-context)). Opus 1M is included with Max/Team/Enterprise at no extra cost.
|
|
337
338
|
|
|
338
339
|
## Endpoints
|
|
339
340
|
|
|
@@ -428,7 +429,7 @@ curl -X POST http://127.0.0.1:3456/auth/refresh
|
|
|
428
429
|
```
|
|
429
430
|
|
|
430
431
|
**I'm hitting rate limits on 1M context. What do I do?**
|
|
431
|
-
Set `MERIDIAN_SONNET_MODEL=sonnet` to
|
|
432
|
+
Meridian defaults Sonnet to 200k context because Sonnet 1M is always billed as Extra Usage on Max plans — even when regular usage isn't exhausted. This is [Anthropic's intended billing model](https://code.claude.com/docs/en/model-config#extended-context), not a bug. Set `MERIDIAN_SONNET_MODEL=sonnet[1m]` to opt in if you have Extra Usage enabled and understand the billing implications. Opus defaults to 1M context, which is included with Max/Team/Enterprise subscriptions at no extra cost. Note: there is a [known upstream bug](https://github.com/anthropics/claude-code/issues/39841) where Claude Code incorrectly gates Opus 1M behind Extra Usage on Max — this is Anthropic's to fix.
|
|
432
433
|
|
|
433
434
|
**Why does the health endpoint show `"plugin": "not-configured"`?**
|
|
434
435
|
You haven't run `meridian setup`. Without the plugin, OpenCode requests won't have session tracking or subagent model selection. Run `meridian setup` and restart OpenCode.
|
|
@@ -6628,7 +6628,7 @@ function render(s, reqs, logs) {
|
|
|
6628
6628
|
+ '<span><span class="legend-dot" style="background:var(--ttfb)"></span>TTFB</span>'
|
|
6629
6629
|
+ '<span><span class="legend-dot" style="background:var(--upstream)"></span>Response</span>'
|
|
6630
6630
|
+ '</div>'
|
|
6631
|
-
+ '<table><thead><tr><th>Time</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'
|
|
6631
|
+
+ '<table><thead><tr><th>Time</th><th>Adapter</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'
|
|
6632
6632
|
+ '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';
|
|
6633
6633
|
|
|
6634
6634
|
const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);
|
|
@@ -6648,6 +6648,7 @@ function render(s, reqs, logs) {
|
|
|
6648
6648
|
|
|
6649
6649
|
html += '<tr>'
|
|
6650
6650
|
+ '<td class="mono">' + ago(r.timestamp) + '</td>'
|
|
6651
|
+
+ '<td>' + (r.adapter || '—') + '</td>'
|
|
6651
6652
|
+ '<td>' + (r.requestModel || r.model) + '<br><span style="font-size:10px;color:var(--muted)">' + r.model + '</span></td>'
|
|
6652
6653
|
+ '<td>' + r.mode + '</td>'
|
|
6653
6654
|
+ '<td class="mono">' + sessionShort + ' ' + lineageBadge + '<br><span style="font-size:10px;color:var(--muted)">' + msgCount + ' msgs</span></td>'
|
|
@@ -7020,15 +7021,12 @@ function mapModelToClaudeModel(model, subscriptionType, agentMode) {
|
|
|
7020
7021
|
return "opus";
|
|
7021
7022
|
}
|
|
7022
7023
|
const sonnetOverride = process.env.MERIDIAN_SONNET_MODEL ?? process.env.CLAUDE_PROXY_SONNET_MODEL;
|
|
7023
|
-
if (sonnetOverride === "sonnet
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
return "sonnet";
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
if (isExtendedContextKnownUnavailable())
|
|
7030
|
-
return "sonnet";
|
|
7031
|
-
return subscriptionType === "max" ? "sonnet[1m]" : "sonnet";
|
|
7024
|
+
if (sonnetOverride === "sonnet[1m]") {
|
|
7025
|
+
if (!use1m || isSubagent || isExtendedContextKnownUnavailable())
|
|
7026
|
+
return "sonnet";
|
|
7027
|
+
return "sonnet[1m]";
|
|
7028
|
+
}
|
|
7029
|
+
return "sonnet";
|
|
7032
7030
|
}
|
|
7033
7031
|
var EXTRA_USAGE_RETRY_MS = 60 * 60 * 1000;
|
|
7034
7032
|
var extraUsageUnavailableAt = 0;
|
|
@@ -7870,8 +7868,8 @@ var passthroughAdapter = {
|
|
|
7870
7868
|
usesPassthrough() {
|
|
7871
7869
|
return true;
|
|
7872
7870
|
},
|
|
7873
|
-
prefersStreaming(
|
|
7874
|
-
return
|
|
7871
|
+
prefersStreaming(body) {
|
|
7872
|
+
return body?.stream === true;
|
|
7875
7873
|
}
|
|
7876
7874
|
};
|
|
7877
7875
|
|
|
@@ -7882,6 +7880,7 @@ var PI_ALLOWED_MCP_TOOLS = [
|
|
|
7882
7880
|
`mcp__${PI_MCP_SERVER_NAME}__write`,
|
|
7883
7881
|
`mcp__${PI_MCP_SERVER_NAME}__edit`,
|
|
7884
7882
|
`mcp__${PI_MCP_SERVER_NAME}__bash`,
|
|
7883
|
+
`mcp__${PI_MCP_SERVER_NAME}__glob`,
|
|
7885
7884
|
`mcp__${PI_MCP_SERVER_NAME}__grep`
|
|
7886
7885
|
];
|
|
7887
7886
|
function extractPiCwd(body) {
|
|
@@ -7953,7 +7952,11 @@ var ADAPTER_MAP = {
|
|
|
7953
7952
|
passthrough: passthroughAdapter,
|
|
7954
7953
|
pi: piAdapter
|
|
7955
7954
|
};
|
|
7956
|
-
var
|
|
7955
|
+
var envDefault = process.env.MERIDIAN_DEFAULT_AGENT || "";
|
|
7956
|
+
if (envDefault && !ADAPTER_MAP[envDefault]) {
|
|
7957
|
+
console.warn(`[meridian] Unknown MERIDIAN_DEFAULT_AGENT="${envDefault}". ` + `Valid values: ${Object.keys(ADAPTER_MAP).join(", ")}. Falling back to opencode.`);
|
|
7958
|
+
}
|
|
7959
|
+
var defaultAdapter = ADAPTER_MAP[envDefault] ?? openCodeAdapter;
|
|
7957
7960
|
function isLiteLLMRequest(c) {
|
|
7958
7961
|
if ((c.req.header("user-agent") || "").startsWith("litellm/"))
|
|
7959
7962
|
return true;
|
|
@@ -7961,11 +7964,17 @@ function isLiteLLMRequest(c) {
|
|
|
7961
7964
|
return Object.keys(headers).some((k) => k.toLowerCase().startsWith("x-litellm-"));
|
|
7962
7965
|
}
|
|
7963
7966
|
function detectAdapter(c) {
|
|
7964
|
-
const agentOverride = c.req.header("x-meridian-agent");
|
|
7967
|
+
const agentOverride = c.req.header("x-meridian-agent")?.toLowerCase();
|
|
7965
7968
|
if (agentOverride && ADAPTER_MAP[agentOverride]) {
|
|
7966
7969
|
return ADAPTER_MAP[agentOverride];
|
|
7967
7970
|
}
|
|
7971
|
+
if (c.req.header("x-opencode-session") || c.req.header("x-session-affinity")) {
|
|
7972
|
+
return openCodeAdapter;
|
|
7973
|
+
}
|
|
7968
7974
|
const userAgent = c.req.header("user-agent") || "";
|
|
7975
|
+
if (userAgent.startsWith("opencode/")) {
|
|
7976
|
+
return openCodeAdapter;
|
|
7977
|
+
}
|
|
7969
7978
|
if (userAgent.startsWith("factory-cli/")) {
|
|
7970
7979
|
return droidAdapter;
|
|
7971
7980
|
}
|
|
@@ -14407,6 +14416,7 @@ function createProxyServer(config = {}) {
|
|
|
14407
14416
|
const handleMessages = async (c, requestMeta) => {
|
|
14408
14417
|
const requestStartAt = Date.now();
|
|
14409
14418
|
return withClaudeLogContext({ requestId: requestMeta.requestId, endpoint: requestMeta.endpoint }, async () => {
|
|
14419
|
+
const adapter = detectAdapter(c);
|
|
14410
14420
|
try {
|
|
14411
14421
|
let stripCacheControl = function(content) {
|
|
14412
14422
|
if (!Array.isArray(content))
|
|
@@ -14435,7 +14445,6 @@ function createProxyServer(config = {}) {
|
|
|
14435
14445
|
const authStatus = await getClaudeAuthStatusAsync();
|
|
14436
14446
|
const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
|
|
14437
14447
|
let model = mapModelToClaudeModel(body.model || "sonnet", authStatus?.subscriptionType, agentMode);
|
|
14438
|
-
const adapter = detectAdapter(c);
|
|
14439
14448
|
const adapterStreamPref = adapter.prefersStreaming?.(body);
|
|
14440
14449
|
const stream2 = adapterStreamPref !== undefined ? adapterStreamPref : body.stream ?? false;
|
|
14441
14450
|
const workingDirectory = (process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR) || adapter.extractWorkingDirectory(body) || process.cwd();
|
|
@@ -14484,7 +14493,7 @@ function createProxyServer(config = {}) {
|
|
|
14484
14493
|
}).join(" → ");
|
|
14485
14494
|
const lineageType = lineageResult.type === "diverged" && !cachedSession ? "new" : lineageResult.type;
|
|
14486
14495
|
const msgCount = Array.isArray(body.messages) ? body.messages.length : 0;
|
|
14487
|
-
const requestLogLine = `${requestMeta.requestId} model=${model} stream=${stream2} tools=${body.tools?.length ?? 0} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
|
|
14496
|
+
const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name} model=${model} stream=${stream2} tools=${body.tools?.length ?? 0} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
|
|
14488
14497
|
console.error(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
|
|
14489
14498
|
diagnosticLog.session(`${requestLogLine}`, requestMeta.requestId);
|
|
14490
14499
|
claudeLog("request.received", {
|
|
@@ -14795,12 +14804,21 @@ function createProxyServer(config = {}) {
|
|
|
14795
14804
|
ttfbMs: firstChunkAt - upstreamStartAt
|
|
14796
14805
|
});
|
|
14797
14806
|
}
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14807
|
+
const isPassthroughTurn2 = passthrough && assistantMessages > 1 && contentBlocks.some((b) => b.type === "tool_use");
|
|
14808
|
+
if (isPassthroughTurn2) {
|
|
14809
|
+
claudeLog("passthrough.turn2_skipped", { mode: "non_stream", assistantMessages });
|
|
14810
|
+
} else {
|
|
14811
|
+
for (const block of message.message.content) {
|
|
14812
|
+
const b = block;
|
|
14813
|
+
if (passthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
|
|
14814
|
+
claudeLog("passthrough.thinking_stripped", { mode: "non_stream", type: b.type });
|
|
14815
|
+
continue;
|
|
14816
|
+
}
|
|
14817
|
+
if (passthrough && b.type === "tool_use" && typeof b.name === "string") {
|
|
14818
|
+
b.name = stripMcpPrefix(b.name);
|
|
14819
|
+
}
|
|
14820
|
+
contentBlocks.push(b);
|
|
14802
14821
|
}
|
|
14803
|
-
contentBlocks.push(b);
|
|
14804
14822
|
}
|
|
14805
14823
|
const msgUsage = message.message.usage;
|
|
14806
14824
|
if (msgUsage)
|
|
@@ -14880,6 +14898,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14880
14898
|
telemetryStore.record({
|
|
14881
14899
|
requestId: requestMeta.requestId,
|
|
14882
14900
|
timestamp: Date.now(),
|
|
14901
|
+
adapter: adapter.name,
|
|
14883
14902
|
model,
|
|
14884
14903
|
requestModel: body.model || undefined,
|
|
14885
14904
|
mode: "non-stream",
|
|
@@ -15141,6 +15160,20 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
15141
15160
|
if (startUsage)
|
|
15142
15161
|
lastUsage = { ...lastUsage, ...startUsage };
|
|
15143
15162
|
if (messageStartEmitted) {
|
|
15163
|
+
if (passthrough && streamedToolUseIds.size > 0) {
|
|
15164
|
+
safeEnqueue(encoder.encode(`event: message_delta
|
|
15165
|
+
data: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: "tool_use", stop_sequence: null }, usage: { output_tokens: lastUsage?.output_tokens ?? 0 } })}
|
|
15166
|
+
|
|
15167
|
+
`), "passthrough_turn2_stop");
|
|
15168
|
+
safeEnqueue(encoder.encode(`event: message_stop
|
|
15169
|
+
data: ${JSON.stringify({ type: "message_stop" })}
|
|
15170
|
+
|
|
15171
|
+
`), "passthrough_turn2_stop");
|
|
15172
|
+
claudeLog("passthrough.turn2_suppressed", { mode: "stream", toolUses: streamedToolUseIds.size });
|
|
15173
|
+
streamClosed = true;
|
|
15174
|
+
controller.close();
|
|
15175
|
+
break;
|
|
15176
|
+
}
|
|
15144
15177
|
continue;
|
|
15145
15178
|
}
|
|
15146
15179
|
messageStartEmitted = true;
|
|
@@ -15150,6 +15183,12 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
15150
15183
|
}
|
|
15151
15184
|
if (eventType === "content_block_start") {
|
|
15152
15185
|
const block = event.content_block;
|
|
15186
|
+
if (passthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
|
|
15187
|
+
if (eventIndex !== undefined)
|
|
15188
|
+
skipBlockIndices.add(eventIndex);
|
|
15189
|
+
claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
|
|
15190
|
+
continue;
|
|
15191
|
+
}
|
|
15153
15192
|
if (block?.type === "tool_use" && typeof block.name === "string") {
|
|
15154
15193
|
if (passthrough && block.name.startsWith(PASSTHROUGH_MCP_PREFIX)) {
|
|
15155
15194
|
block.name = stripMcpPrefix(block.name);
|
|
@@ -15159,6 +15198,8 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
15159
15198
|
if (eventIndex !== undefined)
|
|
15160
15199
|
skipBlockIndices.add(eventIndex);
|
|
15161
15200
|
continue;
|
|
15201
|
+
} else if (passthrough && block.id) {
|
|
15202
|
+
streamedToolUseIds.add(block.id);
|
|
15162
15203
|
}
|
|
15163
15204
|
}
|
|
15164
15205
|
if (eventIndex !== undefined) {
|
|
@@ -15325,6 +15366,7 @@ data: {"type":"message_stop"}
|
|
|
15325
15366
|
telemetryStore.record({
|
|
15326
15367
|
requestId: requestMeta.requestId,
|
|
15327
15368
|
timestamp: Date.now(),
|
|
15369
|
+
adapter: adapter.name,
|
|
15328
15370
|
model,
|
|
15329
15371
|
requestModel: body.model || undefined,
|
|
15330
15372
|
mode: "stream",
|
|
@@ -15433,6 +15475,7 @@ data: ${JSON.stringify({
|
|
|
15433
15475
|
telemetryStore.record({
|
|
15434
15476
|
requestId: requestMeta.requestId,
|
|
15435
15477
|
timestamp: Date.now(),
|
|
15478
|
+
adapter: adapter.name,
|
|
15436
15479
|
model: "unknown",
|
|
15437
15480
|
requestModel: undefined,
|
|
15438
15481
|
mode: "non-stream",
|
package/dist/cli.js
CHANGED
|
@@ -11,10 +11,12 @@ import type { AgentAdapter } from "../adapter";
|
|
|
11
11
|
*
|
|
12
12
|
* Detection rules (evaluated in order):
|
|
13
13
|
* 1. x-meridian-agent header → explicit adapter override
|
|
14
|
-
* 2.
|
|
15
|
-
* 3. User-Agent starts with "
|
|
16
|
-
* 4.
|
|
17
|
-
* 5.
|
|
14
|
+
* 2. x-opencode-session or x-session-affinity header → OpenCode adapter
|
|
15
|
+
* 3. User-Agent starts with "opencode/" → OpenCode adapter
|
|
16
|
+
* 4. User-Agent starts with "factory-cli/" → Droid adapter
|
|
17
|
+
* 5. User-Agent starts with "Charm-Crush/" → Crush adapter
|
|
18
|
+
* 6. litellm/* UA or x-litellm-* headers → LiteLLM passthrough adapter
|
|
19
|
+
* 7. Default → MERIDIAN_DEFAULT_AGENT env var, or OpenCode
|
|
18
20
|
*/
|
|
19
21
|
export declare function detectAdapter(c: Context): AgentAdapter;
|
|
20
22
|
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/detect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/detect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAqC9C;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CA+BtD"}
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Key characteristics:
|
|
10
10
|
* - Passthrough mode always enabled (overrides MERIDIAN_PASSTHROUGH env var)
|
|
11
|
-
* -
|
|
12
|
-
* so we can't reliably distinguish them; non-streaming is safe for all requests
|
|
11
|
+
* - Streaming: respects the client's stream parameter (body.stream)
|
|
13
12
|
* - Session continuity: uses x-litellm-session-id header when present
|
|
14
13
|
* - CWD: extracts from <env cwd="..."> blocks in the prompt if available
|
|
15
14
|
* - MCP server name: "litellm" (tools appear as mcp__litellm__*)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passthrough.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/passthrough.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"passthrough.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/passthrough.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAiD9C,eAAO,MAAM,kBAAkB,EAAE,YA4EhC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/pi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/pi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAwC9C,eAAO,MAAM,SAAS,EAAE,YA+FvB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA0BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA0BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CA8B7H;AAWD;;;;;;GAMG;AACH,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,IAAI,OAAO,CAG3D;AAED,0EAA0E;AAC1E,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAIpE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAE9D;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAkCjF;AAOD;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,CA4DpE;AAED,2CAA2C;AAC3C,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,IAAI,IAAI,CAMlD;AAED;;6DAE6D;AAC7D,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAoBvD,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;AAEzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAoG7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAoBvD,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;AAEzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAoG7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA0/ChF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0ChG"}
|
package/dist/server.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Inline HTML dashboard for telemetry.
|
|
3
3
|
* No framework, no build step, no CDN. Single self-contained page.
|
|
4
4
|
*/
|
|
5
|
-
export declare const dashboardHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Meridian \u2014 Telemetry</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"/telemetry/icon.svg\">\n<style>\n :root {\n --bg: #0d1117; --surface: #161b22; --border: #30363d;\n --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;\n --green: #3fb950; --yellow: #d29922; --red: #f85149;\n --blue: #58a6ff; --purple: #bc8cff;\n --queue: #d29922; --ttfb: #58a6ff; --upstream: #3fb950; --total: #bc8cff;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n background: var(--bg); color: var(--text); padding: 24px; line-height: 1.5; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }\n .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }\n .card-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n .card-value { font-size: 28px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }\n .card-detail { font-size: 12px; color: var(--muted); margin-top: 2px; }\n .section { margin-bottom: 24px; }\n .section-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--muted);\n text-transform: uppercase; letter-spacing: 0.5px; }\n table { width: 100%; border-collapse: collapse; background: var(--surface);\n border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 13px; }\n th { text-align: left; padding: 10px 12px; background: var(--bg); color: var(--muted);\n font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }\n td { padding: 8px 12px; border-top: 1px solid var(--border); font-variant-numeric: tabular-nums; }\n tr:hover td { background: rgba(88,166,255,0.04); }\n .waterfall { display: flex; align-items: center; height: 18px; min-width: 200px; position: relative; }\n .waterfall-seg { height: 100%; border-radius: 2px; min-width: 2px; }\n .waterfall-seg.queue { background: var(--queue); }\n .waterfall-seg.overhead { background: var(--yellow); }\n .waterfall-seg.ttfb { background: var(--ttfb); }\n .waterfall-seg.response { background: var(--upstream); }\n .legend { display: flex; gap: 16px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }\n .legend-dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; vertical-align: middle; }\n .status-ok { color: var(--green); }\n .status-err { color: var(--red); }\n .pct-table td:first-child { font-weight: 500; }\n .pct-table .phase-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }\n .mono { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }\n .refresh-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }\n .refresh-bar select, .refresh-bar button {\n background: var(--surface); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer;\n }\n .refresh-bar button:hover { border-color: var(--accent); }\n .refresh-indicator { font-size: 11px; color: var(--muted); }\n .empty { text-align: center; padding: 48px; color: var(--muted); }\n\n /* Tabs */\n .tabs { display: flex; gap: 0; margin-bottom: 20px; border-bottom: 1px solid var(--border); }\n .tab { padding: 10px 20px; font-size: 13px; font-weight: 500; color: var(--muted); cursor: pointer;\n border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.15s, border-color 0.15s;\n user-select: none; }\n .tab:hover { color: var(--text); }\n .tab.active { color: var(--accent); border-bottom-color: var(--accent); }\n .tab-badge { font-size: 10px; padding: 1px 6px; border-radius: 10px; margin-left: 6px;\n background: var(--border); color: var(--muted); font-variant-numeric: tabular-nums; }\n .tab.active .tab-badge { background: rgba(88,166,255,0.15); color: var(--accent); }\n .tab-panel { display: none; }\n .tab-panel.active { display: block; }\n\n /* Log filters */\n .log-filters { display: flex; gap: 8px; margin-bottom: 12px; }\n .log-filter { font-size: 11px; padding: 3px 10px; border-radius: 12px; cursor: pointer;\n border: 1px solid var(--border); background: var(--surface); color: var(--muted);\n transition: all 0.15s; }\n .log-filter:hover { border-color: var(--accent); color: var(--text); }\n .log-filter.active { background: rgba(88,166,255,0.1); border-color: var(--accent); color: var(--accent); }\n</style>\n</head>\n<body>\n<h1>Meridian</h1>\n<div class=\"subtitle\">Request Performance Telemetry</div>\n\n<div class=\"refresh-bar\">\n <select id=\"window\">\n <option value=\"300000\">Last 5 min</option>\n <option value=\"900000\">Last 15 min</option>\n <option value=\"3600000\" selected>Last 1 hour</option>\n <option value=\"86400000\">Last 24 hours</option>\n </select>\n <button onclick=\"refresh()\">Refresh</button>\n <label><input type=\"checkbox\" id=\"autoRefresh\" checked> Auto (5s)</label>\n <span class=\"refresh-indicator\" id=\"lastUpdate\"></span>\n</div>\n\n<div id=\"content\"><div class=\"empty\">Loading\u2026</div></div>\n\n<script>\nconst $ = s => document.querySelector(s);\nconst $$ = s => document.querySelectorAll(s);\nlet timer;\nlet activeTab = 'requests';\nlet activeLogFilter = 'all';\n\nfunction ms(v) {\n if (v == null) return '\u2014';\n if (v < 1000) return v + 'ms';\n return (v / 1000).toFixed(1) + 's';\n}\n\nfunction ago(ts) {\n const s = Math.floor((Date.now() - ts) / 1000);\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.floor(s/60) + 'm ago';\n return Math.floor(s/3600) + 'h ago';\n}\n\nfunction pctRow(label, color, phase) {\n return '<tr>'\n + '<td><span class=\"phase-dot\" style=\"background:' + color + '\"></span>' + label + '</td>'\n + '<td class=\"mono\">' + ms(phase.p50) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p95) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p99) + '</td>'\n + '<td class=\"mono\">' + ms(phase.min) + '</td>'\n + '<td class=\"mono\">' + ms(phase.max) + '</td>'\n + '<td class=\"mono\">' + ms(phase.avg) + '</td>'\n + '</tr>';\n}\n\nfunction switchTab(tab) {\n activeTab = tab;\n $$('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));\n $$('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'panel-' + tab));\n}\n\nfunction setLogFilter(filter) {\n activeLogFilter = filter;\n $$('.log-filter').forEach(f => f.classList.toggle('active', f.dataset.filter === filter));\n $$('.log-row').forEach(r => {\n r.style.display = (filter === 'all' || r.dataset.category === filter) ? '' : 'none';\n });\n}\n\nasync function refresh() {\n const w = $('#window').value;\n try {\n const [summary, reqs, logs] = await Promise.all([\n fetch('/telemetry/summary?window=' + w).then(r => r.json()),\n fetch('/telemetry/requests?limit=50&since=' + (Date.now() - Number(w))).then(r => r.json()),\n fetch('/telemetry/logs?limit=200&since=' + (Date.now() - Number(w))).then(r => r.json()),\n ]);\n render(summary, reqs, logs);\n $('#lastUpdate').textContent = 'Updated ' + new Date().toLocaleTimeString();\n } catch (e) {\n $('#content').innerHTML = '<div class=\"empty\">Failed to load telemetry</div>';\n }\n}\n\nfunction render(s, reqs, logs) {\n if (s.totalRequests === 0 && (!logs || logs.length === 0)) {\n $('#content').innerHTML = '<div class=\"empty\">No requests recorded yet. Send a request through the proxy to see telemetry.</div>';\n return;\n }\n\n // Count lineage types for badges\n const lineageCounts = {};\n for (const r of reqs) { const t = r.lineageType || 'unknown'; lineageCounts[t] = (lineageCounts[t] || 0) + 1; }\n const logCounts = { session: 0, lineage: 0, error: 0 };\n for (const l of logs) { if (logCounts[l.category] !== undefined) logCounts[l.category]++; }\n\n // Tabs\n let html = '<div class=\"tabs\">'\n + '<div class=\"tab' + (activeTab === 'overview' ? ' active' : '') + '\" data-tab=\"overview\" onclick=\"switchTab('overview')\">Overview</div>'\n + '<div class=\"tab' + (activeTab === 'requests' ? ' active' : '') + '\" data-tab=\"requests\" onclick=\"switchTab('requests')\">'\n + 'Requests<span class=\"tab-badge\">' + reqs.length + '</span></div>'\n + '<div class=\"tab' + (activeTab === 'logs' ? ' active' : '') + '\" data-tab=\"logs\" onclick=\"switchTab('logs')\">'\n + 'Logs<span class=\"tab-badge\">' + logs.length + '</span></div>'\n + '</div>';\n\n // ==================== Overview tab ====================\n html += '<div id=\"panel-overview\" class=\"tab-panel' + (activeTab === 'overview' ? ' active' : '') + '\">';\n\n // Summary cards\n html += '<div class=\"cards\">'\n + card('Requests', s.totalRequests, s.requestsPerMinute.toFixed(1) + ' req/min')\n + card('Errors', s.errorCount, s.totalRequests > 0 ? ((s.errorCount/s.totalRequests)*100).toFixed(1) + '% error rate' : '')\n + card('Median Total', ms(s.totalDuration.p50), 'p95: ' + ms(s.totalDuration.p95))\n + card('Median TTFB', ms(s.ttfb.p50), 'p95: ' + ms(s.ttfb.p95))\n + card('Proxy Overhead', ms(s.proxyOverhead.p50), 'p95: ' + ms(s.proxyOverhead.p95))\n + card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))\n + '</div>';\n\n // Model breakdown\n const models = Object.entries(s.byModel);\n if (models.length > 0) {\n html += '<div class=\"cards\">';\n for (const [name, data] of models) {\n html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));\n }\n html += '</div>';\n }\n\n // Lineage breakdown\n if (Object.keys(lineageCounts).length > 0) {\n html += '<div class=\"cards\">';\n const lineageColors = {continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'};\n for (const [type, count] of Object.entries(lineageCounts)) {\n html += '<div class=\"card\"><div class=\"card-label\">Lineage: ' + type + '</div>'\n + '<div class=\"card-value\" style=\"color:' + (lineageColors[type] || 'var(--text)') + '\">' + count + '</div></div>';\n }\n html += '</div>';\n }\n\n // Percentile table\n html += '<div class=\"section\"><div class=\"section-title\">Percentiles</div>'\n + '<table class=\"pct-table\"><thead><tr><th>Phase</th><th>p50</th><th>p95</th><th>p99</th><th>Min</th><th>Max</th><th>Avg</th></tr></thead><tbody>'\n + pctRow('Queue Wait', 'var(--queue)', s.queueWait)\n + pctRow('Proxy Overhead', 'var(--yellow)', s.proxyOverhead)\n + pctRow('TTFB', 'var(--ttfb)', s.ttfb)\n + pctRow('Upstream', 'var(--upstream)', s.upstreamDuration)\n + pctRow('Total', 'var(--purple)', s.totalDuration)\n + '</tbody></table></div>';\n\n html += '</div>'; // end overview panel\n\n // ==================== Requests tab ====================\n html += '<div id=\"panel-requests\" class=\"tab-panel' + (activeTab === 'requests' ? ' active' : '') + '\">';\n\n html += '<div class=\"legend\">'\n + '<span><span class=\"legend-dot\" style=\"background:var(--queue)\"></span>Queue</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--yellow)\"></span>Proxy</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--ttfb)\"></span>TTFB</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--upstream)\"></span>Response</span>'\n + '</div>'\n + '<table><thead><tr><th>Time</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'\n + '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';\n\n const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);\n\n for (const r of reqs) {\n const statusClass = r.error ? 'status-err' : 'status-ok';\n const statusText = r.error ? r.error : r.status;\n const scale = 280 / maxTotal;\n const qW = Math.max(r.queueWaitMs * scale, 2);\n const ohW = Math.max((r.proxyOverheadMs || 0) * scale, 0);\n const ttfbW = Math.max((r.ttfbMs || 0) * scale, 0);\n const respW = Math.max((r.upstreamDurationMs - (r.ttfbMs || 0)) * scale, 2);\n\n const lineageBadge = r.lineageType ? '<span style=\"font-size:10px;padding:1px 5px;border-radius:3px;background:' + ({continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'}[r.lineageType] || 'var(--muted)') + ';color:var(--bg)\">' + r.lineageType + '</span>' : '';\n const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '\u2014';\n const msgCount = r.messageCount != null ? r.messageCount : '?';\n\n html += '<tr>'\n + '<td class=\"mono\">' + ago(r.timestamp) + '</td>'\n + '<td>' + (r.requestModel || r.model) + '<br><span style=\"font-size:10px;color:var(--muted)\">' + r.model + '</span></td>'\n + '<td>' + r.mode + '</td>'\n + '<td class=\"mono\">' + sessionShort + ' ' + lineageBadge + '<br><span style=\"font-size:10px;color:var(--muted)\">' + msgCount + ' msgs</span></td>'\n + '<td class=\"' + statusClass + '\">' + statusText + '</td>'\n + '<td class=\"mono\">' + ms(r.queueWaitMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.proxyOverheadMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.ttfbMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.totalDurationMs) + '</td>'\n + '<td><div class=\"waterfall\">'\n + '<div class=\"waterfall-seg queue\" style=\"width:' + qW + 'px\"></div>'\n + '<div class=\"waterfall-seg overhead\" style=\"width:' + ohW + 'px\"></div>'\n + '<div class=\"waterfall-seg ttfb\" style=\"width:' + ttfbW + 'px\"></div>'\n + '<div class=\"waterfall-seg response\" style=\"width:' + respW + 'px\"></div>'\n + '</div></td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n html += '</div>'; // end requests panel\n\n // ==================== Logs tab ====================\n html += '<div id=\"panel-logs\" class=\"tab-panel' + (activeTab === 'logs' ? ' active' : '') + '\">';\n\n // Filter buttons\n html += '<div class=\"log-filters\">'\n + '<span class=\"log-filter' + (activeLogFilter === 'all' ? ' active' : '') + '\" data-filter=\"all\" onclick=\"setLogFilter('all')\">All<span class=\"tab-badge\">' + logs.length + '</span></span>'\n + '<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>'\n + '<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>'\n + '<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>'\n + '</div>';\n\n if (logs.length === 0) {\n html += '<div class=\"empty\">No diagnostic logs in this time window.</div>';\n } else {\n html += '<table><thead><tr>'\n + '<th style=\"width:80px\">Time</th><th style=\"width:55px\">Level</th><th style=\"width:70px\">Category</th><th>Message</th>'\n + '</tr></thead><tbody>';\n\n for (const log of logs) {\n const levelColor = {info:'var(--green)',warn:'var(--yellow)',error:'var(--red)'}[log.level] || 'var(--muted)';\n const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)'}[log.category] || 'var(--muted)';\n const display = (activeLogFilter === 'all' || log.category === activeLogFilter) ? '' : 'display:none';\n html += '<tr class=\"log-row\" data-category=\"' + log.category + '\" style=\"' + display + '\">'\n + '<td class=\"mono\">' + ago(log.timestamp) + '</td>'\n + '<td><span style=\"color:' + levelColor + '\">' + log.level + '</span></td>'\n + '<td><span style=\"color:' + catColor + '\">' + log.category + '</span></td>'\n + '<td class=\"mono\" style=\"word-break:break-all\">' + log.message + '</td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n }\n html += '</div>'; // end logs panel\n\n $('#content').innerHTML = html;\n}\n\nfunction card(label, value, detail) {\n return '<div class=\"card\"><div class=\"card-label\">' + label + '</div>'\n + '<div class=\"card-value\">' + value + '</div>'\n + (detail ? '<div class=\"card-detail\">' + detail + '</div>' : '')\n + '</div>';\n}\n\n$('#autoRefresh').addEventListener('change', function() {\n clearInterval(timer);\n if (this.checked) timer = setInterval(refresh, 5000);\n});\n$('#window').addEventListener('change', refresh);\n\nrefresh();\ntimer = setInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
5
|
+
export declare const dashboardHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Meridian \u2014 Telemetry</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"/telemetry/icon.svg\">\n<style>\n :root {\n --bg: #0d1117; --surface: #161b22; --border: #30363d;\n --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;\n --green: #3fb950; --yellow: #d29922; --red: #f85149;\n --blue: #58a6ff; --purple: #bc8cff;\n --queue: #d29922; --ttfb: #58a6ff; --upstream: #3fb950; --total: #bc8cff;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n background: var(--bg); color: var(--text); padding: 24px; line-height: 1.5; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }\n .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }\n .card-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n .card-value { font-size: 28px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }\n .card-detail { font-size: 12px; color: var(--muted); margin-top: 2px; }\n .section { margin-bottom: 24px; }\n .section-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--muted);\n text-transform: uppercase; letter-spacing: 0.5px; }\n table { width: 100%; border-collapse: collapse; background: var(--surface);\n border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 13px; }\n th { text-align: left; padding: 10px 12px; background: var(--bg); color: var(--muted);\n font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }\n td { padding: 8px 12px; border-top: 1px solid var(--border); font-variant-numeric: tabular-nums; }\n tr:hover td { background: rgba(88,166,255,0.04); }\n .waterfall { display: flex; align-items: center; height: 18px; min-width: 200px; position: relative; }\n .waterfall-seg { height: 100%; border-radius: 2px; min-width: 2px; }\n .waterfall-seg.queue { background: var(--queue); }\n .waterfall-seg.overhead { background: var(--yellow); }\n .waterfall-seg.ttfb { background: var(--ttfb); }\n .waterfall-seg.response { background: var(--upstream); }\n .legend { display: flex; gap: 16px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }\n .legend-dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; vertical-align: middle; }\n .status-ok { color: var(--green); }\n .status-err { color: var(--red); }\n .pct-table td:first-child { font-weight: 500; }\n .pct-table .phase-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }\n .mono { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }\n .refresh-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }\n .refresh-bar select, .refresh-bar button {\n background: var(--surface); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer;\n }\n .refresh-bar button:hover { border-color: var(--accent); }\n .refresh-indicator { font-size: 11px; color: var(--muted); }\n .empty { text-align: center; padding: 48px; color: var(--muted); }\n\n /* Tabs */\n .tabs { display: flex; gap: 0; margin-bottom: 20px; border-bottom: 1px solid var(--border); }\n .tab { padding: 10px 20px; font-size: 13px; font-weight: 500; color: var(--muted); cursor: pointer;\n border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.15s, border-color 0.15s;\n user-select: none; }\n .tab:hover { color: var(--text); }\n .tab.active { color: var(--accent); border-bottom-color: var(--accent); }\n .tab-badge { font-size: 10px; padding: 1px 6px; border-radius: 10px; margin-left: 6px;\n background: var(--border); color: var(--muted); font-variant-numeric: tabular-nums; }\n .tab.active .tab-badge { background: rgba(88,166,255,0.15); color: var(--accent); }\n .tab-panel { display: none; }\n .tab-panel.active { display: block; }\n\n /* Log filters */\n .log-filters { display: flex; gap: 8px; margin-bottom: 12px; }\n .log-filter { font-size: 11px; padding: 3px 10px; border-radius: 12px; cursor: pointer;\n border: 1px solid var(--border); background: var(--surface); color: var(--muted);\n transition: all 0.15s; }\n .log-filter:hover { border-color: var(--accent); color: var(--text); }\n .log-filter.active { background: rgba(88,166,255,0.1); border-color: var(--accent); color: var(--accent); }\n</style>\n</head>\n<body>\n<h1>Meridian</h1>\n<div class=\"subtitle\">Request Performance Telemetry</div>\n\n<div class=\"refresh-bar\">\n <select id=\"window\">\n <option value=\"300000\">Last 5 min</option>\n <option value=\"900000\">Last 15 min</option>\n <option value=\"3600000\" selected>Last 1 hour</option>\n <option value=\"86400000\">Last 24 hours</option>\n </select>\n <button onclick=\"refresh()\">Refresh</button>\n <label><input type=\"checkbox\" id=\"autoRefresh\" checked> Auto (5s)</label>\n <span class=\"refresh-indicator\" id=\"lastUpdate\"></span>\n</div>\n\n<div id=\"content\"><div class=\"empty\">Loading\u2026</div></div>\n\n<script>\nconst $ = s => document.querySelector(s);\nconst $$ = s => document.querySelectorAll(s);\nlet timer;\nlet activeTab = 'requests';\nlet activeLogFilter = 'all';\n\nfunction ms(v) {\n if (v == null) return '\u2014';\n if (v < 1000) return v + 'ms';\n return (v / 1000).toFixed(1) + 's';\n}\n\nfunction ago(ts) {\n const s = Math.floor((Date.now() - ts) / 1000);\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.floor(s/60) + 'm ago';\n return Math.floor(s/3600) + 'h ago';\n}\n\nfunction pctRow(label, color, phase) {\n return '<tr>'\n + '<td><span class=\"phase-dot\" style=\"background:' + color + '\"></span>' + label + '</td>'\n + '<td class=\"mono\">' + ms(phase.p50) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p95) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p99) + '</td>'\n + '<td class=\"mono\">' + ms(phase.min) + '</td>'\n + '<td class=\"mono\">' + ms(phase.max) + '</td>'\n + '<td class=\"mono\">' + ms(phase.avg) + '</td>'\n + '</tr>';\n}\n\nfunction switchTab(tab) {\n activeTab = tab;\n $$('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));\n $$('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'panel-' + tab));\n}\n\nfunction setLogFilter(filter) {\n activeLogFilter = filter;\n $$('.log-filter').forEach(f => f.classList.toggle('active', f.dataset.filter === filter));\n $$('.log-row').forEach(r => {\n r.style.display = (filter === 'all' || r.dataset.category === filter) ? '' : 'none';\n });\n}\n\nasync function refresh() {\n const w = $('#window').value;\n try {\n const [summary, reqs, logs] = await Promise.all([\n fetch('/telemetry/summary?window=' + w).then(r => r.json()),\n fetch('/telemetry/requests?limit=50&since=' + (Date.now() - Number(w))).then(r => r.json()),\n fetch('/telemetry/logs?limit=200&since=' + (Date.now() - Number(w))).then(r => r.json()),\n ]);\n render(summary, reqs, logs);\n $('#lastUpdate').textContent = 'Updated ' + new Date().toLocaleTimeString();\n } catch (e) {\n $('#content').innerHTML = '<div class=\"empty\">Failed to load telemetry</div>';\n }\n}\n\nfunction render(s, reqs, logs) {\n if (s.totalRequests === 0 && (!logs || logs.length === 0)) {\n $('#content').innerHTML = '<div class=\"empty\">No requests recorded yet. Send a request through the proxy to see telemetry.</div>';\n return;\n }\n\n // Count lineage types for badges\n const lineageCounts = {};\n for (const r of reqs) { const t = r.lineageType || 'unknown'; lineageCounts[t] = (lineageCounts[t] || 0) + 1; }\n const logCounts = { session: 0, lineage: 0, error: 0 };\n for (const l of logs) { if (logCounts[l.category] !== undefined) logCounts[l.category]++; }\n\n // Tabs\n let html = '<div class=\"tabs\">'\n + '<div class=\"tab' + (activeTab === 'overview' ? ' active' : '') + '\" data-tab=\"overview\" onclick=\"switchTab('overview')\">Overview</div>'\n + '<div class=\"tab' + (activeTab === 'requests' ? ' active' : '') + '\" data-tab=\"requests\" onclick=\"switchTab('requests')\">'\n + 'Requests<span class=\"tab-badge\">' + reqs.length + '</span></div>'\n + '<div class=\"tab' + (activeTab === 'logs' ? ' active' : '') + '\" data-tab=\"logs\" onclick=\"switchTab('logs')\">'\n + 'Logs<span class=\"tab-badge\">' + logs.length + '</span></div>'\n + '</div>';\n\n // ==================== Overview tab ====================\n html += '<div id=\"panel-overview\" class=\"tab-panel' + (activeTab === 'overview' ? ' active' : '') + '\">';\n\n // Summary cards\n html += '<div class=\"cards\">'\n + card('Requests', s.totalRequests, s.requestsPerMinute.toFixed(1) + ' req/min')\n + card('Errors', s.errorCount, s.totalRequests > 0 ? ((s.errorCount/s.totalRequests)*100).toFixed(1) + '% error rate' : '')\n + card('Median Total', ms(s.totalDuration.p50), 'p95: ' + ms(s.totalDuration.p95))\n + card('Median TTFB', ms(s.ttfb.p50), 'p95: ' + ms(s.ttfb.p95))\n + card('Proxy Overhead', ms(s.proxyOverhead.p50), 'p95: ' + ms(s.proxyOverhead.p95))\n + card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))\n + '</div>';\n\n // Model breakdown\n const models = Object.entries(s.byModel);\n if (models.length > 0) {\n html += '<div class=\"cards\">';\n for (const [name, data] of models) {\n html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));\n }\n html += '</div>';\n }\n\n // Lineage breakdown\n if (Object.keys(lineageCounts).length > 0) {\n html += '<div class=\"cards\">';\n const lineageColors = {continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'};\n for (const [type, count] of Object.entries(lineageCounts)) {\n html += '<div class=\"card\"><div class=\"card-label\">Lineage: ' + type + '</div>'\n + '<div class=\"card-value\" style=\"color:' + (lineageColors[type] || 'var(--text)') + '\">' + count + '</div></div>';\n }\n html += '</div>';\n }\n\n // Percentile table\n html += '<div class=\"section\"><div class=\"section-title\">Percentiles</div>'\n + '<table class=\"pct-table\"><thead><tr><th>Phase</th><th>p50</th><th>p95</th><th>p99</th><th>Min</th><th>Max</th><th>Avg</th></tr></thead><tbody>'\n + pctRow('Queue Wait', 'var(--queue)', s.queueWait)\n + pctRow('Proxy Overhead', 'var(--yellow)', s.proxyOverhead)\n + pctRow('TTFB', 'var(--ttfb)', s.ttfb)\n + pctRow('Upstream', 'var(--upstream)', s.upstreamDuration)\n + pctRow('Total', 'var(--purple)', s.totalDuration)\n + '</tbody></table></div>';\n\n html += '</div>'; // end overview panel\n\n // ==================== Requests tab ====================\n html += '<div id=\"panel-requests\" class=\"tab-panel' + (activeTab === 'requests' ? ' active' : '') + '\">';\n\n html += '<div class=\"legend\">'\n + '<span><span class=\"legend-dot\" style=\"background:var(--queue)\"></span>Queue</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--yellow)\"></span>Proxy</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--ttfb)\"></span>TTFB</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--upstream)\"></span>Response</span>'\n + '</div>'\n + '<table><thead><tr><th>Time</th><th>Adapter</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'\n + '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';\n\n const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);\n\n for (const r of reqs) {\n const statusClass = r.error ? 'status-err' : 'status-ok';\n const statusText = r.error ? r.error : r.status;\n const scale = 280 / maxTotal;\n const qW = Math.max(r.queueWaitMs * scale, 2);\n const ohW = Math.max((r.proxyOverheadMs || 0) * scale, 0);\n const ttfbW = Math.max((r.ttfbMs || 0) * scale, 0);\n const respW = Math.max((r.upstreamDurationMs - (r.ttfbMs || 0)) * scale, 2);\n\n const lineageBadge = r.lineageType ? '<span style=\"font-size:10px;padding:1px 5px;border-radius:3px;background:' + ({continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'}[r.lineageType] || 'var(--muted)') + ';color:var(--bg)\">' + r.lineageType + '</span>' : '';\n const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '\u2014';\n const msgCount = r.messageCount != null ? r.messageCount : '?';\n\n html += '<tr>'\n + '<td class=\"mono\">' + ago(r.timestamp) + '</td>'\n + '<td>' + (r.adapter || '\u2014') + '</td>'\n + '<td>' + (r.requestModel || r.model) + '<br><span style=\"font-size:10px;color:var(--muted)\">' + r.model + '</span></td>'\n + '<td>' + r.mode + '</td>'\n + '<td class=\"mono\">' + sessionShort + ' ' + lineageBadge + '<br><span style=\"font-size:10px;color:var(--muted)\">' + msgCount + ' msgs</span></td>'\n + '<td class=\"' + statusClass + '\">' + statusText + '</td>'\n + '<td class=\"mono\">' + ms(r.queueWaitMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.proxyOverheadMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.ttfbMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.totalDurationMs) + '</td>'\n + '<td><div class=\"waterfall\">'\n + '<div class=\"waterfall-seg queue\" style=\"width:' + qW + 'px\"></div>'\n + '<div class=\"waterfall-seg overhead\" style=\"width:' + ohW + 'px\"></div>'\n + '<div class=\"waterfall-seg ttfb\" style=\"width:' + ttfbW + 'px\"></div>'\n + '<div class=\"waterfall-seg response\" style=\"width:' + respW + 'px\"></div>'\n + '</div></td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n html += '</div>'; // end requests panel\n\n // ==================== Logs tab ====================\n html += '<div id=\"panel-logs\" class=\"tab-panel' + (activeTab === 'logs' ? ' active' : '') + '\">';\n\n // Filter buttons\n html += '<div class=\"log-filters\">'\n + '<span class=\"log-filter' + (activeLogFilter === 'all' ? ' active' : '') + '\" data-filter=\"all\" onclick=\"setLogFilter('all')\">All<span class=\"tab-badge\">' + logs.length + '</span></span>'\n + '<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>'\n + '<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>'\n + '<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>'\n + '</div>';\n\n if (logs.length === 0) {\n html += '<div class=\"empty\">No diagnostic logs in this time window.</div>';\n } else {\n html += '<table><thead><tr>'\n + '<th style=\"width:80px\">Time</th><th style=\"width:55px\">Level</th><th style=\"width:70px\">Category</th><th>Message</th>'\n + '</tr></thead><tbody>';\n\n for (const log of logs) {\n const levelColor = {info:'var(--green)',warn:'var(--yellow)',error:'var(--red)'}[log.level] || 'var(--muted)';\n const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)'}[log.category] || 'var(--muted)';\n const display = (activeLogFilter === 'all' || log.category === activeLogFilter) ? '' : 'display:none';\n html += '<tr class=\"log-row\" data-category=\"' + log.category + '\" style=\"' + display + '\">'\n + '<td class=\"mono\">' + ago(log.timestamp) + '</td>'\n + '<td><span style=\"color:' + levelColor + '\">' + log.level + '</span></td>'\n + '<td><span style=\"color:' + catColor + '\">' + log.category + '</span></td>'\n + '<td class=\"mono\" style=\"word-break:break-all\">' + log.message + '</td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n }\n html += '</div>'; // end logs panel\n\n $('#content').innerHTML = html;\n}\n\nfunction card(label, value, detail) {\n return '<div class=\"card\"><div class=\"card-label\">' + label + '</div>'\n + '<div class=\"card-value\">' + value + '</div>'\n + (detail ? '<div class=\"card-detail\">' + detail + '</div>' : '')\n + '</div>';\n}\n\n$('#autoRefresh').addEventListener('change', function() {\n clearInterval(timer);\n if (this.checked) timer = setInterval(refresh, 5000);\n});\n$('#window').addEventListener('change', refresh);\n\nrefresh();\ntimer = setInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
6
6
|
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,aAAa,gsiBAsUlB,CAAA"}
|
|
@@ -14,6 +14,8 @@ export interface RequestMetric {
|
|
|
14
14
|
requestId: string;
|
|
15
15
|
/** When this metric was recorded */
|
|
16
16
|
timestamp: number;
|
|
17
|
+
/** Which agent adapter handled this request */
|
|
18
|
+
adapter?: string;
|
|
17
19
|
/** Model used for SDK query (sonnet, opus, haiku, sonnet[1m], etc.) */
|
|
18
20
|
model: string;
|
|
19
21
|
/** Original model string from the client request (e.g. "claude-sonnet-4-6-20250312") */
|
|
@@ -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,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;CACrB;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;CAC9D"}
|
|
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;CACrB;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;CAC9D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynfar/meridian",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.2",
|
|
4
4
|
"description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/server.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"build": "rm -rf dist && bun build bin/cli.ts src/proxy/server.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --entry-naming '[name].js' && tsc -p tsconfig.build.json",
|
|
25
25
|
"postbuild": "node --check dist/cli.js && node --check dist/server.js && test -f dist/proxy/server.d.ts",
|
|
26
26
|
"prepublishOnly": "bun run build",
|
|
27
|
-
"test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*proxy-extra-usage-fallback*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' && bun test src/__tests__/proxy-extra-usage-fallback.test.ts && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts",
|
|
27
|
+
"test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*proxy-extra-usage-fallback*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' --path-ignore-patterns '**/*proxy-passthrough-thinking*' && bun test src/__tests__/proxy-extra-usage-fallback.test.ts && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts && bun test src/__tests__/proxy-passthrough-thinking.test.ts",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"proxy:direct": "bun run ./bin/cli.ts"
|
|
30
30
|
},
|
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
"opencode",
|
|
60
60
|
"cline",
|
|
61
61
|
"aider",
|
|
62
|
+
"crush",
|
|
63
|
+
"charmbracelet",
|
|
64
|
+
"droid",
|
|
65
|
+
"factory-ai",
|
|
66
|
+
"pi-coding-agent",
|
|
67
|
+
"open-webui",
|
|
62
68
|
"llm",
|
|
63
69
|
"coding-assistant",
|
|
64
70
|
"opencode-claude-max-proxy"
|
|
@@ -75,4 +81,4 @@
|
|
|
75
81
|
"license": "MIT",
|
|
76
82
|
"private": false,
|
|
77
83
|
"packageManager": "bun@1.3.11"
|
|
78
|
-
}
|
|
84
|
+
}
|