diff --git a/packages/types/src/model.ts b/packages/types/src/model.ts index 6c7d0a4b4b..cacb53d2b2 100644 --- a/packages/types/src/model.ts +++ b/packages/types/src/model.ts @@ -86,6 +86,8 @@ export const modelInfoSchema = z.object({ // Capability flag to indicate whether the model supports temperature parameter supportsTemperature: z.boolean().optional(), defaultTemperature: z.number().optional(), + // When true, force-disable request timeouts for this model (providers will set timeout=0) + disableTimeout: z.boolean().optional(), requiredReasoningBudget: z.boolean().optional(), supportsReasoningEffort: z .union([z.boolean(), z.array(z.enum(["disable", "none", "minimal", "low", "medium", "high", "xhigh"]))]) diff --git a/src/api/providers/__tests__/lm-studio-timeout.spec.ts b/src/api/providers/__tests__/lm-studio-timeout.spec.ts index 659fcaaf67..a7abdb4679 100644 --- a/src/api/providers/__tests__/lm-studio-timeout.spec.ts +++ b/src/api/providers/__tests__/lm-studio-timeout.spec.ts @@ -88,4 +88,34 @@ describe("LmStudioHandler timeout configuration", () => { }), ) }) + + it("should force zero timeout when model info disables timeout", () => { + ;(getApiRequestTimeout as any).mockReturnValue(600000) + + const spy = vitest.spyOn(LmStudioHandler.prototype as any, "getModel").mockReturnValue({ + id: "llama2", + info: { + maxTokens: -1, + contextWindow: 128000, + supportsPromptCache: false, + supportsImages: true, + disableTimeout: true, + }, + }) + + const options: ApiHandlerOptions = { + apiModelId: "llama2", + lmStudioModelId: "llama2", + } + + new LmStudioHandler(options) + + expect(mockOpenAIConstructor).toHaveBeenCalledWith( + expect.objectContaining({ + timeout: 0, + }), + ) + + spy.mockRestore() + }) }) diff --git a/src/api/providers/__tests__/openai-timeout.spec.ts b/src/api/providers/__tests__/openai-timeout.spec.ts index 2a09fd94ff..2ef7df2ada 100644 --- a/src/api/providers/__tests__/openai-timeout.spec.ts +++ b/src/api/providers/__tests__/openai-timeout.spec.ts @@ -141,4 +141,28 @@ describe("OpenAiHandler timeout configuration", () => { }), ) }) + + it("should force zero timeout when model info disables timeout", () => { + ;(getApiRequestTimeout as any).mockReturnValue(600000) + + const options: ApiHandlerOptions = { + apiModelId: "gpt-4", + openAiModelId: "gpt-4", + openAiCustomModelInfo: { + maxTokens: -1, + contextWindow: 128000, + supportsPromptCache: false, + supportsImages: true, + disableTimeout: true, + } as any, + } + + new OpenAiHandler(options) + + expect(mockOpenAIConstructor).toHaveBeenCalledWith( + expect.objectContaining({ + timeout: 0, // Forced no timeout via model info + }), + ) + }) }) diff --git a/src/api/providers/lm-studio.ts b/src/api/providers/lm-studio.ts index 6c58a96ae1..1d048bf679 100644 --- a/src/api/providers/lm-studio.ts +++ b/src/api/providers/lm-studio.ts @@ -32,7 +32,7 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan this.client = new OpenAI({ baseURL: (this.options.lmStudioBaseUrl || "http://localhost:1234") + "/v1", apiKey: apiKey, - timeout: getApiRequestTimeout(), + timeout: this.getModel().info?.disableTimeout === true ? 0 : getApiRequestTimeout(), }) } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 2a2065edd6..586a86458c 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -49,7 +49,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(this.options.openAiHeaders || {}), } - const timeout = getApiRequestTimeout() + let timeout = getApiRequestTimeout() + try { + const modelInfo = this.getModel().info + if (modelInfo?.disableTimeout === true) { + timeout = 0 + } + } catch {} if (isAzureAiInference) { // Azure AI Inference Service (e.g., for DeepSeek) uses a different path structure