Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions docs/src/content/docs/docs/AIAssistant.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,6 @@ window, output limit, and sampling support where the source reports them.
If model import fails, you can still add models manually. Use the provider's
exact model id and the model's context-window token count.

QuickAdd saves the models.dev directory (about 5 MB) as `models-dev-cache.json`
in its plugin folder. On later launches it asks models.dev whether the directory
changed and downloads it again only when it did. If models.dev is unreachable,
QuickAdd uses the saved copy. Deleting the file is safe; QuickAdd downloads it
again the next time it needs it.

### Keep model lists current: Auto-sync {#auto-sync}

Each provider has an **Auto-sync models** toggle. While it is on, QuickAdd
Expand Down
238 changes: 0 additions & 238 deletions src/ai/modelsDirectory.cache.test.ts

This file was deleted.

79 changes: 79 additions & 0 deletions src/ai/modelsDirectory.fetch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { beforeEach, describe, expect, it, vi } from "vitest";

const storeState = vi.hoisted(() => ({ disableOnlineFeatures: false }));
const requestUrlMock = vi.hoisted(() => vi.fn());

vi.mock("obsidian", async (importOriginal) => ({
...(await importOriginal<typeof import("obsidian")>()),

Check warning on line 7 in src/ai/modelsDirectory.fetch.test.ts

View workflow job for this annotation

GitHub Actions / Build + Lint

`import()` type annotations are forbidden
requestUrl: requestUrlMock,
}));

vi.mock("src/settingsStore", () => ({
settingsStore: { getState: () => storeState },
}));

const VALID = {
openai: {
id: "openai",
name: "OpenAI",
models: { "gpt-a": { id: "gpt-a" } },
},
};

/** Fresh module = empty memory cache (as after an Obsidian restart). */
async function loadFetch() {
vi.resetModules();
const mod = await import("./modelsDirectory");
return mod.fetchModelsDevDirectory;
}

describe("fetchModelsDevDirectory validation", () => {
beforeEach(() => {
requestUrlMock.mockReset();
storeState.disableOnlineFeatures = false;
});

it("caches a valid directory for the session", async () => {
const fetch = await loadFetch();
requestUrlMock.mockResolvedValueOnce({ status: 200, json: VALID });

expect(await fetch()).toEqual(VALID);
expect(await fetch()).toEqual(VALID);
expect(requestUrlMock).toHaveBeenCalledTimes(1);
});

it.each([
["an array body", []],
["an empty object", {}],
["a provider without models", { openai: { id: "openai", name: "OpenAI" } }],
])("rejects %s without poisoning the memory cache", async (_label, body) => {
const fetch = await loadFetch();
requestUrlMock
.mockResolvedValueOnce({ status: 200, json: body })
.mockResolvedValueOnce({ status: 200, json: VALID });

await expect(fetch()).rejects.toThrow(/unexpected response/);
expect(await fetch()).toEqual(VALID);
expect(requestUrlMock).toHaveBeenCalledTimes(2);
});

it("drops only the malformed providers and keeps the rest", async () => {
const fetch = await loadFetch();
const body = {
...VALID,
broken: { id: "broken", name: "Broken" },
nullModels: { id: "nullModels", name: "Null", models: null },
};
requestUrlMock.mockResolvedValueOnce({ status: 200, json: body });

expect(await fetch()).toEqual(VALID);
});

it("keeps the online-features gate", async () => {
const fetch = await loadFetch();
storeState.disableOnlineFeatures = true;

await expect(fetch()).rejects.toThrow(/Online features are turned off/);
expect(requestUrlMock).not.toHaveBeenCalled();
});
});
Loading
Loading