@rynfar/meridian 1.52.0 → 1.54.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.
- package/README.md +84 -16
- package/dist/{profileCli-s1h4vh8w.js → cli-jhm27q0x.js} +346 -31
- package/dist/{cli-tq3vtp0q.js → cli-mqht6rs9.js} +905 -61
- package/dist/cli.js +3 -3
- package/dist/grepTool.d.ts +23 -0
- package/dist/grepTool.d.ts.map +1 -0
- package/dist/mcpTools.d.ts.map +1 -1
- package/dist/profileCli-d9tcp916.js +35 -0
- package/dist/proxy/adapters/codex.d.ts +22 -0
- package/dist/proxy/adapters/codex.d.ts.map +1 -0
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/design.d.ts +82 -0
- package/dist/proxy/design.d.ts.map +1 -0
- package/dist/proxy/openai.d.ts +5 -0
- package/dist/proxy/openai.d.ts.map +1 -1
- package/dist/proxy/openaiResponses.d.ts +137 -0
- package/dist/proxy/openaiResponses.d.ts.map +1 -0
- package/dist/proxy/profileCli.d.ts +48 -0
- package/dist/proxy/profileCli.d.ts.map +1 -0
- package/dist/proxy/sdkFeatures.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/transforms/codex.d.ts +12 -0
- package/dist/proxy/transforms/codex.d.ts.map +1 -0
- package/dist/proxy/transforms/opencode.d.ts.map +1 -1
- package/dist/proxy/transforms/registry.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/plugin/meridian.ts +58 -13
- package/dist/cli-vjeftz4z.js +0 -329
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
} from "./cli-xmweegb1.js";
|
|
24
24
|
import {
|
|
25
25
|
CANONICAL_SONNET_MODEL,
|
|
26
|
+
OAUTH_CLIENT_ID,
|
|
27
|
+
OAUTH_REDIRECT_URI,
|
|
28
|
+
OAUTH_TOKEN_URL,
|
|
29
|
+
createManualOAuthSession,
|
|
26
30
|
env,
|
|
27
31
|
envBool,
|
|
28
32
|
envInt,
|
|
@@ -34,12 +38,13 @@ import {
|
|
|
34
38
|
init_env,
|
|
35
39
|
isClosedControllerError,
|
|
36
40
|
mapModelToClaudeModel,
|
|
41
|
+
parseAuthorizationCodeInput,
|
|
37
42
|
recordExtendedContextUnavailable,
|
|
38
43
|
resolveClaudeExecutableAsync,
|
|
39
44
|
resolvePassthrough,
|
|
40
45
|
resolveSdkModelDefaults,
|
|
41
46
|
stripExtendedContext
|
|
42
|
-
} from "./cli-
|
|
47
|
+
} from "./cli-jhm27q0x.js";
|
|
43
48
|
import {
|
|
44
49
|
getSetting
|
|
45
50
|
} from "./cli-340h1chz.js";
|
|
@@ -1482,13 +1487,13 @@ __export(exports_sdkFeatures, {
|
|
|
1482
1487
|
getAllFeatureConfigs: () => getAllFeatureConfigs
|
|
1483
1488
|
});
|
|
1484
1489
|
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3 } from "node:fs";
|
|
1485
|
-
import { join as
|
|
1486
|
-
import { homedir as
|
|
1490
|
+
import { join as join7 } from "node:path";
|
|
1491
|
+
import { homedir as homedir6 } from "node:os";
|
|
1487
1492
|
function getConfigPath2() {
|
|
1488
|
-
const dir =
|
|
1493
|
+
const dir = join7(homedir6(), ".config", "meridian");
|
|
1489
1494
|
if (!existsSync7(dir))
|
|
1490
1495
|
mkdirSync3(dir, { recursive: true });
|
|
1491
|
-
return
|
|
1496
|
+
return join7(dir, "sdk-features.json");
|
|
1492
1497
|
}
|
|
1493
1498
|
function readConfig() {
|
|
1494
1499
|
const now = Date.now();
|
|
@@ -1534,7 +1539,7 @@ function getExplicitThinking(adapterName) {
|
|
|
1534
1539
|
return typeof explicit === "string" && VALID_THINKING_VALUES.has(explicit) ? explicit : undefined;
|
|
1535
1540
|
}
|
|
1536
1541
|
function getAllFeatureConfigs() {
|
|
1537
|
-
const adapters = ["opencode", "crush", "forgecode", "pi", "droid", "passthrough", "openai"];
|
|
1542
|
+
const adapters = ["opencode", "crush", "forgecode", "pi", "droid", "passthrough", "openai", "codex"];
|
|
1538
1543
|
const result = {};
|
|
1539
1544
|
for (const name of adapters) {
|
|
1540
1545
|
result[name] = getFeaturesForAdapter(name);
|
|
@@ -1612,6 +1617,9 @@ var init_sdkFeatures = __esm(() => {
|
|
|
1612
1617
|
},
|
|
1613
1618
|
cherry: {
|
|
1614
1619
|
codeSystemPrompt: false
|
|
1620
|
+
},
|
|
1621
|
+
codex: {
|
|
1622
|
+
codeSystemPrompt: false
|
|
1615
1623
|
}
|
|
1616
1624
|
};
|
|
1617
1625
|
VALID_CLAUDE_MD_VALUES = new Set(["off", "project", "full"]);
|
|
@@ -3638,6 +3646,112 @@ var cors = (options) => {
|
|
|
3638
3646
|
};
|
|
3639
3647
|
};
|
|
3640
3648
|
|
|
3649
|
+
// node_modules/hono/dist/utils/stream.js
|
|
3650
|
+
var StreamingApi = class {
|
|
3651
|
+
writer;
|
|
3652
|
+
encoder;
|
|
3653
|
+
writable;
|
|
3654
|
+
abortSubscribers = [];
|
|
3655
|
+
responseReadable;
|
|
3656
|
+
aborted = false;
|
|
3657
|
+
closed = false;
|
|
3658
|
+
constructor(writable, _readable) {
|
|
3659
|
+
this.writable = writable;
|
|
3660
|
+
this.writer = writable.getWriter();
|
|
3661
|
+
this.encoder = new TextEncoder;
|
|
3662
|
+
const reader = _readable.getReader();
|
|
3663
|
+
this.abortSubscribers.push(async () => {
|
|
3664
|
+
await reader.cancel();
|
|
3665
|
+
});
|
|
3666
|
+
this.responseReadable = new ReadableStream({
|
|
3667
|
+
async pull(controller) {
|
|
3668
|
+
const { done, value } = await reader.read();
|
|
3669
|
+
done ? controller.close() : controller.enqueue(value);
|
|
3670
|
+
},
|
|
3671
|
+
cancel: () => {
|
|
3672
|
+
this.abort();
|
|
3673
|
+
}
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3676
|
+
async write(input) {
|
|
3677
|
+
try {
|
|
3678
|
+
if (typeof input === "string") {
|
|
3679
|
+
input = this.encoder.encode(input);
|
|
3680
|
+
}
|
|
3681
|
+
await this.writer.write(input);
|
|
3682
|
+
} catch {}
|
|
3683
|
+
return this;
|
|
3684
|
+
}
|
|
3685
|
+
async writeln(input) {
|
|
3686
|
+
await this.write(input + `
|
|
3687
|
+
`);
|
|
3688
|
+
return this;
|
|
3689
|
+
}
|
|
3690
|
+
sleep(ms) {
|
|
3691
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
3692
|
+
}
|
|
3693
|
+
async close() {
|
|
3694
|
+
try {
|
|
3695
|
+
await this.writer.close();
|
|
3696
|
+
} catch {}
|
|
3697
|
+
this.closed = true;
|
|
3698
|
+
}
|
|
3699
|
+
async pipe(body) {
|
|
3700
|
+
this.writer.releaseLock();
|
|
3701
|
+
await body.pipeTo(this.writable, { preventClose: true });
|
|
3702
|
+
this.writer = this.writable.getWriter();
|
|
3703
|
+
}
|
|
3704
|
+
onAbort(listener) {
|
|
3705
|
+
this.abortSubscribers.push(listener);
|
|
3706
|
+
}
|
|
3707
|
+
abort() {
|
|
3708
|
+
if (!this.aborted) {
|
|
3709
|
+
this.aborted = true;
|
|
3710
|
+
this.abortSubscribers.forEach((subscriber) => subscriber());
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
};
|
|
3714
|
+
|
|
3715
|
+
// node_modules/hono/dist/helper/streaming/utils.js
|
|
3716
|
+
var isOldBunVersion = () => {
|
|
3717
|
+
const version = typeof Bun !== "undefined" ? Bun.version : undefined;
|
|
3718
|
+
if (version === undefined) {
|
|
3719
|
+
return false;
|
|
3720
|
+
}
|
|
3721
|
+
const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
|
|
3722
|
+
isOldBunVersion = () => result;
|
|
3723
|
+
return result;
|
|
3724
|
+
};
|
|
3725
|
+
|
|
3726
|
+
// node_modules/hono/dist/helper/streaming/stream.js
|
|
3727
|
+
var contextStash = /* @__PURE__ */ new WeakMap;
|
|
3728
|
+
var stream = (c, cb, onError) => {
|
|
3729
|
+
const { readable, writable } = new TransformStream;
|
|
3730
|
+
const stream2 = new StreamingApi(writable, readable);
|
|
3731
|
+
if (isOldBunVersion()) {
|
|
3732
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
3733
|
+
if (!stream2.closed) {
|
|
3734
|
+
stream2.abort();
|
|
3735
|
+
}
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
contextStash.set(stream2.responseReadable, c);
|
|
3739
|
+
(async () => {
|
|
3740
|
+
try {
|
|
3741
|
+
await cb(stream2);
|
|
3742
|
+
} catch (e) {
|
|
3743
|
+
if (e === undefined) {} else if (e instanceof Error && onError) {
|
|
3744
|
+
await onError(e, stream2);
|
|
3745
|
+
} else {
|
|
3746
|
+
console.error(e);
|
|
3747
|
+
}
|
|
3748
|
+
} finally {
|
|
3749
|
+
stream2.close();
|
|
3750
|
+
}
|
|
3751
|
+
})();
|
|
3752
|
+
return c.newResponse(stream2.responseReadable);
|
|
3753
|
+
};
|
|
3754
|
+
|
|
3641
3755
|
// node_modules/@hono/node-server/dist/index.mjs
|
|
3642
3756
|
import { createServer as createServerHTTP } from "http";
|
|
3643
3757
|
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
@@ -3918,13 +4032,13 @@ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromi
|
|
|
3918
4032
|
}
|
|
3919
4033
|
}
|
|
3920
4034
|
}
|
|
3921
|
-
function writeFromReadableStream(
|
|
3922
|
-
if (
|
|
4035
|
+
function writeFromReadableStream(stream2, writable) {
|
|
4036
|
+
if (stream2.locked) {
|
|
3923
4037
|
throw new TypeError("ReadableStream is locked.");
|
|
3924
4038
|
} else if (writable.destroyed) {
|
|
3925
4039
|
return;
|
|
3926
4040
|
}
|
|
3927
|
-
return writeFromReadableStreamDefaultReader(
|
|
4041
|
+
return writeFromReadableStreamDefaultReader(stream2.getReader(), writable);
|
|
3928
4042
|
}
|
|
3929
4043
|
var buildOutgoingHttpHeaders = (headers) => {
|
|
3930
4044
|
const res = {};
|
|
@@ -4189,8 +4303,8 @@ var serve = (options, listeningListener) => {
|
|
|
4189
4303
|
};
|
|
4190
4304
|
|
|
4191
4305
|
// src/proxy/server.ts
|
|
4192
|
-
import { homedir as
|
|
4193
|
-
import { join as
|
|
4306
|
+
import { homedir as homedir7 } from "node:os";
|
|
4307
|
+
import { join as join8 } from "node:path";
|
|
4194
4308
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
4195
4309
|
|
|
4196
4310
|
// src/proxy/rateLimitStore.ts
|
|
@@ -4539,7 +4653,7 @@ function createRequestContext(params) {
|
|
|
4539
4653
|
}
|
|
4540
4654
|
// src/proxy/server.ts
|
|
4541
4655
|
import { exec as execCallback } from "child_process";
|
|
4542
|
-
import { promisify as
|
|
4656
|
+
import { promisify as promisify3 } from "util";
|
|
4543
4657
|
import { randomUUID } from "crypto";
|
|
4544
4658
|
|
|
4545
4659
|
// src/proxy/passthroughTools.ts
|
|
@@ -10271,6 +10385,217 @@ function formatSdkTermination(t, ctx) {
|
|
|
10271
10385
|
return `sdk_termination ${parts.join(" ")}`;
|
|
10272
10386
|
}
|
|
10273
10387
|
|
|
10388
|
+
// src/proxy/design.ts
|
|
10389
|
+
import { homedir as homedir3 } from "node:os";
|
|
10390
|
+
import { join as join3, dirname as dirname3 } from "node:path";
|
|
10391
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
10392
|
+
var DESIGN_SCOPES = ["user:design:read", "user:design:write"];
|
|
10393
|
+
var DESIGN_UPSTREAM_ORIGIN = "https://api.anthropic.com";
|
|
10394
|
+
var EXPIRY_SKEW_MS = 60000;
|
|
10395
|
+
var LOGIN_SESSION_TTL_MS = 10 * 60 * 1000;
|
|
10396
|
+
function defaultDesignTokenPath() {
|
|
10397
|
+
return process.env.MERIDIAN_DESIGN_TOKEN_PATH || join3(homedir3(), ".config", "meridian", "design-token.json");
|
|
10398
|
+
}
|
|
10399
|
+
function createFileDesignTokenStore(path = defaultDesignTokenPath()) {
|
|
10400
|
+
return {
|
|
10401
|
+
async read() {
|
|
10402
|
+
try {
|
|
10403
|
+
const raw2 = await readFile(path, "utf-8");
|
|
10404
|
+
const data = JSON.parse(raw2);
|
|
10405
|
+
if (typeof data.accessToken === "string" && typeof data.expiresAt === "number") {
|
|
10406
|
+
return data;
|
|
10407
|
+
}
|
|
10408
|
+
return null;
|
|
10409
|
+
} catch {
|
|
10410
|
+
return null;
|
|
10411
|
+
}
|
|
10412
|
+
},
|
|
10413
|
+
async write(data) {
|
|
10414
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
10415
|
+
await writeFile(path, JSON.stringify(data), { mode: 384 });
|
|
10416
|
+
}
|
|
10417
|
+
};
|
|
10418
|
+
}
|
|
10419
|
+
function isDesignTokenFresh(data, now = Date.now()) {
|
|
10420
|
+
return data.expiresAt > now + EXPIRY_SKEW_MS;
|
|
10421
|
+
}
|
|
10422
|
+
async function getDesignAccessToken(opts) {
|
|
10423
|
+
const now = opts.now ?? Date.now();
|
|
10424
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
10425
|
+
const data = await opts.store.read();
|
|
10426
|
+
if (!data)
|
|
10427
|
+
return null;
|
|
10428
|
+
if (isDesignTokenFresh(data, now))
|
|
10429
|
+
return data.accessToken;
|
|
10430
|
+
if (!data.refreshToken)
|
|
10431
|
+
return null;
|
|
10432
|
+
let response;
|
|
10433
|
+
try {
|
|
10434
|
+
response = await fetchFn(OAUTH_TOKEN_URL, {
|
|
10435
|
+
method: "POST",
|
|
10436
|
+
headers: { "Content-Type": "application/json" },
|
|
10437
|
+
body: JSON.stringify({
|
|
10438
|
+
grant_type: "refresh_token",
|
|
10439
|
+
client_id: OAUTH_CLIENT_ID,
|
|
10440
|
+
refresh_token: data.refreshToken
|
|
10441
|
+
}),
|
|
10442
|
+
signal: AbortSignal.timeout(30000)
|
|
10443
|
+
});
|
|
10444
|
+
} catch {
|
|
10445
|
+
return null;
|
|
10446
|
+
}
|
|
10447
|
+
if (!response.ok)
|
|
10448
|
+
return null;
|
|
10449
|
+
let tokenData;
|
|
10450
|
+
try {
|
|
10451
|
+
tokenData = await response.json();
|
|
10452
|
+
} catch {
|
|
10453
|
+
return null;
|
|
10454
|
+
}
|
|
10455
|
+
if (!tokenData.access_token)
|
|
10456
|
+
return null;
|
|
10457
|
+
const refreshed = {
|
|
10458
|
+
accessToken: tokenData.access_token,
|
|
10459
|
+
refreshToken: tokenData.refresh_token ?? data.refreshToken,
|
|
10460
|
+
expiresAt: tokenData.expires_at ?? now + (tokenData.expires_in ?? 8 * 60 * 60) * 1000,
|
|
10461
|
+
scopes: tokenData.scope?.split(" ").filter(Boolean) ?? data.scopes
|
|
10462
|
+
};
|
|
10463
|
+
await opts.store.write(refreshed);
|
|
10464
|
+
return refreshed.accessToken;
|
|
10465
|
+
}
|
|
10466
|
+
async function resolveDesignAuthHeaders(opts) {
|
|
10467
|
+
if (opts.designToken)
|
|
10468
|
+
return { Authorization: `Bearer ${opts.designToken}` };
|
|
10469
|
+
const { profile } = opts;
|
|
10470
|
+
if (profile.type === "api" && profile.env.ANTHROPIC_API_KEY) {
|
|
10471
|
+
return { "x-api-key": profile.env.ANTHROPIC_API_KEY };
|
|
10472
|
+
}
|
|
10473
|
+
if (profile.type === "oauth-token" && profile.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
10474
|
+
return { Authorization: `Bearer ${profile.env.CLAUDE_CODE_OAUTH_TOKEN}` };
|
|
10475
|
+
}
|
|
10476
|
+
if (opts.credentialStore) {
|
|
10477
|
+
if (opts.ensureFresh)
|
|
10478
|
+
await opts.ensureFresh(opts.credentialStore).catch(() => {});
|
|
10479
|
+
const creds = await opts.credentialStore.read();
|
|
10480
|
+
const token = creds?.claudeAiOauth?.accessToken;
|
|
10481
|
+
if (token)
|
|
10482
|
+
return { Authorization: `Bearer ${token}` };
|
|
10483
|
+
}
|
|
10484
|
+
return {};
|
|
10485
|
+
}
|
|
10486
|
+
function buildDesignForwardHeaders(getHeader, authHeaders) {
|
|
10487
|
+
const headers = {
|
|
10488
|
+
"anthropic-version": getHeader("anthropic-version") || "2023-06-01",
|
|
10489
|
+
"accept-encoding": "identity",
|
|
10490
|
+
...authHeaders
|
|
10491
|
+
};
|
|
10492
|
+
for (const name of ["content-type", "anthropic-beta", "mcp-session-id"]) {
|
|
10493
|
+
const value = getHeader(name);
|
|
10494
|
+
if (value)
|
|
10495
|
+
headers[name] = value;
|
|
10496
|
+
}
|
|
10497
|
+
return headers;
|
|
10498
|
+
}
|
|
10499
|
+
var HOP_BY_HOP = new Set([
|
|
10500
|
+
"transfer-encoding",
|
|
10501
|
+
"connection",
|
|
10502
|
+
"keep-alive",
|
|
10503
|
+
"proxy-authenticate",
|
|
10504
|
+
"proxy-authorization",
|
|
10505
|
+
"te",
|
|
10506
|
+
"trailer",
|
|
10507
|
+
"upgrade"
|
|
10508
|
+
]);
|
|
10509
|
+
function filterUpstreamResponseHeaders(headers) {
|
|
10510
|
+
const out = {};
|
|
10511
|
+
for (const [key, value] of headers) {
|
|
10512
|
+
if (!HOP_BY_HOP.has(key.toLowerCase()))
|
|
10513
|
+
out[key] = value;
|
|
10514
|
+
}
|
|
10515
|
+
return out;
|
|
10516
|
+
}
|
|
10517
|
+
function isDesignAuthFailure(status) {
|
|
10518
|
+
return status === 401 || status === 404;
|
|
10519
|
+
}
|
|
10520
|
+
function createDesignLogin(deps) {
|
|
10521
|
+
const createSession = deps.createSession ?? createManualOAuthSession;
|
|
10522
|
+
const fetchFn = deps.fetchFn ?? fetch;
|
|
10523
|
+
const now = deps.now ?? Date.now;
|
|
10524
|
+
const sessions = new Map;
|
|
10525
|
+
const startRaw = () => {
|
|
10526
|
+
for (const [state, session2] of sessions) {
|
|
10527
|
+
if (session2.expiresAt < now())
|
|
10528
|
+
sessions.delete(state);
|
|
10529
|
+
}
|
|
10530
|
+
const session = createSession(DESIGN_SCOPES);
|
|
10531
|
+
sessions.set(session.state, { codeVerifier: session.codeVerifier, expiresAt: now() + LOGIN_SESSION_TTL_MS });
|
|
10532
|
+
return { authorizeUrl: session.authorizeUrl, state: session.state };
|
|
10533
|
+
};
|
|
10534
|
+
return {
|
|
10535
|
+
startRaw,
|
|
10536
|
+
start() {
|
|
10537
|
+
const { authorizeUrl } = startRaw();
|
|
10538
|
+
return {
|
|
10539
|
+
authorizeUrl,
|
|
10540
|
+
instructions: 'Open the URL in your browser. After authorizing, POST the code to /design-login with body { "code": "<paste-code-here>" }'
|
|
10541
|
+
};
|
|
10542
|
+
},
|
|
10543
|
+
async exchange(rawBody) {
|
|
10544
|
+
const body = rawBody && typeof rawBody === "object" ? rawBody : {};
|
|
10545
|
+
const parsed = body.code ? parseAuthorizationCodeInput(body.code) : null;
|
|
10546
|
+
if (!parsed) {
|
|
10547
|
+
return { status: 400, body: { type: "error", error: { type: "invalid_request", message: "Missing or invalid 'code' field." } } };
|
|
10548
|
+
}
|
|
10549
|
+
const stateKey = parsed.state ?? body.state;
|
|
10550
|
+
const stored = stateKey ? sessions.get(stateKey) : undefined;
|
|
10551
|
+
if (!stateKey || !stored || stored.expiresAt < now()) {
|
|
10552
|
+
return {
|
|
10553
|
+
status: 400,
|
|
10554
|
+
body: { type: "error", error: { type: "session_expired", message: "OAuth session expired or not found. Call GET /design-login to start a new session." } }
|
|
10555
|
+
};
|
|
10556
|
+
}
|
|
10557
|
+
sessions.delete(stateKey);
|
|
10558
|
+
let response;
|
|
10559
|
+
try {
|
|
10560
|
+
response = await fetchFn(OAUTH_TOKEN_URL, {
|
|
10561
|
+
method: "POST",
|
|
10562
|
+
headers: { "Content-Type": "application/json" },
|
|
10563
|
+
body: JSON.stringify({
|
|
10564
|
+
grant_type: "authorization_code",
|
|
10565
|
+
client_id: OAUTH_CLIENT_ID,
|
|
10566
|
+
code: parsed.code,
|
|
10567
|
+
redirect_uri: OAUTH_REDIRECT_URI,
|
|
10568
|
+
code_verifier: stored.codeVerifier,
|
|
10569
|
+
state: stateKey
|
|
10570
|
+
}),
|
|
10571
|
+
signal: AbortSignal.timeout(30000)
|
|
10572
|
+
});
|
|
10573
|
+
} catch (err) {
|
|
10574
|
+
return { status: 502, body: { type: "error", error: { type: "upstream_error", message: err instanceof Error ? err.message : String(err) } } };
|
|
10575
|
+
}
|
|
10576
|
+
if (!response.ok) {
|
|
10577
|
+
const text = await response.text().catch(() => "");
|
|
10578
|
+
return {
|
|
10579
|
+
status: 502,
|
|
10580
|
+
body: { type: "error", error: { type: "token_exchange_failed", message: `Token exchange failed (${response.status}): ${text.slice(0, 200)}` } }
|
|
10581
|
+
};
|
|
10582
|
+
}
|
|
10583
|
+
const tokenData = await response.json();
|
|
10584
|
+
if (!tokenData.access_token) {
|
|
10585
|
+
return { status: 502, body: { type: "error", error: { type: "token_exchange_failed", message: "Token response missing access_token." } } };
|
|
10586
|
+
}
|
|
10587
|
+
const expiresAt = tokenData.expires_at ?? now() + (tokenData.expires_in ?? 8 * 60 * 60) * 1000;
|
|
10588
|
+
const scopes = tokenData.scope?.split(" ").filter(Boolean) ?? DESIGN_SCOPES;
|
|
10589
|
+
try {
|
|
10590
|
+
await deps.store.write({ accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, expiresAt, scopes });
|
|
10591
|
+
} catch (err) {
|
|
10592
|
+
return { status: 500, body: { type: "error", error: { type: "storage_error", message: `Failed to store design token: ${err instanceof Error ? err.message : String(err)}` } } };
|
|
10593
|
+
}
|
|
10594
|
+
return { status: 200, body: { success: true, scopes } };
|
|
10595
|
+
}
|
|
10596
|
+
};
|
|
10597
|
+
}
|
|
10598
|
+
|
|
10274
10599
|
// src/proxy/openai.ts
|
|
10275
10600
|
function extractOpenAiContent(content) {
|
|
10276
10601
|
if (typeof content === "string")
|
|
@@ -10738,6 +11063,331 @@ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000))
|
|
|
10738
11063
|
];
|
|
10739
11064
|
}
|
|
10740
11065
|
|
|
11066
|
+
// src/proxy/openaiResponses.ts
|
|
11067
|
+
function partsToText(content) {
|
|
11068
|
+
if (typeof content === "string")
|
|
11069
|
+
return content;
|
|
11070
|
+
return content.filter((p) => typeof p.text === "string").map((p) => p.text).join("");
|
|
11071
|
+
}
|
|
11072
|
+
function mapToolChoice(tc) {
|
|
11073
|
+
if (tc === "auto")
|
|
11074
|
+
return { type: "auto" };
|
|
11075
|
+
if (tc === "required")
|
|
11076
|
+
return { type: "any" };
|
|
11077
|
+
if (tc && typeof tc === "object") {
|
|
11078
|
+
const o = tc;
|
|
11079
|
+
if (o.type === "function" && o.name)
|
|
11080
|
+
return { type: "tool", name: o.name };
|
|
11081
|
+
if (o.type === "auto")
|
|
11082
|
+
return { type: "auto" };
|
|
11083
|
+
if (o.type === "any" || o.type === "required")
|
|
11084
|
+
return { type: "any" };
|
|
11085
|
+
}
|
|
11086
|
+
return;
|
|
11087
|
+
}
|
|
11088
|
+
function translateResponsesToAnthropic(body) {
|
|
11089
|
+
if (body.input === undefined || body.input === null)
|
|
11090
|
+
return null;
|
|
11091
|
+
const items = typeof body.input === "string" ? [{ type: "message", role: "user", content: [{ type: "input_text", text: body.input }] }] : body.input;
|
|
11092
|
+
const systemParts = [];
|
|
11093
|
+
if (body.instructions)
|
|
11094
|
+
systemParts.push(body.instructions);
|
|
11095
|
+
const messages = [];
|
|
11096
|
+
const pushBlock = (role, block) => {
|
|
11097
|
+
const last = messages[messages.length - 1];
|
|
11098
|
+
if (last && last.role === role && Array.isArray(last.content)) {
|
|
11099
|
+
last.content.push(block);
|
|
11100
|
+
} else {
|
|
11101
|
+
messages.push({ role, content: [block] });
|
|
11102
|
+
}
|
|
11103
|
+
};
|
|
11104
|
+
for (const item of items) {
|
|
11105
|
+
switch (item.type) {
|
|
11106
|
+
case "message": {
|
|
11107
|
+
const msg = item;
|
|
11108
|
+
if (msg.role === "developer" || msg.role === "system") {
|
|
11109
|
+
const t = partsToText(msg.content);
|
|
11110
|
+
if (t)
|
|
11111
|
+
systemParts.push(t);
|
|
11112
|
+
break;
|
|
11113
|
+
}
|
|
11114
|
+
const text = partsToText(msg.content);
|
|
11115
|
+
if (text)
|
|
11116
|
+
pushBlock(msg.role === "assistant" ? "assistant" : "user", { type: "text", text });
|
|
11117
|
+
break;
|
|
11118
|
+
}
|
|
11119
|
+
case "function_call": {
|
|
11120
|
+
const fc = item;
|
|
11121
|
+
let input = {};
|
|
11122
|
+
try {
|
|
11123
|
+
input = fc.arguments ? JSON.parse(fc.arguments) : {};
|
|
11124
|
+
} catch {
|
|
11125
|
+
input = {};
|
|
11126
|
+
}
|
|
11127
|
+
pushBlock("assistant", { type: "tool_use", id: fc.call_id, name: fc.name, input });
|
|
11128
|
+
break;
|
|
11129
|
+
}
|
|
11130
|
+
case "function_call_output": {
|
|
11131
|
+
const fo = item;
|
|
11132
|
+
pushBlock("user", { type: "tool_result", tool_use_id: fo.call_id, content: fo.output });
|
|
11133
|
+
break;
|
|
11134
|
+
}
|
|
11135
|
+
case "reasoning":
|
|
11136
|
+
break;
|
|
11137
|
+
default:
|
|
11138
|
+
break;
|
|
11139
|
+
}
|
|
11140
|
+
}
|
|
11141
|
+
const result = {
|
|
11142
|
+
model: typeof body.model === "string" ? body.model : "",
|
|
11143
|
+
messages,
|
|
11144
|
+
max_tokens: body.max_output_tokens ?? 8192,
|
|
11145
|
+
stream: body.stream ?? false
|
|
11146
|
+
};
|
|
11147
|
+
if (systemParts.length > 0)
|
|
11148
|
+
result.system = systemParts.join(`
|
|
11149
|
+
|
|
11150
|
+
`);
|
|
11151
|
+
if (Array.isArray(body.tools) && body.tools.length > 0) {
|
|
11152
|
+
result.tools = body.tools.filter((t) => t.type === "function").map((t) => ({
|
|
11153
|
+
name: t.name,
|
|
11154
|
+
description: t.description ?? "",
|
|
11155
|
+
input_schema: t.parameters ?? { type: "object", properties: {} }
|
|
11156
|
+
}));
|
|
11157
|
+
}
|
|
11158
|
+
const tc = mapToolChoice(body.tool_choice);
|
|
11159
|
+
if (tc)
|
|
11160
|
+
result.tool_choice = tc;
|
|
11161
|
+
if (body.temperature !== undefined)
|
|
11162
|
+
result.temperature = body.temperature;
|
|
11163
|
+
if (body.top_p !== undefined)
|
|
11164
|
+
result.top_p = body.top_p;
|
|
11165
|
+
if (body.reasoning?.effort !== undefined)
|
|
11166
|
+
result.reasoning_effort = body.reasoning.effort;
|
|
11167
|
+
return result;
|
|
11168
|
+
}
|
|
11169
|
+
function reasoningRequested(body) {
|
|
11170
|
+
if (body.reasoning && typeof body.reasoning === "object")
|
|
11171
|
+
return true;
|
|
11172
|
+
const include = body.include;
|
|
11173
|
+
return Array.isArray(include) && include.some((v) => typeof v === "string" && v.startsWith("reasoning"));
|
|
11174
|
+
}
|
|
11175
|
+
function mapUsage(usage) {
|
|
11176
|
+
const input = usage?.input_tokens ?? 0;
|
|
11177
|
+
const output = usage?.output_tokens ?? 0;
|
|
11178
|
+
return { input_tokens: input, output_tokens: output, total_tokens: input + output };
|
|
11179
|
+
}
|
|
11180
|
+
function translateAnthropicToResponses(res, ctx) {
|
|
11181
|
+
const output = [];
|
|
11182
|
+
const textParts = [];
|
|
11183
|
+
for (const block of res.content ?? []) {
|
|
11184
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
11185
|
+
textParts.push({ type: "output_text", text: block.text, annotations: [] });
|
|
11186
|
+
} else if (block.type === "tool_use") {
|
|
11187
|
+
output.push({
|
|
11188
|
+
type: "function_call",
|
|
11189
|
+
id: `fc_${block.id}`,
|
|
11190
|
+
call_id: block.id,
|
|
11191
|
+
name: block.name,
|
|
11192
|
+
arguments: JSON.stringify(block.input ?? {}),
|
|
11193
|
+
status: "completed"
|
|
11194
|
+
});
|
|
11195
|
+
}
|
|
11196
|
+
}
|
|
11197
|
+
if (textParts.length > 0) {
|
|
11198
|
+
output.unshift({
|
|
11199
|
+
type: "message",
|
|
11200
|
+
id: `msg_${ctx.responseId}`,
|
|
11201
|
+
status: "completed",
|
|
11202
|
+
role: "assistant",
|
|
11203
|
+
content: textParts
|
|
11204
|
+
});
|
|
11205
|
+
}
|
|
11206
|
+
if (ctx.reasoningRequested) {
|
|
11207
|
+
output.unshift({
|
|
11208
|
+
type: "reasoning",
|
|
11209
|
+
id: `rs_${ctx.responseId}`,
|
|
11210
|
+
summary: [],
|
|
11211
|
+
encrypted_content: null,
|
|
11212
|
+
status: "completed"
|
|
11213
|
+
});
|
|
11214
|
+
}
|
|
11215
|
+
return {
|
|
11216
|
+
id: ctx.responseId,
|
|
11217
|
+
object: "response",
|
|
11218
|
+
created_at: ctx.created,
|
|
11219
|
+
model: ctx.model,
|
|
11220
|
+
status: "completed",
|
|
11221
|
+
output,
|
|
11222
|
+
usage: mapUsage(res.usage),
|
|
11223
|
+
parallel_tool_calls: true,
|
|
11224
|
+
tool_choice: "auto",
|
|
11225
|
+
tools: []
|
|
11226
|
+
};
|
|
11227
|
+
}
|
|
11228
|
+
function createResponsesSseTranslator(ctx) {
|
|
11229
|
+
let seq = 0;
|
|
11230
|
+
let outputIndex = 0;
|
|
11231
|
+
let inputTokens = 0;
|
|
11232
|
+
let outputTokens = 0;
|
|
11233
|
+
let createdEmitted = false;
|
|
11234
|
+
const blocks = new Map;
|
|
11235
|
+
const finalOutput = [];
|
|
11236
|
+
const emit = (event, data) => ({
|
|
11237
|
+
event,
|
|
11238
|
+
data: { type: event, ...data, sequence_number: seq++ }
|
|
11239
|
+
});
|
|
11240
|
+
const responseEnvelope = (status, extra = {}) => ({
|
|
11241
|
+
id: ctx.responseId,
|
|
11242
|
+
object: "response",
|
|
11243
|
+
created_at: ctx.created,
|
|
11244
|
+
model: ctx.model,
|
|
11245
|
+
status,
|
|
11246
|
+
...extra
|
|
11247
|
+
});
|
|
11248
|
+
return (event) => {
|
|
11249
|
+
const out = [];
|
|
11250
|
+
switch (event.type) {
|
|
11251
|
+
case "message_start": {
|
|
11252
|
+
inputTokens = event.message?.usage?.input_tokens ?? 0;
|
|
11253
|
+
if (!createdEmitted) {
|
|
11254
|
+
createdEmitted = true;
|
|
11255
|
+
out.push(emit("response.created", { response: responseEnvelope("in_progress", { output: [] }) }));
|
|
11256
|
+
out.push(emit("response.in_progress", { response: responseEnvelope("in_progress", { output: [] }) }));
|
|
11257
|
+
if (ctx.reasoningRequested) {
|
|
11258
|
+
const oi = outputIndex++;
|
|
11259
|
+
const itemId = `rs_${ctx.responseId}`;
|
|
11260
|
+
const item = { type: "reasoning", id: itemId, summary: [], encrypted_content: null, status: "completed" };
|
|
11261
|
+
out.push(emit("response.output_item.added", { output_index: oi, item: { ...item, status: "in_progress" } }));
|
|
11262
|
+
finalOutput.push(item);
|
|
11263
|
+
out.push(emit("response.output_item.done", { output_index: oi, item }));
|
|
11264
|
+
}
|
|
11265
|
+
}
|
|
11266
|
+
break;
|
|
11267
|
+
}
|
|
11268
|
+
case "content_block_start": {
|
|
11269
|
+
const idx = event.index ?? 0;
|
|
11270
|
+
const cb = event.content_block ?? {};
|
|
11271
|
+
if (cb.type === "text") {
|
|
11272
|
+
const oi = outputIndex++;
|
|
11273
|
+
const itemId = `msg_${ctx.responseId}_${oi}`;
|
|
11274
|
+
blocks.set(idx, { kind: "text", outputIndex: oi, itemId, text: "", args: "" });
|
|
11275
|
+
out.push(emit("response.output_item.added", {
|
|
11276
|
+
output_index: oi,
|
|
11277
|
+
item: { type: "message", id: itemId, status: "in_progress", role: "assistant", content: [] }
|
|
11278
|
+
}));
|
|
11279
|
+
out.push(emit("response.content_part.added", {
|
|
11280
|
+
item_id: itemId,
|
|
11281
|
+
output_index: oi,
|
|
11282
|
+
content_index: 0,
|
|
11283
|
+
part: { type: "output_text", text: "", annotations: [] }
|
|
11284
|
+
}));
|
|
11285
|
+
} else if (cb.type === "tool_use") {
|
|
11286
|
+
const oi = outputIndex++;
|
|
11287
|
+
const itemId = `fc_${cb.id}`;
|
|
11288
|
+
blocks.set(idx, { kind: "tool", outputIndex: oi, itemId, text: "", args: "", callId: cb.id, name: cb.name });
|
|
11289
|
+
out.push(emit("response.output_item.added", {
|
|
11290
|
+
output_index: oi,
|
|
11291
|
+
item: { type: "function_call", id: itemId, call_id: cb.id, name: cb.name, arguments: "", status: "in_progress" }
|
|
11292
|
+
}));
|
|
11293
|
+
} else {
|
|
11294
|
+
blocks.set(idx, { kind: "skip", outputIndex: -1, itemId: "", text: "", args: "" });
|
|
11295
|
+
}
|
|
11296
|
+
break;
|
|
11297
|
+
}
|
|
11298
|
+
case "content_block_delta": {
|
|
11299
|
+
const idx = event.index ?? 0;
|
|
11300
|
+
const st = blocks.get(idx);
|
|
11301
|
+
if (!st || st.kind === "skip")
|
|
11302
|
+
break;
|
|
11303
|
+
if (st.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
|
|
11304
|
+
st.text += event.delta.text;
|
|
11305
|
+
out.push(emit("response.output_text.delta", {
|
|
11306
|
+
item_id: st.itemId,
|
|
11307
|
+
output_index: st.outputIndex,
|
|
11308
|
+
content_index: 0,
|
|
11309
|
+
delta: event.delta.text
|
|
11310
|
+
}));
|
|
11311
|
+
} else if (st.kind === "tool" && event.delta?.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
|
|
11312
|
+
st.args += event.delta.partial_json;
|
|
11313
|
+
out.push(emit("response.function_call_arguments.delta", {
|
|
11314
|
+
item_id: st.itemId,
|
|
11315
|
+
output_index: st.outputIndex,
|
|
11316
|
+
delta: event.delta.partial_json
|
|
11317
|
+
}));
|
|
11318
|
+
}
|
|
11319
|
+
break;
|
|
11320
|
+
}
|
|
11321
|
+
case "content_block_stop": {
|
|
11322
|
+
const idx = event.index ?? 0;
|
|
11323
|
+
const st = blocks.get(idx);
|
|
11324
|
+
if (!st || st.kind === "skip")
|
|
11325
|
+
break;
|
|
11326
|
+
if (st.kind === "text") {
|
|
11327
|
+
out.push(emit("response.output_text.done", {
|
|
11328
|
+
item_id: st.itemId,
|
|
11329
|
+
output_index: st.outputIndex,
|
|
11330
|
+
content_index: 0,
|
|
11331
|
+
text: st.text
|
|
11332
|
+
}));
|
|
11333
|
+
out.push(emit("response.content_part.done", {
|
|
11334
|
+
item_id: st.itemId,
|
|
11335
|
+
output_index: st.outputIndex,
|
|
11336
|
+
content_index: 0,
|
|
11337
|
+
part: { type: "output_text", text: st.text, annotations: [] }
|
|
11338
|
+
}));
|
|
11339
|
+
const item = {
|
|
11340
|
+
type: "message",
|
|
11341
|
+
id: st.itemId,
|
|
11342
|
+
status: "completed",
|
|
11343
|
+
role: "assistant",
|
|
11344
|
+
content: [{ type: "output_text", text: st.text, annotations: [] }]
|
|
11345
|
+
};
|
|
11346
|
+
finalOutput.push(item);
|
|
11347
|
+
out.push(emit("response.output_item.done", { output_index: st.outputIndex, item }));
|
|
11348
|
+
} else if (st.kind === "tool") {
|
|
11349
|
+
out.push(emit("response.function_call_arguments.done", {
|
|
11350
|
+
item_id: st.itemId,
|
|
11351
|
+
output_index: st.outputIndex,
|
|
11352
|
+
arguments: st.args
|
|
11353
|
+
}));
|
|
11354
|
+
const item = {
|
|
11355
|
+
type: "function_call",
|
|
11356
|
+
id: st.itemId,
|
|
11357
|
+
call_id: st.callId,
|
|
11358
|
+
name: st.name,
|
|
11359
|
+
arguments: st.args,
|
|
11360
|
+
status: "completed"
|
|
11361
|
+
};
|
|
11362
|
+
finalOutput.push(item);
|
|
11363
|
+
out.push(emit("response.output_item.done", { output_index: st.outputIndex, item }));
|
|
11364
|
+
}
|
|
11365
|
+
break;
|
|
11366
|
+
}
|
|
11367
|
+
case "message_delta": {
|
|
11368
|
+
if (typeof event.usage?.output_tokens === "number")
|
|
11369
|
+
outputTokens = event.usage.output_tokens;
|
|
11370
|
+
break;
|
|
11371
|
+
}
|
|
11372
|
+
case "message_stop": {
|
|
11373
|
+
out.push(emit("response.completed", {
|
|
11374
|
+
response: responseEnvelope("completed", {
|
|
11375
|
+
output: finalOutput,
|
|
11376
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens, total_tokens: inputTokens + outputTokens },
|
|
11377
|
+
parallel_tool_calls: true,
|
|
11378
|
+
tool_choice: "auto",
|
|
11379
|
+
tools: []
|
|
11380
|
+
})
|
|
11381
|
+
}));
|
|
11382
|
+
break;
|
|
11383
|
+
}
|
|
11384
|
+
default:
|
|
11385
|
+
break;
|
|
11386
|
+
}
|
|
11387
|
+
return out;
|
|
11388
|
+
};
|
|
11389
|
+
}
|
|
11390
|
+
|
|
10741
11391
|
// src/proxy/messages.ts
|
|
10742
11392
|
function stripCacheControlForHashing(obj) {
|
|
10743
11393
|
if (!obj || typeof obj !== "object")
|
|
@@ -11136,7 +11786,7 @@ init_env();
|
|
|
11136
11786
|
var openCodeTransforms = [
|
|
11137
11787
|
{
|
|
11138
11788
|
name: "opencode-core",
|
|
11139
|
-
adapters: ["opencode", "openai"],
|
|
11789
|
+
adapters: ["opencode", "openai", "codex"],
|
|
11140
11790
|
onRequest(ctx) {
|
|
11141
11791
|
const body = ctx.body;
|
|
11142
11792
|
const blockedTools = BLOCKED_BUILTIN_TOOLS;
|
|
@@ -11940,6 +12590,15 @@ var openAiAdapter = {
|
|
|
11940
12590
|
name: "openai"
|
|
11941
12591
|
};
|
|
11942
12592
|
|
|
12593
|
+
// src/proxy/adapters/codex.ts
|
|
12594
|
+
var codexAdapter = {
|
|
12595
|
+
...openCodeAdapter,
|
|
12596
|
+
name: "codex",
|
|
12597
|
+
usesPassthrough() {
|
|
12598
|
+
return true;
|
|
12599
|
+
}
|
|
12600
|
+
};
|
|
12601
|
+
|
|
11943
12602
|
// src/proxy/adapters/cherry.ts
|
|
11944
12603
|
var CHERRY_WEB_TOOLS = ["WebSearch", "WebFetch"];
|
|
11945
12604
|
var isWebTool = (t) => CHERRY_WEB_TOOLS.includes(t);
|
|
@@ -11970,9 +12629,9 @@ var cherryAdapter = {
|
|
|
11970
12629
|
|
|
11971
12630
|
// src/proxy/adapterInstances.ts
|
|
11972
12631
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
11973
|
-
import { join as
|
|
11974
|
-
import { homedir as
|
|
11975
|
-
var CONFIG_FILE =
|
|
12632
|
+
import { join as join4 } from "node:path";
|
|
12633
|
+
import { homedir as homedir4 } from "node:os";
|
|
12634
|
+
var CONFIG_FILE = join4(homedir4(), ".config", "meridian", "adapter-instances.json");
|
|
11976
12635
|
function parseAdapterInstances(raw2) {
|
|
11977
12636
|
if (!raw2)
|
|
11978
12637
|
return {};
|
|
@@ -12061,7 +12720,8 @@ var ADAPTER_MAP = {
|
|
|
12061
12720
|
claudecode: claudeCodeAdapter,
|
|
12062
12721
|
cherry: cherryAdapter,
|
|
12063
12722
|
cherrystudio: cherryAdapter,
|
|
12064
|
-
openai: openAiAdapter
|
|
12723
|
+
openai: openAiAdapter,
|
|
12724
|
+
codex: codexAdapter
|
|
12065
12725
|
};
|
|
12066
12726
|
var envDefault = process.env.MERIDIAN_DEFAULT_AGENT || "";
|
|
12067
12727
|
if (envDefault && !ADAPTER_MAP[envDefault]) {
|
|
@@ -12140,7 +12800,7 @@ import { createSdkMcpServer as createSdkMcpServer2, tool } from "@anthropic-ai/c
|
|
|
12140
12800
|
import * as fs from "node:fs/promises";
|
|
12141
12801
|
import * as path2 from "node:path";
|
|
12142
12802
|
import { exec } from "node:child_process";
|
|
12143
|
-
import { promisify } from "node:util";
|
|
12803
|
+
import { promisify as promisify2 } from "node:util";
|
|
12144
12804
|
|
|
12145
12805
|
// node_modules/@isaacs/balanced-match/dist/esm/index.js
|
|
12146
12806
|
var balanced = (a, b, str) => {
|
|
@@ -17661,7 +18321,7 @@ function globIterate(pattern, options = {}) {
|
|
|
17661
18321
|
return new Glob(pattern, options).iterate();
|
|
17662
18322
|
}
|
|
17663
18323
|
var streamSync = globStreamSync;
|
|
17664
|
-
var
|
|
18324
|
+
var stream2 = Object.assign(globStream, { sync: globStreamSync });
|
|
17665
18325
|
var iterateSync = globIterateSync;
|
|
17666
18326
|
var iterate = Object.assign(globIterate, {
|
|
17667
18327
|
sync: globIterateSync
|
|
@@ -17675,7 +18335,7 @@ var glob = Object.assign(glob_, {
|
|
|
17675
18335
|
globSync,
|
|
17676
18336
|
sync,
|
|
17677
18337
|
globStream,
|
|
17678
|
-
stream,
|
|
18338
|
+
stream: stream2,
|
|
17679
18339
|
globStreamSync,
|
|
17680
18340
|
streamSync,
|
|
17681
18341
|
globIterate,
|
|
@@ -17689,9 +18349,35 @@ var glob = Object.assign(glob_, {
|
|
|
17689
18349
|
});
|
|
17690
18350
|
glob.glob = glob;
|
|
17691
18351
|
|
|
17692
|
-
// src/
|
|
17693
|
-
|
|
18352
|
+
// src/grepTool.ts
|
|
18353
|
+
import { execFile } from "node:child_process";
|
|
18354
|
+
import { promisify } from "node:util";
|
|
18355
|
+
var execFileAsync = promisify(execFile);
|
|
17694
18356
|
var getCwd = () => process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR ?? process.cwd();
|
|
18357
|
+
async function runGrepTool(args) {
|
|
18358
|
+
try {
|
|
18359
|
+
const searchPath = args.path || getCwd();
|
|
18360
|
+
const includePattern = args.include || "*";
|
|
18361
|
+
const grepArgs = ["-rn", `--include=${includePattern}`, args.pattern, searchPath];
|
|
18362
|
+
const { stdout } = await execFileAsync("grep", grepArgs, { maxBuffer: 10 * 1024 * 1024 });
|
|
18363
|
+
return {
|
|
18364
|
+
content: [{ type: "text", text: stdout || "(no matches)" }]
|
|
18365
|
+
};
|
|
18366
|
+
} catch (error) {
|
|
18367
|
+
const grepError = error;
|
|
18368
|
+
if (grepError.code === 1) {
|
|
18369
|
+
return { content: [{ type: "text", text: grepError.stdout || "(no matches)" }] };
|
|
18370
|
+
}
|
|
18371
|
+
return {
|
|
18372
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
18373
|
+
isError: true
|
|
18374
|
+
};
|
|
18375
|
+
}
|
|
18376
|
+
}
|
|
18377
|
+
|
|
18378
|
+
// src/mcpTools.ts
|
|
18379
|
+
var execAsync = promisify2(exec);
|
|
18380
|
+
var getCwd2 = () => process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR ?? process.cwd();
|
|
17695
18381
|
function createOpencodeMcpServer() {
|
|
17696
18382
|
return createSdkMcpServer2({
|
|
17697
18383
|
name: "opencode",
|
|
@@ -17702,7 +18388,7 @@ function createOpencodeMcpServer() {
|
|
|
17702
18388
|
encoding: exports_external.string().optional().describe("File encoding, defaults to utf-8")
|
|
17703
18389
|
}, async (args) => {
|
|
17704
18390
|
try {
|
|
17705
|
-
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(
|
|
18391
|
+
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(getCwd2(), args.path);
|
|
17706
18392
|
const content = await fs.readFile(filePath, args.encoding || "utf-8");
|
|
17707
18393
|
return {
|
|
17708
18394
|
content: [{ type: "text", text: content }]
|
|
@@ -17719,7 +18405,7 @@ function createOpencodeMcpServer() {
|
|
|
17719
18405
|
content: exports_external.string().describe("Content to write")
|
|
17720
18406
|
}, async (args) => {
|
|
17721
18407
|
try {
|
|
17722
|
-
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(
|
|
18408
|
+
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(getCwd2(), args.path);
|
|
17723
18409
|
await fs.mkdir(path2.dirname(filePath), { recursive: true });
|
|
17724
18410
|
await fs.writeFile(filePath, args.content, "utf-8");
|
|
17725
18411
|
return {
|
|
@@ -17738,7 +18424,7 @@ function createOpencodeMcpServer() {
|
|
|
17738
18424
|
newString: exports_external.string().describe("The replacement text")
|
|
17739
18425
|
}, async (args) => {
|
|
17740
18426
|
try {
|
|
17741
|
-
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(
|
|
18427
|
+
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(getCwd2(), args.path);
|
|
17742
18428
|
const content = await fs.readFile(filePath, "utf-8");
|
|
17743
18429
|
if (!content.includes(args.oldString)) {
|
|
17744
18430
|
return {
|
|
@@ -17764,7 +18450,7 @@ function createOpencodeMcpServer() {
|
|
|
17764
18450
|
}, async (args) => {
|
|
17765
18451
|
try {
|
|
17766
18452
|
const options = {
|
|
17767
|
-
cwd: args.cwd ||
|
|
18453
|
+
cwd: args.cwd || getCwd2(),
|
|
17768
18454
|
timeout: 120000
|
|
17769
18455
|
};
|
|
17770
18456
|
const { stdout, stderr } = await execAsync(args.command, options);
|
|
@@ -17787,7 +18473,7 @@ function createOpencodeMcpServer() {
|
|
|
17787
18473
|
}, async (args) => {
|
|
17788
18474
|
try {
|
|
17789
18475
|
const files = await glob(args.pattern, {
|
|
17790
|
-
cwd: args.cwd ||
|
|
18476
|
+
cwd: args.cwd || getCwd2(),
|
|
17791
18477
|
nodir: true,
|
|
17792
18478
|
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
17793
18479
|
});
|
|
@@ -17806,22 +18492,7 @@ function createOpencodeMcpServer() {
|
|
|
17806
18492
|
pattern: exports_external.string().describe("Regex pattern to search for"),
|
|
17807
18493
|
path: exports_external.string().optional().describe("Directory or file to search in"),
|
|
17808
18494
|
include: exports_external.string().optional().describe("File pattern to include, e.g., *.ts")
|
|
17809
|
-
},
|
|
17810
|
-
try {
|
|
17811
|
-
const searchPath = args.path || getCwd();
|
|
17812
|
-
const includePattern = args.include || "*";
|
|
17813
|
-
let cmd = `grep -rn --include="${includePattern}" "${args.pattern}" "${searchPath}" 2>/dev/null || true`;
|
|
17814
|
-
const { stdout } = await execAsync(cmd, { maxBuffer: 10 * 1024 * 1024 });
|
|
17815
|
-
return {
|
|
17816
|
-
content: [{ type: "text", text: stdout || "(no matches)" }]
|
|
17817
|
-
};
|
|
17818
|
-
} catch (error) {
|
|
17819
|
-
return {
|
|
17820
|
-
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
17821
|
-
isError: true
|
|
17822
|
-
};
|
|
17823
|
-
}
|
|
17824
|
-
})
|
|
18495
|
+
}, runGrepTool)
|
|
17825
18496
|
]
|
|
17826
18497
|
});
|
|
17827
18498
|
}
|
|
@@ -17881,7 +18552,7 @@ function buildQueryOptions(ctx, abortController) {
|
|
|
17881
18552
|
systemContext,
|
|
17882
18553
|
claudeExecutable,
|
|
17883
18554
|
passthrough,
|
|
17884
|
-
stream:
|
|
18555
|
+
stream: stream3,
|
|
17885
18556
|
sdkAgents,
|
|
17886
18557
|
passthroughMcp,
|
|
17887
18558
|
cleanEnv,
|
|
@@ -17923,7 +18594,7 @@ function buildQueryOptions(ctx, abortController) {
|
|
|
17923
18594
|
model,
|
|
17924
18595
|
pathToClaudeCodeExecutable: claudeExecutable,
|
|
17925
18596
|
...abortController ? { abortController } : {},
|
|
17926
|
-
...
|
|
18597
|
+
...stream3 ? { includePartialMessages: true } : {},
|
|
17927
18598
|
permissionMode: "bypassPermissions",
|
|
17928
18599
|
allowDangerouslySkipPermissions: true,
|
|
17929
18600
|
...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt, cwdNote),
|
|
@@ -18034,6 +18705,17 @@ var cherryTransforms = [
|
|
|
18034
18705
|
}
|
|
18035
18706
|
];
|
|
18036
18707
|
|
|
18708
|
+
// src/proxy/transforms/codex.ts
|
|
18709
|
+
var codexTransforms = [
|
|
18710
|
+
{
|
|
18711
|
+
name: "codex-force-passthrough",
|
|
18712
|
+
adapters: ["codex"],
|
|
18713
|
+
onRequest(ctx) {
|
|
18714
|
+
return { ...ctx, passthrough: true };
|
|
18715
|
+
}
|
|
18716
|
+
}
|
|
18717
|
+
];
|
|
18718
|
+
|
|
18037
18719
|
// src/proxy/transforms/registry.ts
|
|
18038
18720
|
var ADAPTER_TRANSFORMS = {
|
|
18039
18721
|
opencode: openCodeTransforms,
|
|
@@ -18043,7 +18725,8 @@ var ADAPTER_TRANSFORMS = {
|
|
|
18043
18725
|
forgecode: forgeCodeTransforms,
|
|
18044
18726
|
passthrough: passthroughTransforms,
|
|
18045
18727
|
cherry: cherryTransforms,
|
|
18046
|
-
openai: openCodeTransforms
|
|
18728
|
+
openai: openCodeTransforms,
|
|
18729
|
+
codex: [...openCodeTransforms, ...codexTransforms]
|
|
18047
18730
|
};
|
|
18048
18731
|
function getAdapterTransforms(adapterName) {
|
|
18049
18732
|
return ADAPTER_TRANSFORMS[adapterName] ?? [];
|
|
@@ -18051,7 +18734,7 @@ function getAdapterTransforms(adapterName) {
|
|
|
18051
18734
|
|
|
18052
18735
|
// src/proxy/plugins/loader.ts
|
|
18053
18736
|
import { readdirSync as readdirSync2, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
18054
|
-
import { join as
|
|
18737
|
+
import { join as join5, isAbsolute as isAbsolute2, extname } from "path";
|
|
18055
18738
|
import { pathToFileURL } from "url";
|
|
18056
18739
|
|
|
18057
18740
|
// src/proxy/plugins/validation.ts
|
|
@@ -18136,7 +18819,7 @@ async function loadPlugins(pluginDir, configPath) {
|
|
|
18136
18819
|
const loaded = [];
|
|
18137
18820
|
const seenNames = new Set;
|
|
18138
18821
|
for (const { filename, entry } of ordered) {
|
|
18139
|
-
const filePath = isAbsolute2(filename) ? filename :
|
|
18822
|
+
const filePath = isAbsolute2(filename) ? filename : join5(pluginDir, filename);
|
|
18140
18823
|
if (entry && !entry.enabled) {
|
|
18141
18824
|
loaded.push({
|
|
18142
18825
|
name: filename,
|
|
@@ -18518,8 +19201,8 @@ import {
|
|
|
18518
19201
|
unlinkSync,
|
|
18519
19202
|
writeFileSync as writeFileSync2
|
|
18520
19203
|
} from "node:fs";
|
|
18521
|
-
import { homedir as
|
|
18522
|
-
import { join as
|
|
19204
|
+
import { homedir as homedir5 } from "node:os";
|
|
19205
|
+
import { join as join6 } from "node:path";
|
|
18523
19206
|
var DEFAULT_MAX_STORED_SESSIONS = 1e4;
|
|
18524
19207
|
var STALE_LOCK_THRESHOLD_MS = 30000;
|
|
18525
19208
|
function getMaxStoredSessions() {
|
|
@@ -18570,11 +19253,11 @@ function getStorePath() {
|
|
|
18570
19253
|
if (!existsSync6(dir)) {
|
|
18571
19254
|
mkdirSync2(dir, { recursive: true });
|
|
18572
19255
|
}
|
|
18573
|
-
return
|
|
19256
|
+
return join6(dir, "sessions.json");
|
|
18574
19257
|
}
|
|
18575
19258
|
function getDefaultCacheDir() {
|
|
18576
|
-
const newDir =
|
|
18577
|
-
const oldDir =
|
|
19259
|
+
const newDir = join6(homedir5(), ".cache", "meridian");
|
|
19260
|
+
const oldDir = join6(homedir5(), ".cache", "opencode-claude-max-proxy");
|
|
18578
19261
|
if (existsSync6(newDir))
|
|
18579
19262
|
return newDir;
|
|
18580
19263
|
if (existsSync6(oldDir)) {
|
|
@@ -18914,7 +19597,7 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
|
|
|
18914
19597
|
}
|
|
18915
19598
|
|
|
18916
19599
|
// src/proxy/server.ts
|
|
18917
|
-
var exec2 =
|
|
19600
|
+
var exec2 = promisify3(execCallback);
|
|
18918
19601
|
var claudeExecutable = "";
|
|
18919
19602
|
var UPSTREAM_IDLE_MS = 90000;
|
|
18920
19603
|
function credentialStoreForProfile(profile) {
|
|
@@ -19144,8 +19827,8 @@ function createProxyServer(config = {}) {
|
|
|
19144
19827
|
pendingSessionStores.delete(key);
|
|
19145
19828
|
};
|
|
19146
19829
|
};
|
|
19147
|
-
const pluginDir = finalConfig.pluginDir ??
|
|
19148
|
-
const pluginConfigPath = finalConfig.pluginConfigPath ??
|
|
19830
|
+
const pluginDir = finalConfig.pluginDir ?? join8(homedir7(), ".config", "meridian", "plugins");
|
|
19831
|
+
const pluginConfigPath = finalConfig.pluginConfigPath ?? join8(homedir7(), ".config", "meridian", "plugins.json");
|
|
19149
19832
|
let loadedPlugins = [];
|
|
19150
19833
|
let pluginTransforms = [];
|
|
19151
19834
|
const app = new Hono2;
|
|
@@ -19161,6 +19844,7 @@ function createProxyServer(config = {}) {
|
|
|
19161
19844
|
app.use("/plugins", requireAuth);
|
|
19162
19845
|
app.use("/settings/*", requireAuth);
|
|
19163
19846
|
app.use("/settings", requireAuth);
|
|
19847
|
+
app.use("/design-login", requireAuth);
|
|
19164
19848
|
app.use("/auth/*", requireAuth);
|
|
19165
19849
|
app.get("/", (c) => {
|
|
19166
19850
|
const accept = c.req.header("accept") || "";
|
|
@@ -19169,7 +19853,7 @@ function createProxyServer(config = {}) {
|
|
|
19169
19853
|
status: "ok",
|
|
19170
19854
|
service: "meridian",
|
|
19171
19855
|
format: "anthropic",
|
|
19172
|
-
endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/models", "/telemetry", "/metrics", "/health"]
|
|
19856
|
+
endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/responses", "/v1/models", "/v1/design/*", "/design-login", "/telemetry", "/metrics", "/health"]
|
|
19173
19857
|
});
|
|
19174
19858
|
}
|
|
19175
19859
|
return c.html(landingHtml);
|
|
@@ -19282,7 +19966,7 @@ function createProxyServer(config = {}) {
|
|
|
19282
19966
|
stream: body.stream ?? false,
|
|
19283
19967
|
workingDirectory
|
|
19284
19968
|
}), adapterBase);
|
|
19285
|
-
const
|
|
19969
|
+
const stream3 = pipelineCtx.prefersStreaming !== undefined ? pipelineCtx.prefersStreaming : body.stream ?? false;
|
|
19286
19970
|
const effortHeader = c.req.header("x-opencode-effort");
|
|
19287
19971
|
const thinkingHeader = c.req.header("x-opencode-thinking");
|
|
19288
19972
|
const taskBudgetHeader = c.req.header("x-opencode-task-budget");
|
|
@@ -19357,7 +20041,7 @@ function createProxyServer(config = {}) {
|
|
|
19357
20041
|
const lineageType = lineageResult.type === "diverged" && !cachedSession ? "new" : lineageResult.type;
|
|
19358
20042
|
const msgCount = Array.isArray(body.messages) ? body.messages.length : 0;
|
|
19359
20043
|
const toolCount = body.tools?.length ?? 0;
|
|
19360
|
-
const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name}${requestSource ? ` source=${requestSource}` : ""}${profile.id !== "default" ? ` profile=${profile.id}${routingMode === "sticky" ? "(sticky)" : ""}` : ""} model=${model} stream=${
|
|
20044
|
+
const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name}${requestSource ? ` source=${requestSource}` : ""}${profile.id !== "default" ? ` profile=${profile.id}${routingMode === "sticky" ? "(sticky)" : ""}` : ""} model=${model} stream=${stream3} tools=${toolCount} 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}`;
|
|
19361
20045
|
plog(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
|
|
19362
20046
|
diagnosticLog2.session(`${requestLogLine}`, requestMeta.requestId);
|
|
19363
20047
|
if (lineageResult.type === "diverged" && profileSessionId && !isIndependentSession) {
|
|
@@ -19371,7 +20055,7 @@ function createProxyServer(config = {}) {
|
|
|
19371
20055
|
}
|
|
19372
20056
|
claudeLog("request.received", {
|
|
19373
20057
|
model,
|
|
19374
|
-
stream:
|
|
20058
|
+
stream: stream3,
|
|
19375
20059
|
queueWaitMs: requestMeta.queueStartedAt - requestMeta.queueEnteredAt,
|
|
19376
20060
|
messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
|
|
19377
20061
|
hasSystemPrompt: Boolean(body.system)
|
|
@@ -19610,7 +20294,7 @@ function createProxyServer(config = {}) {
|
|
|
19610
20294
|
stderrLines.push(data.trimEnd());
|
|
19611
20295
|
claudeLog("subprocess.stderr", { line: data.trimEnd() });
|
|
19612
20296
|
};
|
|
19613
|
-
if (!
|
|
20297
|
+
if (!stream3) {
|
|
19614
20298
|
const contentBlocks = [];
|
|
19615
20299
|
let assistantMessages = 0;
|
|
19616
20300
|
let hasStructuredOutput = false;
|
|
@@ -21534,6 +22218,107 @@ data: ${JSON.stringify({
|
|
|
21534
22218
|
}
|
|
21535
22219
|
});
|
|
21536
22220
|
});
|
|
22221
|
+
app.post("/v1/responses", async (c) => {
|
|
22222
|
+
const rawBody = await c.req.json();
|
|
22223
|
+
const anthropicBody = translateResponsesToAnthropic(rawBody);
|
|
22224
|
+
if (!anthropicBody) {
|
|
22225
|
+
return c.json({ error: { type: "invalid_request_error", message: "input: Field required", code: null } }, 400);
|
|
22226
|
+
}
|
|
22227
|
+
if (!anthropicBody.model) {
|
|
22228
|
+
return c.json({ error: { type: "invalid_request_error", message: "model: Field required", code: null } }, 400);
|
|
22229
|
+
}
|
|
22230
|
+
const internalHeaders = {
|
|
22231
|
+
"Content-Type": "application/json",
|
|
22232
|
+
"x-meridian-agent": "codex"
|
|
22233
|
+
};
|
|
22234
|
+
const xApiKey = c.req.header("x-api-key");
|
|
22235
|
+
if (xApiKey)
|
|
22236
|
+
internalHeaders["x-api-key"] = xApiKey;
|
|
22237
|
+
const authz = c.req.header("authorization");
|
|
22238
|
+
if (authz)
|
|
22239
|
+
internalHeaders["authorization"] = authz;
|
|
22240
|
+
const xProfile = c.req.header("x-meridian-profile");
|
|
22241
|
+
if (xProfile)
|
|
22242
|
+
internalHeaders["x-meridian-profile"] = xProfile;
|
|
22243
|
+
const internalReq = new Request("http://internal/v1/messages", {
|
|
22244
|
+
method: "POST",
|
|
22245
|
+
headers: internalHeaders,
|
|
22246
|
+
body: JSON.stringify(anthropicBody)
|
|
22247
|
+
});
|
|
22248
|
+
const internalRes = await app.fetch(internalReq);
|
|
22249
|
+
if (!internalRes.ok) {
|
|
22250
|
+
const errBody = await internalRes.text();
|
|
22251
|
+
return c.json({ error: { type: "upstream_error", message: errBody, code: null } }, internalRes.status);
|
|
22252
|
+
}
|
|
22253
|
+
const responseId = `resp_${randomUUID().replace(/-/g, "")}`;
|
|
22254
|
+
const created = Math.floor(Date.now() / 1000);
|
|
22255
|
+
const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : CANONICAL_SONNET_MODEL;
|
|
22256
|
+
const ctx = { responseId, model, created, reasoningRequested: reasoningRequested(rawBody) };
|
|
22257
|
+
if (!anthropicBody.stream) {
|
|
22258
|
+
const anthropicRes = await internalRes.json();
|
|
22259
|
+
return c.json(translateAnthropicToResponses(anthropicRes, ctx));
|
|
22260
|
+
}
|
|
22261
|
+
const encoder = new TextEncoder;
|
|
22262
|
+
const readable = new ReadableStream({
|
|
22263
|
+
async start(controller) {
|
|
22264
|
+
const reader = internalRes.body?.getReader();
|
|
22265
|
+
if (!reader) {
|
|
22266
|
+
controller.close();
|
|
22267
|
+
return;
|
|
22268
|
+
}
|
|
22269
|
+
const decoder = new TextDecoder;
|
|
22270
|
+
let buffer = "";
|
|
22271
|
+
const translate = createResponsesSseTranslator(ctx);
|
|
22272
|
+
try {
|
|
22273
|
+
while (true) {
|
|
22274
|
+
const { done, value } = await reader.read();
|
|
22275
|
+
if (done)
|
|
22276
|
+
break;
|
|
22277
|
+
buffer += decoder.decode(value, { stream: true });
|
|
22278
|
+
const lines = buffer.split(`
|
|
22279
|
+
`);
|
|
22280
|
+
buffer = lines.pop() ?? "";
|
|
22281
|
+
for (const line of lines) {
|
|
22282
|
+
if (!line.startsWith("data: "))
|
|
22283
|
+
continue;
|
|
22284
|
+
const dataStr = line.slice(6).trim();
|
|
22285
|
+
if (!dataStr)
|
|
22286
|
+
continue;
|
|
22287
|
+
let event;
|
|
22288
|
+
try {
|
|
22289
|
+
event = JSON.parse(dataStr);
|
|
22290
|
+
} catch {
|
|
22291
|
+
continue;
|
|
22292
|
+
}
|
|
22293
|
+
if (typeof event.type !== "string")
|
|
22294
|
+
continue;
|
|
22295
|
+
for (const emission of translate(event)) {
|
|
22296
|
+
controller.enqueue(encoder.encode(`event: ${emission.event}
|
|
22297
|
+
data: ${JSON.stringify(emission.data)}
|
|
22298
|
+
|
|
22299
|
+
`));
|
|
22300
|
+
}
|
|
22301
|
+
}
|
|
22302
|
+
}
|
|
22303
|
+
} catch (err) {
|
|
22304
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22305
|
+
controller.enqueue(encoder.encode(`event: response.failed
|
|
22306
|
+
data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: { message } } })}
|
|
22307
|
+
|
|
22308
|
+
`));
|
|
22309
|
+
} finally {
|
|
22310
|
+
controller.close();
|
|
22311
|
+
}
|
|
22312
|
+
}
|
|
22313
|
+
});
|
|
22314
|
+
return new Response(readable, {
|
|
22315
|
+
headers: {
|
|
22316
|
+
"Content-Type": "text/event-stream",
|
|
22317
|
+
"Cache-Control": "no-cache",
|
|
22318
|
+
Connection: "keep-alive"
|
|
22319
|
+
}
|
|
22320
|
+
});
|
|
22321
|
+
});
|
|
21537
22322
|
app.get("/v1/models", async (c) => {
|
|
21538
22323
|
const authStatus = await getClaudeAuthStatusAsync();
|
|
21539
22324
|
const isMax = authStatus?.subscriptionType === "max";
|
|
@@ -21696,6 +22481,65 @@ data: ${JSON.stringify({
|
|
|
21696
22481
|
} : {}
|
|
21697
22482
|
});
|
|
21698
22483
|
});
|
|
22484
|
+
const designTokenStore = createFileDesignTokenStore();
|
|
22485
|
+
const designLogin = createDesignLogin({ store: designTokenStore });
|
|
22486
|
+
app.get("/design-login", (c) => c.json(designLogin.start()));
|
|
22487
|
+
app.post("/design-login", async (c) => {
|
|
22488
|
+
let body;
|
|
22489
|
+
try {
|
|
22490
|
+
body = await c.req.json();
|
|
22491
|
+
} catch {
|
|
22492
|
+
return c.json({ type: "error", error: { type: "invalid_request", message: "Request body must be JSON with a 'code' field." } }, 400);
|
|
22493
|
+
}
|
|
22494
|
+
const result = await designLogin.exchange(body);
|
|
22495
|
+
if (result.status === 200)
|
|
22496
|
+
plog(`[PROXY] Design token stored via /design-login`);
|
|
22497
|
+
return new Response(JSON.stringify(result.body), {
|
|
22498
|
+
status: result.status,
|
|
22499
|
+
headers: { "content-type": "application/json" }
|
|
22500
|
+
});
|
|
22501
|
+
});
|
|
22502
|
+
app.get("/v1/design/*", (c) => {
|
|
22503
|
+
c.status(200);
|
|
22504
|
+
c.header("content-type", "text/event-stream");
|
|
22505
|
+
c.header("cache-control", "no-cache");
|
|
22506
|
+
return stream(c, async (s) => {
|
|
22507
|
+
while (!s.aborted) {
|
|
22508
|
+
await s.write(`: keepalive
|
|
22509
|
+
|
|
22510
|
+
`);
|
|
22511
|
+
await s.sleep(15000);
|
|
22512
|
+
}
|
|
22513
|
+
});
|
|
22514
|
+
});
|
|
22515
|
+
app.post("/v1/design/*", async (c) => {
|
|
22516
|
+
const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined);
|
|
22517
|
+
const url = new URL(c.req.url);
|
|
22518
|
+
const upstreamUrl = `${DESIGN_UPSTREAM_ORIGIN}${url.pathname}${url.search}`;
|
|
22519
|
+
const designToken = await getDesignAccessToken({ store: designTokenStore });
|
|
22520
|
+
const authHeaders = await resolveDesignAuthHeaders({
|
|
22521
|
+
designToken,
|
|
22522
|
+
profile,
|
|
22523
|
+
credentialStore: credentialStoreForProfile(profile),
|
|
22524
|
+
ensureFresh: ensureFreshToken
|
|
22525
|
+
});
|
|
22526
|
+
const body = await c.req.arrayBuffer();
|
|
22527
|
+
const forwardHeaders = buildDesignForwardHeaders((name) => c.req.header(name), authHeaders);
|
|
22528
|
+
let upstreamRes;
|
|
22529
|
+
try {
|
|
22530
|
+
upstreamRes = await fetch(upstreamUrl, { method: "POST", headers: forwardHeaders, body });
|
|
22531
|
+
} catch (err) {
|
|
22532
|
+
return c.json({ type: "error", error: { type: "upstream_error", message: err instanceof Error ? err.message : String(err) } }, 502);
|
|
22533
|
+
}
|
|
22534
|
+
if (isDesignAuthFailure(upstreamRes.status)) {
|
|
22535
|
+
return c.json({ type: "error", error: { type: "auth_error", message: "Unauthorized. Run /design-login to authorize Claude Design (adds user:design:read/write scopes)." } }, 401);
|
|
22536
|
+
}
|
|
22537
|
+
plog(`[PROXY] DESIGN upstream=${upstreamRes.status}`);
|
|
22538
|
+
return new Response(upstreamRes.body, {
|
|
22539
|
+
status: upstreamRes.status,
|
|
22540
|
+
headers: filterUpstreamResponseHeaders(upstreamRes.headers.entries())
|
|
22541
|
+
});
|
|
22542
|
+
});
|
|
21699
22543
|
app.all("*", (c) => {
|
|
21700
22544
|
plog(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
|
|
21701
22545
|
return c.json({ error: { type: "not_found", message: `Endpoint not supported: ${c.req.method} ${new URL(c.req.url).pathname}` } }, 404);
|