@rynfar/meridian 1.37.8 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +54 -3
  2. package/dist/{cli-pr79d7nw.js → cli-4rqtm83g.js} +33 -2
  3. package/dist/{cli-z5r7ptsm.js → cli-jbdchsr4.js} +1109 -231
  4. package/dist/cli-sry5aqdj.js +54 -0
  5. package/dist/cli.js +5 -4
  6. package/dist/pluginPage-85s6t6k8.js +405 -0
  7. package/dist/{profilePage-g5t5t6av.js → profilePage-77z05e0r.js} +4 -8
  8. package/dist/proxy/adapter.d.ts +45 -12
  9. package/dist/proxy/adapter.d.ts.map +1 -1
  10. package/dist/proxy/adapters/claudecode.d.ts +21 -0
  11. package/dist/proxy/adapters/claudecode.d.ts.map +1 -0
  12. package/dist/proxy/adapters/crush.d.ts +2 -0
  13. package/dist/proxy/adapters/crush.d.ts.map +1 -1
  14. package/dist/proxy/adapters/detect.d.ts +3 -2
  15. package/dist/proxy/adapters/detect.d.ts.map +1 -1
  16. package/dist/proxy/adapters/droid.d.ts +2 -0
  17. package/dist/proxy/adapters/droid.d.ts.map +1 -1
  18. package/dist/proxy/adapters/forgecode.d.ts +2 -0
  19. package/dist/proxy/adapters/forgecode.d.ts.map +1 -1
  20. package/dist/proxy/adapters/opencode.d.ts +2 -0
  21. package/dist/proxy/adapters/opencode.d.ts.map +1 -1
  22. package/dist/proxy/adapters/passthrough.d.ts +2 -0
  23. package/dist/proxy/adapters/passthrough.d.ts.map +1 -1
  24. package/dist/proxy/adapters/pi.d.ts +2 -0
  25. package/dist/proxy/adapters/pi.d.ts.map +1 -1
  26. package/dist/proxy/agentDefs.d.ts +2 -0
  27. package/dist/proxy/agentDefs.d.ts.map +1 -1
  28. package/dist/proxy/agentMatch.d.ts +11 -1
  29. package/dist/proxy/agentMatch.d.ts.map +1 -1
  30. package/dist/proxy/messages.d.ts +11 -0
  31. package/dist/proxy/messages.d.ts.map +1 -1
  32. package/dist/proxy/models.d.ts +25 -0
  33. package/dist/proxy/models.d.ts.map +1 -1
  34. package/dist/proxy/openai.d.ts.map +1 -1
  35. package/dist/proxy/passthroughTools.d.ts +18 -0
  36. package/dist/proxy/passthroughTools.d.ts.map +1 -1
  37. package/dist/proxy/plugins/loader.d.ts +6 -0
  38. package/dist/proxy/plugins/loader.d.ts.map +1 -0
  39. package/dist/proxy/plugins/pluginPage.d.ts +7 -0
  40. package/dist/proxy/plugins/pluginPage.d.ts.map +1 -0
  41. package/dist/proxy/plugins/stats.d.ts +61 -0
  42. package/dist/proxy/plugins/stats.d.ts.map +1 -0
  43. package/dist/proxy/plugins/types.d.ts +21 -0
  44. package/dist/proxy/plugins/types.d.ts.map +1 -0
  45. package/dist/proxy/plugins/validation.d.ts +8 -0
  46. package/dist/proxy/plugins/validation.d.ts.map +1 -0
  47. package/dist/proxy/query.d.ts +27 -4
  48. package/dist/proxy/query.d.ts.map +1 -1
  49. package/dist/proxy/server.d.ts +2 -0
  50. package/dist/proxy/server.d.ts.map +1 -1
  51. package/dist/proxy/session/lineage.d.ts +10 -1
  52. package/dist/proxy/session/lineage.d.ts.map +1 -1
  53. package/dist/proxy/transform.d.ts +137 -0
  54. package/dist/proxy/transform.d.ts.map +1 -0
  55. package/dist/proxy/transforms/crush.d.ts +3 -0
  56. package/dist/proxy/transforms/crush.d.ts.map +1 -0
  57. package/dist/proxy/transforms/droid.d.ts +3 -0
  58. package/dist/proxy/transforms/droid.d.ts.map +1 -0
  59. package/dist/proxy/transforms/forgecode.d.ts +3 -0
  60. package/dist/proxy/transforms/forgecode.d.ts.map +1 -0
  61. package/dist/proxy/transforms/opencode.d.ts +3 -0
  62. package/dist/proxy/transforms/opencode.d.ts.map +1 -0
  63. package/dist/proxy/transforms/passthrough.d.ts +3 -0
  64. package/dist/proxy/transforms/passthrough.d.ts.map +1 -0
  65. package/dist/proxy/transforms/pi.d.ts +3 -0
  66. package/dist/proxy/transforms/pi.d.ts.map +1 -0
  67. package/dist/proxy/transforms/registry.d.ts +3 -0
  68. package/dist/proxy/transforms/registry.d.ts.map +1 -0
  69. package/dist/proxy/types.d.ts +6 -0
  70. package/dist/proxy/types.d.ts.map +1 -1
  71. package/dist/server.js +14 -5
  72. package/dist/stats-4c4ewmdh.js +17 -0
  73. package/dist/telemetry/dashboard.d.ts.map +1 -1
  74. package/dist/telemetry/landing.d.ts.map +1 -1
  75. package/dist/telemetry/profileBar.d.ts +13 -1
  76. package/dist/telemetry/profileBar.d.ts.map +1 -1
  77. package/dist/telemetry/profilePage.d.ts.map +1 -1
  78. package/dist/telemetry/settingsPage.d.ts +1 -1
  79. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  80. package/dist/telemetry/sqlite.d.ts.map +1 -1
  81. package/dist/telemetry/types.d.ts +4 -0
  82. package/dist/telemetry/types.d.ts.map +1 -1
  83. package/package.json +4 -2
@@ -1,9 +1,25 @@
1
+ import {
2
+ getActiveProfileId,
3
+ getEffectiveProfiles,
4
+ listProfiles,
5
+ resolveProfile,
6
+ restoreActiveProfile,
7
+ setActiveProfile
8
+ } from "./cli-vdp9s10c.js";
9
+ import {
10
+ isTrackedPlugin,
11
+ recordError,
12
+ recordInvocation,
13
+ registerPluginStats,
14
+ resetAllPluginStats
15
+ } from "./cli-sry5aqdj.js";
1
16
  import {
2
17
  init_profileBar,
3
18
  profileBarCss,
4
19
  profileBarHtml,
5
- profileBarJs
6
- } from "./cli-pr79d7nw.js";
20
+ profileBarJs,
21
+ themeCss
22
+ } from "./cli-4rqtm83g.js";
7
23
  import {
8
24
  checkPluginConfigured
9
25
  } from "./cli-rtab0qa6.js";
@@ -12,14 +28,6 @@ import {
12
28
  refreshOAuthToken,
13
29
  withClaudeLogContext
14
30
  } from "./cli-m9pfb7h9.js";
