@rynfar/meridian 1.63.0 → 1.64.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.js CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-hdyptm7z.js";
4
+ } from "./cli-4ghkaajs.js";
5
5
  import"./cli-m0p2bc8v.js";
6
6
  import"./cli-sry5aqdj.js";
7
7
  import"./cli-8yp89fan.js";
8
8
  import {
9
9
  resolveClaudeExecutableAsync
10
- } from "./cli-xfbhn15a.js";
11
- import"./cli-vj9cv18n.js";
12
- import"./cli-pc0mtjjv.js";
10
+ } from "./cli-0ed6j0vk.js";
13
11
  import"./cli-khhjyk04.js";
12
+ import"./cli-vj9cv18n.js";
13
+ import"./cli-pbvm9kc2.js";
14
14
  import {
15
15
  __require
16
16
  } from "./cli-p9swy5t3.js";
@@ -39,6 +39,11 @@ Commands:
39
39
  profile Manage Claude account profiles (add, list, switch, remove)
40
40
  refresh-token Refresh the Claude Code OAuth token
41
41
 
42
+ Setup options:
43
+ --v1 Install the OpenCode V1 plugin
44
+ --v2 Install the pinned OpenCode V2 beta plugin
45
+ --opencode-bin <executable> Probe this OpenCode executable
46
+
42
47
  Options:
43
48
  -v, --version Show version
44
49
  -h, --help Show this help
@@ -55,7 +60,7 @@ See https://github.com/rynfar/meridian for full documentation.`);
55
60
  process.exit(0);
56
61
  }
