@rynfar/meridian 1.39.1 → 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,
@@ -17749,6 +17773,9 @@ function createProxyServer(config = {}) {
17749
17773
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
17750
17774
  advisorModel
17751
17775
  }))) {
17776
+ if (event.type === "rate_limit_event") {
17777
+ rateLimitStore.record(event.rate_limit_info);
17778
+ }
17752
17779
  if (event.type === "assistant" && !event.error) {
17753
17780
  didYieldContent = true;
17754
17781
  }
@@ -18140,6 +18167,9 @@ Subprocess stderr: ${stderrOutput}`;
18140
18167
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
18141
18168
  advisorModel
18142
18169
  }))) {
18170
+ if (event.type === "rate_limit_event") {
18171
+ rateLimitStore.record(event.rate_limit_info);
18172
+ }
18143
18173
  if (event.type === "stream_event") {
18144
18174
  didYieldClientEvent = true;
18145
18175
  }
@@ -18852,7 +18882,8 @@ data: ${JSON.stringify({
18852
18882
  }
18853
18883
  setActiveProfile(body.profile);
18854
18884
  clearSessionCache();
18855
- 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)`);
18856
18887
  return c.json({ success: true, activeProfile: body.profile });
18857
18888
  });
18858
18889
  app.get("/plugins/list", async (c) => {
@@ -18897,6 +18928,7 @@ data: ${JSON.stringify({
18897
18928
  app.post("/auth/refresh", async (c) => {
18898
18929
  const success = await refreshOAuthToken();
18899
18930
  if (success) {
18931
+ rateLimitStore.clear();
18900
18932
  return c.json({ success: true, message: "OAuth token refreshed successfully" });
18901
18933
  }
18902
18934
  return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
@@ -18989,6 +19021,24 @@ data: ${JSON.stringify({
18989
19021
  const isMax = authStatus?.subscriptionType === "max";
18990
19022
  return c.json({ object: "list", data: buildModelList(isMax) });
18991
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
+ });
18992
19042
  app.get("/v1/sessions/:claudeSessionId/context-usage", (c) => {
18993
19043
  const claudeSessionId = c.req.param("claudeSessionId");
18994
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-wv0e3636.js";
4
+ } from "./cli-3azh7s3k.js";
5
5
  import"./cli-vdp9s10c.js";
6
6
  import"./cli-sry5aqdj.js";
7
7
  import"./cli-4rqtm83g.js";
@@ -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-wv0e3636.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.1",
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",