feat(providers): add --refresh flag to providers list

routstrd providers list --refresh now:
- Re-fetches Nostr kind 38421 provider discovery events
- Re-fetches Nostr kind 38423 routstr21 model list
- Re-fetches Nostr kind 38425 review events (applies LGTM-based disable)
- Fetches models from all discovered providers
- Syncs fresh provider list and disabled status into the store

Usage: routstrd providers list --refresh
This commit is contained in:
redshift
2026-06-07 11:13:39 +08:00
parent 27b13c30b0
commit 4a66af87da
4 changed files with 75 additions and 3 deletions
+8 -2
View File
@@ -743,10 +743,16 @@ const providersCmd = program
providersCmd
.command("list")
.description("List all providers with their enabled/disabled status")
.action(async () => {
.option("--refresh", "Force re-fetch all Nostr events and refresh models from all enabled providers", false)
.action(async (options: { refresh: boolean }) => {
await ensureDaemonRunning();
const result = await callDaemon("/providers");
const query = options.refresh ? "/providers?refresh=true" : "/providers";
if (options.refresh) {
console.log("Force-refreshing providers from Nostr and fetching models...\n");
}
const result = await callDaemon(query);
if (result.error) {
console.log(result.error);
process.exit(1);
+10
View File
@@ -41,6 +41,7 @@ type DaemonDeps = {
ensureProvidersBootstrapped: () => Promise<void>;
getRoutstr21Models: (forceRefresh?: boolean) => Promise<any[]>;
getModelProviders: (modelId: string) => Promise<any>;
refreshProvidersAndModels: () => Promise<void>;
mode?: ClientMode;
/** Nostr hex pubkey for routstr review/model events (kind 38425/38423). */
routstrPubkey?: string;
@@ -294,6 +295,7 @@ export function createDaemonRequestHandler(deps: {
ensureProvidersBootstrapped: () => Promise<void>;
getRoutstr21Models: (forceRefresh?: boolean) => Promise<any[]>;
getModelProviders: (modelId: string) => Promise<any>;
refreshProvidersAndModels: () => Promise<void>;
mode?: "xcashu" | "apikeys";
/** Nostr hex pubkey for routstr review/model events (kind 38425/38423). */
routstrPubkey?: string;
@@ -1079,6 +1081,14 @@ export function createDaemonRequestHandler(deps: {
if (req.method === "GET" && url.pathname === "/providers") {
try {
const forceRefresh =
url.searchParams.get("refresh")?.toLowerCase() === "true";
if (forceRefresh) {
logger.log("Force-refreshing providers from Nostr and fetching models...");
await deps.refreshProvidersAndModels();
}
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
const disabledProviders: string[] = state.disabledProviders || [];
+2 -1
View File
@@ -70,7 +70,7 @@ async function main(): Promise<void> {
});
// Create shared ProviderManager for consistent failure tracking across all requests
const providerManager = new ProviderManager(providerRegistry, store, daemonSdkLogger);
const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders } =
const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders, refreshProvidersAndModels } =
createModelService(modelManager, store);
const walletClient = createCocodClient({ cocodPath: config.cocodPath });
@@ -123,6 +123,7 @@ async function main(): Promise<void> {
ensureProvidersBootstrapped,
getRoutstr21Models,
getModelProviders,
refreshProvidersAndModels,
mode: config.mode || "apikeys",
routstrPubkey: config.routstrPubkey,
usageTrackingDriver,
+55
View File
@@ -120,9 +120,64 @@ export function createModelService(modelManager: ModelManager, store: SdkStore)
};
};
/**
* Force-refresh everything: re-fetch Nostr provider discovery events,
* routstr21 model list, Nostr review events, and models from all enabled
* providers. Syncs the discovered provider list into the store.
*/
const refreshProvidersAndModels = async (): Promise<void> => {
// Reset the bootstrap promise so we don't reuse cached results
providerBootstrapPromise = null;
logger.log("Force-refreshing providers from Nostr...");
// Force-refresh provider discovery from Nostr (kind 38421)
const providers = await modelManager.bootstrapProviders(false, true);
logger.log(`Discovered ${providers.length} providers from Nostr`);
// Force-refresh routstr21 models from Nostr (kind 38423)
const routstr21ModelIds = await modelManager.fetchRoutstr21Models(true);
logger.log(`Fetched ${routstr21ModelIds.length} routstr21 model IDs from Nostr`);
// Force-refresh models from all providers
const models = await modelManager.fetchModels(providers, true);
logger.log(`Fetched ${models.length} models from ${providers.length} providers`);
// Sync review events from Nostr (kind 38425) and apply disabled status
const reviewedDisabled = await modelManager.syncReviewedProvidersFromNostr(
providers,
undefined,
true,
);
if (reviewedDisabled.length > 0) {
logger.log(
`Review sync disabled ${reviewedDisabled.length} provider(s): ${reviewedDisabled.join(", ")}`,
);
}
// Sync discovered providers into the store
const { baseUrlsList, setBaseUrlsList, disabledProviders, setDisabledProviders } =
store.getState() as any;
// Replace baseUrlsList with the fresh provider list
setBaseUrlsList(providers);
// Merge review-disabled providers into the store's disabled list
const existingDisabled = new Set(disabledProviders || []);
for (const url of reviewedDisabled) {
existingDisabled.add(url);
}
setDisabledProviders([...existingDisabled]);
logger.log(
`Provider refresh complete: ${providers.length} total, ${existingDisabled.size} disabled`,
);
};
return {
ensureProvidersBootstrapped,
getRoutstr21Models,
getModelProviders,
refreshProvidersAndModels,
};
}