@rynfar/meridian 1.39.1 → 1.41.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.
@@ -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-0eky480v.js";
31
32
  import {
32
33
  __commonJS,
33
34
  __esm,
@@ -3706,6 +3707,144 @@ import { homedir as homedir5 } from "node:os";
3706
3707
  import { join as join7 } from "node:path";
3707
3708
  import { query } from "@anthropic-ai/claude-agent-sdk";
3708
3709
 
3710
+ // src/proxy/rateLimitStore.ts
3711
+ class RateLimitStore {
3712
+ entries = new Map;
3713
+ record(info) {
3714
+ if (!info || typeof info !== "object")
3715
+ return;
3716
+ const key = info.rateLimitType ?? "default";
3717
+ this.entries.set(key, { ...info, observedAt: Date.now() });
3718
+ }
3719
+ getAll() {
3720
+ return Array.from(this.entries.values()).sort((a, b) => b.observedAt - a.observedAt);
3721
+ }
3722
+ get(key) {
3723
+ return this.entries.get(key);
3724
+ }
3725
+ get size() {
3726
+ return this.entries.size;
3727
+ }
3728
+ clear() {
3729
+ this.entries.clear();
3730
+ }
3731
+ }
3732
+ var rateLimitStore = new RateLimitStore;
3733
+
3734
+ // src/proxy/oauthUsage.ts
3735
+ var OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
3736
+ var OAUTH_BETA_HEADER = "oauth-2025-04-20";
3737
+ var CACHE_TTL_MS_DEFAULT = 30000;
3738
+ var cacheByProfile = new Map;
3739
+ var inflightByProfile = new Map;
3740
+ var DEFAULT_KEY = "__default__";
3741
+ var WINDOW_TYPES = [
3742
+ "five_hour",
3743
+ "seven_day",
3744
+ "seven_day_opus",
3745
+ "seven_day_sonnet",
3746
+ "seven_day_oauth_apps",
3747
+ "seven_day_cowork",
3748
+ "seven_day_omelette"
3749
+ ];
3750
+ function parseIsoToMs(raw2) {
3751
+ if (!raw2)
3752
+ return null;
3753
+ const ms = Date.parse(raw2);
3754
+ return Number.isFinite(ms) ? ms : null;
3755
+ }
3756
+ function normalizeUtilization(raw2) {
3757
+ if (typeof raw2 !== "number" || !Number.isFinite(raw2))
3758
+ return null;
3759
+ return Math.max(0, raw2 / 100);
3760
+ }
3761
+ function buildSnapshot(raw2) {
3762
+ const windows = [];
3763
+ for (const key of WINDOW_TYPES) {
3764
+ const w = raw2[key];
3765
+ if (!w)
3766
+ continue;
3767
+ const utilization = normalizeUtilization(w.utilization);
3768
+ const resetsAt = parseIsoToMs(w.resets_at);
3769
+ if (utilization === null && resetsAt === null)
3770
+ continue;
3771
+ windows.push({ type: key, utilization, resetsAt });
3772
+ }
3773
+ const extra = raw2.extra_usage;
3774
+ const extraUsage = extra ? {
3775
+ isEnabled: !!extra.is_enabled,
3776
+ monthlyLimit: extra.monthly_limit ?? 0,
3777
+ usedCredits: extra.used_credits ?? 0,
3778
+ utilization: normalizeUtilization(extra.utilization ?? null),
3779
+ currency: extra.currency ?? "USD"
3780
+ } : null;
3781
+ return { windows, extraUsage, fetchedAt: Date.now() };
3782
+ }
3783
+ async function readAccessToken(store) {
3784
+ const creds = await store.read();
3785
+ return creds?.claudeAiOauth?.accessToken ?? null;
3786
+ }
3787
+ async function callAnthropic(token, signal) {
3788
+ const res = await fetch(OAUTH_USAGE_URL, {
3789
+ headers: {
3790
+ Authorization: `Bearer ${token}`,
3791
+ "anthropic-beta": OAUTH_BETA_HEADER,
3792
+ Accept: "application/json"
3793
+ },
3794
+ signal: signal ?? AbortSignal.timeout(1e4)
3795
+ });
3796
+ if (!res.ok)
3797
+ return { __status: res.status };
3798
+ return await res.json();
3799
+ }
3800
+ async function fetchOAuthUsage(opts) {
3801
+ const ttl = opts?.ttlMs ?? CACHE_TTL_MS_DEFAULT;
3802
+ const cacheKey2 = opts?.profileId ?? DEFAULT_KEY;
3803
+ if (!opts?.force) {
3804
+ const cached = cacheByProfile.get(cacheKey2);
3805
+ if (cached && Date.now() - cached.fetchedAt < ttl)
3806
+ return cached;
3807
+ }
3808
+ const existing = inflightByProfile.get(cacheKey2);
3809
+ if (existing)
3810
+ return existing;
3811
+ const store = opts?.store ?? createPlatformCredentialStore({ claudeConfigDir: opts?.claudeConfigDir });
3812
+ const promise = (async () => {
3813
+ try {
3814
+ const token = await readAccessToken(store);
3815
+ if (!token)
3816
+ return null;
3817
+ let result = await callAnthropic(token);
3818
+ if ("__status" in result && result.__status === 401) {
3819
+ claudeLog("oauth_usage.token_refresh_attempt", { profile: cacheKey2 });
3820
+ const refreshed = await refreshOAuthToken(store);
3821
+ if (!refreshed) {
3822
+ claudeLog("oauth_usage.refresh_failed", { profile: cacheKey2 });
3823
+ return null;
3824
+ }
3825
+ const newToken = await readAccessToken(store);
3826
+ if (!newToken)
3827
+ return null;
3828
+ result = await callAnthropic(newToken);
3829
+ }
3830
+ if ("__status" in result) {
3831
+ claudeLog("oauth_usage.upstream_error", { profile: cacheKey2, status: result.__status });
3832
+ return null;
3833
+ }
3834
+ const snapshot = buildSnapshot(result);
3835
+ cacheByProfile.set(cacheKey2, snapshot);
3836
+ return snapshot;
3837
+ } catch (err) {
3838
+ claudeLog("oauth_usage.fetch_failed", { profile: cacheKey2, error: err instanceof Error ? err.message : String(err) });
3839
+ return null;
3840
+ } finally {
3841
+ inflightByProfile.delete(cacheKey2);
3842
+ }
3843
+ })();
3844
+ inflightByProfile.set(cacheKey2, promise);
3845
+ return promise;
3846
+ }
3847
+
3709
3848
  // src/proxy/types.ts
3710
3849
  var DEFAULT_PROXY_CONFIG = {
3711
3850
  port: 3456,
@@ -7969,13 +8108,20 @@ function ensureDefaultAgents(agents, mcpToolNames) {
7969
8108
  }
7970
8109
  }
7971
8110
  }
