@rynfar/meridian 1.44.0 → 1.44.1

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 CHANGED
@@ -197,7 +197,7 @@ The system prompt controls are independent — any combination works:
197
197
 
198
198
  The core question is **who executes the tools** — the SDK or the client?
199
199
 
200
- - **Passthrough mode** (default for OpenCode) — Claude generates tool calls, but Meridian captures them and sends them back to the client for execution. The client runs the tool using its own implementation, with its own sandboxing, file tracking, and UI, then sends the result in the next request. This is how OpenCode, oh-my-opencagent (OMO), and most coding agents work — they have their own read/write/bash tools and need to stay in control of what runs on the user's machine.
200
+ - **Passthrough mode** (default for OpenCode and Pi) — Claude generates tool calls, but Meridian captures them and sends them back to the client for execution. The client runs the tool using its own implementation, with its own sandboxing, file tracking, and UI, then sends the result in the next request. This is how OpenCode, oh-my-opencagent (OMO), and most coding agents work — they have their own read/write/bash tools and need to stay in control of what runs on the user's machine.
201
201
  - **Internal mode** — Claude Code handles everything. The SDK executes tools directly on the host, runs its full agent loop, and returns the final result. This is for clients that are purely chat interfaces (Open WebUI, simple API consumers) with no tool execution of their own.
202
202
 
203
203
  Most users don't need to configure anything — the adapter sets the right mode automatically. To override:
@@ -526,6 +526,8 @@ Pi uses the `@mariozechner/pi-ai` library which supports a configurable `baseUrl
526
526
 
527
527
  Pi mimics Claude Code's User-Agent, so automatic detection isn't possible. The `x-meridian-agent: pi` header in the config above tells Meridian to use the Pi adapter. Alternatively, if Pi is your only agent, you can set `MERIDIAN_DEFAULT_AGENT=pi` as an env var instead.
528
528
 
529
+ Pi runs in passthrough mode by default — it executes its own tools and Meridian just forwards the `tool_use` blocks. Opt out with `MERIDIAN_PASSTHROUGH=0`.
530
+
529
531
  ### Claude Code
530
532
 
531
533
  Claude Code can point at Meridian like any other Anthropic API client. The
@@ -573,7 +575,7 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:3456
573
575
  | [Cline](https://github.com/cline/cline) | ✅ Verified | Config (see above) — full tool support, file read/write/edit, bash, session resume |
574
576
  | [Aider](https://github.com/paul-gauthier/aider) | ✅ Verified | Env vars — file editing, streaming; `--no-stream` broken (litellm bug) |
575
577
  | [Open WebUI](https://github.com/open-webui/open-webui) | ✅ Verified | OpenAI-compatible endpoints — set base URL to `http://127.0.0.1:3456` |
