@rynfar/meridian 1.24.5 → 1.26.5
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.
- package/README.md +144 -195
- package/dist/cli-a05ws7rb.js +18 -0
- package/dist/{cli-jd4atcxs.js → cli-m9pfb7h9.js} +1 -18
- package/dist/cli-rtab0qa6.js +67 -0
- package/dist/{cli-9pc43rfa.js → cli-yve9q0a0.js} +471 -66
- package/dist/cli.js +39 -3
- package/dist/proxy/adapter.d.ts +1 -1
- package/dist/proxy/fileChanges.d.ts.map +1 -1
- package/dist/proxy/models.d.ts +17 -1
- package/dist/proxy/models.d.ts.map +1 -1
- package/dist/proxy/openai.d.ts +142 -0
- package/dist/proxy/openai.d.ts.map +1 -0
- package/dist/proxy/query.d.ts +23 -63
- package/dist/proxy/query.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/session/cache.d.ts +9 -4
- package/dist/proxy/session/cache.d.ts.map +1 -1
- package/dist/proxy/session/lineage.d.ts +9 -0
- package/dist/proxy/session/lineage.d.ts.map +1 -1
- package/dist/proxy/sessionStore.d.ts +5 -1
- package/dist/proxy/sessionStore.d.ts.map +1 -1
- package/dist/proxy/setup.d.ts +42 -0
- package/dist/proxy/setup.d.ts.map +1 -0
- package/dist/server.js +4 -2
- package/dist/setup-5x116vbs.js +13 -0
- package/dist/telemetry/dashboard.d.ts +1 -1
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/routes.d.ts.map +1 -1
- package/dist/{tokenRefresh-wzn2bvrq.js → tokenRefresh-ywwpe8k2.js} +2 -1
- package/package.json +4 -3
- package/plugin/meridian.ts +54 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
checkPluginConfigured
|
|
3
|
+
} from "./cli-rtab0qa6.js";
|
|
4
|
+
import {
|
|
4
5
|
claudeLog,
|
|
5
6
|
refreshOAuthToken,
|
|
6
7
|
withClaudeLogContext
|
|
7
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-m9pfb7h9.js";
|
|
9
|
+
import {
|
|
10
|
+
__export,
|
|
11
|
+
__require
|
|
12
|
+
} from "./cli-a05ws7rb.js";
|
|
8
13
|
|
|
9
14
|
// node_modules/hono/dist/compose.js
|
|
10
15
|
var compose = (middleware, onError, onNotFound) => {
|
|
@@ -6384,6 +6389,11 @@ class DiagnosticLogStore {
|
|
|
6384
6389
|
}
|
|
6385
6390
|
}
|
|
6386
6391
|
var diagnosticLog = new DiagnosticLogStore;
|
|
6392
|
+
// src/telemetry/routes.ts
|
|
6393
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6394
|
+
import { resolve, dirname } from "node:path";
|
|
6395
|
+
import { fileURLToPath } from "node:url";
|
|
6396
|
+
|
|
6387
6397
|
// src/telemetry/dashboard.ts
|
|
6388
6398
|
var dashboardHtml = `<!DOCTYPE html>
|
|
6389
6399
|
<html lang="en">
|
|
@@ -6391,6 +6401,7 @@ var dashboardHtml = `<!DOCTYPE html>
|
|
|
6391
6401
|
<meta charset="utf-8">
|
|
6392
6402
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6393
6403
|
<title>Meridian — Telemetry</title>
|
|
6404
|
+
<link rel="icon" type="image/svg+xml" href="/telemetry/icon.svg">
|
|
6394
6405
|
<style>
|
|
6395
6406
|
:root {
|
|
6396
6407
|
--bg: #0d1117; --surface: #161b22; --border: #30363d;
|
|
@@ -6712,11 +6723,21 @@ timer = setInterval(refresh, 5000);
|
|
|
6712
6723
|
</html>`;
|
|
6713
6724
|
|
|
6714
6725
|
// src/telemetry/routes.ts
|
|
6726
|
+
var _iconPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
|
|
6727
|
+
var _iconSvg = existsSync(_iconPath) ? readFileSync(_iconPath, "utf-8") : null;
|
|
6715
6728
|
function createTelemetryRoutes() {
|
|
6716
6729
|
const routes = new Hono2;
|
|
6717
6730
|
routes.get("/", (c) => {
|
|
6718
6731
|
return c.html(dashboardHtml);
|
|
6719
6732
|
});
|
|
6733
|
+
routes.get("/icon.svg", (c) => {
|
|
6734
|
+
if (!_iconSvg)
|
|
6735
|
+
return c.notFound();
|
|
6736
|
+
return c.body(_iconSvg, 200, {
|
|
6737
|
+
"Content-Type": "image/svg+xml",
|
|
6738
|
+
"Cache-Control": "public, max-age=3600"
|
|
6739
|
+
});
|
|
6740
|
+
});
|
|
6720
6741
|
routes.get("/requests", (c) => {
|
|
6721
6742
|
const limit = Number.parseInt(c.req.query("limit") || "50", 10);
|
|
6722
6743
|
const since = c.req.query("since") ? Number.parseInt(c.req.query("since"), 10) : undefined;
|
|
@@ -6971,9 +6992,9 @@ function isExtraUsageRequiredError(errMsg) {
|
|
|
6971
6992
|
|
|
6972
6993
|
// src/proxy/models.ts
|
|
6973
6994
|
import { exec as execCallback } from "child_process";
|
|
6974
|
-
import { existsSync } from "fs";
|
|
6975
|
-
import { fileURLToPath } from "url";
|
|
6976
|
-
import { join, dirname } from "path";
|
|
6995
|
+
import { existsSync as existsSync2 } from "fs";
|
|
6996
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6997
|
+
import { join, dirname as dirname2 } from "path";
|
|
6977
6998
|
import { promisify } from "util";
|
|
6978
6999
|
var exec = promisify(execCallback);
|
|
6979
7000
|
var AUTH_STATUS_CACHE_TTL_MS = 60000;
|
|
@@ -6988,19 +7009,35 @@ function supports1mContext(model) {
|
|
|
6988
7009
|
return false;
|
|
6989
7010
|
return true;
|
|
6990
7011
|
}
|
|
6991
|
-
function mapModelToClaudeModel(model, subscriptionType) {
|
|
7012
|
+
function mapModelToClaudeModel(model, subscriptionType, agentMode) {
|
|
6992
7013
|
if (model.includes("haiku"))
|
|
6993
7014
|
return "haiku";
|
|
6994
7015
|
const use1m = supports1mContext(model);
|
|
6995
|
-
|
|
6996
|
-
|
|
7016
|
+
const isSubagent = agentMode === "subagent";
|
|
7017
|
+
if (model.includes("opus")) {
|
|
7018
|
+
if (use1m && !isSubagent && !isExtendedContextKnownUnavailable())
|
|
7019
|
+
return "opus[1m]";
|
|
7020
|
+
return "opus";
|
|
7021
|
+
}
|
|
6997
7022
|
const sonnetOverride = process.env.MERIDIAN_SONNET_MODEL ?? process.env.CLAUDE_PROXY_SONNET_MODEL;
|
|
6998
7023
|
if (sonnetOverride === "sonnet" || sonnetOverride === "sonnet[1m]")
|
|
6999
7024
|
return sonnetOverride;
|
|
7000
7025
|
if (!use1m)
|
|
7001
7026
|
return "sonnet";
|
|
7027
|
+
if (isSubagent)
|
|
7028
|
+
return "sonnet";
|
|
7029
|
+
if (isExtendedContextKnownUnavailable())
|
|
7030
|
+
return "sonnet";
|
|
7002
7031
|
return subscriptionType === "max" ? "sonnet[1m]" : "sonnet";
|
|
7003
7032
|
}
|
|
7033
|
+
var EXTRA_USAGE_RETRY_MS = 60 * 60 * 1000;
|
|
7034
|
+
var extraUsageUnavailableAt = 0;
|
|
7035
|
+
function recordExtendedContextUnavailable() {
|
|
7036
|
+
extraUsageUnavailableAt = Date.now();
|
|
7037
|
+
}
|
|
7038
|
+
function isExtendedContextKnownUnavailable() {
|
|
7039
|
+
return extraUsageUnavailableAt > 0 && Date.now() - extraUsageUnavailableAt < EXTRA_USAGE_RETRY_MS;
|
|
7040
|
+
}
|
|
7004
7041
|
function stripExtendedContext(model) {
|
|
7005
7042
|
if (model === "opus[1m]")
|
|
7006
7043
|
return "opus";
|
|
@@ -7051,9 +7088,9 @@ async function resolveClaudeExecutableAsync() {
|
|
|
7051
7088
|
const runningUnderBun = typeof process.versions.bun !== "undefined";
|
|
7052
7089
|
if (runningUnderBun) {
|
|
7053
7090
|
try {
|
|
7054
|
-
const sdkPath =
|
|
7055
|
-
const sdkCliJs = join(
|
|
7056
|
-
if (
|
|
7091
|
+
const sdkPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
|
|
7092
|
+
const sdkCliJs = join(dirname2(sdkPath), "cli.js");
|
|
7093
|
+
if (existsSync2(sdkCliJs)) {
|
|
7057
7094
|
cachedClaudePath = sdkCliJs;
|
|
7058
7095
|
return sdkCliJs;
|
|
7059
7096
|
}
|
|
@@ -7062,16 +7099,16 @@ async function resolveClaudeExecutableAsync() {
|
|
|
7062
7099
|
try {
|
|
7063
7100
|
const { stdout } = await exec("which claude");
|
|
7064
7101
|
const claudePath = stdout.trim();
|
|
7065
|
-
if (claudePath &&
|
|
7102
|
+
if (claudePath && existsSync2(claudePath)) {
|
|
7066
7103
|
cachedClaudePath = claudePath;
|
|
7067
7104
|
return claudePath;
|
|
7068
7105
|
}
|
|
7069
7106
|
} catch {}
|
|
7070
7107
|
if (!runningUnderBun) {
|
|
7071
7108
|
try {
|
|
7072
|
-
const sdkPath =
|
|
7073
|
-
const sdkCliJs = join(
|
|
7074
|
-
if (
|
|
7109
|
+
const sdkPath = fileURLToPath2(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
|
|
7110
|
+
const sdkCliJs = join(dirname2(sdkPath), "cli.js");
|
|
7111
|
+
if (existsSync2(sdkCliJs)) {
|
|
7075
7112
|
cachedClaudePath = sdkCliJs;
|
|
7076
7113
|
return sdkCliJs;
|
|
7077
7114
|
}
|
|
@@ -7091,6 +7128,146 @@ function isClosedControllerError(error) {
|
|
|
7091
7128
|
return error.message.includes("Controller is already closed");
|
|
7092
7129
|
}
|
|
7093
7130
|
|
|
7131
|
+
// src/proxy/openai.ts
|
|
7132
|
+
function extractOpenAiContent(content) {
|
|
7133
|
+
if (typeof content === "string")
|
|
7134
|
+
return content;
|
|
7135
|
+
return content.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
7136
|
+
}
|
|
7137
|
+
function translateOpenAiToAnthropic(body) {
|
|
7138
|
+
const messages = body.messages ?? [];
|
|
7139
|
+
if (messages.length === 0)
|
|
7140
|
+
return null;
|
|
7141
|
+
const systemParts = [];
|
|
7142
|
+
const turns = [];
|
|
7143
|
+
for (const msg of messages) {
|
|
7144
|
+
const text = extractOpenAiContent(msg.content ?? "");
|
|
7145
|
+
if (msg.role === "system") {
|
|
7146
|
+
if (text)
|
|
7147
|
+
systemParts.push(text);
|
|
7148
|
+
} else {
|
|
7149
|
+
turns.push({
|
|
7150
|
+
role: msg.role === "assistant" ? "assistant" : "user",
|
|
7151
|
+
content: text
|
|
7152
|
+
});
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
let systemPrompt = systemParts.join(`
|
|
7156
|
+
`);
|
|
7157
|
+
let messagesToSend = turns;
|
|
7158
|
+
if (turns.length > 1) {
|
|
7159
|
+
const history = turns.slice(0, -1).map((m) => `${m.role}: ${m.content}`).join(`
|
|
7160
|
+
`);
|
|
7161
|
+
const historyBlock = `<conversation_history>
|
|
7162
|
+
${history}
|
|
7163
|
+
</conversation_history>
|
|
7164
|
+
|
|
7165
|
+
` + `Continue this conversation naturally. Respond to the user's latest message.`;
|
|
7166
|
+
systemPrompt = systemPrompt ? `${systemPrompt}
|
|
7167
|
+
|
|
7168
|
+
${historyBlock}` : historyBlock;
|
|
7169
|
+
messagesToSend = turns.slice(-1);
|
|
7170
|
+
}
|
|
7171
|
+
const result = {
|
|
7172
|
+
model: body.model ?? "claude-sonnet-4-6",
|
|
7173
|
+
messages: messagesToSend,
|
|
7174
|
+
max_tokens: body.max_tokens ?? body.max_completion_tokens ?? 8192,
|
|
7175
|
+
stream: body.stream ?? false
|
|
7176
|
+
};
|
|
7177
|
+
if (systemPrompt)
|
|
7178
|
+
result.system = systemPrompt;
|
|
7179
|
+
if (body.temperature !== undefined)
|
|
7180
|
+
result.temperature = body.temperature;
|
|
7181
|
+
if (body.top_p !== undefined)
|
|
7182
|
+
result.top_p = body.top_p;
|
|
7183
|
+
return result;
|
|
7184
|
+
}
|
|
7185
|
+
function toFinishReason(stopReason) {
|
|
7186
|
+
if (stopReason === "max_tokens")
|
|
7187
|
+
return "length";
|
|
7188
|
+
return "stop";
|
|
7189
|
+
}
|
|
7190
|
+
function translateAnthropicToOpenAi(response, completionId, model, created) {
|
|
7191
|
+
const content = (response.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
7192
|
+
const promptTokens = response.usage?.input_tokens ?? 0;
|
|
7193
|
+
const completionTokens = response.usage?.output_tokens ?? 0;
|
|
7194
|
+
return {
|
|
7195
|
+
id: completionId,
|
|
7196
|
+
object: "chat.completion",
|
|
7197
|
+
created,
|
|
7198
|
+
model,
|
|
7199
|
+
choices: [{
|
|
7200
|
+
index: 0,
|
|
7201
|
+
message: { role: "assistant", content },
|
|
7202
|
+
finish_reason: toFinishReason(response.stop_reason)
|
|
7203
|
+
}],
|
|
7204
|
+
usage: {
|
|
7205
|
+
prompt_tokens: promptTokens,
|
|
7206
|
+
completion_tokens: completionTokens,
|
|
7207
|
+
total_tokens: promptTokens + completionTokens
|
|
7208
|
+
}
|
|
7209
|
+
};
|
|
7210
|
+
}
|
|
7211
|
+
function translateAnthropicSseEvent(event, completionId, model, created) {
|
|
7212
|
+
if (event.type === "message_start") {
|
|
7213
|
+
return {
|
|
7214
|
+
id: completionId,
|
|
7215
|
+
object: "chat.completion.chunk",
|
|
7216
|
+
created,
|
|
7217
|
+
model,
|
|
7218
|
+
choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }]
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7221
|
+
if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
|
|
7222
|
+
return {
|
|
7223
|
+
id: completionId,
|
|
7224
|
+
object: "chat.completion.chunk",
|
|
7225
|
+
created,
|
|
7226
|
+
model,
|
|
7227
|
+
choices: [{ index: 0, delta: { content: event.delta.text }, finish_reason: null }]
|
|
7228
|
+
};
|
|
7229
|
+
}
|
|
7230
|
+
if (event.type === "message_delta" && event.delta?.stop_reason) {
|
|
7231
|
+
return {
|
|
7232
|
+
id: completionId,
|
|
7233
|
+
object: "chat.completion.chunk",
|
|
7234
|
+
created,
|
|
7235
|
+
model,
|
|
7236
|
+
choices: [{ index: 0, delta: {}, finish_reason: toFinishReason(event.delta.stop_reason) }]
|
|
7237
|
+
};
|
|
7238
|
+
}
|
|
7239
|
+
return null;
|
|
7240
|
+
}
|
|
7241
|
+
function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000)) {
|
|
7242
|
+
const extendedContext = isMaxSubscription ? 1e6 : 200000;
|
|
7243
|
+
return [
|
|
7244
|
+
{
|
|
7245
|
+
id: "claude-sonnet-4-6",
|
|
7246
|
+
object: "model",
|
|
7247
|
+
created: now,
|
|
7248
|
+
owned_by: "anthropic",
|
|
7249
|
+
display_name: "Claude Sonnet 4.6",
|
|
7250
|
+
context_window: extendedContext
|
|
7251
|
+
},
|
|
7252
|
+
{
|
|
7253
|
+
id: "claude-opus-4-6",
|
|
7254
|
+
object: "model",
|
|
7255
|
+
created: now,
|
|
7256
|
+
owned_by: "anthropic",
|
|
7257
|
+
display_name: "Claude Opus 4.6",
|
|
7258
|
+
context_window: extendedContext
|
|
7259
|
+
},
|
|
7260
|
+
{
|
|
7261
|
+
id: "claude-haiku-4-5-20251001",
|
|
7262
|
+
object: "model",
|
|
7263
|
+
created: now,
|
|
7264
|
+
owned_by: "anthropic",
|
|
7265
|
+
display_name: "Claude Haiku 4.5",
|
|
7266
|
+
context_window: 200000
|
|
7267
|
+
}
|
|
7268
|
+
];
|
|
7269
|
+
}
|
|
7270
|
+
|
|
7094
7271
|
// src/proxy/messages.ts
|
|
7095
7272
|
function stripCacheControlForHashing(obj) {
|
|
7096
7273
|
if (!obj || typeof obj !== "object")
|
|
@@ -7163,6 +7340,17 @@ function createFileChangeHook(changes, mcpPrefix) {
|
|
|
7163
7340
|
}]
|
|
7164
7341
|
};
|
|
7165
7342
|
}
|
|
7343
|
+
function isLikelyFilePath(s) {
|
|
7344
|
+
if (/[()[\]]/.test(s))
|
|
7345
|
+
return false;
|
|
7346
|
+
if (/^-?\d+$/.test(s))
|
|
7347
|
+
return false;
|
|
7348
|
+
if (/^[{}]$/.test(s))
|
|
7349
|
+
return false;
|
|
7350
|
+
if (!/[\w/.]/.test(s))
|
|
7351
|
+
return false;
|
|
7352
|
+
return true;
|
|
7353
|
+
}
|
|
7166
7354
|
function extractFileChangesFromBash(command) {
|
|
7167
7355
|
const changes = [];
|
|
7168
7356
|
const seen = new Set;
|
|
@@ -7177,10 +7365,12 @@ function extractFileChangesFromBash(command) {
|
|
|
7177
7365
|
changes.push({ operation, path });
|
|
7178
7366
|
}
|
|
7179
7367
|
};
|
|
7180
|
-
const redirectRegex = /(?<![0-9])>{1,2}\s*['"]?([^\s'";&|)]+)['"]?/g;
|
|
7368
|
+
const redirectRegex = /(?<![0-9=])>{1,2}\s*['"]?([^\s'";&|)]+)['"]?/g;
|
|
7181
7369
|
let match2;
|
|
7182
7370
|
while ((match2 = redirectRegex.exec(command)) !== null) {
|
|
7183
|
-
|
|
7371
|
+
if (isLikelyFilePath(match2[1])) {
|
|
7372
|
+
addChange("wrote", match2[1]);
|
|
7373
|
+
}
|
|
7184
7374
|
}
|
|
7185
7375
|
const teeRegex = /\btee\s+(?:-[a-zA-Z]\s+)*['"]?([^\s'";&|)]+)['"]?/g;
|
|
7186
7376
|
while ((match2 = teeRegex.exec(command)) !== null) {
|
|
@@ -9148,7 +9338,7 @@ minimatch.escape = escape;
|
|
|
9148
9338
|
minimatch.unescape = unescape;
|
|
9149
9339
|
|
|
9150
9340
|
// node_modules/glob/dist/esm/glob.js
|
|
9151
|
-
import { fileURLToPath as
|
|
9341
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
9152
9342
|
|
|
9153
9343
|
// node_modules/lru-cache/dist/esm/index.js
|
|
9154
9344
|
var defaultPerf = typeof performance === "object" && performance && typeof performance.now === "function" ? performance : Date;
|
|
@@ -10311,7 +10501,7 @@ class LRUCache {
|
|
|
10311
10501
|
|
|
10312
10502
|
// node_modules/path-scurry/dist/esm/index.js
|
|
10313
10503
|
import { posix, win32 } from "node:path";
|
|
10314
|
-
import { fileURLToPath as
|
|
10504
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10315
10505
|
import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps } from "fs";
|
|
10316
10506
|
import * as actualFS from "node:fs";
|
|
10317
10507
|
import { lstat, readdir, readlink, realpath } from "node:fs/promises";
|
|
@@ -10858,10 +11048,10 @@ class Minipass extends EventEmitter {
|
|
|
10858
11048
|
return this[ENCODING] ? buf.join("") : Buffer.concat(buf, buf.dataLength);
|
|
10859
11049
|
}
|
|
10860
11050
|
async promise() {
|
|
10861
|
-
return new Promise((
|
|
11051
|
+
return new Promise((resolve2, reject) => {
|
|
10862
11052
|
this.on(DESTROYED, () => reject(new Error("stream destroyed")));
|
|
10863
11053
|
this.on("error", (er) => reject(er));
|
|
10864
|
-
this.on("end", () =>
|
|
11054
|
+
this.on("end", () => resolve2());
|
|
10865
11055
|
});
|
|
10866
11056
|
}
|
|
10867
11057
|
[Symbol.asyncIterator]() {
|
|
@@ -10880,7 +11070,7 @@ class Minipass extends EventEmitter {
|
|
|
10880
11070
|
return Promise.resolve({ done: false, value: res });
|
|
10881
11071
|
if (this[EOF])
|
|
10882
11072
|
return stop();
|
|
10883
|
-
let
|
|
11073
|
+
let resolve2;
|
|
10884
11074
|
let reject;
|
|
10885
11075
|
const onerr = (er) => {
|
|
10886
11076
|
this.off("data", ondata);
|
|
@@ -10894,19 +11084,19 @@ class Minipass extends EventEmitter {
|
|
|
10894
11084
|
this.off("end", onend);
|
|
10895
11085
|
this.off(DESTROYED, ondestroy);
|
|
10896
11086
|
this.pause();
|
|
10897
|
-
|
|
11087
|
+
resolve2({ value, done: !!this[EOF] });
|
|
10898
11088
|
};
|
|
10899
11089
|
const onend = () => {
|
|
10900
11090
|
this.off("error", onerr);
|
|
10901
11091
|
this.off("data", ondata);
|
|
10902
11092
|
this.off(DESTROYED, ondestroy);
|
|
10903
11093
|
stop();
|
|
10904
|
-
|
|
11094
|
+
resolve2({ done: true, value: undefined });
|
|
10905
11095
|
};
|
|
10906
11096
|
const ondestroy = () => onerr(new Error("stream destroyed"));
|
|
10907
11097
|
return new Promise((res2, rej) => {
|
|
10908
11098
|
reject = rej;
|
|
10909
|
-
|
|
11099
|
+
resolve2 = res2;
|
|
10910
11100
|
this.once(DESTROYED, ondestroy);
|
|
10911
11101
|
this.once("error", onerr);
|
|
10912
11102
|
this.once("end", onend);
|
|
@@ -11606,8 +11796,8 @@ class PathBase {
|
|
|
11606
11796
|
if (this.#asyncReaddirInFlight) {
|
|
11607
11797
|
await this.#asyncReaddirInFlight;
|
|
11608
11798
|
} else {
|
|
11609
|
-
let
|
|
11610
|
-
this.#asyncReaddirInFlight = new Promise((res) =>
|
|
11799
|
+
let resolve2 = () => {};
|
|
11800
|
+
this.#asyncReaddirInFlight = new Promise((res) => resolve2 = res);
|
|
11611
11801
|
try {
|
|
11612
11802
|
for (const e of await this.#fs.promises.readdir(fullpath, {
|
|
11613
11803
|
withFileTypes: true
|
|
@@ -11620,7 +11810,7 @@ class PathBase {
|
|
|
11620
11810
|
children.provisional = 0;
|
|
11621
11811
|
}
|
|
11622
11812
|
this.#asyncReaddirInFlight = undefined;
|
|
11623
|
-
|
|
11813
|
+
resolve2();
|
|
11624
11814
|
}
|
|
11625
11815
|
return children.slice(0, children.provisional);
|
|
11626
11816
|
}
|
|
@@ -11766,7 +11956,7 @@ class PathScurryBase {
|
|
|
11766
11956
|
constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs = defaultFS } = {}) {
|
|
11767
11957
|
this.#fs = fsFromOption(fs);
|
|
11768
11958
|
if (cwd instanceof URL || cwd.startsWith("file://")) {
|
|
11769
|
-
cwd =
|
|
11959
|
+
cwd = fileURLToPath3(cwd);
|
|
11770
11960
|
}
|
|
11771
11961
|
const cwdPath = pathImpl.resolve(cwd);
|
|
11772
11962
|
this.roots = Object.create(null);
|
|
@@ -13070,7 +13260,7 @@ class Glob {
|
|
|
13070
13260
|
if (!opts.cwd) {
|
|
13071
13261
|
this.cwd = "";
|
|
13072
13262
|
} else if (opts.cwd instanceof URL || opts.cwd.startsWith("file://")) {
|
|
13073
|
-
opts.cwd =
|
|
13263
|
+
opts.cwd = fileURLToPath4(opts.cwd);
|
|
13074
13264
|
}
|
|
13075
13265
|
this.cwd = opts.cwd || "";
|
|
13076
13266
|
this.root = opts.root;
|
|
@@ -13415,7 +13605,11 @@ function buildQueryOptions(ctx) {
|
|
|
13415
13605
|
undoRollbackUuid,
|
|
13416
13606
|
sdkHooks,
|
|
13417
13607
|
adapter,
|
|
13418
|
-
onStderr
|
|
13608
|
+
onStderr,
|
|
13609
|
+
effort,
|
|
13610
|
+
thinking,
|
|
13611
|
+
taskBudget,
|
|
13612
|
+
betas
|
|
13419
13613
|
} = ctx;
|
|
13420
13614
|
const blockedTools = [...adapter.getBlockedBuiltinTools(), ...adapter.getAgentIncompatibleTools()];
|
|
13421
13615
|
const mcpServerName = adapter.getMcpServerName();
|
|
@@ -13454,7 +13648,11 @@ function buildQueryOptions(ctx) {
|
|
|
13454
13648
|
...Object.keys(sdkAgents).length > 0 ? { agents: sdkAgents } : {},
|
|
13455
13649
|
...resumeSessionId ? { resume: resumeSessionId } : {},
|
|
13456
13650
|
...isUndo ? { forkSession: true, ...undoRollbackUuid ? { resumeSessionAt: undoRollbackUuid } : {} } : {},
|
|
13457
|
-
...sdkHooks ? { hooks: sdkHooks } : {}
|
|
13651
|
+
...sdkHooks ? { hooks: sdkHooks } : {},
|
|
13652
|
+
...effort ? { effort } : {},
|
|
13653
|
+
...thinking ? { thinking } : {},
|
|
13654
|
+
...taskBudget ? { taskBudget } : {},
|
|
13655
|
+
...betas && betas.length > 0 ? { betas } : {}
|
|
13458
13656
|
}
|
|
13459
13657
|
};
|
|
13460
13658
|
}
|
|
@@ -13624,10 +13822,10 @@ class LRUMap {
|
|
|
13624
13822
|
// src/proxy/sessionStore.ts
|
|
13625
13823
|
import {
|
|
13626
13824
|
closeSync,
|
|
13627
|
-
existsSync as
|
|
13825
|
+
existsSync as existsSync3,
|
|
13628
13826
|
mkdirSync,
|
|
13629
13827
|
openSync,
|
|
13630
|
-
readFileSync,
|
|
13828
|
+
readFileSync as readFileSync2,
|
|
13631
13829
|
renameSync,
|
|
13632
13830
|
statSync,
|
|
13633
13831
|
unlinkSync,
|
|
@@ -13682,7 +13880,7 @@ var sessionDirOverride = null;
|
|
|
13682
13880
|
var skipLocking = false;
|
|
13683
13881
|
function getStorePath() {
|
|
13684
13882
|
const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
|
|
13685
|
-
if (!
|
|
13883
|
+
if (!existsSync3(dir)) {
|
|
13686
13884
|
mkdirSync(dir, { recursive: true });
|
|
13687
13885
|
}
|
|
13688
13886
|
return join2(dir, "sessions.json");
|
|
@@ -13690,9 +13888,9 @@ function getStorePath() {
|
|
|
13690
13888
|
function getDefaultCacheDir() {
|
|
13691
13889
|
const newDir = join2(homedir(), ".cache", "meridian");
|
|
13692
13890
|
const oldDir = join2(homedir(), ".cache", "opencode-claude-max-proxy");
|
|
13693
|
-
if (
|
|
13891
|
+
if (existsSync3(newDir))
|
|
13694
13892
|
return newDir;
|
|
13695
|
-
if (
|
|
13893
|
+
if (existsSync3(oldDir)) {
|
|
13696
13894
|
try {
|
|
13697
13895
|
const { symlinkSync } = __require("fs");
|
|
13698
13896
|
symlinkSync(oldDir, newDir);
|
|
@@ -13705,10 +13903,10 @@ function getDefaultCacheDir() {
|
|
|
13705
13903
|
}
|
|
13706
13904
|
function readStore() {
|
|
13707
13905
|
const path3 = getStorePath();
|
|
13708
|
-
if (!
|
|
13906
|
+
if (!existsSync3(path3))
|
|
13709
13907
|
return {};
|
|
13710
13908
|
try {
|
|
13711
|
-
const data =
|
|
13909
|
+
const data = readFileSync2(path3, "utf-8");
|
|
13712
13910
|
return JSON.parse(data);
|
|
13713
13911
|
} catch (e) {
|
|
13714
13912
|
console.error("[sessionStore] read failed:", e.message);
|
|
@@ -13734,7 +13932,19 @@ function lookupSharedSession(key) {
|
|
|
13734
13932
|
const store = readStore();
|
|
13735
13933
|
return store[key];
|
|
13736
13934
|
}
|
|
13737
|
-
function
|
|
13935
|
+
function lookupSharedSessionByClaudeId(claudeSessionId) {
|
|
13936
|
+
const sessions = Object.values(readStore());
|
|
13937
|
+
let newest;
|
|
13938
|
+
for (const session of sessions) {
|
|
13939
|
+
if (session.claudeSessionId !== claudeSessionId)
|
|
13940
|
+
continue;
|
|
13941
|
+
if (!newest || session.lastUsedAt > newest.lastUsedAt) {
|
|
13942
|
+
newest = session;
|
|
13943
|
+
}
|
|
13944
|
+
}
|
|
13945
|
+
return newest;
|
|
13946
|
+
}
|
|
13947
|
+
function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, messageHashes, sdkMessageUuids, contextUsage) {
|
|
13738
13948
|
const path3 = getStorePath();
|
|
13739
13949
|
const lockPath = `${path3}.lock`;
|
|
13740
13950
|
const hasLock = skipLocking ? false : acquireLock(lockPath);
|
|
@@ -13751,7 +13961,8 @@ function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, mes
|
|
|
13751
13961
|
messageCount: messageCount ?? existing?.messageCount ?? 0,
|
|
13752
13962
|
lineageHash: lineageHash ?? existing?.lineageHash,
|
|
13753
13963
|
messageHashes: messageHashes ?? existing?.messageHashes,
|
|
13754
|
-
sdkMessageUuids: sdkMessageUuids ?? existing?.sdkMessageUuids
|
|
13964
|
+
sdkMessageUuids: sdkMessageUuids ?? existing?.sdkMessageUuids,
|
|
13965
|
+
contextUsage: contextUsage ?? existing?.contextUsage
|
|
13755
13966
|
};
|
|
13756
13967
|
const maxEntries = getMaxStoredSessions();
|
|
13757
13968
|
const keys = Object.keys(store);
|
|
@@ -13897,7 +14108,8 @@ function lookupSession(sessionId, messages, workingDirectory) {
|
|
|
13897
14108
|
messageCount: shared.messageCount || 0,
|
|
13898
14109
|
lineageHash: shared.lineageHash || "",
|
|
13899
14110
|
messageHashes: shared.messageHashes,
|
|
13900
|
-
sdkMessageUuids: shared.sdkMessageUuids
|
|
14111
|
+
sdkMessageUuids: shared.sdkMessageUuids,
|
|
14112
|
+
contextUsage: shared.contextUsage
|
|
13901
14113
|
};
|
|
13902
14114
|
const result = verifyLineage(state, messages, sessionId, sessionCache);
|
|
13903
14115
|
if (result.type === "continuation" || result.type === "compaction") {
|
|
@@ -13924,7 +14136,8 @@ function lookupSession(sessionId, messages, workingDirectory) {
|
|
|
13924
14136
|
messageCount: shared.messageCount || 0,
|
|
13925
14137
|
lineageHash: shared.lineageHash || "",
|
|
13926
14138
|
messageHashes: shared.messageHashes,
|
|
13927
|
-
sdkMessageUuids: shared.sdkMessageUuids
|
|
14139
|
+
sdkMessageUuids: shared.sdkMessageUuids,
|
|
14140
|
+
contextUsage: shared.contextUsage
|
|
13928
14141
|
};
|
|
13929
14142
|
const result = verifyLineage(state, messages, fp, fingerprintCache);
|
|
13930
14143
|
if (result.type === "continuation" || result.type === "compaction") {
|
|
@@ -13935,7 +14148,34 @@ function lookupSession(sessionId, messages, workingDirectory) {
|
|
|
13935
14148
|
}
|
|
13936
14149
|
return { type: "diverged" };
|
|
13937
14150
|
}
|
|
13938
|
-
function
|
|
14151
|
+
function getSessionByClaudeId(claudeSessionId) {
|
|
14152
|
+
let newest;
|
|
14153
|
+
const consider = (state) => {
|
|
14154
|
+
if (!state || state.claudeSessionId !== claudeSessionId)
|
|
14155
|
+
return;
|
|
14156
|
+
if (!newest || state.lastAccess > newest.lastAccess) {
|
|
14157
|
+
newest = state;
|
|
14158
|
+
}
|
|
14159
|
+
};
|
|
14160
|
+
for (const state of sessionCache.values())
|
|
14161
|
+
consider(state);
|
|
14162
|
+
for (const state of fingerprintCache.values())
|
|
14163
|
+
consider(state);
|
|
14164
|
+
const shared = lookupSharedSessionByClaudeId(claudeSessionId);
|
|
14165
|
+
if (shared) {
|
|
14166
|
+
consider({
|
|
14167
|
+
claudeSessionId: shared.claudeSessionId,
|
|
14168
|
+
lastAccess: shared.lastUsedAt,
|
|
14169
|
+
messageCount: shared.messageCount || 0,
|
|
14170
|
+
lineageHash: shared.lineageHash || "",
|
|
14171
|
+
messageHashes: shared.messageHashes,
|
|
14172
|
+
sdkMessageUuids: shared.sdkMessageUuids,
|
|
14173
|
+
contextUsage: shared.contextUsage
|
|
14174
|
+
});
|
|
14175
|
+
}
|
|
14176
|
+
return newest;
|
|
14177
|
+
}
|
|
14178
|
+
function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sdkMessageUuids, contextUsage) {
|
|
13939
14179
|
if (!claudeSessionId)
|
|
13940
14180
|
return;
|
|
13941
14181
|
const lineageHash = computeLineageHash(messages);
|
|
@@ -13946,7 +14186,8 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
|
|
|
13946
14186
|
messageCount: messages?.length || 0,
|
|
13947
14187
|
lineageHash,
|
|
13948
14188
|
messageHashes,
|
|
13949
|
-
sdkMessageUuids
|
|
14189
|
+
sdkMessageUuids,
|
|
14190
|
+
...contextUsage ? { contextUsage } : {}
|
|
13950
14191
|
};
|
|
13951
14192
|
if (sessionId)
|
|
13952
14193
|
sessionCache.set(sessionId, state);
|
|
@@ -13954,8 +14195,9 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
|
|
|
13954
14195
|
if (fp)
|
|
13955
14196
|
fingerprintCache.set(fp, state);
|
|
13956
14197
|
const key = sessionId || fp;
|
|
13957
|
-
if (key)
|
|
13958
|
-
storeSharedSession(key, claudeSessionId, state.messageCount, lineageHash, messageHashes, sdkMessageUuids);
|
|
14198
|
+
if (key) {
|
|
14199
|
+
storeSharedSession(key, claudeSessionId, state.messageCount, lineageHash, messageHashes, sdkMessageUuids, contextUsage);
|
|
14200
|
+
}
|
|
13959
14201
|
}
|
|
13960
14202
|
|
|
13961
14203
|
// src/proxy/server.ts
|
|
@@ -14033,6 +14275,16 @@ function buildFreshPrompt(messages, stripCacheControl) {
|
|
|
14033
14275
|
|
|
14034
14276
|
`) || "";
|
|
14035
14277
|
}
|
|
14278
|
+
function logUsage(requestId, usage) {
|
|
14279
|
+
const fmt = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
|
|
14280
|
+
const parts = [
|
|
14281
|
+
`input=${fmt(usage.input_tokens ?? 0)}`,
|
|
14282
|
+
`output=${fmt(usage.output_tokens ?? 0)}`,
|
|
14283
|
+
...usage.cache_read_input_tokens ? [`cache_read=${fmt(usage.cache_read_input_tokens)}`] : [],
|
|
14284
|
+
...usage.cache_creation_input_tokens ? [`cache_write=${fmt(usage.cache_creation_input_tokens)}`] : []
|
|
14285
|
+
];
|
|
14286
|
+
console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}`);
|
|
14287
|
+
}
|
|
14036
14288
|
function createProxyServer(config = {}) {
|
|
14037
14289
|
const finalConfig = { ...DEFAULT_PROXY_CONFIG, ...config };
|
|
14038
14290
|
const app = new Hono2;
|
|
@@ -14044,7 +14296,7 @@ function createProxyServer(config = {}) {
|
|
|
14044
14296
|
status: "ok",
|
|
14045
14297
|
service: "meridian",
|
|
14046
14298
|
format: "anthropic",
|
|
14047
|
-
endpoints: ["/v1/messages", "/messages", "/telemetry", "/health"]
|
|
14299
|
+
endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/models", "/telemetry", "/health"]
|
|
14048
14300
|
});
|
|
14049
14301
|
}
|
|
14050
14302
|
return c.html(landingHtml);
|
|
@@ -14057,8 +14309,8 @@ function createProxyServer(config = {}) {
|
|
|
14057
14309
|
activeSessions++;
|
|
14058
14310
|
return;
|
|
14059
14311
|
}
|
|
14060
|
-
return new Promise((
|
|
14061
|
-
sessionQueue.push({ resolve:
|
|
14312
|
+
return new Promise((resolve3) => {
|
|
14313
|
+
sessionQueue.push({ resolve: resolve3 });
|
|
14062
14314
|
});
|
|
14063
14315
|
}
|
|
14064
14316
|
function releaseSession() {
|
|
@@ -14094,11 +14346,15 @@ function createProxyServer(config = {}) {
|
|
|
14094
14346
|
return textPrompt;
|
|
14095
14347
|
};
|
|
14096
14348
|
const body = await c.req.json();
|
|
14349
|
+
if (!Array.isArray(body.messages)) {
|
|
14350
|
+
return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
|
|
14351
|
+
}
|
|
14097
14352
|
const authStatus = await getClaudeAuthStatusAsync();
|
|
14098
|
-
|
|
14353
|
+
const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
|
|
14354
|
+
let model = mapModelToClaudeModel(body.model || "sonnet", authStatus?.subscriptionType, agentMode);
|
|
14099
14355
|
const adapter = detectAdapter(c);
|
|
14100
14356
|
const adapterStreamPref = adapter.prefersStreaming?.(body);
|
|
14101
|
-
const stream2 = adapterStreamPref !== undefined ? adapterStreamPref : body.stream ??
|
|
14357
|
+
const stream2 = adapterStreamPref !== undefined ? adapterStreamPref : body.stream ?? false;
|
|
14102
14358
|
const workingDirectory = (process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR) || adapter.extractWorkingDirectory(body) || process.cwd();
|
|
14103
14359
|
const {
|
|
14104
14360
|
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,
|
|
@@ -14116,6 +14372,22 @@ function createProxyServer(config = {}) {
|
|
|
14116
14372
|
`);
|
|
14117
14373
|
}
|
|
14118
14374
|
}
|
|
14375
|
+
const effortHeader = c.req.header("x-opencode-effort");
|
|
14376
|
+
const thinkingHeader = c.req.header("x-opencode-thinking");
|
|
14377
|
+
const taskBudgetHeader = c.req.header("x-opencode-task-budget");
|
|
14378
|
+
const betaHeader = c.req.header("anthropic-beta");
|
|
14379
|
+
const effort = effortHeader || body.effort || undefined;
|
|
14380
|
+
let thinking = body.thinking || undefined;
|
|
14381
|
+
if (thinkingHeader !== undefined) {
|
|
14382
|
+
try {
|
|
14383
|
+
thinking = JSON.parse(thinkingHeader);
|
|
14384
|
+
} catch (e) {
|
|
14385
|
+
console.error(`[PROXY] ${requestMeta.requestId} ignoring malformed x-opencode-thinking header: ${e instanceof Error ? e.message : String(e)}`);
|
|
14386
|
+
}
|
|
14387
|
+
}
|
|
14388
|
+
const parsedBudget = taskBudgetHeader ? Number.parseInt(taskBudgetHeader, 10) : NaN;
|
|
14389
|
+
const taskBudget = Number.isFinite(parsedBudget) ? { total: parsedBudget } : body.task_budget ? { total: body.task_budget.total ?? body.task_budget } : undefined;
|
|
14390
|
+
const betas = betaHeader ? betaHeader.split(",").map((b) => b.trim()).filter(Boolean) : undefined;
|
|
14119
14391
|
const agentSessionId = adapter.getSessionId(c);
|
|
14120
14392
|
const lineageResult = lookupSession(agentSessionId, body.messages || [], workingDirectory);
|
|
14121
14393
|
const isResume = lineageResult.type === "continuation" || lineageResult.type === "compaction";
|
|
@@ -14129,7 +14401,7 @@ function createProxyServer(config = {}) {
|
|
|
14129
14401
|
}).join(" → ");
|
|
14130
14402
|
const lineageType = lineageResult.type === "diverged" && !cachedSession ? "new" : lineageResult.type;
|
|
14131
14403
|
const msgCount = Array.isArray(body.messages) ? body.messages.length : 0;
|
|
14132
|
-
const requestLogLine = `${requestMeta.requestId} model=${model} stream=${stream2} tools=${body.tools?.length ?? 0} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
|
|
14404
|
+
const requestLogLine = `${requestMeta.requestId} model=${model} stream=${stream2} tools=${body.tools?.length ?? 0} lineage=${lineageType} session=${resumeSessionId?.slice(0, 8) || "new"}${isUndo && undoRollbackUuid ? ` rollback=${undoRollbackUuid.slice(0, 8)}` : ""}${agentMode ? ` agent=${agentMode}` : ""} active=${activeSessions}/${MAX_CONCURRENT_SESSIONS} msgCount=${msgCount}`;
|
|
14133
14405
|
console.error(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
|
|
14134
14406
|
diagnosticLog.session(`${requestLogLine}`, requestMeta.requestId);
|
|
14135
14407
|
claudeLog("request.received", {
|
|
@@ -14289,6 +14561,7 @@ function createProxyServer(config = {}) {
|
|
|
14289
14561
|
while (sdkUuidMap.length < allMessages.length)
|
|
14290
14562
|
sdkUuidMap.push(null);
|
|
14291
14563
|
claudeLog("upstream.start", { mode: "non_stream", model });
|
|
14564
|
+
let lastUsage;
|
|
14292
14565
|
try {
|
|
14293
14566
|
if (!claudeExecutable) {
|
|
14294
14567
|
claudeExecutable = await resolveClaudeExecutableAsync();
|
|
@@ -14317,9 +14590,13 @@ function createProxyServer(config = {}) {
|
|
|
14317
14590
|
undoRollbackUuid,
|
|
14318
14591
|
sdkHooks,
|
|
14319
14592
|
adapter,
|
|
14320
|
-
onStderr
|
|
14593
|
+
onStderr,
|
|
14594
|
+
effort,
|
|
14595
|
+
thinking,
|
|
14596
|
+
taskBudget,
|
|
14597
|
+
betas
|
|
14321
14598
|
}))) {
|
|
14322
|
-
if (event.type === "assistant") {
|
|
14599
|
+
if (event.type === "assistant" && !event.error) {
|
|
14323
14600
|
didYieldContent = true;
|
|
14324
14601
|
}
|
|
14325
14602
|
yield event;
|
|
@@ -14356,20 +14633,25 @@ function createProxyServer(config = {}) {
|
|
|
14356
14633
|
undoRollbackUuid: undefined,
|
|
14357
14634
|
sdkHooks,
|
|
14358
14635
|
adapter,
|
|
14359
|
-
onStderr
|
|
14636
|
+
onStderr,
|
|
14637
|
+
effort,
|
|
14638
|
+
thinking,
|
|
14639
|
+
taskBudget,
|
|
14640
|
+
betas
|
|
14360
14641
|
}));
|
|
14361
14642
|
return;
|
|
14362
14643
|
}
|
|
14363
14644
|
if (isExtraUsageRequiredError(errMsg) && hasExtendedContext(model)) {
|
|
14364
14645
|
const from = model;
|
|
14365
14646
|
model = stripExtendedContext(model);
|
|
14647
|
+
recordExtendedContextUnavailable();
|
|
14366
14648
|
claudeLog("upstream.context_fallback", {
|
|
14367
14649
|
mode: "non_stream",
|
|
14368
14650
|
from,
|
|
14369
14651
|
to: model,
|
|
14370
14652
|
reason: "extra_usage_required"
|
|
14371
14653
|
});
|
|
14372
|
-
console.error(`[PROXY] ${requestMeta.requestId} extra usage required for [1m], falling back to ${model}`);
|
|
14654
|
+
console.error(`[PROXY] ${requestMeta.requestId} extra usage required for [1m], falling back to ${model} (skipping [1m] for 1h)`);
|
|
14373
14655
|
continue;
|
|
14374
14656
|
}
|
|
14375
14657
|
if (isExpiredTokenError(errMsg) && !tokenRefreshed) {
|
|
@@ -14437,6 +14719,9 @@ function createProxyServer(config = {}) {
|
|
|
14437
14719
|
}
|
|
14438
14720
|
contentBlocks.push(b);
|
|
14439
14721
|
}
|
|
14722
|
+
const msgUsage = message.message.usage;
|
|
14723
|
+
if (msgUsage)
|
|
14724
|
+
lastUsage = { ...lastUsage, ...msgUsage };
|
|
14440
14725
|
}
|
|
14441
14726
|
}
|
|
14442
14727
|
claudeLog("upstream.completed", {
|
|
@@ -14445,6 +14730,8 @@ function createProxyServer(config = {}) {
|
|
|
14445
14730
|
assistantMessages,
|
|
14446
14731
|
durationMs: Date.now() - upstreamStartAt
|
|
14447
14732
|
});
|
|
14733
|
+
if (lastUsage)
|
|
14734
|
+
logUsage(requestMeta.requestId, lastUsage);
|
|
14448
14735
|
} catch (error) {
|
|
14449
14736
|
const stderrOutput = stderrLines.join(`
|
|
14450
14737
|
`).trim();
|
|
@@ -14529,7 +14816,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14529
14816
|
error: null
|
|
14530
14817
|
});
|
|
14531
14818
|
if (currentSessionId) {
|
|
14532
|
-
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap);
|
|
14819
|
+
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
|
|
14533
14820
|
}
|
|
14534
14821
|
const responseSessionId = currentSessionId || resumeSessionId || `session_${Date.now()}`;
|
|
14535
14822
|
return new Response(JSON.stringify({
|
|
@@ -14583,6 +14870,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14583
14870
|
while (sdkUuidMap.length < allMessages.length)
|
|
14584
14871
|
sdkUuidMap.push(null);
|
|
14585
14872
|
let messageStartEmitted = false;
|
|
14873
|
+
let lastUsage;
|
|
14586
14874
|
try {
|
|
14587
14875
|
let currentSessionId;
|
|
14588
14876
|
const MAX_RATE_LIMIT_RETRIES = 2;
|
|
@@ -14609,7 +14897,11 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14609
14897
|
undoRollbackUuid,
|
|
14610
14898
|
sdkHooks,
|
|
14611
14899
|
adapter,
|
|
14612
|
-
onStderr
|
|
14900
|
+
onStderr,
|
|
14901
|
+
effort,
|
|
14902
|
+
thinking,
|
|
14903
|
+
taskBudget,
|
|
14904
|
+
betas
|
|
14613
14905
|
}))) {
|
|
14614
14906
|
if (event.type === "stream_event") {
|
|
14615
14907
|
didYieldClientEvent = true;
|
|
@@ -14648,20 +14940,25 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14648
14940
|
undoRollbackUuid: undefined,
|
|
14649
14941
|
sdkHooks,
|
|
14650
14942
|
adapter,
|
|
14651
|
-
onStderr
|
|
14943
|
+
onStderr,
|
|
14944
|
+
effort,
|
|
14945
|
+
thinking,
|
|
14946
|
+
taskBudget,
|
|
14947
|
+
betas
|
|
14652
14948
|
}));
|
|
14653
14949
|
return;
|
|
14654
14950
|
}
|
|
14655
14951
|
if (isExtraUsageRequiredError(errMsg) && hasExtendedContext(model)) {
|
|
14656
14952
|
const from = model;
|
|
14657
14953
|
model = stripExtendedContext(model);
|
|
14954
|
+
recordExtendedContextUnavailable();
|
|
14658
14955
|
claudeLog("upstream.context_fallback", {
|
|
14659
14956
|
mode: "stream",
|
|
14660
14957
|
from,
|
|
14661
14958
|
to: model,
|
|
14662
14959
|
reason: "extra_usage_required"
|
|
14663
14960
|
});
|
|
14664
|
-
console.error(`[PROXY] ${requestMeta.requestId} extra usage required for [1m], falling back to ${model}`);
|
|
14961
|
+
console.error(`[PROXY] ${requestMeta.requestId} extra usage required for [1m], falling back to ${model} (skipping [1m] for 1h)`);
|
|
14665
14962
|
continue;
|
|
14666
14963
|
}
|
|
14667
14964
|
if (isExpiredTokenError(errMsg) && !tokenRefreshed) {
|
|
@@ -14757,6 +15054,9 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14757
15054
|
if (eventType === "message_start") {
|
|
14758
15055
|
skipBlockIndices.clear();
|
|
14759
15056
|
sdkToClientIndex.clear();
|
|
15057
|
+
const startUsage = event.message?.usage;
|
|
15058
|
+
if (startUsage)
|
|
15059
|
+
lastUsage = { ...lastUsage, ...startUsage };
|
|
14760
15060
|
if (messageStartEmitted) {
|
|
14761
15061
|
continue;
|
|
14762
15062
|
}
|
|
@@ -14789,6 +15089,9 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14789
15089
|
event.index = sdkToClientIndex.get(eventIndex);
|
|
14790
15090
|
}
|
|
14791
15091
|
if (eventType === "message_delta") {
|
|
15092
|
+
const deltaUsage = event.usage;
|
|
15093
|
+
if (deltaUsage)
|
|
15094
|
+
lastUsage = { ...lastUsage, ...deltaUsage };
|
|
14792
15095
|
const stopReason = event.delta?.stop_reason;
|
|
14793
15096
|
if (stopReason === "tool_use" && skipBlockIndices.size > 0) {
|
|
14794
15097
|
continue;
|
|
@@ -14830,8 +15133,10 @@ data: ${JSON.stringify({ type: "message_stop" })}
|
|
|
14830
15133
|
eventsForwarded,
|
|
14831
15134
|
textEventsForwarded
|
|
14832
15135
|
});
|
|
15136
|
+
if (lastUsage)
|
|
15137
|
+
logUsage(requestMeta.requestId, lastUsage);
|
|
14833
15138
|
if (currentSessionId) {
|
|
14834
|
-
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap);
|
|
15139
|
+
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
|
|
14835
15140
|
}
|
|
14836
15141
|
if (!streamClosed) {
|
|
14837
15142
|
const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
|
|
@@ -15106,7 +15411,8 @@ data: ${JSON.stringify({
|
|
|
15106
15411
|
email: auth.email,
|
|
15107
15412
|
subscriptionType: auth.subscriptionType
|
|
15108
15413
|
},
|
|
15109
|
-
mode: process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH ? "passthrough" : "internal"
|
|
15414
|
+
mode: process.env.MERIDIAN_PASSTHROUGH ?? process.env.CLAUDE_PROXY_PASSTHROUGH ? "passthrough" : "internal",
|
|
15415
|
+
plugin: { opencode: checkPluginConfigured() ? "configured" : "not-configured" }
|
|
15110
15416
|
});
|
|
15111
15417
|
} catch {
|
|
15112
15418
|
return c.json({
|
|
@@ -15123,6 +15429,105 @@ data: ${JSON.stringify({
|
|
|
15123
15429
|
}
|
|
15124
15430
|
return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
|
|
15125
15431
|
});
|
|
15432
|
+
app.post("/v1/chat/completions", async (c) => {
|
|
15433
|
+
const rawBody = await c.req.json();
|
|
15434
|
+
const anthropicBody = translateOpenAiToAnthropic(rawBody);
|
|
15435
|
+
if (!anthropicBody) {
|
|
15436
|
+
return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
|
|
15437
|
+
}
|
|
15438
|
+
const internalReq = new Request("http://internal/v1/messages", {
|
|
15439
|
+
method: "POST",
|
|
15440
|
+
headers: { "Content-Type": "application/json" },
|
|
15441
|
+
body: JSON.stringify(anthropicBody)
|
|
15442
|
+
});
|
|
15443
|
+
const internalRes = await app.fetch(internalReq);
|
|
15444
|
+
if (!internalRes.ok) {
|
|
15445
|
+
const errBody = await internalRes.text();
|
|
15446
|
+
return c.json({ type: "error", error: { type: "upstream_error", message: errBody } }, internalRes.status);
|
|
15447
|
+
}
|
|
15448
|
+
const completionId = `chatcmpl-${randomUUID()}`;
|
|
15449
|
+
const created = Math.floor(Date.now() / 1000);
|
|
15450
|
+
const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : "claude-sonnet-4-6";
|
|
15451
|
+
if (!anthropicBody.stream) {
|
|
15452
|
+
const anthropicRes = await internalRes.json();
|
|
15453
|
+
return c.json(translateAnthropicToOpenAi(anthropicRes, completionId, model, created));
|
|
15454
|
+
}
|
|
15455
|
+
const encoder = new TextEncoder;
|
|
15456
|
+
const readable = new ReadableStream({
|
|
15457
|
+
async start(controller) {
|
|
15458
|
+
const reader = internalRes.body?.getReader();
|
|
15459
|
+
if (!reader) {
|
|
15460
|
+
controller.close();
|
|
15461
|
+
return;
|
|
15462
|
+
}
|
|
15463
|
+
const decoder = new TextDecoder;
|
|
15464
|
+
let buffer = "";
|
|
15465
|
+
let streamError = null;
|
|
15466
|
+
try {
|
|
15467
|
+
while (true) {
|
|
15468
|
+
const { done, value } = await reader.read();
|
|
15469
|
+
if (done)
|
|
15470
|
+
break;
|
|
15471
|
+
buffer += decoder.decode(value, { stream: true });
|
|
15472
|
+
const lines = buffer.split(`
|
|
15473
|
+
`);
|
|
15474
|
+
buffer = lines.pop() ?? "";
|
|
15475
|
+
for (const line of lines) {
|
|
15476
|
+
if (!line.startsWith("data: "))
|
|
15477
|
+
continue;
|
|
15478
|
+
const dataStr = line.slice(6).trim();
|
|
15479
|
+
if (!dataStr)
|
|
15480
|
+
continue;
|
|
15481
|
+
let event;
|
|
15482
|
+
try {
|
|
15483
|
+
event = JSON.parse(dataStr);
|
|
15484
|
+
} catch {
|
|
15485
|
+
continue;
|
|
15486
|
+
}
|
|
15487
|
+
if (typeof event.type !== "string")
|
|
15488
|
+
continue;
|
|
15489
|
+
const chunk = translateAnthropicSseEvent(event, completionId, model, created);
|
|
15490
|
+
if (chunk)
|
|
15491
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
|
|
15492
|
+
|
|
15493
|
+
`));
|
|
15494
|
+
}
|
|
15495
|
+
}
|
|
15496
|
+
} catch (err) {
|
|
15497
|
+
streamError = err instanceof Error ? err : new Error(String(err));
|
|
15498
|
+
} finally {
|
|
15499
|
+
if (!streamError)
|
|
15500
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
15501
|
+
|
|
15502
|
+
`));
|
|
15503
|
+
controller.close();
|
|
15504
|
+
}
|
|
15505
|
+
}
|
|
15506
|
+
});
|
|
15507
|
+
return new Response(readable, {
|
|
15508
|
+
headers: {
|
|
15509
|
+
"Content-Type": "text/event-stream",
|
|
15510
|
+
"Cache-Control": "no-cache",
|
|
15511
|
+
Connection: "keep-alive"
|
|
15512
|
+
}
|
|
15513
|
+
});
|
|
15514
|
+
});
|
|
15515
|
+
app.get("/v1/models", async (c) => {
|
|
15516
|
+
const authStatus = await getClaudeAuthStatusAsync();
|
|
15517
|
+
const isMax = authStatus?.subscriptionType === "max";
|
|
15518
|
+
return c.json({ object: "list", data: buildModelList(isMax) });
|
|
15519
|
+
});
|
|
15520
|
+
app.get("/v1/sessions/:claudeSessionId/context-usage", (c) => {
|
|
15521
|
+
const claudeSessionId = c.req.param("claudeSessionId");
|
|
15522
|
+
const session = getSessionByClaudeId(claudeSessionId);
|
|
15523
|
+
if (!session) {
|
|
15524
|
+
return c.json({ error: "Session not found" }, 404);
|
|
15525
|
+
}
|
|
15526
|
+
if (!session.contextUsage) {
|
|
15527
|
+
return c.json({ error: "No usage data available for this session" }, 404);
|
|
15528
|
+
}
|
|
15529
|
+
return c.json({ session_id: claudeSessionId, context_usage: session.contextUsage });
|
|
15530
|
+
});
|
|
15126
15531
|
app.all("*", (c) => {
|
|
15127
15532
|
console.error(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
|
|
15128
15533
|
return c.json({ error: { type: "not_found", message: `Endpoint not supported: ${c.req.method} ${new URL(c.req.url).pathname}` } }, 404);
|
|
@@ -15166,8 +15571,8 @@ Or use a different port:`);
|
|
|
15166
15571
|
server,
|
|
15167
15572
|
config: finalConfig,
|
|
15168
15573
|
async close() {
|
|
15169
|
-
await new Promise((
|
|
15170
|
-
server.close((err) => err ? reject(err) :
|
|
15574
|
+
await new Promise((resolve3, reject) => {
|
|
15575
|
+
server.close((err) => err ? reject(err) : resolve3());
|
|
15171
15576
|
});
|
|
15172
15577
|
}
|
|
15173
15578
|
};
|