8111
+ function cloneAgentDefinition(def) {
8112
+ return {
8113
+ ...def,
8114
+ ...def.tools ? { tools: [...def.tools] } : {},
8115
+ ...def.disallowedTools ? { disallowedTools: [...def.disallowedTools] } : {}
8116
+ };
8117
+ }
7972
8118
  function addCaseVariants(agents) {
7973
8119
  const baseNames = Object.keys(agents);
7974
8120
  for (const name of baseNames) {
7975
8121
  const def = agents[name];
7976
8122
  const titleCase = name.replace(/(^|-)(\w)/g, (_m, sep, ch) => sep + ch.toUpperCase());
7977
8123
  if (titleCase !== name && !agents[titleCase]) {
7978
- agents[titleCase] = def;
8124
+ agents[titleCase] = cloneAgentDefinition(def);
7979
8125
  }
7980
8126
  }
7981
8127
  const ALIASES = {
@@ -7984,10 +8130,50 @@ function addCaseVariants(agents) {
7984
8130
  };
7985
8131
  for (const [alias, target] of Object.entries(ALIASES)) {
7986
8132
  if (!agents[alias] && agents[target]) {
7987
- agents[alias] = agents[target];
8133
+ agents[alias] = cloneAgentDefinition(agents[target]);
7988
8134
  }
7989
8135
  }
7990
8136
  }
8137
+ function getNested(obj, ...keys) {
8138
+ let cur = obj;
8139
+ for (const key of keys) {
8140
+ if (cur === null || typeof cur !== "object")
8141
+ return;
8142
+ cur = cur[key];
8143
+ }
8144
+ return cur;
8145
+ }
8146
+ function parseAgentNamesFromSchema(taskTool) {
8147
+ const enumNames = getNested(taskTool, "input_schema", "properties", "subagent_type", "enum");
8148
+ if (!Array.isArray(enumNames))
8149
+ return [];
8150
+ return enumNames.filter((n) => typeof n === "string");
8151
+ }
8152
+ function buildAgentDefinitionsFromTool(taskTool, mcpToolNames) {
8153
+ const rawDescription = getNested(taskTool, "description");
8154
+ const description = typeof rawDescription === "string" ? rawDescription : "";
8155
+ const fromDescription = buildAgentDefinitions(description, mcpToolNames);
8156
+ if (Object.keys(fromDescription).length > 0)
8157
+ return fromDescription;
8158
+ const names = parseAgentNamesFromSchema(taskTool);
8159
+ if (names.length === 0)
8160
+ return {};
8161
+ const agents = {};
8162
+ for (const name of names) {
8163
+ if (agents[name])
8164
+ continue;
8165
+ const desc = `User-defined agent: ${name}`;
8166
+ agents[name] = {
8167
+ description: desc,
8168
+ prompt: buildAgentPrompt(name, desc),
8169
+ model: "inherit",
8170
+ ...mcpToolNames?.length ? { tools: [...mcpToolNames] } : {}
8171
+ };
8172
+ }
8173
+ ensureDefaultAgents(agents, mcpToolNames);
8174
+ addCaseVariants(agents);
8175
+ return agents;
8176
+ }
7991
8177
  function buildAgentPrompt(name, description) {
7992
8178
  return `You are the "${name}" agent. ${description}
7993
8179
 
@@ -8364,8 +8550,6 @@ let timer;
8364
8550
  let activeTab = 'requests';
8365
8551
  let activeLogFilter = 'all';
8366
8552
 
8367
-
8368
-
8369
8553
  function ms(v) {
8370
8554
  if (v == null) return '—';
8371
8555
  if (v < 1000) return v + 'ms';
@@ -8967,6 +9151,82 @@ function isExtraUsageRequiredError(errMsg) {
8967
9151
  const lower = errMsg.toLowerCase();
8968
9152
  return lower.includes("extra usage") && lower.includes("1m");
8969
9153
  }
9154
+ var STDERR_TAIL_MAX = 500;
9155
+ var RAW_TAIL_MAX = 300;
9156
+ function extractStderrTail(errMsg) {
9157
+ const marker = "Subprocess stderr:";
9158
+ const idx = errMsg.indexOf(marker);
9159
+ if (idx < 0)
9160
+ return;
9161
+ const tail = errMsg.slice(idx + marker.length).trim();
9162
+ if (!tail)
9163
+ return;
9164
+ return tail.length > STDERR_TAIL_MAX ? tail.slice(0, STDERR_TAIL_MAX) : tail;
9165
+ }
9166
+ function makeRawTail(errMsg) {
9167
+ const marker = "Subprocess stderr:";
9168
+ const idx = errMsg.indexOf(marker);
9169
+ const head = (idx >= 0 ? errMsg.slice(0, idx) : errMsg).trim();
9170
+ if (!head)
9171
+ return;
9172
+ return head.length > RAW_TAIL_MAX ? head.slice(0, RAW_TAIL_MAX) : head;
9173
+ }
9174
+ function extractSdkTermination(errMsg) {
9175
+ const stderrTail = extractStderrTail(errMsg);
9176
+ const haystack = `${errMsg}
9177
+ ${stderrTail ?? ""}`;
9178
+ const lower = haystack.toLowerCase();
9179
+ if (lower.includes("reached maximum number of turns")) {
9180
+ const m = haystack.match(/Reached maximum number of turns \((\d+)\)/i);
9181
+ return {
9182
+ reason: "max_turns",
9183
+ ...m ? { turns: Number(m[1]) } : {},
9184
+ ...stderrTail ? { stderrTail } : {}
9185
+ };
9186
+ }
9187
+ if (lower.includes("exited with code") || lower.includes("process exited")) {
9188
+ const m = haystack.match(/exited with code (\d+)/i);
9189
+ return {
9190
+ reason: "process_exit",
9191
+ ...m ? { exitCode: Number(m[1]) } : {},
9192
+ ...stderrTail ? { stderrTail } : {}
9193
+ };
9194
+ }
9195
+ if (lower.includes("aborterror") || /\baborted\b/.test(lower)) {
9196
+ return {
9197
+ reason: "aborted",
9198
+ ...stderrTail ? { stderrTail } : {}
9199
+ };
9200
+ }
9201
+ const rawTail = makeRawTail(errMsg);
9202
+ return {
9203
+ reason: "unknown",
9204
+ ...stderrTail ? { stderrTail } : {},
9205
+ ...rawTail ? { rawTail } : {}
9206
+ };
9207
+ }
9208
+ function formatSdkTermination(t, ctx) {
9209
+ const parts = [`reason=${t.reason}`];
9210
+ if (t.turns !== undefined)
9211
+ parts.push(`turns=${t.turns}`);
9212
+ if (t.exitCode !== undefined)
9213
+ parts.push(`exit=${t.exitCode}`);
9214
+ if (ctx.model)
9215
+ parts.push(`model=${ctx.model}`);
9216
+ if (ctx.requestSource)
9217
+ parts.push(`source=${ctx.requestSource}`);
9218
+ if (ctx.isResume !== undefined)
9219
+ parts.push(`resume=${ctx.isResume}`);
9220
+ if (ctx.hasDeferredTools !== undefined)
9221
+ parts.push(`deferred=${ctx.hasDeferredTools}`);
9222
+ if (ctx.sdkSessionId)
9223
+ parts.push(`session=${ctx.sdkSessionId.slice(0, 8)}`);
9224
+ if (t.rawTail)
9225
+ parts.push(`raw=${JSON.stringify(t.rawTail)}`);
9226
+ if (t.stderrTail)
9227
+ parts.push(`stderr=${JSON.stringify(t.stderrTail)}`);
9228
+ return `sdk_termination ${parts.join(" ")}`;
9229
+ }
8970
9230
 
8971
9231
  // src/proxy/models.ts
8972
9232
  import { exec as execCallback } from "child_process";
@@ -9192,7 +9452,7 @@ function parseDataUrlImage(url) {
9192
9452
  }
9193
9453
  function translateOpenAiContentToAnthropic(content) {
9194
9454
  if (typeof content === "string")
9195
- return content;
9455
+ return [{ type: "text", text: content }];
9196
9456
  const parts = [];
9197
9457
  for (const part of content) {
9198
9458
  if (part.type === "text" && typeof part.text === "string") {
@@ -9211,9 +9471,6 @@ function translateOpenAiContentToAnthropic(content) {
9211
9471
  parts.push({ type: "text", text: "[Unsupported image_url omitted: only data URLs are currently supported]" });
9212
9472
  }
9213
9473
  }
9214
- if (parts.length === 1 && parts[0]?.type === "text") {
9215
- return parts[0].text;
9216
- }
9217
9474
  return parts;
9218
9475
  }
9219
9476
  function summarizeAnthropicContent(content) {
@@ -9222,6 +9479,32 @@ function summarizeAnthropicContent(content) {
9222
9479
  return content.map((part) => {
9223
9480
  if (part.type === "text")
9224
9481
  return part.text;
9482
+ if (part.type === "thinking")
9483
+ return `
9484
+ <think>
9485
+ ` + part.thinking + `
9486
+ </think>
9487
+ `;
9488
+ if (part.type === "tool_use")
9489
+ return `
9490
+ <tool_call name="` + part.name + `">
9491
+ ` + JSON.stringify(part.input) + `
9492
+ </tool_call>
9493
+ `;
9494
+ if (part.type === "tool_result") {
9495
+ if (typeof part.content === "string")
9496
+ return `
9497
+ <tool_result>
9498
+ ` + part.content + `
9499
+ </tool_result>
9500
+ `;
9501
+ else
9502
+ return part.content.map((c) => c.type === "text" ? `
9503
+ <tool_result>
9504
+ ${c.text}
9505
+ </tool_result>
9506
+ ` : "").join("");
9507
+ }
9225
9508
  if (part.type === "image")
9226
9509
  return "[Image attached]";
9227
9510
  return "";
@@ -9233,18 +9516,89 @@ function translateOpenAiToAnthropic(body) {
9233
9516
  return null;
9234
9517
  const systemParts = [];
9235
9518
  const turns = [];
9519
+ const tools = [];
9236
9520
  for (const msg of messages) {
9237
9521
  const text = extractOpenAiContent(msg.content ?? "");
9238
9522
  if (msg.role === "system") {
9239
9523
  if (text)
9240
9524
  systemParts.push(text);
9525
+ } else if (msg.role === "tool") {
9526
+ turns.push({
9527
+ role: "user",
9528
+ content: [{
9529
+ type: "tool_result",
9530
+ tool_use_id: msg.tool_call_id ?? "",
9531
+ content: translateOpenAiContentToAnthropic(msg.content ?? "")
9532
+ }]
9533
+ });
9534
+ } else if (msg.role === "assistant") {
9535
+ const msgContent = translateOpenAiContentToAnthropic(msg.content ?? "");
9536
+ const content = [];
9537
+ const toolCalls = msg.tool_calls ?? null;
9538
+ const firstBlock = msgContent[0];
9539
+ const endOfThink = firstBlock?.type === "text" && firstBlock.text.startsWith("<think>") ? firstBlock.text.indexOf("</think>") : -1;
9540
+ if (firstBlock?.type === "text" && firstBlock.text.startsWith("<think>") && endOfThink !== -1) {
9541
+ const thinking = firstBlock.text.substring("<think>".length, endOfThink);
9542
+ let textStart = endOfThink + "</think>".length;
9543
+ if (firstBlock.text[textStart] === `
9544
+ `)
9545
+ textStart += 1;
9546
+ const text2 = firstBlock.text.substring(textStart);
9547
+ content.push({ type: "thinking", thinking });
9548
+ if (text2.length)
9549
+ content.push({ type: "text", text: text2 });
9550
+ if (msgContent.length > 1)
9551
+ content.push(...msgContent.slice(1));
9552
+ } else {
9553
+ content.push(...msgContent);
9554
+ }
9555
+ if (toolCalls) {
9556
+ const calls = toolCalls.filter((call) => call.type === "function").map((call) => {
9557
+ let input;
9558
+ try {
9559
+ const parsed = JSON.parse(call.function.arguments);
9560
+ input = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { __raw: call.function.arguments };
9561
+ } catch {
9562
+ input = { __raw: call.function.arguments };
9563
+ }
9564
+ return {
9565
+ type: "tool_use",
9566
+ id: call.id,
9567
+ name: call.function.name,
9568
+ input
9569
+ };
9570
+ });
9571
+ content.push(...calls);
9572
+ }
9573
+ let finalContent = content;
9574
+ if (content.length === 1 && content[0]?.type === "text") {
9575
+ finalContent = content[0].text;
9576
+ }
9577
+ turns.push({
9578
+ role: "assistant",
9579
+ content: finalContent
9580
+ });
9241
9581
  } else {
9242
9582
  turns.push({
9243
- role: msg.role === "assistant" ? "assistant" : "user",
9583
+ role: "user",
9244
9584
  content: translateOpenAiContentToAnthropic(msg.content ?? "")
9245
9585
  });
9246
9586
  }
9247
9587
  }
9588
+ const reqTools = body.tools ?? [];
9589
+ for (const reqTool of reqTools) {
9590
+ if (reqTool.type === "function") {
9591
+ const tool = reqTool.function;
9592
+ tools.push({
9593
+ name: tool.name,
9594
+ description: tool.description ?? "",
9595
+ input_schema: tool.parameters,
9596
+ strict: tool.strict
9597
+ });
9598
+ } else {
9599
+ return null;
9600
+ }
9601
+ }
9248
9602
  let systemPrompt = systemParts.join(`
9249
9603
  `);
9250
9604
  let messagesToSend = turns;
@@ -9265,6 +9619,7 @@ ${historyBlock}` : historyBlock;
9265
9619
  model: body.model ?? "claude-sonnet-4-6",
9266
9620
  messages: messagesToSend,
9267
9621
  max_tokens: body.max_tokens ?? body.max_completion_tokens ?? 8192,
9622
+ tools,
9268
9623
  stream: body.stream ?? false
9269
9624
  };
9270
9625
  if (systemPrompt)
@@ -9278,10 +9633,23 @@ ${historyBlock}` : historyBlock;
9278
9633
  function toFinishReason(stopReason) {
9279
9634
  if (stopReason === "max_tokens")
9280
9635
  return "length";
9636
+ else if (stopReason === "tool_use")
9637
+ return "tool_calls";
9281
9638
  return "stop";
9282
9639
  }
9283
- function translateAnthropicToOpenAi(response, completionId, model, created) {
9284
- const content = (response.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
9640
+ function translateAnthropicToOpenAi(response, completionId, model, created, options) {
9641
+ const contentBlocks = response.content ?? [];
9642
+ const content = contentBlocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
9643
+ const toolCalls = contentBlocks.filter((b) => b.type === "tool_use").map((b) => ({
9644
+ type: "function",
9645
+ id: b.id,
9646
+ function: {
9647
+ name: b.name,
9648
+ arguments: JSON.stringify(b.input)
9649
+ }
9650
+ }));
9651
+ const thinkingPassthrough = options?.thinkingPassthrough;
9652
+ const thinking = thinkingPassthrough !== false ? contentBlocks.filter((b) => b.type === "thinking").map((b) => b.thinking).join("") : "";
9285
9653
  const promptTokens = response.usage?.input_tokens ?? 0;
9286
9654
  const completionTokens = response.usage?.output_tokens ?? 0;
9287
9655
  return {
@@ -9291,7 +9659,12 @@ function translateAnthropicToOpenAi(response, completionId, model, created) {
9291
9659
  model,
9292
9660
  choices: [{
9293
9661
  index: 0,
9294
- message: { role: "assistant", content },
9662
+ message: {
9663
+ role: "assistant",
9664
+ content: content || null,
9665
+ reasoning_content: thinking.length ? thinking : undefined,
9666
+ tool_calls: toolCalls.length ? toolCalls : undefined
9667
+ },
9295
9668
  finish_reason: toFinishReason(response.stop_reason)
9296
9669
  }],
9297
9670
  usage: {
@@ -9301,7 +9674,16 @@ function translateAnthropicToOpenAi(response, completionId, model, created) {
9301
9674
  }
9302
9675
  };
9303
9676
  }
9304
- function translateAnthropicSseEvent(event, completionId, model, created) {
9677
+ function createSseTranslator(ctx) {
9678
+ let toolCallIndex = -1;
9679
+ return (event) => {
9680
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use" && typeof event.content_block.name === "string") {
9681
+ toolCallIndex++;
9682
+ }
9683
+ return translateAnthropicSseEvent(event, ctx.completionId, ctx.model, ctx.created, toolCallIndex, ctx.thinkingPassthrough);
9684
+ };
9685
+ }
9686
+ function translateAnthropicSseEvent(event, completionId, model, created, toolCallNum, thinkingPassthrough) {
9305
9687
  if (event.type === "message_start") {
9306
9688
  return {
9307
9689
  id: completionId,
@@ -9320,6 +9702,69 @@ function translateAnthropicSseEvent(event, completionId, model, created) {
9320
9702
  choices: [{ index: 0, delta: { content: event.delta.text }, finish_reason: null }]
9321
9703
  };
9322
9704
  }
9705
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use" && typeof event.content_block?.name === "string") {
9706
+ return {
9707
+ id: completionId,
9708
+ object: "chat.completion.chunk",
9709
+ created,
9710
+ model,
9711
+ choices: [{
9712
+ index: 0,
9713
+ delta: {
9714
+ tool_calls: [{
9715
+ type: "function",
9716
+ index: toolCallNum,
9717
+ id: event.content_block?.id,
9718
+ function: {
9719
+ name: event.content_block.name,
9720
+ arguments: ""
9721
+ }
9722
+ }]
9723
+ },
9724
+ finish_reason: null
9725
+ }]
9726
+ };
9727
+ }
9728
+ if (event.type === "content_block_delta" && event.delta?.type === "input_json_delta" && typeof event.delta?.partial_json === "string") {
9729
+ return {
9730
+ id: completionId,
9731
+ object: "chat.completion.chunk",
9732
+ created,
9733
+ model,
9734
+ choices: [{
9735
+ index: 0,
9736
+ delta: {
9737
+ tool_calls: [{
9738
+ index: toolCallNum,
9739
+ function: {
9740
+ arguments: event.delta.partial_json
9741
+ }
9742
+ }]
9743
+ },
9744
+ finish_reason: null
9745
+ }]
9746
+ };
9747
+ }
9748
+ if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta") {
9749
+ if (thinkingPassthrough === false) {
9750
+ return null;
9751
+ }
9752
+ if (typeof event.delta?.thinking === "string") {
9753
+ return {
9754
+ id: completionId,
9755
+ object: "chat.completion.chunk",
9756
+ created,
9757
+ model,
9758
+ choices: [{
9759
+ index: 0,
9760
+ delta: {
9761
+ reasoning_content: event.delta?.thinking
9762
+ },
9763
+ finish_reason: null
9764
+ }]
9765
+ };
9766
+ }
9767
+ }
9323
9768
  if (event.type === "message_delta" && event.delta?.stop_reason) {
9324
9769
  return {
9325
9770
  id: completionId,
@@ -9636,7 +10081,11 @@ var CLAUDE_CODE_ONLY_TOOLS = [
9636
10081
  "ExitPlanMode",
9637
10082
  "EnterWorktree",
9638
10083
  "ExitWorktree",
10084
+ "Monitor",
9639
10085
  "NotebookEdit",
10086
+ "PushNotification",
10087
+ "RemoteTrigger",
10088
+ "ScheduleWakeup",
9640
10089
  "TodoWrite",
9641
10090
  "AskUserQuestion",
9642
10091
  "Skill",
@@ -9671,8 +10120,8 @@ var openCodeTransforms = [
9671
10120
  let sdkAgents = {};
9672
10121
  if (Array.isArray(body.tools)) {
9673
10122
  const taskTool = body.tools.find((t) => t.name === "task" || t.name === "Task");
9674
- if (taskTool?.description) {
9675
- sdkAgents = buildAgentDefinitions(taskTool.description, [...allowedMcpTools]);
10123
+ if (taskTool) {
10124
+ sdkAgents = buildAgentDefinitionsFromTool(taskTool, [...allowedMcpTools]);
9676
10125
  }
9677
10126
  }
9678
10127
  let sdkHooks = undefined;
@@ -9778,9 +10227,9 @@ var openCodeAdapter = {
9778
10227
  if (!Array.isArray(body.tools))
9779
10228
  return {};
9780
10229
  const taskTool = body.tools.find((t) => t.name === "task" || t.name === "Task");
9781
- if (!taskTool?.description)
10230
+ if (!taskTool)
9782
10231
  return {};
9783
- return buildAgentDefinitions(taskTool.description, [...mcpToolNames]);
10232
+ return buildAgentDefinitionsFromTool(taskTool, [...mcpToolNames]);
9784
10233
  },
9785
10234
  buildSdkHooks(body, sdkAgents) {
9786
10235
  const validAgentNames = Object.keys(sdkAgents);
@@ -11938,7 +12387,7 @@ var emitWarning = (msg, type, code, fn) => {
11938
12387
  var AC = globalThis.AbortController;
11939
12388
  var AS = globalThis.AbortSignal;
11940
12389
  if (typeof AC === "undefined") {
11941
- AS = class AbortSignal {
12390
+ AS = class AbortSignal2 {
11942
12391
  onabort;
11943
12392
  _onabort = [];
11944
12393
  reason;
@@ -16178,7 +16627,7 @@ function createOpencodeMcpServer() {
16178
16627
  // src/proxy/query.ts
16179
16628
  function computePassthroughMaxTurns(resumeSessionId, hasDeferredTools, advisorModel) {
16180
16629
  const hasResume = !!resumeSessionId;
16181
- const base = hasResume && hasDeferredTools ? 4 : hasResume || hasDeferredTools ? 3 : 2;
16630
+ const base = hasResume && hasDeferredTools ? 4 : 3;
16182
16631
  const advisorBump = advisorModel ? 3 : 0;
16183
16632
  return base + advisorBump;
16184
16633
  }
@@ -17674,7 +18123,7 @@ function createProxyServer(config = {}) {
17674
18123
  });
17675
18124
  return {
17676
18125
  decision: "block",
17677
- reason: "Forwarding to client for execution"
18126
+ 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."
17678
18127
  };
17679
18128
  }]
17680
18129
  }]
@@ -17749,6 +18198,9 @@ function createProxyServer(config = {}) {
17749
18198
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
17750
18199
  advisorModel
17751
18200
  }))) {
18201
+ if (event.type === "rate_limit_event") {
18202
+ rateLimitStore.record(event.rate_limit_info);
18203
+ }
17752
18204
  if (event.type === "assistant" && !event.error) {
17753
18205
  didYieldContent = true;
17754
18206
  }
@@ -18092,6 +18544,7 @@ Subprocess stderr: ${stderrOutput}`;
18092
18544
  sdkUuidMap.push(null);
18093
18545
  let messageStartEmitted = false;
18094
18546
  let lastUsage;
18547
+ const streamedToolUseIds = new Set;
18095
18548
  try {
18096
18549
  let currentSessionId;
18097
18550
  const MAX_RATE_LIMIT_RETRIES = 2;
@@ -18140,6 +18593,9 @@ Subprocess stderr: ${stderrOutput}`;
18140
18593
  additionalDirectories: sdkFeatures.additionalDirectories ? sdkFeatures.additionalDirectories.split(",").map((d) => d.trim()).filter(Boolean) : undefined,
18141
18594
  advisorModel
18142
18595
  }))) {
18596
+ if (event.type === "rate_limit_event") {
18597
+ rateLimitStore.record(event.rate_limit_info);
18598
+ }
18143
18599
  if (event.type === "stream_event") {
18144
18600
  didYieldClientEvent = true;
18145
18601
  }
@@ -18279,7 +18735,6 @@ Subprocess stderr: ${stderrOutput}`;
18279
18735
  const skipBlockIndices = new Set;
18280
18736
  const taskToolBlockIndices = new Set;
18281
18737
  const taskToolJsonBuffer = new Map;
18282
- const streamedToolUseIds = new Set;
18283
18738
  let nextClientBlockIndex = 0;
18284
18739
  const sdkToClientIndex = new Map;
18285
18740
  try {
@@ -18642,6 +19097,127 @@ Subprocess stderr: ${stderrOutput}`;
18642
19097
  });
18643
19098
  const streamErr = classifyError(errMsg);
18644
19099
  claudeLog("proxy.anthropic.error", { error: errMsg, classified: streamErr.type });
19100
+ const sdkTerm = extractSdkTermination(errMsg);
19101
+ const canRecoverAsToolUse = sdkTerm.reason === "max_turns" && passthrough && capturedToolUses.length > 0 && messageStartEmitted;
19102
+ if (canRecoverAsToolUse) {
19103
+ diagnosticLog2.session(`${requestMeta.requestId} sdk_termination_recovered ${formatSdkTermination(sdkTerm, {
19104
+ model,
19105
+ requestSource,
19106
+ isResume,
19107
+ hasDeferredTools,
19108
+ sdkSessionId: resumeSessionId
19109
+ })} captured=${capturedToolUses.length}`, requestMeta.requestId);
19110
+ const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
19111
+ for (let i = 0;i < unseenToolUses.length; i++) {
19112
+ const tu = unseenToolUses[i];
19113
+ const blockIndex = eventsForwarded + i;
19114
+ safeEnqueue(encoder.encode(`event: content_block_start
19115
+ data: ${JSON.stringify({
19116
+ type: "content_block_start",
19117
+ index: blockIndex,
19118
+ content_block: { type: "tool_use", id: tu.id, name: tu.name, input: {} }
19119
+ })}
19120
+
19121
+ `), "recover_tool_block_start");
19122
+ safeEnqueue(encoder.encode(`event: content_block_delta
19123
+ data: ${JSON.stringify({
19124
+ type: "content_block_delta",
19125
+ index: blockIndex,
19126
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(tu.input) }
19127
+ })}
19128
+
19129
+ `), "recover_tool_input");
19130
+ safeEnqueue(encoder.encode(`event: content_block_stop
19131
+ data: ${JSON.stringify({
19132
+ type: "content_block_stop",
19133
+ index: blockIndex
19134
+ })}
19135
+
19136
+ `), "recover_tool_block_stop");
19137
+ }
19138
+ safeEnqueue(encoder.encode(`event: message_delta
19139
+ data: ${JSON.stringify({
19140
+ type: "message_delta",
19141
+ delta: { stop_reason: "tool_use", stop_sequence: null },
19142
+ usage: { output_tokens: 0 }
19143
+ })}
19144
+
19145
+ `), "recover_message_delta");
19146
+ safeEnqueue(encoder.encode(`event: message_stop
19147
+ data: {"type":"message_stop"}
19148
+
19149
+ `), "recover_message_stop");
19150
+ const recoverTotalMs = Date.now() - requestStartAt;
19151
+ const recoverQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
19152
+ telemetryStore2.record({
19153
+ requestId: requestMeta.requestId,
19154
+ timestamp: Date.now(),
19155
+ adapter: adapter.name,
19156
+ requestSource,
19157
+ model,
19158
+ requestModel: body.model || undefined,
19159
+ mode: "stream",
19160
+ isResume,
19161
+ isPassthrough: passthrough,
19162
+ hasDeferredTools,
19163
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
19164
+ toolCount,
19165
+ lineageType,
19166
+ messageCount: allMessages.length,
19167
+ sdkSessionId: resumeSessionId,
19168
+ status: 200,
19169
+ queueWaitMs: recoverQueueWaitMs,
19170
+ proxyOverheadMs: upstreamStartAt - requestStartAt - recoverQueueWaitMs,
19171
+ ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
19172
+ upstreamDurationMs: Date.now() - upstreamStartAt,
19173
+ totalDurationMs: recoverTotalMs,
19174
+ contentBlocks: eventsForwarded + unseenToolUses.length,
19175
+ textEvents: textEventsForwarded,
19176
+ error: null
19177
+ });
19178
+ if (!streamClosed) {
19179
+ try {
19180
+ controller.close();
19181
+ } catch {}
19182
+ streamClosed = true;
19183
+ }
19184
+ return;
19185
+ }
19186
+ diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
19187
+ model,
19188
+ requestSource,
19189
+ isResume,
19190
+ hasDeferredTools,
19191
+ sdkSessionId: resumeSessionId
19192
+ })}`, requestMeta.requestId);
19193
+ const streamErrTotalMs = Date.now() - requestStartAt;
19194
+ const streamErrQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
19195
+ telemetryStore2.record({
19196
+ requestId: requestMeta.requestId,
19197
+ timestamp: Date.now(),
19198
+ adapter: adapter.name,
19199
+ requestSource,
19200
+ model,
19201
+ requestModel: body.model || undefined,
19202
+ mode: "stream",
19203
+ isResume,
19204
+ isPassthrough: passthrough,
19205
+ hasDeferredTools,
19206
+ deferredToolCount: hasDeferredTools ? deferredToolCount : undefined,
19207
+ toolCount,
19208
+ lineageType,
19209
+ messageCount: allMessages.length,
19210
+ sdkSessionId: resumeSessionId,
19211
+ status: streamErr.status,
19212
+ queueWaitMs: streamErrQueueWaitMs,
19213
+ proxyOverheadMs: upstreamStartAt - requestStartAt - streamErrQueueWaitMs,
19214
+ ttfbMs: firstChunkAt ? firstChunkAt - upstreamStartAt : null,
19215
+ upstreamDurationMs: Date.now() - upstreamStartAt,
19216
+ totalDurationMs: streamErrTotalMs,
19217
+ contentBlocks: eventsForwarded,
19218
+ textEvents: textEventsForwarded,
19219
+ error: streamErr.type
19220
+ });
18645
19221
  if (messageStartEmitted) {
18646
19222
  safeEnqueue(encoder.encode(`event: message_delta
18647
19223
  data: ${JSON.stringify({
@@ -18689,6 +19265,10 @@ data: ${JSON.stringify({
18689
19265
  });
18690
19266
  const classified = classifyError(errMsg);
18691
19267
  claudeLog("proxy.error", { error: errMsg, classified: classified.type });
19268
+ const sdkTerm = extractSdkTermination(errMsg);
19269
+ diagnosticLog2.error(`${requestMeta.requestId} ${formatSdkTermination(sdkTerm, {
19270
+ requestSource: c.req.header("x-meridian-source")?.slice(0, 64) || undefined
19271
+ })}`, requestMeta.requestId);
18692
19272
  const errorQueueWaitMs = requestMeta.queueStartedAt - requestMeta.queueEnteredAt;
18693
19273
  telemetryStore2.record({
18694
19274
  requestId: requestMeta.requestId,
@@ -18830,7 +19410,7 @@ data: ${JSON.stringify({
18830
19410
  });
18831
19411
  });
18832
19412
  app.get("/profiles", async (c) => {
18833
- const { profilePageHtml } = await import("./profilePage-77z05e0r.js");
19413
+ const { profilePageHtml } = await import("./profilePage-k0faye28.js");
18834
19414
  return c.html(profilePageHtml);
18835
19415
  });
18836
19416
  app.post("/profiles/active", async (c) => {
@@ -18852,7 +19432,8 @@ data: ${JSON.stringify({
18852
19432
  }
18853
19433
  setActiveProfile(body.profile);
18854
19434
  clearSessionCache();
18855
- console.error(`[PROXY] Active profile switched to: ${body.profile} (session cache cleared)`);
19435
+ rateLimitStore.clear();
19436
+ console.error(`[PROXY] Active profile switched to: ${body.profile} (session + rate-limit caches cleared)`);
18856
19437
  return c.json({ success: true, activeProfile: body.profile });
18857
19438
  });
18858
19439
  app.get("/plugins/list", async (c) => {
@@ -18897,6 +19478,7 @@ data: ${JSON.stringify({
18897
19478
  app.post("/auth/refresh", async (c) => {
18898
19479
  const success = await refreshOAuthToken();
18899
19480
  if (success) {
19481
+ rateLimitStore.clear();
18900
19482
  return c.json({ success: true, message: "OAuth token refreshed successfully" });
18901
19483
  }
18902
19484
  return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
@@ -18920,9 +19502,14 @@ data: ${JSON.stringify({
18920
19502
  const completionId = `chatcmpl-${randomUUID()}`;
18921
19503
  const created = Math.floor(Date.now() / 1000);
18922
19504
  const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : "claude-sonnet-4-6";
19505
+ const { getFeaturesForAdapter: getFeaturesForAdapter2 } = (init_sdkFeatures(), __toCommonJS(exports_sdkFeatures));
19506
+ const adapter = detectAdapter(c);
19507
+ const sdkFeatures = getFeaturesForAdapter2(adapter.name);
18923
19508
  if (!anthropicBody.stream) {
18924
19509
  const anthropicRes = await internalRes.json();
18925
- return c.json(translateAnthropicToOpenAi(anthropicRes, completionId, model, created));
19510
+ return c.json(translateAnthropicToOpenAi(anthropicRes, completionId, model, created, {
19511
+ thinkingPassthrough: sdkFeatures.thinkingPassthrough
19512
+ }));
18926
19513
  }
18927
19514
  const encoder = new TextEncoder;
18928
19515
  const readable = new ReadableStream({
@@ -18935,6 +19522,7 @@ data: ${JSON.stringify({
18935
19522
  const decoder = new TextDecoder;
18936
19523
  let buffer = "";
18937
19524
  let streamError = null;
19525
+ const translate = createSseTranslator({ completionId, model, created, thinkingPassthrough: sdkFeatures.thinkingPassthrough });
18938
19526
  try {
18939
19527
  while (true) {
18940
19528
  const { done, value } = await reader.read();
@@ -18958,7 +19546,7 @@ data: ${JSON.stringify({
18958
19546
  }
18959
19547
  if (typeof event.type !== "string")
18960
19548
  continue;
18961
- const chunk = translateAnthropicSseEvent(event, completionId, model, created);
19549
+ const chunk = translate(event);
18962
19550
  if (chunk)
18963
19551
  controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
18964
19552
 
@@ -18989,6 +19577,112 @@ data: ${JSON.stringify({
18989
19577
  const isMax = authStatus?.subscriptionType === "max";
18990
19578
  return c.json({ object: "list", data: buildModelList(isMax) });
18991
19579
  });
19580
+ app.get("/v1/usage/quota", async (c) => {
19581
+ const sdkEntries = rateLimitStore.getAll().filter((entry) => entry.rateLimitType !== undefined);
19582
+ const requestedProfile = c.req.query("profile");
19583
+ const profilesList = getEffectiveProfiles(finalConfig.profiles);
19584
+ const targetProfileId = requestedProfile || getActiveProfileId() || finalConfig.defaultProfile || profilesList[0]?.id || null;
19585
+ const targetProfile = targetProfileId ? profilesList.find((p) => p.id === targetProfileId) : undefined;
19586
+ const oauth = await fetchOAuthUsage({
19587
+ profileId: targetProfileId ?? undefined,
19588
+ claudeConfigDir: targetProfile?.claudeConfigDir
19589
+ });
19590
+ const byType = new Map;
19591
+ for (const entry of sdkEntries) {
19592
+ const type = entry.rateLimitType;
19593
+ byType.set(type, {
19594
+ type,
19595
+ status: entry.status,
19596
+ utilization: entry.utilization ?? null,
19597
+ resetsAt: entry.resetsAt ?? null,
19598
+ isUsingOverage: entry.isUsingOverage ?? false,
19599
+ overageStatus: entry.overageStatus ?? null,
19600
+ overageResetsAt: entry.overageResetsAt ?? null,
19601
+ overageDisabledReason: entry.overageDisabledReason ?? null,
19602
+ surpassedThreshold: entry.surpassedThreshold ?? null,
19603
+ observedAt: entry.observedAt
19604
+ });
19605
+ }
19606
+ if (oauth) {
19607
+ for (const w of oauth.windows) {
19608
+ const existing = byType.get(w.type);
19609
+ const status = (w.utilization ?? 0) >= 1 ? "rejected" : (w.utilization ?? 0) >= 0.8 ? "allowed_warning" : "allowed";
19610
+ byType.set(w.type, {
19611
+ type: w.type,
19612
+ status: existing?.status === "rejected" ? "rejected" : status,
19613
+ utilization: w.utilization ?? existing?.utilization ?? null,
19614
+ resetsAt: w.resetsAt ?? existing?.resetsAt ?? null,
19615
+ isUsingOverage: existing?.isUsingOverage ?? false,
19616
+ overageStatus: existing?.overageStatus ?? null,
19617
+ overageResetsAt: existing?.overageResetsAt ?? null,
19618
+ overageDisabledReason: existing?.overageDisabledReason ?? null,
19619
+ surpassedThreshold: existing?.surpassedThreshold ?? null,
19620
+ observedAt: oauth.fetchedAt
19621
+ });
19622
+ }
19623
+ }
19624
+ return c.json({
19625
+ profile: targetProfileId ?? null,
19626
+ buckets: Array.from(byType.values()),
19627
+ extraUsage: oauth?.extraUsage ?? null,
19628
+ sources: {
19629
+ oauth: oauth ? { fetchedAt: oauth.fetchedAt } : null,
19630
+ sdk: { entryCount: sdkEntries.length }
19631
+ },
19632
+ asOf: Date.now()
19633
+ });
19634
+ });
19635
+ app.get("/v1/usage/quota/all", async (c) => {
19636
+ const profilesList = getEffectiveProfiles(finalConfig.profiles);
19637
+ const activeId = getActiveProfileId() || finalConfig.defaultProfile || profilesList[0]?.id || null;
19638
+ if (profilesList.length === 0) {
19639
+ const oauth = await fetchOAuthUsage({});
19640
+ return c.json({
19641
+ profiles: [{
19642
+ id: "default",
19643
+ isActive: true,
19644
+ windows: oauth?.windows ?? [],
19645
+ extraUsage: oauth?.extraUsage ?? null,
19646
+ fetchedAt: oauth?.fetchedAt ?? null,
19647
+ error: oauth ? null : "no_token"
19648
+ }],
19649
+ activeProfile: "default",
19650
+ asOf: Date.now()
19651
+ });
19652
+ }
19653
+ const results = await Promise.all(profilesList.map(async (p) => {
19654
+ const type = p.type ?? "claude-max";
19655
+ if (type !== "claude-max") {
19656
+ return {
19657
+ id: p.id,
19658
+ isActive: p.id === activeId,
19659
+ type,
19660
+ windows: [],
19661
+ extraUsage: null,
19662
+ fetchedAt: null,
19663
+ error: "not_oauth"
19664
+ };
19665
+ }
19666
+ const oauth = await fetchOAuthUsage({
19667
+ profileId: p.id,
19668
+ claudeConfigDir: p.claudeConfigDir
19669
+ });
19670
+ return {
19671
+ id: p.id,
19672
+ isActive: p.id === activeId,
19673
+ type,
19674
+ windows: oauth?.windows ?? [],
19675
+ extraUsage: oauth?.extraUsage ?? null,
19676
+ fetchedAt: oauth?.fetchedAt ?? null,
19677
+ error: oauth ? null : "no_token"
19678
+ };
19679
+ }));
19680
+ return c.json({
19681
+ profiles: results,
19682
+ activeProfile: activeId,
19683
+ asOf: Date.now()
19684
+ });
19685
+ });
18992
19686
  app.get("/v1/sessions/:claudeSessionId/context-usage", (c) => {
18993
19687
  const claudeSessionId = c.req.param("claudeSessionId");
18994
19688
  const session = getSessionByClaudeId(claudeSessionId);