Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,25 @@ describe("buildOpenAISource", () => {
});
});

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",
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",
Expand Down
15 changes: 14 additions & 1 deletion src/config/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -85,6 +85,19 @@ test("provider settings reject simultaneous sampling fields but preserve zero",
expect(isSettings({ providers: { custom: { ...provider, topP: 0 } } })).toBe(
false,
);
// 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(() => {
Expand Down
5 changes: 4 additions & 1 deletion src/config/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading