Skip to content
Open
85 changes: 84 additions & 1 deletion packages/types/src/__tests__/provider-settings.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,87 @@
import { getApiProtocol } from "../provider-settings.js"
import { getApiProtocol, providerSettingsSchema } from "../provider-settings.js"

describe("Ollama Settings Schema", () => {
it("should accept valid ollamaRequestTimeout", () => {
const result = providerSettingsSchema.safeParse({
ollamaRequestTimeout: 3600000,
})
expect(result.success).toBe(true)
})

it("should reject ollamaRequestTimeout below minimum", () => {
const result = providerSettingsSchema.safeParse({
ollamaRequestTimeout: 500,
})
expect(result.success).toBe(false)
})

it("should reject ollamaRequestTimeout above maximum", () => {
const result = providerSettingsSchema.safeParse({
ollamaRequestTimeout: 8000000,
})
expect(result.success).toBe(false)
})

it("should accept valid ollamaModelDiscoveryTimeout", () => {
const result = providerSettingsSchema.safeParse({
ollamaModelDiscoveryTimeout: 10000,
})
expect(result.success).toBe(true)
})

it("should reject ollamaModelDiscoveryTimeout above maximum", () => {
const result = providerSettingsSchema.safeParse({
ollamaModelDiscoveryTimeout: 700000,
})
expect(result.success).toBe(false)
})

it("should accept valid ollamaMaxRetries", () => {
const result = providerSettingsSchema.safeParse({
ollamaMaxRetries: 3,
})
expect(result.success).toBe(true)
})

it("should reject ollamaMaxRetries above maximum", () => {
const result = providerSettingsSchema.safeParse({
ollamaMaxRetries: 15,
})
expect(result.success).toBe(false)
})

it("should accept valid ollamaRetryDelay", () => {
const result = providerSettingsSchema.safeParse({
ollamaRetryDelay: 2000,
})
expect(result.success).toBe(true)
})

it("should reject ollamaRetryDelay below minimum", () => {
const result = providerSettingsSchema.safeParse({
ollamaRetryDelay: 50,
})
expect(result.success).toBe(false)
})

it("should accept ollamaEnableLogging boolean", () => {
const result = providerSettingsSchema.safeParse({
ollamaEnableLogging: true,
})
expect(result.success).toBe(true)
})

it("should accept all optional fields together", () => {
const result = providerSettingsSchema.safeParse({
ollamaRequestTimeout: 3600000,
ollamaModelDiscoveryTimeout: 10000,
ollamaMaxRetries: 2,
ollamaRetryDelay: 1000,
ollamaEnableLogging: true,
})
expect(result.success).toBe(true)
})
})

describe("getApiProtocol", () => {
describe("Anthropic-style providers", () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ const ollamaSchema = baseProviderSettingsSchema.extend({
ollamaBaseUrl: z.string().optional(),
ollamaApiKey: z.string().optional(),
ollamaNumCtx: z.number().int().min(128).optional(),
ollamaRequestTimeout: z.number().int().min(1000).max(7200000).optional(),
ollamaModelDiscoveryTimeout: z.number().int().min(1000).max(600000).optional(),
ollamaMaxRetries: z.number().int().min(0).max(10).optional(),
ollamaRetryDelay: z.number().int().min(100).max(10000).optional(),
ollamaEnableLogging: z.boolean().optional(),
})

const vsCodeLmSchema = baseProviderSettingsSchema.extend({
Expand Down
16 changes: 16 additions & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export interface ExtensionMessage {
| "routerModels"
| "openAiModels"
| "ollamaModels"
| "ollamaConnectionTestResult"
| "ollamaModelsRefreshResult"
| "lmStudioModels"
| "vsCodeLmModels"
| "huggingFaceModels"
Expand Down Expand Up @@ -124,6 +126,18 @@ export interface ExtensionMessage {
routerModels?: RouterModels
openAiModels?: string[]
ollamaModels?: ModelRecord
ollamaModelsWithTools?: Array<{
name: string
contextWindow: number
size?: number
quantizationLevel?: string
family?: string
supportsImages: boolean
modelInfo: ModelRecord[string]
}>
modelsWithoutTools?: string[]
message?: string
durationMs?: number
lmStudioModels?: ModelRecord
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
huggingFaceModels?: Array<{
Expand Down Expand Up @@ -393,6 +407,8 @@ export interface WebviewMessage {
| "requestRouterModels"
| "requestOpenAiModels"
| "requestOllamaModels"
| "testOllamaConnection"
| "refreshOllamaModels"
| "requestLmStudioModels"
| "requestRooModels"
| "requestRooCreditBalance"
Expand Down
101 changes: 101 additions & 0 deletions src/api/providers/fetchers/__tests__/ollama-axios-config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import axios from "axios"
import { createOllamaAxiosInstance } from "../ollama"
import type { AxiosInstance, AxiosError } from "axios"

vi.mock("axios")

const mockAxiosInstance = {
interceptors: {
request: { use: vi.fn() },
response: { use: vi.fn() },
},
get: vi.fn(),
post: vi.fn(),
} as unknown as AxiosInstance

describe("createOllamaAxiosInstance", () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(axios.create).mockReturnValue(mockAxiosInstance)
})

it("should create instance with default configuration", () => {
const instance = createOllamaAxiosInstance()
expect(instance).toBeDefined()
expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "http://localhost:11434",
timeout: 3600000,
}),
)
})

it("should create instance with custom baseUrl", () => {
createOllamaAxiosInstance({ baseUrl: "http://custom:11434" })
expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "http://custom:11434",
}),
)
})

it("should include Authorization header when apiKey provided", () => {
createOllamaAxiosInstance({ apiKey: "test-key" })
expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
Authorization: "Bearer test-key",
},
}),
)
})

it("should not include Authorization header when apiKey not provided", () => {
createOllamaAxiosInstance()
expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
headers: {},
}),
)
})

it("should set up retry interceptor when retries > 0", () => {
createOllamaAxiosInstance({ retries: 2, retryDelay: 1000 })
expect(mockAxiosInstance.interceptors.response.use).toHaveBeenCalled()
})

it("should not set up retry interceptor when retries = 0", () => {
createOllamaAxiosInstance({ retries: 0 })
expect(mockAxiosInstance.interceptors.response.use).not.toHaveBeenCalled()
})

it("should set up logging interceptor when enableLogging is true", () => {
createOllamaAxiosInstance({ enableLogging: true })
expect(mockAxiosInstance.interceptors.request.use).toHaveBeenCalled()
expect(mockAxiosInstance.interceptors.response.use).toHaveBeenCalled()
})

it("should not set up logging interceptor when enableLogging is false", () => {
createOllamaAxiosInstance({ enableLogging: false })
expect(mockAxiosInstance.interceptors.request.use).not.toHaveBeenCalled()
})

it("should use custom timeout", () => {
createOllamaAxiosInstance({ timeout: 5000 })
expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
timeout: 5000,
}),
)
})

it("should set timeout error message", () => {
createOllamaAxiosInstance({ timeout: 10000 })
expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
timeoutErrorMessage: "Ollama request timed out after 10000ms",
}),
)
})
})
Loading