@rynfar/meridian 1.39.0 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3706,6 +3706,30 @@ import { homedir as homedir5 } from "node:os";
3706
3706
  import { join as join7 } from "node:path";
3707
3707
  import { query } from "@anthropic-ai/claude-agent-sdk";
3708
3708
 
3709
+ // src/proxy/rateLimitStore.ts
3710
+ class RateLimitStore {
3711
+ entries = new Map;
3712
+ record(info) {
3713
+ if (!info || typeof info !== "object")
3714
+ return;
3715
+ const key = info.rateLimitType ?? "default";
3716
+ this.entries.set(key, { ...info, observedAt: Date.now() });
3717
+ }
3718
+ getAll() {
3719
+ return Array.from(this.entries.values()).sort((a, b) => b.observedAt - a.observedAt);
3720
+ }
3721
+ get(key) {
3722
+ return this.entries.get(key);
3723
+ }
3724
+ get size() {
3725
+ return this.entries.size;
3726
+ }
3727
+ clear() {
3728
+ this.entries.clear();
3729
+ }
3730
+ }
3731
+ var rateLimitStore = new RateLimitStore;
3732
+
3709
3733
  // src/proxy/types.ts
3710
3734
  var DEFAULT_PROXY_CONFIG = {
3711
3735
  port: 3456,
@@ -10468,6 +10492,10 @@ function detectAdapter(c) {
10468
10492
  return crushAdapter;
10469
10493
  }
10470
10494
  if (userAgent.startsWith("claude-cli/")) {
10495
+ const claudeCliOverride = (process.env.MERIDIAN_DEFAULT_AGENT || "").toLowerCase();
10496
+ if (claudeCliOverride && ADAPTER_MAP[claudeCliOverride] && claudeCliOverride !== "claude-code" && claudeCliOverride !== "claudecode") {
10497
+ return ADAPTER_MAP[claudeCliOverride];
10498
+ }
10471
10499
  return claudeCodeAdapter;
10472
10500
  }
10473
10501
  if (isLiteLLMRequest(c)) {
@@ -17745,6 +17773,9 @@ function createProxyServer(config = {}) {
17745
17773
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
17746
17774
  advisorModel
17747
17775
  }))) {
17776
+ if (event.type === "rate_limit_event") {
17777
+ rateLimitStore.record(event.rate_limit_info);
17778
+ }
17748
17779
  if (event.type === "assistant" && !event.error) {
17749
17780
  didYieldContent = true;
17750
17781
  }
@@ -18136,6 +18167,9 @@ Subprocess stderr: ${stderrOutput}`;
18136
18167
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
18137
18168
  advisorModel
18138
18169
  }))) {
18170
+ if (event.type === "rate_limit_event") {
18171
+ rateLimitStore.record(event.rate_limit_info);
18172
+ }
18139
18173
  if (event.type === "stream_event") {
18140
18174
  didYieldClientEvent = true;
18141
18175
  }
@@ -18848,7 +18882,8 @@ data: ${JSON.stringify({
18848
18882
  }
18849
18883
  setActiveProfile(body.profile);
18850
18884
  clearSessionCache();
18851
- console.error(`[PROXY] Active profile switched to: ${body.profile} (session cache cleared)`);
18885
+ rateLimitStore.clear();
18886
+ console.error(`[PROXY] Active profile switched to: ${body.profile} (session + rate-limit caches cleared)`);
18852
18887
  return c.json({ success: true, activeProfile: body.profile });
18853
18888
  });
18854
18889
  app.get("/plugins/list", async (c) => {
@@ -18893,6 +18928,7 @@ data: ${JSON.stringify({
18893
18928
  app.post("/auth/refresh", async (c) => {
18894
18929
  const success = await refreshOAuthToken();
18895
18930
  if (success) {
18931
+ rateLimitStore.clear();
18896
18932
  return c.json({ success: true, message: "OAuth token refreshed successfully" });
18897
18933
  }
18898
18934
  return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
@@ -18985,6 +19021,24 @@ data: ${JSON.stringify({
18985
19021
  const isMax = authStatus?.subscriptionType === "max";
18986
19022
  return c.json({ object: "list", data: buildModelList(isMax) });
18987
19023
  });
19024
+ app.get("/v1/usage/quota", (c) => {
19025
+ const entries = rateLimitStore.getAll().filter((entry) => entry.rateLimitType !== undefined);
19026
+ return c.json({
19027
+ buckets: entries.map((entry) => ({
19028
+ type: entry.rateLimitType,
19029
+ status: entry.status,
19030
+ utilization: entry.utilization ?? null,
19031
+ resetsAt: entry.resetsAt ?? null,
19032
+ isUsingOverage: entry.isUsingOverage ?? false,
19033
+ overageStatus: entry.overageStatus ?? null,
19034
+ overageResetsAt: entry.overageResetsAt ?? null,
19035
+ overageDisabledReason: entry.overageDisabledReason ?? null,
19036
+ surpassedThreshold: entry.surpassedThreshold ?? null,
19037
+ observedAt: entry.observedAt
19038
+ })),
19039
+ asOf: Date.now()
19040
+ });
19041
+ });
18988
19042
  app.get("/v1/sessions/:claudeSessionId/context-usage", (c) => {
18989
19043
  const claudeSessionId = c.req.param("claudeSessionId");
18990
19044
  const session = getSessionByClaudeId(claudeSessionId);
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-jbdchsr4.js";
4
+ } from "./cli-3azh7s3k.js";
5
5
  import"./cli-vdp9s10c.js";
6
6
  import"./cli-sry5aqdj.js";
7
7
  import"./cli-4rqtm83g.js";
@@ -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;AA0C9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CAsCtD"}
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;AA0C9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CAgDtD"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Rate limit store — captures `SDKRateLimitInfo` events emitted by
3
+ * `@anthropic-ai/claude-agent-sdk`'s `query()` stream.
4
+ *
5
+ * The SDK reports the live Claude Max subscription quota state as
6
+ * `rate_limit_event` events in the form:
7
+ *
8
+ * {
9
+ * type: "rate_limit_event",
10
+ * rate_limit_info: {
11
+ * status: "allowed" | "allowed_warning" | "rejected",
12
+ * resetsAt?: number, // epoch ms
13
+ * rateLimitType?: "five_hour" | "seven_day"
14
+ * | "seven_day_opus" | "seven_day_sonnet"
15
+ * | "overage",
16
+ * utilization?: number, // 0..1
17
+ * overageStatus?: "allowed" | "allowed_warning" | "rejected",
18
+ * overageResetsAt?: number,
19
+ * isUsingOverage?: boolean,
20
+ * surpassedThreshold?: number,
21
+ * ...
22
+ * },
23
+ * uuid, session_id
24
+ * }
25
+ *
26
+ * We keep the most recent entry per `rateLimitType` (or "default" if absent)
27
+ * in memory. State resets on proxy restart — that's fine because the SDK will
28
+ * push a fresh event on the next request.
29
+ *
30
+ * Singleton — one Meridian process holds one snapshot at a time. With
31
+ * multi-profile setups (`x-meridian-profile` / `POST /profiles/active`)
32
+ * each profile is a separate Claude Max subscription with separate quotas,
33
+ * so the store is **cleared on profile switch and on `/auth/refresh`** —
34
+ * the next SDK call repopulates it for the active profile. Consumers of
35
+ * `/v1/usage/quota` should treat the snapshot as "the active profile's
36
+ * latest known state" and re-fetch after switching profiles.
37
+ */
38
+ import type { SDKRateLimitInfo } from "@anthropic-ai/claude-agent-sdk";
39
+ export interface RateLimitEntry extends SDKRateLimitInfo {
40
+ /** When this entry was captured (epoch ms). */
41
+ observedAt: number;
42
+ }
43
+ /** Type discriminator for the entry's bucket key. */
44
+ export type RateLimitBucketKey = NonNullable<SDKRateLimitInfo["rateLimitType"]> | "default";
45
+ declare class RateLimitStore {
46
+ private entries;
47
+ /**
48
+ * Record a rate-limit info snapshot.
49
+ * Last-write-wins per bucket key (rateLimitType). Older entries for the
50
+ * same key are overwritten — clients should treat the latest as canonical.
51
+ */
52
+ record(info: SDKRateLimitInfo | undefined | null): void;
53
+ /** Snapshot all current entries, newest-first by observedAt. */
54
+ getAll(): RateLimitEntry[];
55
+ /** Snapshot a single bucket, or undefined if not yet seen. */
56
+ get(key: RateLimitBucketKey): RateLimitEntry | undefined;
57
+ /** Number of distinct buckets observed. */
58
+ get size(): number;
59
+ /**
60
+ * Drop all stored entries. Wired into the `POST /profiles/active` and
61
+ * `POST /auth/refresh` handlers so quotas can't leak across profiles or
62
+ * stale credential boundaries. Also used by tests for isolation.
63
+ */
64
+ clear(): void;
65
+ }
66
+ /**
67
+ * Process-wide singleton. Importers should always use this instance — do
68
+ * not instantiate `RateLimitStore` directly outside of tests.
69
+ */
70
+ export declare const rateLimitStore: RateLimitStore;
71
+ /** Exported for test isolation only. */
72
+ export { RateLimitStore as _RateLimitStoreForTests };
73
+ //# sourceMappingURL=rateLimitStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimitStore.d.ts","sourceRoot":"","sources":["../../src/proxy/rateLimitStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEtE,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,qDAAqD;AACrD,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC,GAAG,SAAS,CAAA;AAE3F,cAAM,cAAc;IAClB,OAAO,CAAC,OAAO,CAAgD;IAE/D;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI;IAMvD,gEAAgE;IAChE,MAAM,IAAI,cAAc,EAAE;IAI1B,8DAA8D;IAC9D,GAAG,CAAC,GAAG,EAAE,kBAAkB,GAAG,cAAc,GAAG,SAAS;IAIxD,2CAA2C;IAC3C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,KAAK,IAAI,IAAI;CAGd;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,gBAAuB,CAAA;AAElD,wCAAwC;AACxC,OAAO,EAAE,cAAc,IAAI,uBAAuB,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAGvD,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAA;AAKpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AA+BnG,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EAEpB,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AA+N7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA+lEhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAoEhG"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAGvD,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAA;AAKpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AA+BnG,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EAEpB,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AA+N7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAkpEhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAoEhG"}
package/dist/server.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  runObserveHook,
11
11
  runTransformHook,
12
12
  startProxyServer
13
- } from "./cli-jbdchsr4.js";
13
+ } from "./cli-3azh7s3k.js";
14
14
  import"./cli-vdp9s10c.js";
15
15
  import"./cli-sry5aqdj.js";
16
16
  import"./cli-4rqtm83g.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.39.0",
3
+ "version": "1.40.0",
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",