@rynfar/meridian 1.26.6 → 1.27.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/README.md +54 -5
- package/dist/{cli-jk1p4nw8.js → cli-6hehvt9f.js} +83 -1
- package/dist/cli.js +1 -1
- package/dist/proxy/adapters/detect.d.ts +5 -4
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/adapters/pi.d.ts +28 -0
- package/dist/proxy/adapters/pi.d.ts.map +1 -0
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
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, 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, 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
16
|
> [!NOTE]
|
|
17
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.
|
|
@@ -192,6 +192,49 @@ Point any OpenAI-compatible tool at `http://127.0.0.1:3456` with any API key val
|
|
|
192
192
|
|
|
193
193
|
> **Note:** Multi-turn conversations work by packing prior turns into the system prompt. Each request is a fresh SDK session — OpenAI clients replay full history themselves and don't use Meridian's session resumption.
|
|
194
194
|
|
|
195
|
+
### Pi
|
|
196
|
+
|
|
197
|
+
Pi uses the `@mariozechner/pi-ai` library which supports a configurable `baseUrl` on the model. Add a provider-level override in `~/.pi/agent/models.json`:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"anthropic": {
|
|
202
|
+
"baseUrl": "http://127.0.0.1:3456"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Then start Meridian with the pi default adapter:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
MERIDIAN_DEFAULT_AGENT=pi meridian
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
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).
|
|
214
|
+
|
|
215
|
+
### OpenClaw
|
|
216
|
+
|
|
217
|
+
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`:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"models": {
|
|
222
|
+
"providers": {
|
|
223
|
+
"anthropic": {
|
|
224
|
+
"baseUrl": "http://127.0.0.1:3456",
|
|
225
|
+
"apiKey": "dummy",
|
|
226
|
+
"models": [
|
|
227
|
+
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6 (Meridian)" },
|
|
228
|
+
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6 (Meridian)" }
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Then start Meridian with the pi adapter: `MERIDIAN_DEFAULT_AGENT=pi meridian`
|
|
237
|
+
|
|
195
238
|
### Any Anthropic-compatible tool
|
|
196
239
|
|
|
197
240
|
```bash
|
|
@@ -209,6 +252,8 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:3456
|
|
|
209
252
|
| [Cline](https://github.com/cline/cline) | ✅ Verified | Config (see above) — full tool support, file read/write/edit, bash, session resume |
|
|
210
253
|
| [Aider](https://github.com/paul-gauthier/aider) | ✅ Verified | Env vars — file editing, streaming; `--no-stream` broken (litellm bug) |
|
|
211
254
|
| [Open WebUI](https://github.com/open-webui/open-webui) | ✅ Verified | OpenAI-compatible endpoints — set base URL to `http://127.0.0.1:3456` |
|
|
255
|
+
| [Pi](https://github.com/mariozechner/pi-coding-agent) | ✅ Verified | models.json config (see above) — requires `MERIDIAN_DEFAULT_AGENT=pi` |
|
|
256
|
+
| [OpenClaw](https://github.com/openclaw/openclaw) | ✅ Verified | Provider config (see above) — uses pi adapter via `MERIDIAN_DEFAULT_AGENT=pi` |
|
|
212
257
|
| [Continue](https://github.com/continuedev/continue) | 🔲 Untested | OpenAI-compatible endpoints should work — set `apiBase` to `http://127.0.0.1:3456` |
|
|
213
258
|
|
|
214
259
|
Tested an agent or built a plugin? [Open an issue](https://github.com/rynfar/meridian/issues) and we'll add it.
|
|
@@ -224,6 +269,7 @@ src/proxy/
|
|
|
224
269
|
│ ├── opencode.ts ← OpenCode adapter
|
|
225
270
|
│ ├── crush.ts ← Crush adapter
|
|
226
271
|
│ ├── droid.ts ← Droid adapter
|
|
272
|
+
│ ├── pi.ts ← Pi adapter
|
|
227
273
|
│ └── passthrough.ts ← LiteLLM passthrough adapter
|
|
228
274
|
├── query.ts ← SDK query options builder
|
|
229
275
|
├── errors.ts ← Error classification
|
|
@@ -258,11 +304,13 @@ Sessions are stored in-memory (LRU) and persisted to `~/.cache/meridian/sessions
|
|
|
258
304
|
|
|
259
305
|
Agents are identified from request headers automatically:
|
|
260
306
|
|
|
261
|
-
|
|
|
307
|
+
| Signal | Adapter |
|
|
262
308
|
|---|---|
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
|
|
|
309
|
+
| `x-meridian-agent` header | Explicit override (any adapter) |
|
|
310
|
+
| `Charm-Crush/` User-Agent | Crush |
|
|
311
|
+
| `factory-cli/` User-Agent | Droid |
|
|
312
|
+
| `litellm/` UA or `x-litellm-*` headers | LiteLLM passthrough |
|
|
313
|
+
| *(anything else)* | `MERIDIAN_DEFAULT_AGENT` env var, or OpenCode |
|
|
266
314
|
|
|
267
315
|
### Adding a New Agent
|
|
268
316
|
|
|
@@ -283,6 +331,7 @@ Implement the `AgentAdapter` interface in `src/proxy/adapters/`. See [`adapters/
|
|
|
283
331
|
| `MERIDIAN_TELEMETRY_SIZE` | `CLAUDE_PROXY_TELEMETRY_SIZE` | `1000` | Telemetry ring buffer size |
|
|
284
332
|
| `MERIDIAN_NO_FILE_CHANGES` | `CLAUDE_PROXY_NO_FILE_CHANGES` | unset | Disable "Files changed" summary in responses |
|
|
285
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_DEFAULT_AGENT` | — | `opencode` | Default adapter for unrecognized agents: `opencode`, `pi`, `crush`, `droid`, `passthrough`. Requires restart. |
|
|
286
335
|
|
|
287
336
|
*`sonnet[1m]` requires Max subscription with Extra Usage enabled. Falls back to `sonnet` automatically if not available.
|
|
288
337
|
|
|
@@ -7875,7 +7875,85 @@ var passthroughAdapter = {
|
|
|
7875
7875
|
}
|
|
7876
7876
|
};
|
|
7877
7877
|
|
|
7878
|
+
// src/proxy/adapters/pi.ts
|
|
7879
|
+
var PI_MCP_SERVER_NAME = "pi";
|
|
7880
|
+
var PI_ALLOWED_MCP_TOOLS = [
|
|
7881
|
+
`mcp__${PI_MCP_SERVER_NAME}__read`,
|
|
7882
|
+
`mcp__${PI_MCP_SERVER_NAME}__write`,
|
|
7883
|
+
`mcp__${PI_MCP_SERVER_NAME}__edit`,
|
|
7884
|
+
`mcp__${PI_MCP_SERVER_NAME}__bash`,
|
|
7885
|
+
`mcp__${PI_MCP_SERVER_NAME}__grep`
|
|
7886
|
+
];
|
|
7887
|
+
function extractPiCwd(body) {
|
|
7888
|
+
let systemText = "";
|
|
7889
|
+
if (typeof body.system === "string") {
|
|
7890
|
+
systemText = body.system;
|
|
7891
|
+
} else if (Array.isArray(body.system)) {
|
|
7892
|
+
systemText = body.system.filter((b) => b.type === "text" && b.text).map((b) => b.text).join(`
|
|
7893
|
+
`);
|
|
7894
|
+
}
|
|
7895
|
+
if (!systemText)
|
|
7896
|
+
return;
|
|
7897
|
+
const match2 = systemText.match(/Current working directory:\s*([^\n]+)/i);
|
|
7898
|
+
return match2?.[1]?.trim() || undefined;
|
|
7899
|
+
}
|
|
7900
|
+
var piAdapter = {
|
|
7901
|
+
name: "pi",
|
|
7902
|
+
getSessionId(_c) {
|
|
7903
|
+
return;
|
|
7904
|
+
},
|
|
7905
|
+
extractWorkingDirectory(body) {
|
|
7906
|
+
return extractPiCwd(body);
|
|
7907
|
+
},
|
|
7908
|
+
normalizeContent(content) {
|
|
7909
|
+
return normalizeContent(content);
|
|
7910
|
+
},
|
|
7911
|
+
getBlockedBuiltinTools() {
|
|
7912
|
+
return BLOCKED_BUILTIN_TOOLS;
|
|
7913
|
+
},
|
|
7914
|
+
getAgentIncompatibleTools() {
|
|
7915
|
+
return CLAUDE_CODE_ONLY_TOOLS;
|
|
7916
|
+
},
|
|
7917
|
+
getMcpServerName() {
|
|
7918
|
+
return PI_MCP_SERVER_NAME;
|
|
7919
|
+
},
|
|
7920
|
+
getAllowedMcpTools() {
|
|
7921
|
+
return PI_ALLOWED_MCP_TOOLS;
|
|
7922
|
+
},
|
|
7923
|
+
buildSdkAgents(_body, _mcpToolNames) {
|
|
7924
|
+
return {};
|
|
7925
|
+
},
|
|
7926
|
+
buildSdkHooks(_body, _sdkAgents) {
|
|
7927
|
+
return;
|
|
7928
|
+
},
|
|
7929
|
+
buildSystemContextAddendum(_body, _sdkAgents) {
|
|
7930
|
+
return "";
|
|
7931
|
+
},
|
|
7932
|
+
extractFileChangesFromToolUse(toolName, toolInput) {
|
|
7933
|
+
const input = toolInput;
|
|
7934
|
+
const filePath = input?.filePath ?? input?.file_path ?? input?.path;
|
|
7935
|
+
if (toolName === "write" && filePath) {
|
|
7936
|
+
return [{ operation: "wrote", path: String(filePath) }];
|
|
7937
|
+
}
|
|
7938
|
+
if (toolName === "edit" && filePath) {
|
|
7939
|
+
return [{ operation: "edited", path: String(filePath) }];
|
|
7940
|
+
}
|
|
7941
|
+
if (toolName === "bash" && input?.command) {
|
|
7942
|
+
return extractFileChangesFromBash(String(input.command));
|
|
7943
|
+
}
|
|
7944
|
+
return [];
|
|
7945
|
+
}
|
|
7946
|
+
};
|
|
7947
|
+
|
|
7878
7948
|
// src/proxy/adapters/detect.ts
|
|
7949
|
+
var ADAPTER_MAP = {
|
|
7950
|
+
opencode: openCodeAdapter,
|
|
7951
|
+
droid: droidAdapter,
|
|
7952
|
+
crush: crushAdapter,
|
|
7953
|
+
passthrough: passthroughAdapter,
|
|
7954
|
+
pi: piAdapter
|
|
7955
|
+
};
|
|
7956
|
+
var defaultAdapter = ADAPTER_MAP[process.env.MERIDIAN_DEFAULT_AGENT || ""] ?? openCodeAdapter;
|
|
7879
7957
|
function isLiteLLMRequest(c) {
|
|
7880
7958
|
if ((c.req.header("user-agent") || "").startsWith("litellm/"))
|
|
7881
7959
|
return true;
|
|
@@ -7883,6 +7961,10 @@ function isLiteLLMRequest(c) {
|
|
|
7883
7961
|
return Object.keys(headers).some((k) => k.toLowerCase().startsWith("x-litellm-"));
|
|
7884
7962
|
}
|
|
7885
7963
|
function detectAdapter(c) {
|
|
7964
|
+
const agentOverride = c.req.header("x-meridian-agent");
|
|
7965
|
+
if (agentOverride && ADAPTER_MAP[agentOverride]) {
|
|
7966
|
+
return ADAPTER_MAP[agentOverride];
|
|
7967
|
+
}
|
|
7886
7968
|
const userAgent = c.req.header("user-agent") || "";
|
|
7887
7969
|
if (userAgent.startsWith("factory-cli/")) {
|
|
7888
7970
|
return droidAdapter;
|
|
@@ -7893,7 +7975,7 @@ function detectAdapter(c) {
|
|
|
7893
7975
|
if (isLiteLLMRequest(c)) {
|
|
7894
7976
|
return passthroughAdapter;
|
|
7895
7977
|
}
|
|
7896
|
-
return
|
|
7978
|
+
return defaultAdapter;
|
|
7897
7979
|
}
|
|
7898
7980
|
|
|
7899
7981
|
// src/mcpTools.ts
|
package/dist/cli.js
CHANGED
|
@@ -10,10 +10,11 @@ import type { AgentAdapter } from "../adapter";
|
|
|
10
10
|
* Detect which agent adapter to use based on request headers.
|
|
11
11
|
*
|
|
12
12
|
* Detection rules (evaluated in order):
|
|
13
|
-
* 1.
|
|
14
|
-
* 2. User-Agent starts with "
|
|
15
|
-
* 3.
|
|
16
|
-
* 4.
|
|
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
|
|
17
18
|
*/
|
|
18
19
|
export declare function detectAdapter(c: Context): AgentAdapter;
|
|
19
20
|
//# 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;
|
|
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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi coding agent adapter.
|
|
3
|
+
*
|
|
4
|
+
* Provides pi-specific behavior for session tracking, working directory
|
|
5
|
+
* extraction, content normalization, and tool configuration.
|
|
6
|
+
*
|
|
7
|
+
* Pi (@mariozechner/pi-coding-agent) is a CLI coding agent that makes
|
|
8
|
+
* standard Anthropic Messages API calls. When using a custom baseUrl
|
|
9
|
+
* (pointing at Meridian), pi operates in non-OAuth mode with lowercase
|
|
10
|
+
* tool names and its own tool execution loop.
|
|
11
|
+
*
|
|
12
|
+
* Key characteristics:
|
|
13
|
+
* - User-Agent: claude-cli/<version> (mimics Claude Code) or default SDK UA
|
|
14
|
+
* - No session header: relies on fingerprint-based session cache
|
|
15
|
+
* - CWD in system prompt: "Current working directory: /path/to/project"
|
|
16
|
+
* - 7 lowercase tools: read, write, edit, bash, grep, find, ls
|
|
17
|
+
* - Always streams (stream: true)
|
|
18
|
+
* - Manages its own tool execution loop: passthrough mode is appropriate
|
|
19
|
+
* - No subagent routing: pi-coding-agent is single-agent (pylon adds orchestration on top)
|
|
20
|
+
*
|
|
21
|
+
* Detection: pi mimics Claude Code's User-Agent, so automatic detection is
|
|
22
|
+
* unreliable. Use one of:
|
|
23
|
+
* - x-meridian-agent: pi header (per-request)
|
|
24
|
+
* - MERIDIAN_DEFAULT_AGENT=pi env var (global default)
|
|
25
|
+
*/
|
|
26
|
+
import type { AgentAdapter } from "../adapter";
|
|
27
|
+
export declare const piAdapter: AgentAdapter;
|
|
28
|
+
//# sourceMappingURL=pi.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/server.js
CHANGED
package/package.json
CHANGED