fix: claude-sub uses bridge HTTP instead of direct WS
All checks were successful
Build & Deploy Dashboard / deploy (push) Successful in 1m2s
All checks were successful
Build & Deploy Dashboard / deploy (push) Successful in 1m2s
This commit is contained in:
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user