From 88dd937f575bd0d5963bf1ff2baeae46184e62f8 Mon Sep 17 00:00:00 2001 From: Paperclip Deployment Engineer Date: Fri, 24 Jul 2026 12:08:36 +0000 Subject: [PATCH] fix: prevent config override on restart and skip disabled providers in model fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem 1 — config overridden on restart: parseArgs() always returned port=8008 when --port was not passed on the CLI. index.ts then spread { ...config, port, provider } and saved, silently overwriting the user's persisted port with 8008 on every daemon restart. Fix: parseArgs now returns null when --port is absent. index.ts falls back to config.port (then 8008 as last resort), so the persisted config is preserved across restarts. Problem 2 — fetching models from disabled providers: The SDK's fetchModels() still makes HTTP requests to every provider in the list, even disabled ones — it only skips adding their models to the best-priced map. models.ts passed unfiltered provider lists to fetchModels in three places (bootstrap, getRoutstr21Models, refresh). Fix: Added filterDisabled() helper that reads disabledProviders from the store and strips them before fetchModels is called, preventing wasteful HTTP requests to providers the user has explicitly disabled. --- src/daemon/args.ts | 6 ++++-- src/daemon/index.ts | 2 +- src/daemon/models.ts | 42 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/daemon/args.ts b/src/daemon/args.ts index 659f35d..3b5c0aa 100644 --- a/src/daemon/args.ts +++ b/src/daemon/args.ts @@ -1,5 +1,5 @@ export function parseArgs(argv: string[]): { - port: number; + port: number | null; provider: string | null; } { const portFlagIndex = argv.findIndex((arg) => arg === "--port"); @@ -7,10 +7,12 @@ export function parseArgs(argv: string[]): { (arg) => arg === "--provider" || arg === "-p", ); + // Only use the CLI flag port if explicitly passed; otherwise fall back to + // the persisted config so a restart doesn't clobber the user's setting. const port = portFlagIndex !== -1 ? Number.parseInt(argv[portFlagIndex + 1] || "8008", 10) - : 8008; + : null; const providerValue = providerFlagIndex !== -1 ? argv[providerFlagIndex + 1] : undefined; const provider = providerValue ? providerValue.trim() : null; diff --git a/src/daemon/index.ts b/src/daemon/index.ts index c44e605..1cbe023 100644 --- a/src/daemon/index.ts +++ b/src/daemon/index.ts @@ -64,7 +64,7 @@ async function main(): Promise { const args = parseArgs(process.argv); const config = await loadDaemonConfig(); - const port = args.port; + const port = args.port ?? config.port ?? 8008; const provider = args.provider || config.provider; const requestResponseLogDir = process.env.ROUTSTRD_REQUEST_RESPONSE_LOG_DIR || diff --git a/src/daemon/models.ts b/src/daemon/models.ts index 702dd18..d9ec851 100644 --- a/src/daemon/models.ts +++ b/src/daemon/models.ts @@ -19,13 +19,47 @@ export type ModelWithProviders = ExposedModel & { export function createModelService(modelManager: ModelManager, store: SdkStore) { let providerBootstrapPromise: Promise | null = null; + /** + * Returns the set of disabled provider base URLs (normalized with trailing + * slash, matching the SDK's convention) so callers can skip them before + * passing a provider list to `fetchModels`. + */ + const getDisabledProviderSet = (): Set => { + const raw: string[] = store.getState().disabledProviders || []; + return new Set( + raw.map((url) => (url.endsWith("/") ? url : `${url}/`)), + ); + }; + + /** + * Filter a list of provider base URLs, removing any that are currently + * disabled in the store. This prevents wasteful HTTP requests to providers + * the user has explicitly disabled. + */ + const filterDisabled = (providers: string[]): string[] => { + const disabled = getDisabledProviderSet(); + if (disabled.size === 0) return providers; + const filtered = providers.filter( + (url) => { + const base = url.endsWith("/") ? url : `${url}/`; + return !disabled.has(base); + }, + ); + if (filtered.length < providers.length) { + logger.log( + `Skipped ${providers.length - filtered.length} disabled provider(s) before model fetch`, + ); + } + return filtered; + }; + const ensureProvidersBootstrapped = (): Promise => { if (!providerBootstrapPromise) { providerBootstrapPromise = (async () => { logger.log("Bootstrapping providers..."); const providers = await modelManager.bootstrapProviders(false); logger.log(`Bootstrapped ${providers.length} providers`); - await modelManager.fetchModels(providers); + await modelManager.fetchModels(filterDisabled(providers)); // Sync discovered providers into the store so `providers list` reflects // the same set that the model manager knows about. @@ -61,7 +95,7 @@ export function createModelService(modelManager: ModelManager, store: SdkStore) ).slice(0, 21); const baseUrls = modelManager.getBaseUrls(); const discoveredModels = await modelManager.fetchModels( - baseUrls, + filterDisabled(baseUrls), forceRefresh, ); const modelsById = new Map(discoveredModels.map((model) => [model.id, model])); @@ -139,8 +173,8 @@ export function createModelService(modelManager: ModelManager, store: SdkStore) const routstr21ModelIds = await modelManager.fetchRoutstr21Models(true); console.log(`Fetched ${routstr21ModelIds.length} routstr21 model IDs from Nostr`); - // Force-refresh models from all providers - const models = await modelManager.fetchModels(providers, true); + // Force-refresh models from all enabled providers + const models = await modelManager.fetchModels(filterDisabled(providers), true); console.log(`Fetched ${models.length} models from ${providers.length} providers`); // Sync review events from Nostr (kind 38425) and apply disabled status