@rynfar/meridian 1.35.0 → 1.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import {
2
+ init_profileBar,
2
3
  profileBarCss,
3
4
  profileBarHtml,
4
5
  profileBarJs
5
- } from "./cli-g9ypdz51.js";
6
+ } from "./cli-pr79d7nw.js";
6
7
  import {
7
8
  checkPluginConfigured
8
9
  } from "./cli-rtab0qa6.js";
@@ -1148,6 +1149,392 @@ var init_sqlite = __esm(() => {
1148
1149
  import_libsql = __toESM(require_libsql(), 1);
1149
1150
  });
1150
1151
 
1152
+ // src/proxy/sdkFeatures.ts
1153
+ var exports_sdkFeatures = {};
1154
+ __export(exports_sdkFeatures, {
1155
+ validateFeatureUpdate: () => validateFeatureUpdate,
1156
+ updateAdapterFeatures: () => updateAdapterFeatures,
1157
+ resetAdapterFeatures: () => resetAdapterFeatures,
1158
+ getFeaturesForAdapter: () => getFeaturesForAdapter,
1159
+ getAllFeatureConfigs: () => getAllFeatureConfigs
1160
+ });
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";
1163
+ import { homedir as homedir4 } from "node:os";
1164
+ function getConfigPath() {
1165
+ const dir = join5(homedir4(), ".config", "meridian");
1166
+ if (!existsSync4(dir))
1167
+ mkdirSync2(dir, { recursive: true });
1168
+ return join5(dir, "sdk-features.json");
1169
+ }
1170
+ function readConfig() {
1171
+ const now = Date.now();
1172
+ if (cachedConfig && now - lastReadTime < CACHE_TTL_MS)
1173
+ return cachedConfig;
1174
+ const path3 = getConfigPath();
1175
+ try {
1176
+ if (existsSync4(path3)) {
1177
+ cachedConfig = JSON.parse(readFileSync3(path3, "utf-8"));
1178
+ } else {
1179
+ cachedConfig = {};
1180
+ }
1181
+ } catch {
1182
+ cachedConfig = {};
1183
+ }
1184
+ lastReadTime = now;
1185
+ return cachedConfig;
1186
+ }
1187
+ function writeConfig(config) {
1188
+ const path3 = getConfigPath();
1189
+ const tmp = `${path3}.tmp`;
1190
+ try {
1191
+ writeFileSync2(tmp, JSON.stringify(config, null, 2));
1192
+ renameSync2(tmp, path3);
1193
+ cachedConfig = config;
1194
+ lastReadTime = Date.now();
1195
+ } catch (e) {
1196
+ console.error("[sdk-features] write failed:", e.message);
1197
+ }
1198
+ }
1199
+ function getFeaturesForAdapter(adapterName) {
1200
+ const config = readConfig();
1201
+ const userOverrides = config[adapterName] ?? {};
1202
+ const adapterDefaults = ADAPTER_DEFAULTS[adapterName] ?? {};
1203
+ return {
1204
+ ...DEFAULT_FEATURES,
1205
+ ...adapterDefaults,
1206
+ ...userOverrides
1207
+ };
1208
+ }
1209
+ function getAllFeatureConfigs() {
1210
+ const adapters = ["opencode", "crush", "forgecode", "pi", "droid", "passthrough"];
1211
+ const result = {};
1212
+ for (const name of adapters) {
1213
+ result[name] = getFeaturesForAdapter(name);
1214
+ }
1215
+ return result;
1216
+ }
1217
+ function validateFeatureUpdate(raw2) {
1218
+ if (raw2 === null || typeof raw2 !== "object" || Array.isArray(raw2)) {
1219
+ throw new Error("body must be a JSON object");
1220
+ }
1221
+ const input = raw2;
1222
+ const result = {};
1223
+ for (const [key, value] of Object.entries(input)) {
1224
+ if (!(key in DEFAULT_FEATURES))
1225
+ continue;
1226
+ const expected = typeof DEFAULT_FEATURES[key];
1227
+ if (key === "claudeMd") {
1228
+ if (typeof value !== "string" || !VALID_CLAUDE_MD_VALUES.has(value)) {
1229
+ throw new Error(`claudeMd must be one of: ${[...VALID_CLAUDE_MD_VALUES].join(", ")}`);
1230
+ }
1231
+ result[key] = value;
1232
+ } else if (key === "thinking") {
1233
+ if (typeof value !== "string" || !VALID_THINKING_VALUES.has(value)) {
1234
+ throw new Error(`thinking must be one of: ${[...VALID_THINKING_VALUES].join(", ")}`);
1235
+ }
1236
+ result[key] = value;
1237
+ } else if (expected === "boolean") {
1238
+ if (typeof value !== "boolean")
1239
+ throw new Error(`${key} must be a boolean`);
1240
+ result[key] = value;
1241
+ } else if (expected === "number") {
1242
+ if (typeof value !== "number" || !isFinite(value))
1243
+ throw new Error(`${key} must be a finite number`);
1244
+ result[key] = value;
1245
+ } else if (expected === "string") {
1246
+ if (typeof value !== "string")
1247
+ throw new Error(`${key} must be a string`);
1248
+ result[key] = value;
1249
+ }
1250
+ }
1251
+ return result;
1252
+ }
1253
+ function updateAdapterFeatures(adapterName, features) {
1254
+ const config = readConfig();
1255
+ config[adapterName] = { ...config[adapterName] ?? {}, ...features };
1256
+ writeConfig(config);
1257
+ }
1258
+ function resetAdapterFeatures(adapterName) {
1259
+ const config = readConfig();
1260
+ delete config[adapterName];
1261
+ writeConfig(config);
1262
+ }
1263
+ var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null, lastReadTime = 0, CACHE_TTL_MS = 5000, VALID_CLAUDE_MD_VALUES, VALID_THINKING_VALUES;
1264
+ var init_sdkFeatures = __esm(() => {
1265
+ DEFAULT_FEATURES = {
1266
+ codeSystemPrompt: false,
1267
+ clientSystemPrompt: true,
1268
+ claudeMd: "off",
1269
+ memory: false,
1270
+ dreaming: false,
1271
+ thinking: "disabled",
1272
+ thinkingPassthrough: false,
1273
+ sharedMemory: false,
1274
+ maxBudgetUsd: 0,
1275
+ fallbackModel: "",
1276
+ sdkDebug: false,
1277
+ additionalDirectories: ""
1278
+ };
1279
+ ADAPTER_DEFAULTS = {};
1280
+ VALID_CLAUDE_MD_VALUES = new Set(["off", "project", "full"]);
1281
+ VALID_THINKING_VALUES = new Set(["adaptive", "enabled", "disabled"]);
1282
+ });
1283
+
1284
+ // src/telemetry/settingsPage.ts
1285
+ var exports_settingsPage = {};
1286
+ __export(exports_settingsPage, {
1287
+ settingsPageHtml: () => settingsPageHtml
1288
+ });
1289
+ var settingsPageHtml;
1290
+ var init_settingsPage = __esm(() => {
1291
+ init_profileBar();
1292
+ settingsPageHtml = `<!DOCTYPE html>
1293
+ <html lang="en">
1294
+ <head>
1295
+ <meta charset="utf-8">
1296
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1297
+ <title>Meridian — SDK Features</title>
1298
+ <link rel="icon" type="image/svg+xml" href="/telemetry/icon.svg">
1299
+ <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
+ }
1306
+ * { box-sizing: border-box; margin: 0; padding: 0; }
1307
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
1308
+ background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
1309
+ ${profileBarCss}
1310
+ .content { max-width: 900px; margin: 0 auto; padding: 24px; }
1311
+ h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
1312
+ .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }
1313
+ .nav { display: flex; gap: 16px; margin-bottom: 24px; font-size: 13px; }
1314
+ .nav a { color: var(--muted); text-decoration: none; }
1315
+ .nav a:hover { color: var(--accent); }
1316
+ .nav a.active { color: var(--accent); }
1317
+
1318
+ .adapter-card {
1319
+ background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
1320
+ padding: 20px; margin-bottom: 16px;
1321
+ }
1322
+ .adapter-header {
1323
+ display: flex; align-items: center; justify-content: space-between;
1324
+ margin-bottom: 16px;
1325
+ }
1326
+ .adapter-name { font-size: 16px; font-weight: 600; }
1327
+ .adapter-badge {
1328
+ font-size: 10px; padding: 2px 8px; border-radius: 10px;
1329
+ text-transform: uppercase; letter-spacing: 0.5px;
1330
+ }
1331
+ .badge-active { background: rgba(63, 185, 80, 0.15); color: var(--green); }
1332
+ .badge-inactive { background: rgba(139, 148, 158, 0.15); color: var(--muted); }
1333
+
1334
+ .feature-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
1335
+ @media (max-width: 600px) { .feature-grid { grid-template-columns: 1fr; } }
1336
+
1337
+ .feature-row {
1338
+ display: flex; align-items: center; justify-content: space-between;
1339
+ padding: 10px 14px; border-radius: 6px;
1340
+ background: var(--bg); border: 1px solid var(--border);
1341
+ }
1342
+ .feature-info { display: flex; flex-direction: column; }
1343
+ .feature-label { font-size: 13px; font-weight: 500; }
1344
+ .feature-desc { font-size: 11px; color: var(--muted); margin-top: 2px; }
1345
+
1346
+ /* Toggle switch */
1347
+ .toggle { position: relative; width: 36px; height: 20px; flex-shrink: 0; }
1348
+ .toggle input { opacity: 0; width: 0; height: 0; }
1349
+ .toggle-track {
1350
+ position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
1351
+ background: var(--border); border-radius: 10px; transition: background 0.2s;
1352
+ }
1353
+ .toggle-track::after {
1354
+ content: ""; position: absolute; height: 14px; width: 14px;
1355
+ left: 3px; bottom: 3px; background: var(--muted); border-radius: 50%;
1356
+ transition: transform 0.2s, background 0.2s;
1357
+ }
1358
+ .toggle input:checked + .toggle-track { background: var(--accent); }
1359
+ .toggle input:checked + .toggle-track::after {
1360
+ transform: translateX(16px); background: var(--text);
1361
+ }
1362
+
1363
+ /* Select dropdown */
1364
+ .feature-select {
1365
+ background: var(--surface); color: var(--text); border: 1px solid var(--border);
1366
+ border-radius: 6px; padding: 4px 8px; font-size: 12px; cursor: pointer;
1367
+ }
1368
+
1369
+ .save-indicator {
1370
+ position: fixed; bottom: 24px; right: 24px;
1371
+ background: var(--green); color: #000; padding: 8px 16px;
1372
+ border-radius: 6px; font-size: 13px; font-weight: 500;
1373
+ opacity: 0; transition: opacity 0.3s; pointer-events: none;
1374
+ }
1375
+ .save-indicator.visible { opacity: 1; }
1376
+
1377
+ .reset-btn {
1378
+ background: none; border: 1px solid var(--border); color: var(--muted);
1379
+ border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer;
1380
+ }
1381
+ .reset-btn:hover { border-color: var(--red); color: var(--red); }
1382
+ </style>
1383
+ </head>
1384
+ <body>
1385
+ ${profileBarHtml}
1386
+ <div class="content">
1387
+ <h1>SDK Features <span style="font-size:11px;padding:2px 8px;border-radius:10px;background:rgba(210,153,34,0.15);color:var(--yellow);vertical-align:middle;margin-left:8px">Experimental</span></h1>
1388
+ <p class="subtitle" style="max-width:720px;line-height:1.6">
1389
+ Unlock Claude Code features for any connected agent. Capabilities like auto-memory, dreaming, and CLAUDE.md — normally
1390
+ exclusive to Claude Code — become available to OpenCode, Crush, Droid, and any other harness routed through Meridian.
1391
+ Each agent keeps its own toolchain while gaining access to these additional features.<br><br>
1392
+ <strong style="color:var(--text)">System prompts:</strong> For these features to work correctly, both the Claude Code prompt and your client prompt
1393
+ should be enabled. When both are active, they are appended together — Claude Code's base instructions come first,
1394
+ followed by your agent's specific instructions.
1395
+ </p>
1396
+
1397
+ <div id="adapters"></div>
1398
+ </div>
1399
+
1400
+ <div class="save-indicator" id="saveIndicator">Saved</div>
1401
+
1402
+ <script>
1403
+ const FEATURES = [
1404
+ { key: 'codeSystemPrompt', label: 'Claude Code Prompt', desc: 'Include the built-in Claude Code system prompt (tool usage rules, safety guidelines, coding best practices)', type: 'toggle' },
1405
+ { key: 'clientSystemPrompt', label: 'Client Prompt', desc: 'Include the system prompt sent by the connecting agent (e.g. OpenCode or Crush instructions)', type: 'toggle' },
1406
+ { key: 'claudeMd', label: 'CLAUDE.md', desc: 'Load CLAUDE.md instruction files — Off: none, Project: ./CLAUDE.md only, Full: ~/.claude/CLAUDE.md + ./CLAUDE.md', type: 'select', options: ['off', 'project', 'full'] },
1407
+ { key: 'memory', label: 'Memory', desc: 'Read and write memories across sessions', type: 'toggle' },
1408
+ { key: 'dreaming', label: 'Auto-Dream', desc: 'Background memory consolidation', type: 'toggle' },
1409
+ { key: 'thinking', label: 'Thinking', desc: 'Extended thinking mode', type: 'select', options: ['disabled', 'adaptive', 'enabled'] },
1410
+ { key: 'thinkingPassthrough', label: 'Thinking Passthrough', desc: 'Forward thinking blocks to the client', type: 'toggle' },
1411
+ { key: 'sharedMemory', label: 'Shared Memory', desc: 'Share memory with Claude Code (~/.claude) instead of isolated storage', type: 'toggle' },
1412
+ { key: 'maxBudgetUsd', label: 'Max Budget (USD)', desc: 'Per-request cost cap — query aborts if exceeded (0 = disabled)', type: 'number' },
1413
+ { key: 'fallbackModel', label: 'Fallback Model', desc: 'Auto-fallback model if primary fails', type: 'select', options: ['', 'sonnet', 'opus', 'haiku', 'sonnet[1m]', 'opus[1m]'] },
1414
+ { key: 'sdkDebug', label: 'SDK Debug Logging', desc: 'Enable verbose SDK debug output to proxy stderr', type: 'toggle' },
1415
+ { key: 'additionalDirectories', label: 'Additional Directories', desc: 'Comma-separated extra paths Claude can access (monorepo libs, etc.)', type: 'text' },
1416
+ ];
1417
+
1418
+ const ADAPTER_LABELS = {
1419
+ opencode: 'OpenCode',
1420
+ crush: 'Crush',
1421
+ forgecode: 'ForgeCode',
1422
+ pi: 'Pi',
1423
+ droid: 'Droid',
1424
+ passthrough: 'LiteLLM / Passthrough',
1425
+ };
1426
+
1427
+ let currentConfig = {};
1428
+
1429
+ async function loadConfig() {
1430
+ const res = await fetch('/settings/api/features');
1431
+ currentConfig = await res.json();
1432
+ render();
1433
+ }
1434
+
1435
+ async function saveFeature(adapter, key, value) {
1436
+ const patch = {};
1437
+ patch[key] = value;
1438
+ await fetch('/settings/api/features/' + adapter, {
1439
+ method: 'PATCH',
1440
+ headers: { 'Content-Type': 'application/json' },
1441
+ body: JSON.stringify(patch),
1442
+ });
1443
+ currentConfig[adapter][key] = value;
1444
+ showSaved();
1445
+ }
1446
+
1447
+ async function resetAdapter(adapter) {
1448
+ await fetch('/settings/api/features/' + adapter, { method: 'DELETE' });
1449
+ await loadConfig();
1450
+ showSaved();
1451
+ }
1452
+
1453
+ function showSaved() {
1454
+ const el = document.getElementById('saveIndicator');
1455
+ el.classList.add('visible');
1456
+ setTimeout(() => el.classList.remove('visible'), 1500);
1457
+ }
1458
+
1459
+ function hasAnyEnabled(features) {
1460
+ return features.codeSystemPrompt || !features.clientSystemPrompt || features.claudeMd !== 'off' || features.memory || features.dreaming ||
1461
+ features.thinking !== 'disabled' || features.thinkingPassthrough ||
1462
+ features.sharedMemory || features.maxBudgetUsd > 0 ||
1463
+ features.fallbackModel || features.sdkDebug ||
1464
+ features.additionalDirectories;
1465
+ }
1466
+
1467
+ function render() {
1468
+ const container = document.getElementById('adapters');
1469
+ container.innerHTML = '';
1470
+
1471
+ for (const [adapter, label] of Object.entries(ADAPTER_LABELS)) {
1472
+ const features = currentConfig[adapter] || {};
1473
+ const active = hasAnyEnabled(features);
1474
+
1475
+ const card = document.createElement('div');
1476
+ card.className = 'adapter-card';
1477
+ card.innerHTML = '<div class="adapter-header">' +
1478
+ '<span class="adapter-name">' + label + '</span>' +
1479
+ '<div style="display:flex;gap:8px;align-items:center">' +
1480
+ '<span class="adapter-badge ' + (active ? 'badge-active' : 'badge-inactive') + '">' +
1481
+ (active ? 'Active' : 'Default') +
1482
+ '</span>' +
1483
+ '<button class="reset-btn" onclick="resetAdapter(\\''+adapter+'\\')">Reset</button>' +
1484
+ '</div>' +
1485
+ '</div>';
1486
+
1487
+ const grid = document.createElement('div');
1488
+ grid.className = 'feature-grid';
1489
+
1490
+ for (const feat of FEATURES) {
1491
+ const row = document.createElement('div');
1492
+ row.className = 'feature-row';
1493
+
1494
+ const info = '<div class="feature-info"><span class="feature-label">' +
1495
+ feat.label + '</span><span class="feature-desc">' + feat.desc + '</span></div>';
1496
+
1497
+ if (feat.type === 'toggle') {
1498
+ const checked = features[feat.key] ? 'checked' : '';
1499
+ row.innerHTML = info +
1500
+ '<label class="toggle"><input type="checkbox" ' + checked +
1501
+ ' onchange="saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.checked)">' +
1502
+ '<span class="toggle-track"></span></label>';
1503
+ } else if (feat.type === 'select') {
1504
+ const options = feat.options.map(o => {
1505
+ const label = o === '' ? '(None)' : o.charAt(0).toUpperCase()+o.slice(1);
1506
+ return '<option value="'+o+'"'+(features[feat.key]===o?' selected':'')+'>'+label+'</option>';
1507
+ }).join('');
1508
+ row.innerHTML = info +
1509
+ '<select class="feature-select" onchange="saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.value)">' +
1510
+ options + '</select>';
1511
+ } else if (feat.type === 'number') {
1512
+ const value = features[feat.key] ?? 0;
1513
+ row.innerHTML = info +
1514
+ '<input type="number" class="feature-select" style="width:80px;text-align:right" min="0" step="0.01" value="'+value+'"' +
1515
+ ' onchange="saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', parseFloat(this.value)||0)">';
1516
+ } else if (feat.type === 'text') {
1517
+ const value = (features[feat.key] ?? '').toString().replace(/"/g, '&quot;');
1518
+ row.innerHTML = info +
1519
+ '<input type="text" class="feature-select" style="width:180px" value="'+value+'"' +
1520
+ ' onchange="saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.value)">';
1521
+ }
1522
+
1523
+ grid.appendChild(row);
1524
+ }
1525
+
1526
+ card.appendChild(grid);
1527
+ container.appendChild(card);
1528
+ }
1529
+ }
1530
+
1531
+ loadConfig();
1532
+ ${profileBarJs}
1533
+ </script>
1534
+ </body>
1535
+ </html>`;
1536
+ });
1537
+
1151
1538
  // node_modules/hono/dist/compose.js
