fix: claude-sub uses bridge HTTP instead of direct WS
All checks were successful
Build & Deploy Dashboard / deploy (push) Successful in 1m2s

This commit is contained in:
Cosmo
2026-04-16 09:45:29 +00:00
parent 35f6456363
commit 1d4a00a80d

View File

@@ -1,97 +1,38 @@
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
const GATEWAY_URL = "ws://192.168.31.103:18789"; // Bridge HTTP API (cosmo-studio bridge в той же сети coolify)
const GATEWAY_TOKEN = "c55292f854e8308c4fed926c40c3a8995a7213fde79fed72"; const BRIDGE_URL = "http://172.18.0.5:3402/api/usage";
async function gatewayRequest(method: string, params: object = {}): Promise<unknown> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
ws.close();
reject(new Error("Gateway timeout"));
}, 8000);
const ws = new (require("ws"))(`${GATEWAY_URL}`);
let connected = false;
let reqId = 1;
ws.on("open", () => {});
ws.on("message", (data: Buffer) => {
try {
const msg = JSON.parse(data.toString());
// Auth challenge
if (msg.type === "event" && msg.event === "connect.challenge") {
ws.send(JSON.stringify({
type: "request",
id: String(reqId++),
method: "connect",
params: { token: GATEWAY_TOKEN },
}));
return;
}
// Connect response
if (msg.type === "response" && !connected) {
if (msg.ok) {
connected = true;
// Send actual request
ws.send(JSON.stringify({
type: "request",
id: String(reqId++),
method,
params,
}));
} else {
clearTimeout(timeout);
ws.close();
reject(new Error("Auth failed"));
}
return;
}
// Method response
if (msg.type === "response" && connected) {
clearTimeout(timeout);
ws.close();
if (msg.ok) {
resolve(msg.payload);
} else {
reject(new Error(msg.error || "Request failed"));
}
}
} catch {}
});
ws.on("error", (err: Error) => {
clearTimeout(timeout);
reject(err);
});
});
}
export async function GET() { export async function GET() {
try { try {
const [statusResult, costResult] = await Promise.allSettled([ const res = await fetch(BRIDGE_URL, {
gatewayRequest("usage.status", {}), signal: AbortSignal.timeout(5000),
gatewayRequest("usage.cost", {}), cache: "no-store",
]); });
const status = statusResult.status === "fulfilled" ? statusResult.value : null; if (!res.ok) throw new Error(`Bridge HTTP ${res.status}`);
const cost = costResult.status === "fulfilled" ? costResult.value : null; const data = await res.json();
// Извлечь данные Anthropic const usage = data.usage;
const anthropic = (status as any)?.providers?.find((p: any) => p.provider === "anthropic"); if (!usage) {
const windows = anthropic?.windows || []; return NextResponse.json({ ok: false, error: "No usage data yet" });
}
// Найти данные Anthropic
const anthropicProvider = usage.planLimits?.providers?.find(
(p: any) => p.provider === "anthropic"
);
const windows = anthropicProvider?.windows || [];
const window5h = windows.find((w: any) => w.label?.includes("5") || w.label === "5h"); const window5h = windows.find((w: any) => w.label?.includes("5") || w.label === "5h");
const windowWeek = windows.find((w: any) => w.label?.toLowerCase().includes("week") || w.label === "Week"); const windowWeek = windows.find((w: any) => w.label?.toLowerCase().includes("week"));
const windowSonnet = windows.find((w: any) => w.label?.toLowerCase().includes("sonnet")); const windowSonnet = windows.find((w: any) => w.label?.toLowerCase().includes("sonnet"));
const todayCost = (cost as any)?.daily?.[0]?.totalCost || 0; // Стоимость сегодня
const todayTokens = (cost as any)?.daily?.[0]?.totalTokens || 0; const todayEntry = usage.cost?.daily?.find((d: any) => {
const totalCost = (cost as any)?.totals?.totalCost || 0; const today = new Date().toISOString().split("T")[0];
return d.date === today;
});
return NextResponse.json({ return NextResponse.json({
ok: true, ok: true,
@@ -99,14 +40,15 @@ export async function GET() {
window5h: window5h || null, window5h: window5h || null,
windowWeek: windowWeek || null, windowWeek: windowWeek || null,
windowSonnet: windowSonnet || null, windowSonnet: windowSonnet || null,
error: anthropic?.error || null, error: anthropicProvider?.error || null,
plan: anthropicProvider?.plan || null,
}, },
cost: { cost: {
today: todayCost, today: todayEntry?.totalCost || 0,
todayTokens, todayTokens: todayEntry?.totalTokens || 0,
total: totalCost, total: usage.cost?.totals?.totalCost || 0,
}, },
raw: { status, cost }, updatedAt: usage.updatedAt,
}); });
} catch (e) { } catch (e) {
return NextResponse.json({ return NextResponse.json({