@rynfar/meridian 1.40.0 → 1.41.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.
@@ -25,9 +25,10 @@ import {
25
25
  } from "./cli-rtab0qa6.js";
26
26
  import {
27
27
  claudeLog,
28
+ createPlatformCredentialStore,
28
29
  refreshOAuthToken,
29
30
  withClaudeLogContext
30
- } from "./cli-m9pfb7h9.js";
31
+ } from "./cli-e289rj3k.js";
31
32
  import {
32
33
  __commonJS,
33
34
  __esm,
@@ -1178,13 +1179,13 @@ __export(exports_sdkFeatures, {
1178
1179
  getAllFeatureConfigs: () => getAllFeatureConfigs
1179
1180
  });
1180
1181
  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";
1182
- import { homedir as homedir4 } from "node:os";
1182
+ import { join as join5 } from "node:path";
1183
+ import { homedir as homedir3 } from "node:os";
1183
1184
  function getConfigPath() {
1184
- const dir = join6(homedir4(), ".config", "meridian");
1185
+ const dir = join5(homedir3(), ".config", "meridian");
1185
1186
  if (!existsSync5(dir))
1186
1187
  mkdirSync2(dir, { recursive: true });
1187
- return join6(dir, "sdk-features.json");
1188
+ return join5(dir, "sdk-features.json");
1188
1189
  }
1189
1190
  function readConfig() {
1190
1191
  const now = Date.now();
@@ -1282,7 +1283,7 @@ function resetAdapterFeatures(adapterName) {
1282
1283
  var DEFAULT_FEATURES, ADAPTER_DEFAULTS, cachedConfig = null, lastReadTime = 0, CACHE_TTL_MS = 5000, VALID_CLAUDE_MD_VALUES, VALID_THINKING_VALUES;
1283
1284
  var init_sdkFeatures = __esm(() => {
1284
1285
  DEFAULT_FEATURES = {
1285
- codeSystemPrompt: false,
1286
+ codeSystemPrompt: true,
1286
1287
  clientSystemPrompt: true,
1287
1288
  claudeMd: "off",
1288
1289
  memory: false,
@@ -1295,7 +1296,11 @@ var init_sdkFeatures = __esm(() => {
1295
1296
  sdkDebug: false,
1296
1297
  additionalDirectories: ""
1297
1298
  };
1298
- ADAPTER_DEFAULTS = {};
1299
+ ADAPTER_DEFAULTS = {
1300
+ passthrough: {
1301
+ codeSystemPrompt: false
1302
+ }
1303
+ };
1299
1304
  VALID_CLAUDE_MD_VALUES = new Set(["off", "project", "full"]);
1300
1305
  VALID_THINKING_VALUES = new Set(["adaptive", "enabled", "disabled"]);
1301
1306
  });
@@ -3702,8 +3707,8 @@ var serve = (options, listeningListener) => {
3702
3707
  };
3703
3708
 
3704
3709
  // src/proxy/server.ts
3705
- import { homedir as homedir5 } from "node:os";
3706
- import { join as join7 } from "node:path";
3710
+ import { homedir as homedir4 } from "node:os";
3711
+ import { join as join6 } from "node:path";
3707
3712
  import { query } from "@anthropic-ai/claude-agent-sdk";
3708
3713
 
3709
3714
  // src/proxy/rateLimitStore.ts
@@ -3730,6 +3735,120 @@ class RateLimitStore {
3730
3735
  }
3731
3736
  var rateLimitStore = new RateLimitStore;
3732
3737
 
3738
+ // src/proxy/oauthUsage.ts
3739
+ var OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
3740
+ var OAUTH_BETA_HEADER = "oauth-2025-04-20";
3741
+ var CACHE_TTL_MS_DEFAULT = 30000;
3742
+ var cacheByProfile = new Map;
3743
+ var inflightByProfile = new Map;
3744
+ var DEFAULT_KEY = "__default__";
3745
+ var WINDOW_TYPES = [
3746
+ "five_hour",
3747
+ "seven_day",
3748
+ "seven_day_opus",
3749
+ "seven_day_sonnet",
3750
+ "seven_day_oauth_apps",
3751
+ "seven_day_cowork",
3752
+ "seven_day_omelette"
3753
+ ];
3754
+ function parseIsoToMs(raw2) {
3755
+ if (!raw2)
3756
+ return null;
3757
+ const ms = Date.parse(raw2);
3758
+ return Number.isFinite(ms) ? ms : null;
3759
+ }
3760
+ function normalizeUtilization(raw2) {
3761
+ if (typeof raw2 !== "number" || !Number.isFinite(raw2))
3762
+ return null;
3763
+ return Math.max(0, raw2 / 100);
3764
+ }
3765
+ function buildSnapshot(raw2) {
3766
+ const windows = [];
3767
+ for (const key of WINDOW_TYPES) {
3768
+ const w = raw2[key];
3769
+ if (!w)
3770
+ continue;
3771
+ const utilization = normalizeUtilization(w.utilization);
3772
+ const resetsAt = parseIsoToMs(w.resets_at);
3773
+ if (utilization === null && resetsAt === null)
3774
+ continue;
3775
+ windows.push({ type: key, utilization, resetsAt });
3776
+ }
3777
+ const extra = raw2.extra_usage;
3778
+ const extraUsage = extra ? {
3779
+ isEnabled: !!extra.is_enabled,
3780
+ monthlyLimit: extra.monthly_limit ?? 0,
3781
+ usedCredits: extra.used_credits ?? 0,
3782
+ utilization: normalizeUtilization(extra.utilization ?? null),
3783
+ currency: extra.currency ?? "USD"
3784
+ } : null;
3785
+ return { windows, extraUsage, fetchedAt: Date.now() };
3786
+ }
3787
+ async function readAccessToken(store) {
3788
+ const creds = await store.read();
3789
+ return creds?.claudeAiOauth?.accessToken ?? null;
3790
+ }
3791
+ async function callAnthropic(token, signal) {
3792
+ const res = await fetch(OAUTH_USAGE_URL, {
3793
+ headers: {
3794
+ Authorization: `Bearer ${token}`,
3795
+ "anthropic-beta": OAUTH_BETA_HEADER,
3796
+ Accept: "application/json"
3797
+ },
3798
+ signal: signal ?? AbortSignal.timeout(1e4)
3799
+ });
3800
+ if (!res.ok)
3801
+ return { __status: res.status };
3802
+ return await res.json();
3803
+ }
3804
+ async function fetchOAuthUsage(opts) {
3805
+ const ttl = opts?.ttlMs ?? CACHE_TTL_MS_DEFAULT;
3806
+ const cacheKey2 = opts?.profileId ?? DEFAULT_KEY;
3807
+ if (!opts?.force) {
3808
+ const cached = cacheByProfile.get(cacheKey2);
3809
+ if (cached && Date.now() - cached.fetchedAt < ttl)
3810
+ return cached;
3811
+ }
3812
+ const existing = inflightByProfile.get(cacheKey2);
3813
+ if (existing)
3814
+ return existing;
3815
+ const store = opts?.store ?? createPlatformCredentialStore({ claudeConfigDir: opts?.claudeConfigDir });
3816
+ const promise = (async () => {
3817
+ try {
3818
+ const token = await readAccessToken(store);
3819
+ if (!token)
3820
+ return null;
3821
+ let result = await callAnthropic(token);
3822
+ if ("__status" in result && result.__status === 401) {
3823
+ claudeLog("oauth_usage.token_refresh_attempt", { profile: cacheKey2 });
3824
+ const refreshed = await refreshOAuthToken(store);
3825
+ if (!refreshed) {
3826
+ claudeLog("oauth_usage.refresh_failed", { profile: cacheKey2 });
3827
+ return null;
3828
+ }
3829
+ const newToken = await readAccessToken(store);
3830
+ if (!newToken)
3831
+ return null;
3832
+ result = await callAnthropic(newToken);
3833
+ }
3834
+ if ("__status" in result) {
3835
+ claudeLog("oauth_usage.upstream_error", { profile: cacheKey2, status: result.__status });
3836
+ return null;
3837
+ }
3838
+ const snapshot = buildSnapshot(result);
3839
+ cacheByProfile.set(cacheKey2, snapshot);
3840
+ return snapshot;
3841
+ } catch (err) {
3842
+ claudeLog("oauth_usage.fetch_failed", { profile: cacheKey2, error: err instanceof Error ? err.message : String(err) });
3843
+ return null;
3844
+ } finally {
3845
+ inflightByProfile.delete(cacheKey2);
3846
+ }
3847
+ })();
3848
+ inflightByProfile.set(cacheKey2, promise);
3849
+ return promise;
3850
+ }
3851
+
3733
3852
  // src/proxy/types.ts
3734
3853
  var DEFAULT_PROXY_CONFIG = {
3735
3854
  port: 3456,
@@ -7993,13 +8112,20 @@ function ensureDefaultAgents(agents, mcpToolNames) {
7993
8112
  }
7994
8113
  }
7995
8114
  }
8115
+ function cloneAgentDefinition(def) {
8116
+ return {
8117
+ ...def,
8118
+ ...def.tools ? { tools: [...def.tools] } : {},
8119
+ ...def.disallowedTools ? { disallowedTools: [...def.disallowedTools] } : {}
8120
+ };
8121
+ }
7996
8122
  function addCaseVariants(agents) {
7997
8123
  const baseNames = Object.keys(agents);
7998
8124
  for (const name of baseNames) {
7999
8125
  const def = agents[name];
8000
8126
  const titleCase = name.replace(/(^|-)(\w)/g, (_m, sep, ch) => sep + ch.toUpperCase());
8001
8127
  if (titleCase !== name && !agents[titleCase]) {
8002
- agents[titleCase] = def;
8128
+ agents[titleCase] = cloneAgentDefinition(def);
8003
8129
  }
8004
8130
  }
8005
8131
  const ALIASES = {
@@ -8008,10 +8134,50 @@ function addCaseVariants(agents) {
8008
8134
  };
8009
8135
  for (const [alias, target] of Object.entries(ALIASES)) {
8010
8136
  if (!agents[alias] && agents[target]) {
8011
- agents[alias] = agents[target];
8137
+ agents[alias] = cloneAgentDefinition(agents[target]);
8012
8138
  }
8013
8139
  }
8014
8140
  }
8141
+ function getNested(obj, ...keys) {
8142
+ let cur = obj;
8143
+ for (const key of keys) {
8144
+ if (cur === null || typeof cur !== "object")
8145
+ return;
8146
+ cur = cur[key];
8147
+ }
8148
+ return cur;
8149
+ }
8150
+ function parseAgentNamesFromSchema(taskTool) {
8151
+ const enumNames = getNested(taskTool, "input_schema", "properties", "subagent_type", "enum");
8152
+ if (!Array.isArray(enumNames))
8153
+ return [];
8154
+ return enumNames.filter((n) => typeof n === "string");
8155
+ }
8156
+ function buildAgentDefinitionsFromTool(taskTool, mcpToolNames) {
8157
+ const rawDescription = getNested(taskTool, "description");
8158
+ const description = typeof rawDescription === "string" ? rawDescription : "";
8159
+ const fromDescription = buildAgentDefinitions(description, mcpToolNames);
8160
+ if (Object.keys(fromDescription).length > 0)
8161
+ return fromDescription;
8162
+ const names = parseAgentNamesFromSchema(taskTool);
8163
+ if (names.length === 0)
8164
+ return {};
8165
+ const agents = {};
8166
+ for (const name of names) {
8167
+ if (agents[name])
8168
+ continue;
8169
+ const desc = `User-defined agent: ${name}`;
8170
+ agents[name] = {
8171
+ description: desc,
8172
+ prompt: buildAgentPrompt(name, desc),
8173
+ model: "inherit",
8174
+ ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
8175
+ };
8176
+ }
8177
+ ensureDefaultAgents(agents, mcpToolNames);
8178
+ addCaseVariants(agents);
8179
+ return agents;
8180
+ }
8015
8181
  function buildAgentPrompt(name, description) {
8016
8182
  return `You are the "${name}" agent. ${description}
8017
8183
 
@@ -8388,8 +8554,6 @@ let timer;
8388
8554
  let activeTab = 'requests';
8389
8555
  let activeLogFilter = 'all';
8390
8556
 
8391
-
8392
-
8393
8557
  function ms(v) {
8394
8558
  if (v == null) return '—';
8395
8559
  if (v < 1000) return v + 'ms';
@@ -8991,14 +9155,91 @@ function isExtraUsageRequiredError(errMsg) {
8991
9155
  const lower = errMsg.toLowerCase();
8992
9156
  return lower.includes("extra usage") && lower.includes("1m");
8993
9157
  }
9158
+ var STDERR_TAIL_MAX = 500;
9159
+ var RAW_TAIL_MAX = 300;
9160
+ function extractStderrTail(errMsg) {
9161
+ const marker = "Subprocess stderr:";
9162
+ const idx = errMsg.indexOf(marker);
9163
+ if (idx < 0)
9164
+ return;
9165
+ const tail = errMsg.slice(idx + marker.length).trim();
9166
+ if (!tail)
9167
+ return;
9168
+ return tail.length > STDERR_TAIL_MAX ? tail.slice(0, STDERR_TAIL_MAX) : tail;
9169
+ }
9170
+ function makeRawTail(errMsg) {
9171
+ const marker = "Subprocess stderr:";
9172
+ const idx = errMsg.indexOf(marker);
9173
+ const head = (idx >= 0 ? errMsg.slice(0, idx) : errMsg).trim();
9174
+ if (!head)
9175
+ return;
9176
+ return head.length > RAW_TAIL_MAX ? head.slice(0, RAW_TAIL_MAX) : head;
9177
+ }
9178
+ function extractSdkTermination(errMsg) {
9179
+ const stderrTail = extractStderrTail(errMsg);
9180
+ const haystack = `${errMsg}
9181
+ ${stderrTail ?? ""}`;
9182
+ const lower = haystack.toLowerCase();
9183
+ if (lower.includes("reached maximum number of turns")) {
9184
+ const m = haystack.match(/Reached maximum number of turns \((\d+)\)/i);
9185
+ return {
9186
+ reason: "max_turns",
9187
+ ...m ? { turns: Number(m[1]) } : {},
9188
+ ...stderrTail ? { stderrTail } : {}
9189
+ };
9190
+ }
9191
+ if (lower.includes("exited with code") || lower.includes("process exited")) {
9192
+ const m = haystack.match(/exited with code (\d+)/i);
9193
+ return {
9194
+ reason: "process_exit",
9195
+ ...m ? { exitCode: Number(m[1]) } : {},
9196
+ ...stderrTail ? { stderrTail } : {}
9197
+ };
9198
+ }
9199
+ if (lower.includes("aborterror") || /\baborted\b/.test(lower)) {
9200
+ return {
9201
+ reason: "aborted",
9202
+ ...stderrTail ? { stderrTail } : {}
9203
+ };
9204
+ }
9205
+ const rawTail = makeRawTail(errMsg);
9206
+ return {
9207
+ reason: "unknown",
9208
+ ...stderrTail ? { stderrTail } : {},
9209
+ ...rawTail ? { rawTail } : {}
9210
+ };
9211
+ }
9212
+ function formatSdkTermination(t, ctx) {
9213
+ const parts = [`reason=${t.reason}`];
9214
+ if (t.turns !== undefined)
9215
+ parts.push(`turns=${t.turns}`);
9216
+ if (t.exitCode !== undefined)
9217
+ parts.push(`exit=${t.exitCode}`);
9218
+ if (ctx.model)
9219
+ parts.push(`model=${ctx.model}`);
9220
+ if (ctx.requestSource)
9221
+ parts.push(`source=${ctx.requestSource}`);
9222
+ if (ctx.isResume !== undefined)
9223
+ parts.push(`resume=${ctx.isResume}`);
9224
+ if (ctx.hasDeferredTools !== undefined)
9225
+ parts.push(`deferred=${ctx.hasDeferredTools}`);
9226
+ if (ctx.sdkSessionId)
9227
+ parts.push(`session=${ctx.sdkSessionId.slice(0, 8)}`);
9228
+ if (t.rawTail)
9229
+ parts.push(`raw=${JSON.stringify(t.rawTail)}`);
9230
+ if (t.stderrTail)
9231
+ parts.push(`stderr=${JSON.stringify(t.stderrTail)}`);
9232
+ return `sdk_termination ${parts.join(" ")}`;
9233
+ }
8994
9234
 
8995
9235
  // src/proxy/models.ts
8996
9236
  import { exec as execCallback } from "child_process";
8997
- import { existsSync as existsSync2 } from "fs";
9237
+ import { existsSync as existsSync2, statSync } from "fs";
8998
9238
  import { fileURLToPath as fileURLToPath2 } from "url";
8999
9239
  import { join as join2, dirname as dirname2 } from "path";
9000
9240
  import { promisify } from "util";
9001
9241
  var exec = promisify(execCallback);
9242
+ var STUB_SIZE_THRESHOLD = 4096;
9002
9243
  var CANONICAL_OPUS_MODEL = "claude-opus-4-7";
9003
9244
  var CANONICAL_SONNET_MODEL = "claude-sonnet-4-6";
9004
9245
  var CANONICAL_HAIKU_MODEL = "claude-haiku-4-5";
@@ -9138,40 +9379,92 @@ async function getClaudeAuthStatusAsync(profileId, envOverrides) {
9138
9379
  }
9139
9380
  var cachedClaudePath = null;
9140
9381
  var cachedClaudePathPromise = null;
9382
+ var DEFAULT_DEPS = {
9383
+ existsSync: existsSync2,
9384
+ statSync: (p) => statSync(p),
9385
+ exec,
9386
+ resolvePackage: (specifier) => fileURLToPath2(import.meta.resolve(specifier)),
9387
+ envGet: (name) => process.env[name],
9388
+ platform: process.platform,
9389
+ arch: process.arch,
9390
+ isBun: typeof process.versions.bun !== "undefined"
9391
+ };
9392
+ function tryEnvOverride(deps) {
9393
+ const explicit = deps.envGet("MERIDIAN_CLAUDE_PATH");
9394
+ if (!explicit)
9395
+ return null;
9396
+ return deps.existsSync(explicit) ? explicit : null;
9397
+ }
9398
+ function tryBundledBinary(deps) {
9399
+ try {
9400
+ const pkgPath = deps.resolvePackage("@anthropic-ai/claude-code/package.json");
9401
+ const bundled = join2(dirname2(pkgPath), "bin", "claude.exe");
9402
+ if (!deps.existsSync(bundled))
9403
+ return null;
9404
+ const size = deps.statSync(bundled).size;
9405
+ if (size <= STUB_SIZE_THRESHOLD)
9406
+ return null;
9407
+ return bundled;
9408
+ } catch {
9409
+ return null;
9410
+ }
9411
+ }
9412
+ function tryPlatformPackage(deps) {
9413
+ const binName = deps.platform === "win32" ? "claude.exe" : "claude";
9414
+ const candidates = [`@anthropic-ai/claude-code-${deps.platform}-${deps.arch}`];
9415
+ if (deps.platform === "linux") {
9416
+ candidates.push(`@anthropic-ai/claude-code-${deps.platform}-${deps.arch}-musl`);
9417
+ }
9418
+ for (const pkg of candidates) {
9419
+ try {
9420
+ const pkgJson = deps.resolvePackage(`${pkg}/package.json`);
9421
+ const candidate = join2(dirname2(pkgJson), binName);
9422
+ if (deps.existsSync(candidate))
9423
+ return candidate;
9424
+ } catch {}
9425
+ }
9426
+ return null;
9427
+ }
9428
+ async function tryPathLookup(deps) {
9429
+ const cmd = deps.platform === "win32" ? "where claude" : "which claude";
9430
+ try {
9431
+ const { stdout } = await deps.exec(cmd);
9432
+ const candidates = stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
9433
+ for (const candidate of candidates) {
9434
+ if (deps.platform === "win32" && candidate.startsWith("/"))
9435
+ continue;
9436
+ if (deps.existsSync(candidate))
9437
+ return candidate;
9438
+ }
9439
+ } catch {}
9440
+ return null;
9441
+ }
9442
+ function tryLegacySdkCliJs(deps) {
9443
+ if (!deps.isBun)
9444
+ return null;
9445
+ try {
9446
+ const sdkPath = deps.resolvePackage("@anthropic-ai/claude-agent-sdk");
9447
+ const cliJs = join2(dirname2(sdkPath), "cli.js");
9448
+ return deps.existsSync(cliJs) ? cliJs : null;
9449
+ } catch {
9450
+ return null;
9451
+ }
9452
+ }
9453
+ async function resolveClaudeExecutable(deps = DEFAULT_DEPS) {
9454
+ return tryEnvOverride(deps) ?? tryBundledBinary(deps) ?? tryPlatformPackage(deps) ?? await tryPathLookup(deps) ?? tryLegacySdkCliJs(deps);
9455
+ }
9141
9456
  async function resolveClaudeExecutableAsync() {
9142
9457
  if (cachedClaudePath)
9143
9458
  return cachedClaudePath;
9144
9459
  if (cachedClaudePathPromise)
9145
9460
  return cachedClaudePathPromise;
9146
9461
  cachedClaudePathPromise = (async () => {
9147
- const runningUnderBun = typeof process.versions.bun !== "undefined";
9148
- try {
9149
- const pkgPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-code/package.json"));
9150
- const bundledBinary = join2(dirname2(pkgPath), "bin", "claude.exe");
9151
- if (existsSync2(bundledBinary)) {
9152
- cachedClaudePath = bundledBinary;
9153
- return bundledBinary;
9154
- }
9155
- } catch {}
9156
- try {
9157
- const { stdout } = await exec("which claude");
9158
- const claudePath = stdout.trim();
9159
- if (claudePath && existsSync2(claudePath)) {
9160
- cachedClaudePath = claudePath;
9161
- return claudePath;
9162
- }
9163
- } catch {}
9164
- if (runningUnderBun) {
9165
- try {
9166
- const sdkPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
9167
- const sdkCliJs = join2(dirname2(sdkPath), "cli.js");
9168
- if (existsSync2(sdkCliJs)) {
9169
- cachedClaudePath = sdkCliJs;
9170
- return sdkCliJs;
9171
- }
9172
- } catch {}
9462
+ const resolved = await resolveClaudeExecutable();
9463
+ if (resolved) {
9464
+ cachedClaudePath = resolved;
9465
+ return resolved;
9173
9466
  }
9174
- throw new Error("Could not find Claude Code executable. Install via: npm install -g @anthropic-ai/claude-code");
9467
+ throw new Error("Could not find Claude Code executable. Install via: npm install -g @anthropic-ai/claude-code, " + "or set MERIDIAN_CLAUDE_PATH=/path/to/claude to point at an existing binary.");
9175
9468
  })();
9176
9469
  try {
9177
9470
  return await cachedClaudePathPromise;
@@ -9216,7 +9509,7 @@ function parseDataUrlImage(url) {
9216
9509
  }
9217
9510
  function translateOpenAiContentToAnthropic(content) {
9218
9511
  if (typeof content === "string")
9219
- return content;
9512
+ return [{ type: "text", text: content }];
9220
9513
  const parts = [];
9221
9514
  for (const part of content) {
9222
9515
  if (part.type === "text" && typeof part.text === "string") {
@@ -9235,9 +9528,6 @@ function translateOpenAiContentToAnthropic(content) {
9235
9528
  parts.push({ type: "text", text: "[Unsupported image_url omitted: only data URLs are currently supported]" });
9236
9529
  }
9237
9530
  }
9238
- if (parts.length === 1 && parts[0]?.type === "text") {
9239
- return parts[0].text;
9240
- }
9241
9531
  return parts;
9242
9532
  }
9243
9533
  function summarizeAnthropicContent(content) {
@@ -9246,6 +9536,32 @@ function summarizeAnthropicContent(content) {
9246
9536
  return content.map((part) => {
9247
9537
  if (part.type === "text")
9248
9538
  return part.text;
9539
+ if (part.type === "thinking")
9540
+ return `
9541
+ <think>
9542
+ ` + part.thinking + `
9543
+ </think>
9544
+ `;
9545
+ if (part.type === "tool_use")
9546
+ return `
9547
+ <tool_call name="` + part.name + `">
9548
+ ` + JSON.stringify(part.input) + `
9549
+ </tool_call>
9550
+ `;
9551
+ if (part.type === "tool_result") {
9552
+ if (typeof part.content === "string")
9553
+ return `
9554
+ <tool_result>
9555
+ ` + part.content + `
9556
+ </tool_result>
9557
+ `;
9558
+ else
9559
+ return part.content.map((c) => c.type === "text" ? `
9560
+ <tool_result>
9561
+ ${c.text}
9562
+ </tool_result>
9563
+ ` : "").join("");
9564
+ }
9249
9565
  if (part.type === "image")
9250
9566
  return "[Image attached]";
9251
9567
  return "";
@@ -9257,18 +9573,89 @@ function translateOpenAiToAnthropic(body) {
9257
9573
  return null;
9258
9574
  const systemParts = [];
9259
9575
  const turns = [];
9576
+ const tools = [];
9260
9577
  for (const msg of messages) {
9261
9578
  const text = extractOpenAiContent(msg.content ?? "");
9262
9579
  if (msg.role === "system") {
9263
9580
  if (text)
9264
9581
  systemParts.push(text);
9582
+ } else if (msg.role === "tool") {
9583
+ turns.push({
9584
+ role: "user",
9585
+ content: [{
9586
+ type: "tool_result",
9587
+ tool_use_id: msg.tool_call_id ?? "",
9588
+ content: translateOpenAiContentToAnthropic(msg.content ?? "")
9589
+ }]
9590
+ });
9591
+ } else if (msg.role === "assistant") {
9592
+ const msgContent = translateOpenAiContentToAnthropic(msg.content ?? "");
9593
+ const content = [];
9594
+ const toolCalls = msg.tool_calls ?? null;
9595
+ const firstBlock = msgContent[0];
9596
+ const endOfThink = firstBlock?.type === "text" && firstBlock.text.startsWith("<think>") ? firstBlock.text.indexOf("</think>") : -1;
9597
+ if (firstBlock?.type === "text" && firstBlock.text.startsWith("<think>") && endOfThink !== -1) {
9598
+ const thinking = firstBlock.text.substring("<think>".length, endOfThink);
9599
+ let textStart = endOfThink + "</think>".length;
9600
+ if (firstBlock.text[textStart] === `
9601
+ `)
9602
+ textStart += 1;
9603
+ const text2 = firstBlock.text.substring(textStart);
9604
+ content.push({ type: "thinking", thinking });
9605
+ if (text2.length)
9606
+ content.push({ type: "text", text: text2 });
9607
+ if (msgContent.length > 1)
9608
+ content.push(...msgContent.slice(1));
9609
+ } else {
9610
+ content.push(...msgContent);
9611
+ }
9612
+ if (toolCalls) {
9613
+ const calls = toolCalls.filter((call) => call.type === "function").map((call) => {
9614
+ let input;
9615
+ try {
9616
+ const parsed = JSON.parse(call.function.arguments);
9617
+ input = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { __raw: call.function.arguments };
9618
+ } catch {
9619
+ input = { __raw: call.function.arguments };
9620
+ }
9621
+ return {
9622
+ type: "tool_use",
9623
+ id: call.id,
9624
+ name: call.function.name,
9625
+ input
9626
+ };
9627
+ });
9628
+ content.push(...calls);
9629
+ }
9630
+ let finalContent = content;
9631
+ if (content.length === 1 && content[0]?.type === "text") {
9632
+ finalContent = content[0].text;
9633
+ }
9634
+ turns.push({
9635
+ role: "assistant",
9636
+ content: finalContent
9637
+ });
9265
9638
  } else {
9266
9639
  turns.push({
9267
- role: msg.role === "assistant" ? "assistant" : "user",
9640
+ role: "user",
9268
9641
  content: translateOpenAiContentToAnthropic(msg.content ?? "")
9269
9642
  });
9270
9643
  }
9271
9644
  }
9645
+ const reqTools = body.tools ?? [];
9646
+ for (const reqTool of reqTools) {
9647
+ if (reqTool.type === "function") {
9648
+ const tool = reqTool.function;
9649
+ tools.push({
9650
+ name: tool.name,
9651
+ description: tool.description ?? "",
9652
+ input_schema: tool.parameters,
9653
+ strict: tool.strict
9654
+ });
9655
+ } else {
9656
+ return null;
9657
+ }
9658
+ }
9272
9659
  let systemPrompt = systemParts.join(`
9273
9660
  `);
9274
9661
  let messagesToSend = turns;
@@ -9289,6 +9676,7 @@ ${historyBlock}` : historyBlock;
9289
9676
  model: body.model ?? "claude-sonnet-4-6",
9290
9677
  messages: messagesToSend,
9291
9678
  max_tokens: body.max_tokens ?? body.max_completion_tokens ?? 8192,
9679
+ tools,
9292
9680
  stream: body.stream ?? false
9293
9681
  };
9294
9682
  if (systemPrompt)
@@ -9302,10 +9690,23 @@ ${historyBlock}` : historyBlock;
9302
9690
  function toFinishReason(stopReason) {
9303
9691
  if (stopReason === "max_tokens")
9304
9692
  return "length";
9693
+ else if (stopReason === "tool_use")
9694
+ return "tool_calls";
9305
9695
  return "stop";
9306
9696
  }
9307
- function translateAnthropicToOpenAi(response, completionId, model, created) {
9308
- const content = (response.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
9697
+ function translateAnthropicToOpenAi(response, completionId, model, created, options) {
9698
+ const contentBlocks = response.content ?? [];
9699
+ const content = contentBlocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
9700
+ const toolCalls = contentBlocks.filter((b) => b.type === "tool_use").map((b) => ({
9701
+ type: "function",
9702
+ id: b.id,
9703
+ function: {
9704
+ name: b.name,
9705
+ arguments: JSON.stringify(b.input)
9706
+ }
9707
+ }));
9708
+ const thinkingPassthrough = options?.thinkingPassthrough;
9709
+ const thinking = thinkingPassthrough !== false ? contentBlocks.filter((b) => b.type === "thinking").map((b) => b.thinking).join("") : "";
9309
9710
  const promptTokens = response.usage?.input_tokens ?? 0;
9310
9711
  const completionTokens = response.usage?.output_tokens ?? 0;
9311
9712
  return {
@@ -9315,7 +9716,12 @@ function translateAnthropicToOpenAi(response, completionId, model, created) {
9315
9716
  model,
9316
9717
  choices: [{
9317
9718
  index: 0,
9318
- message: { role: "assistant", content },
9719
+ message: {
9720
+ role: "assistant",
9721
+ content: content || null,
9722
+ reasoning_content: thinking.length ? thinking : undefined,
9723
+ tool_calls: toolCalls.length ? toolCalls : undefined
9724
+ },
9319
9725
  finish_reason: toFinishReason(response.stop_reason)
9320
9726
  }],
9321
9727
  usage: {
@@ -9325,7 +9731,16 @@ function translateAnthropicToOpenAi(response, completionId, model, created) {
9325
9731
  }
9326
9732
  };
9327
9733
  }
9328
- function translateAnthropicSseEvent(event, completionId, model, created) {
9734
+ function createSseTranslator(ctx) {
9735
+ let toolCallIndex = -1;
9736
+ return (event) => {
9737
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use" && typeof event.content_block.name === "string") {
9738
+ toolCallIndex++;
9739
+ }
9740
+ return translateAnthropicSseEvent(event, ctx.completionId, ctx.model, ctx.created, toolCallIndex, ctx.thinkingPassthrough);
9741
+ };
9742
+ }
9743
+ function translateAnthropicSseEvent(event, completionId, model, created, toolCallNum, thinkingPassthrough) {
9329
9744
  if (event.type === "message_start") {
9330
9745
  return {
9331
9746
  id: completionId,
@@ -9344,6 +9759,69 @@ function translateAnthropicSseEvent(event, completionId, model, created) {
9344
9759
  choices: [{ index: 0, delta: { content: event.delta.text }, finish_reason: null }]
9345
9760
  };
9346
9761
  }
9762
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use" && typeof event.content_block?.name === "string") {
9763
+ return {
9764
+ id: completionId,
9765
+ object: "chat.completion.chunk",
9766
+ created,
9767
+ model,
9768
+ choices: [{
9769
+ index: 0,
9770
+ delta: {
9771
+ tool_calls: [{
9772
+ type: "function",
9773
+ index: toolCallNum,
9774
+ id: event.content_block?.id,
9775
+ function: {
9776
+ name: event.content_block.name,
9777
+ arguments: ""
9778
+ }
9779
+ }]
9780
+ },
9781
+ finish_reason: null
9782
+ }]
9783
+ };
9784
+ }
9785
+ if (event.type === "content_block_delta" && event.delta?.type === "input_json_delta" && typeof event.delta?.partial_json === "string") {
9786
+ return {
9787
+ id: completionId,
9788
+ object: "chat.completion.chunk",
9789
+ created,
9790
+ model,
9791
+ choices: [{
9792
+ index: 0,
9793
+ delta: {
9794
+ tool_calls: [{
9795
+ index: toolCallNum,
9796
+ function: {
9797
+ arguments: event.delta.partial_json
9798
+ }
9799
+ }]
9800
+ },
9801
+ finish_reason: null
9802
+ }]
9803
+ };
9804
+ }
9805
+ if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta") {
9806
+ if (thinkingPassthrough === false) {
9807
+ return null;
9808
+ }
9809
+ if (typeof event.delta?.thinking === "string") {
9810
+ return {
9811
+ id: completionId,
9812
+ object: "chat.completion.chunk",
9813
+ created,
9814
+ model,
9815
+ choices: [{
9816
+ index: 0,
9817
+ delta: {
9818
+ reasoning_content: event.delta?.thinking
9819
+ },
9820
+ finish_reason: null
9821
+ }]
9822
+ };
9823
+ }
9824
+ }
9347
9825
  if (event.type === "message_delta" && event.delta?.stop_reason) {
9348
9826
  return {
9349
9827
  id: completionId,
@@ -9660,7 +10138,11 @@ var CLAUDE_CODE_ONLY_TOOLS = [
9660
10138
  "ExitPlanMode",
9661
10139
  "EnterWorktree",
9662
10140
  "ExitWorktree",
10141
+ "Monitor",
9663
10142
  "NotebookEdit",
10143
+ "PushNotification",
10144
+ "RemoteTrigger",
10145
+ "ScheduleWakeup",
9664
10146
  "TodoWrite",
9665
10147
  "AskUserQuestion",
9666
10148
  "Skill",
@@ -9695,8 +10177,8 @@ var openCodeTransforms = [
9695
10177
  let sdkAgents = {};
9696
10178
  if (Array.isArray(body.tools)) {
9697
10179
  const taskTool = body.tools.find((t) => t.name === "task" || t.name === "Task");
9698
- if (taskTool?.description) {
9699
- sdkAgents = buildAgentDefinitions(taskTool.description, [...allowedMcpTools]);
10180
+ if (taskTool) {
10181
+ sdkAgents = buildAgentDefinitionsFromTool(taskTool, [...allowedMcpTools]);
9700
10182
  }
9701
10183
  }
9702
10184
  let sdkHooks = undefined;
@@ -9802,9 +10284,9 @@ var openCodeAdapter = {
9802
10284
  if (!Array.isArray(body.tools))
9803
10285
  return {};
9804
10286
  const taskTool = body.tools.find((t) => t.name === "task" || t.name === "Task");
9805
- if (!taskTool?.description)
10287
+ if (!taskTool)
9806
10288
  return {};
9807
- return buildAgentDefinitions(taskTool.description, [...mcpToolNames]);
10289
+ return buildAgentDefinitionsFromTool(taskTool, [...mcpToolNames]);
9808
10290
  },
9809
10291
  buildSdkHooks(body, sdkAgents) {
9810
10292
  const validAgentNames = Object.keys(sdkAgents);
@@ -9860,6 +10342,10 @@ var DROID_ALLOWED_MCP_TOOLS = [
9860
10342
  `mcp__${DROID_MCP_SERVER_NAME}__glob`,
9861
10343
  `mcp__${DROID_MCP_SERVER_NAME}__grep`
9862
10344
  ];
10345
+ function resolveDroidPassthrough() {
10346
+ const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10347
+ return envVal === "1" || envVal === "true" || envVal === "yes";
10348
+ }
9863
10349
  var droidTransforms = [
9864
10350
  {
9865
10351
  name: "droid-core",
@@ -9871,7 +10357,7 @@ var droidTransforms = [
9871
10357
  incompatibleTools: CLAUDE_CODE_ONLY_TOOLS,
9872
10358
  allowedMcpTools: DROID_ALLOWED_MCP_TOOLS,
9873
10359
  sdkAgents: {},
9874
- passthrough: false,
10360
+ passthrough: resolveDroidPassthrough(),
9875
10361
  leaksCwdViaSystemReminder: true
9876
10362
  };
9877
10363
  }
@@ -9942,7 +10428,8 @@ var droidAdapter = {
9942
10428
  return "";
9943
10429
  },
9944
10430
  usesPassthrough() {
9945
- return false;
10431
+ const envVal = process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH;
10432
+ return envVal === "1" || envVal === "true" || envVal === "yes";
9946
10433
  }
9947
10434
  };
9948
10435
 
@@ -10504,10 +10991,6 @@ function detectAdapter(c) {
10504
10991
  return defaultAdapter;
10505
10992
  }
10506
10993
 
10507
- // src/proxy/query.ts
10508
- import { join as join3 } from "node:path";
10509
- import { homedir as homedir2 } from "node:os";
10510
-
10511
10994
  // src/mcpTools.ts
10512
10995
  import { createSdkMcpServer as createSdkMcpServer2, tool } from "@anthropic-ai/claude-agent-sdk";
10513
10996
  import * as fs from "node:fs/promises";
@@ -11962,7 +12445,7 @@ var emitWarning = (msg, type, code, fn) => {
11962
12445
  var AC = globalThis.AbortController;
11963
12446
  var AS = globalThis.AbortSignal;
11964
12447
  if (typeof AC === "undefined") {
11965
- AS = class AbortSignal {
12448
+ AS = class AbortSignal2 {
11966
12449
  onabort;
11967
12450
  _onabort = [];
11968
12451
  reason;
@@ -16200,9 +16683,16 @@ function createOpencodeMcpServer() {
16200
16683
  }
16201
16684
 
16202
16685
  // src/proxy/query.ts
16686
+ function stripConfigDir(env2) {
16687
+ if (!("CLAUDE_CONFIG_DIR" in env2))
16688
+ return env2;
16689
+ const out = { ...env2 };
16690
+ delete out.CLAUDE_CONFIG_DIR;
16691
+ return out;
16692
+ }
16203
16693
  function computePassthroughMaxTurns(resumeSessionId, hasDeferredTools, advisorModel) {
16204
16694
  const hasResume = !!resumeSessionId;
16205
- const base = hasResume && hasDeferredTools ? 4 : hasResume || hasDeferredTools ? 3 : 2;
16695
+ const base = hasResume && hasDeferredTools ? 4 : 3;
16206
16696
  const advisorBump = advisorModel ? 3 : 0;
16207
16697
  return base + advisorBump;
16208
16698
  }
@@ -16304,10 +16794,9 @@ function buildQueryOptions(ctx) {
16304
16794
  } : {},
16305
16795
  ...onStderr ? { stderr: onStderr } : {},
16306
16796
  env: {
16307
- ...cleanEnv,
16797
+ ...sharedMemory ? stripConfigDir(cleanEnv) : cleanEnv,
16308
16798
  ENABLE_TOOL_SEARCH: hasDeferredTools ? "true" : "false",
16309
16799
  ...passthrough ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {},
16310
- ...sharedMemory ? { CLAUDE_CONFIG_DIR: join3(homedir2(), ".claude") } : {},
16311
16800
  ...process.getuid?.() === 0 ? { IS_SANDBOX: "1" } : {}
16312
16801
  },
16313
16802
  ...Object.keys(sdkAgents).length > 0 ? { agents: sdkAgents } : {},
@@ -16342,7 +16831,7 @@ function getAdapterTransforms(adapterName) {
16342
16831
 
16343
16832
  // src/proxy/plugins/loader.ts
16344
16833
  import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
16345
- import { join as join4, isAbsolute as isAbsolute2, extname } from "path";
16834
+ import { join as join3, isAbsolute as isAbsolute2, extname } from "path";
16346
16835
 
16347
16836
  // src/proxy/plugins/validation.ts
16348
16837
  var KNOWN_ADAPTERS = ["opencode", "crush", "droid", "pi", "forgecode", "passthrough"];
@@ -16426,7 +16915,7 @@ async function loadPlugins(pluginDir, configPath) {
16426
16915
  const loaded = [];
16427
16916
  const seenNames = new Set;
16428
16917
  for (const { filename, entry } of ordered) {
16429
- const filePath = isAbsolute2(filename) ? filename : join4(pluginDir, filename);
16918
+ const filePath = isAbsolute2(filename) ? filename : join3(pluginDir, filename);
16430
16919
  if (entry && !entry.enabled) {
16431
16920
  loaded.push({
16432
16921
  name: filename,
@@ -16802,12 +17291,12 @@ import {
16802
17291
  openSync,
16803
17292
  readFileSync as readFileSync3,
16804
17293
  renameSync,
16805
- statSync,
17294
+ statSync as statSync2,
16806
17295
  unlinkSync,
16807
17296
  writeFileSync
16808
17297
  } from "node:fs";
16809
- import { homedir as homedir3 } from "node:os";
16810
- import { join as join5 } from "node:path";
17298
+ import { homedir as homedir2 } from "node:os";
17299
+ import { join as join4 } from "node:path";
16811
17300
  var DEFAULT_MAX_STORED_SESSIONS = 1e4;
16812
17301
  var STALE_LOCK_THRESHOLD_MS = 30000;
16813
17302
  function getMaxStoredSessions() {
@@ -16831,7 +17320,7 @@ function acquireLock(lockPath) {
16831
17320
  return false;
16832
17321
  }
16833
17322
  try {
16834
- const stat = statSync(lockPath);
17323
+ const stat = statSync2(lockPath);
16835
17324
  if (Date.now() - stat.mtimeMs > STALE_LOCK_THRESHOLD_MS) {
16836
17325
  unlinkSync(lockPath);
16837
17326
  const fd = openSync(lockPath, "wx");
@@ -16858,11 +17347,11 @@ function getStorePath() {
16858
17347
  if (!existsSync4(dir)) {
16859
17348
  mkdirSync(dir, { recursive: true });
16860
17349
  }
16861
- return join5(dir, "sessions.json");
17350
+ return join4(dir, "sessions.json");
16862
17351
  }
16863
17352
  function getDefaultCacheDir() {
16864
- const newDir = join5(homedir3(), ".cache", "meridian");
16865
- const oldDir = join5(homedir3(), ".cache", "opencode-claude-max-proxy");
17353
+ const newDir = join4(homedir2(), ".cache", "meridian");
17354
+ const oldDir = join4(homedir2(), ".cache", "opencode-claude-max-proxy");
16866
17355
  if (existsSync4(newDir))
16867
17356
  return newDir;
16868
17357
  if (existsSync4(oldDir)) {
@@ -17380,8 +17869,8 @@ function createProxyServer(config = {}) {
17380
17869
  const sessionDiscoveredTools = new Map;
17381
17870
  const sessionToolCache = new Map;
17382
17871
  const sessionMcpCache = new LRUMap(getMaxSessionsLimit());
17383
- const pluginDir = finalConfig.pluginDir ?? join7(homedir5(), ".config", "meridian", "plugins");
17384
- const pluginConfigPath = finalConfig.pluginConfigPath ?? join7(homedir5(), ".config", "meridian", "plugins.json");
17872
+ const pluginDir = finalConfig.pluginDir ?? join6(homedir4(), ".config", "meridian", "plugins");
17873
+ const pluginConfigPath = finalConfig.pluginConfigPath ?? join6(homedir4(), ".config", "meridian", "plugins.json");
17385
17874
  let loadedPlugins = [];
17386
17875
  let pluginTransforms = [];
17387
17876
  const app = new Hono2;
@@ -17447,6 +17936,9 @@ function createProxyServer(config = {}) {
17447
17936
  if (!Array.isArray(body.messages)) {
17448
17937
  return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
17449
17938
  }
17939
+ if (body.messages.length === 0) {
17940
+ return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Cannot be empty — at least one message is required" } }, 400);
17941
+ }
17450
17942
  const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined);
17451
17943
  const authStatus = await getClaudeAuthStatusAsync(profile.id !== "default" ? profile.id : undefined, Object.keys(profile.env).length > 0 ? profile.env : undefined);
17452
17944
  const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
@@ -17455,10 +17947,11 @@ function createProxyServer(config = {}) {
17455
17947
  const workingDirectory = (process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR) || adapter.extractWorkingDirectory(body) || process.cwd();
17456
17948
  const clientWorkingDirectory = adapter.extractClientWorkingDirectory?.(body) || workingDirectory;
17457
17949
  const {
17458
- CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,
17459
17950
  ANTHROPIC_API_KEY: _dropApiKey,
17460
17951
  ANTHROPIC_BASE_URL: _dropBaseUrl,
17461
17952
  ANTHROPIC_AUTH_TOKEN: _dropAuthToken,
17953
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,
17954
+ CLAUDE_CODE_USE_POWERSHELL_TOOL: _dropUsePowershell,
17462
17955
  ...cleanEnv
17463
17956
  } = process.env;
17464
17957
  const sdkModelDefaults = resolveSdkModelDefaults();
@@ -17698,7 +18191,7 @@ function createProxyServer(config = {}) {
17698
18191
  });
17699
18192
  return {
17700
18193
  decision: "block",
17701
- reason: "Forwarding to client for execution"
18194
+ reason: "This tool call has been forwarded to the client for execution. " + "The result will be delivered in a future turn. " + "Do not retry, do not call additional tools, and do not generate further text — end your turn now."
17702
18195
  };
17703
18196
  }]
17704
18197
  }]
@@ -17717,7 +18210,7 @@ function createProxyServer(config = {}) {
17717
18210
  const upstreamStartAt = Date.now();
17718
18211
  let firstChunkAt;
17719
18212
  let currentSessionId;
17720
- const sdkUuidMap = cachedSession?.sdkMessageUuids ? [...cachedSession.sdkMessageUuids] : new Array(allMessages.length - 1).fill(null);
18213
+ const sdkUuidMap = cachedSession?.sdkMessageUuids ? [...cachedSession.sdkMessageUuids] : [];
17721
18214
  while (sdkUuidMap.length < allMessages.length)
17722
18215
  sdkUuidMap.push(null);
17723
18216
  claudeLog("upstream.start", { mode: "non_stream", model });
@@ -17762,7 +18255,7 @@ function createProxyServer(config = {}) {
17762
18255
  taskBudget,
17763
18256
  betas,
17764
18257
  settingSources,
17765
- codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
18258
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
17766
18259
  clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
17767
18260
  memory: sdkFeatures.memory,
17768
18261
  dreaming: sdkFeatures.dreaming,
@@ -17824,7 +18317,7 @@ function createProxyServer(config = {}) {
17824
18317
  taskBudget,
17825
18318
  betas,
17826
18319
  settingSources,
17827
- codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
18320
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
17828
18321
  clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
17829
18322
  memory: sdkFeatures.memory,
17830
18323
  dreaming: sdkFeatures.dreaming,
@@ -17935,6 +18428,12 @@ function createProxyServer(config = {}) {
17935
18428
  lastStopReason = message.message.stop_reason;
17936
18429
  }
17937
18430
  }
18431
+ if (message.type === "result") {
18432
+ const resultUsage = message.usage;
18433
+ if (resultUsage) {
18434
+ lastUsage = { ...lastUsage, ...resultUsage };
18435
+ }
18436
+ }
17938
18437
  }
17939
18438
  claudeLog("upstream.completed", {
17940
18439
  mode: "non_stream",
@@ -18114,11 +18613,12 @@ Subprocess stderr: ${stderrOutput}`;
18114
18613
  throw error;
18115
18614
  }
18116
18615
  };
18117
- const sdkUuidMap = cachedSession?.sdkMessageUuids ? [...cachedSession.sdkMessageUuids] : new Array(allMessages.length - 1).fill(null);
18616
+ const sdkUuidMap = cachedSession?.sdkMessageUuids ? [...cachedSession.sdkMessageUuids] : [];
18118
18617
  while (sdkUuidMap.length < allMessages.length)
18119
18618
  sdkUuidMap.push(null);
18120
18619
  let messageStartEmitted = false;
18121
18620
  let lastUsage;
18621
+ const streamedToolUseIds = new Set;
18122
18622
  try {
18123
18623
  let currentSessionId;
18124
18624
  const MAX_RATE_LIMIT_RETRIES = 2;
@@ -18156,7 +18656,7 @@ Subprocess stderr: ${stderrOutput}`;
18156
18656
  taskBudget,
18157
18657
  betas,
18158
18658
  settingSources,
18159
- codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
18659
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
18160
18660
  clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
18161
18661
  memory: sdkFeatures.memory,
18162
18662
  dreaming: sdkFeatures.dreaming,
@@ -18218,7 +18718,7 @@ Subprocess stderr: ${stderrOutput}`;
18218
18718
  taskBudget,
18219
18719
  betas,
18220
18720
  settingSources,
18221
- codeSystemPrompt: sdkFeatures.codeSystemPrompt ? true : undefined,
18721
+ codeSystemPrompt: sdkFeatures.codeSystemPrompt,
18222
18722
  clientSystemPrompt: sdkFeatures.clientSystemPrompt === false ? false : undefined,
18223
18723
  memory: sdkFeatures.memory,
18224
18724
  dreaming: sdkFeatures.dreaming,
@@ -18309,7 +18809,6 @@ Subprocess stderr: ${stderrOutput}`;
18309
18809
  const skipBlockIndices = new Set;
18310
18810
  const taskToolBlockIndices = new Set;
18311
18811
  const taskToolJsonBuffer = new Map;
18312
- const streamedToolUseIds = new Set;
18313
18812
  let nextClientBlockIndex = 0;
18314
18813
  const sdkToClientIndex = new Map;
18315
18814
  try {
@@ -18672,6 +19171,127 @@ Subprocess stderr: ${stderrOutput}`;
18672
19171
  });
18673
19172
  const streamErr = classifyError(errMsg);
18674
19173
  claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
19174
+ const sdkTerm = extractSdkTermination(errMsg);
19175
+ const canRecoverAsToolUse = sdkTerm.reason === "max_turns" && passthrough && capturedToolUses.length > 0 && messageStartEmitted;
19176
+ if (canRecoverAsToolUse) {
19177
+ diagnosticLog2.session(`${requestMeta.requestId} sdk_termination_recovered ${formatSdkTermination(sdkTerm, {
19178
+ model,
19179
+ requestSource,
19180
+ isResume,
19181
+ hasDeferredTools,
19182
+ sdkSessionId: resumeSessionId
19183
+ })} captured=${capturedToolUses.length}`, requestMeta.requestId);
19184
+ const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
19185
+ for (let i = 0;i < unseenToolUses.length; i++) {
19186
+ const tu = unseenToolUses[i];
19187
+ const blockIndex = eventsForwarded + i;
19188
+ safeEnqueue(encoder.encode(`event: content_block_start
19189
+ data: ${JSON.stringify({
19190
+ type: "content_block_start",
19191
+ index: blockIndex,
19192
+ content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
19193
+ })}
19194
+
19195
+ `), "recover_tool_block_start");
19196
+ safeEnqueue(encoder.encode(`event: content_block_delta
19197
+ data: ${JSON.stringify({
19198
+ type: "content_block_delta",
19199
+ index: blockIndex,
19200
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
19201
+ })}
19202
+
19203
+ `), "recover_tool_input");
19204
+ safeEnqueue(encoder.encode(`event: content_block_stop
19205
+ data: ${JSON.stringify({
19206
+ type: "content_block_stop",
19207
+ index: blockIndex
19208
+ })}
19209
+
19210
+ `), "recover_tool_block_stop");
19211
+ }
19212
+ safeEnqueue(encoder.encode(`event: message_delta
19213
+ data: ${JSON.stringify({
19214
+ type: "message_delta",
19215
+ delta: { stop_reason: "tool_use", stop_sequence: null },
19216
+ usage: { output_tokens: 0 }
19217
+ })}
19218
+
19219
+ `), "recover_message_delta");
19220
+ safeEnqueue(encoder.encode(`event: message_stop
19221
+ data: {"type":"message_stop"}
19222
+
19223
+ `), "recover_message_stop");
19224
+ const recoverTotalMs = Date.now() - requestStartAt;
19225
+ const recoverQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
19226
+ telemetryStore2.record({
19227
+ requestId: requestMeta.requestId,
19228
+ timestamp: Date.now(),
19229
+ adapter: adapter.name,
19230
+ requestSource,
19231
+ model,
19232
+ requestModel: body.model || undefined,
19233
+ mode: "stream",
19234
+ isResume,
19235
+ isPassthrough: passthrough,
19236
+ hasDeferredTools,
19237
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
19238
+ toolCount,
19239
+ lineageType,
19240
+ messageCount: allMessages.length,
19241
+ sdkSessionId: resumeSessionId,
19242
+ status: 200,
19243
+ queueWaitMs: recoverQueueWaitMs,
19244
+ proxyOverheadMs: upstreamStartAt - requestStartAt - recoverQueueWaitMs,
19245
+ ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
19246
+ upstreamDurationMs: Date.now() - upstreamStartAt,
19247
+ totalDurationMs: recoverTotalMs,
19248
+ contentBlocks: eventsForwarded + unseenToolUses.length,
19249
+ textEvents: textEventsForwarded,
19250
+ error: null
19251
+ });
19252
+ if (!streamClosed) {
19253
+ try {
19254
+ controller.close();
19255
+ } catch {}
19256
+ streamClosed = true;
19257
+ }
19258
+ return;
19259
+ }
19260
+ diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
19261
+ model,
19262
+ requestSource,
19263
+ isResume,
19264
+ hasDeferredTools,
19265
+ sdkSessionId: resumeSessionId
19266
+ })}`, requestMeta.requestId);
19267
+ const streamErrTotalMs = Date.now() - requestStartAt;
19268
+ const streamErrQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
19269
+ telemetryStore2.record({
19270
+ requestId: requestMeta.requestId,
19271
+ timestamp: Date.now(),
19272
+ adapter: adapter.name,
19273
+ requestSource,
19274
+ model,
19275
+ requestModel: body.model || undefined,
19276
+ mode: "stream",
19277
+ isResume,
19278
+ isPassthrough: passthrough,
19279
+ hasDeferredTools,
19280
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
19281
+ toolCount,
19282
+ lineageType,
19283
+ messageCount: allMessages.length,
19284
+ sdkSessionId: resumeSessionId,
19285
+ status: streamErr.status,
19286
+ queueWaitMs: streamErrQueueWaitMs,
19287
+ proxyOverheadMs: upstreamStartAt - requestStartAt - streamErrQueueWaitMs,
19288
+ ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
19289
+ upstreamDurationMs: Date.now() - upstreamStartAt,
19290
+ totalDurationMs: streamErrTotalMs,
19291
+ contentBlocks: eventsForwarded,
19292
+ textEvents: textEventsForwarded,
19293
+ error: streamErr.type
19294
+ });
18675
19295
  if (messageStartEmitted) {
18676
19296
  safeEnqueue(encoder.encode(`event: message_delta
