From eec87837403df7efc24460d4b11646c8a4278aea Mon Sep 17 00:00:00 2001 From: Pluviobyte Date: Fri, 29 May 2026 06:28:16 +0000 Subject: [PATCH] fix(llm): guard getMaxStopWords against undefined apiBase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenAI.getMaxStopWords() unconditionally ran `new URL(this.apiBase!)` at the top of the method, but apiBase can legitimately be undefined — e.g. continue-proxy, whose static defaultOptions replace the parent OpenAI defaults and don't carry an apiBase. When it is undefined the non-null assertion lies and `new URL(undefined)` throws "TypeError: Invalid URL", crashing stop-word trimming. This moves the URL parse below the maxStopWords short-circuit (so it is only built when actually needed) and returns Infinity when apiBase is absent — the safe default already used for unknown hosts. Adds regression tests covering both branches. Related to #11872 Co-authored-by: Cursor --- core/llm/llms/OpenAI.ts | 13 ++++++++++--- core/llm/llms/OpenAI.vitest.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/core/llm/llms/OpenAI.ts b/core/llm/llms/OpenAI.ts index c65b55dc1a5..32288badeaf 100644 --- a/core/llm/llms/OpenAI.ts +++ b/core/llm/llms/OpenAI.ts @@ -253,11 +253,18 @@ class OpenAI extends BaseLLM { } protected getMaxStopWords(): number { - const url = new URL(this.apiBase!); - if (this.maxStopWords !== undefined) { return this.maxStopWords; - } else if (url.host === "api.deepseek.com") { + } + + // apiBase can legitimately be undefined (e.g. continue-proxy, whose + // defaultOptions don't carry one), so guard against `new URL(undefined)`. + if (!this.apiBase) { + return Infinity; + } + + const url = new URL(this.apiBase); + if (url.host === "api.deepseek.com") { return 16; } else if ( url.port === "1337" || diff --git a/core/llm/llms/OpenAI.vitest.ts b/core/llm/llms/OpenAI.vitest.ts index b07f05b15d8..38450ddbb68 100644 --- a/core/llm/llms/OpenAI.vitest.ts +++ b/core/llm/llms/OpenAI.vitest.ts @@ -424,4 +424,31 @@ describe("OpenAI", () => { }, }); }); + + describe("getMaxStopWords", () => { + test("returns Infinity instead of throwing when apiBase is undefined", () => { + const openai = new OpenAI({ + apiKey: "test-api-key", + model: "gpt-4", + apiBase: "https://api.openai.com/v1/", + }); + // continue-proxy and similar providers can leave apiBase undefined + (openai as any).apiBase = undefined; + + expect(() => (openai as any).getMaxStopWords()).not.toThrow(); + expect((openai as any).getMaxStopWords()).toBe(Infinity); + }); + + test("honors maxStopWords even when apiBase is undefined", () => { + const openai = new OpenAI({ + apiKey: "test-api-key", + model: "gpt-4", + apiBase: "https://api.openai.com/v1/", + }); + (openai as any).apiBase = undefined; + (openai as any).maxStopWords = 7; + + expect((openai as any).getMaxStopWords()).toBe(7); + }); + }); });