diff --git a/src/daemon/models.test.ts b/src/daemon/models.test.ts new file mode 100644 index 0000000..18c2114 --- /dev/null +++ b/src/daemon/models.test.ts @@ -0,0 +1,81 @@ +import { describe, it, expect } from "bun:test"; +import type { SdkStore } from "@routstr/sdk"; +import { createModelService } from "./models"; + +/** + * `ensureProvidersBootstrapped` must mirror the review-disabled provider set + * (kind 38425) into the SdkStore. The SDK applies review disables to the + * discovery adapter during bootstrap, but `providers list` and per-model + * provider views read the store — if the store is not kept in sync, a fresh + * install reports "0 disabled" while routing silently excludes them. + */ +describe("createModelService.ensureProvidersBootstrapped", () => { + function makeStore(initialBaseUrls: string[] = []) { + const disabledCalls: string[][] = []; + const state: Record = { + baseUrlsList: initialBaseUrls, + setBaseUrlsList: (urls: string[]) => { + state.baseUrlsList = urls; + }, + disabledProviders: [], + setDisabledProviders: (urls: string[]) => { + state.disabledProviders = urls; + disabledCalls.push(urls); + }, + manuallyDisabledProviders: [], + manuallyEnabledProviders: [], + }; + + return { + store: { getState: () => state } as unknown as SdkStore, + state, + disabledCalls, + }; + } + + function makeModelManager( + providers: string[], + reviewedDisabled: string[] | null, + ) { + return { + bootstrapProviders: async () => providers, + syncReviewedProvidersFromNostr: async () => reviewedDisabled, + } as never; + } + + it("mirrors review-disabled providers into the store after bootstrap", async () => { + const { store, state, disabledCalls } = makeStore(); + const modelManager = makeModelManager( + ["https://ok.example/", "https://bad.example/"], + ["https://bad.example/"], + ); + const service = createModelService( + modelManager as never, + {} as never, + store, + ); + + await service.ensureProvidersBootstrapped(); + + expect(disabledCalls).toEqual([["https://bad.example/"]]); + expect(state.disabledProviders).toEqual(["https://bad.example/"]); + }); + + it("leaves the store unchanged when review sync reports no change (null)", async () => { + const { store, state, disabledCalls } = makeStore(["https://ok.example/"]); + const modelManager = makeModelManager( + ["https://ok.example/", "https://new.example/"], + null, + ); + const service = createModelService( + modelManager as never, + {} as never, + store, + ); + + await service.ensureProvidersBootstrapped(); + + expect(disabledCalls).toEqual([]); + expect(state.disabledProviders).toEqual([]); + }); +}); diff --git a/src/daemon/models.ts b/src/daemon/models.ts index 03bd9bf..876cefb 100644 --- a/src/daemon/models.ts +++ b/src/daemon/models.ts @@ -89,7 +89,11 @@ export function createModelService( // Sync discovered providers into the store so `providers list` reflects // the same set that the model manager knows about. - const { baseUrlsList, setBaseUrlsList } = store.getState(); + const { + baseUrlsList, + setBaseUrlsList, + setDisabledProviders, + } = store.getState(); const existing = new Set(baseUrlsList); const merged = [ ...baseUrlsList, @@ -102,6 +106,18 @@ export function createModelService( ); } + // Mirror the review-disabled set (kind 38425) into the store. The SDK + // applies review disables to the discovery adapter during bootstrap, + // but `providers list` and the per-model provider views read the store, + // so without this a fresh install reports "0 disabled" while routing + // silently excludes the review-disabled providers. + const reviewedDisabled = await modelManager.syncReviewedProvidersFromNostr( + providers, + ); + if (reviewedDisabled !== null) { + setDisabledProviders(reviewedDisabled); + } + logger.log("Provider bootstrap complete."); })().catch((error) => { providerBootstrapPromise = null;