fix(providers): add alwaysReasons metadata for reka-flash-3 (#13531)

`reka-flash-3` is marked `supportsReasoning` + `alwaysReasons` (with its 8192 output cap). `DefaultExecutor.ensureThinkingBudget` treats always-on reasoning as an implicit opt-in, so a small caller `max_tokens` gets the 4096 floor instead of being spent on reasoning and returning an empty answer (#13198). `alwaysReasons` is documented on `RegistryModel`.

Maintainer addition: `tests/unit/reka-flash-3-always-reasons-13198.test.ts` asserts the floor for `reka-flash-3` without any reasoning settings (fails on the release tip) and that `reka-flash` keeps the caller value. The branch also carried the #13532 Compare-scroll commit, which landed first; its hunks are identical, so this squash only adds the reka change.

Validated in one consolidated batch of this series (37 PRs boarded together on `release/v3.8.51`): `typecheck:core`, `check:open-sse-typecheck` and `check:dashboard-typecheck` clean; ESLint clean on every changed file; file-size, complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync and migration-numbering gates green (only the pre-existing `open-sse/utils/stream.ts` file-size red remains, inherited from the base); 3,743 focused `node:test` cases plus 34 vitest cases green.

Thanks @KooshaPari!
This commit is contained in:
Koosha Paridehpour
2026-09-14 20:08:31 -07:00
committed by GitHub
parent 3b5ce24acb
commit 2d1a281d12
4 changed files with 32 additions and 2 deletions

View File

@@ -11,7 +11,7 @@ export const rekaProvider: RegistryEntry = {
models: [
// reka-flash-3 stays first so it remains the provider default (the free-tier
// model in freeModelCatalog); reka-flash was added in #4621 as an extra option.
{ id: "reka-flash-3", name: "Reka Flash 3" },
{ id: "reka-flash-3", name: "Reka Flash 3", supportsReasoning: true, alwaysReasons: true, maxOutputTokens: 8192 },
{ id: "reka-flash", name: "Reka Flash" },
{ id: "reka-edge-2603", name: "Reka Edge 2603" },
],

View File

@@ -56,6 +56,13 @@ export interface RegistryModel {
liveCatalogIds?: readonly string[];
toolCalling?: boolean;
supportsReasoning?: boolean;
/**
* Model reasons unconditionally (always-on reasoning). When true,
* ensureThinkingBudget treats it as implicit reasoning opt-in so a tiny
* caller max_tokens gets the 4096 floor even without explicit thinking
* settings (#13198).
*/
alwaysReasons?: boolean;
supportedThinkingEfforts?: readonly string[];
supportsVision?: boolean;
supportsAudio?: boolean;

View File

@@ -1076,7 +1076,8 @@ export class DefaultExecutor extends BaseExecutor {
const reasoningEnabled =
thinking?.type === "enabled" ||
(typeof effort === "string" && effort !== "none" && effort !== "off") ||
effort === true;
effort === true ||
modelEntry.alwaysReasons === true;
if (!reasoningEnabled) return body;
const MIN_TOKENS = 4096;

View File

@@ -0,0 +1,22 @@
import test from "node:test";
import assert from "node:assert/strict";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
// #13198: reka-flash-3 reasons on every request, so a tiny caller max_tokens is
// spent on reasoning and the answer comes back empty unless the 4096 floor applies
// even without an explicit reasoning_effort / thinking setting.
test("#13198 reka-flash-3 gets the thinking-budget floor without explicit reasoning settings", () => {
const executor = new DefaultExecutor("reka");
const body = { model: "reka-flash-3", max_tokens: 256 } as Record<string, unknown>;
executor.ensureThinkingBudget(body, "reka-flash-3");
assert.equal(body.max_tokens, 4096);
});
test("#13198 non-reasoning reka models keep the caller's max_tokens", () => {
const executor = new DefaultExecutor("reka");
const body = { model: "reka-flash", max_tokens: 256 } as Record<string, unknown>;
executor.ensureThinkingBudget(body, "reka-flash");
assert.equal(body.max_tokens, 256);
});