15
- import {
16
- getActiveProfileId,
17
- getEffectiveProfiles,
18
- listProfiles,
19
- resolveProfile,
20
- restoreActiveProfile,
21
- setActiveProfile
22
- } from "./cli-vdp9s10c.js";
23
31
  import {
24
32
  __commonJS,
25
33
  __esm,
@@ -849,6 +857,11 @@ function openDatabase(dbPath) {
849
857
  db.pragma("synchronous = NORMAL");
850
858
  db.exec(METRICS_SCHEMA);
851
859
  db.exec(LOGS_SCHEMA);
860
+ for (const sql of METRICS_MIGRATIONS) {
861
+ try {
862
+ db.exec(sql);
863
+ } catch {}
864
+ }
852
865
  return db;
853
866
  }
854
867
 
@@ -863,7 +876,7 @@ class SqliteTelemetryStore {
863
876
  this.retentionMs = retentionDays * 24 * 60 * 60 * 1000;
864
877
  this.insertStmt = db.prepare(`
865
878
  INSERT INTO metrics (
866
- request_id, timestamp, adapter, model, request_model, mode,
879
+ request_id, timestamp, adapter, request_source, model, request_model, mode,
867
880
  is_resume, is_passthrough, lineage_type,
868
881
  has_deferred_tools, deferred_tool_count, tool_count, discovered_tools, session_discovered_count,
869
882
  message_count, sdk_session_id,
@@ -872,7 +885,7 @@ class SqliteTelemetryStore {
872
885
  input_tokens, output_tokens, cache_read_input_tokens,
873
886
  cache_creation_input_tokens, cache_hit_rate
874
887
  ) VALUES (
875
- @requestId, @timestamp, @adapter, @model, @requestModel, @mode,
888
+ @requestId, @timestamp, @adapter, @requestSource, @model, @requestModel, @mode,
876
889
  @isResume, @isPassthrough, @lineageType,
877
890
  @hasDeferredTools, @deferredToolCount, @toolCount, @discoveredTools, @sessionDiscoveredCount,
878
891
  @messageCount, @sdkSessionId,
@@ -890,6 +903,7 @@ class SqliteTelemetryStore {
890
903
  requestId: metric.requestId,
891
904
  timestamp: metric.timestamp,
892
905
  adapter: metric.adapter ?? null,
906
+ requestSource: metric.requestSource ?? null,
893
907
  model: metric.model,
894
908
  requestModel: metric.requestModel ?? null,
895
909
  mode: metric.mode,
@@ -1054,6 +1068,7 @@ function rowToMetric(r) {
1054
1068
  requestId: r.request_id,
1055
1069
  timestamp: r.timestamp,
1056
1070
  adapter: r.adapter ?? undefined,
1071
+ requestSource: r.request_source ?? undefined,
1057
1072
  model: r.model,
1058
1073
  requestModel: r.request_model ?? undefined,
1059
1074
  mode: r.mode,
@@ -1101,6 +1116,7 @@ CREATE TABLE IF NOT EXISTS metrics (
1101
1116
  request_id TEXT NOT NULL,
1102
1117
  timestamp INTEGER NOT NULL,
1103
1118
  adapter TEXT,
1119
+ request_source TEXT,
1104
1120
  model TEXT NOT NULL,
1105
1121
  request_model TEXT,
1106
1122
  mode TEXT NOT NULL,
@@ -1132,7 +1148,7 @@ CREATE TABLE IF NOT EXISTS metrics (
1132
1148
  CREATE INDEX IF NOT EXISTS idx_metrics_ts ON metrics(timestamp);
1133
1149
  CREATE INDEX IF NOT EXISTS idx_metrics_model ON metrics(model);
1134
1150
  CREATE INDEX IF NOT EXISTS idx_metrics_session_success ON metrics(sdk_session_id, timestamp DESC, id DESC);
1135
- `, LOGS_SCHEMA = `
1151
+ `, METRICS_MIGRATIONS, LOGS_SCHEMA = `
1136
1152
  CREATE TABLE IF NOT EXISTS diagnostic_logs (
1137
1153
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1138
1154
  timestamp INTEGER NOT NULL,
@@ -1147,6 +1163,9 @@ CREATE INDEX IF NOT EXISTS idx_logs_cat ON diagnostic_logs(category);
1147
1163
  var init_sqlite = __esm(() => {
1148
1164
  init_percentiles();
1149
1165
  import_libsql = __toESM(require_libsql(), 1);
1166
+ METRICS_MIGRATIONS = [
1167
+ "ALTER TABLE metrics ADD COLUMN request_source TEXT"
1168
+ ];
1150
1169
  });
1151
1170
 
1152
1171
  // src/proxy/sdkFeatures.ts
@@ -1158,14 +1177,14 @@ __export(exports_sdkFeatures, {
1158
1177
  getFeaturesForAdapter: () => getFeaturesForAdapter,
1159
1178
  getAllFeatureConfigs: () => getAllFeatureConfigs
1160
1179
  });
1161
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
1162
- import { join as join5 } from "node:path";
1180
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
1181
+ import { join as join6 } from "node:path";
1163
1182
  import { homedir as homedir4 } from "node:os";
1164
1183
  function getConfigPath() {
1165
- const dir = join5(homedir4(), ".config", "meridian");
1166
- if (!existsSync4(dir))
1184
+ const dir = join6(homedir4(), ".config", "meridian");
1185
+ if (!existsSync5(dir))
1167
1186
  mkdirSync2(dir, { recursive: true });
1168
- return join5(dir, "sdk-features.json");
1187
+ return join6(dir, "sdk-features.json");
1169
1188
  }
1170
1189
  function readConfig() {
1171
1190
  const now = Date.now();
@@ -1173,8 +1192,8 @@ function readConfig() {
1173
1192
  return cachedConfig;
1174
1193
  const path3 = getConfigPath();
1175
1194
  try {
1176
- if (existsSync4(path3)) {
1177
- cachedConfig = JSON.parse(readFileSync3(path3, "utf-8"));
1195
+ if (existsSync5(path3)) {
1196
+ cachedConfig = JSON.parse(readFileSync4(path3, "utf-8"));
1178
1197
  } else {
1179
1198
  cachedConfig = {};
1180
1199
  }
@@ -1297,12 +1316,7 @@ var init_settingsPage = __esm(() => {
1297
1316
  <title>Meridian — SDK Features</title>
1298
1317
  <link rel="icon" type="image/svg+xml" href="/telemetry/icon.svg">
1299
1318
  <style>
1300
- :root {
1301
- --bg: #0d1117; --surface: #161b22; --border: #30363d;
1302
- --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
1303
- --green: #3fb950; --yellow: #d29922; --red: #f85149;
1304
- --purple: #bc8cff;
1305
- }
1319
+ ${themeCss}
1306
1320
  * { box-sizing: border-box; margin: 0; padding: 0; }
1307
1321
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
1308
1322
  background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
@@ -3688,6 +3702,8 @@ var serve = (options, listeningListener) => {
3688
3702
  };
3689
3703
 
3690
3704
  // src/proxy/server.ts
3705
+ import { homedir as homedir5 } from "node:os";
3706
+ import { join as join7 } from "node:path";
3691
3707
  import { query } from "@anthropic-ai/claude-agent-sdk";
3692
3708
 
3693
3709
  // src/proxy/types.ts
@@ -3718,6 +3734,73 @@ function envInt(suffix, defaultValue) {
3718
3734
  return Number.isFinite(parsed) ? parsed : defaultValue;
3719
3735
  }
3720
3736
 
3737
+ // src/proxy/transform.ts
3738
+ function runTransformHook(transforms, hook, ctx, adapterName) {
3739
+ return transforms.reduce((acc, transform) => {
3740
+ const fn = transform[hook];
3741
+ if (!fn)
3742
+ return acc;
3743
+ if (transform.adapters && !transform.adapters.includes(adapterName))
3744
+ return acc;
3745
+ const tracked = isTrackedPlugin(transform.name);
3746
+ const startAt = tracked ? performance.now() : 0;
3747
+ try {
3748
+ const result = fn.call(transform, acc);
3749
+ if (tracked)
3750
+ recordInvocation(transform.name, hook, performance.now() - startAt);
3751
+ return result;
3752
+ } catch (err) {
3753
+ if (tracked)
3754
+ recordError(transform.name, hook, err);
3755
+ console.error(`[PLUGIN] Transform "${transform.name}" threw in ${hook}: ${err instanceof Error ? err.message : String(err)}`);
3756
+ return acc;
3757
+ }
3758
+ }, ctx);
3759
+ }
3760
+ function runObserveHook(transforms, hook, ctx, adapterName) {
3761
+ for (const transform of transforms) {
3762
+ const fn = transform[hook];
3763
+ if (!fn)
3764
+ continue;
3765
+ if (transform.adapters && !transform.adapters.includes(adapterName))
3766
+ continue;
3767
+ const tracked = isTrackedPlugin(transform.name);
3768
+ const startAt = tracked ? performance.now() : 0;
3769
+ try {
3770
+ fn.call(transform, ctx);
3771
+ if (tracked)
3772
+ recordInvocation(transform.name, hook, performance.now() - startAt);
3773
+ } catch (err) {
3774
+ if (tracked)
3775
+ recordError(transform.name, hook, err);
3776
+ console.error(`[PLUGIN] Transform "${transform.name}" threw in ${hook}: ${err instanceof Error ? err.message : String(err)}`);
3777
+ }
3778
+ }
3779
+ }
3780
+ function buildPipeline(adapterTransforms, pluginTransforms) {
3781
+ return [...adapterTransforms, ...pluginTransforms];
3782
+ }
3783
+ function createRequestContext(params) {
3784
+ return {
3785
+ adapter: params.adapter,
3786
+ body: params.body,
3787
+ headers: params.headers,
3788
+ model: params.model,
3789
+ messages: params.messages,
3790
+ systemContext: params.systemContext,
3791
+ tools: params.tools,
3792
+ stream: params.stream,
3793
+ workingDirectory: params.workingDirectory,
3794
+ blockedTools: [],
3795
+ incompatibleTools: [],
3796
+ allowedMcpTools: [],
3797
+ sdkAgents: {},
3798
+ supportsThinking: false,
3799
+ shouldTrackFileChanges: true,
3800
+ leaksCwdViaSystemReminder: false,
3801
+ metadata: {}
3802
+ };
3803
+ }
3721
3804
  // src/proxy/server.ts
3722
3805
  import { exec as execCallback2 } from "child_process";
3723
3806
  import { promisify as promisify3 } from "util";
@@ -7805,6 +7888,184 @@ function stripMcpPrefix(toolName) {
7805
7888
  }
7806
7889
  return toolName;
7807
7890
  }
7891
+ function toCamelCase(s) {
7892
+ return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
7893
+ }
7894
+ function toSnakeCase(s) {
7895
+ return s.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
7896
+ }
7897
+ function normalizeToolInput(input, clientSchema) {
7898
+ if (!input || !clientSchema?.properties)
7899
+ return input;
7900
+ const schemaKeys = new Set(Object.keys(clientSchema.properties));
7901
+ const required = new Set(clientSchema.required ?? []);
7902
+ const missingRequired = [...required].filter((k) => input[k] === undefined);
7903
+ if (missingRequired.length === 0)
7904
+ return input;
7905
+ const normalized = { ...input };
7906
+ for (const key of Object.keys(normalized)) {
7907
+ if (schemaKeys.has(key))
7908
+ continue;
7909
+ const camel = toCamelCase(key);
7910
+ if (camel !== key && schemaKeys.has(camel) && normalized[camel] === undefined) {
7911
+ normalized[camel] = normalized[key];
7912
+ delete normalized[key];
7913
+ continue;
7914
+ }
7915
+ const snake = toSnakeCase(key);
7916
+ if (snake !== key && schemaKeys.has(snake) && normalized[snake] === undefined) {
7917
+ normalized[snake] = normalized[key];
7918
+ delete normalized[key];
7919
+ }
7920
+ }
7921
+ return normalized;
7922
+ }
7923
+
7924
+ // src/proxy/agentDefs.ts
7925
+ var FALLBACK_AGENT_NAME = "general";
7926
+ var DEFAULT_AGENT_TYPES = {
7927
+ build: "The default agent. Executes tools based on configured permissions.",
7928
+ plan: "Plan mode. Disallows all edit tools.",
7929
+ explore: "Contextual grep for codebases. Answers 'Where is X?', 'Which file has Y?'.",
7930
+ general: "General-purpose agent for researching complex questions and executing multi-step tasks."
7931
+ };
7932
+ function parseAgentDescriptions(taskDescription) {
7933
+ const agents = new Map;
7934
+ const agentSection = taskDescription.match(/Available agent types.*?:\n((?:- [\w][\w-]*:.*\n?)+)/s);
7935
+ if (!agentSection)
7936
+ return agents;
7937
+ const entries = agentSection[1].matchAll(/^- ([\w][\w-]*):\s*(.+)/gm);
7938
+ for (const match2 of entries) {
7939
+ agents.set(match2[1], match2[2].trim());
7940
+ }
7941
+ return agents;
7942
+ }
7943
+ function buildAgentDefinitions(taskDescription, mcpToolNames) {
7944
+ const descriptions = parseAgentDescriptions(taskDescription);
7945
+ const agents = {};
7946
+ for (const [name, description] of descriptions) {
7947
+ agents[name] = {
7948
+ description,
7949
+ prompt: buildAgentPrompt(name, description),
7950
+ model: "inherit",
7951
+ ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
7952
+ };
7953
+ }
7954
+ if (descriptions.size > 0) {
7955
+ ensureDefaultAgents(agents, mcpToolNames);
7956
+ addCaseVariants(agents);
7957
+ }
7958
+ return agents;
7959
+ }
7960
+ function ensureDefaultAgents(agents, mcpToolNames) {
7961
+ for (const [name, description] of Object.entries(DEFAULT_AGENT_TYPES)) {
7962
+ if (!agents[name]) {
7963
+ agents[name] = {
7964
+ description,
7965
+ prompt: buildAgentPrompt(name, description),
7966
+ model: "inherit",
7967
+ ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
7968
+ };
7969
+ }
7970
+ }
7971
+ }
7972
+ function addCaseVariants(agents) {
7973
+ const baseNames = Object.keys(agents);
7974
+ for (const name of baseNames) {
7975
+ const def = agents[name];
7976
+ const titleCase = name.replace(/(^|-)(\w)/g, (_m, sep, ch) => sep + ch.toUpperCase());
7977
+ if (titleCase !== name && !agents[titleCase]) {
7978
+ agents[titleCase] = def;
7979
+ }
7980
+ }
7981
+ const ALIASES = {
7982
+ "general-purpose": "general",
7983
+ "General-Purpose": "general"
7984
+ };
7985
+ for (const [alias, target] of Object.entries(ALIASES)) {
7986
+ if (!agents[alias] && agents[target]) {
7987
+ agents[alias] = agents[target];
7988
+ }
7989
+ }
7990
+ }
7991
+ function buildAgentPrompt(name, description) {
7992
+ return `You are the "${name}" agent. ${description}
7993
+
7994
+ Focus on your specific role and complete the task thoroughly. Return a clear, concise result.`;
7995
+ }
7996
+
7997
+ // src/proxy/agentMatch.ts
7998
+ var KNOWN_ALIASES = {
7999
+ "general-purpose": "general",
8000
+ default: "general",
8001
+ "code-reviewer": "oracle",
8002
+ reviewer: "oracle",
8003
+ "code-review": "oracle",
8004
+ review: "oracle",
8005
+ consultation: "oracle",
8006
+ analyzer: "oracle",
8007
+ debugger: "oracle",
8008
+ search: "explore",
8009
+ grep: "explore",
8010
+ find: "explore",
8011
+ "codebase-search": "explore",
8012
+ research: "librarian",
8013
+ docs: "librarian",
8014
+ documentation: "librarian",
8015
+ lookup: "librarian",
8016
+ reference: "librarian",
8017
+ consult: "oracle",
8018
+ architect: "oracle",
8019
+ "image-analyzer": "multimodal-looker",
8020
+ image: "multimodal-looker",
8021
+ pdf: "multimodal-looker",
8022
+ visual: "multimodal-looker",
8023
+ planner: "plan",
8024
+ planning: "plan",
8025
+ builder: "build",
8026
+ coder: "build",
8027
+ developer: "build",
8028
+ writer: "build",
8029
+ executor: "build"
8030
+ };
8031
+ var STRIP_SUFFIXES = ["-agent", "-tool", "-worker", "-task", " agent", " tool"];
8032
+ function resolveAgentAlias(input) {
8033
+ const lowered = input.toLowerCase();
8034
+ return KNOWN_ALIASES[lowered] ?? lowered;
8035
+ }
8036
+ function fuzzyMatchAgentName(input, validAgents) {
8037
+ if (!input)
8038
+ return input;
8039
+ if (validAgents.length === 0)
8040
+ return input.toLowerCase();
8041
+ const lowered = input.toLowerCase();
8042
+ const exact = validAgents.find((a) => a.toLowerCase() === lowered);
8043
+ if (exact)
8044
+ return exact;
8045
+ const alias = KNOWN_ALIASES[lowered];
8046
+ if (alias && validAgents.includes(alias))
8047
+ return alias;
8048
+ const prefixMatch = validAgents.find((a) => a.toLowerCase().startsWith(lowered));
8049
+ if (prefixMatch)
8050
+ return prefixMatch;
8051
+ const substringMatch = validAgents.find((a) => a.toLowerCase().includes(lowered));
8052
+ if (substringMatch)
8053
+ return substringMatch;
8054
+ for (const suffix of STRIP_SUFFIXES) {
8055
+ if (lowered.endsWith(suffix)) {
8056
+ const stripped = lowered.slice(0, -suffix.length);
8057
+ const strippedMatch = validAgents.find((a) => a.toLowerCase() === stripped);
8058
+ if (strippedMatch)
8059
+ return strippedMatch;
8060
+ }
8061
+ }
8062
+ const reverseMatch = validAgents.find((a) => lowered.includes(a.toLowerCase()));
8063
+ if (reverseMatch)
8064
+ return reverseMatch;
8065
+ if (validAgents.includes(FALLBACK_AGENT_NAME))
8066
+ return FALLBACK_AGENT_NAME;
8067
+ return lowered;
8068
+ }
7808
8069
 
7809
8070
  // src/utils/lruMap.ts
7810
8071
  class LRUMap {
@@ -8010,13 +8271,8 @@ var dashboardHtml = `<!DOCTYPE html>
8010
8271
  <title>Meridian — Telemetry</title>
8011
8272
  <link rel="icon" type="image/svg+xml" href="/telemetry/icon.svg">
8012
8273
  <style>
8013
- :root {
8014
- --bg: #0d1117; --surface: #161b22; --border: #30363d;
8015
- --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
8016
- --green: #3fb950; --yellow: #d29922; --red: #f85149;
8017
- --blue: #58a6ff; --purple: #bc8cff;
8018
- --queue: #d29922; --ttfb: #58a6ff; --upstream: #3fb950; --total: #bc8cff;
8019
- }
8274
+ ${themeCss}
8275
+ :root { --total: var(--accent); }
8020
8276
  * { box-sizing: border-box; margin: 0; padding: 0; }
8021
8277
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
8022
8278
  background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
@@ -8272,9 +8528,11 @@ function render(s, reqs, logs) {
8272
8528
  const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '—';
8273
8529
  const msgCount = r.messageCount != null ? r.messageCount : '?';
8274
8530
 
8531
+ const sourceBadge = r.requestSource ? '<br><span class="mono" style="font-size:9px;color:var(--violet)">' + r.requestSource + '</span>' : '';
8532
+
8275
8533
  html += '<tr>'
8276
8534
  + '<td class="mono">' + ago(r.timestamp) + '</td>'
8277
- + '<td>' + (r.adapter || '—') + '</td>'
8535
+ + '<td>' + (r.adapter || '—') + sourceBadge + '</td>'
8278
8536
  + '<td>' + (r.requestModel || r.model) + '<br><span style="font-size:10px;color:var(--muted)">' + r.model + '</span></td>'
8279
8537
  + '<td>' + r.mode + (r.hasDeferredTools ? (function() { var sessDisc = r.sessionDiscoveredCount || 0; var loaded = ((r.toolCount || 0) - (r.deferredToolCount || 0)) + sessDisc; var deferred = Math.max(0, (r.deferredToolCount || 0) - sessDisc); var newDisc = r.discoveredTools || []; return '<br><span style="font-size:10px;color:var(--purple)">loaded=' + loaded + ' deferred=' + deferred + '</span>' + (newDisc.length > 0 ? '<br><span style="font-size:10px;color:var(--green)">+' + newDisc.join(', +') + '</span>' : ''); })() : '') + '</td>'
8280
8538
  + '<td class="mono">' + sessionShort + ' ' + lineageBadge + '<br><span style="font-size:10px;color:var(--muted)">' + msgCount + ' msgs</span></td>'
@@ -8407,12 +8665,7 @@ var landingHtml = `<!DOCTYPE html>
8407
8665
  <meta name="viewport" content="width=device-width, initial-scale=1">
8408
8666
  <title>Meridian</title>
8409
8667
  <style>
8410
- :root {
8411
- --bg: #0f0b1a; --surface: #1a1030; --surface2: #221840; --border: #2d2545;
8412
- --text: #e0e7ff; --muted: #8b8aa0; --accent: #8b5cf6; --accent2: #6366f1;
8413
- --green: #3fb950; --yellow: #d29922; --red: #f85149;
8414
- --violet: #a78bfa; --lavender: #c4b5fd;
8415
- }
8668
+ ${themeCss}
8416
8669
  * { box-sizing: border-box; margin: 0; padding: 0; }
8417
8670
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
8418
8671
  background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }
@@ -8722,6 +8975,16 @@ import { fileURLToPath as fileURLToPath2 } from "url";
8722
8975
  import { join as join2, dirname as dirname2 } from "path";
8723
8976
  import { promisify } from "util";
8724
8977
  var exec = promisify(execCallback);
8978
+ var CANONICAL_OPUS_MODEL = "claude-opus-4-7";
8979
+ var CANONICAL_SONNET_MODEL = "claude-sonnet-4-6";
8980
+ var CANONICAL_HAIKU_MODEL = "claude-haiku-4-5";
8981
+ function resolveSdkModelDefaults() {
8982
+ return {
8983
+ ANTHROPIC_DEFAULT_OPUS_MODEL: process.env.MERIDIAN_DEFAULT_OPUS_MODEL ?? CANONICAL_OPUS_MODEL,
8984
+ ANTHROPIC_DEFAULT_SONNET_MODEL: process.env.MERIDIAN_DEFAULT_SONNET_MODEL ?? CANONICAL_SONNET_MODEL,
8985
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: process.env.MERIDIAN_DEFAULT_HAIKU_MODEL ?? CANONICAL_HAIKU_MODEL
8986
+ };
8987
+ }
8725
8988
  var AUTH_STATUS_CACHE_TTL_MS = 60000;
8726
8989
  var AUTH_STATUS_FAILURE_TTL_MS = 5000;
8727
8990
  var cachedAuthStatus = null;
@@ -8858,16 +9121,14 @@ async function resolveClaudeExecutableAsync() {
8858
9121
  return cachedClaudePathPromise;
8859
9122
  cachedClaudePathPromise = (async () => {
8860
9123
  const runningUnderBun = typeof process.versions.bun !== "undefined";
8861
- if (runningUnderBun) {
8862
- try {
8863
- const sdkPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
8864
- const sdkCliJs = join2(dirname2(sdkPath), "cli.js");
8865
- if (existsSync2(sdkCliJs)) {
8866
- cachedClaudePath = sdkCliJs;
8867
- return sdkCliJs;
8868
- }
8869
- } catch {}
8870
- }
9124
+ try {
9125
+ const pkgPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-code/package.json"));
9126
+ const bundledBinary = join2(dirname2(pkgPath), "bin", "claude.exe");
9127
+ if (existsSync2(bundledBinary)) {
9128
+ cachedClaudePath = bundledBinary;
9129
+ return bundledBinary;
9130
+ }
9131
+ } catch {}
8871
9132
  try {
8872
9133
  const { stdout } = await exec("which claude");
8873
9134
  const claudePath = stdout.trim();
@@ -8876,7 +9137,7 @@ async function resolveClaudeExecutableAsync() {
8876
9137
  return claudePath;
8877
9138
  }
8878
9139
  } catch {}
8879
- if (!runningUnderBun) {
9140
+ if (runningUnderBun) {
8880
9141
  try {
8881
9142
  const sdkPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
8882
9143
  const sdkCliJs = join2(dirname2(sdkPath), "cli.js");
@@ -9089,7 +9350,15 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
9089
9350
  context_window: isMaxSubscription ? 1e6 : 200000
9090
9351
  },
9091
9352
  {
9092
- id: "claude-haiku-4-5-20251001",
9353
+ id: "claude-opus-4-7",
9354
+ object: "model",
9355
+ created: now,
9356
+ owned_by: "anthropic",
9357
+ display_name: "Claude Opus 4.7",
9358
+ context_window: isMaxSubscription ? 1e6 : 200000
9359
+ },
9360
+ {
9361
+ id: "claude-haiku-4-5",
9093
9362
  object: "model",
9094
9363
  created: now,
9095
9364
  owned_by: "anthropic",
@@ -9129,6 +9398,27 @@ function normalizeContent(content) {
9129
9398
  }
9130
9399
  return String(content);
9131
9400
  }
9401
+ function extractAdvisorModel(tools) {
9402
+ if (!Array.isArray(tools))
9403
+ return;
9404
+ for (const tool of tools) {
9405
+ if (!tool || typeof tool !== "object")
9406
+ continue;
9407
+ const candidate = tool;
9408
+ if (typeof candidate.type === "string" && candidate.type.startsWith("advisor_") && typeof candidate.model === "string" && candidate.model.length > 0) {
9409
+ return candidate.model;
9410
+ }
9411
+ }
9412
+ return;
9413
+ }
9414
+ function stripAdvisorTools(tools) {
9415
+ return tools.filter((tool) => {
9416
+ if (!tool || typeof tool !== "object")
9417
+ return true;
9418
+ const candidate = tool;
9419
+ return !(typeof candidate.type === "string" && candidate.type.startsWith("advisor_"));
9420
+ });
9421
+ }
9132
9422
  function getLastUserMessage(messages) {
9133
9423
  for (let i = messages.length - 1;i >= 0; i--) {
9134
9424
  if (messages[i]?.role === "user")
@@ -9365,103 +9655,84 @@ var ALLOWED_MCP_TOOLS = [
9365
9655
  `mcp__${MCP_SERVER_NAME}__grep`
9366
9656
  ];
9367
9657
 
9368
- // src/proxy/agentDefs.ts
9369
- function parseAgentDescriptions(taskDescription) {
9370
- const agents = new Map;
9371
- const agentSection = taskDescription.match(/Available agent types.*?:\n((?:- [\w][\w-]*:.*\n?)+)/s);
9372
- if (!agentSection)
9373
- return agents;
9374
- const entries = agentSection[1].matchAll(/^- ([\w][\w-]*):\s*(.+)/gm);
9375
- for (const match2 of entries) {
9376
- agents.set(match2[1], match2[2].trim());
9377
- }
9378
- return agents;
9379
- }
9380
- function buildAgentDefinitions(taskDescription, mcpToolNames) {
9381
- const descriptions = parseAgentDescriptions(taskDescription);
9382
- const agents = {};
9383
- for (const [name, description] of descriptions) {
9384
- agents[name] = {
9385
- description,
9386
- prompt: buildAgentPrompt(name, description),
9387
- model: "inherit",
9388
- ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
9389
- };
9390
- }
9391
- return agents;
9392
- }
9393
- function buildAgentPrompt(name, description) {
9394
- return `You are the "${name}" agent. ${description}
9395
-
9396
- Focus on your specific role and complete the task thoroughly. Return a clear, concise result.`;
9397
- }
9658
+ // src/proxy/transforms/opencode.ts
9659
+ var openCodeTransforms = [
9660
+ {
9661
+ name: "opencode-core",
9662
+ adapters: ["opencode"],
9663
+ onRequest(ctx) {
9664
+ const body = ctx.body;
9665
+ const blockedTools = BLOCKED_BUILTIN_TOOLS;
9666
+ const incompatibleTools = CLAUDE_CODE_ONLY_TOOLS;
9667
+ const allowedMcpTools = ALLOWED_MCP_TOOLS;
9668
+ const coreToolNames = ["read", "write", "edit", "bash", "glob", "grep"];
9669
+ const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
9670
+ const passthrough = !(envVal === "0" || envVal === "false" || envVal === "no");
9671
+ let sdkAgents = {};
9672
+ if (Array.isArray(body.tools)) {
9673
+ const taskTool = body.tools.find((t) => t.name === "task" || t.name === "Task");
9674
+ if (taskTool?.description) {
9675
+ sdkAgents = buildAgentDefinitions(taskTool.description, [...allowedMcpTools]);
9676
+ }
9677
+ }
9678
+ let sdkHooks = undefined;
9679
+ const validAgentNames = Object.keys(sdkAgents);
9680
+ if (validAgentNames.length > 0) {
9681
+ sdkHooks = {
9682
+ PreToolUse: [{
9683
+ matcher: "Task",
9684
+ hooks: [async (input) => ({
9685
+ hookSpecificOutput: {
9686
+ hookEventName: "PreToolUse",
9687
+ updatedInput: {
9688
+ ...input.tool_input,
9689
+ subagent_type: fuzzyMatchAgentName(String(input.tool_input?.subagent_type || ""), validAgentNames)
9690
+ }
9691
+ }
9692
+ })]
9693
+ }]
9694
+ };
9695
+ }
9696
+ let systemContext = ctx.systemContext;
9697
+ if (validAgentNames.length > 0 && systemContext !== undefined) {
9698
+ systemContext += `
9398
9699
 
9399
- // src/proxy/agentMatch.ts
9400
- var KNOWN_ALIASES = {
9401
- "general-purpose": "general",
9402
- default: "general",
9403
- "code-reviewer": "oracle",
9404
- reviewer: "oracle",
9405
- "code-review": "oracle",
9406
- review: "oracle",
9407
- consultation: "oracle",
9408
- analyzer: "oracle",
9409
- debugger: "oracle",
9410
- search: "explore",
9411
- grep: "explore",
9412
- find: "explore",
9413
- "codebase-search": "explore",
9414
- research: "librarian",
9415
- docs: "librarian",
9416
- documentation: "librarian",
9417
- lookup: "librarian",
9418
- reference: "librarian",
9419
- consult: "oracle",
9420
- architect: "oracle",
9421
- "image-analyzer": "multimodal-looker",
9422
- image: "multimodal-looker",
9423
- pdf: "multimodal-looker",
9424
- visual: "multimodal-looker",
9425
- planner: "plan",
9426
- planning: "plan",
9427
- builder: "build",
9428
- coder: "build",
9429
- developer: "build",
9430
- writer: "build",
9431
- executor: "build"
9432
- };
9433
- var STRIP_SUFFIXES = ["-agent", "-tool", "-worker", "-task", " agent", " tool"];
9434
- function fuzzyMatchAgentName(input, validAgents) {
9435
- if (!input)
9436
- return input;
9437
- if (validAgents.length === 0)
9438
- return input.toLowerCase();
9439
- const lowered = input.toLowerCase();
9440
- const exact = validAgents.find((a) => a.toLowerCase() === lowered);
9441
- if (exact)
9442
- return exact;
9443
- const alias = KNOWN_ALIASES[lowered];
9444
- if (alias && validAgents.includes(alias))
9445
- return alias;
9446
- const prefixMatch = validAgents.find((a) => a.toLowerCase().startsWith(lowered));
9447
- if (prefixMatch)
9448
- return prefixMatch;
9449
- const substringMatch = validAgents.find((a) => a.toLowerCase().includes(lowered));
9450
- if (substringMatch)
9451
- return substringMatch;
9452
- for (const suffix of STRIP_SUFFIXES) {
9453
- if (lowered.endsWith(suffix)) {
9454
- const stripped = lowered.slice(0, -suffix.length);
9455
- const strippedMatch = validAgents.find((a) => a.toLowerCase() === stripped);
9456
- if (strippedMatch)
9457
- return strippedMatch;
9700
+ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be one of these exact values (case-sensitive, lowercase): ${validAgentNames.join(", ")}. Do NOT capitalize or modify these names.`;
9701
+ } else if (validAgentNames.length > 0) {
9702
+ systemContext = `IMPORTANT: When using the task/Task tool, the subagent_type parameter must be one of these exact values (case-sensitive, lowercase): ${validAgentNames.join(", ")}. Do NOT capitalize or modify these names.`;
9703
+ }
9704
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
9705
+ const input = toolInput;
9706
+ const filePath = input?.filePath ?? input?.file_path ?? input?.path;
9707
+ const lowerName = toolName.toLowerCase();
9708
+ if (lowerName === "write" && filePath) {
9709
+ return [{ operation: "wrote", path: String(filePath) }];
9710
+ }
9711
+ if ((lowerName === "edit" || lowerName === "multiedit") && filePath) {
9712
+ return [{ operation: "edited", path: String(filePath) }];
9713
+ }
9714
+ if (lowerName === "bash" && input?.command) {
9715
+ return extractFileChangesFromBash(String(input.command));
9716
+ }
9717
+ return [];
9718
+ };
9719
+ return {
9720
+ ...ctx,
9721
+ blockedTools,
9722
+ incompatibleTools,
9723
+ allowedMcpTools,
9724
+ coreToolNames,
9725
+ passthrough,
9726
+ sdkAgents,
9727
+ sdkHooks,
9728
+ systemContext,
9729
+ supportsThinking: true,
9730
+ shouldTrackFileChanges: false,
9731
+ extractFileChangesFromToolUse
9732
+ };
9458
9733
  }
9459
9734
  }
9460
- const reverseMatch = validAgents.find((a) => lowered.includes(a.toLowerCase()));
9461
- if (reverseMatch)
9462
- return reverseMatch;
9463
- return lowered;
9464
- }
9735
+ ];
9465
9736
 
9466
9737
  // src/proxy/adapters/opencode.ts
9467
9738
  var openCodeAdapter = {
@@ -9555,7 +9826,7 @@ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be on
9555
9826
  }
9556
9827
  };
9557
9828
 
9558
- // src/proxy/adapters/droid.ts
9829
+ // src/proxy/transforms/droid.ts
9559
9830
  var DROID_MCP_SERVER_NAME = "droid";
9560
9831
  var DROID_ALLOWED_MCP_TOOLS = [
9561
9832
  `mcp__${DROID_MCP_SERVER_NAME}__read`,
@@ -9565,6 +9836,34 @@ var DROID_ALLOWED_MCP_TOOLS = [
9565
9836
  `mcp__${DROID_MCP_SERVER_NAME}__glob`,
9566
9837
  `mcp__${DROID_MCP_SERVER_NAME}__grep`
9567
9838
  ];
9839
+ var droidTransforms = [
9840
+ {
9841
+ name: "droid-core",
9842
+ adapters: ["droid"],
9843
+ onRequest(ctx) {
9844
+ return {
9845
+ ...ctx,
9846
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
9847
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
9848
+ allowedMcpTools: DROID_ALLOWED_MCP_TOOLS,
9849
+ sdkAgents: {},
9850
+ passthrough: false,
9851
+ leaksCwdViaSystemReminder: true
9852
+ };
9853
+ }
9854
+ }
9855
+ ];
9856
+
9857
+ // src/proxy/adapters/droid.ts
9858
+ var DROID_MCP_SERVER_NAME2 = "droid";
9859
+ var DROID_ALLOWED_MCP_TOOLS2 = [
9860
+ `mcp__${DROID_MCP_SERVER_NAME2}__read`,
9861
+ `mcp__${DROID_MCP_SERVER_NAME2}__write`,
9862
+ `mcp__${DROID_MCP_SERVER_NAME2}__edit`,
9863
+ `mcp__${DROID_MCP_SERVER_NAME2}__bash`,
9864
+ `mcp__${DROID_MCP_SERVER_NAME2}__glob`,
9865
+ `mcp__${DROID_MCP_SERVER_NAME2}__grep`
9866
+ ];
9568
9867
  function extractDroidCwd(body) {
9569
9868
  const messages = body.messages;
9570
9869
  if (!Array.isArray(messages))
@@ -9604,10 +9903,10 @@ var droidAdapter = {
9604
9903
  return CLAUDE_CODE_ONLY_TOOLS;
9605
9904
  },
9606
9905
  getMcpServerName() {
9607
- return DROID_MCP_SERVER_NAME;
9906
+ return DROID_MCP_SERVER_NAME2;
9608
9907
  },
9609
9908
  getAllowedMcpTools() {
9610
- return DROID_ALLOWED_MCP_TOOLS;
9909
+ return DROID_ALLOWED_MCP_TOOLS2;
9611
9910
  },
9612
9911
  buildSdkAgents(_body, _mcpToolNames) {
9613
9912
  return {};
@@ -9623,7 +9922,7 @@ var droidAdapter = {
9623
9922
  }
9624
9923
  };
9625
9924
 
9626
- // src/proxy/adapters/crush.ts
9925
+ // src/proxy/transforms/crush.ts
9627
9926
  var CRUSH_MCP_SERVER_NAME = "crush";
9628
9927
  var CRUSH_ALLOWED_MCP_TOOLS = [
9629
9928
  `mcp__${CRUSH_MCP_SERVER_NAME}__read`,
@@ -9633,6 +9932,45 @@ var CRUSH_ALLOWED_MCP_TOOLS = [
9633
9932
  `mcp__${CRUSH_MCP_SERVER_NAME}__glob`,
9634
9933
  `mcp__${CRUSH_MCP_SERVER_NAME}__grep`
9635
9934
  ];
9935
+ var crushTransforms = [
9936
+ {
9937
+ name: "crush-core",
9938
+ adapters: ["crush"],
9939
+ onRequest(ctx) {
9940
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
9941
+ const input = toolInput;
9942
+ const filePath = input?.file_path ?? input?.path;
9943
+ if (toolName === "write" && filePath)
9944
+ return [{ operation: "wrote", path: String(filePath) }];
9945
+ if ((toolName === "edit" || toolName === "patch") && filePath)
9946
+ return [{ operation: "edited", path: String(filePath) }];
9947
+ if (toolName === "bash" && input?.command)
9948
+ return extractFileChangesFromBash(String(input.command));
9949
+ return [];
9950
+ };
9951
+ return {
9952
+ ...ctx,
9953
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
9954
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
9955
+ allowedMcpTools: CRUSH_ALLOWED_MCP_TOOLS,
9956
+ sdkAgents: {},
9957
+ supportsThinking: true,
9958
+ extractFileChangesFromToolUse
9959
+ };
9960
+ }
9961
+ }
9962
+ ];
9963
+
9964
+ // src/proxy/adapters/crush.ts
9965
+ var CRUSH_MCP_SERVER_NAME2 = "crush";
9966
+ var CRUSH_ALLOWED_MCP_TOOLS2 = [
9967
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__read`,
9968
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__write`,
9969
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__edit`,
9970
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__bash`,
9971
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__glob`,
9972
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__grep`
9973
+ ];
9636
9974
  var crushAdapter = {
9637
9975
  name: "crush",
9638
9976
  getSessionId(_c) {
@@ -9651,10 +9989,10 @@ var crushAdapter = {
9651
9989
  return CLAUDE_CODE_ONLY_TOOLS;
9652
9990
  },
9653
9991
  getMcpServerName() {
9654
- return CRUSH_MCP_SERVER_NAME;
9992
+ return CRUSH_MCP_SERVER_NAME2;
9655
9993
  },
9656
9994
  getAllowedMcpTools() {
9657
- return CRUSH_ALLOWED_MCP_TOOLS;
9995
+ return CRUSH_ALLOWED_MCP_TOOLS2;
9658
9996
  },
9659
9997
  buildSdkAgents(_body, _mcpToolNames) {
9660
9998
  return {};
@@ -9684,7 +10022,7 @@ var crushAdapter = {
9684
10022
  }
9685
10023
  };
9686
10024
 
9687
- // src/proxy/adapters/passthrough.ts
10025
+ // src/proxy/transforms/passthrough.ts
9688
10026
  var MCP_SERVER_NAME2 = "litellm";
9689
10027
  var ALLOWED_MCP_TOOLS2 = [
9690
10028
  `mcp__${MCP_SERVER_NAME2}__read`,
@@ -9694,6 +10032,34 @@ var ALLOWED_MCP_TOOLS2 = [
9694
10032
  `mcp__${MCP_SERVER_NAME2}__glob`,
9695
10033
  `mcp__${MCP_SERVER_NAME2}__grep`
9696
10034
  ];
10035
+ var passthroughTransforms = [
10036
+ {
10037
+ name: "passthrough-core",
10038
+ adapters: ["passthrough"],
10039
+ onRequest(ctx) {
10040
+ return {
10041
+ ...ctx,
10042
+ blockedTools: [],
10043
+ incompatibleTools: [],
10044
+ allowedMcpTools: ALLOWED_MCP_TOOLS2,
10045
+ sdkAgents: {},
10046
+ passthrough: true,
10047
+ prefersStreaming: ctx.body?.stream === true
10048
+ };
10049
+ }
10050
+ }
10051
+ ];
10052
+
10053
+ // src/proxy/adapters/passthrough.ts
10054
+ var MCP_SERVER_NAME3 = "litellm";
10055
+ var ALLOWED_MCP_TOOLS3 = [
10056
+ `mcp__${MCP_SERVER_NAME3}__read`,
10057
+ `mcp__${MCP_SERVER_NAME3}__write`,
10058
+ `mcp__${MCP_SERVER_NAME3}__edit`,
10059
+ `mcp__${MCP_SERVER_NAME3}__bash`,
10060
+ `mcp__${MCP_SERVER_NAME3}__glob`,
10061
+ `mcp__${MCP_SERVER_NAME3}__grep`
10062
+ ];
9697
10063
  function extractCwdFromBody(body) {
9698
10064
  if (!body)
9699
10065
  return;
@@ -9741,10 +10107,10 @@ var passthroughAdapter = {
9741
10107
  return [];
9742
10108
  },
9743
10109
  getMcpServerName() {
9744
- return MCP_SERVER_NAME2;
10110
+ return MCP_SERVER_NAME3;
9745
10111
  },
9746
10112
  getAllowedMcpTools() {
9747
- return ALLOWED_MCP_TOOLS2;
10113
+ return ALLOWED_MCP_TOOLS3;
9748
10114
  },
9749
10115
  buildSdkAgents(_body, _mcpToolNames) {
9750
10116
  return {};
@@ -9763,7 +10129,7 @@ var passthroughAdapter = {
9763
10129
  }
9764
10130
  };
9765
10131
 
9766
- // src/proxy/adapters/pi.ts
10132
+ // src/proxy/transforms/pi.ts
9767
10133
  var PI_MCP_SERVER_NAME = "pi";
9768
10134
  var PI_ALLOWED_MCP_TOOLS = [
9769
10135
  `mcp__${PI_MCP_SERVER_NAME}__read`,
@@ -9773,6 +10139,45 @@ var PI_ALLOWED_MCP_TOOLS = [
9773
10139
  `mcp__${PI_MCP_SERVER_NAME}__glob`,
9774
10140
  `mcp__${PI_MCP_SERVER_NAME}__grep`
9775
10141
  ];
10142
+ var piTransforms = [
10143
+ {
10144
+ name: "pi-core",
10145
+ adapters: ["pi"],
10146
+ onRequest(ctx) {
10147
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
10148
+ const input = toolInput;
10149
+ const filePath = input?.filePath ?? input?.file_path ?? input?.path;
10150
+ if (toolName === "write" && filePath)
10151
+ return [{ operation: "wrote", path: String(filePath) }];
10152
+ if (toolName === "edit" && filePath)
10153
+ return [{ operation: "edited", path: String(filePath) }];
10154
+ if (toolName === "bash" && input?.command)
10155
+ return extractFileChangesFromBash(String(input.command));
10156
+ return [];
10157
+ };
10158
+ return {
10159
+ ...ctx,
10160
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
10161
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
10162
+ allowedMcpTools: PI_ALLOWED_MCP_TOOLS,
10163
+ sdkAgents: {},
10164
+ supportsThinking: true,
10165
+ extractFileChangesFromToolUse
10166
+ };
10167
+ }
10168
+ }
10169
+ ];
10170
+
10171
+ // src/proxy/adapters/pi.ts
10172
+ var PI_MCP_SERVER_NAME2 = "pi";
10173
+ var PI_ALLOWED_MCP_TOOLS2 = [
10174
+ `mcp__${PI_MCP_SERVER_NAME2}__read`,
10175
+ `mcp__${PI_MCP_SERVER_NAME2}__write`,
10176
+ `mcp__${PI_MCP_SERVER_NAME2}__edit`,
10177
+ `mcp__${PI_MCP_SERVER_NAME2}__bash`,
10178
+ `mcp__${PI_MCP_SERVER_NAME2}__glob`,
10179
+ `mcp__${PI_MCP_SERVER_NAME2}__grep`
10180
+ ];
9776
10181
  function extractPiCwd(body) {
9777
10182
  let systemText = "";
9778
10183
  if (typeof body.system === "string") {
@@ -9794,6 +10199,9 @@ var piAdapter = {
9794
10199
  extractWorkingDirectory(body) {
9795
10200
  return extractPiCwd(body);
9796
10201
  },
10202
+ extractClientWorkingDirectory(body) {
10203
+ return extractPiCwd(body);
10204
+ },
9797
10205
  normalizeContent(content) {
9798
10206
  return normalizeContent(content);
9799
10207
  },
@@ -9804,10 +10212,10 @@ var piAdapter = {
9804
10212
  return CLAUDE_CODE_ONLY_TOOLS;
9805
10213
  },
9806
10214
  getMcpServerName() {
9807
- return PI_MCP_SERVER_NAME;
10215
+ return PI_MCP_SERVER_NAME2;
9808
10216
  },
9809
10217
  getAllowedMcpTools() {
9810
- return PI_ALLOWED_MCP_TOOLS;
10218
+ return PI_ALLOWED_MCP_TOOLS2;
9811
10219
  },
9812
10220
  buildSdkAgents(_body, _mcpToolNames) {
9813
10221
  return {};
@@ -9837,7 +10245,7 @@ var piAdapter = {
9837
10245
  }
9838
10246
  };
9839
10247
 
9840
- // src/proxy/adapters/forgecode.ts
10248
+ // src/proxy/transforms/forgecode.ts
9841
10249
  var FORGECODE_MCP_SERVER_NAME = "forgecode";
9842
10250
  var FORGECODE_ALLOWED_MCP_TOOLS = [
9843
10251
  `mcp__${FORGECODE_MCP_SERVER_NAME}__read`,
@@ -9847,6 +10255,44 @@ var FORGECODE_ALLOWED_MCP_TOOLS = [
9847
10255
  `mcp__${FORGECODE_MCP_SERVER_NAME}__glob`,
9848
10256
  `mcp__${FORGECODE_MCP_SERVER_NAME}__grep`
9849
10257
  ];
10258
+ var forgeCodeTransforms = [
10259
+ {
10260
+ name: "forgecode-core",
10261
+ adapters: ["forgecode"],
10262
+ onRequest(ctx) {
10263
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
10264
+ const input = toolInput;
10265
+ const filePath = input?.file_path ?? input?.filePath ?? input?.path;
10266
+ if (toolName === "write" && filePath)
10267
+ return [{ operation: "wrote", path: String(filePath) }];
10268
+ if ((toolName === "patch" || toolName === "multi_patch") && filePath)
10269
+ return [{ operation: "edited", path: String(filePath) }];
10270
+ if (toolName === "shell" && input?.command)
10271
+ return extractFileChangesFromBash(String(input.command));
10272
+ return [];
10273
+ };
10274
+ return {
10275
+ ...ctx,
10276
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
10277
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
10278
+ allowedMcpTools: FORGECODE_ALLOWED_MCP_TOOLS,
10279
+ sdkAgents: {},
10280
+ extractFileChangesFromToolUse
10281
+ };
10282
+ }
10283
+ }
10284
+ ];
10285
+
10286
+ // src/proxy/adapters/forgecode.ts
10287
+ var FORGECODE_MCP_SERVER_NAME2 = "forgecode";
10288
+ var FORGECODE_ALLOWED_MCP_TOOLS2 = [
10289
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__read`,
10290
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__write`,
10291
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__edit`,
10292
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__bash`,
10293
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__glob`,
10294
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__grep`
10295
+ ];
9850
10296
  function extractForgeCodeCwd(body) {
9851
10297
  let systemText = "";
9852
10298
  if (typeof body.system === "string") {
@@ -9878,10 +10324,10 @@ var forgeCodeAdapter = {
9878
10324
  return CLAUDE_CODE_ONLY_TOOLS;
9879
10325
  },
9880
10326
  getMcpServerName() {
9881
- return FORGECODE_MCP_SERVER_NAME;
10327
+ return FORGECODE_MCP_SERVER_NAME2;
9882
10328
  },
9883
10329
  getAllowedMcpTools() {
9884
- return FORGECODE_ALLOWED_MCP_TOOLS;
10330
+ return FORGECODE_ALLOWED_MCP_TOOLS2;
9885
10331
  },
9886
10332
  buildSdkAgents(_body, _mcpToolNames) {
9887
10333
  return {};
@@ -9908,6 +10354,79 @@ var forgeCodeAdapter = {
9908
10354
  }
9909
10355
  };
9910
10356
 
10357
+ // src/proxy/adapters/claudecode.ts
10358
+ function extractClaudeCodeClientCwd(body) {
10359
+ let systemText = "";
10360
+ if (typeof body.system === "string") {
10361
+ systemText = body.system;
10362
+ } else if (Array.isArray(body.system)) {
10363
+ systemText = body.system.filter((b) => b.type === "text" && b.text).map((b) => b.text).join(`
10364
+ `);
10365
+ }
10366
+ if (!systemText)
10367
+ return;
10368
+ const match2 = systemText.match(/Primary working directory:\s*([^\n<]+)/i);
10369
+ return match2?.[1]?.trim() || undefined;
10370
+ }
10371
+ var claudeCodeAdapter = {
10372
+ name: "claude-code",
10373
+ getSessionId(_c) {
10374
+ return;
10375
+ },
10376
+ extractWorkingDirectory(_body) {
10377
+ return;
10378
+ },
10379
+ extractClientWorkingDirectory(body) {
10380
+ return extractClaudeCodeClientCwd(body);
10381
+ },
10382
+ normalizeContent(content) {
10383
+ return normalizeContent(content);
10384
+ },
10385
+ getBlockedBuiltinTools() {
10386
+ return BLOCKED_BUILTIN_TOOLS;
10387
+ },
10388
+ getAgentIncompatibleTools() {
10389
+ return CLAUDE_CODE_ONLY_TOOLS;
10390
+ },
10391
+ getMcpServerName() {
10392
+ return MCP_SERVER_NAME;
10393
+ },
10394
+ getAllowedMcpTools() {
10395
+ return ALLOWED_MCP_TOOLS;
10396
+ },
10397
+ getCoreToolNames() {
10398
+ return ["Read", "Write", "Edit", "Bash", "Glob", "Grep"];
10399
+ },
10400
+ usesPassthrough() {
10401
+ const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10402
+ if (envVal === "0" || envVal === "false" || envVal === "no") {
10403
+ return false;
10404
+ }
10405
+ return true;
10406
+ },
10407
+ supportsThinking() {
10408
+ return true;
10409
+ },
10410
+ shouldTrackFileChanges() {
10411
+ return false;
10412
+ },
10413
+ extractFileChangesFromToolUse(toolName, toolInput) {
10414
+ const input = toolInput;
10415
+ const filePath = input?.file_path ?? input?.filePath ?? input?.path;
10416
+ const lowerName = toolName.toLowerCase();
10417
+ if (lowerName === "write" && filePath) {
10418
+ return [{ operation: "wrote", path: String(filePath) }];
10419
+ }
10420
+ if ((lowerName === "edit" || lowerName === "multiedit") && filePath) {
10421
+ return [{ operation: "edited", path: String(filePath) }];
10422
+ }
10423
+ if (lowerName === "bash" && input?.command) {
10424
+ return extractFileChangesFromBash(String(input.command));
10425
+ }
10426
+ return [];
10427
+ }
10428
+ };
10429
+
9911
10430
  // src/proxy/adapters/detect.ts
9912
10431
  var ADAPTER_MAP = {
9913
10432
  opencode: openCodeAdapter,
@@ -9915,7 +10434,9 @@ var ADAPTER_MAP = {
9915
10434
  crush: crushAdapter,
9916
10435
  passthrough: passthroughAdapter,
9917
10436
  pi: piAdapter,
9918
- forgecode: forgeCodeAdapter
10437
+ forgecode: forgeCodeAdapter,
10438
+ "claude-code": claudeCodeAdapter,
10439
+ claudecode: claudeCodeAdapter
9919
10440
  };
9920
10441
  var envDefault = process.env.MERIDIAN_DEFAULT_AGENT || "";
9921
10442
  if (envDefault && !ADAPTER_MAP[envDefault]) {
@@ -9946,6 +10467,9 @@ function detectAdapter(c) {
9946
10467
  if (userAgent.startsWith("Charm-Crush/")) {
9947
10468
  return crushAdapter;
9948
10469
  }
10470
+ if (userAgent.startsWith("claude-cli/")) {
10471
+ return claudeCodeAdapter;
10472
+ }
9949
10473
  if (isLiteLLMRequest(c)) {
9950
10474
  return passthroughAdapter;
9951
10475
  }
@@ -15648,16 +16172,35 @@ function createOpencodeMcpServer() {
15648
16172
  }
15649
16173
 
15650
16174
  // src/proxy/query.ts
15651
- function resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt) {
16175
+ function computePassthroughMaxTurns(resumeSessionId, hasDeferredTools, advisorModel) {
16176
+ const hasResume = !!resumeSessionId;
16177
+ const base = hasResume && hasDeferredTools ? 4 : hasResume || hasDeferredTools ? 3 : 2;
16178
+ const advisorBump = advisorModel ? 3 : 0;
16179
+ return base + advisorBump;
16180
+ }
16181
+ function buildCwdNote(sdkCwd, clientCwd) {
16182
+ if (!clientCwd || clientCwd === sdkCwd)
16183
+ return "";
16184
+ return `
16185
+
16186
+ <env>
16187
+ ` + `Working directory: ${clientCwd}
16188
+ ` + `</env>
16189
+ ` + `<meridian-note>
16190
+ ` + `You are reached through a proxy. The subprocess running you resides at ` + `"${sdkCwd}" on the proxy host, but that is not the user's working directory. ` + `Always treat "${clientCwd}" as the working directory when referring to files or paths.
16191
+ ` + `</meridian-note>`;
16192
+ }
16193
+ function resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt, cwdNote) {
15652
16194
  const hasSettings = settingSources != null && settingSources.length > 0;
15653
16195
  const usePreset = codeSystemPrompt ?? (hasSettings || !passthrough && !!systemContext);
15654
16196
  const includeClient = clientSystemPrompt ?? true;
15655
16197
  const clientContext = includeClient ? systemContext : undefined;
16198
+ const append = [clientContext, cwdNote].filter(Boolean).join("") || undefined;
15656
16199
  if (usePreset) {
15657
- return clientContext ? { systemPrompt: { type: "preset", preset: "claude_code", append: clientContext } } : { systemPrompt: { type: "preset", preset: "claude_code" } };
16200
+ return append ? { systemPrompt: { type: "preset", preset: "claude_code", append } } : { systemPrompt: { type: "preset", preset: "claude_code" } };
15658
16201
  }
15659
- if (clientContext)
15660
- return { systemPrompt: clientContext };
16202
+ if (append)
16203
+ return { systemPrompt: append };
15661
16204
  return {};
15662
16205
  }
15663
16206
  function buildQueryOptions(ctx) {
@@ -15665,6 +16208,7 @@ function buildQueryOptions(ctx) {
15665
16208
  prompt,
15666
16209
  model,
15667
16210
  workingDirectory,
16211
+ clientWorkingDirectory,
15668
16212
  systemContext,
15669
16213
  claudeExecutable,
15670
16214
  passthrough,
@@ -15677,7 +16221,10 @@ function buildQueryOptions(ctx) {
15677
16221
  isUndo,
15678
16222
  undoRollbackUuid,
15679
16223
  sdkHooks,
15680
- adapter,
16224
+ blockedTools,
16225
+ incompatibleTools,
16226
+ mcpServerName,
16227
+ allowedMcpTools,
15681
16228
  onStderr,
15682
16229
  effort,
15683
16230
  thinking,
@@ -15694,30 +16241,29 @@ function buildQueryOptions(ctx) {
15694
16241
  sdkDebug,
15695
16242
  additionalDirectories
15696
16243
  } = ctx;
15697
- const blockedTools = [...adapter.getBlockedBuiltinTools(), ...adapter.getAgentIncompatibleTools()];
15698
- const mcpServerName = adapter.getMcpServerName();
15699
- const allowedMcpTools = [...adapter.getAllowedMcpTools()];
16244
+ const cwdNote = buildCwdNote(workingDirectory, clientWorkingDirectory);
16245
+ const allBlockedTools = [...blockedTools, ...incompatibleTools];
15700
16246
  return {
15701
16247
  prompt,
15702
16248
  options: {
15703
16249
  executable: "node",
15704
- maxTurns: passthrough ? resumeSessionId || hasDeferredTools ? 3 : 2 : 200,
16250
+ maxTurns: passthrough ? computePassthroughMaxTurns(resumeSessionId, hasDeferredTools, ctx.advisorModel) : 200,
15705
16251
  cwd: workingDirectory,
15706
16252
  model,
15707
16253
  pathToClaudeCodeExecutable: claudeExecutable,
15708
16254
  ...stream2 ? { includePartialMessages: true } : {},
15709
16255
  permissionMode: "bypassPermissions",
15710
16256
  allowDangerouslySkipPermissions: true,
15711
- ...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt),
16257
+ ...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt, cwdNote),
15712
16258
  ...passthrough ? {
15713
- disallowedTools: blockedTools,
16259
+ disallowedTools: [...allBlockedTools],
15714
16260
  ...passthroughMcp ? {
15715
- allowedTools: passthroughMcp.toolNames,
16261
+ allowedTools: [...passthroughMcp.toolNames],
15716
16262
  mcpServers: { [PASSTHROUGH_MCP_NAME]: passthroughMcp.server }
15717
16263
  } : {}
15718
16264
  } : {
15719
- disallowedTools: blockedTools,
15720
- allowedTools: allowedMcpTools,
16265
+ disallowedTools: [...allBlockedTools],
16266
+ allowedTools: [...allowedMcpTools],
15721
16267
  mcpServers: { [mcpServerName]: createOpencodeMcpServer() }
15722
16268
  },
15723
16269
  plugins: [],
@@ -15747,11 +16293,182 @@ function buildQueryOptions(ctx) {
15747
16293
  ...maxBudgetUsd && maxBudgetUsd > 0 ? { maxBudgetUsd } : {},
15748
16294
  ...fallbackModel ? { fallbackModel } : {},
15749
16295
  ...sdkDebug ? { debug: true } : {},
15750
- ...additionalDirectories && additionalDirectories.length > 0 ? { additionalDirectories } : {}
16296
+ ...additionalDirectories && additionalDirectories.length > 0 ? { additionalDirectories } : {},
16297
+ ...ctx.advisorModel ? { advisorModel: ctx.advisorModel } : {}
16298
+ }
16299
+ };
16300
+ }
16301
+
16302
+ // src/proxy/transforms/registry.ts
16303
+ var ADAPTER_TRANSFORMS = {
16304
+ opencode: openCodeTransforms,
16305
+ crush: crushTransforms,
16306
+ droid: droidTransforms,
16307
+ pi: piTransforms,
16308
+ forgecode: forgeCodeTransforms,
16309
+ passthrough: passthroughTransforms
16310
+ };
16311
+ function getAdapterTransforms(adapterName) {
16312
+ return ADAPTER_TRANSFORMS[adapterName] ?? [];
16313
+ }
16314
+
16315
+ // src/proxy/plugins/loader.ts
16316
+ import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
16317
+ import { join as join4, isAbsolute as isAbsolute2, extname } from "path";
16318
+
16319
+ // src/proxy/plugins/validation.ts
16320
+ var KNOWN_ADAPTERS = ["opencode", "crush", "droid", "pi", "forgecode", "passthrough"];
16321
+ var KNOWN_HOOKS = ["onRequest", "onResponse", "onTelemetry", "onSession", "onToolUse", "onToolResult", "onError"];
16322
+ function validateTransform(exported) {
16323
+ if (exported == null || typeof exported !== "object") {
16324
+ return { valid: false, hooks: [], error: "Plugin must export an object" };
16325
+ }
16326
+ const obj = exported;
16327
+ if (typeof obj.name !== "string" || obj.name.length === 0) {
16328
+ return { valid: false, hooks: [], error: "Plugin must have a name: string property" };
16329
+ }
16330
+ const hooks = [];
16331
+ for (const hook of KNOWN_HOOKS) {
16332
+ if (obj[hook] !== undefined) {
16333
+ if (typeof obj[hook] !== "function") {
16334
+ return { valid: false, hooks: [], error: `${hook} must be a function, got ${typeof obj[hook]}` };
16335
+ }
16336
+ hooks.push(hook);
16337
+ }
16338
+ }
16339
+ const warnings = [];
16340
+ if (Array.isArray(obj.adapters)) {
16341
+ for (const adapter of obj.adapters) {
16342
+ if (typeof adapter === "string" && !KNOWN_ADAPTERS.includes(adapter)) {
16343
+ warnings.push(adapter);
16344
+ }
15751
16345
  }
16346
+ }
16347
+ return {
16348
+ valid: true,
16349
+ hooks,
16350
+ ...warnings.length > 0 ? { warnings } : {}
15752
16351
  };
15753
16352
  }
15754
16353
 
16354
+ // src/proxy/plugins/loader.ts
16355
+ var loadCounter = 0;
16356
+ function parsePluginConfig(configPath) {
16357
+ if (!existsSync3(configPath))
16358
+ return [];
16359
+ try {
16360
+ const raw2 = readFileSync2(configPath, "utf-8");
16361
+ const parsed = JSON.parse(raw2);
16362
+ return Array.isArray(parsed.plugins) ? parsed.plugins : [];
16363
+ } catch {
16364
+ return [];
16365
+ }
16366
+ }
16367
+ async function loadPlugins(pluginDir, configPath) {
16368
+ resetAllPluginStats();
16369
+ const config = configPath ? parsePluginConfig(configPath) : [];
16370
+ const pluginDirExists = existsSync3(pluginDir);
16371
+ let filenames = [];
16372
+ if (pluginDirExists) {
16373
+ try {
16374
+ filenames = readdirSync2(pluginDir).filter((f) => {
16375
+ const ext2 = extname(f);
16376
+ return ext2 === ".ts" || ext2 === ".js";
16377
+ });
16378
+ } catch {
16379
+ filenames = [];
16380
+ }
16381
+ }
16382
+ if (!pluginDirExists && config.length === 0)
16383
+ return [];
16384
+ const ordered = [];
16385
+ const seen = new Set;
16386
+ for (const entry of config) {
16387
+ const filename = isAbsolute2(entry.path) ? entry.path : entry.path;
16388
+ if (filenames.includes(filename) || isAbsolute2(entry.path)) {
16389
+ ordered.push({ filename, entry });
16390
+ seen.add(filename);
16391
+ }
16392
+ }
16393
+ for (const filename of filenames) {
16394
+ if (!seen.has(filename)) {
16395
+ ordered.push({ filename });
16396
+ }
16397
+ }
16398
+ const loaded = [];
16399
+ const seenNames = new Set;
16400
+ for (const { filename, entry } of ordered) {
16401
+ const filePath = isAbsolute2(filename) ? filename : join4(pluginDir, filename);
16402
+ if (entry && !entry.enabled) {
16403
+ loaded.push({
16404
+ name: filename,
16405
+ status: "disabled",
16406
+ hooks: [],
16407
+ path: filePath,
16408
+ transform: { name: filename }
16409
+ });
16410
+ continue;
16411
+ }
16412
+ try {
16413
+ const cacheBuster = `?t=${Date.now()}-${++loadCounter}`;
16414
+ const mod = await import(filePath + cacheBuster);
16415
+ const exported = mod.default ?? mod;
16416
+ const transforms = Array.isArray(exported) ? exported : [exported];
16417
+ for (const item of transforms) {
16418
+ const validation = validateTransform(item);
16419
+ if (!validation.valid) {
16420
+ loaded.push({
16421
+ name: filename,
16422
+ status: "error",
16423
+ error: validation.error,
16424
+ hooks: [],
16425
+ path: filePath,
16426
+ transform: { name: filename }
16427
+ });
16428
+ continue;
16429
+ }
16430
+ const transform = item;
16431
+ if (seenNames.has(transform.name)) {
16432
+ loaded.push({
16433
+ name: transform.name,
16434
+ status: "error",
16435
+ error: `Skipped: duplicate plugin name "${transform.name}"`,
16436
+ hooks: validation.hooks,
16437
+ path: filePath,
16438
+ transform
16439
+ });
16440
+ continue;
16441
+ }
16442
+ seenNames.add(transform.name);
16443
+ registerPluginStats(transform.name);
16444
+ loaded.push({
16445
+ name: transform.name,
16446
+ description: transform.description,
16447
+ version: transform.version,
16448
+ adapters: transform.adapters,
16449
+ hooks: validation.hooks,
16450
+ status: "active",
16451
+ path: filePath,
16452
+ transform
16453
+ });
16454
+ }
16455
+ } catch (err) {
16456
+ loaded.push({
16457
+ name: filename,
16458
+ status: "error",
16459
+ error: `Failed to load: ${err instanceof Error ? err.message : String(err)}`,
16460
+ hooks: [],
16461
+ path: filePath,
16462
+ transform: { name: filename }
16463
+ });
16464
+ }
16465
+ }
16466
+ return loaded;
16467
+ }
16468
+ function getActiveTransforms(plugins) {
16469
+ return plugins.filter((p) => p.status === "active").map((p) => p.transform);
16470
+ }
16471
+
15755
16472
  // src/proxy/betas.ts
15756
16473
  var BILLABLE_BETA_PREFIXES_ON_MAX = [
15757
16474
  "extended-cache-ttl-"
@@ -15917,6 +16634,10 @@ function sanitizeTextContent(text, opts = {}) {
15917
16634
 
15918
16635
  // src/proxy/session/lineage.ts
15919
16636
  import { createHash as createHash2 } from "crypto";
16637
+ function normalizeContextUsage(usage) {
16638
+ const lastIteration = usage.iterations?.at(-1);
16639
+ return lastIteration ?? usage;
16640
+ }
15920
16641
  var MIN_SUFFIX_FOR_COMPACTION = 2;
15921
16642
  function computeLineageHash(messages) {
15922
16643
  if (!messages || messages.length === 0)
@@ -16048,17 +16769,17 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
16048
16769
  // src/proxy/sessionStore.ts
16049
16770
  import {
16050
16771
  closeSync,
16051
- existsSync as existsSync3,
16772
+ existsSync as existsSync4,
16052
16773
  mkdirSync,
16053
16774
  openSync,
16054
- readFileSync as readFileSync2,
16775
+ readFileSync as readFileSync3,
16055
16776
  renameSync,
16056
16777
  statSync,
16057
16778
  unlinkSync,
16058
16779
  writeFileSync
16059
16780
  } from "node:fs";
16060
16781
  import { homedir as homedir3 } from "node:os";
16061
- import { join as join4 } from "node:path";
16782
+ import { join as join5 } from "node:path";
16062
16783
  var DEFAULT_MAX_STORED_SESSIONS = 1e4;
16063
16784
  var STALE_LOCK_THRESHOLD_MS = 30000;
16064
16785
  function getMaxStoredSessions() {
@@ -16106,17 +16827,17 @@ var sessionDirOverride = null;
16106
16827
  var skipLocking = false;
16107
16828
  function getStorePath() {
16108
16829
  const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
16109
- if (!existsSync3(dir)) {
16830
+ if (!existsSync4(dir)) {
16110
16831
  mkdirSync(dir, { recursive: true });
16111
16832
  }
16112
- return join4(dir, "sessions.json");
16833
+ return join5(dir, "sessions.json");
16113
16834
  }
16114
16835
  function getDefaultCacheDir() {
16115
- const newDir = join4(homedir3(), ".cache", "meridian");
16116
- const oldDir = join4(homedir3(), ".cache", "opencode-claude-max-proxy");
16117
- if (existsSync3(newDir))
16836
+ const newDir = join5(homedir3(), ".cache", "meridian");
16837
+ const oldDir = join5(homedir3(), ".cache", "opencode-claude-max-proxy");
16838
+ if (existsSync4(newDir))
16118
16839
  return newDir;
16119
- if (existsSync3(oldDir)) {
16840
+ if (existsSync4(oldDir)) {
16120
16841
  try {
16121
16842
  const { symlinkSync } = __require("fs");
16122
16843
  symlinkSync(oldDir, newDir);
@@ -16129,10 +16850,10 @@ function getDefaultCacheDir() {
16129
16850
  }
16130
16851
  function readStore() {
16131
16852
  const path3 = getStorePath();
16132
- if (!existsSync3(path3))
16853
+ if (!existsSync4(path3))
16133
16854
  return {};
16134
16855
  try {
16135
- const data = readFileSync2(path3, "utf-8");
16856
+ const data = readFileSync3(path3, "utf-8");
16136
16857
  return JSON.parse(data);
16137
16858
  } catch (e) {
16138
16859
  console.error("[sessionStore] read failed:", e.message);
@@ -16631,6 +17352,10 @@ function createProxyServer(config = {}) {
16631
17352
  const sessionDiscoveredTools = new Map;
16632
17353
  const sessionToolCache = new Map;
16633
17354
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
17355
+ const pluginDir = finalConfig.pluginDir ?? join7(homedir5(), ".config", "meridian", "plugins");
17356
+ const pluginConfigPath = finalConfig.pluginConfigPath ?? join7(homedir5(), ".config", "meridian", "plugins.json");
17357
+ let loadedPlugins = [];
17358
+ let pluginTransforms = [];
16634
17359
  const app = new Hono2;
16635
17360
  app.use("*", cors());
16636
17361
  app.use("/v1/*", requireAuth);
@@ -16640,6 +17365,8 @@ function createProxyServer(config = {}) {
16640
17365
  app.use("/metrics", requireAuth);
16641
17366
  app.use("/profiles/*", requireAuth);
16642
17367
  app.use("/profiles", requireAuth);
17368
+ app.use("/plugins/*", requireAuth);
17369
+ app.use("/plugins", requireAuth);
16643
17370
  app.use("/auth/*", requireAuth);
16644
17371
  app.get("/", (c) => {
16645
17372
  const accept = c.req.header("accept") || "";
@@ -16697,9 +17424,8 @@ function createProxyServer(config = {}) {
16697
17424
  const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
16698
17425
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
16699
17426
  let model = mapModelToClaudeModel(body.model || "sonnet", authStatus?.subscriptionType, agentMode);
16700
- const adapterStreamPref = adapter.prefersStreaming?.(body);
16701
- const stream2 = adapterStreamPref !== undefined ? adapterStreamPref : body.stream ?? false;
16702
17427
  const workingDirectory = (process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR) || adapter.extractWorkingDirectory(body) || process.cwd();
17428
+ const clientWorkingDirectory = adapter.extractClientWorkingDirectory?.(body) || workingDirectory;
16703
17429
  const {
16704
17430
  CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,
16705
17431
  ANTHROPIC_API_KEY: _dropApiKey,
@@ -16707,7 +17433,8 @@ function createProxyServer(config = {}) {
16707
17433
  ANTHROPIC_AUTH_TOKEN: _dropAuthToken,
16708
17434
  ...cleanEnv
16709
17435
  } = process.env;
16710
- const profileEnv = { ...cleanEnv, ...profile.env };
17436
+ const sdkModelDefaults = resolveSdkModelDefaults();
17437
+ const profileEnv = { ...sdkModelDefaults, ...cleanEnv, ...profile.env };
16711
17438
  let systemContext = "";
16712
17439
  if (body.system) {
16713
17440
  if (typeof body.system === "string") {
@@ -16717,6 +17444,20 @@ function createProxyServer(config = {}) {
16717
17444
  `);
16718
17445
  }
16719
17446
  }
17447
+ const adapterTransforms = getAdapterTransforms(adapter.name);
17448
+ const pipeline = buildPipeline(adapterTransforms, pluginTransforms);
17449
+ const pipelineCtx = runTransformHook(pipeline, "onRequest", createRequestContext({
17450
+ adapter: adapter.name,
17451
+ body,
17452
+ headers: c.req.raw.headers,
17453
+ model,
17454
+ messages: body.messages || [],
17455
+ systemContext,
17456
+ tools: body.tools,
17457
+ stream: body.stream ?? false,
17458
+ workingDirectory
17459
+ }), adapter.name);
17460
+ const stream2 = pipelineCtx.prefersStreaming !== undefined ? pipelineCtx.prefersStreaming : body.stream ?? false;
16720
17461
  const effortHeader = c.req.header("x-opencode-effort");
16721
17462
  const thinkingHeader = c.req.header("x-opencode-thinking");
16722
17463
  const taskBudgetHeader = c.req.header("x-opencode-task-budget");
@@ -16754,7 +17495,7 @@ function createProxyServer(config = {}) {
16754
17495
  const betas = betaFilter.forwarded;
16755
17496
  const agentSessionId = adapter.getSessionId(c);
16756
17497
  const profileSessionId = profile.id !== "default" && agentSessionId ? `${profile.id}:${agentSessionId}` : agentSessionId;
16757
- const profileScopedCwd = profile.id !== "default" ? `${workingDirectory}::profile=${profile.id}` : workingDirectory;
17498
+ const profileScopedCwd = profile.id !== "default" ? `${clientWorkingDirectory}::profile=${profile.id}` : clientWorkingDirectory;
16758
17499
  const isIndependentSession = requestSource?.startsWith("fork-") || requestSource?.startsWith("subagent-") || false;
16759
17500
  let lineageResult = isIndependentSession ? { type: "diverged" } : lookupSession(profileSessionId, body.messages || [], profileScopedCwd);
16760
17501
  if (lineageResult.type === "undo" && adapter.name === "opencode" && !agentSessionId) {
@@ -16791,14 +17532,14 @@ function createProxyServer(config = {}) {
16791
17532
  messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
16792
17533
  hasSystemPrompt: Boolean(body.system)
16793
17534
  });
16794
- const sdkAgents = adapter.buildSdkAgents?.(body, adapter.getAllowedMcpTools()) ?? {};
17535
+ const sdkAgents = pipelineCtx.sdkAgents;
16795
17536
  const validAgentNames = Object.keys(sdkAgents);
16796
17537
  if ((process.env.MERIDIAN_DEBUG ?? process.env.CLAUDE_PROXY_DEBUG) && validAgentNames.length > 0) {
16797
17538
  claudeLog("debug.agents", { names: validAgentNames, count: validAgentNames.length });
16798
17539
  }
16799
- systemContext += adapter.buildSystemContextAddendum?.(body, sdkAgents) ?? "";
17540
+ systemContext = pipelineCtx.systemContext ?? systemContext;
16800
17541
  const sanitizeOpts = {
16801
- stripSystemReminder: adapter.leaksCwdViaSystemReminder?.() ?? false
17542
+ stripSystemReminder: pipelineCtx.leaksCwdViaSystemReminder
16802
17543
  };
16803
17544
  const allMessages = body.messages || [];
16804
17545
  let messagesToConvert;
@@ -16862,13 +17603,16 @@ function createProxyServer(config = {}) {
16862
17603
 
16863
17604
  `) || "";
16864
17605
  }
16865
- const adapterPassthrough = adapter.usesPassthrough?.();
16866
- const passthrough = adapterPassthrough !== undefined ? adapterPassthrough : envBool("PASSTHROUGH");
16867
- const settingSources = envBool("LOAD_CONTEXT") || sdkFeatures.claudeMd === "full" ? ["user", "project"] : sdkFeatures.claudeMd === "project" ? ["project"] : adapter.getSettingSources?.() ?? [];
17606
+ const passthrough = pipelineCtx.passthrough !== undefined ? pipelineCtx.passthrough : envBool("PASSTHROUGH");
17607
+ const settingSources = envBool("LOAD_CONTEXT") || sdkFeatures.claudeMd === "full" ? ["user", "project"] : sdkFeatures.claudeMd === "project" ? ["project"] : pipelineCtx.settingSources ?? [];
16868
17608
  const capturedToolUses = [];
16869
17609
  const fileChanges = [];
16870
17610
  let passthroughMcp;
16871
17611
  let requestTools = Array.isArray(body.tools) ? body.tools : [];
17612
+ const advisorModel = extractAdvisorModel(requestTools);
17613
+ if (advisorModel) {
17614
+ requestTools = stripAdvisorTools(requestTools);
17615
+ }
16872
17616
  if (passthrough && requestTools.length === 0 && profileSessionId) {
16873
17617
  const cached = sessionToolCache.get(profileSessionId);
16874
17618
  if (cached && cached.length > 0) {
@@ -16882,7 +17626,7 @@ function createProxyServer(config = {}) {
16882
17626
  if (cachedMcp && cachedMcp.key === toolSetKey) {
16883
17627
  passthroughMcp = cachedMcp.mcp;
16884
17628
  } else {
16885
- passthroughMcp = createPassthroughMcpServer(requestTools, adapter.getCoreToolNames?.());
17629
+ passthroughMcp = createPassthroughMcpServer(requestTools, pipelineCtx.coreToolNames ? [...pipelineCtx.coreToolNames] : undefined);
16886
17630
  if (profileSessionId) {
16887
17631
  sessionMcpCache.set(profileSessionId, { key: toolSetKey, mcp: passthroughMcp });
16888
17632
  if (cachedMcp) {
@@ -16894,14 +17638,14 @@ function createProxyServer(config = {}) {
16894
17638
  sessionToolCache.set(profileSessionId, requestTools);
16895
17639
  }
16896
17640
  const hasDeferredTools = passthroughMcp?.hasDeferredTools ?? false;
16897
- const coreNames = adapter.getCoreToolNames?.();
17641
+ const coreNames = pipelineCtx.coreToolNames ? [...pipelineCtx.coreToolNames] : undefined;
16898
17642
  const coreSet = coreNames ? new Set(coreNames.map((n) => n.toLowerCase())) : undefined;
16899
17643
  const deferredToolCount = hasDeferredTools && requestTools.length > 0 ? requestTools.filter((t) => t.defer_loading === true || coreSet && !coreSet.has(String(t.name).toLowerCase())).length : 0;
16900
17644
  if (hasDeferredTools) {
16901
17645
  console.error(`[PROXY] ${requestMeta.requestId} deferred=${deferredToolCount}/${toolCount} tools (core: ${coreNames?.join(",") ?? "none"})`);
16902
17646
  }
16903
17647
  const mcpPrefix = `mcp__${adapter.getMcpServerName()}__`;
16904
- const trackFileChanges = !(process.env.MERIDIAN_NO_FILE_CHANGES ?? process.env.CLAUDE_PROXY_NO_FILE_CHANGES) && adapter.shouldTrackFileChanges?.() !== false;
17648
+ const trackFileChanges = !(process.env.MERIDIAN_NO_FILE_CHANGES ?? process.env.CLAUDE_PROXY_NO_FILE_CHANGES) && pipelineCtx.shouldTrackFileChanges;
16905
17649
  const fileChangeHook = trackFileChanges ? createFileChangeHook(fileChanges, mcpPrefix) : undefined;
16906
17650
  const discoveredTools = new Set;
16907
17651
  const sdkHooks = passthrough ? {
@@ -16914,10 +17658,15 @@ function createProxyServer(config = {}) {
16914
17658
  if (hasDeferredTools && coreSet && !coreSet.has(toolName.toLowerCase())) {
16915
17659
  discoveredTools.add(toolName);
16916
17660
  }
17661
+ const clientTool = requestTools.find((t) => t.name === toolName);
17662
+ let toolInput = normalizeToolInput(input.tool_input, clientTool?.input_schema);
17663
+ if (toolName.toLowerCase() === "task" && toolInput?.subagent_type && typeof toolInput.subagent_type === "string") {
17664
+ toolInput = { ...toolInput, subagent_type: resolveAgentAlias(toolInput.subagent_type) };
17665
+ }
16917
17666
  capturedToolUses.push({
16918
17667
  id: input.tool_use_id,
16919
17668
  name: toolName,
16920
- input: input.tool_input
17669
+ input: toolInput
16921
17670
  });
16922
17671
  return {
16923
17672
  decision: "block",
@@ -16926,7 +17675,7 @@ function createProxyServer(config = {}) {
16926
17675
  }]
16927
17676
  }]
16928
17677
  } : {
16929
- ...adapter.buildSdkHooks?.(body, sdkAgents) ?? {},
17678
+ ...pipelineCtx.sdkHooks ?? {},
16930
17679
  ...fileChangeHook ? { PostToolUse: [fileChangeHook] } : {}
16931
17680
  };
16932
17681
  const stderrLines = [];
@@ -16945,6 +17694,7 @@ function createProxyServer(config = {}) {
16945
17694
  sdkUuidMap.push(null);
16946
17695
  claudeLog("upstream.start", { mode: "non_stream", model });
16947
17696
  let lastUsage;
17697
+ let lastStopReason;
16948
17698
  try {
16949
17699
  if (!claudeExecutable) {
16950
17700
  claudeExecutable = await resolveClaudeExecutableAsync();
@@ -16961,6 +17711,7 @@ function createProxyServer(config = {}) {
16961
17711
  prompt: makePrompt(),
16962
17712
  model,
16963
17713
  workingDirectory,
17714
+ clientWorkingDirectory,
16964
17715
  systemContext,
16965
17716
  claudeExecutable,
16966
17717
  passthrough,
@@ -16973,7 +17724,10 @@ function createProxyServer(config = {}) {
16973
17724
  isUndo,
16974
17725
  undoRollbackUuid,
16975
17726
  sdkHooks,
16976
- adapter,
17727
+ blockedTools: pipelineCtx.blockedTools,
17728
+ incompatibleTools: pipelineCtx.incompatibleTools,
17729
+ mcpServerName: adapter.getMcpServerName(),
17730
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
16977
17731
  onStderr,
16978
17732
  effort,
16979
17733
  thinking,
@@ -16988,7 +17742,8 @@ function createProxyServer(config = {}) {
16988
17742
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
16989
17743
  fallbackModel: sdkFeatures.fallbackModel,
16990
17744
  sdkDebug: sdkFeatures.sdkDebug,
16991
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
17745
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
17746
+ advisorModel
16992
17747
  }))) {
16993
17748
  if (event.type === "assistant" && !event.error) {
16994
17749
  didYieldContent = true;
@@ -17015,6 +17770,7 @@ function createProxyServer(config = {}) {
17015
17770
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
17016
17771
  model,
17017
17772
  workingDirectory,
17773
+ clientWorkingDirectory,
17018
17774
  systemContext,
17019
17775
  claudeExecutable,
17020
17776
  passthrough,
@@ -17027,7 +17783,10 @@ function createProxyServer(config = {}) {
17027
17783
  isUndo: false,
17028
17784
  undoRollbackUuid: undefined,
17029
17785
  sdkHooks,
17030
- adapter,
17786
+ blockedTools: pipelineCtx.blockedTools,
17787
+ incompatibleTools: pipelineCtx.incompatibleTools,
17788
+ mcpServerName: adapter.getMcpServerName(),
17789
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
17031
17790
  onStderr,
17032
17791
  effort,
17033
17792
  thinking,
@@ -17042,7 +17801,8 @@ function createProxyServer(config = {}) {
17042
17801
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17043
17802
  fallbackModel: sdkFeatures.fallbackModel,
17044
17803
  sdkDebug: sdkFeatures.sdkDebug,
17045
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
17804
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
17805
+ advisorModel
17046
17806
  }));
17047
17807
  return;
17048
17808
  }
@@ -17127,7 +17887,7 @@ function createProxyServer(config = {}) {
17127
17887
  claudeLog("passthrough.toolsearch_filtered", { mode: "non_stream" });
17128
17888
  continue;
17129
17889
  }
17130
- if (passthrough && !adapter.supportsThinking?.() && !sdkFeatures.thinkingPassthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
17890
+ if (passthrough && !pipelineCtx.supportsThinking && !sdkFeatures.thinkingPassthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
17131
17891
  claudeLog("passthrough.thinking_stripped", { mode: "non_stream", type: b.type });
17132
17892
  continue;
17133
17893
  }
@@ -17140,6 +17900,9 @@ function createProxyServer(config = {}) {
17140
17900
  const msgUsage = message.message.usage;
17141
17901
  if (msgUsage)
17142
17902
  lastUsage = { ...lastUsage, ...msgUsage };
17903
+ if (typeof message.message.stop_reason === "string") {
17904
+ lastStopReason = message.message.stop_reason;
17905
+ }
17143
17906
  }
17144
17907
  }
17145
17908
  claudeLog("upstream.completed", {
@@ -17177,22 +17940,30 @@ Subprocess stderr: ${stderrOutput}`;
17177
17940
  throw error;
17178
17941
  }
17179
17942
  if (passthrough && capturedToolUses.length > 0) {
17180
- for (const tu of capturedToolUses) {
17181
- if (!contentBlocks.some((b) => b.type === "tool_use" && b.id === tu.id)) {
17182
- contentBlocks.push({
17183
- type: "tool_use",
17184
- id: tu.id,
17185
- name: tu.name,
17186
- input: tu.input
17187
- });
17943
+ const capturedById = new Map(capturedToolUses.map((tu) => [tu.id, tu]));
17944
+ for (const block of contentBlocks) {
17945
+ if (block.type === "tool_use" && capturedById.has(block.id)) {
17946
+ const captured = capturedById.get(block.id);
17947
+ block.name = captured.name;
17948
+ block.input = captured.input;
17949
+ capturedById.delete(block.id);
17188
17950
  }
17189
17951
  }
17952
+ for (const tu of capturedById.values()) {
17953
+ contentBlocks.push({
17954
+ type: "tool_use",
17955
+ id: tu.id,
17956
+ name: tu.name,
17957
+ input: tu.input
17958
+ });
17959
+ }
17190
17960
  }
17191
17961
  const hasToolUse = contentBlocks.some((b) => b.type === "tool_use");
17192
- const stopReason = hasToolUse ? "tool_use" : "end_turn";
17962
+ const heuristicStopReason = hasToolUse ? "tool_use" : "end_turn";
17963
+ const stopReason = lastStopReason && lastStopReason !== "end_turn" && lastStopReason !== "tool_use" ? lastStopReason : heuristicStopReason;
17193
17964
  if (trackFileChanges) {
17194
- if (passthrough && stopReason === "end_turn" && adapter.extractFileChangesFromToolUse) {
17195
- const passthroughChanges = extractFileChangesFromMessages(body.messages || [], adapter.extractFileChangesFromToolUse.bind(adapter));
17965
+ if (passthrough && stopReason === "end_turn" && pipelineCtx.extractFileChangesFromToolUse) {
17966
+ const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
17196
17967
  fileChanges.push(...passthroughChanges);
17197
17968
  }
17198
17969
  const fileChangeSummary = formatFileChangeSummary(fileChanges);
@@ -17227,6 +17998,7 @@ Subprocess stderr: ${stderrOutput}`;
17227
17998
  requestId: requestMeta.requestId,
17228
17999
  timestamp: Date.now(),
17229
18000
  adapter: adapter.name,
18001
+ requestSource,
17230
18002
  model,
17231
18003
  requestModel: body.model || undefined,
17232
18004
  mode: "non-stream",
@@ -17330,6 +18102,7 @@ Subprocess stderr: ${stderrOutput}`;
17330
18102
  prompt: makePrompt(),
17331
18103
  model,
17332
18104
  workingDirectory,
18105
+ clientWorkingDirectory,
17333
18106
  systemContext,
17334
18107
  claudeExecutable,
17335
18108
  passthrough,
@@ -17342,7 +18115,10 @@ Subprocess stderr: ${stderrOutput}`;
17342
18115
  isUndo,
17343
18116
  undoRollbackUuid,
17344
18117
  sdkHooks,
17345
- adapter,
18118
+ blockedTools: pipelineCtx.blockedTools,
18119
+ incompatibleTools: pipelineCtx.incompatibleTools,
18120
+ mcpServerName: adapter.getMcpServerName(),
18121
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
17346
18122
  onStderr,
17347
18123
  effort,
17348
18124
  thinking,
@@ -17357,7 +18133,8 @@ Subprocess stderr: ${stderrOutput}`;
17357
18133
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17358
18134
  fallbackModel: sdkFeatures.fallbackModel,
17359
18135
  sdkDebug: sdkFeatures.sdkDebug,
17360
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
18136
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
18137
+ advisorModel
17361
18138
  }))) {
17362
18139
  if (event.type === "stream_event") {
17363
18140
  didYieldClientEvent = true;
@@ -17384,6 +18161,7 @@ Subprocess stderr: ${stderrOutput}`;
17384
18161
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
17385
18162
  model,
17386
18163
  workingDirectory,
18164
+ clientWorkingDirectory,
17387
18165
  systemContext,
17388
18166
  claudeExecutable,
17389
18167
  passthrough,
@@ -17396,7 +18174,10 @@ Subprocess stderr: ${stderrOutput}`;
17396
18174
  isUndo: false,
17397
18175
  undoRollbackUuid: undefined,
17398
18176
  sdkHooks,
17399
- adapter,
18177
+ blockedTools: pipelineCtx.blockedTools,
18178
+ incompatibleTools: pipelineCtx.incompatibleTools,
18179
+ mcpServerName: adapter.getMcpServerName(),
18180
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
17400
18181
  onStderr,
17401
18182
  effort,
17402
18183
  thinking,
@@ -17411,7 +18192,8 @@ Subprocess stderr: ${stderrOutput}`;
17411
18192
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17412
18193
  fallbackModel: sdkFeatures.fallbackModel,
17413
18194
  sdkDebug: sdkFeatures.sdkDebug,
17414
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
18195
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
18196
+ advisorModel
17415
18197
  }));
17416
18198
  return;
17417
18199
  }
@@ -17491,6 +18273,8 @@ Subprocess stderr: ${stderrOutput}`;
17491
18273
  }
17492
18274
  }, 15000);
17493
18275
  const skipBlockIndices = new Set;
18276
+ const taskToolBlockIndices = new Set;
18277
+ const taskToolJsonBuffer = new Map;
17494
18278
  const streamedToolUseIds = new Set;
17495
18279
  let nextClientBlockIndex = 0;
17496
18280
  const sdkToClientIndex = new Map;
@@ -17548,7 +18332,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
17548
18332
  }
17549
18333
  if (eventType === "content_block_start") {
17550
18334
  const block = event.content_block;
17551
- if (passthrough && !adapter.supportsThinking?.() && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
18335
+ if (passthrough && !pipelineCtx.supportsThinking && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
17552
18336
  if (eventIndex !== undefined)
17553
18337
  skipBlockIndices.add(eventIndex);
17554
18338
  claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
@@ -17571,6 +18355,9 @@ data: ${JSON.stringify({ type: "message_stop" })}
17571
18355
  } else if (passthrough && block.id) {
17572
18356
  streamedToolUseIds.add(block.id);
17573
18357
  }
18358
+ if (passthrough && eventIndex !== undefined && block.name.toLowerCase() === "task") {
18359
+ taskToolBlockIndices.add(eventIndex);
18360
+ }
17574
18361
  }
17575
18362
  if (eventIndex !== undefined) {
17576
18363
  sdkToClientIndex.set(eventIndex, nextClientBlockIndex++);
@@ -17591,6 +18378,39 @@ data: ${JSON.stringify({ type: "message_stop" })}
17591
18378
  continue;
17592
18379
  }
17593
18380
  }
18381
+ if (passthrough && eventIndex !== undefined && taskToolBlockIndices.has(eventIndex)) {
18382
+ if (eventType === "content_block_delta") {
18383
+ const delta = event.delta;
18384
+ if (delta?.type === "input_json_delta" && typeof delta.partial_json === "string") {
18385
+ const prev = taskToolJsonBuffer.get(eventIndex) ?? "";
18386
+ taskToolJsonBuffer.set(eventIndex, prev + delta.partial_json);
18387
+ continue;
18388
+ }
18389
+ }
18390
+ if (eventType === "content_block_stop") {
18391
+ const buffered = taskToolJsonBuffer.get(eventIndex);
18392
+ if (buffered) {
18393
+ let fixed = buffered;
18394
+ try {
18395
+ const parsed = JSON.parse(buffered);
18396
+ if (typeof parsed.subagent_type === "string") {
18397
+ parsed.subagent_type = resolveAgentAlias(parsed.subagent_type);
18398
+ }
18399
+ fixed = JSON.stringify(parsed);
18400
+ } catch {}
18401
+ const clientIdx = sdkToClientIndex.get(eventIndex) ?? eventIndex;
18402
+ safeEnqueue(encoder.encode(`event: content_block_delta
18403
+ data: ${JSON.stringify({
18404
+ type: "content_block_delta",
18405
+ index: clientIdx,
18406
+ delta: { type: "input_json_delta", partial_json: fixed }
18407
+ })}
18408
+
18409
+ `), "task_tool_fixed_delta");
18410
+ taskToolJsonBuffer.delete(eventIndex);
18411
+ }
18412
+ }
18413
+ }
17594
18414
  const payload = encoder.encode(`event: ${eventType}
17595
18415
  data: ${JSON.stringify(event)}
17596
18416
 
@@ -17681,8 +18501,8 @@ data: ${JSON.stringify({
17681
18501
 
17682
18502
  `), "passthrough_message_delta");
17683
18503
  }
17684
- if (trackFileChanges && passthrough && adapter.extractFileChangesFromToolUse) {
17685
- const passthroughChanges = extractFileChangesFromMessages(body.messages || [], adapter.extractFileChangesFromToolUse.bind(adapter));
18504
+ if (trackFileChanges && passthrough && pipelineCtx.extractFileChangesFromToolUse) {
18505
+ const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
17686
18506
  fileChanges.push(...passthroughChanges);
17687
18507
  }
17688
18508
  if (trackFileChanges) {
@@ -17750,6 +18570,7 @@ data: {"type":"message_stop"}
17750
18570
  requestId: requestMeta.requestId,
17751
18571
  timestamp: Date.now(),
17752
18572
  adapter: adapter.name,
18573
+ requestSource,
17753
18574
  model,
17754
18575
  requestModel: body.model || undefined,
17755
18576
  mode: "stream",
@@ -18005,7 +18826,7 @@ data: ${JSON.stringify({
18005
18826
  });
18006
18827
  });
18007
18828
  app.get("/profiles", async (c) => {
18008
- const { profilePageHtml } = await import("./profilePage-g5t5t6av.js");
18829
+ const { profilePageHtml } = await import("./profilePage-77z05e0r.js");
18009
18830
  return c.html(profilePageHtml);
18010
18831
  });
18011
18832
  app.post("/profiles/active", async (c) => {
@@ -18030,6 +18851,45 @@ data: ${JSON.stringify({
18030
18851
  console.error(`[PROXY] Active profile switched to: ${body.profile} (session cache cleared)`);
18031
18852
  return c.json({ success: true, activeProfile: body.profile });
18032
18853
  });
18854
+ app.get("/plugins/list", async (c) => {
18855
+ const { getPluginStats } = await import("./stats-4c4ewmdh.js");
18856
+ return c.json({
18857
+ plugins: loadedPlugins.map((p) => ({
18858
+ name: p.name,
18859
+ description: p.description,
18860
+ version: p.version,
18861
+ adapters: p.adapters,
18862
+ hooks: p.hooks,
18863
+ status: p.status,
18864
+ path: p.path,
18865
+ ...p.error ? { error: p.error } : {},
18866
+ ...p.status === "active" ? { stats: getPluginStats(p.name) } : {}
18867
+ }))
18868
+ });
18869
+ });
18870
+ app.post("/plugins/reload", async (c) => {
18871
+ try {
18872
+ loadedPlugins = await loadPlugins(pluginDir, pluginConfigPath);
18873
+ pluginTransforms = getActiveTransforms(loadedPlugins);
18874
+ const active = loadedPlugins.filter((p) => p.status === "active").length;
18875
+ console.error(`[PROXY] Plugins reloaded: ${active} active`);
18876
+ return c.json({
18877
+ success: true,
18878
+ plugins: loadedPlugins.map((p) => ({
18879
+ name: p.name,
18880
+ status: p.status,
18881
+ hooks: p.hooks,
18882
+ ...p.error ? { error: p.error } : {}
18883
+ }))
18884
+ });
18885
+ } catch (err) {
18886
+ return c.json({ success: false, error: String(err) }, 500);
18887
+ }
18888
+ });
18889
+ app.get("/plugins", async (c) => {
18890
+ const { pluginPageHtml } = await import("./pluginPage-85s6t6k8.js");
18891
+ return c.html(pluginPageHtml);
18892
+ });
18033
18893
  app.post("/auth/refresh", async (c) => {
18034
18894
  const success = await refreshOAuthToken();
18035
18895
  if (success) {
@@ -18134,7 +18994,7 @@ data: ${JSON.stringify({
18134
18994
  if (!session.contextUsage) {
18135
18995
  return c.json({ error: "No usage data available for this session" }, 404);
18136
18996
  }
18137
- return c.json({ session_id: claudeSessionId, context_usage: session.contextUsage });
18997
+ return c.json({ session_id: claudeSessionId, context_usage: normalizeContextUsage(session.contextUsage) });
18138
18998
  });
18139
18999
  app.get("/v1/sessions/recover", (c) => {
18140
19000
  const sessions = listStoredSessions();
@@ -18180,11 +19040,27 @@ data: ${JSON.stringify({
18180
19040
  console.error(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
18181
19041
  return c.json({ error: { type: "not_found", message: `Endpoint not supported: ${c.req.method} ${new URL(c.req.url).pathname}` } }, 404);
18182
19042
  });
18183
- return { app, config: finalConfig };
19043
+ async function initPluginsAsync() {
19044
+ try {
19045
+ loadedPlugins = await loadPlugins(pluginDir, pluginConfigPath);
19046
+ pluginTransforms = getActiveTransforms(loadedPlugins);
19047
+ if (loadedPlugins.length > 0) {
19048
+ const active = loadedPlugins.filter((p) => p.status === "active").length;
19049
+ const disabled = loadedPlugins.filter((p) => p.status === "disabled").length;
19050
+ const errored = loadedPlugins.filter((p) => p.status === "error").length;
19051
+ console.error(`[PROXY] Plugins loaded: ${active} active, ${disabled} disabled, ${errored} errors`);
19052
+ }
19053
+ } catch (err) {
19054
+ console.error(`[PROXY] Plugin loading failed: ${err instanceof Error ? err.message : String(err)}`);
19055
+ }
19056
+ }
19057
+ return { app, config: finalConfig, initPlugins: initPluginsAsync };
18184
19058
  }
18185
19059
  async function startProxyServer(config = {}) {
18186
19060
  claudeExecutable = await resolveClaudeExecutableAsync();
18187
- const { app, config: finalConfig } = createProxyServer(config);
19061
+ const { app, config: finalConfig, initPlugins } = createProxyServer(config);
19062
+ if (initPlugins)
19063
+ await initPlugins();
18188
19064
  const server = serve({
18189
19065
  fetch: app.fetch,
18190
19066
  port: finalConfig.port,
@@ -18194,6 +19070,8 @@ async function startProxyServer(config = {}) {
18194
19070
  if (!finalConfig.silent) {
18195
19071
  console.log(`Meridian running at http://${finalConfig.host}:${info.port}`);
18196
19072
  console.log(`Telemetry dashboard: http://${finalConfig.host}:${info.port}/telemetry`);
19073
+ const pins = resolveSdkModelDefaults();
19074
+ console.log(`Model pins: opus=${pins.ANTHROPIC_DEFAULT_OPUS_MODEL} sonnet=${pins.ANTHROPIC_DEFAULT_SONNET_MODEL} haiku=${pins.ANTHROPIC_DEFAULT_HAIKU_MODEL}`);
18197
19075
  console.log(`
18198
19076
  Point any Anthropic-compatible tool at this endpoint:`);
18199
19077
  console.log(` ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://${finalConfig.host}:${info.port}`);
@@ -18245,4 +19123,4 @@ Or use a different port:`);
18245
19123
  };
18246
19124
  }
18247
19125
 
18248
- export { computeLineageHash, hashMessage, computeMessageHashes, getMaxSessionsLimit, clearSessionCache, createProxyServer, startProxyServer };
19126
+ export { runTransformHook, runObserveHook, buildPipeline, createRequestContext, computeLineageHash, hashMessage, computeMessageHashes, getMaxSessionsLimit, clearSessionCache, createProxyServer, startProxyServer };