@rynfar/meridian 1.38.0 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7888,6 +7888,184 @@ function stripMcpPrefix(toolName) {
7888
7888
  }
7889
7889
  return toolName;
7890
7890
  }
7891
+ function toCamelCase(s) {
7892
+ return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
7893
+ }
7894
+ function toSnakeCase(s) {
7895
+ return s.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
7896
+ }
7897
+ function normalizeToolInput(input, clientSchema) {
7898
+ if (!input || !clientSchema?.properties)
7899
+ return input;
7900
+ const schemaKeys = new Set(Object.keys(clientSchema.properties));
7901
+ const required = new Set(clientSchema.required ?? []);
7902
+ const missingRequired = [...required].filter((k) => input[k] === undefined);
7903
+ if (missingRequired.length === 0)
7904
+ return input;
7905
+ const normalized = { ...input };
7906
+ for (const key of Object.keys(normalized)) {
7907
+ if (schemaKeys.has(key))
7908
+ continue;
7909
+ const camel = toCamelCase(key);
7910
+ if (camel !== key && schemaKeys.has(camel) && normalized[camel] === undefined) {
7911
+ normalized[camel] = normalized[key];
7912
+ delete normalized[key];
7913
+ continue;
7914
+ }
7915
+ const snake = toSnakeCase(key);
7916
+ if (snake !== key && schemaKeys.has(snake) && normalized[snake] === undefined) {
7917
+ normalized[snake] = normalized[key];
7918
+ delete normalized[key];
7919
+ }
7920
+ }
7921
+ return normalized;
7922
+ }
7923
+
7924
+ // src/proxy/agentDefs.ts
7925
+ var FALLBACK_AGENT_NAME = "general";
7926
+ var DEFAULT_AGENT_TYPES = {
7927
+ build: "The default agent. Executes tools based on configured permissions.",
7928
+ plan: "Plan mode. Disallows all edit tools.",
7929
+ explore: "Contextual grep for codebases. Answers 'Where is X?', 'Which file has Y?'.",
7930
+ general: "General-purpose agent for researching complex questions and executing multi-step tasks."
7931
+ };
7932
+ function parseAgentDescriptions(taskDescription) {
7933
+ const agents = new Map;
7934
+ const agentSection = taskDescription.match(/Available agent types.*?:\n((?:- [\w][\w-]*:.*\n?)+)/s);
7935
+ if (!agentSection)
7936
+ return agents;
7937
+ const entries = agentSection[1].matchAll(/^- ([\w][\w-]*):\s*(.+)/gm);
7938
+ for (const match2 of entries) {
7939
+ agents.set(match2[1], match2[2].trim());
7940
+ }
7941
+ return agents;
7942
+ }
7943
+ function buildAgentDefinitions(taskDescription, mcpToolNames) {
7944
+ const descriptions = parseAgentDescriptions(taskDescription);
7945
+ const agents = {};
7946
+ for (const [name, description] of descriptions) {
7947
+ agents[name] = {
7948
+ description,
7949
+ prompt: buildAgentPrompt(name, description),
7950
+ model: "inherit",
7951
+ ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
7952
+ };
7953
+ }
7954
+ if (descriptions.size > 0) {
7955
+ ensureDefaultAgents(agents, mcpToolNames);
7956
+ addCaseVariants(agents);
7957
+ }
7958
+ return agents;
7959
+ }
7960
+ function ensureDefaultAgents(agents, mcpToolNames) {
7961
+ for (const [name, description] of Object.entries(DEFAULT_AGENT_TYPES)) {
7962
+ if (!agents[name]) {
7963
+ agents[name] = {
7964
+ description,
7965
+ prompt: buildAgentPrompt(name, description),
7966
+ model: "inherit",
7967
+ ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
7968
+ };
7969
+ }
7970
+ }
7971
+ }
7972
+ function addCaseVariants(agents) {
7973
+ const baseNames = Object.keys(agents);
7974
+ for (const name of baseNames) {
7975
+ const def = agents[name];
7976
+ const titleCase = name.replace(/(^|-)(\w)/g, (_m, sep, ch) => sep + ch.toUpperCase());
7977
+ if (titleCase !== name && !agents[titleCase]) {
7978
+ agents[titleCase] = def;
7979
+ }
7980
+ }
7981
+ const ALIASES = {
7982
+ "general-purpose": "general",
7983
+ "General-Purpose": "general"
7984
+ };
7985
+ for (const [alias, target] of Object.entries(ALIASES)) {
7986
+ if (!agents[alias] && agents[target]) {
7987
+ agents[alias] = agents[target];
7988
+ }
7989
+ }
7990
+ }
7991
+ function buildAgentPrompt(name, description) {
7992
+ return `You are the "${name}" agent. ${description}
7993
+
7994
+ Focus on your specific role and complete the task thoroughly. Return a clear, concise result.`;
7995
+ }
7996
+
7997
+ // src/proxy/agentMatch.ts
7998
+ var KNOWN_ALIASES = {
7999
+ "general-purpose": "general",
8000
+ default: "general",
8001
+ "code-reviewer": "oracle",
8002
+ reviewer: "oracle",
8003
+ "code-review": "oracle",
8004
+ review: "oracle",
8005
+ consultation: "oracle",
8006
+ analyzer: "oracle",
8007
+ debugger: "oracle",
8008
+ search: "explore",
8009
+ grep: "explore",
8010
+ find: "explore",
8011
+ "codebase-search": "explore",
8012
+ research: "librarian",
8013
+ docs: "librarian",
8014
+ documentation: "librarian",
8015
+ lookup: "librarian",
8016
+ reference: "librarian",
8017
+ consult: "oracle",
8018
+ architect: "oracle",
8019
+ "image-analyzer": "multimodal-looker",
8020
+ image: "multimodal-looker",
8021
+ pdf: "multimodal-looker",
8022
+ visual: "multimodal-looker",
8023
+ planner: "plan",
8024
+ planning: "plan",
8025
+ builder: "build",
8026
+ coder: "build",
8027
+ developer: "build",
8028
+ writer: "build",
8029
+ executor: "build"
8030
+ };
8031
+ var STRIP_SUFFIXES = ["-agent", "-tool", "-worker", "-task", " agent", " tool"];
8032
+ function resolveAgentAlias(input) {
8033
+ const lowered = input.toLowerCase();
8034
+ return KNOWN_ALIASES[lowered] ?? lowered;
8035
+ }
8036
+ function fuzzyMatchAgentName(input, validAgents) {
8037
+ if (!input)
8038
+ return input;
8039
+ if (validAgents.length === 0)
8040
+ return input.toLowerCase();
8041
+ const lowered = input.toLowerCase();
8042
+ const exact = validAgents.find((a) => a.toLowerCase() === lowered);
8043
+ if (exact)
8044
+ return exact;
8045
+ const alias = KNOWN_ALIASES[lowered];
8046
+ if (alias && validAgents.includes(alias))
8047
+ return alias;
8048
+ const prefixMatch = validAgents.find((a) => a.toLowerCase().startsWith(lowered));
8049
+ if (prefixMatch)
8050
+ return prefixMatch;
8051
+ const substringMatch = validAgents.find((a) => a.toLowerCase().includes(lowered));
8052
+ if (substringMatch)
8053
+ return substringMatch;
8054
+ for (const suffix of STRIP_SUFFIXES) {
8055
+ if (lowered.endsWith(suffix)) {
8056
+ const stripped = lowered.slice(0, -suffix.length);
8057
+ const strippedMatch = validAgents.find((a) => a.toLowerCase() === stripped);
8058
+ if (strippedMatch)
8059
+ return strippedMatch;
8060
+ }
8061
+ }
8062
+ const reverseMatch = validAgents.find((a) => lowered.includes(a.toLowerCase()));
8063
+ if (reverseMatch)
8064
+ return reverseMatch;
8065
+ if (validAgents.includes(FALLBACK_AGENT_NAME))
8066
+ return FALLBACK_AGENT_NAME;
8067
+ return lowered;
8068
+ }
7891
8069
 
