@rynfar/meridian 1.37.8 → 1.38.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 (66) hide show
  1. package/README.md +21 -0
  2. package/dist/{cli-pr79d7nw.js → cli-4rqtm83g.js} +33 -2
  3. package/dist/{cli-z5r7ptsm.js → cli-bfgya0hb.js} +683 -100
  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 +17 -10
  9. package/dist/proxy/adapter.d.ts.map +1 -1
  10. package/dist/proxy/adapters/crush.d.ts +2 -0
  11. package/dist/proxy/adapters/crush.d.ts.map +1 -1
  12. package/dist/proxy/adapters/droid.d.ts +2 -0
  13. package/dist/proxy/adapters/droid.d.ts.map +1 -1
  14. package/dist/proxy/adapters/forgecode.d.ts +2 -0
  15. package/dist/proxy/adapters/forgecode.d.ts.map +1 -1
  16. package/dist/proxy/adapters/opencode.d.ts +2 -0
  17. package/dist/proxy/adapters/opencode.d.ts.map +1 -1
  18. package/dist/proxy/adapters/passthrough.d.ts +2 -0
  19. package/dist/proxy/adapters/passthrough.d.ts.map +1 -1
  20. package/dist/proxy/adapters/pi.d.ts +2 -0
  21. package/dist/proxy/adapters/pi.d.ts.map +1 -1
  22. package/dist/proxy/plugins/loader.d.ts +6 -0
  23. package/dist/proxy/plugins/loader.d.ts.map +1 -0
  24. package/dist/proxy/plugins/pluginPage.d.ts +7 -0
  25. package/dist/proxy/plugins/pluginPage.d.ts.map +1 -0
  26. package/dist/proxy/plugins/stats.d.ts +61 -0
  27. package/dist/proxy/plugins/stats.d.ts.map +1 -0
  28. package/dist/proxy/plugins/types.d.ts +21 -0
  29. package/dist/proxy/plugins/types.d.ts.map +1 -0
  30. package/dist/proxy/plugins/validation.d.ts +8 -0
  31. package/dist/proxy/plugins/validation.d.ts.map +1 -0
  32. package/dist/proxy/query.d.ts +8 -3
  33. package/dist/proxy/query.d.ts.map +1 -1
  34. package/dist/proxy/server.d.ts +2 -0
  35. package/dist/proxy/server.d.ts.map +1 -1
  36. package/dist/proxy/transform.d.ts +137 -0
  37. package/dist/proxy/transform.d.ts.map +1 -0
  38. package/dist/proxy/transforms/crush.d.ts +3 -0
  39. package/dist/proxy/transforms/crush.d.ts.map +1 -0
  40. package/dist/proxy/transforms/droid.d.ts +3 -0
  41. package/dist/proxy/transforms/droid.d.ts.map +1 -0
  42. package/dist/proxy/transforms/forgecode.d.ts +3 -0
  43. package/dist/proxy/transforms/forgecode.d.ts.map +1 -0
  44. package/dist/proxy/transforms/opencode.d.ts +3 -0
  45. package/dist/proxy/transforms/opencode.d.ts.map +1 -0
  46. package/dist/proxy/transforms/passthrough.d.ts +3 -0
  47. package/dist/proxy/transforms/passthrough.d.ts.map +1 -0
  48. package/dist/proxy/transforms/pi.d.ts +3 -0
  49. package/dist/proxy/transforms/pi.d.ts.map +1 -0
  50. package/dist/proxy/transforms/registry.d.ts +3 -0
  51. package/dist/proxy/transforms/registry.d.ts.map +1 -0
  52. package/dist/proxy/types.d.ts +6 -0
  53. package/dist/proxy/types.d.ts.map +1 -1
  54. package/dist/server.js +14 -5
  55. package/dist/stats-4c4ewmdh.js +17 -0
  56. package/dist/telemetry/dashboard.d.ts.map +1 -1
  57. package/dist/telemetry/landing.d.ts.map +1 -1
  58. package/dist/telemetry/profileBar.d.ts +13 -1
  59. package/dist/telemetry/profileBar.d.ts.map +1 -1
  60. package/dist/telemetry/profilePage.d.ts.map +1 -1
  61. package/dist/telemetry/settingsPage.d.ts +1 -1
  62. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  63. package/dist/telemetry/sqlite.d.ts.map +1 -1
  64. package/dist/telemetry/types.d.ts +4 -0
  65. package/dist/telemetry/types.d.ts.map +1 -1
  66. package/package.json +1 -1
@@ -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";
@@ -8010,13 +8093,8 @@ var dashboardHtml = `<!DOCTYPE html>
8010
8093
  <title>Meridian — Telemetry</title>
8011
8094
  <link rel="icon" type="image/svg+xml" href="/telemetry/icon.svg">
8012
8095
  <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
- }
8096
+ ${themeCss}
8097
+ :root { --total: var(--accent); }
8020
8098
  * { box-sizing: border-box; margin: 0; padding: 0; }
8021
8099
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
8022
8100
  background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
@@ -8272,9 +8350,11 @@ function render(s, reqs, logs) {
8272
8350
  const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '—';
8273
8351
  const msgCount = r.messageCount != null ? r.messageCount : '?';
8274
8352
 
8353
+ const sourceBadge = r.requestSource ? '<br><span class="mono" style="font-size:9px;color:var(--violet)">' + r.requestSource + '</span>' : '';
8354
+
8275
8355
  html += '<tr>'
8276
8356
  + '<td class="mono">' + ago(r.timestamp) + '</td>'
8277
- + '<td>' + (r.adapter || '—') + '</td>'
8357
+ + '<td>' + (r.adapter || '—') + sourceBadge + '</td>'
8278
8358
  + '<td>' + (r.requestModel || r.model) + '<br><span style="font-size:10px;color:var(--muted)">' + r.model + '</span></td>'
8279
8359
  + '<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
8360
  + '<td class="mono">' + sessionShort + ' ' + lineageBadge + '<br><span style="font-size:10px;color:var(--muted)">' + msgCount + ' msgs</span></td>'
@@ -8407,12 +8487,7 @@ var landingHtml = `<!DOCTYPE html>
8407
8487
  <meta name="viewport" content="width=device-width, initial-scale=1">
8408
8488
  <title>Meridian</title>
8409
8489
  <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
- }
8490
+ ${themeCss}
8416
8491
  * { box-sizing: border-box; margin: 0; padding: 0; }
8417
8492
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
8418
8493
  background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }
@@ -9463,6 +9538,85 @@ function fuzzyMatchAgentName(input, validAgents) {
9463
9538
  return lowered;
9464
9539
  }
9465
9540
 
