From 7c8d35358b045af574170965fa2ba8a230886316 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Thu, 2 Jul 2026 22:01:32 -0300 Subject: [PATCH] feat(xai): register XaiExecutor with reasoning-effort suffix parsing New open-sse/executors/xai.ts (XaiExecutor) parses reasoning-effort model suffixes; registered in executors/index.ts. Re-cut onto release/v3.8.44 tip (branch was stale from a pre-v3.8.40 snapshot). --- .../config/providers/registry/xai/index.ts | 2 +- open-sse/executors/index.ts | 3 + open-sse/executors/xai.ts | 94 +++++++++++++++++++ tests/unit/executors/xai-executor.test.ts | 94 +++++++++++++++++++ 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 open-sse/executors/xai.ts create mode 100644 tests/unit/executors/xai-executor.test.ts diff --git a/open-sse/config/providers/registry/xai/index.ts b/open-sse/config/providers/registry/xai/index.ts index 82d6314a5c..501fa4fcbf 100644 --- a/open-sse/config/providers/registry/xai/index.ts +++ b/open-sse/config/providers/registry/xai/index.ts @@ -4,7 +4,7 @@ export const xaiProvider: RegistryEntry = { id: "xai", alias: "xai", format: "openai", - executor: "default", + executor: "xai", baseUrl: "https://api.x.ai/v1/chat/completions", authType: "apikey", authHeader: "bearer", diff --git a/open-sse/executors/index.ts b/open-sse/executors/index.ts index 65d09889e3..f522a13cf6 100644 --- a/open-sse/executors/index.ts +++ b/open-sse/executors/index.ts @@ -54,6 +54,7 @@ import { MimocodeExecutor } from "./mimocode.ts"; import { GrokCliExecutor } from "./grok-cli.ts"; import { CodeBuddyCnExecutor } from "./codebuddy-cn.ts"; import { ZenmuxFreeExecutor } from "./zenmux-free.ts"; +import { XaiExecutor } from "./xai.ts"; const executors = { antigravity: new AntigravityExecutor(), @@ -154,6 +155,7 @@ const executors = { cbcn: new CodeBuddyCnExecutor(), // Alias for codebuddy-cn "zenmux-free": new ZenmuxFreeExecutor(), zmf: new ZenmuxFreeExecutor(), // Alias for zenmux-free + xai: new XaiExecutor(), }; const defaultCache = new Map(); @@ -217,3 +219,4 @@ export { MimocodeExecutor } from "./mimocode.ts"; export { GrokCliExecutor } from "./grok-cli.ts"; export { CodeBuddyCnExecutor } from "./codebuddy-cn.ts"; export { ZenmuxFreeExecutor } from "./zenmux-free.ts"; +export { XaiExecutor } from "./xai.ts"; diff --git a/open-sse/executors/xai.ts b/open-sse/executors/xai.ts new file mode 100644 index 0000000000..9f806e2c5a --- /dev/null +++ b/open-sse/executors/xai.ts @@ -0,0 +1,94 @@ +import { BaseExecutor, type ProviderCredentials } from "./base.ts"; +import { PROVIDERS } from "../config/constants.ts"; + +type JsonRecord = Record; + +/** + * xAI/Grok model ids (open-sse/config/providers/registry/xai/index.ts) that accept + * a graduated `reasoning_effort`. Kept narrow and reconciled against the REAL + * catalog rather than upstream's example ids (grok-4/grok-3 do not exist here): + * - grok-4.3 — current-generation flagship, reasoning-capable. + * - grok-4.20-0309-reasoning — explicit reasoning variant. + * + * grok-4.20-multi-agent-0309 is intentionally left unclassified (neither allow + * nor deny): its reasoning support is not documented in the local catalog, so + * we pass it through unchanged rather than guess. + */ +const REASONING_ALLOWED = ["grok-4.3", "grok-4.20-0309-reasoning"]; + +/** + * Model ids that reject `reasoning_effort` outright: + * - grok-build-0.1 — build/tool-oriented model, no reasoning mode. + * - grok-4.20-0309-non-reasoning — already encodes "no reasoning" in the id; + * forwarding reasoning_effort here would be redundant/rejected upstream. + */ +const REASONING_DENIED = ["grok-build-0.1", "grok-4.20-0309-non-reasoning"]; + +/** `-{level}` suffixes some clients append to a model id to select reasoning intensity. */ +const EFFORT_SUFFIXES = ["low", "medium", "high", "xhigh"] as const; + +function asRecord(value: unknown): JsonRecord | null { + return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null; +} + +/** + * xAI/Grok executor (port of decolua/9router#2147). + * + * Some Grok clients select reasoning intensity via a `-{low,medium,high,xhigh}` + * suffix on the model id (e.g. `grok-4.3-high`) rather than a native + * `reasoning_effort` field — xAI itself does not recognize the suffixed id. + * This executor: + * 1. Parses and strips that suffix off the model id before the request + * reaches xAI, mapping it to `reasoning_effort` for allow-listed models. + * 2. Strips any `reasoning_effort` for deny-listed models — including ids + * that already encode their reasoning state in the name (`-reasoning` / + * `-non-reasoning`), which must not be double-mutated by also stacking a + * `reasoning_effort` field on top of what the id already declares. + * 3. Leaves unclassified models and bodies untouched otherwise. + */ +export class XaiExecutor extends BaseExecutor { + constructor() { + super("xai", PROVIDERS.xai); + } + + transformRequest( + model: string, + body: unknown, + stream: boolean, + credentials: ProviderCredentials + ): unknown { + const cleaned = super.transformRequest(model, body, stream, credentials); + const record = asRecord(cleaned); + if (!record) return cleaned; + + const out: JsonRecord = { ...record }; + let modelId = typeof out.model === "string" ? out.model : model; + + let suffixEffort: string | null = null; + for (const level of EFFORT_SUFFIXES) { + const suffix = `-${level}`; + if (modelId.endsWith(suffix)) { + suffixEffort = level; + modelId = modelId.slice(0, -suffix.length); + break; + } + } + if (suffixEffort && typeof out.model === "string") { + out.model = modelId; + } + + const isDenied = REASONING_DENIED.some((id) => modelId.includes(id)); + const isAllowed = REASONING_ALLOWED.some((id) => modelId.includes(id)); + + if (isDenied) { + delete out.reasoning_effort; + } else if (isAllowed) { + const effort = suffixEffort || out.reasoning_effort; + if (effort) out.reasoning_effort = effort; + } + + return out; + } +} + +export default XaiExecutor; diff --git a/tests/unit/executors/xai-executor.test.ts b/tests/unit/executors/xai-executor.test.ts new file mode 100644 index 0000000000..1042460ea6 --- /dev/null +++ b/tests/unit/executors/xai-executor.test.ts @@ -0,0 +1,94 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { XaiExecutor } from "../../../open-sse/executors/xai.ts"; +import { getExecutor, hasSpecializedExecutor } from "../../../open-sse/executors/index.ts"; +import { xaiProvider } from "../../../open-sse/config/providers/registry/xai/index.ts"; + +// Real xai catalog ids (open-sse/config/providers/registry/xai/index.ts): +// grok-4.3 — plain, reasoning-capable +// grok-build-0.1 — build/tool model, no reasoning mode +// grok-4.20-multi-agent-0309 — neutral (not in either allow/deny list) +// grok-4.20-0309-reasoning — already encodes reasoning in the id +// grok-4.20-0309-non-reasoning — already encodes non-reasoning in the id + +const credentials = { apiKey: "test-key" }; + +test("XaiExecutor is registered under the 'xai' key and set as the registry executor", () => { + assert.equal(hasSpecializedExecutor("xai"), true); + assert.ok(getExecutor("xai") instanceof XaiExecutor); + assert.equal(xaiProvider.executor, "xai"); +}); + +test("strips a -{level} suffix from an allow-listed model and sets reasoning_effort", () => { + const executor = new XaiExecutor(); + + for (const level of ["low", "medium", "high", "xhigh"] as const) { + const body = { model: `grok-4.3-${level}`, messages: [] }; + const out = executor.transformRequest(`grok-4.3-${level}`, body, false, credentials) as Record< + string, + unknown + >; + assert.equal(out.model, "grok-4.3", `level=${level} should strip suffix from model id`); + assert.equal(out.reasoning_effort, level, `level=${level} should set reasoning_effort`); + } +}); + +test("suffix parsing also applies to the explicit -reasoning variant without double-mutating it", () => { + const executor = new XaiExecutor(); + const body = { model: "grok-4.20-0309-reasoning-high", messages: [] }; + const out = executor.transformRequest( + "grok-4.20-0309-reasoning-high", + body, + false, + credentials + ) as Record; + + assert.equal(out.model, "grok-4.20-0309-reasoning"); + assert.equal(out.reasoning_effort, "high"); +}); + +test("strips reasoning_effort for a deny-listed model (grok-build-0.1)", () => { + const executor = new XaiExecutor(); + const body = { model: "grok-build-0.1", reasoning_effort: "high", messages: [] }; + const out = executor.transformRequest("grok-build-0.1", body, false, credentials) as Record< + string, + unknown + >; + + assert.equal(out.model, "grok-build-0.1"); + assert.equal(out.reasoning_effort, undefined); +}); + +test("strips reasoning_effort for the explicit -non-reasoning variant (already encodes reasoning state)", () => { + const executor = new XaiExecutor(); + const body = { + model: "grok-4.20-0309-non-reasoning", + reasoning_effort: "high", + messages: [], + }; + const out = executor.transformRequest( + "grok-4.20-0309-non-reasoning", + body, + false, + credentials + ) as Record; + + assert.equal(out.model, "grok-4.20-0309-non-reasoning"); + assert.equal(out.reasoning_effort, undefined); +}); + +test("leaves a plain, unlisted model id and body unchanged (no suffix, not allow/deny listed)", () => { + const executor = new XaiExecutor(); + const body = { model: "grok-4.20-multi-agent-0309", messages: [{ role: "user", content: "hi" }] }; + const out = executor.transformRequest( + "grok-4.20-multi-agent-0309", + body, + false, + credentials + ) as Record; + + assert.equal(out.model, "grok-4.20-multi-agent-0309"); + assert.equal(out.reasoning_effort, undefined); + assert.deepEqual(out.messages, body.messages); +});