@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 +46 -1
- package/dist/{cli-cx463q74.js → cli-f0yqy2d2.js} +30 -3
- package/dist/{cli-vhrprwj4.js → cli-mjrt119g.js} +184 -49
- package/dist/cli.js +3 -3
- package/dist/{profiles-rdd84b45.js → profiles-84hnbd6c.js} +1 -1
- package/dist/proxy/adapter.d.ts +12 -0
- package/dist/proxy/adapter.d.ts.map +1 -1
- package/dist/proxy/adapterInstances.d.ts +63 -0
- package/dist/proxy/adapterInstances.d.ts.map +1 -0
- package/dist/proxy/adapters/detect.d.ts +0 -13
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/profiles.d.ts +16 -1
- package/dist/proxy/profiles.d.ts.map +1 -1
- package/dist/proxy/routing.d.ts +44 -0
- package/dist/proxy/routing.d.ts.map +1 -0
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/settings.d.ts +3 -0
- package/dist/proxy/settings.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +1 -1
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
|
|
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-
|
|
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
|
|
1202
|
-
import { join as
|
|
1203
|
-
import { homedir as
|
|
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 =
|
|
1206
|
-
if (!
|
|
1209
|
+
const dir = join5(homedir4(), ".config", "meridian");
|
|
1210
|
+
if (!existsSync6(dir))
|
|
1207
1211
|
mkdirSync2(dir, { recursive: true });
|
|
1208
|
-
return
|
|
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 (
|
|
1217
|
-
cachedConfig = JSON.parse(
|
|
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
|
|
3742
|
-
import { join as
|
|
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
|
|
17223
|
-
import { join as
|
|
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 (!
|
|
17379
|
+
if (!existsSync4(configPath))
|
|
17265
17380
|
return [];
|
|
17266
17381
|
try {
|
|
17267
|
-
const raw2 =
|
|
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 =
|
|
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 :
|
|
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
|
|
17796
|
+
existsSync as existsSync5,
|
|
17682
17797
|
mkdirSync,
|
|
17683
17798
|
openSync,
|
|
17684
|
-
readFileSync as
|
|
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
|
|
17691
|
-
import { join as
|
|
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 (!
|
|
17854
|
+
if (!existsSync5(dir)) {
|
|
17740
17855
|
mkdirSync(dir, { recursive: true });
|
|
17741
17856
|
}
|
|
17742
|
-
return
|
|
17857
|
+
return join4(dir, "sessions.json");
|
|
17743
17858
|
}
|
|
17744
17859
|
function getDefaultCacheDir() {
|
|
17745
|
-
const newDir =
|
|
17746
|
-
const oldDir =
|
|
17747
|
-
if (
|
|
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 (
|
|
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 (!
|
|
17877
|
+
if (!existsSync5(path3))
|
|
17763
17878
|
return {};
|
|
17764
17879
|
try {
|
|
17765
|
-
const data =
|
|
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
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
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 ??
|
|
18295
|
-
const pluginConfigPath = finalConfig.pluginConfigPath ??
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
}),
|
|
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.
|
|
18450
|
-
if (
|
|
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 =
|
|
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" &&
|
|
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
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
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-
|
|
5
|
-
import"./cli-
|
|
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-
|
|
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 });
|
package/dist/proxy/adapter.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/proxy/profiles.d.ts
CHANGED
|
@@ -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;
|
|
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;
|
|
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"}
|
package/dist/proxy/settings.d.ts
CHANGED
|
@@ -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;
|
|
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-
|
|
15
|
-
import"./cli-
|
|
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