@rynfar/meridian 1.29.1 → 1.29.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -38
- package/dist/{cli-msyx6dnk.js → cli-trtwsfge.js} +50 -5
- package/dist/cli.js +1 -1
- package/dist/proxy/betas.d.ts +70 -0
- package/dist/proxy/betas.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
package/README.md
CHANGED
|
@@ -7,31 +7,34 @@
|
|
|
7
7
|
<a href="https://www.npmjs.com/package/@rynfar/meridian"><img src="https://img.shields.io/npm/v/@rynfar/meridian?style=flat-square&color=8b5cf6&label=npm" alt="npm"></a>
|
|
8
8
|
<a href="#"><img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-a78bfa?style=flat-square" alt="Platform"></a>
|
|
9
9
|
<a href="#"><img src="https://img.shields.io/badge/license-MIT-c4b5fd?style=flat-square" alt="License"></a>
|
|
10
|
+
<a href="https://discord.gg/7vNVFYBz"><img src="https://img.shields.io/badge/discord-join-5865F2?style=flat-square&logo=discord&logoColor=white" alt="Discord"></a>
|
|
10
11
|
</p>
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
|
-
Meridian bridges the Claude Code SDK to the standard Anthropic API. No OAuth interception. No binary patches. No hacks. Just pure, documented SDK calls. Any tool that speaks the Anthropic or OpenAI protocol — OpenCode,
|
|
15
|
+
Meridian bridges the Claude Code SDK to the standard Anthropic API. No OAuth interception. No binary patches. No hacks. Just pure, documented SDK calls. Any tool that speaks the Anthropic or OpenAI protocol — OpenCode, Crush, Cline, Aider, Pi, Droid, Open WebUI — connects to Meridian and gets Claude, with session management, streaming, and prompt caching handled natively by the SDK.
|
|
15
16
|
|
|
16
|
-
> [!
|
|
17
|
-
> ### Meridian
|
|
17
|
+
> [!NOTE]
|
|
18
|
+
> ### How Meridian works with Anthropic
|
|
18
19
|
>
|
|
19
|
-
>
|
|
20
|
+
> Meridian is built entirely on the [Claude Code SDK](https://docs.anthropic.com/en/docs/claude-code/sdk). Every request flows through `query()` — the same documented function Anthropic provides for programmatic access. No OAuth tokens are extracted, no binaries are patched, nothing is reverse-engineered.
|
|
20
21
|
>
|
|
21
|
-
>
|
|
22
|
+
> Because we use the SDK, Anthropic remains in full control of prompt caching, context window management, compaction, rate limiting, and authentication. Meridian doesn't bypass these mechanisms — it depends on them. Max subscription tokens flow through the correct channel, governed by the same guardrails Anthropic built into Claude Code.
|
|
22
23
|
>
|
|
23
|
-
>
|
|
24
|
-
> - **Real Claude Code sessions.** The SDK spawns the actual Claude Code process, manages its own authentication, and handles all communication with Anthropic's servers. Meridian's traffic doesn't *look like* Claude Code — it *is* Claude Code.
|
|
25
|
-
> - **Documented API surface only.** Session resume, MCP tool servers, agent definitions, thinking configuration, permission modes, tool blocking — every feature Meridian uses is a published, documented SDK option. Nothing is reverse-engineered or patched.
|
|
26
|
-
> - **Native benefits and controls preserved.** Prompt caching, conversation persistence, context window management, and compaction all function exactly as they do in Claude Code — because the SDK manages them directly. This means Anthropic's engineering investments in efficiency and their rate-limiting controls work as designed. Max subscription tokens flow through the correct channel, governed by the same guardrails Anthropic built into Claude Code. Meridian doesn't bypass these mechanisms; it depends on them.
|
|
24
|
+
> What Meridian adds is a **presentation and interoperability layer**. We translate Claude Code's output into the standard Anthropic API format so developers can connect the editors, terminals, and workflows they prefer. The SDK does the work; Meridian formats the result.
|
|
27
25
|
>
|
|
28
|
-
>
|
|
26
|
+
> If you're looking for a tool that circumvents usage limits or bypasses Anthropic's controls, this project is not for you. We play nice with the SDK because we believe that's how developers can continue to choose their own frontends while respecting Anthropic's platform.
|
|
27
|
+
|
|
28
|
+
> [!WARNING]
|
|
29
|
+
> ### Why Meridian does not support OpenClaw
|
|
30
|
+
>
|
|
31
|
+
> There is technically a way to make Meridian work with OpenClaw, but we're not interested in pursuing it.
|
|
29
32
|
>
|
|
30
|
-
>
|
|
33
|
+
> The reason Claude Max offers generous usage limits is because Anthropic can justify it through Claude Code — their harness, their optimizations, their control. OpenClaw blows through that with autonomous workflows that Anthropic has little ability to manage or optimize. Using Opus to check an email when a local model would handle it fine isn't efficient use — it's waste that degrades the plan for everyone.
|
|
31
34
|
>
|
|
32
|
-
>
|
|
35
|
+
> I built Meridian because I believe developers should have the right to use the frontend of their choice. But that right comes with a responsibility: don't wreck the subscription for the rest of us. Sloppy autonomous agents that burn through Claude Max tokens are directly counter-productive to developers like me who depend on the plan being sustainable.
|
|
33
36
|
>
|
|
34
|
-
>
|
|
37
|
+
> Meridian's philosophy is simple — play nice with the SDK, let Anthropic optimize how they see fit, and use the frontend you want within the constraints of Claude Code. OpenClaw is not just a frontend; it's an autonomous system that abuses the Max plan. We won't be supporting it.
|
|
35
38
|
|
|
36
39
|
## Quick Start
|
|
37
40
|
|
|
@@ -290,29 +293,6 @@ MERIDIAN_DEFAULT_AGENT=pi meridian
|
|
|
290
293
|
|
|
291
294
|
Pi mimics Claude Code's User-Agent, so automatic detection isn't possible. The `MERIDIAN_DEFAULT_AGENT` env var tells Meridian to use the pi adapter for all unrecognized requests. If you run other agents alongside pi, use the `x-meridian-agent: pi` header instead (requires pi-ai support for custom headers).
|
|
292
295
|
|
|
293
|
-
### OpenClaw
|
|
294
|
-
|
|
295
|
-
OpenClaw uses `@mariozechner/pi-ai` under the hood, so the pi adapter handles it with no additional code. Add a provider override in `~/.openclaw/openclaw.json`:
|
|
296
|
-
|
|
297
|
-
```json
|
|
298
|
-
{
|
|
299
|
-
"models": {
|
|
300
|
-
"providers": {
|
|
301
|
-
"anthropic": {
|
|
302
|
-
"baseUrl": "http://127.0.0.1:3456",
|
|
303
|
-
"apiKey": "dummy",
|
|
304
|
-
"models": [
|
|
305
|
-
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6 (Meridian)" },
|
|
306
|
-
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6 (Meridian)" }
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
Then start Meridian with the pi adapter: `MERIDIAN_DEFAULT_AGENT=pi meridian`
|
|
315
|
-
|
|
316
296
|
### Any Anthropic-compatible tool
|
|
317
297
|
|
|
318
298
|
```bash
|
|
@@ -331,7 +311,6 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:3456
|
|
|
331
311
|
| [Aider](https://github.com/paul-gauthier/aider) | ✅ Verified | Env vars — file editing, streaming; `--no-stream` broken (litellm bug) |
|
|
332
312
|
| [Open WebUI](https://github.com/open-webui/open-webui) | ✅ Verified | OpenAI-compatible endpoints — set base URL to `http://127.0.0.1:3456` |
|
|
333
313
|
| [Pi](https://github.com/mariozechner/pi-coding-agent) | ✅ Verified | models.json config (see above) — requires `MERIDIAN_DEFAULT_AGENT=pi` |
|
|
334
|
-
| [OpenClaw](https://github.com/openclaw/openclaw) | ✅ Verified | Provider config (see above) — uses pi adapter via `MERIDIAN_DEFAULT_AGENT=pi` |
|
|
335
314
|
| [Continue](https://github.com/continuedev/continue) | 🔲 Untested | OpenAI-compatible endpoints should work — set `apiBase` to `http://127.0.0.1:3456` |
|
|
336
315
|
|
|
337
316
|
Tested an agent or built a plugin? [Open an issue](https://github.com/rynfar/meridian/issues) and we'll add it.
|
|
@@ -529,7 +508,7 @@ You haven't run `meridian setup`. Without the plugin, OpenCode requests won't ha
|
|
|
529
508
|
|
|
530
509
|
## Contributing
|
|
531
510
|
|
|
532
|
-
Issues and PRs welcome. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for module structure and dependency rules, [`CLAUDE.md`](CLAUDE.md) for coding guidelines, and [`E2E.md`](E2E.md) for end-to-end test procedures.
|
|
511
|
+
Issues and PRs welcome. Join the [Discord](https://discord.gg/7vNVFYBz) to discuss ideas before opening issues. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for module structure and dependency rules, [`CLAUDE.md`](CLAUDE.md) for coding guidelines, and [`E2E.md`](E2E.md) for end-to-end test procedures.
|
|
533
512
|
|
|
534
513
|
## License
|
|
535
514
|
|
|
@@ -13833,6 +13833,50 @@ function buildQueryOptions(ctx) {
|
|
|
13833
13833
|
};
|
|
13834
13834
|
}
|
|
13835
13835
|
|
|
13836
|
+
// src/proxy/betas.ts
|
|
13837
|
+
var BILLABLE_BETA_PREFIXES_ON_MAX = [
|
|
13838
|
+
"extended-cache-ttl-"
|
|
13839
|
+
];
|
|
13840
|
+
var DEFAULT_BETA_POLICY = "allow-safe";
|
|
13841
|
+
function getBetaPolicyFromEnv() {
|
|
13842
|
+
const raw2 = process.env.MERIDIAN_BETA_POLICY;
|
|
13843
|
+
if (raw2 === "allow-safe" || raw2 === "strip-all" || raw2 === "allow-all") {
|
|
13844
|
+
return raw2;
|
|
13845
|
+
}
|
|
13846
|
+
return DEFAULT_BETA_POLICY;
|
|
13847
|
+
}
|
|
13848
|
+
function filterBetasForProfile(rawBetaHeader, profileType, policy = DEFAULT_BETA_POLICY) {
|
|
13849
|
+
if (!rawBetaHeader) {
|
|
13850
|
+
return { forwarded: undefined, stripped: [] };
|
|
13851
|
+
}
|
|
13852
|
+
const parsed = rawBetaHeader.split(",").map((b) => b.trim()).filter(Boolean);
|
|
13853
|
+
if (parsed.length === 0) {
|
|
13854
|
+
return { forwarded: undefined, stripped: [] };
|
|
13855
|
+
}
|
|
13856
|
+
if (profileType === "api") {
|
|
13857
|
+
return { forwarded: parsed, stripped: [] };
|
|
13858
|
+
}
|
|
13859
|
+
if (policy === "allow-all") {
|
|
13860
|
+
return { forwarded: parsed, stripped: [] };
|
|
13861
|
+
}
|
|
13862
|
+
if (policy === "strip-all") {
|
|
13863
|
+
return { forwarded: undefined, stripped: parsed };
|
|
13864
|
+
}
|
|
13865
|
+
const forwarded = [];
|
|
13866
|
+
const stripped = [];
|
|
13867
|
+
for (const beta of parsed) {
|
|
13868
|
+
if (BILLABLE_BETA_PREFIXES_ON_MAX.some((prefix) => beta.startsWith(prefix))) {
|
|
13869
|
+
stripped.push(beta);
|
|
13870
|
+
} else {
|
|
13871
|
+
forwarded.push(beta);
|
|
13872
|
+
}
|
|
13873
|
+
}
|
|
13874
|
+
return {
|
|
13875
|
+
forwarded: forwarded.length > 0 ? forwarded : undefined,
|
|
13876
|
+
stripped
|
|
13877
|
+
};
|
|
13878
|
+
}
|
|
13879
|
+
|
|
13836
13880
|
// src/proxy/session/lineage.ts
|
|
13837
13881
|
import { createHash as createHash2 } from "crypto";
|
|
13838
13882
|
var MIN_SUFFIX_FOR_COMPACTION = 2;
|
|
@@ -14613,7 +14657,11 @@ function createProxyServer(config = {}) {
|
|
|
14613
14657
|
const effortHeader = c.req.header("x-opencode-effort");
|
|
14614
14658
|
const thinkingHeader = c.req.header("x-opencode-thinking");
|
|
14615
14659
|
const taskBudgetHeader = c.req.header("x-opencode-task-budget");
|
|
14616
|
-
const
|
|
14660
|
+
const rawBetaHeader = c.req.header("anthropic-beta");
|
|
14661
|
+
const betaFilter = filterBetasForProfile(rawBetaHeader, profile.type, getBetaPolicyFromEnv());
|
|
14662
|
+
if (betaFilter.stripped.length > 0) {
|
|
14663
|
+
console.error(`[PROXY] ${requestMeta.requestId} stripped anthropic-beta(s) for Max profile: ${betaFilter.stripped.join(", ")}`);
|
|
14664
|
+
}
|
|
14617
14665
|
const effort = effortHeader || body.effort || undefined;
|
|
14618
14666
|
let thinking = body.thinking || undefined;
|
|
14619
14667
|
if (thinkingHeader !== undefined) {
|
|
@@ -14625,10 +14673,7 @@ function createProxyServer(config = {}) {
|
|
|
14625
14673
|
}
|
|
14626
14674
|
const parsedBudget = taskBudgetHeader ? Number.parseInt(taskBudgetHeader, 10) : NaN;
|
|
14627
14675
|
const taskBudget = Number.isFinite(parsedBudget) ? { total: parsedBudget } : body.task_budget ? { total: body.task_budget.total ?? body.task_budget } : undefined;
|
|
14628
|
-
const betas =
|
|
14629
|
-
if (!betaHeader && c.req.header("anthropic-beta")) {
|
|
14630
|
-
console.error(`[PROXY] ${requestMeta.requestId} stripped anthropic-beta header (Max subscription — betas trigger extra usage billing)`);
|
|
14631
|
-
}
|
|
14676
|
+
const betas = betaFilter.forwarded;
|
|
14632
14677
|
const agentSessionId = adapter.getSessionId(c);
|
|
14633
14678
|
const profileSessionId = profile.id !== "default" && agentSessionId ? `${profile.id}:${agentSessionId}` : agentSessionId;
|
|
14634
14679
|
const profileScopedCwd = profile.id !== "default" ? `${workingDirectory}::profile=${profile.id}` : workingDirectory;
|
package/dist/cli.js
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anthropic-beta header filtering for Max vs API profiles.
|
|
3
|
+
*
|
|
4
|
+
* Some betas (e.g. `extended-cache-ttl-*`) trigger Extra-Usage billing on
|
|
5
|
+
* Claude Max subscriptions. The default `allow-safe` policy strips only those
|
|
6
|
+
* for claude-max profiles while forwarding everything else so that prompt
|
|
7
|
+
* caching, 1M context, fine-grained tool streaming, and interleaved thinking
|
|
8
|
+
* continue to work as the SDK expects.
|
|
9
|
+
*
|
|
10
|
+
* Unconditional stripping (the previous behaviour) caused cache misses on
|
|
11
|
+
* every turn, which tripled TTFB and inflated token consumption roughly 3x on
|
|
12
|
+
* long conversations. See issue #278 for the original context.
|
|
13
|
+
*
|
|
14
|
+
* An operator can override the policy at runtime via the `MERIDIAN_BETA_POLICY`
|
|
15
|
+
* env var to force `strip-all` (safest — old behaviour) or `allow-all`
|
|
16
|
+
* (most permissive — matches api-profile behaviour) without a rebuild.
|
|
17
|
+
*
|
|
18
|
+
* This module is pure — no I/O, no imports from server.ts or session/.
|
|
19
|
+
*/
|
|
20
|
+
import type { ProfileType } from "./profiles";
|
|
21
|
+
/**
|
|
22
|
+
* Beta prefixes that are known to trigger Extra-Usage billing on Max accounts.
|
|
23
|
+
*
|
|
24
|
+
* A beta is considered billable if its name starts with any of these strings.
|
|
25
|
+
* Keep this list conservative — prefer allowing unknown betas through over
|
|
26
|
+
* silently stripping something the SDK needs for normal operation.
|
|
27
|
+
*/
|
|
28
|
+
export declare const BILLABLE_BETA_PREFIXES_ON_MAX: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Runtime policy for `anthropic-beta` header handling on claude-max profiles.
|
|
31
|
+
*
|
|
32
|
+
* - `allow-safe` (default): forward all betas except those matching
|
|
33
|
+
* {@link BILLABLE_BETA_PREFIXES_ON_MAX}. Restores prompt caching + 1M
|
|
34
|
+
* context while keeping the original billing-safety intent.
|
|
35
|
+
* - `strip-all`: the pre-fix (1.28.0 – 1.29.x) behaviour. Drops every beta
|
|
36
|
+
* for claude-max profiles. Use this as a kill switch if the allow-safe
|
|
37
|
+
* policy ever causes quota surprises.
|
|
38
|
+
* - `allow-all`: forward every beta unconditionally, same as api profiles.
|
|
39
|
+
* Use only if you've verified your Max tier treats all betas as free.
|
|
40
|
+
*/
|
|
41
|
+
export type BetaPolicy = "allow-safe" | "strip-all" | "allow-all";
|
|
42
|
+
export declare const DEFAULT_BETA_POLICY: BetaPolicy;
|
|
43
|
+
export interface BetaFilterResult {
|
|
44
|
+
/** Betas to forward upstream. `undefined` means no header should be sent. */
|
|
45
|
+
forwarded: string[] | undefined;
|
|
46
|
+
/** Betas that were removed. Empty for api-type profiles. */
|
|
47
|
+
stripped: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read the beta policy from the `MERIDIAN_BETA_POLICY` env var.
|
|
51
|
+
*
|
|
52
|
+
* Falls back to {@link DEFAULT_BETA_POLICY} for missing or invalid values.
|
|
53
|
+
* Invalid values are silently ignored rather than crashing the proxy.
|
|
54
|
+
*/
|
|
55
|
+
export declare function getBetaPolicyFromEnv(): BetaPolicy;
|
|
56
|
+
/**
|
|
57
|
+
* Filter an `anthropic-beta` header value for the given profile type.
|
|
58
|
+
*
|
|
59
|
+
* - For `api` profiles, all betas pass through unchanged regardless of policy.
|
|
60
|
+
* - For `claude-max` profiles, behaviour depends on `policy`:
|
|
61
|
+
* - `allow-safe` (default): strip only billable betas
|
|
62
|
+
* (see {@link BILLABLE_BETA_PREFIXES_ON_MAX}).
|
|
63
|
+
* - `strip-all`: strip every beta.
|
|
64
|
+
* - `allow-all`: forward every beta unchanged.
|
|
65
|
+
* - Whitespace and empty entries are trimmed.
|
|
66
|
+
* - Returns `forwarded: undefined` when the result would be an empty list so
|
|
67
|
+
* callers can omit the header entirely.
|
|
68
|
+
*/
|
|
69
|
+
export declare function filterBetasForProfile(rawBetaHeader: string | undefined, profileType: ProfileType, policy?: BetaPolicy): BetaFilterResult;
|
|
70
|
+
//# sourceMappingURL=betas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"betas.d.ts","sourceRoot":"","sources":["../../src/proxy/betas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAE1D,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,CAAA;AAEjE,eAAO,MAAM,mBAAmB,EAAE,UAAyB,CAAA;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAC/B,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,CAMjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,UAAgC,GACvC,gBAAgB,CA0ClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAsBvD,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;AAGzI,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,CA8pDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
|
package/dist/server.js
CHANGED
package/package.json
CHANGED