576
- | [Pi](https://github.com/mariozechner/pi-coding-agent) | ✅ Verified | models.json config (see above) — requires `MERIDIAN_DEFAULT_AGENT=pi` |
578
+ | [Pi](https://github.com/mariozechner/pi-coding-agent) | ✅ Verified | models.json config (see above) — full tool support via passthrough; detected via `x-meridian-agent: pi` header |
577
579
  | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | ✅ Verified | `ANTHROPIC_BASE_URL` — remote clients share a Max subscription over the network; client CWD preserved in system prompt |
578
580
  | [Continue](https://github.com/continuedev/continue) | 🔲 Untested | OpenAI-compatible endpoints should work — set `apiBase` to `http://127.0.0.1:3456` |
579
581
 
@@ -3753,6 +3753,57 @@ class RateLimitStore {
3753
3753
  }
3754
3754
  var rateLimitStore = new RateLimitStore;
3755
3755
 
3756
+ // src/proxy/streamIdleGuard.ts
3757
+ class UpstreamIdleError extends Error {
3758
+ idleMs;
3759
+ sinceLastMs;
3760
+ constructor(idleMs, sinceLastMs) {
3761
+ super(`upstream idle for ${sinceLastMs}ms (limit ${idleMs}ms)`);
3762
+ this.name = "UpstreamIdleError";
3763
+ this.idleMs = idleMs;
3764
+ this.sinceLastMs = sinceLastMs;
3765
+ }
3766
+ }
3767
+ async function* guardUpstreamIdle(source, idleMs, onStall) {
3768
+ if (idleMs <= 0) {
3769
+ yield* source;
3770
+ return;
3771
+ }
3772
+ const it = source[Symbol.asyncIterator]();
3773
+ let lastAt = Date.now();
3774
+ try {
3775
+ while (true) {
3776
+ const nextP = it.next();
3777
+ nextP.catch(() => {});
3778
+ let timer;
3779
+ const idle = new Promise((_, reject) => {
3780
+ const remaining = Math.max(0, idleMs - (Date.now() - lastAt));
3781
+ timer = setTimeout(() => {
3782
+ const sinceLastMs = Date.now() - lastAt;
3783
+ try {
3784
+ onStall?.(sinceLastMs);
3785
+ } catch {}
3786
+ reject(new UpstreamIdleError(idleMs, sinceLastMs));
3787
+ }, remaining);
3788
+ });
3789
+ let res;
3790
+ try {
3791
+ res = await Promise.race([nextP, idle]);
3792
+ } finally {
3793
+ if (timer)
3794
+ clearTimeout(timer);
3795
+ }
3796
+ if (res.done)
3797
+ return;
3798
+ lastAt = Date.now();
3799
+ yield res.value;
3800
+ }
3801
+ } finally {
3802
+ const returnP = it.return?.(undefined);
3803
+ returnP?.catch(() => {});
3804
+ }
3805
+ }
3806
+
3756
3807
  // src/proxy/oauthUsage.ts
3757
3808
  var OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
3758
3809
  var OAUTH_BETA_HEADER = "oauth-2025-04-20";
@@ -3906,6 +3957,14 @@ function envBool(suffix) {
3906
3957
  const val = env(suffix);
3907
3958
  return val === "1" || val === "true" || val === "yes";
3908
3959
  }
3960
+ function resolvePassthrough(defaultValue) {
3961
+ const val = env("PASSTHROUGH");
3962
+ if (val === "1" || val === "true" || val === "yes")
3963
+ return true;
3964
+ if (val === "0" || val === "false" || val === "no")
3965
+ return false;
3966
+ return defaultValue;
3967
+ }
3909
3968
  function envInt(suffix, defaultValue) {
3910
3969
  const val = env(suffix);
3911
3970
  if (!val)
@@ -9981,8 +10040,7 @@ var openCodeTransforms = [
9981
10040
  const incompatibleTools = CLAUDE_CODE_ONLY_TOOLS;
9982
10041
  const allowedMcpTools = ALLOWED_MCP_TOOLS;
9983
10042
  const coreToolNames = ["read", "write", "edit", "bash", "glob", "grep"];
9984
- const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
9985
- const passthrough = !(envVal === "0" || envVal === "false" || envVal === "no");
10043
+ const passthrough = resolvePassthrough(true);
9986
10044
  let sdkAgents = {};
9987
10045
  if (Array.isArray(body.tools)) {
9988
10046
  const taskTool = body.tools.find((t) => t.name === "task" || t.name === "Task");
@@ -10077,11 +10135,7 @@ var openCodeAdapter = {
10077
10135
  return ["read", "write", "edit", "bash", "glob", "grep"];
10078
10136
  },
10079
10137
  usesPassthrough() {
10080
- const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10081
- if (envVal === "0" || envVal === "false" || envVal === "no") {
10082
- return false;
10083
- }
10084
- return true;
10138
+ return resolvePassthrough(true);
10085
10139
  },
10086
10140
  supportsThinking() {
10087
10141
  return true;
@@ -10152,8 +10206,7 @@ var DROID_ALLOWED_MCP_TOOLS = [
10152
10206
  `mcp__${DROID_MCP_SERVER_NAME}__grep`
10153
10207
  ];
10154
10208
  function resolveDroidPassthrough() {
10155
- const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10156
- return envVal === "1" || envVal === "true" || envVal === "yes";
10209
+ return resolvePassthrough(false);
10157
10210
  }
10158
10211
  var droidTransforms = [
10159
10212
  {
@@ -10237,8 +10290,7 @@ var droidAdapter = {
10237
10290
  return "";
10238
10291
  },
10239
10292
  usesPassthrough() {
10240
- const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10241
- return envVal === "1" || envVal === "true" || envVal === "yes";
10293
+ return resolvePassthrough(false);
10242
10294
  }
10243
10295
  };
10244
10296
 
@@ -10459,6 +10511,9 @@ var PI_ALLOWED_MCP_TOOLS = [
10459
10511
  `mcp__${PI_MCP_SERVER_NAME}__glob`,
10460
10512
  `mcp__${PI_MCP_SERVER_NAME}__grep`
10461
10513
  ];
10514
+ function resolvePiPassthrough() {
10515
+ return resolvePassthrough(true);
10516
+ }
10462
10517
  var piTransforms = [
10463
10518
  {
10464
10519
  name: "pi-core",
@@ -10481,6 +10536,7 @@ var piTransforms = [
10481
10536
  incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
10482
10537
  allowedMcpTools: PI_ALLOWED_MCP_TOOLS,
10483
10538
  sdkAgents: {},
10539
+ passthrough: resolvePiPassthrough(),
10484
10540
  supportsThinking: true,
10485
10541
  extractFileChangesFromToolUse
10486
10542
  };
@@ -10549,6 +10605,9 @@ var piAdapter = {
10549
10605
  buildSystemContextAddendum(_body, _sdkAgents) {
10550
10606
  return "";
10551
10607
  },
10608
+ usesPassthrough() {
10609
+ return resolvePassthrough(true);
10610
+ },
10552
10611
  extractFileChangesFromToolUse(toolName, toolInput) {
10553
10612
  const input = toolInput;
10554
10613
  const filePath = input?.filePath ?? input?.file_path ?? input?.path;
@@ -10718,11 +10777,7 @@ var claudeCodeAdapter = {
10718
10777
  return ["Read", "Write", "Edit", "Bash", "Glob", "Grep"];
10719
10778
  },
10720
10779
  usesPassthrough() {
10721
- const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10722
- if (envVal === "0" || envVal === "false" || envVal === "no") {
10723
- return false;
10724
- }
10725
- return true;
10780
+ return resolvePassthrough(true);
10726
10781
  },
10727
10782
  supportsThinking() {
10728
10783
  return true;
@@ -17525,6 +17580,7 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
17525
17580
  // src/proxy/server.ts
17526
17581
  var exec2 = promisify2(execCallback);
17527
17582
  var claudeExecutable = "";
17583
+ var UPSTREAM_IDLE_MS = 90000;
17528
17584
  function credentialStoreForProfile(profile) {
17529
17585
  if (profile.type !== "claude-max")
17530
17586
  return;
@@ -18799,8 +18855,15 @@ Subprocess stderr: ${stderrOutput}`;
18799
18855
  const taskToolJsonBuffer = new Map;
18800
18856
  let nextClientBlockIndex = 0;
18801
18857
  const sdkToClientIndex = new Map;
18858
+ const guardedResponse = guardUpstreamIdle(response, UPSTREAM_IDLE_MS, (sinceLastMs) => claudeLog("upstream.stalled", {
18859
+ mode: "stream",
18860
+ model,
18861
+ sinceLastMs,
18862
+ streamEventsSeen,
18863
+ firstChunkAt: firstChunkAt ?? null
18864
+ }));
18802
18865
  try {
18803
- for await (const message of response) {
18866
+ for await (const message of guardedResponse) {
18804
18867
  if (streamClosed) {
18805
18868
  break;
18806
18869
  }
@@ -19157,7 +19220,11 @@ Subprocess stderr: ${stderrOutput}`;
19157
19220
  error: errMsg,
19158
19221
  ...stderrOutput ? { stderr: stderrOutput } : {}
19159
19222
  });
19160
- const streamErr = classifyError(errMsg);
19223
+ const streamErr = error instanceof UpstreamIdleError ? {
19224
+ status: 504,
19225
+ type: "upstream_timeout",
19226
+ message: `Upstream stalled: no data for ${error.sinceLastMs}ms`
19227
+ } : classifyError(errMsg);
19161
19228
  claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
19162
19229
  const sdkTerm = extractSdkTermination(errMsg);
19163
19230
  const canRecoverAsToolUse = sdkTerm.reason === "max_turns" && passthrough && capturedToolUses.length > 0 && messageStartEmitted;
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-fc6mt326.js";
4
+ } from "./cli-6rezv582.js";
5
5
  import"./cli-cx463q74.js";
6
6
  import"./cli-sry5aqdj.js";
7
7
  import"./cli-4rqtm83g.js";
package/dist/env.d.ts CHANGED
@@ -17,6 +17,22 @@ export declare function envOr(suffix: string, defaultValue: string): string;
17
17
  * Resolve a boolean env var (truthy = "1", "true", "yes").
18
18
  */
19
19
  export declare function envBool(suffix: string): boolean;
20
+ /**
21
+ * Resolve passthrough mode from the env var (`MERIDIAN_PASSTHROUGH`, falling
22
+ * back to `CLAUDE_PROXY_PASSTHROUGH`), applying an adapter-specific default
23
+ * when the var is unset or holds an unrecognized value.
24
+ *
25
+ * Recognized: "1"/"true"/"yes" → true, "0"/"false"/"no" → false.
26
+ *
27
+ * This is the single source of truth for passthrough parsing. Each adapter's
28
+ * `usesPassthrough()` and its transform both call it with the same default, so
29
+ * they can never drift (the transform-parity tests enforce the pairing).
30
+ *
31
+ * @param defaultValue used when neither env var is set (or is unrecognized).
32
+ * Passthrough-by-default adapters pass `true` (opt out with `=0`); opt-in
33
+ * adapters pass `false` (opt in with `=1`).
34
+ */
35
+ export declare function resolvePassthrough(defaultValue: boolean): boolean;
20
36
  /**
21
37
  * Resolve an integer env var with a default.
22
38
  */
package/dist/env.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtD;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG/C;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAKnE"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtD;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG/C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAKjE;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAKnE"}
@@ -1 +1 @@
1
- {"version":3,"file":"claudecode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/claudecode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAgC9C,eAAO,MAAM,iBAAiB,EAAE,YA8F/B,CAAA"}
1
+ {"version":3,"file":"claudecode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/claudecode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAiC9C,eAAO,MAAM,iBAAiB,EAAE,YA0F/B,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"droid.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/droid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAwC9C,eAAO,MAAM,YAAY,EAAE,YAmF1B,CAAA;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"droid.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/droid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAyC9C,eAAO,MAAM,YAAY,EAAE,YAkF1B,CAAA;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/opencode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAQ9C,eAAO,MAAM,eAAe,EAAE,YA8H7B,CAAA;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
1
+ {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/opencode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAS9C,eAAO,MAAM,eAAe,EAAE,YA0H7B,CAAA;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
@@ -1 +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;AAwC9C,eAAO,MAAM,SAAS,EAAE,YAmHvB,CAAA;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,CAAA"}
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;AAyC9C,eAAO,MAAM,SAAS,EAAE,YA2HvB,CAAA;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAaA,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;AAiCnG,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;AA2P7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA+pFhF;AAWD,wBAAgB,gCAAgC,IAAI,IAAI,CAavD;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmGhG"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAcA,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;AAiCnG,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;AAiQ7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA8qFhF;AAWD,wBAAgB,gCAAgC,IAAI,IAAI,CAavD;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmGhG"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Upstream-idle guard for proxied model streams.
3
+ *
4
+ * Wraps the provider SDK's streaming async iterable and enforces a maximum gap
5
+ * between *real* upstream messages. If the source goes silent for longer than
6
+ * `idleMs` — before the first chunk (slow TTFB) or mid-stream — the guard
7
+ * aborts iteration and throws `UpstreamIdleError`.
8
+ *
9
+ * Why this is needed: the proxy emits downstream SSE heartbeats (`: ping`) on a
10
+ * fixed interval, which resets the *client's* (pi's) byte-level idle timer. A
11
+ * stalled upstream is therefore invisible to the client and would wedge the
12
+ * turn forever. This guard is the authoritative upstream-liveness check.
13
+ *
14
+ * COORDINATION CONTRACT (Pylon Orchestrator): this guard owns *model-stream*
15
+ * liveness. Pylon's runtime stall watchdog is only a BACKSTOP for the
16
+ * model-wait gap with no tool in flight, and keeps its abort threshold above
17
+ * this guard's idle limit (default MERIDIAN_IDLE_TIMEOUT_SECONDS = 120s) so the
18
+ * two layers never race to abort the same hung model. If this default rises,
19
+ * re-check Pylon's STALL_ABORT_MS. See
20
+ * pylon-orchestrator/docs/circuit/specs/stall-watchdog-tool-exempt.md.
21
+ */
22
+ export declare class UpstreamIdleError extends Error {
23
+ readonly idleMs: number;
24
+ readonly sinceLastMs: number;
25
+ constructor(idleMs: number, sinceLastMs: number);
26
+ }
27
+ export declare function guardUpstreamIdle<T>(source: AsyncIterable<T>, idleMs: number, onStall?: (sinceLastMs: number) => void): AsyncGenerator<T>;
28
+ //# sourceMappingURL=streamIdleGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamIdleGuard.d.ts","sourceRoot":"","sources":["../../src/proxy/streamIdleGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;gBAChB,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAMhD;AAED,wBAAuB,iBAAiB,CAAC,CAAC,EACxC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,EACxB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,GACtC,cAAc,CAAC,CAAC,CAAC,CA4CnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAA;AAM7D,eAAO,MAAM,kBAAkB,EAAE,SAAS,EA2FzC,CAAA"}
1
+ {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAA;AAO7D,eAAO,MAAM,kBAAkB,EAAE,SAAS,EA0FzC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/pi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAA;AAc7D,eAAO,MAAM,YAAY,EAAE,SAAS,EAyBnC,CAAA"}
1
+ {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/pi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAA;AA2B7D,eAAO,MAAM,YAAY,EAAE,SAAS,EA0BnC,CAAA"}
package/dist/server.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  runObserveHook,
12
12
  runTransformHook,
13
13
  startProxyServer
14
- } from "./cli-fc6mt326.js";
14
+ } from "./cli-6rezv582.js";
15
15
  import"./cli-cx463q74.js";
16
16
  import"./cli-sry5aqdj.js";
17
17
  import"./cli-4rqtm83g.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.44.0",
3
+ "version": "1.44.1",
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",