57
62
  if (args[0] === "profile") {
58
- const { profileAdd, profileAddOauthToken, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-39vshwdn.js");
63
+ const { profileAdd, profileAddOauthToken, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-ap7eg985.js");
59
64
  const subcommand = args[1];
60
65
  const profileId = args[2];
61
66
  const headless = args.includes("--headless");
@@ -80,11 +85,57 @@ if (args[0] === "profile") {
80
85
  process.exit(0);
81
86
  }
82
87
  if (args[0] === "setup") {
83
- const { findPluginPath, runSetup, UnparseableConfigError } = await import("./setup-0x573t61.js");
84
- const pluginPath = findPluginPath(import.meta.url);
88
+ const {
89
+ detectOpenCodeGeneration,
90
+ DuplicateMeridianConfigError,
91
+ MissingV2PluginError,
92
+ pluginPathForGeneration,
93
+ runSetup,
94
+ SUPPORTED_OPENCODE_V2_VERSION,
95
+ UnparseableConfigError
96
+ } = await import("./setup-4v3zg3c0.js");
97
+ const forceV1 = args.includes("--v1");
98
+ const forceV2 = args.includes("--v2");
99
+ if (forceV1 && forceV2) {
100
+ console.error("Choose only one OpenCode generation: --v1 or --v2");
101
+ process.exit(1);
102
+ }
103
+ const binaryIndex = args.indexOf("--opencode-bin");
104
+ const binary = binaryIndex >= 0 ? args[binaryIndex + 1] : undefined;
105
+ if (binaryIndex >= 0 && (!binary || binary.startsWith("--"))) {
106
+ console.error("--opencode-bin requires an executable path");
107
+ process.exit(1);
108
+ }
109
+ const environmentBinary = process.env.OPENCODE_BIN;
110
+ const commands = binary ? [binary] : environmentBinary ? [environmentBinary] : forceV2 ? ["opencode2", "opencode"] : undefined;
111
+ const detected = forceV1 ? { generation: "v1" } : detectOpenCodeGeneration(commands);
112
+ if (!forceV1 && (binary || environmentBinary) && !detected.version) {
113
+ console.error(`Could not read a supported OpenCode version from ${binary ?? environmentBinary}.`);
114
+ process.exit(1);
115
+ }
116
+ if (forceV2 && detected.generation !== "v2") {
117
+ console.error("Could not find an OpenCode V2 beta. Install the pinned beta or pass --opencode-bin <path>.");
118
+ process.exit(1);
119
+ }
120
+ if (detected.generation === "v2" && detected.version !== SUPPORTED_OPENCODE_V2_VERSION) {
121
+ console.error(`OpenCode V2 ${detected.version ?? "unknown"} is not supported by this Meridian build.`);
122
+ console.error(`Install @opencode-ai/cli@${SUPPORTED_OPENCODE_V2_VERSION}, then re-run meridian setup --v2.`);
123
+ process.exit(1);
124
+ }
125
+ let pluginPath;
126
+ try {
127
+ pluginPath = pluginPathForGeneration(import.meta.url, detected.generation);
128
+ } catch (err) {
129
+ if (err instanceof MissingV2PluginError) {
130
+ console.error(`OpenCode V2 plugin bundle is missing: ${err.expectedPath}`);
131
+ console.error("Reinstall Meridian, then re-run meridian setup --v2.");
132
+ process.exit(1);
133
+ }
134
+ throw err;
135
+ }
85
136
  let result;
86
137
  try {
87
- result = runSetup(pluginPath);
138
+ result = runSetup(pluginPath, undefined, detected.generation);
88
139
  } catch (err) {
89
140
  if (err instanceof UnparseableConfigError) {
90
141
  console.error(`\x1B[31m✗ Could not parse ${err.configPath}\x1B[0m`);
@@ -93,16 +144,24 @@ if (args[0] === "setup") {
93
144
  console.error(` "plugin": ["${pluginPath}"]`);
94
145
  process.exit(1);
95
146
  }
147
+ if (err instanceof DuplicateMeridianConfigError) {
148
+ console.error("\x1B[31m✗ Meridian is already present in the other OpenCode config file\x1B[0m");
149
+ console.error(` OpenCode loads both ${err.configPath} and ${err.siblingPath}.`);
150
+ console.error(` Remove the Meridian entry from ${err.siblingPath}, then re-run 'meridian setup'.`);
151
+ console.error(" Both files were left untouched.");
152
+ process.exit(1);
153
+ }
96
154
  throw err;
97
155
  }
156
+ const generationLabel = detected.generation === "v2" ? "OpenCode V2" : "OpenCode V1";
98
157
  if (result.alreadyConfigured) {
99
- console.log(`\x1B[32m✓ Meridian plugin already configured\x1B[0m`);
158
+ console.log(`\x1B[32m✓ Meridian plugin already configured for ${generationLabel}\x1B[0m`);
100
159
  console.log(` ${result.configPath}`);
101
160
  } else {
102
161
  if (result.removedStale.length > 0) {
103
162
  console.log(` Removed ${result.removedStale.length} stale plugin entr${result.removedStale.length === 1 ? "y" : "ies"}`);
104
163
  }
105
- console.log(`\x1B[32m✓ Meridian plugin configured\x1B[0m`);
164
+ console.log(`\x1B[32m✓ Meridian plugin configured for ${generationLabel}\x1B[0m`);
106
165
  console.log(` Config: ${result.configPath}`);
107
166
  console.log(` Plugin: ${result.pluginPath}`);
108
167
  if (!result.created) {
@@ -146,7 +205,7 @@ async function runCli(start = startProxyServer, runAuthCheck = async () => {
146
205
  return execFile(claudePath, ["auth", "status"], { timeout: 5000 });
147
206
  }) {
148
207
  try {
149
- const { findOpencodeConfigPath, checkPluginConfigured, findPluginPath } = await import("./setup-0x573t61.js");
208
+ const { findOpencodeConfigPath, checkPluginConfigured, findPluginPath } = await import("./setup-4v3zg3c0.js");
150
209
  const configPath = findOpencodeConfigPath();
151
210
  const { existsSync } = await import("fs");
152
211
  if (existsSync(configPath) && !checkPluginConfigured(configPath)) {
@@ -0,0 +1,139 @@
1
+ import"./cli-p9swy5t3.js";
2
+
3
+ // node_modules/@opencode-ai/plugin/dist/promise/plugin.js
4
+ function define(plugin) {
5
+ return plugin;
6
+ }
7
+
8
+ // plugin/meridian-v2.ts
9
+ var SUPPORTED_OPENCODE_V2_VERSION = "0.0.0-beta-18314";
10
+ var MERIDIAN_PROVIDERS = new Set(["anthropic", "meridian"]);
11
+ var PARENT_SESSION_ONE_SHOTS = new Set(["title", "summary"]);
12
+ var ATTACHED_COMPACTION_AGENT = "compaction";
13
+ var SESSION_AFFINITY_HEADERS = [
14
+ "x-opencode-session",
15
+ "x-session-affinity",
16
+ "x-session-id",
17
+ "x-parent-session-id"
18
+ ];
19
+ var MERIDIAN_CONTROL_HEADERS = [
20
+ "x-meridian-source",
21
+ "x-opencode-agent-name",
22
+ "x-opencode-agent-mode"
23
+ ];
24
+ var BUILTIN_AGENT_TRAITS = {
25
+ build: { mode: "primary", hidden: false },
26
+ plan: { mode: "primary", hidden: false },
27
+ general: { mode: "subagent", hidden: false },
28
+ explore: { mode: "subagent", hidden: false },
29
+ title: { mode: "primary", hidden: true },
30
+ summary: { mode: "primary", hidden: true },
31
+ compaction: { mode: "primary", hidden: true }
32
+ };
33
+ function fallbackAgentTraits(agent) {
34
+ return BUILTIN_AGENT_TRAITS[agent] ?? { mode: "primary", hidden: false };
35
+ }
36
+ function deleteHeader(headers, name) {
37
+ if (headers instanceof Headers) {
38
+ headers.delete(name);
39
+ return;
40
+ }
41
+ const lower = name.toLowerCase();
42
+ for (const key of Object.keys(headers)) {
43
+ if (key.toLowerCase() === lower)
44
+ delete headers[key];
45
+ }
46
+ }
47
+ function setHeader(headers, name, value) {
48
+ deleteHeader(headers, name);
49
+ if (headers instanceof Headers)
50
+ headers.set(name, value);
51
+ else
52
+ headers[name] = value;
53
+ }
54
+ function safeAgentName(agent) {
55
+ return agent.replace(/[^\x20-\x7E]/g, "").trim() || "unknown";
56
+ }
57
+ function shouldDetachFromParentSession(agent, _traits) {
58
+ return PARENT_SESSION_ONE_SHOTS.has(agent);
59
+ }
60
+ function applyMeridianV2Headers(headers, input) {
61
+ for (const name2 of SESSION_AFFINITY_HEADERS)
62
+ deleteHeader(headers, name2);
63
+ for (const name2 of MERIDIAN_CONTROL_HEADERS)
64
+ deleteHeader(headers, name2);
65
+ const name = safeAgentName(input.agent);
66
+ const detached = shouldDetachFromParentSession(input.agent, input.traits);
67
+ if (detached) {
68
+ setHeader(headers, "x-meridian-source", `subagent-${name}`);
69
+ } else {
70
+ setHeader(headers, "x-opencode-session", input.sessionID);
71
+ setHeader(headers, "x-session-affinity", input.sessionID);
72
+ setHeader(headers, "x-session-id", input.sessionID);
73
+ if (input.agent === ATTACHED_COMPACTION_AGENT) {
74
+ setHeader(headers, "x-meridian-source", "subagent-compaction");
75
+ }
76
+ }
77
+ const internalMode = PARENT_SESSION_ONE_SHOTS.has(input.agent) ? "subagent" : input.agent === ATTACHED_COMPACTION_AGENT ? "primary" : input.traits.mode;
78
+ setHeader(headers, "x-opencode-agent-name", name);
79
+ setHeader(headers, "x-opencode-agent-mode", internalMode);
80
+ }
81
+ var MeridianV2Plugin = define({
82
+ id: "meridian",
83
+ setup: async (context) => {
84
+ const traitsByAgent = new Map;
85
+ const registered = [];
86
+ const resolveAgentTraits = (agent) => {
87
+ const cached = traitsByAgent.get(agent);
88
+ if (cached && cached.expiresAt > Date.now())
89
+ return cached.pending;
90
+ const pending = Promise.resolve().then(() => context.agent.get({ agentID: agent })).then(({ data }) => ({
91
+ mode: data.mode === "subagent" ? "subagent" : "primary",
92
+ hidden: data.hidden
93
+ })).catch(() => {
94
+ if (traitsByAgent.get(agent)?.pending === pending)
95
+ traitsByAgent.delete(agent);
96
+ return fallbackAgentTraits(agent);
97
+ });
98
+ traitsByAgent.set(agent, { expiresAt: Date.now() + 5000, pending });
99
+ return pending;
100
+ };
101
+ const apply = async (input, headers) => {
102
+ if (!MERIDIAN_PROVIDERS.has(String(input.model.providerID)))
103
+ return;
104
+ const agent = String(input.agent);
105
+ applyMeridianV2Headers(headers, {
106
+ sessionID: String(input.sessionID),
107
+ agent,
108
+ traits: await resolveAgentTraits(agent)
109
+ });
110
+ };
111
+ try {
112
+ for (const providerID of MERIDIAN_PROVIDERS) {
113
+ registered.push(await context.session.hook("model.request", async (input) => {
114
+ await apply(input, input.headers);
115
+ }, { providerID }));
116
+ registered.push(await context.session.hook("http.request", async (input) => {
117
+ await apply(input, input.request.headers);
118
+ }, { providerID }));
119
+ }
120
+ } catch (error) {
121
+ await Promise.allSettled(registered.map(({ dispose }) => dispose()));
122
+ throw error;
123
+ }
124
+ return async () => {
125
+ const results = await Promise.allSettled(registered.map(({ dispose }) => dispose()));
126
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
127
+ if (failures.length > 0)
128
+ throw new AggregateError(failures, "Failed to dispose Meridian V2 hooks");
129
+ };
130
+ }
131
+ });
132
+ var meridian_v2_default = MeridianV2Plugin;
133
+ export {
134
+ shouldDetachFromParentSession,
135
+ fallbackAgentTraits,
136
+ meridian_v2_default as default,
137
+ applyMeridianV2Headers,
138
+ SUPPORTED_OPENCODE_V2_VERSION
139
+ };
@@ -13,9 +13,9 @@ import {
13
13
  profileLogin,
14
14
  profileRemove,
15
15
  profileSwitch
16
- } from "./cli-xfbhn15a.js";
17
- import"./cli-vj9cv18n.js";
16
+ } from "./cli-0ed6j0vk.js";
18
17
  import"./cli-khhjyk04.js";
18
+ import"./cli-vj9cv18n.js";
19
19
  import"./cli-p9swy5t3.js";
20
20
  export {
21
21
  profileSwitch,
@@ -1 +1 @@
1
- {"version":3,"file":"passthroughTools.d.ts","sourceRoot":"","sources":["../../src/proxy/passthroughTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,eAAO,MAAM,oBAAoB,OAAO,CAAA;AACxC,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AA0CtE,wBAAgB,qBAAqB,IAAI,MAAM,CAM9C;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,EACjG,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE;;;;EA4DlC;AAqBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAC9E,MAAM,CASR;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAErE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKvD;AAUD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,YAAY,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,GACtF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAgCrC"}
1
+ {"version":3,"file":"passthroughTools.d.ts","sourceRoot":"","sources":["../../src/proxy/passthroughTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,eAAO,MAAM,oBAAoB,OAAO,CAAA;AACxC,eAAO,MAAM,sBAAsB,cAAmC,CAAA;AA0CtE,wBAAgB,qBAAqB,IAAI,MAAM,CAM9C;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,EACjG,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE;;;;EA8ClC;AAqBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAC9E,MAAM,CASR;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAErE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKvD;AAUD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,YAAY,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,GACtF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAgCrC"}
@@ -18,22 +18,46 @@ export declare class UnparseableConfigError extends Error {
18
18
  readonly configPath: string;
19
19
  constructor(configPath: string);
20
20
  }
21
- /**
22
- * Resolve the OpenCode global config file path.
23
- * Respects OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME env vars.
24
- */
21
+ export declare class MissingV2PluginError extends Error {
22
+ readonly expectedPath: string;
23
+ constructor(expectedPath: string);
24
+ }
25
+ export declare class DuplicateMeridianConfigError extends Error {
26
+ readonly configPath: string;
27
+ readonly siblingPath: string;
28
+ constructor(configPath: string, siblingPath: string);
29
+ }
25
30
  export declare function findOpencodeConfigPath(): string;
26
31
  /**
27
32
  * Resolve the absolute path to plugin/meridian.ts from any entry point.
28
33
  * Works whether called from bin/cli.ts (dev) or dist/cli.js (installed).
29
34
  */
30
35
  export declare function findPluginPath(fromUrl: string): string;
36
+ export type OpenCodeGeneration = "v1" | "v2";
37
+ export interface OpenCodeDetection {
38
+ generation: OpenCodeGeneration;
39
+ version?: string;
40
+ command?: string;
41
+ }
42
+ export declare const SUPPORTED_OPENCODE_V2_VERSION = "0.0.0-beta-18314";
43
+ /** Resolve the V2 plugin without selecting stale or incomplete artifacts. */
44
+ export declare function findV2PluginPath(fromUrl: string): string;
45
+ /** Parse the public V1 and beta V2 version formats without guessing. */
46
+ export declare function classifyOpenCodeVersion(output: string): OpenCodeDetection | undefined;
47
+ type VersionProbe = (command: string) => string | undefined;
48
+ /**
49
+ * Detect the installed OpenCode generation with bounded probes. `opencode`
50
+ * remains first so an existing V1 installation keeps its current behavior;
51
+ * side-by-side beta users can select `opencode2` explicitly.
52
+ */
53
+ export declare function detectOpenCodeGeneration(commands?: readonly string[], probe?: VersionProbe): OpenCodeDetection;
54
+ export declare function pluginPathForGeneration(fromUrl: string, generation: OpenCodeGeneration): string;
31
55
  /**
32
56
  * Returns true if the meridian plugin is already configured in the
33
57
  * OpenCode global config. Returns false if config doesn't exist or
34
58
  * plugin is missing.
35
59
  */
36
- export declare function checkPluginConfigured(configPath?: string): boolean;
60
+ export declare function checkPluginConfigured(configPath?: string, expectedPluginPath?: string): boolean;
37
61
  /** Reset the warned-session memory. Used by tests. */
38
62
  export declare function clearPluginlessWarnings(): void;
39
63
  /**
@@ -83,12 +107,13 @@ export interface SetupResult {
83
107
  created: boolean;
84
108
  }
85
109
  /**
86
- * Configure the meridian plugin in ~/.config/opencode/opencode.json.
110
+ * Configure the Meridian plugin in ~/.config/opencode/opencode.json.
87
111
  *
88
- * - Creates the config file if it doesn't exist
89
- * - Removes stale meridian plugin entries from previous installs
112
+ * - Uses `plugin` for V1 and canonical `plugins` for V2
113
+ * - Removes Meridian entries from both fields to prevent duplicate plugin IDs
90
114
  * - Adds the current plugin path
91
- * - Leaves all other plugins untouched
115
+ * - Leaves all unrelated plugins and settings untouched
92
116
  */
93
- export declare function runSetup(pluginPath: string, configPath?: string): SetupResult;
117
+ export declare function runSetup(pluginPath: string, configPath?: string, generation?: OpenCodeGeneration): SetupResult;
118
+ export {};
94
119
  //# sourceMappingURL=setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/proxy/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aACnB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAoBD;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAW/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAkBD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAOlE;AAaD,sDAAsD;AACtD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,mEAAmE;IACnE,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,mEAAmE;IACnE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B,GAAG,MAAM,GAAG,SAAS,CAkBrB;AAMD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAsC7E"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/proxy/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aACnB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,YAAY,EAAE,MAAM;gBAApB,YAAY,EAAE,MAAM;CAIjD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;aAEnC,UAAU,EAAE,MAAM;aAClB,WAAW,EAAE,MAAM;gBADnB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM;CAKtC;AA+BD,wBAAgB,sBAAsB,IAAI,MAAM,CAK/C;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE5C,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,kBAAkB,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,6BAA6B,qBAAqB,CAAA;AAE/D,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAYrF;AAED,KAAK,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;AAE3D;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,GAAE,SAAS,MAAM,EAEI,EAC7B,KAAK,GAAE,YASN,GACA,iBAAiB,CAQnB;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAE/F;AA4BD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAgB/F;AAaD,sDAAsD;AACtD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,mEAAmE;IACnE,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,mEAAmE;IACnE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B,GAAG,MAAM,GAAG,SAAS,CAkBrB;AAMD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,GAAE,kBAAyB,GACpC,WAAW,CA2Eb"}
package/dist/server.js CHANGED
@@ -11,14 +11,14 @@ import {
11
11
  runObserveHook,
12
12
  runTransformHook,
13
13
  startProxyServer
14
- } from "./cli-hdyptm7z.js";
14
+ } from "./cli-4ghkaajs.js";
15
15
  import"./cli-m0p2bc8v.js";
16
16
  import"./cli-sry5aqdj.js";
17
17
  import"./cli-8yp89fan.js";
18
- import"./cli-xfbhn15a.js";
19
- import"./cli-vj9cv18n.js";
20
- import"./cli-pc0mtjjv.js";
18
+ import"./cli-0ed6j0vk.js";
21
19
  import"./cli-khhjyk04.js";
20
+ import"./cli-vj9cv18n.js";
21
+ import"./cli-pbvm9kc2.js";
22
22
  import"./cli-p9swy5t3.js";
23
23
  export {
24
24
  startProxyServer,
@@ -0,0 +1,33 @@
1
+ import {
2
+ DuplicateMeridianConfigError,
3
+ MissingV2PluginError,
4
+ SUPPORTED_OPENCODE_V2_VERSION,
5
+ UnparseableConfigError,
6
+ checkPluginConfigured,
7
+ classifyOpenCodeVersion,
8
+ clearPluginlessWarnings,
9
+ detectOpenCodeGeneration,
10
+ findOpencodeConfigPath,
11
+ findPluginPath,
12
+ findV2PluginPath,
13
+ notePluginlessOpenCodeRequest,
14
+ pluginPathForGeneration,
15
+ runSetup
16
+ } from "./cli-pbvm9kc2.js";
17
+ import"./cli-p9swy5t3.js";
18
+ export {
19
+ runSetup,
20
+ pluginPathForGeneration,
21
+ notePluginlessOpenCodeRequest,
22
+ findV2PluginPath,
23
+ findPluginPath,
24
+ findOpencodeConfigPath,
25
+ detectOpenCodeGeneration,
26
+ clearPluginlessWarnings,
27
+ classifyOpenCodeVersion,
28
+ checkPluginConfigured,
29
+ UnparseableConfigError,
30
+ SUPPORTED_OPENCODE_V2_VERSION,
31
+ MissingV2PluginError,
32
+ DuplicateMeridianConfigError
33
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.63.0",
3
+ "version": "1.64.0",
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",
@@ -21,8 +21,8 @@
21
21
  "scripts": {
22
22
  "start": "./bin/claude-proxy-supervisor.sh",
23
23
  "proxy": "./bin/claude-proxy-supervisor.sh",
24
- "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && bun build bin/cli.ts src/proxy/server.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --external jsonc-parser --entry-naming \"[name].js\" && tsc -p tsconfig.build.json",
25
- "postbuild": "node scripts/fix-bun-exports.mjs && node --check dist/cli.js && node --check dist/server.js && node -e \"if(!require('fs').existsSync('dist/proxy/server.d.ts'))process.exit(1)\"",
24
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && bun build bin/cli.ts src/proxy/server.ts plugin/meridian-v2.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --external jsonc-parser --entry-naming \"[name].js\" && tsc -p tsconfig.build.json",
25
+ "postbuild": "node scripts/fix-bun-exports.mjs && node --check dist/cli.js && node --check dist/server.js && node --check dist/meridian-v2.js && node -e \"if(!require('fs').existsSync('dist/proxy/server.d.ts'))process.exit(1)\"",
26
26
  "postinstall": "node -e \"try{require('child_process').spawnSync(process.execPath,[require.resolve('@anthropic-ai/claude-code/install.cjs')],{stdio:'inherit'})}catch(e){console.error('[meridian] claude-code postinstall skipped:',e.message)}\"",
27
27
  "prepublishOnly": "bun run build",
28
28
  "test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' --path-ignore-patterns '**/*proxy-passthrough-thinking*' --path-ignore-patterns '**/*proxy-thinking-setting*' --path-ignore-patterns '**/*session-recovery*' --path-ignore-patterns '**/*models.test*' --path-ignore-patterns '**/*proxy-health-build*' && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts && bun test src/__tests__/proxy-passthrough-thinking.test.ts && bun test src/__tests__/proxy-session-recovery.test.ts && bun test src/__tests__/proxy-thinking-setting.test.ts && bun test src/__tests__/models.test.ts && bun test src/__tests__/proxy-health-build.test.ts",
@@ -33,12 +33,16 @@
33
33
  "dependencies": {
34
34
  "@anthropic-ai/claude-agent-sdk": "^0.2.117",
35
35
  "@anthropic-ai/claude-code": "^2.1.198",
36
+ "cross-spawn": "7.0.6",
36
37
  "jsonc-parser": "^3.3.1",
37
- "libsql": "^0.5.29"
38
+ "libsql": "^0.5.29",
39
+ "zod": "4.4.3"
38
40
  },
39
41
  "devDependencies": {
40
42
  "@hono/node-server": "^1.19.11",
43
+ "@opencode-ai/plugin": "0.0.0-beta-18314",
41
44
  "@types/bun": "^1.3.11",
45
+ "@types/cross-spawn": "6.0.6",
42
46
  "@types/node": "^22.0.0",
43
47
  "bun2nix": "^2.0.8",
44
48
  "glob": "^13.0.0",
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Meridian OpenCode plugin for the V2 beta line.
3
+ *
4
+ * OpenCode V2 runs hidden title and summary requests in the parent session,
5
+ * often in parallel with the visible first turn. V2's core gives all of those
6
+ * requests the same session-affinity headers. Meridian must detach the hidden
7
+ * one-shots before they reach the proxy or they can advance the visible
8
+ * conversation's durable lineage.
9
+ *
10
+ * The model hook applies the identity as early as V2 permits. The HTTP hook
11
+ * enforces it again at the final request boundary. This removes stale or
12
+ * spoofed control headers regardless of header casing.
13
+ *
14
+ * NOTE: OpenCode-specific. Keep this separate from plugin/meridian.ts: V1
15
+ * expects a default plugin function and V2 expects a Plugin.define() object.
16
+ */
17
+
18
+ import * as Plugin from "@opencode-ai/plugin/promise/plugin"
19
+
20
+ /** Exact V2 host used to compile and validate this beta-only integration. */
21
+ export const SUPPORTED_OPENCODE_V2_VERSION = "0.0.0-beta-18314"
22
+
23
+ const MERIDIAN_PROVIDERS = new Set(["anthropic", "meridian"])
24
+ const PARENT_SESSION_ONE_SHOTS = new Set(["title", "summary"])
25
+ const ATTACHED_COMPACTION_AGENT = "compaction"
26
+
27
+ const SESSION_AFFINITY_HEADERS = [
28
+ "x-opencode-session",
29
+ "x-session-affinity",
30
+ "x-session-id",
31
+ "x-parent-session-id",
32
+ ] as const
33
+
34
+ const MERIDIAN_CONTROL_HEADERS = [
35
+ "x-meridian-source",
36
+ "x-opencode-agent-name",
37
+ "x-opencode-agent-mode",
38
+ ] as const
39
+
40
+ export interface AgentTraits {
41
+ mode: "primary" | "subagent"
42
+ hidden: boolean
43
+ }
44
+
45
+ const BUILTIN_AGENT_TRAITS: Record<string, AgentTraits> = {
46
+ build: { mode: "primary", hidden: false },
47
+ plan: { mode: "primary", hidden: false },
48
+ general: { mode: "subagent", hidden: false },
49
+ explore: { mode: "subagent", hidden: false },
50
+ // Exact beta-18314 defines all three hidden internal agents as primary.
51
+ title: { mode: "primary", hidden: true },
52
+ summary: { mode: "primary", hidden: true },
53
+ compaction: { mode: "primary", hidden: true },
54
+ }
55
+
56
+ export function fallbackAgentTraits(agent: string): AgentTraits {
57
+ return BUILTIN_AGENT_TRAITS[agent] ?? { mode: "primary", hidden: false }
58
+ }
59
+
60
+ type MutableHeaders = Record<string, string> | Headers
61
+
62
+ function deleteHeader(headers: MutableHeaders, name: string): void {
63
+ if (headers instanceof Headers) {
64
+ headers.delete(name)
65
+ return
66
+ }
67
+ const lower = name.toLowerCase()
68
+ for (const key of Object.keys(headers)) {
69
+ if (key.toLowerCase() === lower) delete headers[key]
70
+ }
71
+ }
72
+
73
+ function setHeader(headers: MutableHeaders, name: string, value: string): void {
74
+ deleteHeader(headers, name)
75
+ if (headers instanceof Headers) headers.set(name, value)
76
+ else headers[name] = value
77
+ }
78
+
79
+ function safeAgentName(agent: string): string {
80
+ return agent.replace(/[^\x20-\x7E]/g, "").trim() || "unknown"
81
+ }
82
+
83
+ export function shouldDetachFromParentSession(agent: string, _traits: AgentTraits): boolean {
84
+ // The exact built-in ID is authoritative. `hidden` is presentation config:
85
+ // making the built-in visible does not stop V2 from running its parent-
86
+ // session title/summary job concurrently with the primary turn.
87
+ return PARENT_SESSION_ONE_SHOTS.has(agent)
88
+ }
89
+
90
+ /**
91
+ * Rewrite one V2 request's identity without changing its body or model input.
92
+ * This pure helper is shared by model.request and http.request.
93
+ */
94
+ export function applyMeridianV2Headers(
95
+ headers: MutableHeaders,
96
+ input: { sessionID: string; agent: string; traits: AgentTraits },
97
+ ): void {
98
+ for (const name of SESSION_AFFINITY_HEADERS) deleteHeader(headers, name)
99
+ for (const name of MERIDIAN_CONTROL_HEADERS) deleteHeader(headers, name)
100
+
101
+ const name = safeAgentName(input.agent)
102
+ const detached = shouldDetachFromParentSession(input.agent, input.traits)
103
+
104
+ if (detached) {
105
+ setHeader(headers, "x-meridian-source", `subagent-${name}`)
106
+ } else {
107
+ // Set every V2 affinity spelling from the trusted hook input. Do not retain
108
+ // provider-config values that can bind this request to another session.
109
+ setHeader(headers, "x-opencode-session", input.sessionID)
110
+ setHeader(headers, "x-session-affinity", input.sessionID)
111
+ setHeader(headers, "x-session-id", input.sessionID)
112
+ if (input.agent === ATTACHED_COMPACTION_AGENT) {
113
+ // Source selects the base model tier without making the adapter append
114
+ // `#compaction` to the primary session key.
115
+ setHeader(headers, "x-meridian-source", "subagent-compaction")
116
+ }
117
+ }
118
+
119
+ const internalMode = PARENT_SESSION_ONE_SHOTS.has(input.agent)
120
+ ? "subagent"
121
+ : input.agent === ATTACHED_COMPACTION_AGENT ? "primary" : input.traits.mode
122
+ setHeader(headers, "x-opencode-agent-name", name)
123
+ setHeader(headers, "x-opencode-agent-mode", internalMode)
124
+ }
125
+
126
+ const MeridianV2Plugin = Plugin.define({
127
+ id: "meridian",
128
+ setup: async (context) => {
129
+ const traitsByAgent = new Map<string, {
130
+ expiresAt: number
131
+ pending: Promise<AgentTraits>
132
+ }>()
133
+ const registered: Array<{ dispose: () => Promise<void> }> = []
134
+
135
+ const resolveAgentTraits = (agent: string): Promise<AgentTraits> => {
136
+ const cached = traitsByAgent.get(agent)
137
+ if (cached && cached.expiresAt > Date.now()) return cached.pending
138
+
139
+ const pending = Promise.resolve()
140
+ .then(() => context.agent.get({ agentID: agent }))
141
+ .then(({ data }) => ({
142
+ mode: data.mode === "subagent" ? "subagent" as const : "primary" as const,
143
+ hidden: data.hidden,
144
+ }))
145
+ .catch(() => {
146
+ // A transient lookup failure must not permanently promote a custom
147
+ // child to primary. Retry at the final HTTP boundary or next turn.
148
+ if (traitsByAgent.get(agent)?.pending === pending) traitsByAgent.delete(agent)
149
+ return fallbackAgentTraits(agent)
150
+ })
151
+ traitsByAgent.set(agent, { expiresAt: Date.now() + 5_000, pending })
152
+ return pending
153
+ }
154
+
155
+ const apply = async (
156
+ input: { sessionID: string; agent: string; model: { providerID: string } },
157
+ headers: MutableHeaders,
158
+ ): Promise<void> => {
159
+ if (!MERIDIAN_PROVIDERS.has(String(input.model.providerID))) return
160
+ const agent = String(input.agent)
161
+ applyMeridianV2Headers(headers, {
162
+ sessionID: String(input.sessionID),
163
+ agent,
164
+ traits: await resolveAgentTraits(agent),
165
+ })
166
+ }
167
+
168
+ try {
169
+ for (const providerID of MERIDIAN_PROVIDERS) {
170
+ registered.push(await context.session.hook("model.request", async (input) => {
171
+ await apply(input, input.headers)
172
+ }, { providerID }))
173
+ registered.push(await context.session.hook("http.request", async (input) => {
174
+ await apply(input, input.request.headers)
175
+ }, { providerID }))
176
+ }
177
+ } catch (error) {
178
+ await Promise.allSettled(registered.map(({ dispose }) => dispose()))
179
+ throw error
180
+ }
181
+
182
+ return async () => {
183
+ const results = await Promise.allSettled(registered.map(({ dispose }) => dispose()))
184
+ const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : [])
185
+ if (failures.length > 0) throw new AggregateError(failures, "Failed to dispose Meridian V2 hooks")
186
+ }
187
+ },
188
+ })
189
+
190
+ export default MeridianV2Plugin