From bc5a04bb86dd56e8d284e1a054816f54b3d64370 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 16 Oct 2026 18:19:02 -0700 Subject: [PATCH 1/2] docs(config): correct topP/temperature mutual-exclusion comment --- src/config.test.ts | 19 +++++++++++++++++++ src/config/settings.test.ts | 15 ++++++++++++++- src/config/settings.ts | 5 ++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/config.test.ts b/src/config.test.ts index 32d488de0..64e7b243b 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -1467,6 +1467,25 @@ describe("buildOpenAISource", () => { }); }); + test("flows temperature and top_p together when both are present (CL-10760)", () => { + // The temperature/topP exclusion lives in the settings schema (load + // rejects both); buildOpenAISource itself merges whatever it is given, so + // provider-mandated pairs (e.g. DeepSeek V4 model-card defaults) still + // reach the wire. + const source = buildOpenAISource({ + id: "fp", + baseURL: "https://fp/v1", + apiKey: "k", + model: "m", + temperature: 0.7, + topP: 0.9, + }); + expect(source.defaults).toEqual({ + maxTokens: SOURCE_MAX_TOKENS, + providerOptions: { temperature: 0.7, top_p: 0.9 }, + }); + }); + test("coexists temperature with reasoning_effort in one providerOptions", () => { const source = buildOpenAISource({ id: "fp", diff --git a/src/config/settings.test.ts b/src/config/settings.test.ts index 03526dbf5..7f4e8b76c 100644 --- a/src/config/settings.test.ts +++ b/src/config/settings.test.ts @@ -72,7 +72,7 @@ test("invalid token settings cannot be loaded or overwrite a saved provider", as } }); -test("provider settings reject simultaneous sampling fields but preserve zero", () => { +test("provider settings reject simultaneous sampling fields but preserve zero", async () => { const provider = { baseURL: "https://custom.example/v1", keyless: true, @@ -85,6 +85,19 @@ test("provider settings reject simultaneous sampling fields but preserve zero", expect(isSettings({ providers: { custom: { ...provider, topP: 0 } } })).toBe( false, ); + // CL-10760: the exclusion is enforced at load, not just by isSettings — + // a persisted file with both knobs fails to load. + const cwd = await mkdtemp(join(tmpdir(), "both-sampling-knobs-")); + try { + const path = join(cwd, "settings.json"); + await writeFile( + path, + JSON.stringify({ providers: { custom: { ...provider, topP: 0 } } }), + ); + await expect(loadSettings(path)).rejects.toThrow(); + } finally { + await rm(cwd, { recursive: true, force: true }); + } }); afterEach(() => { diff --git a/src/config/settings.ts b/src/config/settings.ts index f099cdc29..f58363b83 100644 --- a/src/config/settings.ts +++ b/src/config/settings.ts @@ -43,7 +43,10 @@ export interface ProviderSettings { // Per-call output cap, overriding SOURCE_MAX_TOKENS when set. maxTokens?: number; // Sampling temperature (0..2, OpenAI-compatible); mutually exclusive with - // topP — never send both on the wire. + // topP in provider settings — load rejects both (see + // ProviderInferenceOptionsSchema). The wire may still carry both when a + // provider adapter mandates it (DeepSeek V4 backfills absent fields from + // its model-card defaults). temperature?: number; // Sampling top-p (0..1, OpenAI-compatible); mutually exclusive with // temperature. From 08663bc82600aad0612dfbcc6959fa705972e223 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 16 Oct 2026 18:25:49 -0700 Subject: [PATCH 2/2] docs(config): correct the topP test comment --- src/config.test.ts | 10 +++++----- src/config/settings.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config.test.ts b/src/config.test.ts index 64e7b243b..4b86d86f3 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -1467,11 +1467,11 @@ describe("buildOpenAISource", () => { }); }); - test("flows temperature and top_p together when both are present (CL-10760)", () => { - // The temperature/topP exclusion lives in the settings schema (load - // rejects both); buildOpenAISource itself merges whatever it is given, so - // provider-mandated pairs (e.g. DeepSeek V4 model-card defaults) still - // reach the wire. + test("flows temperature and top_p together when both are present", () => { + // Settings load rejects both knobs. This builder still forwards a pair + // it is given. DeepSeek V4 model-card defaults reach the wire because + // the adapter writes top_p 0.95 and temperature 1.0 when each body + // field is absent, not because this merge supplies those defaults. const source = buildOpenAISource({ id: "fp", baseURL: "https://fp/v1", diff --git a/src/config/settings.test.ts b/src/config/settings.test.ts index 7f4e8b76c..6ae6eb026 100644 --- a/src/config/settings.test.ts +++ b/src/config/settings.test.ts @@ -85,8 +85,8 @@ test("provider settings reject simultaneous sampling fields but preserve zero", expect(isSettings({ providers: { custom: { ...provider, topP: 0 } } })).toBe( false, ); - // CL-10760: the exclusion is enforced at load, not just by isSettings — - // a persisted file with both knobs fails to load. + // The exclusion is enforced at load, not just by isSettings — a persisted + // file with both knobs fails to load. const cwd = await mkdtemp(join(tmpdir(), "both-sampling-knobs-")); try { const path = join(cwd, "settings.json");