@rynfar/meridian 1.31.2 → 1.34.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
@@ -12,7 +12,7 @@
12
12
 
13
13
  ---
14
14
 
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
+ 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, ForgeCode, 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.
16
16
 
17
17
  > [!NOTE]
18
18
  > ### How Meridian works with Anthropic
@@ -273,6 +273,45 @@ Point any OpenAI-compatible tool at `http://127.0.0.1:3456` with any API key val
273
273
 
274
274
  > **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.
275
275
 
276
+ ### ForgeCode
277
+
278
+ Add a custom provider to `~/forge/.forge.toml`:
279
+
280
+ ```toml
281
+ [[providers]]
282
+ id = "meridian"
283
+ url = "http://127.0.0.1:3456/v1/messages"
284
+ models = "http://127.0.0.1:3456/v1/models"
285
+ api_key_vars = "MERIDIAN_API_KEY"
286
+ response_type = "Anthropic"
287
+ auth_methods = ["api_key"]
288
+
289
+ [session]
290
+ provider_id = "meridian"
291
+ model_id = "claude-opus-4-6"
292
+ ```
293
+
294
+ Set the API key env var (any value — Meridian authenticates through the SDK, not API keys):
295
+
296
+ ```bash
297
+ export MERIDIAN_API_KEY=unused
298
+ ```
299
+
300
+ Then log in and select the model:
301
+
302
+ ```bash
303
+ forge provider login meridian # enter any value when prompted
304
+ forge config set provider meridian --model claude-opus-4-6
305
+ ```
306
+
307
+ Start Meridian with the ForgeCode adapter:
308
+
309
+ ```bash
310
+ MERIDIAN_DEFAULT_AGENT=forgecode meridian
311
+ ```
312
+
313
+ ForgeCode uses reqwest's default User-Agent, so automatic detection isn't possible. The `MERIDIAN_DEFAULT_AGENT` env var tells Meridian to use the ForgeCode adapter for all unrecognized requests. If you run other agents alongside ForgeCode, use the `x-meridian-agent: forgecode` header instead (add `[providers.headers]` to your `.forge.toml`).
314
+
276
315
  ### Pi
277
316
 
278
317
  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`:
@@ -305,6 +344,7 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:3456
305
344
  | Agent | Status | Notes |
306
345
  |-------|--------|-------|
307
346
  | [OpenCode](https://github.com/anomalyco/opencode) | ✅ Verified | Requires `meridian setup` — full tool support, session resume, streaming, subagents |
347
+ | [ForgeCode](https://forgecode.dev) | ✅ Verified | Provider config (see above) — passthrough tool execution, session resume, streaming |
308
348
  | [Droid (Factory AI)](https://factory.ai/product/ide) | ✅ Verified | BYOK config (see above) — full tool support, session resume, streaming |
309
349
  | [Crush](https://github.com/charmbracelet/crush) | ✅ Verified | Provider config (see above) — full tool support, session resume, headless `crush run` |
310
350
  | [Cline](https://github.com/cline/cline) | ✅ Verified | Config (see above) — full tool support, file read/write/edit, bash, session resume |
@@ -324,6 +364,7 @@ src/proxy/
324
364
  ├── adapters/
325
365
  │ ├── detect.ts ← Agent detection from request headers
326
366
  │ ├── opencode.ts ← OpenCode adapter
367
+ │ ├── forgecode.ts ← ForgeCode adapter
327
368
  │ ├── crush.ts ← Crush adapter
328
369
  │ ├── droid.ts ← Droid adapter
329
370
  │ ├── pi.ts ← Pi adapter
@@ -394,7 +435,7 @@ Implement the `AgentAdapter` interface in `src/proxy/adapters/`. See [`adapters/
394
435
  | `MERIDIAN_TELEMETRY_SIZE` | `CLAUDE_PROXY_TELEMETRY_SIZE` | `1000` | Telemetry ring buffer size |
395
436
  | `MERIDIAN_NO_FILE_CHANGES` | `CLAUDE_PROXY_NO_FILE_CHANGES` | unset | Disable "Files changed" summary in responses |
396
437
  | `MERIDIAN_SONNET_MODEL` | `CLAUDE_PROXY_SONNET_MODEL` | `sonnet` | Sonnet context tier: `sonnet` (200k, default) or `sonnet[1m]` (1M, requires Extra Usage†) |