1152
1539
  var compose = (middleware, onError, onNotFound) => {
1153
1540
  return (context, next) => {
@@ -7532,6 +7919,7 @@ import { resolve, dirname } from "node:path";
7532
7919
  import { fileURLToPath } from "node:url";
7533
7920
 
7534
7921
  // src/telemetry/dashboard.ts
7922
+ init_profileBar();
7535
7923
  var dashboardHtml = `<!DOCTYPE html>
7536
7924
  <html lang="en">
7537
7925
  <head>
@@ -7929,6 +8317,7 @@ function createTelemetryRoutes() {
7929
8317
  return routes;
7930
8318
  }
7931
8319
  // src/telemetry/landing.ts
8320
+ init_profileBar();
7932
8321
  var landingHtml = `<!DOCTYPE html>
7933
8322
  <html lang="en">
7934
8323
  <head>
@@ -8041,7 +8430,7 @@ function render(h,s){
8041
8430
  o+='</div>';
8042
8431
  if(s.byModel&&Object.keys(s.byModel).length>0){o+='<div class="section"><div class="section-title">Models (24h)</div><div class="grid">';for(const[n,d]of Object.entries(s.byModel))o+=card(n,d.count,'avg '+ms(d.avgTotalMs),'');o+='</div></div>'}
8043
8432
  o+='<div class="section"><div class="section-title">Connect an Agent</div><div class="snippet"><div class="snippet-tabs"><div class="snippet-tab active" onclick="showTab(this,&apos;opencode&apos;)">OpenCode</div><div class="snippet-tab" onclick="showTab(this,&apos;crush&apos;)">Crush</div><div class="snippet-tab" onclick="showTab(this,&apos;generic&apos;)">Any Tool</div></div><div id="tab-opencode"><code>ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://'+location.host+' opencode</code></div><div id="tab-crush" style="display:none"><code>'+JSON.stringify({providers:{meridian:{type:"anthropic",base_url:"http://"+location.host,api_key:"x",models:[{id:"claude-sonnet-4-5-20250514",name:"Sonnet 4.5"}]}}},null,2)+'</code></div><div id="tab-generic" style="display:none"><code>export ANTHROPIC_API_KEY=x\\nexport ANTHROPIC_BASE_URL=http://'+location.host+'</code></div></div></div>';
8044
- o+='<div class="links"><a href="/telemetry" class="link">\uD83D\uDCCA Telemetry</a><a href="/profiles" class="link">\uD83D\uDC64 Profiles</a><a href="/health" class="link">\uD83E\uDE7A Health</a><a href="/telemetry/summary" class="link">\uD83D\uDCC8 Stats API</a><a href="https://github.com/rynfar/meridian" class="link">⚙️ GitHub</a></div>';
8433
+ o+='<div class="links"><a href="/telemetry" class="link">\uD83D\uDCCA Telemetry</a><a href="/settings" class="link">\uD83D\uDD27 Settings</a><a href="/profiles" class="link">\uD83D\uDC64 Profiles</a><a href="/health" class="link">\uD83E\uDE7A Health</a><a href="/telemetry/summary" class="link">\uD83D\uDCC8 Stats API</a><a href="https://github.com/rynfar/meridian" class="link">⚙️ GitHub</a></div>';
8045
8434
  o+='<div class="footer">Meridian · Built on the <a href="https://github.com/anthropics/claude-code-sdk-js">Claude Code SDK</a></div>';
8046
8435
  document.getElementById('content').innerHTML=o;
8047
8436
  }
@@ -8232,7 +8621,8 @@ function isExpiredTokenError(errMsg) {
8232
8621
  function isStaleSessionError(error) {
8233
8622
  if (!(error instanceof Error))
8234
8623
  return false;
8235
- return error.message.includes("No message found with message.uuid");
8624
+ const msg = error.message;
8625
+ return msg.includes("No message found with message.uuid") || msg.includes("No conversation found with session ID") || msg.includes("No conversation found to continue") || msg.includes("No conversations found to resume");
8236
8626
  }
8237
8627
  function isRateLimitError(errMsg) {
8238
8628
  const lower = errMsg.toLowerCase();
@@ -8605,6 +8995,42 @@ function getLastUserMessage(messages) {
8605
8995
  return messages.slice(-1);
8606
8996
  }
8607
8997
 
8998
+ // src/proxy/auth.ts
8999
+ import { createHmac, timingSafeEqual } from "node:crypto";
9000
+ function getConfiguredKey() {
9001
+ return process.env.MERIDIAN_API_KEY || undefined;
9002
+ }
9003
+ function safeCompare(a, b) {
9004
+ const hashA = createHmac("sha256", "meridian").update(a).digest();
9005
+ const hashB = createHmac("sha256", "meridian").update(b).digest();
9006
+ return timingSafeEqual(hashA, hashB);
9007
+ }
9008
+ function extractKey(c) {
9009
+ const apiKey = c.req.header("x-api-key");
9010
+ if (apiKey)
9011
+ return apiKey;
9012
+ const auth = c.req.header("authorization");
9013
+ if (auth?.startsWith("Bearer "))
9014
+ return auth.slice(7);
9015
+ return;
9016
+ }
9017
+ async function requireAuth(c, next) {
9018
+ const key = getConfiguredKey();
9019
+ if (!key)
9020
+ return next();
9021
+ const provided = extractKey(c);
9022
+ if (!provided || !safeCompare(provided, key)) {
9023
+ return c.json({
9024
+ type: "error",
9025
+ error: {
9026
+ type: "authentication_error",
9027
+ message: "Invalid or missing API key"
9028
+ }
9029
+ }, 401);
9030
+ }
9031
+ return next();
9032
+ }
9033
+
8608
9034
  // src/proxy/fileChanges.ts
8609
9035
  function extractFileChange(toolName, toolInput, mcpPrefix) {
8610
9036
  if (!toolName.startsWith(mcpPrefix))
@@ -9091,6 +9517,9 @@ var crushAdapter = {
9091
9517
  buildSystemContextAddendum(_body, _sdkAgents) {
9092
9518
  return "";
9093
9519
  },
9520
+ supportsThinking() {
9521
+ return true;
9522
+ },
9094
9523
  extractFileChangesFromToolUse(toolName, toolInput) {
9095
9524
  const input = toolInput;
9096
9525
  const filePath = input?.file_path ?? input?.path;
@@ -9375,6 +9804,10 @@ function detectAdapter(c) {
9375
9804
  return defaultAdapter;
9376
9805
  }
9377
9806
 
9807
+ // src/proxy/query.ts
9808
+ import { join as join3 } from "node:path";
9809
+ import { homedir as homedir2 } from "node:os";
9810
+
9378
9811
  // src/mcpTools.ts
9379
9812
  import { createSdkMcpServer as createSdkMcpServer2, tool } from "@anthropic-ai/claude-agent-sdk";
9380
9813
  import * as fs from "node:fs/promises";
@@ -15067,6 +15500,18 @@ function createOpencodeMcpServer() {
15067
15500
  }
15068
15501
 
15069
15502
  // src/proxy/query.ts
15503
+ function resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt) {
15504
+ const hasSettings = settingSources != null && settingSources.length > 0;
15505
+ const usePreset = codeSystemPrompt ?? (hasSettings || !passthrough && !!systemContext);
15506
+ const includeClient = clientSystemPrompt ?? true;
15507
+ const clientContext = includeClient ? systemContext : undefined;
15508
+ if (usePreset) {
15509
+ return clientContext ? { systemPrompt: { type: "preset", preset: "claude_code", append: clientContext } } : { systemPrompt: { type: "preset", preset: "claude_code" } };
15510
+ }
15511
+ if (clientContext)
15512
+ return { systemPrompt: clientContext };
15513
+ return {};
15514
+ }
15070
15515
  function buildQueryOptions(ctx) {
15071
15516
  const {
15072
15517
  prompt,
@@ -15089,7 +15534,17 @@ function buildQueryOptions(ctx) {
15089
15534
  effort,
15090
15535
  thinking,
15091
15536
  taskBudget,
15092
- betas
15537
+ betas,
15538
+ settingSources,
15539
+ codeSystemPrompt,
15540
+ clientSystemPrompt,
15541
+ memory,
15542
+ dreaming,
15543
+ sharedMemory,
15544
+ maxBudgetUsd,
15545
+ fallbackModel,
15546
+ sdkDebug,
15547
+ additionalDirectories
15093
15548
  } = ctx;
15094
15549
  const blockedTools = [...adapter.getBlockedBuiltinTools(), ...adapter.getAgentIncompatibleTools()];
15095
15550
  const mcpServerName = adapter.getMcpServerName();
@@ -15105,9 +15560,7 @@ function buildQueryOptions(ctx) {
15105
15560
  ...stream2 ? { includePartialMessages: true } : {},
15106
15561
  permissionMode: "bypassPermissions",
15107
15562
  allowDangerouslySkipPermissions: true,
15108
- ...systemContext ? {
15109
- systemPrompt: passthrough ? systemContext : { type: "preset", preset: "claude_code", append: systemContext }
15110
- } : {},
15563
+ ...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt),
15111
15564
  ...passthrough ? {
15112
15565
  disallowedTools: blockedTools,
15113
15566
  ...passthroughMcp ? {
@@ -15120,11 +15573,19 @@ function buildQueryOptions(ctx) {
15120
15573
  mcpServers: { [mcpServerName]: createOpencodeMcpServer() }
15121
15574
  },
15122
15575
  plugins: [],
15576
+ ...settingSources && settingSources.length > 0 ? {
15577
+ settingSources,
15578
+ settings: {
15579
+ autoMemoryEnabled: ctx.memory ?? true,
15580
+ autoDreamEnabled: ctx.dreaming ?? false
15581
+ }
15582
+ } : {},
15123
15583
  ...onStderr ? { stderr: onStderr } : {},
15124
15584
  env: {
15125
15585
  ...cleanEnv,
15126
15586
  ENABLE_TOOL_SEARCH: hasDeferredTools ? "true" : "false",
15127
15587
  ...passthrough ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {},
15588
+ ...sharedMemory ? { CLAUDE_CONFIG_DIR: join3(homedir2(), ".claude") } : {},
15128
15589
  ...process.getuid?.() === 0 ? { IS_SANDBOX: "1" } : {}
15129
15590
  },
15130
15591
  ...Object.keys(sdkAgents).length > 0 ? { agents: sdkAgents } : {},
@@ -15134,7 +15595,11 @@ function buildQueryOptions(ctx) {
15134
15595
  ...effort ? { effort } : {},
15135
15596
  ...thinking ? { thinking } : {},
15136
15597
  ...taskBudget ? { taskBudget } : {},
15137
- ...betas && betas.length > 0 ? { betas } : {}
15598
+ ...betas && betas.length > 0 ? { betas } : {},
15599
+ ...maxBudgetUsd && maxBudgetUsd > 0 ? { maxBudgetUsd } : {},
15600
+ ...fallbackModel ? { fallbackModel } : {},
15601
+ ...sdkDebug ? { debug: true } : {},
15602
+ ...additionalDirectories && additionalDirectories.length > 0 ? { additionalDirectories } : {}
15138
15603
  }
15139
15604
  };
15140
15605
  }
@@ -15505,8 +15970,8 @@ import {
15505
15970
  unlinkSync,
15506
15971
  writeFileSync
15507
15972
  } from "node:fs";
15508
- import { homedir as homedir2 } from "node:os";
15509
- import { join as join3 } from "node:path";
15973
+ import { homedir as homedir3 } from "node:os";
15974
+ import { join as join4 } from "node:path";
15510
15975
  var DEFAULT_MAX_STORED_SESSIONS = 1e4;
15511
15976
  var STALE_LOCK_THRESHOLD_MS = 30000;
15512
15977
  function getMaxStoredSessions() {
@@ -15557,11 +16022,11 @@ function getStorePath() {
15557
16022
  if (!existsSync3(dir)) {
15558
16023
  mkdirSync(dir, { recursive: true });
15559
16024
  }
15560
- return join3(dir, "sessions.json");
16025
+ return join4(dir, "sessions.json");
15561
16026
  }
15562
16027
  function getDefaultCacheDir() {
15563
- const newDir = join3(homedir2(), ".cache", "meridian");
15564
- const oldDir = join3(homedir2(), ".cache", "opencode-claude-max-proxy");
16028
+ const newDir = join4(homedir3(), ".cache", "meridian");
16029
+ const oldDir = join4(homedir3(), ".cache", "opencode-claude-max-proxy");
15565
16030
  if (existsSync3(newDir))
15566
16031
  return newDir;
15567
16032
  if (existsSync3(oldDir)) {
@@ -16026,8 +16491,17 @@ function createProxyServer(config = {}) {
16026
16491
  const serverVersion = finalConfig.version ?? "unknown";
16027
16492
  restoreActiveProfile(finalConfig.profiles);
16028
16493
  const sessionDiscoveredTools = new Map;
16494
+ const sessionToolCache = new Map;
16029
16495
  const app = new Hono2;
16030
16496
  app.use("*", cors());
16497
+ app.use("/v1/*", requireAuth);
16498
+ app.use("/messages", requireAuth);
16499
+ app.use("/telemetry/*", requireAuth);
16500
+ app.use("/telemetry", requireAuth);
16501
+ app.use("/metrics", requireAuth);
16502
+ app.use("/profiles/*", requireAuth);
16503
+ app.use("/profiles", requireAuth);
16504
+ app.use("/auth/*", requireAuth);
16031
16505
  app.get("/", (c) => {
16032
16506
  const accept = c.req.header("accept") || "";
16033
16507
  if (accept.includes("application/json") && !accept.includes("text/html")) {
@@ -16130,6 +16604,14 @@ function createProxyServer(config = {}) {
16130
16604
  console.error(`[PROXY] ${requestMeta.requestId} ignoring malformed x-opencode-thinking header: ${e instanceof Error ? e.message : String(e)}`);
16131
16605
  }
16132
16606
  }
16607
+ const { getFeaturesForAdapter: getFeaturesForAdapter2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
16608
+ const sdkFeatures = getFeaturesForAdapter2(adapter.name);
16609
+ if (!thinking) {
16610
+ if (sdkFeatures.thinking === "adaptive")
16611
+ thinking = { type: "adaptive" };
16612
+ else if (sdkFeatures.thinking === "enabled")
16613
+ thinking = { type: "enabled" };
16614
+ }
16133
16615
  const thinkingBetaStripped = betaFilter.stripped.some((b) => b.startsWith("interleaved-thinking"));
16134
16616
  if (thinkingBetaStripped) {
16135
16617
  thinking = { type: "disabled" };
@@ -16282,16 +16764,27 @@ function createProxyServer(config = {}) {
16282
16764
  }
16283
16765
  const adapterPassthrough = adapter.usesPassthrough?.();
16284
16766
  const passthrough = adapterPassthrough !== undefined ? adapterPassthrough : envBool("PASSTHROUGH");
16767
+ const settingSources = envBool("LOAD_CONTEXT") || sdkFeatures.claudeMd === "full" ? ["user", "project"] : sdkFeatures.claudeMd === "project" ? ["project"] : adapter.getSettingSources?.() ?? [];
16285
16768
  const capturedToolUses = [];
16286
16769
  const fileChanges = [];
16287
16770
  let passthroughMcp;
16288
- if (passthrough && Array.isArray(body.tools) && body.tools.length > 0) {
16289
- passthroughMcp = createPassthroughMcpServer(body.tools, adapter.getCoreToolNames?.());
16771
+ let requestTools = Array.isArray(body.tools) ? body.tools : [];
16772
+ if (passthrough && requestTools.length === 0 && profileSessionId) {
16773
+ const cached = sessionToolCache.get(profileSessionId);
16774
+ if (cached && cached.length > 0) {
16775
+ requestTools = cached;
16776
+ console.error(`[PROXY] ${requestMeta.requestId} tools_restored: client sent 0 tools but session had ${cached.length} — reusing cached tools to preserve prompt cache`);
16777
+ }
16778
+ }
16779
+ if (passthrough && requestTools.length > 0) {
16780
+ passthroughMcp = createPassthroughMcpServer(requestTools, adapter.getCoreToolNames?.());
16781
+ if (profileSessionId)
16782
+ sessionToolCache.set(profileSessionId, requestTools);
16290
16783
  }
16291
16784
  const hasDeferredTools = passthroughMcp?.hasDeferredTools ?? false;
16292
16785
  const coreNames = adapter.getCoreToolNames?.();
16293
16786
  const coreSet = coreNames ? new Set(coreNames.map((n) => n.toLowerCase())) : undefined;
16294
- const deferredToolCount = hasDeferredTools && Array.isArray(body.tools) ? body.tools.filter((t) => t.defer_loading === true || coreSet && !coreSet.has(String(t.name).toLowerCase())).length : 0;
16787
+ const deferredToolCount = hasDeferredTools && requestTools.length > 0 ? requestTools.filter((t) => t.defer_loading === true || coreSet && !coreSet.has(String(t.name).toLowerCase())).length : 0;
16295
16788
  if (hasDeferredTools) {
16296
16789
  console.error(`[PROXY] ${requestMeta.requestId} deferred=${deferredToolCount}/${toolCount} tools (core: ${coreNames?.join(",") ?? "none"})`);
16297
16790
  }
@@ -16373,7 +16866,17 @@ function createProxyServer(config = {}) {
16373
16866
  effort,
16374
16867
  thinking,
16375
16868
  taskBudget,
16376
- betas
16869
+ betas,
16870
+ settingSources,
16871
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
16872
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
16873
+ memory: sdkFeatures.memory,
16874
+ dreaming: sdkFeatures.dreaming,
16875
+ sharedMemory: sdkFeatures.sharedMemory,
16876
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
16877
+ fallbackModel: sdkFeatures.fallbackModel,
16878
+ sdkDebug: sdkFeatures.sdkDebug,
16879
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
16377
16880
  }))) {
16378
16881
  if (event.type === "assistant" && !event.error) {
16379
16882
  didYieldContent = true;
@@ -16417,7 +16920,17 @@ function createProxyServer(config = {}) {
16417
16920
  effort,
16418
16921
  thinking,
16419
16922
  taskBudget,
16420
- betas
16923
+ betas,
16924
+ settingSources,
16925
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
16926
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
16927
+ memory: sdkFeatures.memory,
16928
+ dreaming: sdkFeatures.dreaming,
16929
+ sharedMemory: sdkFeatures.sharedMemory,
16930
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
16931
+ fallbackModel: sdkFeatures.fallbackModel,
16932
+ sdkDebug: sdkFeatures.sdkDebug,
16933
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
16421
16934
  }));
16422
16935
  return;
16423
16936
  }
@@ -16502,7 +17015,7 @@ function createProxyServer(config = {}) {
16502
17015
  claudeLog("passthrough.toolsearch_filtered", { mode: "non_stream" });
16503
17016
  continue;
16504
17017
  }
16505
- if (passthrough && !adapter.supportsThinking?.() && (b.type === "thinking" || b.type === "redacted_thinking")) {
17018
+ if (passthrough && !adapter.supportsThinking?.() && !sdkFeatures.thinkingPassthrough && (b.type === "thinking" || b.type === "redacted_thinking")) {
16506
17019
  claudeLog("passthrough.thinking_stripped", { mode: "non_stream", type: b.type });
16507
17020
  continue;
16508
17021
  }
@@ -16641,7 +17154,12 @@ Subprocess stderr: ${stderrOutput}`;
16641
17154
  content: contentBlocks,
16642
17155
  model: body.model,
16643
17156
  stop_reason: stopReason,
16644
- usage: { input_tokens: 0, output_tokens: 0 }
17157
+ usage: {
17158
+ input_tokens: lastUsage?.input_tokens ?? 0,
17159
+ output_tokens: lastUsage?.output_tokens ?? 0,
17160
+ cache_read_input_tokens: lastUsage?.cache_read_input_tokens,
17161
+ cache_creation_input_tokens: lastUsage?.cache_creation_input_tokens
17162
+ }
16645
17163
  }), {
16646
17164
  headers: {
16647
17165
  "Content-Type": "application/json",
@@ -16717,7 +17235,17 @@ Subprocess stderr: ${stderrOutput}`;
16717
17235
  effort,
16718
17236
  thinking,
16719
17237
  taskBudget,
16720
- betas
17238
+ betas,
17239
+ settingSources,
17240
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
17241
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
17242
+ memory: sdkFeatures.memory,
17243
+ dreaming: sdkFeatures.dreaming,
17244
+ sharedMemory: sdkFeatures.sharedMemory,
17245
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17246
+ fallbackModel: sdkFeatures.fallbackModel,
17247
+ sdkDebug: sdkFeatures.sdkDebug,
17248
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
16721
17249
  }))) {
16722
17250
  if (event.type === "stream_event") {
16723
17251
  didYieldClientEvent = true;
@@ -16761,7 +17289,17 @@ Subprocess stderr: ${stderrOutput}`;
16761
17289
  effort,
16762
17290
  thinking,
16763
17291
  taskBudget,
16764
- betas
17292
+ betas,
17293
+ settingSources,
17294
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
17295
+ clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
17296
+ memory: sdkFeatures.memory,
17297
+ dreaming: sdkFeatures.dreaming,
17298
+ sharedMemory: sdkFeatures.sharedMemory,
17299
+ maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17300
+ fallbackModel: sdkFeatures.fallbackModel,
17301
+ sdkDebug: sdkFeatures.sdkDebug,
17302
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
16765
17303
  }));
16766
17304
  return;
16767
17305
  }
@@ -16898,7 +17436,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
16898
17436
  }
16899
17437
  if (eventType === "content_block_start") {
16900
17438
  const block = event.content_block;
16901
- if (passthrough && !adapter.supportsThinking?.() && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
17439
+ if (passthrough && !adapter.supportsThinking?.() && !sdkFeatures.thinkingPassthrough && (block?.type === "thinking" || block?.type === "redacted_thinking")) {
16902
17440
  if (eventIndex !== undefined)
16903
17441
  skipBlockIndices.add(eventIndex);
16904
17442
  claudeLog("passthrough.thinking_stripped", { mode: "stream", type: block.type, index: eventIndex });
@@ -17259,6 +17797,33 @@ data: ${JSON.stringify({
17259
17797
  app.post("/v1/messages", (c) => handleWithQueue(c, "/v1/messages"));
17260
17798
  app.post("/messages", (c) => handleWithQueue(c, "/messages"));
17261
17799
  app.route("/telemetry", createTelemetryRoutes());
17800
+ app.get("/settings", (c) => {
17801
+ const { settingsPageHtml: settingsPageHtml2 } = (init_settingsPage(), __toCommonJS(exports_settingsPage));
17802
+ return c.html(settingsPageHtml2);
17803
+ });
17804
+ app.get("/settings/api/features", (c) => {
17805
+ const { getAllFeatureConfigs: getAllFeatureConfigs2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
17806
+ return c.json(getAllFeatureConfigs2());
17807
+ });
17808
+ app.patch("/settings/api/features/:adapter", async (c) => {
17809
+ const { validateFeatureUpdate: validateFeatureUpdate2, updateAdapterFeatures: updateAdapterFeatures2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
17810
+ const adapter = c.req.param("adapter");
17811
+ const body = await c.req.json();
17812
+ let validated;
17813
+ try {
17814
+ validated = validateFeatureUpdate2(body);
17815
+ } catch (e) {
17816
+ return c.json({ error: e.message }, 400);
17817
+ }
17818
+ updateAdapterFeatures2(adapter, validated);
17819
+ return c.json({ ok: true });
17820
+ });
17821
+ app.delete("/settings/api/features/:adapter", (c) => {
17822
+ const { resetAdapterFeatures: resetAdapterFeatures2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
17823
+ const adapter = c.req.param("adapter");
17824
+ resetAdapterFeatures2(adapter);
17825
+ return c.json({ ok: true });
17826
+ });
17262
17827
  app.get("/metrics", (c) => {
17263
17828
  const body = renderPrometheusMetrics(telemetryStore2);
17264
17829
  return c.body(body, 200, {
@@ -17328,7 +17893,7 @@ data: ${JSON.stringify({
17328
17893
  });
17329
17894
  });
17330
17895
  app.get("/profiles", async (c) => {
17331
- const { profilePageHtml } = await import("./profilePage-e90fq8ye.js");
17896
+ const { profilePageHtml } = await import("./profilePage-g5t5t6av.js");
17332
17897
  return c.html(profilePageHtml);
17333
17898
  });
17334
17899
  app.post("/profiles/active", async (c) => {