9541
+ // src/proxy/transforms/opencode.ts
9542
+ var openCodeTransforms = [
9543
+ {
9544
+ name: "opencode-core",
9545
+ adapters: ["opencode"],
9546
+ onRequest(ctx) {
9547
+ const body = ctx.body;
9548
+ const blockedTools = BLOCKED_BUILTIN_TOOLS;
9549
+ const incompatibleTools = CLAUDE_CODE_ONLY_TOOLS;
9550
+ const allowedMcpTools = ALLOWED_MCP_TOOLS;
9551
+ const coreToolNames = ["read", "write", "edit", "bash", "glob", "grep"];
9552
+ const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
9553
+ const passthrough = !(envVal === "0" || envVal === "false" || envVal === "no");
9554
+ let sdkAgents = {};
9555
+ if (Array.isArray(body.tools)) {
9556
+ const taskTool = body.tools.find((t) => t.name === "task" || t.name === "Task");
9557
+ if (taskTool?.description) {
9558
+ sdkAgents = buildAgentDefinitions(taskTool.description, [...allowedMcpTools]);
9559
+ }
9560
+ }
9561
+ let sdkHooks = undefined;
9562
+ const validAgentNames = Object.keys(sdkAgents);
9563
+ if (validAgentNames.length > 0) {
9564
+ sdkHooks = {
9565
+ PreToolUse: [{
9566
+ matcher: "Task",
9567
+ hooks: [async (input) => ({
9568
+ hookSpecificOutput: {
9569
+ hookEventName: "PreToolUse",
9570
+ updatedInput: {
9571
+ ...input.tool_input,
9572
+ subagent_type: fuzzyMatchAgentName(String(input.tool_input?.subagent_type || ""), validAgentNames)
9573
+ }
9574
+ }
9575
+ })]
9576
+ }]
9577
+ };
9578
+ }
9579
+ let systemContext = ctx.systemContext;
9580
+ if (validAgentNames.length > 0 && systemContext !== undefined) {
9581
+ systemContext += `
9582
+
9583
+ 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.`;
9584
+ } else if (validAgentNames.length > 0) {
9585
+ 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.`;
9586
+ }
9587
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
9588
+ const input = toolInput;
9589
+ const filePath = input?.filePath ?? input?.file_path ?? input?.path;
9590
+ const lowerName = toolName.toLowerCase();
9591
+ if (lowerName === "write" && filePath) {
9592
+ return [{ operation: "wrote", path: String(filePath) }];
9593
+ }
9594
+ if ((lowerName === "edit" || lowerName === "multiedit") && filePath) {
9595
+ return [{ operation: "edited", path: String(filePath) }];
9596
+ }
9597
+ if (lowerName === "bash" && input?.command) {
9598
+ return extractFileChangesFromBash(String(input.command));
9599
+ }
9600
+ return [];
9601
+ };
9602
+ return {
9603
+ ...ctx,
9604
+ blockedTools,
9605
+ incompatibleTools,
9606
+ allowedMcpTools,
9607
+ coreToolNames,
9608
+ passthrough,
9609
+ sdkAgents,
9610
+ sdkHooks,
9611
+ systemContext,
9612
+ supportsThinking: true,
9613
+ shouldTrackFileChanges: false,
9614
+ extractFileChangesFromToolUse
9615
+ };
9616
+ }
9617
+ }
9618
+ ];
9619
+
9466
9620
  // src/proxy/adapters/opencode.ts
9467
9621
  var openCodeAdapter = {
9468
9622
  name: "opencode",
@@ -9555,7 +9709,7 @@ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be on
9555
9709
  }
9556
9710
  };
9557
9711
 
9558
- // src/proxy/adapters/droid.ts
9712
+ // src/proxy/transforms/droid.ts
9559
9713
  var DROID_MCP_SERVER_NAME = "droid";
9560
9714
  var DROID_ALLOWED_MCP_TOOLS = [
9561
9715
  `mcp__${DROID_MCP_SERVER_NAME}__read`,
@@ -9565,6 +9719,34 @@ var DROID_ALLOWED_MCP_TOOLS = [
9565
9719
  `mcp__${DROID_MCP_SERVER_NAME}__glob`,
9566
9720
  `mcp__${DROID_MCP_SERVER_NAME}__grep`
9567
9721
  ];
9722
+ var droidTransforms = [
9723
+ {
9724
+ name: "droid-core",
9725
+ adapters: ["droid"],
9726
+ onRequest(ctx) {
9727
+ return {
9728
+ ...ctx,
9729
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
9730
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
9731
+ allowedMcpTools: DROID_ALLOWED_MCP_TOOLS,
9732
+ sdkAgents: {},
9733
+ passthrough: false,
9734
+ leaksCwdViaSystemReminder: true
9735
+ };
9736
+ }
9737
+ }
9738
+ ];
9739
+
9740
+ // src/proxy/adapters/droid.ts
9741
+ var DROID_MCP_SERVER_NAME2 = "droid";
9742
+ var DROID_ALLOWED_MCP_TOOLS2 = [
9743
+ `mcp__${DROID_MCP_SERVER_NAME2}__read`,
9744
+ `mcp__${DROID_MCP_SERVER_NAME2}__write`,
9745
+ `mcp__${DROID_MCP_SERVER_NAME2}__edit`,
9746
+ `mcp__${DROID_MCP_SERVER_NAME2}__bash`,
9747
+ `mcp__${DROID_MCP_SERVER_NAME2}__glob`,
9748
+ `mcp__${DROID_MCP_SERVER_NAME2}__grep`
9749
+ ];
9568
9750
  function extractDroidCwd(body) {
9569
9751
  const messages = body.messages;
9570
9752
  if (!Array.isArray(messages))
@@ -9604,10 +9786,10 @@ var droidAdapter = {
9604
9786
  return CLAUDE_CODE_ONLY_TOOLS;
9605
9787
  },
9606
9788
  getMcpServerName() {
9607
- return DROID_MCP_SERVER_NAME;
9789
+ return DROID_MCP_SERVER_NAME2;
9608
9790
  },
9609
9791
  getAllowedMcpTools() {
9610
- return DROID_ALLOWED_MCP_TOOLS;
9792
+ return DROID_ALLOWED_MCP_TOOLS2;
9611
9793
  },
9612
9794
  buildSdkAgents(_body, _mcpToolNames) {
9613
9795
  return {};
@@ -9623,7 +9805,7 @@ var droidAdapter = {
9623
9805
  }
9624
9806
  };
9625
9807
 
9626
- // src/proxy/adapters/crush.ts
9808
+ // src/proxy/transforms/crush.ts
9627
9809
  var CRUSH_MCP_SERVER_NAME = "crush";
9628
9810
  var CRUSH_ALLOWED_MCP_TOOLS = [
9629
9811
  `mcp__${CRUSH_MCP_SERVER_NAME}__read`,
@@ -9633,6 +9815,45 @@ var CRUSH_ALLOWED_MCP_TOOLS = [
9633
9815
  `mcp__${CRUSH_MCP_SERVER_NAME}__glob`,
9634
9816
  `mcp__${CRUSH_MCP_SERVER_NAME}__grep`
9635
9817
  ];
9818
+ var crushTransforms = [
9819
+ {
9820
+ name: "crush-core",
9821
+ adapters: ["crush"],
9822
+ onRequest(ctx) {
9823
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
9824
+ const input = toolInput;
9825
+ const filePath = input?.file_path ?? input?.path;
9826
+ if (toolName === "write" && filePath)
9827
+ return [{ operation: "wrote", path: String(filePath) }];
9828
+ if ((toolName === "edit" || toolName === "patch") && filePath)
9829
+ return [{ operation: "edited", path: String(filePath) }];
9830
+ if (toolName === "bash" && input?.command)
9831
+ return extractFileChangesFromBash(String(input.command));
9832
+ return [];
9833
+ };
9834
+ return {
9835
+ ...ctx,
9836
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
9837
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
9838
+ allowedMcpTools: CRUSH_ALLOWED_MCP_TOOLS,
9839
+ sdkAgents: {},
9840
+ supportsThinking: true,
9841
+ extractFileChangesFromToolUse
9842
+ };
9843
+ }
9844
+ }
9845
+ ];
9846
+
9847
+ // src/proxy/adapters/crush.ts
9848
+ var CRUSH_MCP_SERVER_NAME2 = "crush";
9849
+ var CRUSH_ALLOWED_MCP_TOOLS2 = [
9850
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__read`,
9851
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__write`,
9852
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__edit`,
9853
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__bash`,
9854
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__glob`,
9855
+ `mcp__${CRUSH_MCP_SERVER_NAME2}__grep`
9856
+ ];
9636
9857
  var crushAdapter = {
9637
9858
  name: "crush",
9638
9859
  getSessionId(_c) {
@@ -9651,10 +9872,10 @@ var crushAdapter = {
9651
9872
  return CLAUDE_CODE_ONLY_TOOLS;
9652
9873
  },
9653
9874
  getMcpServerName() {
9654
- return CRUSH_MCP_SERVER_NAME;
9875
+ return CRUSH_MCP_SERVER_NAME2;
9655
9876
  },
9656
9877
  getAllowedMcpTools() {
9657
- return CRUSH_ALLOWED_MCP_TOOLS;
9878
+ return CRUSH_ALLOWED_MCP_TOOLS2;
9658
9879
  },
9659
9880
  buildSdkAgents(_body, _mcpToolNames) {
9660
9881
  return {};
@@ -9684,7 +9905,7 @@ var crushAdapter = {
9684
9905
  }
9685
9906
  };
9686
9907
 
9687
- // src/proxy/adapters/passthrough.ts
9908
+ // src/proxy/transforms/passthrough.ts
9688
9909
  var MCP_SERVER_NAME2 = "litellm";
9689
9910
  var ALLOWED_MCP_TOOLS2 = [
9690
9911
  `mcp__${MCP_SERVER_NAME2}__read`,
@@ -9694,6 +9915,34 @@ var ALLOWED_MCP_TOOLS2 = [
9694
9915
  `mcp__${MCP_SERVER_NAME2}__glob`,
9695
9916
  `mcp__${MCP_SERVER_NAME2}__grep`
9696
9917
  ];
9918
+ var passthroughTransforms = [
9919
+ {
9920
+ name: "passthrough-core",
9921
+ adapters: ["passthrough"],
9922
+ onRequest(ctx) {
9923
+ return {
9924
+ ...ctx,
9925
+ blockedTools: [],
9926
+ incompatibleTools: [],
9927
+ allowedMcpTools: ALLOWED_MCP_TOOLS2,
9928
+ sdkAgents: {},
9929
+ passthrough: true,
9930
+ prefersStreaming: ctx.body?.stream === true
9931
+ };
9932
+ }
9933
+ }
9934
+ ];
9935
+
9936
+ // src/proxy/adapters/passthrough.ts
9937
+ var MCP_SERVER_NAME3 = "litellm";
9938
+ var ALLOWED_MCP_TOOLS3 = [
9939
+ `mcp__${MCP_SERVER_NAME3}__read`,
9940
+ `mcp__${MCP_SERVER_NAME3}__write`,
9941
+ `mcp__${MCP_SERVER_NAME3}__edit`,
9942
+ `mcp__${MCP_SERVER_NAME3}__bash`,
9943
+ `mcp__${MCP_SERVER_NAME3}__glob`,
9944
+ `mcp__${MCP_SERVER_NAME3}__grep`
9945
+ ];
9697
9946
  function extractCwdFromBody(body) {
9698
9947
  if (!body)
9699
9948
  return;
@@ -9741,10 +9990,10 @@ var passthroughAdapter = {
9741
9990
  return [];
9742
9991
  },
9743
9992
  getMcpServerName() {
9744
- return MCP_SERVER_NAME2;
9993
+ return MCP_SERVER_NAME3;
9745
9994
  },
9746
9995
  getAllowedMcpTools() {
9747
- return ALLOWED_MCP_TOOLS2;
9996
+ return ALLOWED_MCP_TOOLS3;
9748
9997
  },
9749
9998
  buildSdkAgents(_body, _mcpToolNames) {
9750
9999
  return {};
@@ -9763,7 +10012,7 @@ var passthroughAdapter = {
9763
10012
  }
9764
10013
  };
9765
10014
 
9766
- // src/proxy/adapters/pi.ts
10015
+ // src/proxy/transforms/pi.ts
9767
10016
  var PI_MCP_SERVER_NAME = "pi";
9768
10017
  var PI_ALLOWED_MCP_TOOLS = [
9769
10018
  `mcp__${PI_MCP_SERVER_NAME}__read`,
@@ -9773,6 +10022,45 @@ var PI_ALLOWED_MCP_TOOLS = [
9773
10022
  `mcp__${PI_MCP_SERVER_NAME}__glob`,
9774
10023
  `mcp__${PI_MCP_SERVER_NAME}__grep`
9775
10024
  ];
10025
+ var piTransforms = [
10026
+ {
10027
+ name: "pi-core",
10028
+ adapters: ["pi"],
10029
+ onRequest(ctx) {
10030
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
10031
+ const input = toolInput;
10032
+ const filePath = input?.filePath ?? input?.file_path ?? input?.path;
10033
+ if (toolName === "write" && filePath)
10034
+ return [{ operation: "wrote", path: String(filePath) }];
10035
+ if (toolName === "edit" && filePath)
10036
+ return [{ operation: "edited", path: String(filePath) }];
10037
+ if (toolName === "bash" && input?.command)
10038
+ return extractFileChangesFromBash(String(input.command));
10039
+ return [];
10040
+ };
10041
+ return {
10042
+ ...ctx,
10043
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
10044
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
10045
+ allowedMcpTools: PI_ALLOWED_MCP_TOOLS,
10046
+ sdkAgents: {},
10047
+ supportsThinking: true,
10048
+ extractFileChangesFromToolUse
10049
+ };
10050
+ }
10051
+ }
10052
+ ];
10053
+
10054
+ // src/proxy/adapters/pi.ts
10055
+ var PI_MCP_SERVER_NAME2 = "pi";
10056
+ var PI_ALLOWED_MCP_TOOLS2 = [
10057
+ `mcp__${PI_MCP_SERVER_NAME2}__read`,
10058
+ `mcp__${PI_MCP_SERVER_NAME2}__write`,
10059
+ `mcp__${PI_MCP_SERVER_NAME2}__edit`,
10060
+ `mcp__${PI_MCP_SERVER_NAME2}__bash`,
10061
+ `mcp__${PI_MCP_SERVER_NAME2}__glob`,
10062
+ `mcp__${PI_MCP_SERVER_NAME2}__grep`
10063
+ ];
9776
10064
  function extractPiCwd(body) {
9777
10065
  let systemText = "";
9778
10066
  if (typeof body.system === "string") {
@@ -9804,10 +10092,10 @@ var piAdapter = {
9804
10092
  return CLAUDE_CODE_ONLY_TOOLS;
9805
10093
  },
9806
10094
  getMcpServerName() {
9807
- return PI_MCP_SERVER_NAME;
10095
+ return PI_MCP_SERVER_NAME2;
9808
10096
  },
9809
10097
  getAllowedMcpTools() {
9810
- return PI_ALLOWED_MCP_TOOLS;
10098
+ return PI_ALLOWED_MCP_TOOLS2;
9811
10099
  },
9812
10100
  buildSdkAgents(_body, _mcpToolNames) {
9813
10101
  return {};
@@ -9837,7 +10125,7 @@ var piAdapter = {
9837
10125
  }
9838
10126
  };
9839
10127
 
9840
- // src/proxy/adapters/forgecode.ts
10128
+ // src/proxy/transforms/forgecode.ts
9841
10129
  var FORGECODE_MCP_SERVER_NAME = "forgecode";
9842
10130
  var FORGECODE_ALLOWED_MCP_TOOLS = [
9843
10131
  `mcp__${FORGECODE_MCP_SERVER_NAME}__read`,
@@ -9847,6 +10135,44 @@ var FORGECODE_ALLOWED_MCP_TOOLS = [
9847
10135
  `mcp__${FORGECODE_MCP_SERVER_NAME}__glob`,
9848
10136
  `mcp__${FORGECODE_MCP_SERVER_NAME}__grep`
9849
10137
  ];
10138
+ var forgeCodeTransforms = [
10139
+ {
10140
+ name: "forgecode-core",
10141
+ adapters: ["forgecode"],
10142
+ onRequest(ctx) {
10143
+ const extractFileChangesFromToolUse = (toolName, toolInput) => {
10144
+ const input = toolInput;
10145
+ const filePath = input?.file_path ?? input?.filePath ?? input?.path;
10146
+ if (toolName === "write" && filePath)
10147
+ return [{ operation: "wrote", path: String(filePath) }];
10148
+ if ((toolName === "patch" || toolName === "multi_patch") && filePath)
10149
+ return [{ operation: "edited", path: String(filePath) }];
10150
+ if (toolName === "shell" && input?.command)
10151
+ return extractFileChangesFromBash(String(input.command));
10152
+ return [];
10153
+ };
10154
+ return {
10155
+ ...ctx,
10156
+ blockedTools: BLOCKED_BUILTIN_TOOLS,
10157
+ incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
10158
+ allowedMcpTools: FORGECODE_ALLOWED_MCP_TOOLS,
10159
+ sdkAgents: {},
10160
+ extractFileChangesFromToolUse
10161
+ };
10162
+ }
10163
+ }
10164
+ ];
10165
+
10166
+ // src/proxy/adapters/forgecode.ts
10167
+ var FORGECODE_MCP_SERVER_NAME2 = "forgecode";
10168
+ var FORGECODE_ALLOWED_MCP_TOOLS2 = [
10169
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__read`,
10170
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__write`,
10171
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__edit`,
10172
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__bash`,
10173
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__glob`,
10174
+ `mcp__${FORGECODE_MCP_SERVER_NAME2}__grep`
10175
+ ];
9850
10176
  function extractForgeCodeCwd(body) {
9851
10177
  let systemText = "";
9852
10178
  if (typeof body.system === "string") {
@@ -9878,10 +10204,10 @@ var forgeCodeAdapter = {
9878
10204
  return CLAUDE_CODE_ONLY_TOOLS;
9879
10205
  },
9880
10206
  getMcpServerName() {
9881
- return FORGECODE_MCP_SERVER_NAME;
10207
+ return FORGECODE_MCP_SERVER_NAME2;
9882
10208
  },
9883
10209
  getAllowedMcpTools() {
9884
- return FORGECODE_ALLOWED_MCP_TOOLS;
10210
+ return FORGECODE_ALLOWED_MCP_TOOLS2;
9885
10211
  },
9886
10212
  buildSdkAgents(_body, _mcpToolNames) {
9887
10213
  return {};
@@ -15677,7 +16003,10 @@ function buildQueryOptions(ctx) {
15677
16003
  isUndo,
15678
16004
  undoRollbackUuid,
15679
16005
  sdkHooks,
15680
- adapter,
16006
+ blockedTools,
16007
+ incompatibleTools,
16008
+ mcpServerName,
16009
+ allowedMcpTools,
15681
16010
  onStderr,
15682
16011
  effort,
15683
16012
  thinking,
@@ -15694,9 +16023,7 @@ function buildQueryOptions(ctx) {
15694
16023
  sdkDebug,
15695
16024
  additionalDirectories
15696
16025
  } = ctx;
15697
- const blockedTools = [...adapter.getBlockedBuiltinTools(), ...adapter.getAgentIncompatibleTools()];
15698
- const mcpServerName = adapter.getMcpServerName();
15699
- const allowedMcpTools = [...adapter.getAllowedMcpTools()];
16026
+ const allBlockedTools = [...blockedTools, ...incompatibleTools];
15700
16027
  return {
15701
16028
  prompt,
15702
16029
  options: {
@@ -15710,14 +16037,14 @@ function buildQueryOptions(ctx) {
15710
16037
  allowDangerouslySkipPermissions: true,
15711
16038
  ...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt),
15712
16039
  ...passthrough ? {
15713
- disallowedTools: blockedTools,
16040
+ disallowedTools: [...allBlockedTools],
15714
16041
  ...passthroughMcp ? {
15715
- allowedTools: passthroughMcp.toolNames,
16042
+ allowedTools: [...passthroughMcp.toolNames],
15716
16043
  mcpServers: { [PASSTHROUGH_MCP_NAME]: passthroughMcp.server }
15717
16044
  } : {}
15718
16045
  } : {
15719
- disallowedTools: blockedTools,
15720
- allowedTools: allowedMcpTools,
16046
+ disallowedTools: [...allBlockedTools],
16047
+ allowedTools: [...allowedMcpTools],
15721
16048
  mcpServers: { [mcpServerName]: createOpencodeMcpServer() }
15722
16049
  },
15723
16050
  plugins: [],
@@ -15752,6 +16079,176 @@ function buildQueryOptions(ctx) {
15752
16079
  };
15753
16080
  }
15754
16081
 
16082
+ // src/proxy/transforms/registry.ts
16083
+ var ADAPTER_TRANSFORMS = {
16084
+ opencode: openCodeTransforms,
16085
+ crush: crushTransforms,
16086
+ droid: droidTransforms,
16087
+ pi: piTransforms,
16088
+ forgecode: forgeCodeTransforms,
16089
+ passthrough: passthroughTransforms
16090
+ };
16091
+ function getAdapterTransforms(adapterName) {
16092
+ return ADAPTER_TRANSFORMS[adapterName] ?? [];
16093
+ }
16094
+
16095
+ // src/proxy/plugins/loader.ts
16096
+ import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
16097
+ import { join as join4, isAbsolute as isAbsolute2, extname } from "path";
16098
+
16099
+ // src/proxy/plugins/validation.ts
16100
+ var KNOWN_ADAPTERS = ["opencode", "crush", "droid", "pi", "forgecode", "passthrough"];
16101
+ var KNOWN_HOOKS = ["onRequest", "onResponse", "onTelemetry", "onSession", "onToolUse", "onToolResult", "onError"];
16102
+ function validateTransform(exported) {
16103
+ if (exported == null || typeof exported !== "object") {
16104
+ return { valid: false, hooks: [], error: "Plugin must export an object" };
16105
+ }
16106
+ const obj = exported;
16107
+ if (typeof obj.name !== "string" || obj.name.length === 0) {
16108
+ return { valid: false, hooks: [], error: "Plugin must have a name: string property" };
16109
+ }
16110
+ const hooks = [];
16111
+ for (const hook of KNOWN_HOOKS) {
16112
+ if (obj[hook] !== undefined) {
16113
+ if (typeof obj[hook] !== "function") {
16114
+ return { valid: false, hooks: [], error: `${hook} must be a function, got ${typeof obj[hook]}` };
16115
+ }
16116
+ hooks.push(hook);
16117
+ }
16118
+ }
16119
+ const warnings = [];
16120
+ if (Array.isArray(obj.adapters)) {
16121
+ for (const adapter of obj.adapters) {
16122
+ if (typeof adapter === "string" && !KNOWN_ADAPTERS.includes(adapter)) {
16123
+ warnings.push(adapter);
16124
+ }
16125
+ }
16126
+ }
16127
+ return {
16128
+ valid: true,
16129
+ hooks,
16130
+ ...warnings.length > 0 ? { warnings } : {}
16131
+ };
16132
+ }
16133
+
16134
+ // src/proxy/plugins/loader.ts
16135
+ var loadCounter = 0;
16136
+ function parsePluginConfig(configPath) {
16137
+ if (!existsSync3(configPath))
16138
+ return [];
16139
+ try {
16140
+ const raw2 = readFileSync2(configPath, "utf-8");
16141
+ const parsed = JSON.parse(raw2);
16142
+ return Array.isArray(parsed.plugins) ? parsed.plugins : [];
16143
+ } catch {
16144
+ return [];
16145
+ }
16146
+ }
16147
+ async function loadPlugins(pluginDir, configPath) {
16148
+ resetAllPluginStats();
16149
+ const config = configPath ? parsePluginConfig(configPath) : [];
16150
+ const pluginDirExists = existsSync3(pluginDir);
16151
+ let filenames = [];
16152
+ if (pluginDirExists) {
16153
+ try {
16154
+ filenames = readdirSync2(pluginDir).filter((f) => {
16155
+ const ext2 = extname(f);
16156
+ return ext2 === ".ts" || ext2 === ".js";
16157
+ });
16158
+ } catch {
16159
+ filenames = [];
16160
+ }
16161
+ }
16162
+ if (!pluginDirExists && config.length === 0)
16163
+ return [];
16164
+ const ordered = [];
16165
+ const seen = new Set;
16166
+ for (const entry of config) {
16167
+ const filename = isAbsolute2(entry.path) ? entry.path : entry.path;
16168
+ if (filenames.includes(filename) || isAbsolute2(entry.path)) {
16169
+ ordered.push({ filename, entry });
16170
+ seen.add(filename);
16171
+ }
16172
+ }
16173
+ for (const filename of filenames) {
16174
+ if (!seen.has(filename)) {
16175
+ ordered.push({ filename });
16176
+ }
16177
+ }
16178
+ const loaded = [];
16179
+ const seenNames = new Set;
16180
+ for (const { filename, entry } of ordered) {
16181
+ const filePath = isAbsolute2(filename) ? filename : join4(pluginDir, filename);
16182
+ if (entry && !entry.enabled) {
16183
+ loaded.push({
16184
+ name: filename,
16185
+ status: "disabled",
16186
+ hooks: [],
16187
+ path: filePath,
16188
+ transform: { name: filename }
16189
+ });
16190
+ continue;
16191
+ }
16192
+ try {
16193
+ const cacheBuster = `?t=${Date.now()}-${++loadCounter}`;
16194
+ const mod = await import(filePath + cacheBuster);
16195
+ const exported = mod.default ?? mod;
16196
+ const transforms = Array.isArray(exported) ? exported : [exported];
16197
+ for (const item of transforms) {
16198
+ const validation = validateTransform(item);
16199
+ if (!validation.valid) {
16200
+ loaded.push({
16201
+ name: filename,
16202
+ status: "error",
16203
+ error: validation.error,
16204
+ hooks: [],
16205
+ path: filePath,
16206
+ transform: { name: filename }
16207
+ });
16208
+ continue;
16209
+ }
16210
+ const transform = item;
16211
+ if (seenNames.has(transform.name)) {
16212
+ loaded.push({
16213
+ name: transform.name,
16214
+ status: "error",
16215
+ error: `Skipped: duplicate plugin name "${transform.name}"`,
16216
+ hooks: validation.hooks,
16217
+ path: filePath,
16218
+ transform
16219
+ });
16220
+ continue;
16221
+ }
16222
+ seenNames.add(transform.name);
16223
+ registerPluginStats(transform.name);
16224
+ loaded.push({
16225
+ name: transform.name,
16226
+ description: transform.description,
16227
+ version: transform.version,
16228
+ adapters: transform.adapters,
16229
+ hooks: validation.hooks,
16230
+ status: "active",
16231
+ path: filePath,
16232
+ transform
16233
+ });
16234
+ }
16235
+ } catch (err) {
16236
+ loaded.push({
16237
+ name: filename,
16238
+ status: "error",
16239
+ error: `Failed to load: ${err instanceof Error ? err.message : String(err)}`,
16240
+ hooks: [],
16241
+ path: filePath,
16242
+ transform: { name: filename }
16243
+ });
16244
+ }
16245
+ }
16246
+ return loaded;
16247
+ }
16248
+ function getActiveTransforms(plugins) {
16249
+ return plugins.filter((p) => p.status === "active").map((p) => p.transform);
16250
+ }
16251
+
15755
16252
  // src/proxy/betas.ts
15756
16253
  var BILLABLE_BETA_PREFIXES_ON_MAX = [
15757
16254
  "extended-cache-ttl-"
@@ -16048,17 +16545,17 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
16048
16545
  // src/proxy/sessionStore.ts
16049
16546
  import {
16050
16547
  closeSync,
16051
- existsSync as existsSync3,
16548
+ existsSync as existsSync4,
16052
16549
  mkdirSync,
16053
16550
  openSync,
16054
- readFileSync as readFileSync2,
16551
+ readFileSync as readFileSync3,
16055
16552
  renameSync,
16056
16553
  statSync,
16057
16554
  unlinkSync,
16058
16555
  writeFileSync
16059
16556
  } from "node:fs";
16060
16557
  import { homedir as homedir3 } from "node:os";
16061
- import { join as join4 } from "node:path";
16558
+ import { join as join5 } from "node:path";
16062
16559
  var DEFAULT_MAX_STORED_SESSIONS = 1e4;
16063
16560
  var STALE_LOCK_THRESHOLD_MS = 30000;
16064
16561
  function getMaxStoredSessions() {
@@ -16106,17 +16603,17 @@ var sessionDirOverride = null;
16106
16603
  var skipLocking = false;
16107
16604
  function getStorePath() {
16108
16605
  const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
16109
- if (!existsSync3(dir)) {
16606
+ if (!existsSync4(dir)) {
16110
16607
  mkdirSync(dir, { recursive: true });
16111
16608
  }
16112
- return join4(dir, "sessions.json");
16609
+ return join5(dir, "sessions.json");
16113
16610
  }
16114
16611
  function getDefaultCacheDir() {
16115
- const newDir = join4(homedir3(), ".cache", "meridian");
16116
- const oldDir = join4(homedir3(), ".cache", "opencode-claude-max-proxy");
16117
- if (existsSync3(newDir))
16612
+ const newDir = join5(homedir3(), ".cache", "meridian");
16613
+ const oldDir = join5(homedir3(), ".cache", "opencode-claude-max-proxy");
16614
+ if (existsSync4(newDir))
16118
16615
  return newDir;
16119
- if (existsSync3(oldDir)) {
16616
+ if (existsSync4(oldDir)) {
16120
16617
  try {
16121
16618
  const { symlinkSync } = __require("fs");
16122
16619
  symlinkSync(oldDir, newDir);
@@ -16129,10 +16626,10 @@ function getDefaultCacheDir() {
16129
16626
  }
16130
16627
  function readStore() {
16131
16628
  const path3 = getStorePath();
16132
- if (!existsSync3(path3))
16629
+ if (!existsSync4(path3))
16133
16630
  return {};
16134
16631
  try {
16135
- const data = readFileSync2(path3, "utf-8");
16632
+ const data = readFileSync3(path3, "utf-8");
16136
16633
  return JSON.parse(data);
16137
16634
  } catch (e) {
16138
16635
  console.error("[sessionStore] read failed:", e.message);
@@ -16631,6 +17128,10 @@ function createProxyServer(config = {}) {
16631
17128
  const sessionDiscoveredTools = new Map;
16632
17129
  const sessionToolCache = new Map;
16633
17130
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
17131
+ const pluginDir = finalConfig.pluginDir ?? join7(homedir5(), ".config", "meridian", "plugins");
17132
+ const pluginConfigPath = finalConfig.pluginConfigPath ?? join7(homedir5(), ".config", "meridian", "plugins.json");
17133
+ let loadedPlugins = [];
17134
+ let pluginTransforms = [];
16634
17135
  const app = new Hono2;
16635
17136
  app.use("*", cors());
16636
17137
  app.use("/v1/*", requireAuth);
@@ -16640,6 +17141,8 @@ function createProxyServer(config = {}) {
16640
17141
  app.use("/metrics", requireAuth);
16641
17142
  app.use("/profiles/*", requireAuth);
16642
17143
  app.use("/profiles", requireAuth);
17144
+ app.use("/plugins/*", requireAuth);
17145
+ app.use("/plugins", requireAuth);
16643
17146
  app.use("/auth/*", requireAuth);
16644
17147
  app.get("/", (c) => {
16645
17148
  const accept = c.req.header("accept") || "";
@@ -16697,8 +17200,6 @@ function createProxyServer(config = {}) {
16697
17200
  const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
16698
17201
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
16699
17202
  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
17203
  const workingDirectory = (process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR) || adapter.extractWorkingDirectory(body) || process.cwd();
16703
17204
  const {
16704
17205
  CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,
@@ -16717,6 +17218,20 @@ function createProxyServer(config = {}) {
16717
17218
  `);
16718
17219
  }
16719
17220
  }
17221
+ const adapterTransforms = getAdapterTransforms(adapter.name);
17222
+ const pipeline = buildPipeline(adapterTransforms, pluginTransforms);
17223
+ const pipelineCtx = runTransformHook(pipeline, "onRequest", createRequestContext({
17224
+ adapter: adapter.name,
17225
+ body,
17226
+ headers: c.req.raw.headers,
17227
+ model,
17228
+ messages: body.messages || [],
17229
+ systemContext,
17230
+ tools: body.tools,
17231
+ stream: body.stream ?? false,
17232
+ workingDirectory
17233
+ }), adapter.name);
17234
+ const stream2 = pipelineCtx.prefersStreaming !== undefined ? pipelineCtx.prefersStreaming : body.stream ?? false;
16720
17235
  const effortHeader = c.req.header("x-opencode-effort");
16721
17236
  const thinkingHeader = c.req.header("x-opencode-thinking");
16722
17237
  const taskBudgetHeader = c.req.header("x-opencode-task-budget");
@@ -16791,14 +17306,14 @@ function createProxyServer(config = {}) {
16791
17306
  messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
16792
17307
  hasSystemPrompt: Boolean(body.system)
16793
17308
  });
16794
- const sdkAgents = adapter.buildSdkAgents?.(body, adapter.getAllowedMcpTools()) ?? {};
17309
+ const sdkAgents = pipelineCtx.sdkAgents;
16795
17310
  const validAgentNames = Object.keys(sdkAgents);
16796
17311
  if ((process.env.MERIDIAN_DEBUG ?? process.env.CLAUDE_PROXY_DEBUG) && validAgentNames.length > 0) {
16797
17312
  claudeLog("debug.agents", { names: validAgentNames, count: validAgentNames.length });
16798
17313
  }
16799
- systemContext += adapter.buildSystemContextAddendum?.(body, sdkAgents) ?? "";
17314
+ systemContext = pipelineCtx.systemContext ?? systemContext;
16800
17315
  const sanitizeOpts = {
16801
- stripSystemReminder: adapter.leaksCwdViaSystemReminder?.() ?? false
17316
+ stripSystemReminder: pipelineCtx.leaksCwdViaSystemReminder
16802
17317
  };
16803
17318
  const allMessages = body.messages || [];
16804
17319
  let messagesToConvert;
@@ -16862,9 +17377,8 @@ function createProxyServer(config = {}) {
16862
17377
 
16863
17378
  `) || "";
16864
17379
  }
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?.() ?? [];
17380
+ const passthrough = pipelineCtx.passthrough !== undefined ? pipelineCtx.passthrough : envBool("PASSTHROUGH");
17381
+ const settingSources = envBool("LOAD_CONTEXT") || sdkFeatures.claudeMd === "full" ? ["user", "project"] : sdkFeatures.claudeMd === "project" ? ["project"] : pipelineCtx.settingSources ?? [];
16868
17382
  const capturedToolUses = [];
16869
17383
  const fileChanges = [];
16870
17384
  let passthroughMcp;
@@ -16882,7 +17396,7 @@ function createProxyServer(config = {}) {
16882
17396
  if (cachedMcp && cachedMcp.key === toolSetKey) {
16883
17397
  passthroughMcp = cachedMcp.mcp;
16884
17398
  } else {
16885
- passthroughMcp = createPassthroughMcpServer(requestTools, adapter.getCoreToolNames?.());
17399
+ passthroughMcp = createPassthroughMcpServer(requestTools, pipelineCtx.coreToolNames ? [...pipelineCtx.coreToolNames] : undefined);
16886
17400
  if (profileSessionId) {
16887
17401
  sessionMcpCache.set(profileSessionId, { key: toolSetKey, mcp: passthroughMcp });
16888
17402
  if (cachedMcp) {
@@ -16894,14 +17408,14 @@ function createProxyServer(config = {}) {
16894
17408
  sessionToolCache.set(profileSessionId, requestTools);
16895
17409
  }
16896
17410
  const hasDeferredTools = passthroughMcp?.hasDeferredTools ?? false;
16897
- const coreNames = adapter.getCoreToolNames?.();
17411
+ const coreNames = pipelineCtx.coreToolNames ? [...pipelineCtx.coreToolNames] : undefined;
16898
17412
  const coreSet = coreNames ? new Set(coreNames.map((n) => n.toLowerCase())) : undefined;
16899
17413
  const deferredToolCount = hasDeferredTools && requestTools.length > 0 ? requestTools.filter((t) => t.defer_loading === true || coreSet && !coreSet.has(String(t.name).toLowerCase())).length : 0;
16900
17414
  if (hasDeferredTools) {
16901
17415
  console.error(`[PROXY] ${requestMeta.requestId} deferred=${deferredToolCount}/${toolCount} tools (core: ${coreNames?.join(",") ?? "none"})`);
16902
17416
  }
16903
17417
  const mcpPrefix = `mcp__${adapter.getMcpServerName()}__`;
16904
- const trackFileChanges = !(process.env.MERIDIAN_NO_FILE_CHANGES ?? process.env.CLAUDE_PROXY_NO_FILE_CHANGES) && adapter.shouldTrackFileChanges?.() !== false;
17418
+ const trackFileChanges = !(process.env.MERIDIAN_NO_FILE_CHANGES ?? process.env.CLAUDE_PROXY_NO_FILE_CHANGES) && pipelineCtx.shouldTrackFileChanges;
16905
17419
  const fileChangeHook = trackFileChanges ? createFileChangeHook(fileChanges, mcpPrefix) : undefined;
16906
17420
  const discoveredTools = new Set;
16907
17421
  const sdkHooks = passthrough ? {
@@ -16926,7 +17440,7 @@ function createProxyServer(config = {}) {
16926
17440
  }]
16927
17441
  }]
16928
17442
  } : {
16929
- ...adapter.buildSdkHooks?.(body, sdkAgents) ?? {},
17443
+ ...pipelineCtx.sdkHooks ?? {},
16930
17444
  ...fileChangeHook ? { PostToolUse: [fileChangeHook] } : {}
16931
17445
  };
16932
17446
  const stderrLines = [];
@@ -16973,7 +17487,10 @@ function createProxyServer(config = {}) {
16973
17487
  isUndo,
16974
17488
  undoRollbackUuid,
16975
17489
  sdkHooks,
16976
- adapter,
17490
+ blockedTools: pipelineCtx.blockedTools,
17491
+ incompatibleTools: pipelineCtx.incompatibleTools,
17492
+ mcpServerName: adapter.getMcpServerName(),
17493
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
16977
17494
  onStderr,
16978
17495
  effort,
16979
17496
  thinking,
@@ -17027,7 +17544,10 @@ function createProxyServer(config = {}) {
17027
17544
  isUndo: false,
17028
17545
  undoRollbackUuid: undefined,
17029
17546
  sdkHooks,
17030
- adapter,
17547
+ blockedTools: pipelineCtx.blockedTools,
17548
+ incompatibleTools: pipelineCtx.incompatibleTools,
17549
+ mcpServerName: adapter.getMcpServerName(),
17550
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
17031
17551
  onStderr,
17032
17552
  effort,
17033
17553
  thinking,
@@ -17127,7 +17647,7 @@ function createProxyServer(config = {}) {
17127
17647
  claudeLog("passthrough.toolsearch_filtered", { mode: "non_stream" });
17128
17648
  continue;
17129
17649
  }
17130
- if (passthrough && !adapter.supportsThinking?.() && !sdkFeatures.thinkingPassthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
17650
+ if (passthrough && !pipelineCtx.supportsThinking && !sdkFeatures.thinkingPassthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
17131
17651
  claudeLog("passthrough.thinking_stripped", { mode: "non_stream", type: b.type });
17132
17652
  continue;
17133
17653
  }
@@ -17191,8 +17711,8 @@ Subprocess stderr: ${stderrOutput}`;
17191
17711
  const hasToolUse = contentBlocks.some((b) => b.type === "tool_use");
17192
17712
  const stopReason = hasToolUse ? "tool_use" : "end_turn";
17193
17713
  if (trackFileChanges) {
17194
- if (passthrough && stopReason === "end_turn" && adapter.extractFileChangesFromToolUse) {
17195
- const passthroughChanges = extractFileChangesFromMessages(body.messages || [], adapter.extractFileChangesFromToolUse.bind(adapter));
17714
+ if (passthrough && stopReason === "end_turn" && pipelineCtx.extractFileChangesFromToolUse) {
17715
+ const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
17196
17716
  fileChanges.push(...passthroughChanges);
17197
17717
  }
17198
17718
  const fileChangeSummary = formatFileChangeSummary(fileChanges);
@@ -17227,6 +17747,7 @@ Subprocess stderr: ${stderrOutput}`;
17227
17747
  requestId: requestMeta.requestId,
17228
17748
  timestamp: Date.now(),
17229
17749
  adapter: adapter.name,
17750
+ requestSource,
17230
17751
  model,
17231
17752
  requestModel: body.model || undefined,
17232
17753
  mode: "non-stream",
@@ -17342,7 +17863,10 @@ Subprocess stderr: ${stderrOutput}`;
17342
17863
  isUndo,
17343
17864
  undoRollbackUuid,
17344
17865
  sdkHooks,
17345
- adapter,
17866
+ blockedTools: pipelineCtx.blockedTools,
17867
+ incompatibleTools: pipelineCtx.incompatibleTools,
17868
+ mcpServerName: adapter.getMcpServerName(),
17869
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
17346
17870
  onStderr,
17347
17871
  effort,
17348
17872
  thinking,
@@ -17396,7 +17920,10 @@ Subprocess stderr: ${stderrOutput}`;
17396
17920
  isUndo: false,
17397
17921
  undoRollbackUuid: undefined,
17398
17922
  sdkHooks,
17399
- adapter,
17923
+ blockedTools: pipelineCtx.blockedTools,
17924
+ incompatibleTools: pipelineCtx.incompatibleTools,
17925
+ mcpServerName: adapter.getMcpServerName(),
17926
+ allowedMcpTools: pipelineCtx.allowedMcpTools,
17400
17927
  onStderr,
17401
17928
  effort,
17402
17929
  thinking,
@@ -17548,7 +18075,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
17548
18075
  }
17549
18076
  if (eventType === "content_block_start") {
17550
18077
  const block = event.content_block;
17551
- if (passthrough && !adapter.supportsThinking?.() && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
18078
+ if (passthrough && !pipelineCtx.supportsThinking && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
17552
18079
  if (eventIndex !== undefined)
17553
18080
  skipBlockIndices.add(eventIndex);
17554
18081
  claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
@@ -17681,8 +18208,8 @@ data: ${JSON.stringify({
17681
18208
 
17682
18209
  `), "passthrough_message_delta");
17683
18210
  }
17684
- if (trackFileChanges && passthrough && adapter.extractFileChangesFromToolUse) {
17685
- const passthroughChanges = extractFileChangesFromMessages(body.messages || [], adapter.extractFileChangesFromToolUse.bind(adapter));
18211
+ if (trackFileChanges && passthrough && pipelineCtx.extractFileChangesFromToolUse) {
18212
+ const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
17686
18213
  fileChanges.push(...passthroughChanges);
17687
18214
  }
17688
18215
  if (trackFileChanges) {
@@ -17750,6 +18277,7 @@ data: {"type":"message_stop"}
17750
18277
  requestId: requestMeta.requestId,
17751
18278
  timestamp: Date.now(),
17752
18279
  adapter: adapter.name,
18280
+ requestSource,
17753
18281
  model,
17754
18282
  requestModel: body.model || undefined,
17755
18283
  mode: "stream",
@@ -18005,7 +18533,7 @@ data: ${JSON.stringify({
18005
18533
  });
18006
18534
  });
18007
18535
  app.get("/profiles", async (c) => {
18008
- const { profilePageHtml } = await import("./profilePage-g5t5t6av.js");
18536
+ const { profilePageHtml } = await import("./profilePage-77z05e0r.js");
18009
18537
  return c.html(profilePageHtml);
18010
18538
  });
18011
18539
  app.post("/profiles/active", async (c) => {
@@ -18030,6 +18558,45 @@ data: ${JSON.stringify({
18030
18558
  console.error(`[PROXY] Active profile switched to: ${body.profile} (session cache cleared)`);
18031
18559
  return c.json({ success: true, activeProfile: body.profile });
18032
18560
  });
18561
+ app.get("/plugins/list", async (c) => {
18562
+ const { getPluginStats } = await import("./stats-4c4ewmdh.js");
18563
+ return c.json({
18564
+ plugins: loadedPlugins.map((p) => ({
18565
+ name: p.name,
18566
+ description: p.description,
18567
+ version: p.version,
18568
+ adapters: p.adapters,
18569
+ hooks: p.hooks,
18570
+ status: p.status,
18571
+ path: p.path,
18572
+ ...p.error ? { error: p.error } : {},
18573
+ ...p.status === "active" ? { stats: getPluginStats(p.name) } : {}
18574
+ }))
18575
+ });
18576
+ });
18577
+ app.post("/plugins/reload", async (c) => {
18578
+ try {
18579
+ loadedPlugins = await loadPlugins(pluginDir, pluginConfigPath);
18580
+ pluginTransforms = getActiveTransforms(loadedPlugins);
18581
+ const active = loadedPlugins.filter((p) => p.status === "active").length;
18582
+ console.error(`[PROXY] Plugins reloaded: ${active} active`);
18583
+ return c.json({
18584
+ success: true,
18585
+ plugins: loadedPlugins.map((p) => ({
18586
+ name: p.name,
18587
+ status: p.status,
18588
+ hooks: p.hooks,
18589
+ ...p.error ? { error: p.error } : {}
18590
+ }))
18591
+ });
18592
+ } catch (err) {
18593
+ return c.json({ success: false, error: String(err) }, 500);
18594
+ }
18595
+ });
18596
+ app.get("/plugins", async (c) => {
18597
+ const { pluginPageHtml } = await import("./pluginPage-85s6t6k8.js");
18598
+ return c.html(pluginPageHtml);
18599
+ });
18033
18600
  app.post("/auth/refresh", async (c) => {
18034
18601
  const success = await refreshOAuthToken();
18035
18602
  if (success) {
@@ -18180,11 +18747,27 @@ data: ${JSON.stringify({
18180
18747
  console.error(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
18181
18748
  return c.json({ error: { type: "not_found", message: `Endpoint not supported: ${c.req.method} ${new URL(c.req.url).pathname}` } }, 404);
18182
18749
  });
18183
- return { app, config: finalConfig };
18750
+ async function initPluginsAsync() {
18751
+ try {
18752
+ loadedPlugins = await loadPlugins(pluginDir, pluginConfigPath);
18753
+ pluginTransforms = getActiveTransforms(loadedPlugins);
18754
+ if (loadedPlugins.length > 0) {
18755
+ const active = loadedPlugins.filter((p) => p.status === "active").length;
18756
+ const disabled = loadedPlugins.filter((p) => p.status === "disabled").length;
18757
+ const errored = loadedPlugins.filter((p) => p.status === "error").length;
18758
+ console.error(`[PROXY] Plugins loaded: ${active} active, ${disabled} disabled, ${errored} errors`);
18759
+ }
18760
+ } catch (err) {
18761
+ console.error(`[PROXY] Plugin loading failed: ${err instanceof Error ? err.message : String(err)}`);
18762
+ }
18763
+ }
18764
+ return { app, config: finalConfig, initPlugins: initPluginsAsync };
18184
18765
  }
18185
18766
  async function startProxyServer(config = {}) {
18186
18767
  claudeExecutable = await resolveClaudeExecutableAsync();
18187
- const { app, config: finalConfig } = createProxyServer(config);
18768
+ const { app, config: finalConfig, initPlugins } = createProxyServer(config);
18769
+ if (initPlugins)
18770
+ await initPlugins();
18188
18771
  const server = serve({
18189
18772
  fetch: app.fetch,
18190
18773
  port: finalConfig.port,
@@ -18245,4 +18828,4 @@ Or use a different port:`);
18245
18828
  };
18246
18829
  }
18247
18830
 
18248
- export { computeLineageHash, hashMessage, computeMessageHashes, getMaxSessionsLimit, clearSessionCache, createProxyServer, startProxyServer };
18831
+ export { runTransformHook, runObserveHook, buildPipeline, createRequestContext, computeLineageHash, hashMessage, computeMessageHashes, getMaxSessionsLimit, clearSessionCache, createProxyServer, startProxyServer };