@rynfar/meridian 1.48.1 → 1.49.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
@@ -99,6 +99,8 @@ Then in `~/.config/opencode/opencode.json`:
99
99
 
100
100
  > **Important:** Do not use `meridian setup` on NixOS. It writes an absolute Nix store path (e.g. `/nix/store/...-meridian-1.x.x/lib/...`) into your OpenCode config, which will break on the next `nixos-rebuild switch` or `home-manager switch` when the store path changes. Use one of the approaches above instead.
101
101
 
102
+ > **Note:** The bundled Claude Code binary (`claude.exe`) is patched with `autoPatchelfHook` at build time, so it runs on NixOS out of the box. If you previously enabled `programs.nix-ld.enable = true` as a workaround for `Could not start dynamically linked executable` (#501), that is no longer required for Meridian.
103
+
102
104
  **Home Manager service** -- run Meridian as a user systemd service:
103
105
 
104
106
  ```nix
@@ -163,7 +165,8 @@ The Claude Code SDK provides programmatic access to Claude. But your favorite co
163
165
  - **Auto token refresh** — expired OAuth tokens are refreshed automatically; requests continue without interruption
164
166
  - **Passthrough mode** — forward tool calls to the client instead of executing internally
165
167
  - **Multimodal** — images, documents, file attachments, and multimodal tool results pass through to Claude
166
- - **Multi-profile** — switch between Claude accounts instantly, no restart needed
168
+ - **Multi-profile** — switch between Claude accounts instantly, no restart needed; opt-in [sticky session routing](#sticky-session-routing) distributes sessions across accounts while keeping per-account prompt caches warm
169
+ - **Adapter instances** — run several configurations of the same adapter side by side (per-instance thinking, system prompt, passthrough) selected by header or match rules — see [Adapter instances](#adapter-instances)
167
170
  - **Telemetry dashboard** — real-time performance metrics at `/telemetry`, including token usage and prompt cache efficiency ([`MONITORING.md`](MONITORING.md))
168
171
  - **Telemetry persistence** — opt-in SQLite storage for telemetry data that survives proxy restarts, with configurable retention
169
172
  - **Prometheus metrics** — `GET /metrics` endpoint for scraping request counters and duration histograms
@@ -288,6 +291,22 @@ curl -H "x-meridian-profile: work" ...
288
291
 
289
292
  You can also switch profiles from the web UI at `http://127.0.0.1:3456/profiles` — a dropdown appears in the nav bar on all pages when profiles are configured.
290
293
 
294
+ ### Sticky session routing
295
+
296
+ With multiple profiles (e.g. two Claude Max subscriptions), Meridian can distribute sessions across profiles automatically while preserving **session affinity** — Anthropic's prompt caching is per-account, so a session must stay on one account to keep its ~99% cache hit rate:
297
+
298
+ ```bash
299
+ MERIDIAN_ROUTING=sticky meridian # or set "routing": "sticky" in ~/.config/meridian/settings.json
300
+ ```
301
+
302
+ - Each session is assigned to a profile by rendezvous hashing of its session id — **deterministic and stateless**, so assignments survive proxy restarts with no state to lose
303
+ - Adding/removing a profile only reassigns the sessions belonging to the changed arm — everything else keeps its warm cache
304
+ - A session's subagent/fork requests share its assignment (same session id → same account)
305
+ - The `x-meridian-profile` header still overrides everything, per request
306
+ - Default is `active` (all traffic to the active profile — the pre-existing behavior); sticky is opt-in
307
+
308
+ Request logs show the assignment (`profile=work(sticky)`), and `GET /profiles/list` reports the current `routing` mode.
309
+
291
310
  ### Profile commands
292
311
 
293
312
  | Command | Description |
@@ -331,6 +350,11 @@ Profile shapes:
331
350
 
332
351
  When `MERIDIAN_PROFILES` is set, it takes precedence over disk-configured profiles. When unset, Meridian auto-discovers profiles from `~/.config/meridian/profiles.json` on each request.
333
352
 
353
+ Related environment variables:
354
+
355
+ - `MERIDIAN_ROUTING=sticky` — enable [sticky session routing](#sticky-session-routing) across profiles (default `active`)
356
+ - `MERIDIAN_ADAPTER_INSTANCES='{...}'` — define [adapter instances](#adapter-instances) inline instead of via `~/.config/meridian/adapter-instances.json`
357
+
334
358
  ## Agent Setup
335
359
 
336
360
  ### OpenCode
@@ -580,6 +604,27 @@ Requests flow through the Claude Code adapter which:
580
604
  - Leaves the SDK subprocess cwd on the proxy host (Claude Code's local paths don't exist there).
581
605
  - Runs in passthrough mode by default — Claude Code executes its own tools on the machine it runs on; Meridian just forwards tool_use blocks.
582
606
 
607
+ ### Adapter instances
608
+
609
+ Run several configurations of the same adapter side by side — e.g. a passthrough variant with thinking enabled and one without, or a dedicated config for a specific client. Define instances in `~/.config/meridian/adapter-instances.json` (or the `MERIDIAN_ADAPTER_INSTANCES` env var as a JSON string):
610
+
611
+ ```jsonc
612
+ {
613
+ "oc-thinky": { "base": "opencode", "features": { "thinking": "enabled" } },
614
+ "lite-plain": { "base": "passthrough", "passthrough": true,
615
+ "match": { "userAgentPrefix": "litellm/" } },
616
+ "team-webui": { "base": "opencode", "features": { "codeSystemPrompt": false },
617
+ "match": { "header": { "x-team": "alpha" } } }
618
+ }
619
+ ```
620
+
621
+ - **`base`** — which built-in adapter provides the behavior (tool handling, session tracking, transforms). Existing plugins and transforms scoped to the base adapter apply to its instances automatically.
622
+ - **`features`** — per-instance overrides of the [SDK feature toggles](#sdk-feature-toggles-experimental) (thinking, system prompts, memory, ...) layered over the base's settings. Same keys as the settings UI.
623
+ - **`passthrough`** — per-instance passthrough mode, overriding the adapter default and `MERIDIAN_PASSTHROUGH`.
624
+ - **`match`** — optional automatic selection: exact header values and/or a User-Agent prefix. Match rules outrank built-in User-Agent detection (that's their purpose). Without `match`, select the instance per request with `x-meridian-agent: <instance-name>`.
625
+
626
+ Built-in adapter names are reserved and can't be shadowed. With no instances configured, detection is exactly the built-in chain. Config file changes apply within ~5s, no restart needed.
627
+
583
628
  ### Any Anthropic-compatible tool
584
629
 
585
630
  ```bash
@@ -7,6 +7,32 @@ import {
7
7
  import { existsSync, readFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { homedir } from "node:os";
10
+
11
+ // src/proxy/routing.ts
12
+ import { createHash } from "node:crypto";
13
+ function getRoutingMode(raw) {
14
+ return raw?.toLowerCase() === "sticky" ? "sticky" : "active";
15
+ }
16
+ function rendezvousScore(sessionKey, profileId) {
17
+ const digest = createHash("sha256").update(`${sessionKey}\x00${profileId}`).digest();
18
+ return digest.readBigUInt64BE(0);
19
+ }
20
+ function pickStickyProfile(sessionKey, profileIds) {
21
+ if (!sessionKey || profileIds.length === 0)
22
+ return;
23
+ let best;
24
+ let bestScore = -1n;
25
+ for (const id of profileIds) {
26
+ const score = rendezvousScore(sessionKey, id);
27
+ if (score > bestScore) {
28
+ bestScore = score;
29
+ best = id;
30
+ }
31
+ }
32
+ return best;
33
+ }
34
+
35
+ // src/proxy/profiles.ts
10
36
  var CONFIG_FILE = join(homedir(), ".config", "meridian", "profiles.json");
11
37
  var DISK_CACHE_TTL_MS = 5000;
12
38
  var diskProfilesCache = [];
@@ -72,12 +98,13 @@ function getEffectiveProfiles(configProfiles) {
72
98
  function hasProfiles(configProfiles) {
73
99
  return getEffectiveProfiles(configProfiles).length > 0;
74
100
  }
75
- function resolveProfile(profiles, defaultProfile, requestedId) {
101
+ function resolveProfile(profiles, defaultProfile, requestedId, options) {
76
102
  const effective = getEffectiveProfiles(profiles);
77
103
  if (effective.length === 0) {
78
104
  return { id: DEFAULT_PROFILE_ID, type: "claude-max", env: {} };
79
105
  }
80
- const resolvedId = requestedId || activeProfileId || defaultProfile || effective[0].id;
106
+ const stickyId = options?.routingMode === "sticky" && options.stickySessionKey ? pickStickyProfile(options.stickySessionKey, effective.map((p) => p.id)) : undefined;
107
+ const resolvedId = requestedId || stickyId || activeProfileId || defaultProfile || effective[0].id;
81
108
  const profile = effective.find((p) => p.id === resolvedId);
82
109
  if (!profile) {
83
110
  console.warn(`[meridian] Unknown profile "${resolvedId}". Using first configured profile.`);
@@ -120,4 +147,4 @@ function listProfiles(profiles, defaultProfile) {
120
147
  }));
121
148
  }
122
149
 
123
- export { loadProfilesFromDisk, setActiveProfile, getActiveProfileId, resetActiveProfile, restoreActiveProfile, enableDiskProfileDiscovery, getEffectiveProfiles, hasProfiles, resolveProfile, listProfiles };
150
+ export { getRoutingMode, loadProfilesFromDisk, setActiveProfile, getActiveProfileId, resetActiveProfile, restoreActiveProfile, enableDiskProfileDiscovery, getEffectiveProfiles, hasProfiles, resolveProfile, listProfiles };
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  getActiveProfileId,
3
3
  getEffectiveProfiles,
4
+ getRoutingMode,
4
5
  listProfiles,
5
6
  resolveProfile,
6
7
  restoreActiveProfile,
7
8
  setActiveProfile
8
- } from "./cli-cx463q74.js";
9
+ } from "./cli-f0yqy2d2.js";
9
10
  import {
10
11
  isTrackedPlugin,
11
12
  recordError,
@@ -36,6 +37,9 @@ import {
36
37
  resolveSdkModelDefaults,
37
38
  stripExtendedContext
38
39
  } from "./cli-q6dz9r5p.js";
40
+ import {
41
+ getSetting
42
+ } from "./cli-340h1chz.js";
39
43
  import {
40
44
  checkPluginConfigured
41
45
  } from "./cli-je60fevk.js";
@@ -1198,14 +1202,14 @@ __export(exports_sdkFeatures, {
1198
1202
  getExplicitThinking: () => getExplicitThinking,
1199
1203
  getAllFeatureConfigs: () => getAllFeatureConfigs
1200
1204
  });
1201
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
1202
- import { join as join4 } from "node:path";
1203
- import { homedir as homedir3 } from "node:os";
1205
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
1206
+ import { join as join5 } from "node:path";
1207
+ import { homedir as homedir4 } from "node:os";
1204
1208
  function getConfigPath() {
1205
- const dir = join4(homedir3(), ".config", "meridian");
1206
- if (!existsSync5(dir))
1209
+ const dir = join5(homedir4(), ".config", "meridian");
1210
+ if (!existsSync6(dir))
1207
1211
  mkdirSync2(dir, { recursive: true });
1208
- return join4(dir, "sdk-features.json");
1212
+ return join5(dir, "sdk-features.json");
1209
1213
  }
1210
1214
  function readConfig() {
1211
1215
  const now = Date.now();
@@ -1213,8 +1217,8 @@ function readConfig() {
1213
1217
  return cachedConfig;
1214
1218
  const path3 = getConfigPath();
1215
1219
  try {
1216
- if (existsSync5(path3)) {
1217
- cachedConfig = JSON.parse(readFileSync4(path3, "utf-8"));
1220
+ if (existsSync6(path3)) {
1221
+ cachedConfig = JSON.parse(readFileSync5(path3, "utf-8"));
1218
1222
  } else {
1219
1223
  cachedConfig = {};
1220
1224
  }
@@ -3738,8 +3742,8 @@ var serve = (options, listeningListener) => {
3738
3742
  };
3739
3743
 
3740
3744
  // src/proxy/server.ts
3741
- import { homedir as homedir4 } from "node:os";
3742
- import { join as join5 } from "node:path";
3745
+ import { homedir as homedir5 } from "node:os";
3746
+ import { join as join6 } from "node:path";
3743
3747
  import { query } from "@anthropic-ai/claude-agent-sdk";
3744
3748
 
3745
3749
  // src/proxy/rateLimitStore.ts
@@ -11249,6 +11253,87 @@ var cherryAdapter = {
11249
11253
  }
11250
11254
  };
11251
11255
 
11256
+ // src/proxy/adapterInstances.ts
11257
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
11258
+ import { join as join2 } from "node:path";
11259
+ import { homedir as homedir2 } from "node:os";
11260
+ var CONFIG_FILE = join2(homedir2(), ".config", "meridian", "adapter-instances.json");
11261
+ function parseAdapterInstances(raw2) {
11262
+ if (!raw2)
11263
+ return {};
11264
+ let parsed;
11265
+ try {
11266
+ parsed = JSON.parse(raw2);
11267
+ } catch {
11268
+ console.warn("[meridian] adapter-instances config is not valid JSON — ignoring");
11269
+ return {};
11270
+ }
11271
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
11272
+ return {};
11273
+ const out = {};
11274
+ for (const [name, value] of Object.entries(parsed)) {
11275
+ const v = value;
11276
+ if (!v || typeof v !== "object" || typeof v.base !== "string" || v.base.length === 0) {
11277
+ console.warn(`[meridian] adapter instance "${name}" has no valid "base" — ignoring`);
11278
+ continue;
11279
+ }
11280
+ if (v.features !== undefined && (typeof v.features !== "object" || v.features === null || Array.isArray(v.features))) {
11281
+ console.warn(`[meridian] adapter instance "${name}" has invalid "features" — ignoring`);
11282
+ continue;
11283
+ }
11284
+ if (v.match !== undefined && (typeof v.match !== "object" || v.match === null || Array.isArray(v.match))) {
11285
+ console.warn(`[meridian] adapter instance "${name}" has invalid "match" — ignoring`);
11286
+ continue;
11287
+ }
11288
+ out[name] = {
11289
+ base: v.base,
11290
+ ...v.features ? { features: v.features } : {},
11291
+ ...typeof v.passthrough === "boolean" ? { passthrough: v.passthrough } : {},
11292
+ ...v.match ? { match: v.match } : {}
11293
+ };
11294
+ }
11295
+ return out;
11296
+ }
11297
+ var DISK_CACHE_TTL_MS = 5000;
11298
+ var diskCache = {};
11299
+ var diskCacheAt = 0;
11300
+ function loadAdapterInstances() {
11301
+ const fromEnv = process.env.MERIDIAN_ADAPTER_INSTANCES;
11302
+ if (fromEnv)
11303
+ return parseAdapterInstances(fromEnv);
11304
+ if (diskCacheAt > 0 && Date.now() - diskCacheAt < DISK_CACHE_TTL_MS)
11305
+ return diskCache;
11306
+ try {
11307
+ diskCache = existsSync3(CONFIG_FILE) ? parseAdapterInstances(readFileSync2(CONFIG_FILE, "utf-8")) : {};
11308
+ } catch (err) {
11309
+ console.warn(`[meridian] Failed to read ${CONFIG_FILE}: ${err instanceof Error ? err.message : err}`);
11310
+ diskCache = {};
11311
+ }
11312
+ diskCacheAt = Date.now();
11313
+ return diskCache;
11314
+ }
11315
+ function matchesInstance(def, getHeader) {
11316
+ const match2 = def.match;
11317
+ if (!match2)
11318
+ return false;
11319
+ const hasHeaderRules = match2.header !== undefined && Object.keys(match2.header).length > 0;
11320
+ const hasUaRule = typeof match2.userAgentPrefix === "string" && match2.userAgentPrefix.length > 0;
11321
+ if (!hasHeaderRules && !hasUaRule)
11322
+ return false;
11323
+ if (hasHeaderRules) {
11324
+ for (const [name, want] of Object.entries(match2.header)) {
11325
+ if (getHeader(name.toLowerCase()) !== want)
11326
+ return false;
11327
+ }
11328
+ }
11329
+ if (hasUaRule) {
11330
+ const ua = getHeader("user-agent") ?? "";
11331
+ if (!ua.startsWith(match2.userAgentPrefix))
11332
+ return false;
11333
+ }
11334
+ return true;
11335
+ }
11336
+
11252
11337
  // src/proxy/adapters/detect.ts
11253
11338
  var ADAPTER_MAP = {
11254
11339
  opencode: openCodeAdapter,
@@ -11274,11 +11359,41 @@ function isLiteLLMRequest(c) {
11274
11359
  const headers = c.req.header();
11275
11360
  return Object.keys(headers).some((k) => k.toLowerCase().startsWith("x-litellm-"));
11276
11361
  }
11362
+ function makeInstanceAdapter(name, def) {
11363
+ const base = ADAPTER_MAP[def.base.toLowerCase()];
11364
+ if (!base) {
11365
+ console.warn(`[meridian] adapter instance "${name}" references unknown base "${def.base}" — ignoring`);
11366
+ return;
11367
+ }
11368
+ return {
11369
+ ...base,
11370
+ name,
11371
+ baseName: base.name,
11372
+ ...def.features ? { instanceFeatures: def.features } : {},
11373
+ ...def.passthrough !== undefined ? { instancePassthrough: def.passthrough } : {}
11374
+ };
11375
+ }
11277
11376
  function detectAdapter(c) {
11278
11377
  const agentOverride = c.req.header("x-meridian-agent")?.toLowerCase();
11279
11378
  if (agentOverride && ADAPTER_MAP[agentOverride]) {
11280
11379
  return ADAPTER_MAP[agentOverride];
11281
11380
  }
11381
+ const instances = loadAdapterInstances();
11382
+ const instanceNames = Object.keys(instances);
11383
+ if (instanceNames.length > 0) {
11384
+ if (agentOverride && instances[agentOverride]) {
11385
+ const inst = makeInstanceAdapter(agentOverride, instances[agentOverride]);
11386
+ if (inst)
11387
+ return inst;
11388
+ }
11389
+ for (const name of instanceNames) {
11390
+ if (matchesInstance(instances[name], (h) => c.req.header(h))) {
11391
+ const inst = makeInstanceAdapter(name, instances[name]);
11392
+ if (inst)
11393
+ return inst;
11394
+ }
11395
+ }
11396
+ }
11282
11397
  if (c.req.header("x-opencode-session") || c.req.header("x-session-affinity")) {
11283
11398
  return openCodeAdapter;
11284
11399
  }
@@ -17219,8 +17334,8 @@ function getAdapterTransforms(adapterName) {
17219
17334
  }
17220
17335
 
17221
17336
  // src/proxy/plugins/loader.ts
17222
- import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
17223
- import { join as join2, isAbsolute as isAbsolute2, extname } from "path";
17337
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
17338
+ import { join as join3, isAbsolute as isAbsolute2, extname } from "path";
17224
17339
  import { pathToFileURL } from "url";
17225
17340
 
17226
17341
  // src/proxy/plugins/validation.ts
@@ -17261,10 +17376,10 @@ function validateTransform(exported) {
17261
17376
  // src/proxy/plugins/loader.ts
17262
17377
  var loadCounter = 0;
17263
17378
  function parsePluginConfig(configPath) {
17264
- if (!existsSync3(configPath))
17379
+ if (!existsSync4(configPath))
17265
17380
  return [];
17266
17381
  try {
17267
- const raw2 = readFileSync2(configPath, "utf-8");
17382
+ const raw2 = readFileSync3(configPath, "utf-8");
17268
17383
  const parsed = JSON.parse(raw2);
17269
17384
  return Array.isArray(parsed.plugins) ? parsed.plugins : [];
17270
17385
  } catch {
@@ -17274,7 +17389,7 @@ function parsePluginConfig(configPath) {
17274
17389
  async function loadPlugins(pluginDir, configPath) {
17275
17390
  resetAllPluginStats();
17276
17391
  const config = configPath ? parsePluginConfig(configPath) : [];
17277
- const pluginDirExists = existsSync3(pluginDir);
17392
+ const pluginDirExists = existsSync4(pluginDir);
17278
17393
  let filenames = [];
17279
17394
  if (pluginDirExists) {
17280
17395
  try {
@@ -17305,7 +17420,7 @@ async function loadPlugins(pluginDir, configPath) {
17305
17420
  const loaded = [];
17306
17421
  const seenNames = new Set;
17307
17422
  for (const { filename, entry } of ordered) {
17308
- const filePath = isAbsolute2(filename) ? filename : join2(pluginDir, filename);
17423
+ const filePath = isAbsolute2(filename) ? filename : join3(pluginDir, filename);
17309
17424
  if (entry && !entry.enabled) {
17310
17425
  loaded.push({
17311
17426
  name: filename,
@@ -17678,17 +17793,17 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
17678
17793
  // src/proxy/sessionStore.ts
17679
17794
  import {
17680
17795
  closeSync,
17681
- existsSync as existsSync4,
17796
+ existsSync as existsSync5,
17682
17797
  mkdirSync,
17683
17798
  openSync,
17684
- readFileSync as readFileSync3,
17799
+ readFileSync as readFileSync4,
17685
17800
  renameSync,
17686
17801
  statSync,
17687
17802
  unlinkSync,
17688
17803
  writeFileSync
17689
17804
  } from "node:fs";
17690
- import { homedir as homedir2 } from "node:os";
17691
- import { join as join3 } from "node:path";
17805
+ import { homedir as homedir3 } from "node:os";
17806
+ import { join as join4 } from "node:path";
17692
17807
  var DEFAULT_MAX_STORED_SESSIONS = 1e4;
17693
17808
  var STALE_LOCK_THRESHOLD_MS = 30000;
17694
17809
  function getMaxStoredSessions() {
@@ -17736,17 +17851,17 @@ var sessionDirOverride = null;
17736
17851
  var skipLocking = false;
17737
17852
  function getStorePath() {
17738
17853
  const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
17739
- if (!existsSync4(dir)) {
17854
+ if (!existsSync5(dir)) {
17740
17855
  mkdirSync(dir, { recursive: true });
17741
17856
  }
17742
- return join3(dir, "sessions.json");
17857
+ return join4(dir, "sessions.json");
17743
17858
  }
17744
17859
  function getDefaultCacheDir() {
17745
- const newDir = join3(homedir2(), ".cache", "meridian");
17746
- const oldDir = join3(homedir2(), ".cache", "opencode-claude-max-proxy");
17747
- if (existsSync4(newDir))
17860
+ const newDir = join4(homedir3(), ".cache", "meridian");
17861
+ const oldDir = join4(homedir3(), ".cache", "opencode-claude-max-proxy");
17862
+ if (existsSync5(newDir))
17748
17863
  return newDir;
17749
- if (existsSync4(oldDir)) {
17864
+ if (existsSync5(oldDir)) {
17750
17865
  try {
17751
17866
  const { symlinkSync } = __require("fs");
17752
17867
  symlinkSync(oldDir, newDir);
@@ -17759,10 +17874,10 @@ function getDefaultCacheDir() {
17759
17874
  }
17760
17875
  function readStore() {
17761
17876
  const path3 = getStorePath();
17762
- if (!existsSync4(path3))
17877
+ if (!existsSync5(path3))
17763
17878
  return {};
17764
17879
  try {
17765
- const data = readFileSync3(path3, "utf-8");
17880
+ const data = readFileSync4(path3, "utf-8");
17766
17881
  return JSON.parse(data);
17767
17882
  } catch (e) {
17768
17883
  console.error("[sessionStore] read failed:", e.message);
@@ -18225,9 +18340,11 @@ function buildFreshPrompt(messages, sanitizeOpts = {}) {
18225
18340
  }();
18226
18341
  }
18227
18342
  return messages.map((m) => {
18228
- const role = m.role === "assistant" ? "Assistant" : "Human";
18229
- const content = m.role === "assistant" ? flattenAssistantContent(m.content) : flattenUserContent(m.content, sanitizeOpts, toolIndex);
18230
- return content ? `${role}: ${content}` : "";
18343
+ if (m.role === "assistant") {
18344
+ const assistantText = flattenAssistantContent(m.content);
18345
+ return assistantText ? `[Assistant: ${assistantText}]` : "";
18346
+ }
18347
+ return flattenUserContent(m.content, sanitizeOpts, toolIndex);
18231
18348
  }).filter(Boolean).join(`
18232
18349
 
18233
18350
  `) || "";
@@ -18291,8 +18408,8 @@ function createProxyServer(config = {}) {
18291
18408
  const sessionDiscoveredTools = new Map;
18292
18409
  const sessionToolCache = new Map;
18293
18410
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
18294
- const pluginDir = finalConfig.pluginDir ?? join5(homedir4(), ".config", "meridian", "plugins");
18295
- const pluginConfigPath = finalConfig.pluginConfigPath ?? join5(homedir4(), ".config", "meridian", "plugins.json");
18411
+ const pluginDir = finalConfig.pluginDir ?? join6(homedir5(), ".config", "meridian", "plugins");
18412
+ const pluginConfigPath = finalConfig.pluginConfigPath ?? join6(homedir5(), ".config", "meridian", "plugins.json");
18296
18413
  let loadedPlugins = [];
18297
18414
  let pluginTransforms = [];
18298
18415
  const app = new Hono2;
@@ -18374,7 +18491,8 @@ function createProxyServer(config = {}) {
18374
18491
  return c.json({ type: "error", error: { type: "invalid_request_error", message: parsedOutputFormat.message } }, 400);
18375
18492
  }
18376
18493
  const outputFormat = parsedOutputFormat.value;
18377
- const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined);
18494
+ const routingMode = getRoutingMode(process.env.MERIDIAN_ROUTING ?? getSetting("routing"));
18495
+ const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined, routingMode === "sticky" ? { routingMode, stickySessionKey: adapter.getSessionId(c, body) } : undefined);
18378
18496
  const authStatus = await getClaudeAuthStatusAsync(profile.id !== "default" ? profile.id : undefined, Object.keys(profile.env).length > 0 ? profile.env : undefined);
18379
18497
  const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
18380
18498
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
@@ -18414,10 +18532,11 @@ function createProxyServer(config = {}) {
18414
18532
  `);
18415
18533
  }
18416
18534
  }
18417
- const adapterTransforms = getAdapterTransforms(adapter.name);
18535
+ const adapterBase = adapter.baseName ?? adapter.name;
18536
+ const adapterTransforms = getAdapterTransforms(adapterBase);
18418
18537
  const pipeline = buildPipeline(adapterTransforms, pluginTransforms);
18419
18538
  const pipelineCtx = runTransformHook(pipeline, "onRequest", createRequestContext({
18420
- adapter: adapter.name,
18539
+ adapter: adapterBase,
18421
18540
  body,
18422
18541
  headers: c.req.raw.headers,
18423
18542
  model,
@@ -18426,7 +18545,7 @@ function createProxyServer(config = {}) {
18426
18545
  tools: body.tools,
18427
18546
  stream: body.stream ?? false,
18428
18547
  workingDirectory
18429
- }), adapter.name);
18548
+ }), adapterBase);
18430
18549
  const stream2 = pipelineCtx.prefersStreaming !== undefined ? pipelineCtx.prefersStreaming : body.stream ?? false;