7892
8070
  // src/utils/lruMap.ts
7893
8071
  class LRUMap {
@@ -8797,6 +8975,16 @@ import { fileURLToPath as fileURLToPath2 } from "url";
8797
8975
  import { join as join2, dirname as dirname2 } from "path";
8798
8976
  import { promisify } from "util";
8799
8977
  var exec = promisify(execCallback);
8978
+ var CANONICAL_OPUS_MODEL = "claude-opus-4-7";
8979
+ var CANONICAL_SONNET_MODEL = "claude-sonnet-4-6";
8980
+ var CANONICAL_HAIKU_MODEL = "claude-haiku-4-5";
8981
+ function resolveSdkModelDefaults() {
8982
+ return {
8983
+ ANTHROPIC_DEFAULT_OPUS_MODEL: process.env.MERIDIAN_DEFAULT_OPUS_MODEL ?? CANONICAL_OPUS_MODEL,
8984
+ ANTHROPIC_DEFAULT_SONNET_MODEL: process.env.MERIDIAN_DEFAULT_SONNET_MODEL ?? CANONICAL_SONNET_MODEL,
8985
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: process.env.MERIDIAN_DEFAULT_HAIKU_MODEL ?? CANONICAL_HAIKU_MODEL
8986
+ };
8987
+ }
8800
8988
  var AUTH_STATUS_CACHE_TTL_MS = 60000;
8801
8989
  var AUTH_STATUS_FAILURE_TTL_MS = 5000;
8802
8990
  var cachedAuthStatus = null;
@@ -8933,16 +9121,14 @@ async function resolveClaudeExecutableAsync() {
8933
9121
  return cachedClaudePathPromise;
8934
9122
  cachedClaudePathPromise = (async () => {
8935
9123
  const runningUnderBun = typeof process.versions.bun !== "undefined";
8936
- if (runningUnderBun) {
8937
- try {
8938
- const sdkPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
8939
- const sdkCliJs = join2(dirname2(sdkPath), "cli.js");
8940
- if (existsSync2(sdkCliJs)) {
8941
- cachedClaudePath = sdkCliJs;
8942
- return sdkCliJs;
8943
- }
8944
- } catch {}
8945
- }
9124
+ try {
9125
+ const pkgPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-code/package.json"));
9126
+ const bundledBinary = join2(dirname2(pkgPath), "bin", "claude.exe");
9127
+ if (existsSync2(bundledBinary)) {
9128
+ cachedClaudePath = bundledBinary;
9129
+ return bundledBinary;
9130
+ }
9131
+ } catch {}
8946
9132
  try {
8947
9133
  const { stdout } = await exec("which claude");
8948
9134
  const claudePath = stdout.trim();
@@ -8951,7 +9137,7 @@ async function resolveClaudeExecutableAsync() {
8951
9137
  return claudePath;
8952
9138
  }
8953
9139
  } catch {}
8954
- if (!runningUnderBun) {
9140
+ if (runningUnderBun) {
8955
9141
  try {
8956
9142
  const sdkPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
8957
9143
  const sdkCliJs = join2(dirname2(sdkPath), "cli.js");
@@ -9164,7 +9350,15 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
9164
9350
  context_window: isMaxSubscription ? 1e6 : 200000
9165
9351
  },
9166
9352
  {
9167
- id: "claude-haiku-4-5-20251001",
9353
+ id: "claude-opus-4-7",
9354
+ object: "model",
9355
+ created: now,
9356
+ owned_by: "anthropic",
9357
+ display_name: "Claude Opus 4.7",
9358
+ context_window: isMaxSubscription ? 1e6 : 200000
9359
+ },
9360
+ {
9361
+ id: "claude-haiku-4-5",
9168
9362
  object: "model",
9169
9363
  created: now,
9170
9364
  owned_by: "anthropic",
@@ -9204,6 +9398,27 @@ function normalizeContent(content) {
9204
9398
  }
9205
9399
  return String(content);
9206
9400
  }
9401
+ function extractAdvisorModel(tools) {
9402
+ if (!Array.isArray(tools))
9403
+ return;
9404
+ for (const tool of tools) {
9405
+ if (!tool || typeof tool !== "object")
9406
+ continue;
9407
+ const candidate = tool;
9408
+ if (typeof candidate.type === "string" && candidate.type.startsWith("advisor_") && typeof candidate.model === "string" && candidate.model.length > 0) {
9409
+ return candidate.model;
9410
+ }
9411
+ }
9412
+ return;
9413
+ }
9414
+ function stripAdvisorTools(tools) {
9415
+ return tools.filter((tool) => {
9416
+ if (!tool || typeof tool !== "object")
9417
+ return true;
9418
+ const candidate = tool;
9419
+ return !(typeof candidate.type === "string" && candidate.type.startsWith("advisor_"));
9420
+ });
9421
+ }
9207
9422
  function getLastUserMessage(messages) {
9208
9423
  for (let i = messages.length - 1;i >= 0; i--) {
9209
9424
  if (messages[i]?.role === "user")
@@ -9440,104 +9655,6 @@ var ALLOWED_MCP_TOOLS = [
9440
9655
  `mcp__${MCP_SERVER_NAME}__grep`
9441
9656
  ];
9442
9657
 
9443
- // src/proxy/agentDefs.ts
9444
- function parseAgentDescriptions(taskDescription) {
9445
- const agents = new Map;
9446
- const agentSection = taskDescription.match(/Available agent types.*?:\n((?:- [\w][\w-]*:.*\n?)+)/s);
9447
- if (!agentSection)
9448
- return agents;
9449
- const entries = agentSection[1].matchAll(/^- ([\w][\w-]*):\s*(.+)/gm);
9450
- for (const match2 of entries) {
9451
- agents.set(match2[1], match2[2].trim());
9452
- }
9453
- return agents;
9454
- }
9455
- function buildAgentDefinitions(taskDescription, mcpToolNames) {
9456
- const descriptions = parseAgentDescriptions(taskDescription);
9457
- const agents = {};
9458
- for (const [name, description] of descriptions) {
9459
- agents[name] = {
9460
- description,
9461
- prompt: buildAgentPrompt(name, description),
9462
- model: "inherit",
9463
- ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
9464
- };
9465
- }
9466
- return agents;
9467
- }
9468
- function buildAgentPrompt(name, description) {
9469
- return `You are the "${name}" agent. ${description}
9470
-
9471
- Focus on your specific role and complete the task thoroughly. Return a clear, concise result.`;
9472
- }
9473
-
9474
- // src/proxy/agentMatch.ts
9475
- var KNOWN_ALIASES = {
9476
- "general-purpose": "general",
9477
- default: "general",
9478
- "code-reviewer": "oracle",
9479
- reviewer: "oracle",
9480
- "code-review": "oracle",
9481
- review: "oracle",
9482
- consultation: "oracle",
9483
- analyzer: "oracle",
9484
- debugger: "oracle",
9485
- search: "explore",
9486
- grep: "explore",
9487
- find: "explore",
9488
- "codebase-search": "explore",
9489
- research: "librarian",
9490
- docs: "librarian",
9491
- documentation: "librarian",
9492
- lookup: "librarian",
9493
- reference: "librarian",
9494
- consult: "oracle",
9495
- architect: "oracle",
9496
- "image-analyzer": "multimodal-looker",
9497
- image: "multimodal-looker",
9498
- pdf: "multimodal-looker",
9499
- visual: "multimodal-looker",
9500
- planner: "plan",
9501
- planning: "plan",
9502
- builder: "build",
9503
- coder: "build",
9504
- developer: "build",
9505
- writer: "build",
9506
- executor: "build"
9507
- };
9508
- var STRIP_SUFFIXES = ["-agent", "-tool", "-worker", "-task", " agent", " tool"];
9509
- function fuzzyMatchAgentName(input, validAgents) {
9510
- if (!input)
9511
- return input;
9512
- if (validAgents.length === 0)
9513
- return input.toLowerCase();
9514
- const lowered = input.toLowerCase();
9515
- const exact = validAgents.find((a) => a.toLowerCase() === lowered);
9516
- if (exact)
9517
- return exact;
9518
- const alias = KNOWN_ALIASES[lowered];
9519
- if (alias && validAgents.includes(alias))
9520
- return alias;
9521
- const prefixMatch = validAgents.find((a) => a.toLowerCase().startsWith(lowered));
9522
- if (prefixMatch)
9523
- return prefixMatch;
9524
- const substringMatch = validAgents.find((a) => a.toLowerCase().includes(lowered));
9525
- if (substringMatch)
9526
- return substringMatch;
9527
- for (const suffix of STRIP_SUFFIXES) {
9528
- if (lowered.endsWith(suffix)) {
9529
- const stripped = lowered.slice(0, -suffix.length);
9530
- const strippedMatch = validAgents.find((a) => a.toLowerCase() === stripped);
9531
- if (strippedMatch)
9532
- return strippedMatch;
9533
- }
9534
- }
9535
- const reverseMatch = validAgents.find((a) => lowered.includes(a.toLowerCase()));
9536
- if (reverseMatch)
9537
- return reverseMatch;
9538
- return lowered;
9539
- }
9540
-
9541
9658
  // src/proxy/transforms/opencode.ts
9542
9659
  var openCodeTransforms = [
9543
9660
  {
@@ -10082,6 +10199,9 @@ var piAdapter = {
10082
10199
  extractWorkingDirectory(body) {
10083
10200
  return extractPiCwd(body);
10084
10201
  },
10202
+ extractClientWorkingDirectory(body) {
10203
+ return extractPiCwd(body);
10204
+ },
10085
10205
  normalizeContent(content) {
10086
10206
  return normalizeContent(content);
10087
10207
  },
@@ -10234,6 +10354,79 @@ var forgeCodeAdapter = {
10234
10354
  }
10235
10355
  };
10236
10356
 
10357
+ // src/proxy/adapters/claudecode.ts
10358
+ function extractClaudeCodeClientCwd(body) {
10359
+ let systemText = "";
10360
+ if (typeof body.system === "string") {
10361
+ systemText = body.system;
10362
+ } else if (Array.isArray(body.system)) {
10363
+ systemText = body.system.filter((b) => b.type === "text" && b.text).map((b) => b.text).join(`
10364
+ `);
10365
+ }
10366
+ if (!systemText)
10367
+ return;
10368
+ const match2 = systemText.match(/Primary working directory:\s*([^\n<]+)/i);
10369
+ return match2?.[1]?.trim() || undefined;
10370
+ }
10371
+ var claudeCodeAdapter = {
10372
+ name: "claude-code",
10373
+ getSessionId(_c) {
10374
+ return;
10375
+ },
10376
+ extractWorkingDirectory(_body) {
10377
+ return;
10378
+ },
10379
+ extractClientWorkingDirectory(body) {
10380
+ return extractClaudeCodeClientCwd(body);
10381
+ },
10382
+ normalizeContent(content) {
10383
+ return normalizeContent(content);
10384
+ },
10385
+ getBlockedBuiltinTools() {
10386
+ return BLOCKED_BUILTIN_TOOLS;
10387
+ },
10388
+ getAgentIncompatibleTools() {
10389
+ return CLAUDE_CODE_ONLY_TOOLS;
10390
+ },
10391
+ getMcpServerName() {
10392
+ return MCP_SERVER_NAME;
10393
+ },
10394
+ getAllowedMcpTools() {
10395
+ return ALLOWED_MCP_TOOLS;
10396
+ },
10397
+ getCoreToolNames() {
10398
+ return ["Read", "Write", "Edit", "Bash", "Glob", "Grep"];
10399
+ },
10400
+ usesPassthrough() {
10401
+ const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10402
+ if (envVal === "0" || envVal === "false" || envVal === "no") {
10403
+ return false;
10404
+ }
10405
+ return true;
10406
+ },
10407
+ supportsThinking() {
10408
+ return true;
10409
+ },
10410
+ shouldTrackFileChanges() {
10411
+ return false;
10412
+ },
10413
+ extractFileChangesFromToolUse(toolName, toolInput) {
10414
+ const input = toolInput;
10415
+ const filePath = input?.file_path ?? input?.filePath ?? input?.path;
10416
+ const lowerName = toolName.toLowerCase();
10417
+ if (lowerName === "write" && filePath) {
10418
+ return [{ operation: "wrote", path: String(filePath) }];
10419
+ }
10420
+ if ((lowerName === "edit" || lowerName === "multiedit") && filePath) {
10421
+ return [{ operation: "edited", path: String(filePath) }];
10422
+ }
10423
+ if (lowerName === "bash" && input?.command) {
10424
+ return extractFileChangesFromBash(String(input.command));
10425
+ }
10426
+ return [];
10427
+ }
10428
+ };
10429
+
10237
10430
  // src/proxy/adapters/detect.ts
10238
10431
  var ADAPTER_MAP = {
10239
10432
  opencode: openCodeAdapter,
@@ -10241,7 +10434,9 @@ var ADAPTER_MAP = {
10241
10434
  crush: crushAdapter,
10242
10435
  passthrough: passthroughAdapter,
10243
10436
  pi: piAdapter,
10244
- forgecode: forgeCodeAdapter
10437
+ forgecode: forgeCodeAdapter,
10438
+ "claude-code": claudeCodeAdapter,
10439
+ claudecode: claudeCodeAdapter
10245
10440
  };
10246
10441
  var envDefault = process.env.MERIDIAN_DEFAULT_AGENT || "";
10247
10442
  if (envDefault && !ADAPTER_MAP[envDefault]) {
@@ -10272,6 +10467,9 @@ function detectAdapter(c) {
10272
10467
  if (userAgent.startsWith("Charm-Crush/")) {
10273
10468
  return crushAdapter;
10274
10469
  }
10470
+ if (userAgent.startsWith("claude-cli/")) {
10471
+ return claudeCodeAdapter;
10472
+ }
10275
10473
  if (isLiteLLMRequest(c)) {
10276
10474
  return passthroughAdapter;
10277
10475
  }
@@ -15974,16 +16172,35 @@ function createOpencodeMcpServer() {
15974
16172
  }
15975
16173
 
15976
16174
  // src/proxy/query.ts
15977
- function resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt) {
16175
+ function computePassthroughMaxTurns(resumeSessionId, hasDeferredTools, advisorModel) {
16176
+ const hasResume = !!resumeSessionId;
16177
+ const base = hasResume && hasDeferredTools ? 4 : hasResume || hasDeferredTools ? 3 : 2;
16178
+ const advisorBump = advisorModel ? 3 : 0;
16179
+ return base + advisorBump;
16180
+ }
16181
+ function buildCwdNote(sdkCwd, clientCwd) {
16182
+ if (!clientCwd || clientCwd === sdkCwd)
16183
+ return "";
16184
+ return `
16185
+
16186
+ <env>
16187
+ ` + `Working directory: ${clientCwd}
16188
+ ` + `</env>
16189
+ ` + `<meridian-note>
16190
+ ` + `You are reached through a proxy. The subprocess running you resides at ` + `"${sdkCwd}" on the proxy host, but that is not the user's working directory. ` + `Always treat "${clientCwd}" as the working directory when referring to files or paths.
16191
+ ` + `</meridian-note>`;
16192
+ }
16193
+ function resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt, cwdNote) {
15978
16194
  const hasSettings = settingSources != null && settingSources.length > 0;
15979
16195
  const usePreset = codeSystemPrompt ?? (hasSettings || !passthrough && !!systemContext);
15980
16196
  const includeClient = clientSystemPrompt ?? true;
15981
16197
  const clientContext = includeClient ? systemContext : undefined;
16198
+ const append = [clientContext, cwdNote].filter(Boolean).join("") || undefined;
15982
16199
  if (usePreset) {
15983
- return clientContext ? { systemPrompt: { type: "preset", preset: "claude_code", append: clientContext } } : { systemPrompt: { type: "preset", preset: "claude_code" } };
16200
+ return append ? { systemPrompt: { type: "preset", preset: "claude_code", append } } : { systemPrompt: { type: "preset", preset: "claude_code" } };
15984
16201
  }
15985
- if (clientContext)
15986
- return { systemPrompt: clientContext };
16202
+ if (append)
16203
+ return { systemPrompt: append };
15987
16204
  return {};
15988
16205
  }
15989
16206
  function buildQueryOptions(ctx) {
@@ -15991,6 +16208,7 @@ function buildQueryOptions(ctx) {
15991
16208
  prompt,
15992
16209
  model,
15993
16210
  workingDirectory,
16211
+ clientWorkingDirectory,
15994
16212
  systemContext,
15995
16213
  claudeExecutable,
15996
16214
  passthrough,
@@ -16023,19 +16241,20 @@ function buildQueryOptions(ctx) {
16023
16241
  sdkDebug,
16024
16242
  additionalDirectories
16025
16243
  } = ctx;
16244
+ const cwdNote = buildCwdNote(workingDirectory, clientWorkingDirectory);
16026
16245
  const allBlockedTools = [...blockedTools, ...incompatibleTools];
16027
16246
  return {
16028
16247
  prompt,
16029
16248
  options: {
16030
16249
  executable: "node",
16031
- maxTurns: passthrough ? resumeSessionId || hasDeferredTools ? 3 : 2 : 200,
16250
+ maxTurns: passthrough ? computePassthroughMaxTurns(resumeSessionId, hasDeferredTools, ctx.advisorModel) : 200,
16032
16251
  cwd: workingDirectory,
16033
16252
  model,
16034
16253
  pathToClaudeCodeExecutable: claudeExecutable,
16035
16254
  ...stream2 ? { includePartialMessages: true } : {},
16036
16255
  permissionMode: "bypassPermissions",
16037
16256
  allowDangerouslySkipPermissions: true,
16038
- ...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt),
16257
+ ...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt, cwdNote),
16039
16258
  ...passthrough ? {
16040
16259
  disallowedTools: [...allBlockedTools],
16041
16260
  ...passthroughMcp ? {
@@ -16074,7 +16293,8 @@ function buildQueryOptions(ctx) {
16074
16293
  ...maxBudgetUsd && maxBudgetUsd > 0 ? { maxBudgetUsd } : {},
16075
16294
  ...fallbackModel ? { fallbackModel } : {},
16076
16295
  ...sdkDebug ? { debug: true } : {},
16077
- ...additionalDirectories && additionalDirectories.length > 0 ? { additionalDirectories } : {}
16296
+ ...additionalDirectories && additionalDirectories.length > 0 ? { additionalDirectories } : {},
16297
+ ...ctx.advisorModel ? { advisorModel: ctx.advisorModel } : {}
16078
16298
  }
16079
16299
  };
16080
16300
  }
@@ -16414,6 +16634,10 @@ function sanitizeTextContent(text, opts = {}) {
16414
16634
 
16415
16635
  // src/proxy/session/lineage.ts
16416
16636
  import { createHash as createHash2 } from "crypto";
16637
+ function normalizeContextUsage(usage) {
16638
+ const lastIteration = usage.iterations?.at(-1);
16639
+ return lastIteration ?? usage;
16640
+ }
16417
16641
  var MIN_SUFFIX_FOR_COMPACTION = 2;
16418
16642
  function computeLineageHash(messages) {
16419
16643
  if (!messages || messages.length === 0)
@@ -17201,6 +17425,7 @@ function createProxyServer(config = {}) {
17201
17425
  const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
17202
17426
  let model = mapModelToClaudeModel(body.model || "sonnet", authStatus?.subscriptionType, agentMode);
17203
17427
  const workingDirectory = (process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR) || adapter.extractWorkingDirectory(body) || process.cwd();
17428
+ const clientWorkingDirectory = adapter.extractClientWorkingDirectory?.(body) || workingDirectory;
17204
17429
  const {
17205
17430
  CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,
17206
17431
  ANTHROPIC_API_KEY: _dropApiKey,
@@ -17208,7 +17433,8 @@ function createProxyServer(config = {}) {
17208
17433
  ANTHROPIC_AUTH_TOKEN: _dropAuthToken,
17209
17434
  ...cleanEnv
17210
17435
  } = process.env;
17211
- const profileEnv = { ...cleanEnv, ...profile.env };
17436
+ const sdkModelDefaults = resolveSdkModelDefaults();
17437
+ const profileEnv = { ...sdkModelDefaults, ...cleanEnv, ...profile.env };
17212
17438
  let systemContext = "";
17213
17439
  if (body.system) {
17214
17440
  if (typeof body.system === "string") {
@@ -17269,7 +17495,7 @@ function createProxyServer(config = {}) {
17269
17495
  const betas = betaFilter.forwarded;
17270
17496
  const agentSessionId = adapter.getSessionId(c);
17271
17497
  const profileSessionId = profile.id !== "default" && agentSessionId ? `${profile.id}:${agentSessionId}` : agentSessionId;
17272
- const profileScopedCwd = profile.id !== "default" ? `${workingDirectory}::profile=${profile.id}` : workingDirectory;
17498
+ const profileScopedCwd = profile.id !== "default" ? `${clientWorkingDirectory}::profile=${profile.id}` : clientWorkingDirectory;
17273
17499
  const isIndependentSession = requestSource?.startsWith("fork-") || requestSource?.startsWith("subagent-") || false;
17274
17500
  let lineageResult = isIndependentSession ? { type: "diverged" } : lookupSession(profileSessionId, body.messages || [], profileScopedCwd);
17275
17501
  if (lineageResult.type === "undo" && adapter.name === "opencode" && !agentSessionId) {
@@ -17383,6 +17609,10 @@ function createProxyServer(config = {}) {
17383
17609
  const fileChanges = [];
17384
17610
  let passthroughMcp;
17385
17611
  let requestTools = Array.isArray(body.tools) ? body.tools : [];
17612
+ const advisorModel = extractAdvisorModel(requestTools);
17613
+ if (advisorModel) {
17614
+ requestTools = stripAdvisorTools(requestTools);
17615
+ }
17386
17616
  if (passthrough && requestTools.length === 0 && profileSessionId) {
17387
17617
  const cached = sessionToolCache.get(profileSessionId);
17388
17618
  if (cached && cached.length > 0) {
@@ -17428,10 +17658,15 @@ function createProxyServer(config = {}) {
17428
17658
  if (hasDeferredTools && coreSet && !coreSet.has(toolName.toLowerCase())) {
17429
17659
  discoveredTools.add(toolName);
17430
17660
  }
17661
+ const clientTool = requestTools.find((t) => t.name === toolName);
17662
+ let toolInput = normalizeToolInput(input.tool_input, clientTool?.input_schema);
17663
+ if (toolName.toLowerCase() === "task" && toolInput?.subagent_type && typeof toolInput.subagent_type === "string") {
17664
+ toolInput = { ...toolInput, subagent_type: resolveAgentAlias(toolInput.subagent_type) };
17665
+ }
17431
17666
  capturedToolUses.push({
17432
17667
  id: input.tool_use_id,
17433
17668
  name: toolName,
17434
- input: input.tool_input
17669
+ input: toolInput
17435
17670
  });
17436
17671
  return {
17437
17672
  decision: "block",
@@ -17459,6 +17694,7 @@ function createProxyServer(config = {}) {
17459
17694
  sdkUuidMap.push(null);
17460
17695
  claudeLog("upstream.start", { mode: "non_stream", model });
17461
17696
  let lastUsage;
17697
+ let lastStopReason;
17462
17698
  try {
17463
17699
  if (!claudeExecutable) {
17464
17700
  claudeExecutable = await resolveClaudeExecutableAsync();
@@ -17475,6 +17711,7 @@ function createProxyServer(config = {}) {
17475
17711
  prompt: makePrompt(),
17476
17712
  model,
17477
17713
  workingDirectory,
17714
+ clientWorkingDirectory,
17478
17715
  systemContext,
17479
17716
  claudeExecutable,
17480
17717
  passthrough,
@@ -17505,7 +17742,8 @@ function createProxyServer(config = {}) {
17505
17742
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17506
17743
  fallbackModel: sdkFeatures.fallbackModel,
17507
17744
  sdkDebug: sdkFeatures.sdkDebug,
17508
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
17745
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
17746
+ advisorModel
17509
17747
  }))) {
17510
17748
  if (event.type === "assistant" && !event.error) {
17511
17749
  didYieldContent = true;
@@ -17532,6 +17770,7 @@ function createProxyServer(config = {}) {
17532
17770
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
17533
17771
  model,
17534
17772
  workingDirectory,
17773
+ clientWorkingDirectory,
17535
17774
  systemContext,
17536
17775
  claudeExecutable,
17537
17776
  passthrough,
@@ -17562,7 +17801,8 @@ function createProxyServer(config = {}) {
17562
17801
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17563
17802
  fallbackModel: sdkFeatures.fallbackModel,
17564
17803
  sdkDebug: sdkFeatures.sdkDebug,
17565
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
17804
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
17805
+ advisorModel
17566
17806
  }));
17567
17807
  return;
17568
17808
  }
@@ -17660,6 +17900,9 @@ function createProxyServer(config = {}) {
17660
17900
  const msgUsage = message.message.usage;
17661
17901
  if (msgUsage)
17662
17902
  lastUsage = { ...lastUsage, ...msgUsage };
17903
+ if (typeof message.message.stop_reason === "string") {
17904
+ lastStopReason = message.message.stop_reason;
17905
+ }
17663
17906
  }
17664
17907
  }
17665
17908
  claudeLog("upstream.completed", {
@@ -17697,19 +17940,27 @@ Subprocess stderr: ${stderrOutput}`;
17697
17940
  throw error;
17698
17941
  }
17699
17942
  if (passthrough && capturedToolUses.length > 0) {
17700
- for (const tu of capturedToolUses) {
17701
- if (!contentBlocks.some((b) => b.type === "tool_use" && b.id === tu.id)) {
17702
- contentBlocks.push({
17703
- type: "tool_use",
17704
- id: tu.id,
17705
- name: tu.name,
17706
- input: tu.input
17707
- });
17943
+ const capturedById = new Map(capturedToolUses.map((tu) => [tu.id, tu]));
17944
+ for (const block of contentBlocks) {
17945
+ if (block.type === "tool_use" && capturedById.has(block.id)) {
17946
+ const captured = capturedById.get(block.id);
17947
+ block.name = captured.name;
17948
+ block.input = captured.input;
17949
+ capturedById.delete(block.id);
17708
17950
  }
17709
17951
  }
17952
+ for (const tu of capturedById.values()) {
17953
+ contentBlocks.push({
17954
+ type: "tool_use",
17955
+ id: tu.id,
17956
+ name: tu.name,
17957
+ input: tu.input
17958
+ });
17959
+ }
17710
17960
  }
17711
17961
  const hasToolUse = contentBlocks.some((b) => b.type === "tool_use");
17712
- const stopReason = hasToolUse ? "tool_use" : "end_turn";
17962
+ const heuristicStopReason = hasToolUse ? "tool_use" : "end_turn";
17963
+ const stopReason = lastStopReason && lastStopReason !== "end_turn" && lastStopReason !== "tool_use" ? lastStopReason : heuristicStopReason;
17713
17964
  if (trackFileChanges) {
17714
17965
  if (passthrough && stopReason === "end_turn" && pipelineCtx.extractFileChangesFromToolUse) {
17715
17966
  const passthroughChanges = extractFileChangesFromMessages(body.messages || [], pipelineCtx.extractFileChangesFromToolUse);
@@ -17851,6 +18102,7 @@ Subprocess stderr: ${stderrOutput}`;
17851
18102
  prompt: makePrompt(),
17852
18103
  model,
17853
18104
  workingDirectory,
18105
+ clientWorkingDirectory,
17854
18106
  systemContext,
17855
18107
  claudeExecutable,
17856
18108
  passthrough,
@@ -17881,7 +18133,8 @@ Subprocess stderr: ${stderrOutput}`;
17881
18133
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17882
18134
  fallbackModel: sdkFeatures.fallbackModel,
17883
18135
  sdkDebug: sdkFeatures.sdkDebug,
17884
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
18136
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
18137
+ advisorModel
17885
18138
  }))) {
17886
18139
  if (event.type === "stream_event") {
17887
18140
  didYieldClientEvent = true;
@@ -17908,6 +18161,7 @@ Subprocess stderr: ${stderrOutput}`;
17908
18161
  prompt: buildFreshPrompt(allMessages, sanitizeOpts),
17909
18162
  model,
17910
18163
  workingDirectory,
18164
+ clientWorkingDirectory,
17911
18165
  systemContext,
17912
18166
  claudeExecutable,
17913
18167
  passthrough,
@@ -17938,7 +18192,8 @@ Subprocess stderr: ${stderrOutput}`;
17938
18192
  maxBudgetUsd: sdkFeatures.maxBudgetUsd,
17939
18193
  fallbackModel: sdkFeatures.fallbackModel,
17940
18194
  sdkDebug: sdkFeatures.sdkDebug,
17941
- additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined
18195
+ additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
18196
+ advisorModel
17942
18197
  }));
17943
18198
  return;
17944
18199
  }
@@ -18018,6 +18273,8 @@ Subprocess stderr: ${stderrOutput}`;
18018
18273
  }
18019
18274
  }, 15000);
18020
18275
  const skipBlockIndices = new Set;
18276
+ const taskToolBlockIndices = new Set;
18277
+ const taskToolJsonBuffer = new Map;
18021
18278
  const streamedToolUseIds = new Set;
18022
18279
  let nextClientBlockIndex = 0;
18023
18280
  const sdkToClientIndex = new Map;
@@ -18098,6 +18355,9 @@ data: ${JSON.stringify({ type: "message_stop" })}
18098
18355
  } else if (passthrough && block.id) {
18099
18356
  streamedToolUseIds.add(block.id);
18100
18357
  }
18358
+ if (passthrough && eventIndex !== undefined && block.name.toLowerCase() === "task") {
18359
+ taskToolBlockIndices.add(eventIndex);
18360
+ }
18101
18361
  }
18102
18362
  if (eventIndex !== undefined) {
18103
18363
  sdkToClientIndex.set(eventIndex, nextClientBlockIndex++);
@@ -18118,6 +18378,39 @@ data: ${JSON.stringify({ type: "message_stop" })}
18118
18378
  continue;
18119
18379
  }
18120
18380
  }
18381
+ if (passthrough && eventIndex !== undefined && taskToolBlockIndices.has(eventIndex)) {
18382
+ if (eventType === "content_block_delta") {
18383
+ const delta = event.delta;
18384
+ if (delta?.type === "input_json_delta" && typeof delta.partial_json === "string") {
18385
+ const prev = taskToolJsonBuffer.get(eventIndex) ?? "";
18386
+ taskToolJsonBuffer.set(eventIndex, prev + delta.partial_json);
18387
+ continue;
18388
+ }
18389
+ }
18390
+ if (eventType === "content_block_stop") {
18391
+ const buffered = taskToolJsonBuffer.get(eventIndex);
18392
+ if (buffered) {
18393
+ let fixed = buffered;
18394
+ try {
18395
+ const parsed = JSON.parse(buffered);
18396
+ if (typeof parsed.subagent_type === "string") {
18397
+ parsed.subagent_type = resolveAgentAlias(parsed.subagent_type);
18398
+ }
18399
+ fixed = JSON.stringify(parsed);
18400
+ } catch {}
18401
+ const clientIdx = sdkToClientIndex.get(eventIndex) ?? eventIndex;
18402
+ safeEnqueue(encoder.encode(`event: content_block_delta
18403
+ data: ${JSON.stringify({
18404
+ type: "content_block_delta",
18405
+ index: clientIdx,
18406
+ delta: { type: "input_json_delta", partial_json: fixed }
18407
+ })}
18408
+
18409
+ `), "task_tool_fixed_delta");
18410
+ taskToolJsonBuffer.delete(eventIndex);
18411
+ }
18412
+ }
18413
+ }
18121
18414
  const payload = encoder.encode(`event: ${eventType}
18122
18415
  data: ${JSON.stringify(event)}
18123
18416
 
@@ -18701,7 +18994,7 @@ data: ${JSON.stringify({
18701
18994
  if (!session.contextUsage) {
18702
18995
  return c.json({ error: "No usage data available for this session" }, 404);
18703
18996
  }
18704
- return c.json({ session_id: claudeSessionId, context_usage: session.contextUsage });
18997
+ return c.json({ session_id: claudeSessionId, context_usage: normalizeContextUsage(session.contextUsage) });
18705
18998
  });
18706
18999
  app.get("/v1/sessions/recover", (c) => {
18707
19000
  const sessions = listStoredSessions();
@@ -18777,6 +19070,8 @@ async function startProxyServer(config = {}) {
18777
19070
  if (!finalConfig.silent) {
18778
19071
  console.log(`Meridian running at http://${finalConfig.host}:${info.port}`);
18779
19072
  console.log(`Telemetry dashboard: http://${finalConfig.host}:${info.port}/telemetry`);
19073
+ const pins = resolveSdkModelDefaults();
19074
+ console.log(`Model pins: opus=${pins.ANTHROPIC_DEFAULT_OPUS_MODEL} sonnet=${pins.ANTHROPIC_DEFAULT_SONNET_MODEL} haiku=${pins.ANTHROPIC_DEFAULT_HAIKU_MODEL}`);
18780
19075
  console.log(`
18781
19076
  Point any Anthropic-compatible tool at this endpoint:`);
18782
19077
  console.log(` ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://${finalConfig.host}:${info.port}`);