@rynfar/meridian 1.27.0 → 1.27.1

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 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
- > [!NOTE]
17
- > **Renamed from `opencode-claude-max-proxy`.** If you're upgrading, see [`MIGRATION.md`](MIGRATION.md) for the checklist. Your existing sessions, env vars, and agent configs all continue to work.
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
- - **Subagent model selection** — primary agents use `sonnet[1m]`; subagents automatically use `sonnet` (200k), preserving your 1M context rate-limit budget
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[1m]`* | Force sonnet tier: `sonnet` (200k) or `sonnet[1m]` (1M). Set to `sonnet` if you hit 1M context rate limits |
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
- *`sonnet[1m]` requires Max subscription with Extra Usage enabled. Falls back to `sonnet` automatically if not available.
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 use the 200k model for all requests. If you're using OpenCode with the Meridian plugin, subagents already use 200k automatically only the primary agent uses 1M.
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" || sonnetOverride === "sonnet[1m]")
7024
- return sonnetOverride;
7025
- if (!use1m)
7026
- return "sonnet";
7027
- if (isSubagent)
7028
- return "sonnet";
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;
@@ -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 defaultAdapter = ADAPTER_MAP[process.env.MERIDIAN_DEFAULT_AGENT || ""] ?? openCodeAdapter;
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
- for (const block of message.message.content) {
14799
- const b = block;
14800
- if (passthrough && b.type === "tool_use" && typeof b.name === "string") {
14801
- b.name = stripMcpPrefix(b.name);
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-6hehvt9f.js";
4
+ } from "./cli-9bsar34x.js";
5
5
  import"./cli-rtab0qa6.js";
6
6
  import"./cli-m9pfb7h9.js";
7
7
  import {
@@ -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. User-Agent starts with "factory-cli/" Droid adapter
15
- * 3. User-Agent starts with "Charm-Crush/" Crush adapter
16
- * 4. litellm/* UA or x-litellm-* headers LiteLLM passthrough adapter
17
- * 5. Default → MERIDIAN_DEFAULT_AGENT env var, or OpenCode
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;AA+B9C;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CAqBtD"}
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"}
@@ -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;AAuC9C,eAAO,MAAM,SAAS,EAAE,YA+FvB,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,CAuB7H;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
+ {"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,CAw7ChF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0ChG"}
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
@@ -6,7 +6,7 @@ import {
6
6
  getMaxSessionsLimit,
7
7
  hashMessage,
8
8
  startProxyServer
9
- } from "./cli-6hehvt9f.js";
9
+ } from "./cli-9bsar34x.js";
10
10
  import"./cli-rtab0qa6.js";
11
11
  import"./cli-m9pfb7h9.js";
12
12
  import"./cli-a05ws7rb.js";
@@ -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(&apos;overview&apos;)\">Overview</div>'\n + '<div class=\"tab' + (activeTab === 'requests' ? ' active' : '') + '\" data-tab=\"requests\" onclick=\"switchTab(&apos;requests&apos;)\">'\n + 'Requests<span class=\"tab-badge\">' + reqs.length + '</span></div>'\n + '<div class=\"tab' + (activeTab === 'logs' ? ' active' : '') + '\" data-tab=\"logs\" onclick=\"switchTab(&apos;logs&apos;)\">'\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(&apos;all&apos;)\">All<span class=\"tab-badge\">' + logs.length + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'session' ? ' active' : '') + '\" data-filter=\"session\" onclick=\"setLogFilter(&apos;session&apos;)\" 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(&apos;lineage&apos;)\" 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(&apos;error&apos;)\" 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(&apos;overview&apos;)\">Overview</div>'\n + '<div class=\"tab' + (activeTab === 'requests' ? ' active' : '') + '\" data-tab=\"requests\" onclick=\"switchTab(&apos;requests&apos;)\">'\n + 'Requests<span class=\"tab-badge\">' + reqs.length + '</span></div>'\n + '<div class=\"tab' + (activeTab === 'logs' ? ' active' : '') + '\" data-tab=\"logs\" onclick=\"switchTab(&apos;logs&apos;)\">'\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(&apos;all&apos;)\">All<span class=\"tab-badge\">' + logs.length + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'session' ? ' active' : '') + '\" data-filter=\"session\" onclick=\"setLogFilter(&apos;session&apos;)\" 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(&apos;lineage&apos;)\" 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(&apos;error&apos;)\" 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,4niBAqUlB,CAAA"}
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.0",
3
+ "version": "1.27.1",
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
+ }