397
- | `MERIDIAN_DEFAULT_AGENT` | — | `opencode` | Default adapter for unrecognized agents: `opencode`, `pi`, `crush`, `droid`, `passthrough`. Requires restart. |
438
+ | `MERIDIAN_DEFAULT_AGENT` | — | `opencode` | Default adapter for unrecognized agents: `opencode`, `forgecode`, `pi`, `crush`, `droid`, `passthrough`. Requires restart. |
398
439
  | `MERIDIAN_PROFILES` | — | unset | JSON array of profile configs (overrides disk discovery). See [Multi-Profile Support](#multi-profile-support). |
399
440
  | `MERIDIAN_DEFAULT_PROFILE` | — | *(first profile)* | Default profile ID when no header is sent |
400
441
 
@@ -7413,7 +7413,6 @@ function translateAnthropicSseEvent(event, completionId, model, created) {
7413
7413
  return null;
7414
7414
  }
7415
7415
  function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000)) {
7416
- const extendedContext = isMaxSubscription ? 1e6 : 200000;
7417
7416
  return [
7418
7417
  {
7419
7418
  id: "claude-sonnet-4-6",
@@ -7421,7 +7420,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
7421
7420
  created: now,
7422
7421
  owned_by: "anthropic",
7423
7422
  display_name: "Claude Sonnet 4.6",
7424
- context_window: extendedContext
7423
+ context_window: 200000
7425
7424
  },
7426
7425
  {
7427
7426
  id: "claude-opus-4-6",
@@ -7429,7 +7428,7 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
7429
7428
  created: now,
7430
7429
  owned_by: "anthropic",
7431
7430
  display_name: "Claude Opus 4.6",
7432
- context_window: extendedContext
7431
+ context_window: isMaxSubscription ? 1e6 : 200000
7433
7432
  },
7434
7433
  {
7435
7434
  id: "claude-haiku-4-5-20251001",
@@ -8110,6 +8109,9 @@ var piAdapter = {
8110
8109
  buildSdkAgents(_body, _mcpToolNames) {
8111
8110
  return {};
8112
8111
  },
8112
+ supportsThinking() {
8113
+ return true;
8114
+ },
8113
8115
  buildSdkHooks(_body, _sdkAgents) {
8114
8116
  return;
8115
8117
  },
@@ -8132,13 +8134,85 @@ var piAdapter = {
8132
8134
  }
8133
8135
  };
8134
8136
 
8137
+ // src/proxy/adapters/forgecode.ts
8138
+ var FORGECODE_MCP_SERVER_NAME = "forgecode";
8139
+ var FORGECODE_ALLOWED_MCP_TOOLS = [
8140
+ `mcp__${FORGECODE_MCP_SERVER_NAME}__read`,
8141
+ `mcp__${FORGECODE_MCP_SERVER_NAME}__write`,
8142
+ `mcp__${FORGECODE_MCP_SERVER_NAME}__edit`,
8143
+ `mcp__${FORGECODE_MCP_SERVER_NAME}__bash`,
8144
+ `mcp__${FORGECODE_MCP_SERVER_NAME}__glob`,
8145
+ `mcp__${FORGECODE_MCP_SERVER_NAME}__grep`
8146
+ ];
8147
+ function extractForgeCodeCwd(body) {
8148
+ let systemText = "";
8149
+ if (typeof body.system === "string") {
8150
+ systemText = body.system;
8151
+ } else if (Array.isArray(body.system)) {
8152
+ systemText = body.system.filter((b) => b.type === "text" && b.text).map((b) => b.text).join(`
8153
+ `);
8154
+ }
8155
+ if (!systemText)
8156
+ return;
8157
+ const match2 = systemText.match(/<current_working_directory>\s*([^<]+?)\s*<\/current_working_directory>/i);
8158
+ return match2?.[1]?.trim() || undefined;
8159
+ }
8160
+ var forgeCodeAdapter = {
8161
+ name: "forgecode",
8162
+ getSessionId(_c) {
8163
+ return;
8164
+ },
8165
+ extractWorkingDirectory(body) {
8166
+ return extractForgeCodeCwd(body);
8167
+ },
8168
+ normalizeContent(content) {
8169
+ return normalizeContent(content);
8170
+ },
8171
+ getBlockedBuiltinTools() {
8172
+ return BLOCKED_BUILTIN_TOOLS;
8173
+ },
8174
+ getAgentIncompatibleTools() {
8175
+ return CLAUDE_CODE_ONLY_TOOLS;
8176
+ },
8177
+ getMcpServerName() {
8178
+ return FORGECODE_MCP_SERVER_NAME;
8179
+ },
8180
+ getAllowedMcpTools() {
8181
+ return FORGECODE_ALLOWED_MCP_TOOLS;
8182
+ },
8183
+ buildSdkAgents(_body, _mcpToolNames) {
8184
+ return {};
8185
+ },
8186
+ buildSdkHooks(_body, _sdkAgents) {
8187
+ return;
8188
+ },
8189
+ buildSystemContextAddendum(_body, _sdkAgents) {
8190
+ return "";
8191
+ },
8192
+ extractFileChangesFromToolUse(toolName, toolInput) {
8193
+ const input = toolInput;
8194
+ const filePath = input?.file_path ?? input?.filePath ?? input?.path;
8195
+ if (toolName === "write" && filePath) {
8196
+ return [{ operation: "wrote", path: String(filePath) }];
8197
+ }
8198
+ if ((toolName === "patch" || toolName === "multi_patch") && filePath) {
8199
+ return [{ operation: "edited", path: String(filePath) }];
8200
+ }
8201
+ if (toolName === "shell" && input?.command) {
8202
+ return extractFileChangesFromBash(String(input.command));
8203
+ }
8204
+ return [];
8205
+ }
8206
+ };
8207
+
8135
8208
  // src/proxy/adapters/detect.ts
8136
8209
  var ADAPTER_MAP = {
8137
8210
  opencode: openCodeAdapter,
8138
8211
  droid: droidAdapter,
8139
8212
  crush: crushAdapter,
8140
8213
  passthrough: passthroughAdapter,
8141
- pi: piAdapter
8214
+ pi: piAdapter,
8215
+ forgecode: forgeCodeAdapter
8142
8216
  };
8143
8217
  var envDefault = process.env.MERIDIAN_DEFAULT_AGENT || "";
8144
8218
  if (envDefault && !ADAPTER_MAP[envDefault]) {
@@ -14028,6 +14102,76 @@ function formatAnomalyAlerts(requestId, anomalies) {
14028
14102
  });
14029
14103
  }
14030
14104
 
14105
+ // src/proxy/tokenUsage.ts
14106
+ function computeCacheHitRate(usage) {
14107
+ if (!usage)
14108
+ return;
14109
+ const read = usage.cache_read_input_tokens ?? 0;
14110
+ const creation = usage.cache_creation_input_tokens ?? 0;
14111
+ const uncached = usage.input_tokens ?? 0;
14112
+ const total = uncached + read + creation;
14113
+ if (total === 0)
14114
+ return;
14115
+ return read / total;
14116
+ }
14117
+ function formatTokenCount(n) {
14118
+ return n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
14119
+ }
14120
+ function formatUsageSummary(usage) {
14121
+ const parts = [
14122
+ `input=${formatTokenCount(usage.input_tokens ?? 0)}`,
14123
+ `output=${formatTokenCount(usage.output_tokens ?? 0)}`,
14124
+ ...usage.cache_read_input_tokens ? [`cache_read=${formatTokenCount(usage.cache_read_input_tokens)}`] : [],
14125
+ ...usage.cache_creation_input_tokens ? [`cache_write=${formatTokenCount(usage.cache_creation_input_tokens)}`] : []
14126
+ ];
14127
+ const rate = computeCacheHitRate(usage);
14128
+ const cacheTag = rate !== undefined ? ` cache=${Math.round(rate * 100)}%` : "";
14129
+ return `${parts.join(" ")}${cacheTag}`;
14130
+ }
14131
+
14132
+ // src/proxy/sanitize.ts
14133
+ var ORCHESTRATION_TAGS = [
14134
+ "system-reminder",
14135
+ "env",
14136
+ "system_information",
14137
+ "current_working_directory",
14138
+ "operating_system",
14139
+ "default_shell",
14140
+ "home_directory",
14141
+ "task_metadata",
14142
+ "tool_exec",
14143
+ "tool_output",
14144
+ "skill_content",
14145
+ "skill_files",
14146
+ "directories",
14147
+ "available_skills",
14148
+ "thinking"
14149
+ ];
14150
+ var PAIRED_TAG_PATTERNS = ORCHESTRATION_TAGS.map((tag) => new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi"));
14151
+ var SELF_CLOSING_TAG_PATTERNS = ORCHESTRATION_TAGS.map((tag) => new RegExp(`<${tag}\\b[^>]*\\/>`, "gi"));
14152
+ var NON_XML_PATTERNS = [
14153
+ /<!--\s*OMO_INTERNAL_INITIATOR\s*-->/gi,
14154
+ /\[SYSTEM DIRECTIVE: OH-MY-OPENCODE[^\]]*\]/gi,
14155
+ /⚙\s*background_output\s*\[task_id=[^\]]*\]\n?/g,
14156
+ /\n?---\nFiles changed:[^\n]*(?:\n(?: [-•*] [^\n]*))*\n?/g
14157
+ ];
14158
+ var ALL_PATTERNS = [
14159
+ ...PAIRED_TAG_PATTERNS,
14160
+ ...SELF_CLOSING_TAG_PATTERNS,
14161
+ ...NON_XML_PATTERNS
14162
+ ];
14163
+ function sanitizeTextContent(text) {
14164
+ let result = text;
14165
+ for (const pattern of ALL_PATTERNS) {
14166
+ pattern.lastIndex = 0;
14167
+ result = result.replace(pattern, "");
14168
+ }
14169
+ result = result.replace(/\n{3,}/g, `
14170
+
14171
+ `);
14172
+ return result.trim();
14173
+ }
14174
+
14031
14175
  // src/proxy/session/lineage.ts
14032
14176
  import { createHash as createHash2 } from "crypto";
14033
14177
  var MIN_SUFFIX_FOR_COMPACTION = 2;
@@ -14679,11 +14823,11 @@ function buildFreshPrompt(messages, stripCacheControl) {
14679
14823
  const role = m.role === "assistant" ? "Assistant" : "Human";
14680
14824
  let content;
14681
14825
  if (typeof m.content === "string") {
14682
- content = m.content;
14826
+ content = sanitizeTextContent(m.content);
14683
14827
  } else if (Array.isArray(m.content)) {
14684
14828
  content = m.content.map((block) => {
14685
14829
  if (block.type === "text" && block.text)
14686
- return block.text;
14830
+ return sanitizeTextContent(block.text);
14687
14831
  if (block.type === "tool_use")
14688
14832
  return `[Tool Use: ${block.name}(${JSON.stringify(block.input)})]`;
14689
14833
  if (block.type === "tool_result")
@@ -14706,29 +14850,7 @@ function buildFreshPrompt(messages, stripCacheControl) {
14706
14850
  `) || "";
14707
14851
  }
14708
14852
  function logUsage(requestId, usage) {
14709
- const fmt2 = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
14710
- const cacheRead = usage.cache_read_input_tokens ?? 0;
14711
- const totalInput = usage.input_tokens ?? 0;
14712
- const cacheRate = totalInput > 0 ? Math.round(cacheRead / totalInput * 100) : 0;
14713
- const cacheTag = totalInput > 0 ? ` cache=${cacheRate}%` : "";
14714
- const parts = [
14715
- `input=${fmt2(usage.input_tokens ?? 0)}`,
14716
- `output=${fmt2(usage.output_tokens ?? 0)}`,
14717
- ...usage.cache_read_input_tokens ? [`cache_read=${fmt2(usage.cache_read_input_tokens)}`] : [],
14718
- ...usage.cache_creation_input_tokens ? [`cache_write=${fmt2(usage.cache_creation_input_tokens)}`] : []
14719
- ];
14720
- console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}${cacheTag}`);
14721
- }
14722
- function computeCacheHitRate(usage) {
14723
- if (!usage)
14724
- return;
14725
- const read = usage.cache_read_input_tokens ?? 0;
14726
- const creation = usage.cache_creation_input_tokens ?? 0;
14727
- const uncached = usage.input_tokens ?? 0;
14728
- const total = uncached + read + creation;
14729
- if (total === 0)
14730
- return;
14731
- return read / total;
14853
+ console.error(`[PROXY] ${requestId} usage: ${formatUsageSummary(usage)}`);
14732
14854
  }
14733
14855
  function checkTokenHealth(requestId, sdkSessionId, usage, turnNumber, isResume, isPassthrough) {
14734
14856
  if (!usage || !sdkSessionId)
@@ -15005,11 +15127,11 @@ function createProxyServer(config = {}) {
15005
15127
  const role = m.role === "assistant" ? "Assistant" : "Human";
15006
15128
  let content;
15007
15129
  if (typeof m.content === "string") {
15008
- content = m.content;
15130
+ content = sanitizeTextContent(m.content);
15009
15131
  } else if (Array.isArray(m.content)) {
15010
15132
  content = m.content.map((block) => {
15011
15133
  if (block.type === "text" && block.text)
15012
- return block.text;
15134
+ return sanitizeTextContent(block.text);
15013
15135
  if (block.type === "tool_use")
15014
15136
  return `[Tool Use: ${block.name}(${JSON.stringify(block.input)})]`;
15015
15137
  if (block.type === "tool_result")
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-9qcjkj7n.js";
4
+ } from "./cli-bwchvbfb.js";
5
5
  import"./cli-g9ypdz51.js";
6
6
  import"./cli-rtab0qa6.js";
7
7
  import"./cli-m9pfb7h9.js";
@@ -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;AAqC9C;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CA+BtD"}
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;AAuC9C;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CA+BtD"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * ForgeCode agent adapter.
3
+ *
4
+ * Provides ForgeCode-specific behavior for session tracking, working directory
5
+ * extraction, content normalization, and tool configuration.
6
+ *
7
+ * ForgeCode (forgecode.dev) is a Rust-based terminal coding agent by Antinomy
8
+ * that makes standard Anthropic Messages API calls. When using a custom provider
9
+ * URL (pointing at Meridian), ForgeCode operates in passthrough mode with its
10
+ * own tool execution loop.
11
+ *
12
+ * Key characteristics:
13
+ * - User-Agent: reqwest default (no distinctive prefix) — use x-meridian-agent or env var
14
+ * - No session header: relies on fingerprint-based session cache
15
+ * - CWD in system prompt: <current_working_directory>/path</current_working_directory>
16
+ * - Snake_case tools: read, write, patch, multi_patch, shell, fs_search, etc.
17
+ * - Always streams (stream: true by default)
18
+ * - Manages its own tool execution loop: passthrough mode is appropriate
19
+ * - Subagent routing handled client-side via Task tool — invisible to proxy
20
+ *
21
+ * Detection: ForgeCode uses reqwest's default User-Agent, so automatic detection
22
+ * is unreliable. Use one of:
23
+ * - x-meridian-agent: forgecode header (per-request)
24
+ * - MERIDIAN_DEFAULT_AGENT=forgecode env var (global default)
25
+ */
26
+ import type { AgentAdapter } from "../adapter";
27
+ export declare const forgeCodeAdapter: AgentAdapter;
28
+ //# sourceMappingURL=forgecode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forgecode.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/forgecode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAwC9C,eAAO,MAAM,gBAAgB,EAAE,YA6F9B,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,YA+FvB,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;AAwC9C,eAAO,MAAM,SAAS,EAAE,YAsGvB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/proxy/openai.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/proxy/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Per-block content sanitizer for orchestration wrapper leakage.
3
+ *
4
+ * Agent harnesses (OpenCode, Droid, ForgeCode, oh-my-opencode, etc.) inject
5
+ * internal markup into message content — `<system-reminder>`, `<env>`,
6
+ * `<task_metadata>`, and similar tags. When the proxy flattens messages into
7
+ * a text prompt for the Agent SDK, these tags become model-visible text that
8
+ * can confuse the model or cause it to echo them back ("talking to itself").
9
+ *
10
+ * This module strips known orchestration tags from **individual text blocks**
11
+ * before flattening — not from the final concatenated string. Operating
12
+ * per-block eliminates the cross-message regex risk that makes full-string
13
+ * sanitization fragile.
14
+ *
15
+ * Pure module — no I/O, no imports from server.ts or session/.
16
+ *
17
+ * Fixes: https://github.com/rynfar/meridian/issues/167
18
+ */
19
+ /**
20
+ * Strip orchestration wrappers from a single text string.
21
+ *
22
+ * Designed to be called on individual content blocks (not concatenated
23
+ * prompt strings) to eliminate cross-block regex matching risk.
24
+ */
25
+ export declare function sanitizeTextContent(text: string): string;
26
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/proxy/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA6DH;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUxD"}
@@ -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;AAuBvD,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;AAyK7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAqwDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
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;AAyBvD,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;AAkJ7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAwwDhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiEhG"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Token usage formatting and cache-hit-rate calculations.
3
+ *
4
+ * Pure functions shared between stderr request logging and telemetry storage.
5
+ * Extracting these out of server.ts guarantees a single source of truth for
6
+ * the cache-hit-rate formula — stderr logs and the telemetry dashboard now
7
+ * agree.
8
+ *
9
+ * Previously `logUsage` computed `cache_read / input_tokens`, but the SDK
10
+ * reports `input_tokens` as *only* the non-cached portion, so the ratio blew
11
+ * past 100% (e.g. `cache=2600000%`) whenever a cache hit actually happened.
12
+ * `computeCacheHitRate` already had the correct formula; this module moves
13
+ * `logUsage` onto the same helper.
14
+ *
15
+ * This module is pure — no I/O, no imports from server.ts or session/.
16
+ */
17
+ import type { TokenUsage } from "./session/lineage";
18
+ /**
19
+ * Compute the fraction of input tokens served from cache for a single turn.
20
+ *
21
+ * The SDK's `input_tokens` field reports only the non-cached portion, so the
22
+ * true total input size is `input_tokens + cache_read_input_tokens +
23
+ * cache_creation_input_tokens`. Returns a value in [0, 1], or `undefined` if
24
+ * there is no input to measure against.
25
+ */
26
+ export declare function computeCacheHitRate(usage: TokenUsage | undefined): number | undefined;
27
+ /**
28
+ * Format an integer token count for compact display.
29
+ *
30
+ * - values ≤ 1000 → exact integer (e.g. `"847"`)
31
+ * - values > 1000 → rounded to nearest thousand with "k" suffix (e.g. `"71k"`)
32
+ */
33
+ export declare function formatTokenCount(n: number): string;
34
+ /**
35
+ * Build the single-line stderr summary for a request's token usage.
36
+ *
37
+ * Example outputs:
38
+ * "input=3 output=416 cache_read=71k cache_write=2k cache=97%"
39
+ * "input=10 output=8 cache_write=59k cache=0%"
40
+ * "input=1000 output=50" (no caching activity)
41
+ *
42
+ * The `cache=XX%` tag is emitted whenever `computeCacheHitRate` returns a
43
+ * defined value. The tag is suppressed only when there is literally no input
44
+ * data to compute a ratio from (neither uncached nor cached).
45
+ */
46
+ export declare function formatUsageSummary(usage: TokenUsage): string;
47
+ //# sourceMappingURL=tokenUsage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenUsage.d.ts","sourceRoot":"","sources":["../../src/proxy/tokenUsage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAQrF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAU5D"}
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getMaxSessionsLimit,
7
7
  hashMessage,
8
8
  startProxyServer
9
- } from "./cli-9qcjkj7n.js";
9
+ } from "./cli-bwchvbfb.js";
10
10
  import"./cli-g9ypdz51.js";
11
11
  import"./cli-rtab0qa6.js";
12
12
  import"./cli-m9pfb7h9.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.31.2",
3
+ "version": "1.34.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",
@@ -44,9 +44,12 @@ const MeridianPlugin: Plugin = async () => {
44
44
  output.headers["x-opencode-agent-mode"] = typeof agent === "object"
45
45
  ? (agent.mode ?? "primary")
46
46
  : "primary"
47
- output.headers["x-opencode-agent-name"] = typeof agent === "object"
47
+ const rawName = typeof agent === "object"
48
48
  ? (agent.name ?? "unknown")
49
49
  : String(agent)
50
+ // Strip non-ASCII characters (e.g. zero-width spaces) that cause
51
+ // "Header has invalid value" errors in Node.js / undici.
52
+ output.headers["x-opencode-agent-name"] = rawName.replace(/[^\x20-\x7E]/g, "").trim() || "unknown"
50
53
  },
51
54
  }
52
55
  }