18677
19297
  data: ${JSON.stringify({
@@ -18719,6 +19339,10 @@ data: ${JSON.stringify({
18719
19339
  });
18720
19340
  const classified = classifyError(errMsg);
18721
19341
  claudeLog("proxy.error", { error: errMsg, classified: classified.type });
19342
+ const sdkTerm = extractSdkTermination(errMsg);
19343
+ diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
19344
+ requestSource: c.req.header("x-meridian-source")?.slice(0, 64) || undefined
19345
+ })}`, requestMeta.requestId);
18722
19346
  const errorQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
18723
19347
  telemetryStore2.record({
18724
19348
  requestId: requestMeta.requestId,
@@ -18860,7 +19484,7 @@ data: ${JSON.stringify({
18860
19484
  });
18861
19485
  });
18862
19486
  app.get("/profiles", async (c) => {
18863
- const { profilePageHtml } = await import("./profilePage-77z05e0r.js");
19487
+ const { profilePageHtml } = await import("./profilePage-k0faye28.js");
18864
19488
  return c.html(profilePageHtml);
18865
19489
  });
18866
19490
  app.post("/profiles/active", async (c) => {
@@ -18952,9 +19576,14 @@ data: ${JSON.stringify({
18952
19576
  const completionId = `chatcmpl-${randomUUID()}`;
18953
19577
  const created = Math.floor(Date.now() / 1000);
18954
19578
  const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : "claude-sonnet-4-6";
19579
+ const { getFeaturesForAdapter: getFeaturesForAdapter2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
19580
+ const adapter = detectAdapter(c);
19581
+ const sdkFeatures = getFeaturesForAdapter2(adapter.name);
18955
19582
  if (!anthropicBody.stream) {
18956
19583
  const anthropicRes = await internalRes.json();
18957
- return c.json(translateAnthropicToOpenAi(anthropicRes, completionId, model, created));
19584
+ return c.json(translateAnthropicToOpenAi(anthropicRes, completionId, model, created, {
19585
+ thinkingPassthrough: sdkFeatures.thinkingPassthrough
19586
+ }));
18958
19587
  }
18959
19588
  const encoder = new TextEncoder;
18960
19589
  const readable = new ReadableStream({
@@ -18967,6 +19596,7 @@ data: ${JSON.stringify({
18967
19596
  const decoder = new TextDecoder;
18968
19597
  let buffer = "";
18969
19598
  let streamError = null;
19599
+ const translate = createSseTranslator({ completionId, model, created, thinkingPassthrough: sdkFeatures.thinkingPassthrough });
18970
19600
  try {
18971
19601
  while (true) {
18972
19602
  const { done, value } = await reader.read();
@@ -18990,7 +19620,7 @@ data: ${JSON.stringify({
18990
19620
  }
18991
19621
  if (typeof event.type !== "string")
18992
19622
  continue;
18993
- const chunk = translateAnthropicSseEvent(event, completionId, model, created);
19623
+ const chunk = translate(event);
18994
19624
  if (chunk)
18995
19625
  controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
18996
19626
 
@@ -19021,11 +19651,21 @@ data: ${JSON.stringify({
19021
19651
  const isMax = authStatus?.subscriptionType === "max";
19022
19652
  return c.json({ object: "list", data: buildModelList(isMax) });
19023
19653
  });
19024
- app.get("/v1/usage/quota", (c) => {
19025
- const entries = rateLimitStore.getAll().filter((entry) => entry.rateLimitType !== undefined);
19026
- return c.json({
19027
- buckets: entries.map((entry) => ({
19028
- type: entry.rateLimitType,
19654
+ app.get("/v1/usage/quota", async (c) => {
19655
+ const sdkEntries = rateLimitStore.getAll().filter((entry) => entry.rateLimitType !== undefined);
19656
+ const requestedProfile = c.req.query("profile");
19657
+ const profilesList = getEffectiveProfiles(finalConfig.profiles);
19658
+ const targetProfileId = requestedProfile || getActiveProfileId() || finalConfig.defaultProfile || profilesList[0]?.id || null;
19659
+ const targetProfile = targetProfileId ? profilesList.find((p) => p.id === targetProfileId) : undefined;
19660
+ const oauth = await fetchOAuthUsage({
19661
+ profileId: targetProfileId ?? undefined,
19662
+ claudeConfigDir: targetProfile?.claudeConfigDir
19663
+ });
19664
+ const byType = new Map;
19665
+ for (const entry of sdkEntries) {
19666
+ const type = entry.rateLimitType;
19667
+ byType.set(type, {
19668
+ type,
19029
19669
  status: entry.status,
19030
19670
  utilization: entry.utilization ?? null,
19031
19671
  resetsAt: entry.resetsAt ?? null,
@@ -19035,7 +19675,85 @@ data: ${JSON.stringify({
19035
19675
  overageDisabledReason: entry.overageDisabledReason ?? null,
19036
19676
  surpassedThreshold: entry.surpassedThreshold ?? null,
19037
19677
  observedAt: entry.observedAt
19038
- })),
19678
+ });
19679
+ }
19680
+ if (oauth) {
19681
+ for (const w of oauth.windows) {
19682
+ const existing = byType.get(w.type);
19683
+ const status = (w.utilization ?? 0) >= 1 ? "rejected" : (w.utilization ?? 0) >= 0.8 ? "allowed_warning" : "allowed";
19684
+ byType.set(w.type, {
19685
+ type: w.type,
19686
+ status: existing?.status === "rejected" ? "rejected" : status,
19687
+ utilization: w.utilization ?? existing?.utilization ?? null,
19688
+ resetsAt: w.resetsAt ?? existing?.resetsAt ?? null,
19689
+ isUsingOverage: existing?.isUsingOverage ?? false,
19690
+ overageStatus: existing?.overageStatus ?? null,
19691
+ overageResetsAt: existing?.overageResetsAt ?? null,
19692
+ overageDisabledReason: existing?.overageDisabledReason ?? null,
19693
+ surpassedThreshold: existing?.surpassedThreshold ?? null,
19694
+ observedAt: oauth.fetchedAt
19695
+ });
19696
+ }
19697
+ }
19698
+ return c.json({
19699
+ profile: targetProfileId ?? null,
19700
+ buckets: Array.from(byType.values()),
19701
+ extraUsage: oauth?.extraUsage ?? null,
19702
+ sources: {
19703
+ oauth: oauth ? { fetchedAt: oauth.fetchedAt } : null,
19704
+ sdk: { entryCount: sdkEntries.length }
19705
+ },
19706
+ asOf: Date.now()
19707
+ });
19708
+ });
19709
+ app.get("/v1/usage/quota/all", async (c) => {
19710
+ const profilesList = getEffectiveProfiles(finalConfig.profiles);
19711
+ const activeId = getActiveProfileId() || finalConfig.defaultProfile || profilesList[0]?.id || null;
19712
+ if (profilesList.length === 0) {
19713
+ const oauth = await fetchOAuthUsage({});
19714
+ return c.json({
19715
+ profiles: [{
19716
+ id: "default",
19717
+ isActive: true,
19718
+ windows: oauth?.windows ?? [],
19719
+ extraUsage: oauth?.extraUsage ?? null,
19720
+ fetchedAt: oauth?.fetchedAt ?? null,
19721
+ error: oauth ? null : "no_token"
19722
+ }],
19723
+ activeProfile: "default",
19724
+ asOf: Date.now()
19725
+ });
19726
+ }
19727
+ const results = await Promise.all(profilesList.map(async (p) => {
19728
+ const type = p.type ?? "claude-max";
19729
+ if (type !== "claude-max") {
19730
+ return {
19731
+ id: p.id,
19732
+ isActive: p.id === activeId,
19733
+ type,
19734
+ windows: [],
19735
+ extraUsage: null,
19736
+ fetchedAt: null,
19737
+ error: "not_oauth"
19738
+ };
19739
+ }
19740
+ const oauth = await fetchOAuthUsage({
19741
+ profileId: p.id,
19742
+ claudeConfigDir: p.claudeConfigDir
19743
+ });
19744
+ return {
19745
+ id: p.id,
19746
+ isActive: p.id === activeId,
19747
+ type,
19748
+ windows: oauth?.windows ?? [],
19749
+ extraUsage: oauth?.extraUsage ?? null,
19750
+ fetchedAt: oauth?.fetchedAt ?? null,
19751
+ error: oauth ? null : "no_token"
19752
+ };
19753
+ }));
19754
+ return c.json({
19755
+ profiles: results,
19756
+ activeProfile: activeId,
19039
19757
  asOf: Date.now()
19040
19758
  });
19041
19759
  });