Skip to content

Commit 29f118e

Browse files
author
bcode
committed
fix(provider): honor strict temperature requirements over agent override
Some providers reject any temperature value other than the one they require (e.g. Moonshot's kimi-k2.* returns HTTP 400 "invalid temperature: only 1 is allowed for this model"). The previous chain in session/llm.ts let the agent's preference win: input.agent.temperature ?? ProviderTransform.temperature(input.model) The built-in title agent hard-codes temperature: 0.5, so any session that started with title generation on one of these models silently failed. OSS Laminar telemetry over the past 13 days shows 27 occurrences on kimi-k2.6 alone, plus failures on minimax-m2, glm-4.6/4.7, gemini. Split the model->temperature mapping into: - temperatureStrict(model): only returns a value when the provider rejects other values. These must override agent/user preferences. - temperature(model): falls through to temperatureStrict, otherwise returns a recommended default (qwen=0.55) or undefined. Used as the soft fallback when no agent/user temperature is set. session/llm.ts call site becomes: temperatureStrict(model) ?? agent.temperature ?? temperature(model) so the strict value always wins, the user/agent override still applies on flexible models, and we keep recommended defaults when nothing is set.
1 parent de1d917 commit 29f118e

3 files changed

Lines changed: 71 additions & 5 deletions

File tree

packages/opencode/src/provider/transform.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -475,16 +475,21 @@ export function message(msgs: ModelMessage[], model: Provider.Model, options: Re
475475
return msgs
476476
}
477477

478-
export function temperature(model: Provider.Model) {
478+
// Returns the *required* temperature for models whose provider rejects any
479+
// other value. When this returns a number, it MUST override agent/user
480+
// preferences at the call site — sending a different value will fail the API
481+
// call (e.g. Moonshot's kimi-k2.* returns HTTP 400 "invalid temperature: only
482+
// 1 is allowed for this model"). Returns undefined for models that accept a
483+
// range of temperatures, in which case the agent/user preference wins and
484+
// `temperature()` below supplies a recommended default.
485+
export function temperatureStrict(model: Provider.Model): number | undefined {
479486
const id = model.id.toLowerCase()
480-
if (id.includes("qwen")) return 0.55
481-
if (id.includes("claude")) return undefined
482487
if (id.includes("gemini")) return 1.0
483488
if (id.includes("glm-4.6")) return 1.0
484489
if (id.includes("glm-4.7")) return 1.0
485490
if (id.includes("minimax-m2")) return 1.0
486491
if (id.includes("kimi-k2")) {
487-
// kimi-k2-thinking & kimi-k2.5 && kimi-k2p5 && kimi-k2-5
492+
// kimi-k2-thinking & kimi-k2.5 && kimi-k2p5 && kimi-k2-5 && kimi-k2.6
488493
if (["thinking", "k2.", "k2p", "k2-5"].some((s) => id.includes(s))) {
489494
return 1.0
490495
}
@@ -493,6 +498,18 @@ export function temperature(model: Provider.Model) {
493498
return undefined
494499
}
495500

501+
// Returns the recommended default temperature for models whose API accepts a
502+
// range of values. Used as a fallback when no agent/user temperature is set.
503+
// Models with a strict requirement should be handled by `temperatureStrict`.
504+
export function temperature(model: Provider.Model): number | undefined {
505+
const strict = temperatureStrict(model)
506+
if (strict !== undefined) return strict
507+
const id = model.id.toLowerCase()
508+
if (id.includes("qwen")) return 0.55
509+
if (id.includes("claude")) return undefined
510+
return undefined
511+
}
512+
496513
export function topP(model: Provider.Model) {
497514
const id = model.id.toLowerCase()
498515
if (id.includes("qwen")) return 1

packages/opencode/src/session/llm.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ const live: Layer.Layer<
169169
},
170170
{
171171
temperature: input.model.capabilities.temperature
172-
? (input.agent.temperature ?? ProviderTransform.temperature(input.model))
172+
? (ProviderTransform.temperatureStrict(input.model) ??
173+
input.agent.temperature ??
174+
ProviderTransform.temperature(input.model))
173175
: undefined,
174176
topP: input.agent.topP ?? ProviderTransform.topP(input.model),
175177
topK: ProviderTransform.topK(input.model),

packages/opencode/test/provider/transform.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3686,3 +3686,50 @@ describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {
36863686
expect(result).toEqual({ openaiCompatible: { reasoningEffort: "high" } })
36873687
})
36883688
})
3689+
3690+
describe("ProviderTransform.temperatureStrict", () => {
3691+
// Returns the *required* temperature for models that reject anything else.
3692+
// Regression for OSS telemetry: title agent's hard-coded 0.5 was being sent
3693+
// to kimi-k2.6, which Moonshot rejects with "invalid temperature: only 1 is
3694+
// allowed for this model" (~27 occurrences over 13 days).
3695+
const model = (id: string) => ({ id }) as any
3696+
3697+
test("kimi-k2.6 requires 1.0", () => {
3698+
expect(ProviderTransform.temperatureStrict(model("kimi-k2.6"))).toBe(1.0)
3699+
})
3700+
3701+
test("kimi-k2-thinking requires 1.0", () => {
3702+
expect(ProviderTransform.temperatureStrict(model("kimi-k2-thinking"))).toBe(1.0)
3703+
})
3704+
3705+
test("kimi-k2 (base) requires 0.6", () => {
3706+
expect(ProviderTransform.temperatureStrict(model("kimi-k2"))).toBe(0.6)
3707+
})
3708+
3709+
test("gemini / glm-4.6 / glm-4.7 / minimax-m2 require 1.0", () => {
3710+
expect(ProviderTransform.temperatureStrict(model("gemini-3-pro-preview"))).toBe(1.0)
3711+
expect(ProviderTransform.temperatureStrict(model("glm-4.6"))).toBe(1.0)
3712+
expect(ProviderTransform.temperatureStrict(model("glm-4.7"))).toBe(1.0)
3713+
expect(ProviderTransform.temperatureStrict(model("minimax-m2.7"))).toBe(1.0)
3714+
})
3715+
3716+
test("qwen has no strict requirement (recommended-only)", () => {
3717+
expect(ProviderTransform.temperatureStrict(model("qwen35-27b-fp8"))).toBeUndefined()
3718+
// But the recommended default still applies.
3719+
expect(ProviderTransform.temperature(model("qwen35-27b-fp8"))).toBe(0.55)
3720+
})
3721+
3722+
test("claude / openai have no strict requirement and no recommendation", () => {
3723+
expect(ProviderTransform.temperatureStrict(model("claude-opus-4-7"))).toBeUndefined()
3724+
expect(ProviderTransform.temperature(model("claude-opus-4-7"))).toBeUndefined()
3725+
expect(ProviderTransform.temperatureStrict(model("gpt-5.5"))).toBeUndefined()
3726+
expect(ProviderTransform.temperature(model("gpt-5.5"))).toBeUndefined()
3727+
})
3728+
3729+
test("temperature() falls through to temperatureStrict() for strict models", () => {
3730+
// Ensures the recommended-default helper still serves the correct value
3731+
// when no agent override is present.
3732+
expect(ProviderTransform.temperature(model("kimi-k2.6"))).toBe(1.0)
3733+
expect(ProviderTransform.temperature(model("gemini-3-pro-preview"))).toBe(1.0)
3734+
})
3735+
})

0 commit comments

Comments
 (0)