@rynfar/meridian 1.53.0 → 1.55.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 +25 -925
- package/dist/{profileCli-s1h4vh8w.js → cli-jhm27q0x.js} +346 -31
- package/dist/{cli-027as0fx.js → cli-w63v1ftp.js} +461 -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.map +1 -1
- package/dist/proxy/adapters/pi.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/profileCli.d.ts +48 -0
- package/dist/proxy/profileCli.d.ts.map +1 -0
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +1 -1
- 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();
|
|
@@ -3641,6 +3646,112 @@ var cors = (options) => {
|
|
|
3641
3646
|
};
|
|
3642
3647
|
};
|
|
3643
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
|
+
|
|
3644
3755
|
// node_modules/@hono/node-server/dist/index.mjs
|
|
3645
3756
|
import { createServer as createServerHTTP } from "http";
|
|
3646
3757
|
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
@@ -3921,13 +4032,13 @@ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromi
|
|
|
3921
4032
|
}
|
|
3922
4033
|
}
|
|
3923
4034
|
}
|
|
3924
|
-
function writeFromReadableStream(
|
|
3925
|
-
if (
|
|
4035
|
+
function writeFromReadableStream(stream2, writable) {
|
|
4036
|
+
if (stream2.locked) {
|
|
3926
4037
|
throw new TypeError("ReadableStream is locked.");
|
|
3927
4038
|
} else if (writable.destroyed) {
|
|
3928
4039
|
return;
|
|
3929
4040
|
}
|
|
3930
|
-
return writeFromReadableStreamDefaultReader(
|
|
4041
|
+
return writeFromReadableStreamDefaultReader(stream2.getReader(), writable);
|
|
3931
4042
|
}
|
|
3932
4043
|
var buildOutgoingHttpHeaders = (headers) => {
|
|
3933
4044
|
const res = {};
|
|
@@ -4192,8 +4303,8 @@ var serve = (options, listeningListener) => {
|
|
|
4192
4303
|
};
|
|
4193
4304
|
|
|
4194
4305
|
// src/proxy/server.ts
|
|
4195
|
-
import { homedir as
|
|
4196
|
-
import { join as
|
|
4306
|
+
import { homedir as homedir7 } from "node:os";
|
|
4307
|
+
import { join as join8 } from "node:path";
|
|
4197
4308
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
4198
4309
|
|
|
4199
4310
|
// src/proxy/rateLimitStore.ts
|
|
@@ -4542,7 +4653,7 @@ function createRequestContext(params) {
|
|
|
4542
4653
|
}
|
|
4543
4654
|
// src/proxy/server.ts
|
|
4544
4655
|
import { exec as execCallback } from "child_process";
|
|
4545
|
-
import { promisify as
|
|
4656
|
+
import { promisify as promisify3 } from "util";
|
|
4546
4657
|
import { randomUUID } from "crypto";
|
|
4547
4658
|
|
|
4548
4659
|
// src/proxy/passthroughTools.ts
|
|
@@ -9877,7 +9988,7 @@ function introSection(h){
|
|
|
9877
9988
|
meta.push('port '+location.port);
|
|
9878
9989
|
return '<div class="intro">'
|
|
9879
9990
|
+'<h2>Harness Claude, your way.</h2>'
|
|
9880
|
-
+'<p>Meridian bridges any Anthropic-API agent to your Claude subscription — point the agent’s <code>ANTHROPIC_BASE_URL</code> at <code>http://'+esc(location.host)+'</code> and every request routes through the active account below. Setup guides for each agent live in the <a href="https://github.com/rynfar/meridian
|
|
9991
|
+
+'<p>Meridian bridges any Anthropic-API agent to your Claude subscription — point the agent’s <code>ANTHROPIC_BASE_URL</code> at <code>http://'+esc(location.host)+'</code> and every request routes through the active account below. Setup guides for each agent live in the <a href="https://github.com/rynfar/meridian/blob/main/docs/agents.md">Agent Setup guide</a>.</p>'
|
|
9881
9992
|
+'<div class="intro-meta">'+meta.join(' · ')+'</div>'
|
|
9882
9993
|
+'</div>';
|
|
9883
9994
|
}
|
|
@@ -10274,6 +10385,217 @@ function formatSdkTermination(t, ctx) {
|
|
|
10274
10385
|
return `sdk_termination ${parts.join(" ")}`;
|
|
10275
10386
|
}
|
|
10276
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
|
+
|
|
10277
10599
|
// src/proxy/openai.ts
|
|
10278
10600
|
function extractOpenAiContent(content) {
|
|
10279
10601
|
if (typeof content === "string")
|
|
@@ -12008,8 +12330,8 @@ function extractPiCwd(body) {
|
|
|
12008
12330
|
}
|
|
12009
12331
|
var piAdapter = {
|
|
12010
12332
|
name: "pi",
|
|
12011
|
-
getSessionId(
|
|
12012
|
-
return;
|
|
12333
|
+
getSessionId(c) {
|
|
12334
|
+
return c.req.header("x-session-affinity");
|
|
12013
12335
|
},
|
|
12014
12336
|
extractWorkingDirectory(body) {
|
|
12015
12337
|
return extractPiCwd(body);
|
|
@@ -12274,6 +12596,9 @@ var codexAdapter = {
|
|
|
12274
12596
|
name: "codex",
|
|
12275
12597
|
usesPassthrough() {
|
|
12276
12598
|
return true;
|
|
12599
|
+
},
|
|
12600
|
+
getSessionId(c) {
|
|
12601
|
+
return c.req.header("x-codex-session");
|
|
12277
12602
|
}
|
|
12278
12603
|
};
|
|
12279
12604
|
|
|
@@ -12307,9 +12632,9 @@ var cherryAdapter = {
|
|
|
12307
12632
|
|
|
12308
12633
|
// src/proxy/adapterInstances.ts
|
|
12309
12634
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
12310
|
-
import { join as
|
|
12311
|
-
import { homedir as
|
|
12312
|
-
var CONFIG_FILE =
|
|
12635
|
+
import { join as join4 } from "node:path";
|
|
12636
|
+
import { homedir as homedir4 } from "node:os";
|
|
12637
|
+
var CONFIG_FILE = join4(homedir4(), ".config", "meridian", "adapter-instances.json");
|
|
12313
12638
|
function parseAdapterInstances(raw2) {
|
|
12314
12639
|
if (!raw2)
|
|
12315
12640
|
return {};
|
|
@@ -12478,7 +12803,7 @@ import { createSdkMcpServer as createSdkMcpServer2, tool } from "@anthropic-ai/c
|
|
|
12478
12803
|
import * as fs from "node:fs/promises";
|
|
12479
12804
|
import * as path2 from "node:path";
|
|
12480
12805
|
import { exec } from "node:child_process";
|
|
12481
|
-
import { promisify } from "node:util";
|
|
12806
|
+
import { promisify as promisify2 } from "node:util";
|
|
12482
12807
|
|
|
12483
12808
|
// node_modules/@isaacs/balanced-match/dist/esm/index.js
|
|
12484
12809
|
var balanced = (a, b, str) => {
|
|
@@ -17999,7 +18324,7 @@ function globIterate(pattern, options = {}) {
|
|
|
17999
18324
|
return new Glob(pattern, options).iterate();
|
|
18000
18325
|
}
|
|
18001
18326
|
var streamSync = globStreamSync;
|
|
18002
|
-
var
|
|
18327
|
+
var stream2 = Object.assign(globStream, { sync: globStreamSync });
|
|
18003
18328
|
var iterateSync = globIterateSync;
|
|
18004
18329
|
var iterate = Object.assign(globIterate, {
|
|
18005
18330
|
sync: globIterateSync
|
|
@@ -18013,7 +18338,7 @@ var glob = Object.assign(glob_, {
|
|
|
18013
18338
|
globSync,
|
|
18014
18339
|
sync,
|
|
18015
18340
|
globStream,
|
|
18016
|
-
stream,
|
|
18341
|
+
stream: stream2,
|
|
18017
18342
|
globStreamSync,
|
|
18018
18343
|
streamSync,
|
|
18019
18344
|
globIterate,
|
|
@@ -18027,9 +18352,35 @@ var glob = Object.assign(glob_, {
|
|
|
18027
18352
|
});
|
|
18028
18353
|
glob.glob = glob;
|
|
18029
18354
|
|
|
18030
|
-
// src/
|
|
18031
|
-
|
|
18355
|
+
// src/grepTool.ts
|
|
18356
|
+
import { execFile } from "node:child_process";
|
|
18357
|
+
import { promisify } from "node:util";
|
|
18358
|
+
var execFileAsync = promisify(execFile);
|
|
18032
18359
|
var getCwd = () => process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR ?? process.cwd();
|
|
18360
|
+
async function runGrepTool(args) {
|
|
18361
|
+
try {
|
|
18362
|
+
const searchPath = args.path || getCwd();
|
|
18363
|
+
const includePattern = args.include || "*";
|
|
18364
|
+
const grepArgs = ["-rn", `--include=${includePattern}`, args.pattern, searchPath];
|
|
18365
|
+
const { stdout } = await execFileAsync("grep", grepArgs, { maxBuffer: 10 * 1024 * 1024 });
|
|
18366
|
+
return {
|
|
18367
|
+
content: [{ type: "text", text: stdout || "(no matches)" }]
|
|
18368
|
+
};
|
|
18369
|
+
} catch (error) {
|
|
18370
|
+
const grepError = error;
|
|
18371
|
+
if (grepError.code === 1) {
|
|
18372
|
+
return { content: [{ type: "text", text: grepError.stdout || "(no matches)" }] };
|
|
18373
|
+
}
|
|
18374
|
+
return {
|
|
18375
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
18376
|
+
isError: true
|
|
18377
|
+
};
|
|
18378
|
+
}
|
|
18379
|
+
}
|
|
18380
|
+
|
|
18381
|
+
// src/mcpTools.ts
|
|
18382
|
+
var execAsync = promisify2(exec);
|
|
18383
|
+
var getCwd2 = () => process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR ?? process.cwd();
|
|
18033
18384
|
function createOpencodeMcpServer() {
|
|
18034
18385
|
return createSdkMcpServer2({
|
|
18035
18386
|
name: "opencode",
|
|
@@ -18040,7 +18391,7 @@ function createOpencodeMcpServer() {
|
|
|
18040
18391
|
encoding: exports_external.string().optional().describe("File encoding, defaults to utf-8")
|
|
18041
18392
|
}, async (args) => {
|
|
18042
18393
|
try {
|
|
18043
|
-
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(
|
|
18394
|
+
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(getCwd2(), args.path);
|
|
18044
18395
|
const content = await fs.readFile(filePath, args.encoding || "utf-8");
|
|
18045
18396
|
return {
|
|
18046
18397
|
content: [{ type: "text", text: content }]
|
|
@@ -18057,7 +18408,7 @@ function createOpencodeMcpServer() {
|
|
|
18057
18408
|
content: exports_external.string().describe("Content to write")
|
|
18058
18409
|
}, async (args) => {
|
|
18059
18410
|
try {
|
|
18060
|
-
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(
|
|
18411
|
+
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(getCwd2(), args.path);
|
|
18061
18412
|
await fs.mkdir(path2.dirname(filePath), { recursive: true });
|
|
18062
18413
|
await fs.writeFile(filePath, args.content, "utf-8");
|
|
18063
18414
|
return {
|
|
@@ -18076,7 +18427,7 @@ function createOpencodeMcpServer() {
|
|
|
18076
18427
|
newString: exports_external.string().describe("The replacement text")
|
|
18077
18428
|
}, async (args) => {
|
|
18078
18429
|
try {
|
|
18079
|
-
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(
|
|
18430
|
+
const filePath = path2.isAbsolute(args.path) ? args.path : path2.resolve(getCwd2(), args.path);
|
|
18080
18431
|
const content = await fs.readFile(filePath, "utf-8");
|
|
18081
18432
|
if (!content.includes(args.oldString)) {
|
|
18082
18433
|
return {
|
|
@@ -18102,7 +18453,7 @@ function createOpencodeMcpServer() {
|
|
|
18102
18453
|
}, async (args) => {
|
|
18103
18454
|
try {
|
|
18104
18455
|
const options = {
|
|
18105
|
-
cwd: args.cwd ||
|
|
18456
|
+
cwd: args.cwd || getCwd2(),
|
|
18106
18457
|
timeout: 120000
|
|
18107
18458
|
};
|
|
18108
18459
|
const { stdout, stderr } = await execAsync(args.command, options);
|
|
@@ -18125,7 +18476,7 @@ function createOpencodeMcpServer() {
|
|
|
18125
18476
|
}, async (args) => {
|
|
18126
18477
|
try {
|
|
18127
18478
|
const files = await glob(args.pattern, {
|
|
18128
|
-
cwd: args.cwd ||
|
|
18479
|
+
cwd: args.cwd || getCwd2(),
|
|
18129
18480
|
nodir: true,
|
|
18130
18481
|
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
18131
18482
|
});
|
|
@@ -18144,22 +18495,7 @@ function createOpencodeMcpServer() {
|
|
|
18144
18495
|
pattern: exports_external.string().describe("Regex pattern to search for"),
|
|
18145
18496
|
path: exports_external.string().optional().describe("Directory or file to search in"),
|
|
18146
18497
|
include: exports_external.string().optional().describe("File pattern to include, e.g., *.ts")
|
|
18147
|
-
},
|
|
18148
|
-
try {
|
|
18149
|
-
const searchPath = args.path || getCwd();
|
|
18150
|
-
const includePattern = args.include || "*";
|
|
18151
|
-
let cmd = `grep -rn --include="${includePattern}" "${args.pattern}" "${searchPath}" 2>/dev/null || true`;
|
|
18152
|
-
const { stdout } = await execAsync(cmd, { maxBuffer: 10 * 1024 * 1024 });
|
|
18153
|
-
return {
|
|
18154
|
-
content: [{ type: "text", text: stdout || "(no matches)" }]
|
|
18155
|
-
};
|
|
18156
|
-
} catch (error) {
|
|
18157
|
-
return {
|
|
18158
|
-
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
18159
|
-
isError: true
|
|
18160
|
-
};
|
|
18161
|
-
}
|
|
18162
|
-
})
|
|
18498
|
+
}, runGrepTool)
|
|
18163
18499
|
]
|
|
18164
18500
|
});
|
|
18165
18501
|
}
|
|
@@ -18219,7 +18555,7 @@ function buildQueryOptions(ctx, abortController) {
|
|
|
18219
18555
|
systemContext,
|
|
18220
18556
|
claudeExecutable,
|
|
18221
18557
|
passthrough,
|
|
18222
|
-
stream:
|
|
18558
|
+
stream: stream3,
|
|
18223
18559
|
sdkAgents,
|
|
18224
18560
|
passthroughMcp,
|
|
18225
18561
|
cleanEnv,
|
|
@@ -18261,7 +18597,7 @@ function buildQueryOptions(ctx, abortController) {
|
|
|
18261
18597
|
model,
|
|
18262
18598
|
pathToClaudeCodeExecutable: claudeExecutable,
|
|
18263
18599
|
...abortController ? { abortController } : {},
|
|
18264
|
-
...
|
|
18600
|
+
...stream3 ? { includePartialMessages: true } : {},
|
|
18265
18601
|
permissionMode: "bypassPermissions",
|
|
18266
18602
|
allowDangerouslySkipPermissions: true,
|
|
18267
18603
|
...resolveSystemPrompt(systemContext, passthrough, settingSources, codeSystemPrompt, clientSystemPrompt, cwdNote),
|
|
@@ -18401,7 +18737,7 @@ function getAdapterTransforms(adapterName) {
|
|
|
18401
18737
|
|
|
18402
18738
|
// src/proxy/plugins/loader.ts
|
|
18403
18739
|
import { readdirSync as readdirSync2, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
18404
|
-
import { join as
|
|
18740
|
+
import { join as join5, isAbsolute as isAbsolute2, extname } from "path";
|
|
18405
18741
|
import { pathToFileURL } from "url";
|
|
18406
18742
|
|
|
18407
18743
|
// src/proxy/plugins/validation.ts
|
|
@@ -18486,7 +18822,7 @@ async function loadPlugins(pluginDir, configPath) {
|
|
|
18486
18822
|
const loaded = [];
|
|
18487
18823
|
const seenNames = new Set;
|
|
18488
18824
|
for (const { filename, entry } of ordered) {
|
|
18489
|
-
const filePath = isAbsolute2(filename) ? filename :
|
|
18825
|
+
const filePath = isAbsolute2(filename) ? filename : join5(pluginDir, filename);
|
|
18490
18826
|
if (entry && !entry.enabled) {
|
|
18491
18827
|
loaded.push({
|
|
18492
18828
|
name: filename,
|
|
@@ -18868,8 +19204,8 @@ import {
|
|
|
18868
19204
|
unlinkSync,
|
|
18869
19205
|
writeFileSync as writeFileSync2
|
|
18870
19206
|
} from "node:fs";
|
|
18871
|
-
import { homedir as
|
|
18872
|
-
import { join as
|
|
19207
|
+
import { homedir as homedir5 } from "node:os";
|
|
19208
|
+
import { join as join6 } from "node:path";
|
|
18873
19209
|
var DEFAULT_MAX_STORED_SESSIONS = 1e4;
|
|
18874
19210
|
var STALE_LOCK_THRESHOLD_MS = 30000;
|
|
18875
19211
|
function getMaxStoredSessions() {
|
|
@@ -18920,11 +19256,11 @@ function getStorePath() {
|
|
|
18920
19256
|
if (!existsSync6(dir)) {
|
|
18921
19257
|
mkdirSync2(dir, { recursive: true });
|
|
18922
19258
|
}
|
|
18923
|
-
return
|
|
19259
|
+
return join6(dir, "sessions.json");
|
|
18924
19260
|
}
|
|
18925
19261
|
function getDefaultCacheDir() {
|
|
18926
|
-
const newDir =
|
|
18927
|
-
const oldDir =
|
|
19262
|
+
const newDir = join6(homedir5(), ".cache", "meridian");
|
|
19263
|
+
const oldDir = join6(homedir5(), ".cache", "opencode-claude-max-proxy");
|
|
18928
19264
|
if (existsSync6(newDir))
|
|
18929
19265
|
return newDir;
|
|
18930
19266
|
if (existsSync6(oldDir)) {
|
|
@@ -19264,7 +19600,7 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
|
|
|
19264
19600
|
}
|
|
19265
19601
|
|
|
19266
19602
|
// src/proxy/server.ts
|
|
19267
|
-
var exec2 =
|
|
19603
|
+
var exec2 = promisify3(execCallback);
|
|
19268
19604
|
var claudeExecutable = "";
|
|
19269
19605
|
var UPSTREAM_IDLE_MS = 90000;
|
|
19270
19606
|
function credentialStoreForProfile(profile) {
|
|
@@ -19494,8 +19830,8 @@ function createProxyServer(config = {}) {
|
|
|
19494
19830
|
pendingSessionStores.delete(key);
|
|
19495
19831
|
};
|
|
19496
19832
|
};
|
|
19497
|
-
const pluginDir = finalConfig.pluginDir ??
|
|
19498
|
-
const pluginConfigPath = finalConfig.pluginConfigPath ??
|
|
19833
|
+
const pluginDir = finalConfig.pluginDir ?? join8(homedir7(), ".config", "meridian", "plugins");
|
|
19834
|
+
const pluginConfigPath = finalConfig.pluginConfigPath ?? join8(homedir7(), ".config", "meridian", "plugins.json");
|
|
19499
19835
|
let loadedPlugins = [];
|
|
19500
19836
|
let pluginTransforms = [];
|
|
19501
19837
|
const app = new Hono2;
|
|
@@ -19511,6 +19847,7 @@ function createProxyServer(config = {}) {
|
|
|
19511
19847
|
app.use("/plugins", requireAuth);
|
|
19512
19848
|
app.use("/settings/*", requireAuth);
|
|
19513
19849
|
app.use("/settings", requireAuth);
|
|
19850
|
+
app.use("/design-login", requireAuth);
|
|
19514
19851
|
app.use("/auth/*", requireAuth);
|
|
19515
19852
|
app.get("/", (c) => {
|
|
19516
19853
|
const accept = c.req.header("accept") || "";
|
|
@@ -19519,7 +19856,7 @@ function createProxyServer(config = {}) {
|
|
|
19519
19856
|
status: "ok",
|
|
19520
19857
|
service: "meridian",
|
|
19521
19858
|
format: "anthropic",
|
|
19522
|
-
endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/responses", "/v1/models", "/telemetry", "/metrics", "/health"]
|
|
19859
|
+
endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/responses", "/v1/models", "/v1/design/*", "/design-login", "/telemetry", "/metrics", "/health"]
|
|
19523
19860
|
});
|
|
19524
19861
|
}
|
|
19525
19862
|
return c.html(landingHtml);
|
|
@@ -19632,7 +19969,7 @@ function createProxyServer(config = {}) {
|
|
|
19632
19969
|
stream: body.stream ?? false,
|
|
19633
19970
|
workingDirectory
|
|
19634
19971
|
}), adapterBase);
|
|
19635
|
-
const
|
|
19972
|
+
const stream3 = pipelineCtx.prefersStreaming !== undefined ? pipelineCtx.prefersStreaming : body.stream ?? false;
|
|
19636
19973
|
const effortHeader = c.req.header("x-opencode-effort");
|
|
19637
19974
|
const thinkingHeader = c.req.header("x-opencode-thinking");
|
|
19638
19975
|
const taskBudgetHeader = c.req.header("x-opencode-task-budget");
|
|
@@ -19679,7 +20016,7 @@ function createProxyServer(config = {}) {
|
|
|
19679
20016
|
const lastMessage = Array.isArray(body.messages) ? body.messages[body.messages.length - 1] : undefined;
|
|
19680
20017
|
const lastIsToolResult = Array.isArray(lastMessage?.content) && lastMessage.content.some((b) => b?.type === "tool_result");
|
|
19681
20018
|
const isClientDrivenLoop = adapterBase !== "claude-code" && !agentSessionId && lastIsToolResult;
|
|
19682
|
-
const isIndependentSession = requestSource?.startsWith("fork-") || requestSource?.startsWith("subagent-") || isClientDrivenLoop || false;
|
|
20019
|
+
const isIndependentSession = !agentSessionId && (requestSource?.startsWith("fork-") || requestSource?.startsWith("subagent-")) || isClientDrivenLoop || false;
|
|
19683
20020
|
if (!isIndependentSession && profileSessionId) {
|
|
19684
20021
|
const pendingStore = pendingSessionStores.get(profileSessionId);
|
|
19685
20022
|
if (pendingStore) {
|
|
@@ -19707,7 +20044,7 @@ function createProxyServer(config = {}) {
|
|
|
19707
20044
|
const lineageType = lineageResult.type === "diverged" && !cachedSession ? "new" : lineageResult.type;
|
|
19708
20045
|
const msgCount = Array.isArray(body.messages) ? body.messages.length : 0;
|
|
19709
20046
|
const toolCount = body.tools?.length ?? 0;
|
|
19710
|
-
const requestLogLine = `${requestMeta.requestId} adapter=${adapter.name}${requestSource ? ` source=${requestSource}` : ""}${profile.id !== "default" ? ` profile=${profile.id}${routingMode === "sticky" ? "(sticky)" : ""}` : ""} model=${model} stream=${
|
|
20047
|
+
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}`;
|
|
19711
20048
|
plog(`[PROXY] ${requestLogLine} msgs=${msgSummary}`);
|
|
19712
20049
|
diagnosticLog2.session(`${requestLogLine}`, requestMeta.requestId);
|
|
19713
20050
|
if (lineageResult.type === "diverged" && profileSessionId && !isIndependentSession) {
|
|
@@ -19721,7 +20058,7 @@ function createProxyServer(config = {}) {
|
|
|
19721
20058
|
}
|
|
19722
20059
|
claudeLog("request.received", {
|
|
19723
20060
|
model,
|
|
19724
|
-
stream:
|
|
20061
|
+
stream: stream3,
|
|
19725
20062
|
queueWaitMs: requestMeta.queueStartedAt - requestMeta.queueEnteredAt,
|
|
19726
20063
|
messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
|
|
19727
20064
|
hasSystemPrompt: Boolean(body.system)
|
|
@@ -19960,7 +20297,7 @@ function createProxyServer(config = {}) {
|
|
|
19960
20297
|
stderrLines.push(data.trimEnd());
|
|
19961
20298
|
claudeLog("subprocess.stderr", { line: data.trimEnd() });
|
|
19962
20299
|
};
|
|
19963
|
-
if (!
|
|
20300
|
+
if (!stream3) {
|
|
19964
20301
|
const contentBlocks = [];
|
|
19965
20302
|
let assistantMessages = 0;
|
|
19966
20303
|
let hasStructuredOutput = false;
|
|
@@ -21897,6 +22234,10 @@ data: ${JSON.stringify({
|
|
|
21897
22234
|
"Content-Type": "application/json",
|
|
21898
22235
|
"x-meridian-agent": "codex"
|
|
21899
22236
|
};
|
|
22237
|
+
const promptCacheKey = rawBody.prompt_cache_key;
|
|
22238
|
+
if (typeof promptCacheKey === "string" && promptCacheKey.length > 0) {
|
|
22239
|
+
internalHeaders["x-codex-session"] = promptCacheKey;
|
|
22240
|
+
}
|
|
21900
22241
|
const xApiKey = c.req.header("x-api-key");
|
|
21901
22242
|
if (xApiKey)
|
|
21902
22243
|
internalHeaders["x-api-key"] = xApiKey;
|
|
@@ -22147,6 +22488,65 @@ data: ${JSON.stringify({ response: { id: responseId, status: "failed", error: {
|
|
|
22147
22488
|
} : {}
|
|
22148
22489
|
});
|
|
22149
22490
|
});
|
|
22491
|
+
const designTokenStore = createFileDesignTokenStore();
|
|
22492
|
+
const designLogin = createDesignLogin({ store: designTokenStore });
|
|
22493
|
+
app.get("/design-login", (c) => c.json(designLogin.start()));
|
|
22494
|
+
app.post("/design-login", async (c) => {
|
|
22495
|
+
let body;
|
|
22496
|
+
try {
|
|
22497
|
+
body = await c.req.json();
|
|
22498
|
+
} catch {
|
|
22499
|
+
return c.json({ type: "error", error: { type: "invalid_request", message: "Request body must be JSON with a 'code' field." } }, 400);
|
|
22500
|
+
}
|
|
22501
|
+
const result = await designLogin.exchange(body);
|
|
22502
|
+
if (result.status === 200)
|
|
22503
|
+
plog(`[PROXY] Design token stored via /design-login`);
|
|
22504
|
+
return new Response(JSON.stringify(result.body), {
|
|
22505
|
+
status: result.status,
|
|
22506
|
+
headers: { "content-type": "application/json" }
|
|
22507
|
+
});
|
|
22508
|
+
});
|
|
22509
|
+
app.get("/v1/design/*", (c) => {
|
|
22510
|
+
c.status(200);
|
|
22511
|
+
c.header("content-type", "text/event-stream");
|
|
22512
|
+
c.header("cache-control", "no-cache");
|
|
22513
|
+
return stream(c, async (s) => {
|
|
22514
|
+
while (!s.aborted) {
|
|
22515
|
+
await s.write(`: keepalive
|
|
22516
|
+
|
|
22517
|
+
`);
|
|
22518
|
+
await s.sleep(15000);
|
|
22519
|
+
}
|
|
22520
|
+
});
|
|
22521
|
+
});
|
|
22522
|
+
app.post("/v1/design/*", async (c) => {
|
|
22523
|
+
const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined);
|
|
22524
|
+
const url = new URL(c.req.url);
|
|
22525
|
+
const upstreamUrl = `${DESIGN_UPSTREAM_ORIGIN}${url.pathname}${url.search}`;
|
|
22526
|
+
const designToken = await getDesignAccessToken({ store: designTokenStore });
|
|
22527
|
+
const authHeaders = await resolveDesignAuthHeaders({
|
|
22528
|
+
designToken,
|
|
22529
|
+
profile,
|
|
22530
|
+
credentialStore: credentialStoreForProfile(profile),
|
|
22531
|
+
ensureFresh: ensureFreshToken
|
|
22532
|
+
});
|
|
22533
|
+
const body = await c.req.arrayBuffer();
|
|
22534
|
+
const forwardHeaders = buildDesignForwardHeaders((name) => c.req.header(name), authHeaders);
|
|
22535
|
+
let upstreamRes;
|
|
22536
|
+
try {
|
|
22537
|
+
upstreamRes = await fetch(upstreamUrl, { method: "POST", headers: forwardHeaders, body });
|
|
22538
|
+
} catch (err) {
|
|
22539
|
+
return c.json({ type: "error", error: { type: "upstream_error", message: err instanceof Error ? err.message : String(err) } }, 502);
|
|
22540
|
+
}
|
|
22541
|
+
if (isDesignAuthFailure(upstreamRes.status)) {
|
|
22542
|
+
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);
|
|
22543
|
+
}
|
|
22544
|
+
plog(`[PROXY] DESIGN upstream=${upstreamRes.status}`);
|
|
22545
|
+
return new Response(upstreamRes.body, {
|
|
22546
|
+
status: upstreamRes.status,
|
|
22547
|
+
headers: filterUpstreamResponseHeaders(upstreamRes.headers.entries())
|
|
22548
|
+
});
|
|
22549
|
+
});
|
|
22150
22550
|
app.all("*", (c) => {
|
|
22151
22551
|
plog(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
|
|
22152
22552
|
return c.json({ error: { type: "not_found", message: `Endpoint not supported: ${c.req.method} ${new URL(c.req.url).pathname}` } }, 404);
|