added a way to disable providers

This commit is contained in:
redshift
2026-03-28 18:17:39 +00:00
parent f1b388ef43
commit cb699a3882
2 changed files with 244 additions and 16 deletions
+97
View File
@@ -366,6 +366,103 @@ program
});
});
// Providers - list and manage providers
const providersCmd = program
.command("providers")
.description("List and manage providers");
providersCmd
.command("list")
.description("List all providers with their enabled/disabled status")
.action(async () => {
await ensureDaemonRunning();
const result = await callDaemon("/providers");
if (result.error) {
console.log(result.error);
process.exit(1);
}
const output = result.output as {
providers: Array<{ index: number; baseUrl: string; disabled: boolean }>;
disabledCount: number;
totalCount: number;
} | undefined;
if (!output?.providers) {
console.log("No providers found.");
return;
}
console.log(`Providers (${output.totalCount} total, ${output.disabledCount} disabled):\n`);
for (const provider of output.providers) {
const status = provider.disabled ? "DISABLED" : "enabled ";
console.log(` [${provider.index}] ${status} ${provider.baseUrl}`);
}
});
providersCmd
.command("disable <indices...>")
.description("Disable providers by their indices (e.g., routstrd providers disable 0 2 5)")
.action(async (indices: string[]) => {
await ensureDaemonRunning();
const indexNums = indices.map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n));
if (indexNums.length === 0) {
console.log("No valid indices provided.");
process.exit(1);
}
const result = await callDaemon("/providers/disable", {
method: "POST",
body: { indices: indexNums },
});
if (result.error) {
console.log(result.error);
process.exit(1);
}
const output = result.output as { message: string; disabled: string[] } | undefined;
if (output) {
console.log(output.message);
for (const url of output.disabled) {
console.log(` - ${url}`);
}
}
});
providersCmd
.command("enable <indices...>")
.description("Enable providers by their indices (e.g., routstrd providers enable 0 2 5)")
.action(async (indices: string[]) => {
await ensureDaemonRunning();
const indexNums = indices.map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n));
if (indexNums.length === 0) {
console.log("No valid indices provided.");
process.exit(1);
}
const result = await callDaemon("/providers/enable", {
method: "POST",
body: { indices: indexNums },
});
if (result.error) {
console.log(result.error);
process.exit(1);
}
const output = result.output as { message: string; enabled: string[] } | undefined;
if (output) {
console.log(output.message);
for (const url of output.enabled) {
console.log(` - ${url}`);
}
}
});
// Monitor - interactive TUI
program
.command("monitor")
+147 -16
View File
@@ -1,9 +1,6 @@
import { type IncomingMessage, type ServerResponse } from "http";
import { Readable } from "stream";
import {
routeRequests,
InsufficientBalanceError,
} from "@routstr/sdk";
import { routeRequests, InsufficientBalanceError } from "@routstr/sdk";
import type { UsageTrackingDriver } from "@routstr/sdk";
import { logger } from "../../utils/logger";
@@ -42,8 +39,6 @@ export function createDaemonRequestHandler(deps: {
usageTrackingDriver: UsageTrackingDriver;
}) {
return async function handler(req: IncomingMessage, res: ServerResponse) {
// Log all incoming headers with PipeLineHeaders tag
const host = req.headers.host || "localhost";
const url = new URL(req.url || "/", `http://${host}`);
@@ -187,11 +182,7 @@ export function createDaemonRequestHandler(deps: {
);
const spender = client.getCashuSpender();
const results = await spender.refundProviders(
refundBaseUrls,
mintUrl,
true,
);
const results = await spender.refundProviders(mintUrl);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
@@ -291,15 +282,156 @@ export function createDaemonRequestHandler(deps: {
return;
}
if (req.method === "GET" && url.pathname === "/providers") {
try {
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
const disabledProviders: string[] = state.disabledProviders || [];
const providers = baseUrlsList.map((baseUrl, index) => ({
index,
baseUrl,
disabled: disabledProviders.includes(baseUrl),
}));
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
output: {
providers,
disabledCount: disabledProviders.length,
totalCount: baseUrlsList.length,
},
}),
);
} catch (error) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: String(error) }));
}
return;
}
if (req.method === "POST" && url.pathname === "/providers/disable") {
try {
const bodyText = await readBody(req);
const body = bodyText ? JSON.parse(bodyText) : {};
const indices = body.indices as number[] | undefined;
if (!Array.isArray(indices)) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
error: "Missing or invalid 'indices' field (expected number[]).",
}),
);
return;
}
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
const disabledProviders: string[] = [
...(state.disabledProviders || []),
];
const toDisable: string[] = [];
for (const idx of indices) {
if (
typeof idx === "number" &&
idx >= 0 &&
idx < baseUrlsList.length
) {
const baseUrl = baseUrlsList[idx]!;
if (!disabledProviders.includes(baseUrl)) {
disabledProviders.push(baseUrl);
toDisable.push(baseUrl);
}
}
}
deps.store.getState().setDisabledProviders(disabledProviders);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
output: {
message: `Disabled ${toDisable.length} provider(s)`,
disabled: toDisable,
},
}),
);
} catch (error) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: String(error) }));
}
return;
}
if (req.method === "POST" && url.pathname === "/providers/enable") {
try {
const bodyText = await readBody(req);
const body = bodyText ? JSON.parse(bodyText) : {};
const indices = body.indices as number[] | undefined;
if (!Array.isArray(indices)) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
error: "Missing or invalid 'indices' field (expected number[]).",
}),
);
return;
}
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
const disabledProviders: string[] = [
...(state.disabledProviders || []),
];
const toEnable: string[] = [];
for (const idx of indices) {
if (
typeof idx === "number" &&
idx >= 0 &&
idx < baseUrlsList.length
) {
const baseUrl = baseUrlsList[idx]!;
const pos = disabledProviders.indexOf(baseUrl);
if (pos !== -1) {
disabledProviders.splice(pos, 1);
toEnable.push(baseUrl);
}
}
}
deps.store.getState().setDisabledProviders(disabledProviders);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
output: {
message: `Enabled ${toEnable.length} provider(s)`,
enabled: toEnable,
},
}),
);
} catch (error) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: String(error) }));
}
return;
}
if (req.method === "GET" && url.pathname === "/usage") {
try {
const usageDriver = deps.usageTrackingDriver;
const limit = parseLimit(url.searchParams.get("limit"));
const entries = await usageDriver.list({ limit });
const totalEntries = await usageDriver.count();
const totalSatsCost = (
await usageDriver.list()
).reduce((sum, entry) => sum + (entry.satsCost || 0), 0);
const totalSatsCost = (await usageDriver.list()).reduce(
(sum, entry) => sum + (entry.satsCost || 0),
0,
);
const recentSatsCost = entries.reduce(
(sum, entry) => sum + (entry.satsCost || 0),
0,
@@ -411,7 +543,7 @@ export function createDaemonRequestHandler(deps: {
deps.provider ||
undefined;
// Convert req.headers to Record<string, string> and log with PipeLineHeaders tag
// Convert req.headers to Record<string, string>
const incomingHeaders: Record<string, string> = {};
for (const [key, value] of Object.entries(req.headers)) {
if (typeof value === "string") {
@@ -420,7 +552,6 @@ export function createDaemonRequestHandler(deps: {
incomingHeaders[key] = value[0]!;
}
}
logger.log(`[PipeLineHeaders] Passing headers to routeRequests: ${JSON.stringify(incomingHeaders)}`);
try {
await deps.ensureProvidersBootstrapped();