diff --git a/src/api/providers/__tests__/native-ollama.spec.ts b/src/api/providers/__tests__/native-ollama.spec.ts index f3f312d296..5c3ec9cd78 100644 --- a/src/api/providers/__tests__/native-ollama.spec.ts +++ b/src/api/providers/__tests__/native-ollama.spec.ts @@ -910,6 +910,50 @@ describe("NativeOllamaHandler", () => { }) }) + describe("ensureModelFetched", () => { + it("makes the detected context window available before a request", async () => { + mockGetOllamaModels.mockResolvedValueOnce({ + llama2: { + contextWindow: 1_000_000, + maxTokens: 4096, + supportsImages: false, + supportsPromptCache: true, + }, + }) + + expect(handler.getModel().info.contextWindow).toBe(128_000) + + await handler.ensureModelFetched() + + expect(handler.getModel().info.contextWindow).toBe(1_000_000) + expect(mockGetOllamaModels).toHaveBeenCalledTimes(1) + }) + + it("skips subsequent fetches after models are populated", async () => { + await handler.ensureModelFetched() + mockGetOllamaModels.mockClear() + + await handler.fetchModel() + + expect(mockGetOllamaModels).not.toHaveBeenCalled() + }) + + it("deduplicates concurrent metadata fetches", async () => { + await Promise.all([handler.ensureModelFetched(), handler.ensureModelFetched()]) + + expect(mockGetOllamaModels).toHaveBeenCalledTimes(1) + }) + + it("allows a later fetch after a rejected request", async () => { + mockGetOllamaModels.mockRejectedValueOnce(new Error("network down")) + + await expect(handler.ensureModelFetched()).rejects.toThrow("network down") + await handler.ensureModelFetched() + + expect(mockGetOllamaModels).toHaveBeenCalledTimes(2) + }) + }) + describe("tool calling", () => { it("should include tools when tools are provided", async () => { // Model metadata should not gate tool inclusion; metadata.tools controls it. diff --git a/src/api/providers/native-ollama.ts b/src/api/providers/native-ollama.ts index eb380d1eb2..e2ec634cbc 100644 --- a/src/api/providers/native-ollama.ts +++ b/src/api/providers/native-ollama.ts @@ -227,6 +227,7 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio protected options: ApiHandlerOptions private client: Ollama | undefined protected models: Record = {} + private modelFetchPromise?: Promise<{ id: string; info: ModelInfo }> constructor(options: ApiHandlerOptions) { super() @@ -538,8 +539,26 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio } async fetchModel() { - this.models = await getOllamaModels(this.options.ollamaBaseUrl, this.options.ollamaApiKey) - return this.getModel() + if (Object.keys(this.models).length > 0) { + return this.getModel() + } + + if (!this.modelFetchPromise) { + this.modelFetchPromise = getOllamaModels(this.options.ollamaBaseUrl, this.options.ollamaApiKey) + .then((models) => { + this.models = models + return this.getModel() + }) + .finally(() => { + this.modelFetchPromise = undefined + }) + } + + return this.modelFetchPromise + } + + async ensureModelFetched(): Promise { + await this.fetchModel() } override getModel(): { id: string; info: ModelInfo } {