diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 65e3f9b5b6..205a039a02 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -123,11 +123,13 @@ const openAiSchema = baseProviderSettingsSchema.extend({ openAiStreamingEnabled: z.boolean().optional(), openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration. openAiHeaders: z.record(z.string(), z.string()).optional(), + openAiApiTimeout: z.number().optional(), }) const ollamaSchema = baseProviderSettingsSchema.extend({ ollamaModelId: z.string().optional(), ollamaBaseUrl: z.string().optional(), + ollamaApiTimeout: z.number().optional(), }) const vsCodeLmSchema = baseProviderSettingsSchema.extend({ @@ -146,6 +148,7 @@ const lmStudioSchema = baseProviderSettingsSchema.extend({ lmStudioBaseUrl: z.string().optional(), lmStudioDraftModelId: z.string().optional(), lmStudioSpeculativeDecodingEnabled: z.boolean().optional(), + lmStudioApiTimeout: z.number().optional(), }) const geminiSchema = apiModelIdProviderModelSchema.extend({ @@ -265,9 +268,12 @@ export const MODEL_ID_KEYS: Partial[] = [ "glamaModelId", "openRouterModelId", "openAiModelId", + "openAiApiTimeout", "ollamaModelId", + "ollamaApiTimeout", "lmStudioModelId", "lmStudioDraftModelId", + "lmStudioApiTimeout", "unboundModelId", "requestyModelId", "litellmModelId", diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index fc809819e8..6b1ac31ff0 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -101,6 +101,7 @@ describe("OpenAiHandler", () => { expect(vi.mocked(OpenAI)).toHaveBeenCalledWith({ baseURL: expect.any(String), apiKey: expect.any(String), + timeout: expect.any(Number), defaultHeaders: { "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", "X-Title": "Roo Code", diff --git a/src/api/providers/lm-studio.ts b/src/api/providers/lm-studio.ts index f032e2d560..f3cd0c0719 100644 --- a/src/api/providers/lm-studio.ts +++ b/src/api/providers/lm-studio.ts @@ -21,9 +21,11 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan constructor(options: ApiHandlerOptions) { super() this.options = options + const timeoutMs = (this.options.lmStudioApiTimeout ?? 10) * 60 * 1000 this.client = new OpenAI({ baseURL: (this.options.lmStudioBaseUrl || "http://localhost:1234") + "/v1", apiKey: "noop", + timeout: timeoutMs, }) } diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index 7f384e9a98..85843ecd2e 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -24,9 +24,11 @@ export class OllamaHandler extends BaseProvider implements SingleCompletionHandl constructor(options: ApiHandlerOptions) { super() this.options = options + const timeoutMs = (this.options.ollamaApiTimeout ?? 10) * 60 * 1000 this.client = new OpenAI({ baseURL: (this.options.ollamaBaseUrl || "http://localhost:11434") + "/v1", apiKey: "ollama", + timeout: timeoutMs, }) } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index b4f256f43a..81896ba2d4 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -40,6 +40,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const isAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl) const urlHost = this._getUrlHost(this.options.openAiBaseUrl) const isAzureOpenAi = urlHost === "azure.com" || urlHost.endsWith(".azure.com") || options.openAiUseAzure + const timeoutMs = (this.options.openAiApiTimeout ?? 10) * 60 * 1000 // Timeout is provided in minutes, convert to ms const headers = { ...DEFAULT_HEADERS, @@ -52,6 +53,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl baseURL, apiKey, defaultHeaders: headers, + timeout: timeoutMs, defaultQuery: { "api-version": this.options.azureApiVersion || "2024-05-01-preview" }, }) } else if (isAzureOpenAi) { @@ -62,12 +64,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl apiKey, apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion, defaultHeaders: headers, + timeout: timeoutMs, }) } else { this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: headers, + timeout: timeoutMs, }) } } diff --git a/webview-ui/src/components/settings/providers/LMStudio.tsx b/webview-ui/src/components/settings/providers/LMStudio.tsx index 9177457039..cde628425c 100644 --- a/webview-ui/src/components/settings/providers/LMStudio.tsx +++ b/webview-ui/src/components/settings/providers/LMStudio.tsx @@ -64,6 +64,28 @@ export const LMStudio = ({ apiConfiguration, setApiConfigurationField }: LMStudi className="w-full"> + { + const value = parseInt((e.target as HTMLInputElement).value) + return isNaN(value) ? undefined : value + })} + type="text" + inputMode="numeric" + placeholder="10" + style={{ + borderColor: (() => { + const value = apiConfiguration?.lmStudioApiTimeout + if (!value) return "var(--vscode-input-border)" + return value > 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)" + })(), + }} + className="w-full mt-4"> + + +
+ {t("settings:providers.openAiApiTimeoutDescription")} +
{lmStudioModels.length > 0 && ( + { + const value = parseInt((e.target as HTMLInputElement).value) + return isNaN(value) ? undefined : value + })} + type="text" + inputMode="numeric" + placeholder="10" + style={{ + borderColor: (() => { + const value = apiConfiguration?.ollamaApiTimeout + if (!value) return "var(--vscode-input-border)" + return value > 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)" + })(), + }} + className="w-full mt-4"> + + +
+ {t("settings:providers.openAiApiTimeoutDescription")} +
{ollamaModels.length > 0 && ( + { + const value = parseInt((e.target as HTMLInputElement).value) + return isNaN(value) ? undefined : value + })} + type="text" + inputMode="numeric" + placeholder="10" + style={{ + borderColor: (() => { + const value = apiConfiguration?.openAiApiTimeout + if (!value) return "var(--vscode-input-border)" + return value > 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)" + })(), + }} + className="w-full mt-4"> + + +
+ {t("settings:providers.openAiApiTimeoutDescription")} +
) } diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index c88005ea61..170b315285 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -184,6 +184,8 @@ "apiKey": "Clau API", "openAiBaseUrl": "URL base", "getOpenAiApiKey": "Obtenir clau API d'OpenAI", + "openAiApiTimeout": "Temps d'espera (minuts)", + "openAiApiTimeoutDescription": "Temps d'espera per a sol·licituds d'API al proveïdor (mín. 5 min). Si no hi ha resposta en aquest període, la sol·licitud es reintenta. Augmenta el valor per a models més lents.", "mistralApiKey": "Clau API de Mistral", "getMistralApiKey": "Obtenir clau API de Mistral / Codestral", "codestralBaseUrl": "URL base de Codestral (opcional)", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 27a7486436..6b09e2c1c8 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -184,6 +184,8 @@ "apiKey": "API-Schlüssel", "openAiBaseUrl": "Basis-URL", "getOpenAiApiKey": "OpenAI API-Schlüssel erhalten", + "openAiApiTimeout": "Zeitlimit (Minuten)", + "openAiApiTimeoutDescription": "Zeitlimit für API-Anfragen an den Anbieter (mind. 5 Min.). Wenn innerhalb dieses Zeitraums keine Antwort erfolgt, wird die Anfrage wiederholt. Erhöhen Sie den Wert für langsamere Modelle.", "mistralApiKey": "Mistral API-Schlüssel", "getMistralApiKey": "Mistral / Codestral API-Schlüssel erhalten", "codestralBaseUrl": "Codestral Basis-URL (Optional)", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index b8e51afc50..20bd23abe3 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -183,6 +183,8 @@ "openAiApiKey": "OpenAI API Key", "apiKey": "API Key", "openAiBaseUrl": "Base URL", + "openAiApiTimeout": "Timeout (minutes)", + "openAiApiTimeoutDescription": "Timeout for API requests to Provider (min 5 min). if no response within this period, the request is retried. Increase for slower models.", "getOpenAiApiKey": "Get OpenAI API Key", "mistralApiKey": "Mistral API Key", "getMistralApiKey": "Get Mistral / Codestral API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index db8b4736eb..31dc11a9fd 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -183,6 +183,8 @@ "openAiApiKey": "Clave API de OpenAI", "apiKey": "Clave API", "openAiBaseUrl": "URL base", + "openAiApiTimeout": "Tiempo de espera (minutos)", + "openAiApiTimeoutDescription": "Tiempo de espera para solicitudes de API al proveedor (mín. 5 min). Si no hay respuesta en este período, la solicitud se reintentará. Aumente este valor para modelos más lentos.", "getOpenAiApiKey": "Obtener clave API de OpenAI", "mistralApiKey": "Clave API de Mistral", "getMistralApiKey": "Obtener clave API de Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 0bf837accb..5154967619 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -183,6 +183,8 @@ "openAiApiKey": "Clé API OpenAI", "apiKey": "Clé API", "openAiBaseUrl": "URL de base", + "openAiApiTimeout": "Délai d'attente (minutes)", + "openAiApiTimeoutDescription": "Délai d'attente pour les requêtes API au fournisseur (min. 5 min). Si aucune réponse dans ce délai, la requête sera réessayée. Augmentez cette valeur pour les modèles plus lents.", "getOpenAiApiKey": "Obtenir la clé API OpenAI", "mistralApiKey": "Clé API Mistral", "getMistralApiKey": "Obtenir la clé API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index fec1b27007..5dc7cd46af 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -183,6 +183,8 @@ "openAiApiKey": "OpenAI API कुंजी", "apiKey": "API कुंजी", "openAiBaseUrl": "बेस URL", + "openAiApiTimeout": "Timeout (मिनट)", + "openAiApiTimeoutDescription": "प्रदाता को API अनुरोधों के लिए समय सीमा (न्यूनतम 5 मिनट)। यदि इस अवधि के भीतर कोई प्रतिक्रिया नहीं मिलती है, तो अनुरोध को फिर से भेजा जाता है। धीमे मॉडल के लिए समय सीमा बढ़ाएं।", "getOpenAiApiKey": "OpenAI API कुंजी प्राप्त करें", "mistralApiKey": "Mistral API कुंजी", "getMistralApiKey": "Mistral / Codestral API कुंजी प्राप्त करें", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 6d6a8e93b1..fbccc1b2fc 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -187,6 +187,8 @@ "openAiApiKey": "OpenAI API Key", "apiKey": "API Key", "openAiBaseUrl": "Base URL", + "openAiApiTimeout": "Batas waktu (menit)", + "openAiApiTimeoutDescription": "Batas waktu untuk permintaan API ke Provider (minimal 5 menit). Jika tidak ada respons dalam periode ini, permintaan akan dicoba lagi. Tingkatkan untuk model yang lebih lambat.", "getOpenAiApiKey": "Dapatkan OpenAI API Key", "mistralApiKey": "Mistral API Key", "getMistralApiKey": "Dapatkan Mistral / Codestral API Key", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index fcb389a4a7..823c88f56c 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -183,6 +183,8 @@ "openAiApiKey": "Chiave API OpenAI", "apiKey": "Chiave API", "openAiBaseUrl": "URL base", + "openAiApiTimeout": "Timeout (minuti)", + "openAiApiTimeoutDescription": "Timeout per le richieste API al provider (min. 5 min). Se non c'è risposta entro questo periodo, la richiesta viene ripetuta. Aumentare per modelli più lenti.", "getOpenAiApiKey": "Ottieni chiave API OpenAI", "mistralApiKey": "Chiave API Mistral", "getMistralApiKey": "Ottieni chiave API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index eabd751308..ba37265db7 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -184,6 +184,8 @@ "apiKey": "APIキー", "openAiBaseUrl": "ベースURL", "getOpenAiApiKey": "OpenAI APIキーを取得", + "openAiApiTimeout": "タイムアウト(分)", + "openAiApiTimeoutDescription": "プロバイダーへのAPIリクエストのタイムアウト(最小5分)。この期間内に応答がない場合、リクエストが再試行されます。遅いモデルの場合は値を増やしてください。", "mistralApiKey": "Mistral APIキー", "getMistralApiKey": "Mistral / Codestral APIキーを取得", "codestralBaseUrl": "Codestral ベースURL(オプション)", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 68ca2a963c..fca2a08630 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -184,6 +184,8 @@ "openAiApiKey": "OpenAI API 키", "openAiBaseUrl": "기본 URL", "getOpenAiApiKey": "OpenAI API 키 받기", + "openAiApiTimeout": "타임아웃(분)", + "openAiApiTimeoutDescription": "제공자에 대한 API 요청의 타임아웃(최소 5분). 이 기간 내에 응답이 없으면 요청이 재시도됩니다. 느린 모델의 경우 값을 늘리세요.", "mistralApiKey": "Mistral API 키", "getMistralApiKey": "Mistral / Codestral API 키 받기", "codestralBaseUrl": "Codestral 기본 URL (선택사항)", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 996c0c673c..e8fdfeb240 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -183,6 +183,8 @@ "apiKey": "API-sleutel", "openAiApiKey": "OpenAI API-sleutel", "openAiBaseUrl": "Basis-URL", + "openAiApiTimeout": "Timeout (minuten)", + "openAiApiTimeoutDescription": "Timeout voor API-verzoeken aan de provider (min. 5 min). Als er binnen deze periode geen antwoord is, wordt het verzoek opnieuw geprobeerd. Verhoog deze waarde voor tragere modellen.", "getOpenAiApiKey": "OpenAI API-sleutel ophalen", "mistralApiKey": "Mistral API-sleutel", "getMistralApiKey": "Mistral / Codestral API-sleutel ophalen", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index cf4421e00e..8e671472f2 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -184,6 +184,8 @@ "openAiApiKey": "Klucz API OpenAI", "openAiBaseUrl": "URL bazowy", "getOpenAiApiKey": "Uzyskaj klucz API OpenAI", + "openAiApiTimeout": "Limit czasu (minuty)", + "openAiApiTimeoutDescription": "Limit czasu dla zapytań API do dostawcy (min. 5 min). Jeśli w tym czasie nie ma odpowiedzi, zapytanie jest ponawiane. Zwiększ dla wolniejszych modeli.", "mistralApiKey": "Klucz API Mistral", "getMistralApiKey": "Uzyskaj klucz API Mistral / Codestral", "codestralBaseUrl": "URL bazowy Codestral (opcjonalnie)", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 229419dd23..9a6582821e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -183,6 +183,8 @@ "apiKey": "Chave de API", "openAiApiKey": "Chave de API OpenAI", "openAiBaseUrl": "URL Base", + "openAiApiTimeout": "Tempo limite (minutos)", + "openAiApiTimeoutDescription": "Tempo limite para solicitações de API ao provedor (mín. 5 min). Se não houver resposta neste período, a solicitação será repetida. Aumente este valor para modelos mais lentos.", "getOpenAiApiKey": "Obter chave de API OpenAI", "mistralApiKey": "Chave de API Mistral", "getMistralApiKey": "Obter chave de API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index dcce5e5b1a..bfe57ee7b3 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -183,6 +183,8 @@ "apiKey": "API-ключ", "openAiApiKey": "OpenAI API-ключ", "openAiBaseUrl": "Базовый URL", + "openAiApiTimeout": "Таймаут (минуты)", + "openAiApiTimeoutDescription": "Таймаут для запросов к провайдеру API (минимум 5 мин). Если нет ответа за это время, запрос будет повторён. Увеличьте значение для медленных моделей.", "getOpenAiApiKey": "Получить OpenAI API-ключ", "mistralApiKey": "Mistral API-ключ", "getMistralApiKey": "Получить Mistral / Codestral API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index f8f53ae21c..2113b36927 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -183,6 +183,8 @@ "openAiApiKey": "OpenAI API Anahtarı", "apiKey": "API Anahtarı", "openAiBaseUrl": "Temel URL", + "openAiApiTimeout": "Zaman aşımı (dakika)", + "openAiApiTimeoutDescription": "Sağlayıcıya yapılan API istekleri için zaman aşımı (min. 5 dk). Bu süre içinde yanıt yoksa, istek yeniden denenir. Daha yavaş modeller için bu değeri artırın.", "getOpenAiApiKey": "OpenAI API Anahtarı Al", "mistralApiKey": "Mistral API Anahtarı", "getMistralApiKey": "Mistral / Codestral API Anahtarı Al", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index edb2b386b2..4ce4e631a7 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -183,6 +183,8 @@ "openAiApiKey": "Khóa API OpenAI", "apiKey": "Khóa API", "openAiBaseUrl": "URL cơ sở", + "openAiApiTimeout": "Thời gian chờ (phút)", + "openAiApiTimeoutDescription": "Thời gian chờ cho các yêu cầu API đến nhà cung cấp (tối thiểu 5 phút). Nếu không có phản hồi trong khoảng thời gian này, yêu cầu sẽ được thử lại. Tăng giá trị này cho các mô hình chậm hơn.", "getOpenAiApiKey": "Lấy khóa API OpenAI", "mistralApiKey": "Khóa API Mistral", "getMistralApiKey": "Lấy khóa API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 51ae2269e4..f400948a13 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -184,6 +184,8 @@ "apiKey": "API 密钥", "openAiBaseUrl": "OpenAI 基础 URL", "getOpenAiApiKey": "获取 OpenAI API 密钥", + "openAiApiTimeout": "超时时间(分钟)", + "openAiApiTimeoutDescription": "对提供者的 API 请求超时时间(最少 5 分钟)。若在此期间无响应,将重试请求。对于较慢的模型请增加此值。", "mistralApiKey": "Mistral API 密钥", "getMistralApiKey": "获取 Mistral / Codestral API 密钥", "codestralBaseUrl": "Codestral 基础 URL(可选)", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 07544879cd..7ca26d955f 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -184,6 +184,8 @@ "apiKey": "API 金鑰", "openAiBaseUrl": "基礎 URL", "getOpenAiApiKey": "取得 OpenAI API 金鑰", + "openAiApiTimeout": "逾時(分鐘)", + "openAiApiTimeoutDescription": "對提供者的 API 請求逾時(最少 5 分鐘)。若在此期間無回應,請求將會重試。對於較慢的模型請增加此值。", "mistralApiKey": "Mistral API 金鑰", "getMistralApiKey": "取得 Mistral/Codestral API 金鑰", "codestralBaseUrl": "Codestral 基礎 URL(選用)",