18431
18550
  const effortHeader = c.req.header("x-opencode-effort");
18432
18551
  const thinkingHeader = c.req.header("x-opencode-thinking");
@@ -18446,8 +18565,8 @@ function createProxyServer(config = {}) {
18446
18565
  }
18447
18566
  }
18448
18567
  const { getFeaturesForAdapter: getFeaturesForAdapter2, getExplicitThinking: getExplicitThinking2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
18449
- const sdkFeatures = getFeaturesForAdapter2(adapter.name);
18450
- if (getExplicitThinking2(adapter.name) === "disabled") {
18568
+ const sdkFeatures = { ...getFeaturesForAdapter2(adapterBase), ...adapter.instanceFeatures ?? {} };
18569
+ if ((adapter.instanceFeatures?.thinking ?? getExplicitThinking2(adapterBase)) === "disabled") {
18451
18570
  thinking = { type: "disabled" };
18452
18571
  effort = undefined;
18453
18572
  plog(`[PROXY] ${requestMeta.requestId} thinking disabled (per-adapter setting)`);
@@ -18473,10 +18592,10 @@ function createProxyServer(config = {}) {
18473
18592
  const profileScopedCwd = profile.id !== "default" ? `${clientWorkingDirectory}::profile=${profile.id}` : clientWorkingDirectory;
18474
18593
  const lastMessage = Array.isArray(body.messages) ? body.messages[body.messages.length - 1] : undefined;
18475
18594
  const lastIsToolResult = Array.isArray(lastMessage?.content) && lastMessage.content.some((b) => b?.type === "tool_result");
18476
- const isClientDrivenLoop = adapter.name !== "claude-code" && !agentSessionId && lastIsToolResult;
18595
+ const isClientDrivenLoop = adapterBase !== "claude-code" && !agentSessionId && lastIsToolResult;
18477
18596
  const isIndependentSession = requestSource?.startsWith("fork-") || requestSource?.startsWith("subagent-") || isClientDrivenLoop || false;
18478
18597
  let lineageResult = isIndependentSession ? { type: "diverged" } : lookupSession(profileSessionId, body.messages || [], profileScopedCwd);
18479
- if (lineageResult.type === "undo" && adapter.name === "opencode" && !agentSessionId) {
18598
+ if (lineageResult.type === "undo" && adapterBase === "opencode" && !agentSessionId) {
18480
18599
  lineageResult = { type: "diverged" };
18481
18600
  }
18482
18601
  const isResume = lineageResult.type === "continuation" || lineageResult.type === "compaction";
@@ -18491,7 +18610,7 @@ function createProxyServer(config = {}) {
18491
18610
  const lineageType = lineageResult.type === "diverged" && !cachedSession ? "new" : lineageResult.type;
18492
18611
  const msgCount = Array.isArray(body.messages) ? body.messages.length : 0;
18493
18612
  const toolCount = body.tools?.length ?? 0;
18494
- const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name}${requestSource ? ` source=${requestSource}` : ""} model=${model} stream=${stream2} tools=${toolCount} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
18613
+ const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name}${requestSource ? ` source=${requestSource}` : ""}${profile.id !== "default" ? ` profile=${profile.id}${routingMode === "sticky" ? "(sticky)" : ""}` : ""} model=${model} stream=${stream2} tools=${toolCount} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
18495
18614
  plog(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
18496
18615
  diagnosticLog2.session(`${requestLogLine}`, requestMeta.requestId);
18497
18616
  if (lineageResult.type === "diverged" && profileSessionId && !isIndependentSession) {
@@ -18578,18 +18697,23 @@ function createProxyServer(config = {}) {
18578
18697
  } else {
18579
18698
  const toolIndex = buildToolUseIndex(allMessages ?? messagesToConvert ?? []);
18580
18699
  textPrompt = messagesToConvert?.map((m) => {
18581
- const role = m.role === "assistant" ? "Assistant" : "Human";
18582
- const content = m.role === "assistant" ? flattenAssistantContent(m.content) : flattenUserContent(m.content, sanitizeOpts, toolIndex);
18583
- return content ? `${role}: ${content}` : "";
18700
+ if (m.role === "assistant") {
18701
+ if (isResume)
18702
+ return "";
18703
+ const assistantText = flattenAssistantContent(m.content);
18704
+ return assistantText ? `[Assistant: ${assistantText}]` : "";
18705
+ }
18706
+ return flattenUserContent(m.content, sanitizeOpts, toolIndex);
18584
18707
  }).filter(Boolean).join(`
18585
18708
 
18586
18709
  `) || "";
18587
18710
  }
18588
- const passthrough = pipelineCtx.passthrough !== undefined ? pipelineCtx.passthrough : envBool("PASSTHROUGH");
18711
+ const passthrough = adapter.instancePassthrough !== undefined ? adapter.instancePassthrough : pipelineCtx.passthrough !== undefined ? pipelineCtx.passthrough : envBool("PASSTHROUGH");
18589
18712
  const settingSources = envBool("LOAD_CONTEXT") || sdkFeatures.claudeMd === "full" ? ["user", "project"] : sdkFeatures.claudeMd === "project" ? ["project"] : pipelineCtx.settingSources ?? [];
18590
18713
  const capturedToolUses = [];
18591
18714
  const capturedSignatures = new Set;
18592
18715
  const capturedToolNames = new Set;
18716
+ const droppedToolUseIds = new Set;
18593
18717
  let sawDuplicateToolUse = false;
18594
18718
  const earlyStopEnabled = passthrough && process.env.MERIDIAN_PASSTHROUGH_EARLY_STOP !== "0";
18595
18719
  const earlyStop = createEarlyStopTracker();
@@ -18657,11 +18781,13 @@ function createProxyServer(config = {}) {
18657
18781
  }
18658
18782
  const signature = toolUseSignature(toolName, toolInput);
18659
18783
  const isExactDuplicate = capturedSignatures.has(signature);
18660
- const isSameToolRepeat = !isExactDuplicate && capturedToolNames.has(toolName);
18784
+ const isSameToolRepeat = !earlyStopEnabled && !isExactDuplicate && capturedToolNames.has(toolName);
18661
18785
  const exceedsForcedSingle = forceSingleToolUse && capturedToolUses.length >= 1;
18662
18786
  if (isExactDuplicate) {
18787
+ droppedToolUseIds.add(input.tool_use_id);
18663
18788
  claudeLog("passthrough.duplicate_tool_use_dropped", { name: toolName });
18664
18789
  } else if (isSameToolRepeat || exceedsForcedSingle) {
18790
+ droppedToolUseIds.add(input.tool_use_id);
18665
18791
  sawDuplicateToolUse = true;
18666
18792
  claudeLog("passthrough.extra_tool_use_dropped", {
18667
18793
  name: toolName,
@@ -19091,6 +19217,14 @@ Subprocess stderr: ${stderrOutput}`;
19091
19217
  });
19092
19218
  }
19093
19219
  if (passthrough && capturedToolUses.length > 0) {
19220
+ if (droppedToolUseIds.size > 0) {
19221
+ for (let i = contentBlocks.length - 1;i >= 0; i--) {
19222
+ const b = contentBlocks[i];
19223
+ if (b.type === "tool_use" && droppedToolUseIds.has(b.id)) {
19224
+ contentBlocks.splice(i, 1);
19225
+ }
19226
+ }
19227
+ }
19094
19228
  const capturedById = new Map(capturedToolUses.map((tu) => [tu.id, tu]));
19095
19229
  for (const block of contentBlocks) {
19096
19230
  if (block.type === "tool_use" && capturedById.has(block.id)) {
@@ -20320,7 +20454,8 @@ data: ${JSON.stringify({
20320
20454
  }));
20321
20455
  return c.json({
20322
20456
  profiles: enriched,
20323
- activeProfile: getActiveProfileId() || finalConfig.defaultProfile || profiles[0]?.id || "default"
20457
+ activeProfile: getActiveProfileId() || finalConfig.defaultProfile || profiles[0]?.id || "default",
20458
+ routing: getRoutingMode(process.env.MERIDIAN_ROUTING ?? getSetting("routing"))
20324
20459
  });
20325
20460
  });
20326
20461
  app.get("/profiles", async (c) => {
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-vhrprwj4.js";
5
- import"./cli-cx463q74.js";
4
+ } from "./cli-mjrt119g.js";
5
+ import"./cli-f0yqy2d2.js";
6
6
  import"./cli-sry5aqdj.js";
7
7
  import"./cli-4rqtm83g.js";
8
8
  import {
@@ -167,7 +167,7 @@ async function runCli(start = startProxyServer, runAuthCheck = async () => {
167
167
  console.error("\x1B[33m⚠ Could not verify Claude auth status. If requests fail, run: claude login\x1B[0m");
168
168
  }
169
169
  if (!profiles) {
170
- const { enableDiskProfileDiscovery } = await import("./profiles-rdd84b45.js");
170
+ const { enableDiskProfileDiscovery } = await import("./profiles-84hnbd6c.js");
171
171
  enableDiskProfileDiscovery();
172
172
  }
173
173
  const proxy = await start({ port, host, idleTimeoutSeconds, profiles, defaultProfile, version, installProcessErrorHandlers: true });
@@ -9,7 +9,7 @@ import {
9
9
  resolveProfile,
10
10
  restoreActiveProfile,
11
11
  setActiveProfile
12
- } from "./cli-cx463q74.js";
12
+ } from "./cli-f0yqy2d2.js";
13
13
  import"./cli-340h1chz.js";
14
14
  import"./cli-p9swy5t3.js";
15
15
  export {
@@ -67,6 +67,18 @@ export interface AgentIdentity {
67
67
  * how to interact with the calling agent.
68
68
  */
69
69
  export interface AgentAdapter extends AgentIdentity {
70
+ /**
71
+ * For adapter INSTANCES (#476): the base adapter's name. Behavior keyed by
72
+ * adapter name — transforms, plugin scoping, agent-specific branches —
73
+ * must resolve via `baseName ?? name` so existing transforms/plugins keep
74
+ * applying to instances. Features resolve by instance definition instead.
75
+ * Undefined for built-in adapters.
76
+ */
77
+ readonly baseName?: string;
78
+ /** Instance feature overrides, layered over the base's resolved features. */
79
+ readonly instanceFeatures?: Partial<import("./sdkFeatures").AdapterFeatures>;
80
+ /** Instance passthrough override — beats the adapter transform's default. */
81
+ readonly instancePassthrough?: boolean;
70
82
  /**
71
83
  * SDK built-in tools to block (replaced by MCP equivalents).
72
84
  * These are tools where the agent provides its own implementation.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAEnE;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5D;;;;;;;;;;;;;;;;OAgBG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;;;;;;;;;OAWG;IACH,6BAA6B,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAE7D;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAAE,CAAA;IAEtC;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,IAAI,aAAa,EAAE,CAAA;IAErC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,OAAO,CAAA;IAE5B;;;;;;OAMG;IACH,sBAAsB,CAAC,IAAI,OAAO,CAAA;IAElC;;;;;;;;OAQG;IACH,yBAAyB,CAAC,IAAI,OAAO,CAAA;IAErC;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/proxy/adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAEnE;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAE5D;;;;;;;;;;;;;;;;OAgBG;IACH,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAEtD;;;;;;;;;;;OAWG;IACH,6BAA6B,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;IAE7D;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAAA;IAEtC;;;OAGG;IACH,gBAAgB,IAAI,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAE1B,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC,OAAO,eAAe,EAAE,eAAe,CAAC,CAAA;IAE5E,6EAA6E;IAC7E,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAEtC;;;OAGG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE3C;;;;OAIG;IACH,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAAA;IAE9C;;OAEG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAAA;IAEvC;;;;OAIG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEhF;;;OAGG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAA;IAE9D;;;OAGG;IACH,0BAA0B,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAA;IAE9E;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAA;IAErC;;;;;;;;OAQG;IACH,eAAe,CAAC,IAAI,OAAO,CAAA;IAE3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAAE,CAAA;IAEtC;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,IAAI,aAAa,EAAE,CAAA;IAErC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,OAAO,CAAA;IAE5B;;;;;;OAMG;IACH,sBAAsB,CAAC,IAAI,OAAO,CAAA;IAElC;;;;;;;;OAQG;IACH,yBAAyB,CAAC,IAAI,OAAO,CAAA;IAErC;;;;;;;;;;;;;;;OAeG;IACH,6BAA6B,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,eAAe,EAAE,UAAU,EAAE,CAAA;CAC3G"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Adapter instances (#476, proposed by @Serpentiel).
3
+ *
4
+ * An instance is a named configuration of a base adapter:
5
+ *
6
+ * {
7
+ * "oc-thinky": { "base": "opencode", "features": { "thinking": "enabled" } },
8
+ * "lite-plain": { "base": "passthrough", "passthrough": true,
9
+ * "match": { "userAgentPrefix": "litellm/" } }
10
+ * }
11
+ *
12
+ * Selected explicitly via `x-meridian-agent: <instance-name>`, or
13
+ * automatically via match rules (exact header values and/or a User-Agent
14
+ * prefix). Instances resolve BEHAVIOR (transforms, plugin scoping, session
15
+ * handling) by their base adapter's name and FEATURES by their own
16
+ * definition — see detect.ts and server.ts for the resolution invariant.
17
+ *
18
+ * Config sources (first wins):
19
+ * 1. MERIDIAN_ADAPTER_INSTANCES env var (JSON string)
20
+ * 2. ~/.config/meridian/adapter-instances.json (5s TTL cache)
21
+ *
22
+ * With no instances configured, adapter detection is byte-identical to the
23
+ * built-in chain. Built-in adapter names cannot be shadowed.
24
+ *
25
+ * This is a leaf module — no imports from server.ts, adapters, or session/.
26
+ */
27
+ import type { AdapterFeatures } from "./sdkFeatures";
28
+ export interface AdapterInstanceMatch {
29
+ /** Exact header values — ALL listed headers must match (case-insensitive names). */
30
+ header?: Record<string, string>;
31
+ /** User-Agent prefix match. */
32
+ userAgentPrefix?: string;
33
+ }
34
+ export interface AdapterInstanceDef {
35
+ /** Base adapter name (opencode, passthrough, crush, ...). Behavior comes from here. */
36
+ base: string;
37
+ /** Per-instance feature overrides, layered over the base's resolved features. */
38
+ features?: Partial<AdapterFeatures>;
39
+ /** Per-instance passthrough override — beats the adapter transform's default. */
40
+ passthrough?: boolean;
41
+ /** Automatic selection rules. Omit to select only via x-meridian-agent. */
42
+ match?: AdapterInstanceMatch;
43
+ }
44
+ export type AdapterInstanceMap = Record<string, AdapterInstanceDef>;
45
+ /**
46
+ * Parse and validate an instance map from raw JSON. Malformed input and
47
+ * invalid entries are dropped (with a warning) rather than crashing —
48
+ * a config typo must never take down adapter detection.
49
+ */
50
+ export declare function parseAdapterInstances(raw: string | undefined): AdapterInstanceMap;
51
+ /**
52
+ * Load the configured instance map. Env var wins over the disk file.
53
+ * Returns {} when nothing is configured — the common case, and the
54
+ * guarantee that unconfigured setups keep byte-identical detection.
55
+ */
56
+ export declare function loadAdapterInstances(): AdapterInstanceMap;
57
+ /**
58
+ * Does a request match an instance's rules? No rules = never auto-matched
59
+ * (explicit x-meridian-agent selection only). When both header and UA rules
60
+ * are present, both must match.
61
+ */
62
+ export declare function matchesInstance(def: AdapterInstanceDef, getHeader: (name: string) => string | undefined): boolean;
63
+ //# sourceMappingURL=adapterInstances.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapterInstances.d.ts","sourceRoot":"","sources":["../../src/proxy/adapterInstances.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAIpD,MAAM,WAAW,oBAAoB;IACnC,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,+BAA+B;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAA;IACZ,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;IACnC,iFAAiF;IACjF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,oBAAoB,CAAA;CAC7B;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;AAEnE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,kBAAkB,CAkCjF;AAOD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,kBAAkB,CAazD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,kBAAkB,EACvB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAC9C,OAAO,CAiBT"}
@@ -6,18 +6,5 @@
6
6
  */
7
7
  import type { Context } from "hono";
8
8
  import type { AgentAdapter } from "../adapter";
9
- /**
10
- * Detect which agent adapter to use based on request headers.
11
- *
12
- * Detection rules (evaluated in order):
13
- * 1. x-meridian-agent header → explicit adapter override
14
- * 2. x-opencode-session or x-session-affinity header → OpenCode adapter
15
- * 3. User-Agent starts with "opencode/" → OpenCode adapter
16
- * 4. User-Agent starts with "factory-cli/" → Droid adapter
17
- * 5. User-Agent starts with "Charm-Crush/" → Crush adapter
18
- * 6. User-Agent starts with "claude-cli/" → Claude Code adapter
19
- * 7. litellm/* UA or x-litellm-* headers → LiteLLM passthrough adapter
20
- * 8. Default → MERIDIAN_DEFAULT_AGENT env var, or OpenCode
21
- */
22
9
  export declare function detectAdapter(c: Context): AgentAdapter;
23
10
  //# 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;AAkD9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CAgDtD"}
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;AAsF9C,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CAsEtD"}
@@ -13,6 +13,7 @@
13
13
  *
14
14
  * This is a leaf module — no imports from server.ts or session/.
15
15
  */
16
+ import { type RoutingMode } from "./routing";
16
17
  /**
17
18
  * Load profiles from ~/.config/meridian/profiles.json.
18
19
  * Cached with a 5s TTL so new profiles are picked up without restart,
@@ -70,14 +71,28 @@ export declare function enableDiskProfileDiscovery(): void;
70
71
  export declare function getEffectiveProfiles(configProfiles: ProfileConfig[] | undefined): ProfileConfig[];
71
72
  /** Check if any profiles are available from any source */
72
73
  export declare function hasProfiles(configProfiles: ProfileConfig[] | undefined): boolean;
74
+ /** Options for the sticky-routing resolution step (#383). */
75
+ export interface ResolveProfileOptions {
76
+ /** Session identity for sticky assignment (adapter.getSessionId). */
77
+ stickySessionKey?: string;
78
+ /** Routing mode — "active" (default, pre-#383 chain) or "sticky". */
79
+ routingMode?: RoutingMode;
80
+ }
73
81
  /**
74
82
  * Resolve a profile from the configuration.
75
83
  *
84
+ * Priority: header > sticky assignment (routing="sticky" only) > active >
85
+ * config default > first profile. The sticky step exists so multi-account
86
+ * setups can distribute sessions across profiles WITHOUT losing per-account
87
+ * prompt caching — see routing.ts. With routingMode unset/"active" the
88
+ * chain is exactly the pre-#383 behavior.
89
+ *
76
90
  * @param profiles - Configured profiles (from ProxyConfig)
77
91
  * @param defaultProfile - Default profile ID (from ProxyConfig)
78
92
  * @param requestedId - Explicit profile ID from request header
93
+ * @param options - Sticky-routing inputs (session key + mode)
79
94
  */
80
- export declare function resolveProfile(profiles: ProfileConfig[] | undefined, defaultProfile: string | undefined, requestedId?: string): ResolvedProfile;
95
+ export declare function resolveProfile(profiles: ProfileConfig[] | undefined, defaultProfile: string | undefined, requestedId?: string, options?: ResolveProfileOptions): ResolvedProfile;
81
96
  /**
82
97
  * Get all configured profile IDs with their types.
83
98
  */
@@ -1 +1 @@
1
- {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/proxy/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAcH;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,aAAa,EAAE,CAkBtD;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,KAAK,GAAG,aAAa,CAAA;AAE9D,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAA;IACV;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,WAAW,CAAA;IACjB,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC5B;AAOD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAEvD;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAY3E;AAUD;;kEAEkE;AAClE,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,aAAa,EAAE,CAOjG;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAS,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,CAAC,EAAE,MAAM,GACnB,eAAe,CAkBjB;AAkCD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAS,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAU7D"}
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/proxy/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAS/D;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,aAAa,EAAE,CAkBtD;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,KAAK,GAAG,aAAa,CAAA;AAE9D,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAA;IACV;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,WAAW,CAAA;IACjB,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC5B;AAOD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAEvD;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAY3E;AAUD;;kEAEkE;AAClE,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,aAAa,EAAE,CAOjG;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED,6DAA6D;AAC7D,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qEAAqE;IACrE,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAS,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,eAAe,CAyBjB;AAkCD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAS,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAU7D"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Sticky session-to-profile routing (#383, design by @ShreeMulay).
3
+ *
4
+ * With multiple profiles configured, `routing = "sticky"` distributes
5
+ * sessions across profiles while preserving session affinity — Anthropic's
6
+ * prompt caching is per-account, so a session that flip-flops between
7
+ * accounts pays a cold cache (full context re-creation) on every flip.
8
+ *
9
+ * Assignment uses rendezvous (highest-random-weight) hashing:
10
+ * - deterministic and stateless — stickiness survives proxy restarts with
11
+ * no persisted session→profile map to lose or corrupt
12
+ * - minimal disruption — adding an arm only moves the sessions that hash
13
+ * to the new arm; removing an arm only reassigns that arm's sessions
14
+ *
15
+ * Resolution priority (see resolveProfile): explicit x-meridian-profile
16
+ * header > sticky assignment > active profile > config default > first.
17
+ * Default mode is "active" (the pre-#383 chain) — existing setups are
18
+ * byte-identical unless routing is explicitly enabled.
19
+ *
20
+ * This is a leaf module — pure functions, no I/O.
21
+ */
22
+ export type RoutingMode = "active" | "sticky";
23
+ /**
24
+ * Parse a routing mode string (from settings or MERIDIAN_ROUTING).
25
+ * Unknown values fall back to "active" — a typo must never change
26
+ * routing behavior into something surprising.
27
+ */
28
+ export declare function getRoutingMode(raw: string | undefined): RoutingMode;
29
+ /**
30
+ * Pick the sticky profile for a session: the profile with the highest
31
+ * rendezvous score. Returns undefined when there is nothing to pick
32
+ * (no session identity or no profiles) — callers fall through to the
33
+ * normal resolution chain.
34
+ */
35
+ export declare function pickStickyProfile(sessionKey: string, profileIds: readonly string[]): string | undefined;
36
+ /**
37
+ * Pinned (sessionKey, profiles, expected) triples guarding hash stability.
38
+ * If an implementation change breaks these, every user's sessions would
39
+ * silently reshuffle onto different accounts (cold caches) on upgrade —
40
+ * treat that as a breaking change requiring a migration note, not a test
41
+ * to update casually.
42
+ */
43
+ export declare const RENDEZVOUS_STABLE_GUARD: ReadonlyArray<readonly [string, readonly string[], string]>;
44
+ //# sourceMappingURL=routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/proxy/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE7C;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAEnE;AAaD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAYvG;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,CAAC,CAM/F,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAeA,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;AAoCnG,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;AA2Q7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA+pGhF;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":"AAeA,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;AAsCnG,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;AA+Q7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA2tGhF;AAWD,wBAAgB,gCAAgC,IAAI,IAAI,CAavD;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmGhG"}
@@ -10,6 +10,9 @@
10
10
  export interface MeridianSettings {
11
11
  /** Last active profile ID — restored on proxy startup */
12
12
  activeProfile?: string;
13
+ /** Profile routing mode (#383): "active" (default) or "sticky".
14
+ * MERIDIAN_ROUTING env var takes precedence when set. */
15
+ routing?: string;
13
16
  }
14
17
  /** Read settings from disk. Returns empty object if file doesn't exist or is invalid. */
15
18
  export declare function loadSettings(): MeridianSettings;
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/proxy/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,yFAAyF;AACzF,wBAAgB,YAAY,IAAI,gBAAgB,CAO/C;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CASrE;AAED,iCAAiC;AACjC,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,GAAG,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAExF;AAED,6CAA6C;AAC7C,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,CAErG"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/proxy/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,yFAAyF;AACzF,wBAAgB,YAAY,IAAI,gBAAgB,CAO/C;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CASrE;AAED,iCAAiC;AACjC,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,GAAG,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAExF;AAED,6CAA6C;AAC7C,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,CAErG"}
package/dist/server.js CHANGED
@@ -11,8 +11,8 @@ import {
11
11
  runObserveHook,
12
12
  runTransformHook,
13
13
  startProxyServer
14
- } from "./cli-vhrprwj4.js";
15
- import"./cli-cx463q74.js";
14
+ } from "./cli-mjrt119g.js";
15
+ import"./cli-f0yqy2d2.js";
16
16
  import"./cli-sry5aqdj.js";
17
17
  import"./cli-4rqtm83g.js";
18
18
  import"./cli-q6dz9r5p.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.48.1",
3
+ "version": "1.49.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",