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); + }); + }); });