-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(server): advertise reasoning-effort ladders on the raw /v1/models list #853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Wibias
merged 12 commits into
lidge-jun:dev
from
n3wr1ch:feat/grok-reasoning-effort-list
Aug 2, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
9d6b986
feat(server): advertise reasoning-effort ladders on the raw /v1/model…
n3wr1ch 6c900da
docs(grok-build): document reasoning-effort advertisement on /v1/models
n3wr1ch 084b5b6
fix(server): use canonical default fallback and advertise native ladd…
n3wr1ch 68aa3f3
docs(test): distinguish native ladders and pin regression coverage
n3wr1ch 0938eaf
rebase: preserve latest dev catalog busy handling
n3wr1ch 30c7e97
test(server): cover the first-tier reasoning default fallback
n3wr1ch 6942574
Merge remote-tracking branch 'upstream/dev' into review/pr-853
Wibias 38da791
fix(server): preserve upstream native default on raw /v1/models effor…
Wibias c0426f6
Merge remote-tracking branch 'upstream/dev' into review/pr-853
Wibias 53d8374
fix(catalog): preserve per-model GPT-5.6 effort ladders
n3wr1ch 27af158
Merge remote-tracking branch 'upstream/dev' into review/pr-853
Wibias dd813da
Merge remote-tracking branch 'upstream/dev' into review/pr-853
Wibias File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| import { afterEach, beforeEach, describe, expect, test } from "bun:test"; | ||
| import { mkdtempSync, rmSync } from "node:fs"; | ||
| import { tmpdir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { saveConfig } from "../src/config"; | ||
| import { startServer } from "../src/server"; | ||
| import type { OcxConfig } from "../src/types"; | ||
|
|
||
| const previousHome = process.env.OPENCODEX_HOME; | ||
| let testHome = ""; | ||
|
|
||
| function effortConfig(): OcxConfig { | ||
| return { | ||
| port: 0, | ||
| hostname: "127.0.0.1", | ||
| defaultProvider: "kimi", | ||
| providers: { | ||
| kimi: { | ||
| adapter: "openai-chat", | ||
| baseUrl: "https://kimi.test/v1", | ||
| models: ["k3", "kimi-for-coding"], | ||
| modelReasoningEfforts: { | ||
| k3: ["low", "high", "max"], | ||
| "kimi-for-coding": [], | ||
| }, | ||
| modelDefaultReasoningEfforts: { k3: "high" }, | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| beforeEach(() => { | ||
| testHome = mkdtempSync(join(tmpdir(), "ocx-grok-effort-list-")); | ||
| process.env.OPENCODEX_HOME = testHome; | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| if (previousHome === undefined) delete process.env.OPENCODEX_HOME; | ||
| else process.env.OPENCODEX_HOME = previousHome; | ||
| if (testHome) rmSync(testHome, { recursive: true, force: true }); | ||
| testHome = ""; | ||
| }); | ||
|
|
||
| describe("raw /v1/models list reasoning-effort advertisement (Grok Build discovery)", () => { | ||
| test("routed models with configured tiers advertise the Grok reasoning catalog shape", async () => { | ||
| saveConfig(effortConfig()); | ||
| const server = startServer(0); | ||
| try { | ||
| const res = await fetch(new URL("/v1/models", server.url)); | ||
| expect(res.status).toBe(200); | ||
| const body = await res.json() as { data: Array<Record<string, unknown>> }; | ||
| const k3 = body.data.find(m => m.id === "kimi/k3"); | ||
| expect(k3).toBeDefined(); | ||
| expect(k3!.supports_reasoning_effort).toBe(true); | ||
| expect(k3!.reasoning_effort).toBe("high"); | ||
| expect(k3!.reasoning_efforts).toEqual([ | ||
| { value: "low", label: "Low Effort" }, | ||
| { value: "high", label: "High Effort", default: true }, | ||
| { value: "max", label: "Max Effort" }, | ||
| ]); | ||
| // Native rows preserve the pinned per-model ladder and default. Sol and Terra include | ||
| // ultra, while Luna intentionally ends at max, matching the canonical Codex catalog. | ||
| const nativeExpectations = [ | ||
| { | ||
| id: "gpt-5.6-sol", | ||
| defaultEffort: "low", | ||
| efforts: ["low", "medium", "high", "xhigh", "max", "ultra"], | ||
| }, | ||
| { | ||
| id: "gpt-5.6-terra", | ||
| defaultEffort: "medium", | ||
| efforts: ["low", "medium", "high", "xhigh", "max", "ultra"], | ||
| }, | ||
| { | ||
| id: "gpt-5.6-luna", | ||
| defaultEffort: "medium", | ||
| efforts: ["low", "medium", "high", "xhigh", "max"], | ||
| }, | ||
| ]; | ||
| for (const expected of nativeExpectations) { | ||
| const native = body.data.find(m => m.id === expected.id); | ||
| expect(native).toBeDefined(); | ||
| expect(native!.supports_reasoning_effort).toBe(true); | ||
| expect(native!.reasoning_effort).toBe(expected.defaultEffort); | ||
| expect((native!.reasoning_efforts as Array<{ value: string }>).map(option => option.value)) | ||
| .toEqual(expected.efforts); | ||
| } | ||
| } finally { | ||
| await server.stop(true); | ||
| } | ||
| }); | ||
|
|
||
| test("models with an empty tier list advertise no effort fields", async () => { | ||
| saveConfig(effortConfig()); | ||
| const server = startServer(0); | ||
| try { | ||
| const res = await fetch(new URL("/v1/models", server.url)); | ||
| const body = await res.json() as { data: Array<Record<string, unknown>> }; | ||
| const plain = body.data.find(m => m.id === "kimi/kimi-for-coding"); | ||
| expect(plain).toBeDefined(); | ||
| expect("supports_reasoning_effort" in plain!).toBe(false); | ||
| expect("reasoning_effort" in plain!).toBe(false); | ||
| expect("reasoning_efforts" in plain!).toBe(false); | ||
| } finally { | ||
| await server.stop(true); | ||
| } | ||
| }); | ||
|
|
||
| test("a ladder without a configured default uses the canonical medium default", async () => { | ||
| const config = effortConfig(); | ||
| config.providers.kimi!.modelDefaultReasoningEfforts = {}; | ||
| config.providers.kimi!.modelReasoningEfforts = { k3: ["low", "medium", "high"] }; | ||
| saveConfig(config); | ||
| const server = startServer(0); | ||
| try { | ||
| const res = await fetch(new URL("/v1/models", server.url)); | ||
| const body = await res.json() as { data: Array<Record<string, unknown>> }; | ||
| const k3 = body.data.find(m => m.id === "kimi/k3"); | ||
| expect(k3!.reasoning_effort).toBe("medium"); | ||
| const options = k3!.reasoning_efforts as Array<Record<string, unknown>>; | ||
| expect(options[1]).toEqual({ value: "medium", label: "Medium Effort", default: true }); | ||
| } finally { | ||
| await server.stop(true); | ||
| } | ||
| }); | ||
|
|
||
| test("an invalid configured default falls back with the canonical medium/high/first order", async () => { | ||
| const config = effortConfig(); | ||
| config.providers.kimi!.modelDefaultReasoningEfforts = { k3: "medium" }; | ||
| saveConfig(config); | ||
| const server = startServer(0); | ||
| try { | ||
| const res = await fetch(new URL("/v1/models", server.url)); | ||
| const body = await res.json() as { data: Array<Record<string, unknown>> }; | ||
| const k3 = body.data.find(m => m.id === "kimi/k3"); | ||
| // k3's ladder is low/high/max: no medium, so the canonical fallback picks high. | ||
| expect(k3!.reasoning_effort).toBe("high"); | ||
| const options = k3!.reasoning_efforts as Array<Record<string, unknown>>; | ||
| expect(options[1]).toEqual({ value: "high", label: "High Effort", default: true }); | ||
| } finally { | ||
| await server.stop(true); | ||
| } | ||
| }); | ||
|
|
||
| test("falls back to the first tier when neither medium nor high is available", async () => { | ||
| const config = effortConfig(); | ||
| config.providers.kimi!.models = [...(config.providers.kimi!.models ?? []), "custom-test"]; | ||
| config.providers.kimi!.modelReasoningEfforts = { "custom-test": ["low", "max"] }; | ||
| config.providers.kimi!.modelDefaultReasoningEfforts = { "custom-test": "medium" }; | ||
| saveConfig(config); | ||
| const server = startServer(0); | ||
| try { | ||
| const res = await fetch(new URL("/v1/models", server.url)); | ||
| const body = await res.json() as { data: Array<Record<string, unknown>> }; | ||
| const model = body.data.find(m => m.id === "kimi/custom-test"); | ||
| expect(model!.reasoning_effort).toBe("low"); | ||
| expect(model!.reasoning_efforts).toEqual([ | ||
| { value: "low", label: "Low Effort", default: true }, | ||
| { value: "max", label: "Max Effort" }, | ||
| ]); | ||
| } finally { | ||
| await server.stop(true); | ||
| } | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.