@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.
- package/dist/{cli-jbdchsr4.js → cli-3azh7s3k.js} +55 -1
- package/dist/cli.js +1 -1
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/rateLimitStore.d.ts +73 -0
- package/dist/proxy/rateLimitStore.d.ts.map +1 -0
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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 +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,
|
|
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":"
|
|
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
package/package.json
CHANGED