mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 06:02:14 +03:00
[codex] fix xAI OAuth test and reasoning effort (#5157)
Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale.
This commit is contained in:
@@ -73,6 +73,21 @@ const OAUTH_TEST_CONFIG = {
|
||||
authPrefix: "Bearer ",
|
||||
refreshable: true,
|
||||
},
|
||||
xai: {
|
||||
url: "https://api.x.ai/v1/chat/completions",
|
||||
method: "POST",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer ",
|
||||
extraHeaders: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model: "grok-4.3",
|
||||
messages: [{ role: "user", content: "ping" }],
|
||||
max_tokens: 1,
|
||||
stream: false,
|
||||
reasoning: { effort: "high" },
|
||||
}),
|
||||
refreshable: true,
|
||||
},
|
||||
github: {
|
||||
url: "https://api.github.com/user",
|
||||
method: "GET",
|
||||
@@ -561,7 +576,8 @@ export async function testOAuthConnection(
|
||||
// 400 because the probe body is invalid. A 400 from such a provider means auth
|
||||
// succeeded; only 401/403 means the token is bad.
|
||||
const accepted =
|
||||
res.ok || (Array.isArray(config.acceptStatuses) && config.acceptStatuses.includes(res.status));
|
||||
res.ok ||
|
||||
(Array.isArray(config.acceptStatuses) && config.acceptStatuses.includes(res.status));
|
||||
if (accepted) {
|
||||
return {
|
||||
valid: true,
|
||||
|
||||
@@ -16,6 +16,13 @@ const VALID_EFFORTS = new Set(["minimal", "low", "medium", "high"]);
|
||||
|
||||
export type ReasoningEffort = "minimal" | "low" | "medium" | "high";
|
||||
|
||||
export function normalizeXaiReasoningEffort(effort: unknown): ReasoningEffort | undefined {
|
||||
if (typeof effort !== "string") return undefined;
|
||||
const normalized = effort.toLowerCase();
|
||||
if (normalized === "max" || normalized === "xhigh") return "high";
|
||||
return VALID_EFFORTS.has(normalized) ? (normalized as ReasoningEffort) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a numeric token budget to a discrete effort tier.
|
||||
* <=0 → undefined (disabled)
|
||||
@@ -69,7 +76,7 @@ interface ApplyThinkingOptions {
|
||||
*/
|
||||
export function applyThinking(
|
||||
request: ThinkingRequest,
|
||||
options: ApplyThinkingOptions = {},
|
||||
options: ApplyThinkingOptions = {}
|
||||
): ThinkingRequest {
|
||||
if (!request || typeof request !== "object") return request;
|
||||
const out: ThinkingRequest = { ...request };
|
||||
@@ -77,14 +84,19 @@ export function applyThinking(
|
||||
// 1) Already xAI-native? Honor and stop.
|
||||
if (out.reasoning && typeof out.reasoning === "object") {
|
||||
const reasoning = out.reasoning as Record<string, unknown>;
|
||||
if (typeof reasoning.effort === "string" && VALID_EFFORTS.has(reasoning.effort)) {
|
||||
const normalizedEffort = normalizeXaiReasoningEffort(reasoning.effort);
|
||||
if (normalizedEffort) {
|
||||
if (reasoning.effort !== normalizedEffort) {
|
||||
out.reasoning = { ...reasoning, effort: normalizedEffort };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) OpenAI Chat reasoning_effort
|
||||
if (typeof out.reasoning_effort === "string" && VALID_EFFORTS.has(out.reasoning_effort)) {
|
||||
out.reasoning = { effort: out.reasoning_effort as ReasoningEffort };
|
||||
const reasoningEffort = normalizeXaiReasoningEffort(out.reasoning_effort);
|
||||
if (reasoningEffort) {
|
||||
out.reasoning = { effort: reasoningEffort };
|
||||
delete out.reasoning_effort;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* - aggregated xAI response.completed → OpenAI ChatCompletion JSON
|
||||
* - per-event xAI SSE → OpenAI ChatCompletion stream chunks
|
||||
*/
|
||||
import { normalizeXaiReasoningEffort } from "../thinking.ts";
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -192,8 +193,7 @@ export function chatRequestToXaiResponses(req: OpenAiChatRequest): XaiResponsesR
|
||||
input.push({
|
||||
type: "function_call_output",
|
||||
call_id: m.tool_call_id,
|
||||
output:
|
||||
typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? ""),
|
||||
output: typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? ""),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -229,8 +229,15 @@ export function chatRequestToXaiResponses(req: OpenAiChatRequest): XaiResponsesR
|
||||
if (req.response_format) out.text = { format: req.response_format };
|
||||
if (req.parallel_tool_calls != null) out.parallel_tool_calls = req.parallel_tool_calls;
|
||||
if (req.seed != null) out.seed = req.seed;
|
||||
if (req.reasoning_effort) out.reasoning = { effort: req.reasoning_effort };
|
||||
if (req.reasoning) out.reasoning = req.reasoning;
|
||||
if (req.reasoning_effort) {
|
||||
const effort = normalizeXaiReasoningEffort(req.reasoning_effort);
|
||||
if (effort) out.reasoning = { effort };
|
||||
}
|
||||
if (req.reasoning && typeof req.reasoning === "object") {
|
||||
const reasoning = req.reasoning as Record<string, unknown>;
|
||||
const effort = normalizeXaiReasoningEffort(reasoning.effort);
|
||||
out.reasoning = effort ? { ...reasoning, effort } : reasoning;
|
||||
}
|
||||
if (req.tool_choice) out.tool_choice = req.tool_choice;
|
||||
|
||||
const tools = req.tools ? toolsPassthrough(req.tools) : undefined;
|
||||
@@ -272,7 +279,7 @@ function extractAssistantTextAndCalls(completed: XaiCompleted): {
|
||||
*/
|
||||
export function xaiCompletedToChatJson(
|
||||
completed: XaiCompleted,
|
||||
origReq: OpenAiChatRequest | null = null,
|
||||
origReq: OpenAiChatRequest | null = null
|
||||
): object {
|
||||
const { text, toolCalls, refusal } = extractAssistantTextAndCalls(completed);
|
||||
const finishReason = toolCalls.length ? "tool_calls" : "stop";
|
||||
@@ -296,8 +303,7 @@ export function xaiCompletedToChatJson(
|
||||
out.usage = {
|
||||
prompt_tokens: u.input_tokens ?? u.prompt_tokens ?? 0,
|
||||
completion_tokens: u.output_tokens ?? u.completion_tokens ?? 0,
|
||||
total_tokens:
|
||||
u.total_tokens ?? ((u.input_tokens ?? 0) + (u.output_tokens ?? 0)),
|
||||
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0),
|
||||
};
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -9,23 +9,16 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { budgetToEffort, applyThinking } = await import(
|
||||
"../../src/lib/providers/xai/thinking.ts"
|
||||
);
|
||||
const { chatRequestToXaiResponses, xaiCompletedToChatJson } = await import(
|
||||
"../../src/lib/providers/xai/translators/openai-chat.ts"
|
||||
);
|
||||
const {
|
||||
openaiResponsesRequestToXai,
|
||||
xaiCompletedToOpenaiResponses,
|
||||
xaiSseEventToOpenaiResponses,
|
||||
} = await import("../../src/lib/providers/xai/translators/openai-responses.ts");
|
||||
const { claudeRequestToXaiResponses, xaiCompletedToClaudeJson } = await import(
|
||||
"../../src/lib/providers/xai/translators/claude.ts"
|
||||
);
|
||||
const { geminiRequestToXaiResponses, xaiCompletedToGeminiJson } = await import(
|
||||
"../../src/lib/providers/xai/translators/gemini.ts"
|
||||
);
|
||||
const { budgetToEffort, applyThinking, normalizeXaiReasoningEffort } =
|
||||
await import("../../src/lib/providers/xai/thinking.ts");
|
||||
const { chatRequestToXaiResponses, xaiCompletedToChatJson } =
|
||||
await import("../../src/lib/providers/xai/translators/openai-chat.ts");
|
||||
const { openaiResponsesRequestToXai, xaiCompletedToOpenaiResponses, xaiSseEventToOpenaiResponses } =
|
||||
await import("../../src/lib/providers/xai/translators/openai-responses.ts");
|
||||
const { claudeRequestToXaiResponses, xaiCompletedToClaudeJson } =
|
||||
await import("../../src/lib/providers/xai/translators/claude.ts");
|
||||
const { geminiRequestToXaiResponses, xaiCompletedToGeminiJson } =
|
||||
await import("../../src/lib/providers/xai/translators/gemini.ts");
|
||||
|
||||
// ─── budgetToEffort ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -67,6 +60,21 @@ test("applyThinking: honors xAI-native reasoning.effort verbatim", () => {
|
||||
assert.equal((out as Record<string, unknown>).foo, 1);
|
||||
});
|
||||
|
||||
test("normalizeXaiReasoningEffort: downgrades max/xhigh to xAI-supported high", () => {
|
||||
assert.equal(normalizeXaiReasoningEffort("max"), "high");
|
||||
assert.equal(normalizeXaiReasoningEffort("xhigh"), "high");
|
||||
assert.equal(normalizeXaiReasoningEffort("HIGH"), "high");
|
||||
assert.equal(normalizeXaiReasoningEffort("ultra"), undefined);
|
||||
});
|
||||
|
||||
test("applyThinking: normalizes xAI-native max/xhigh to high", () => {
|
||||
const maxOut = applyThinking({ reasoning: { effort: "max", summary: "auto" } });
|
||||
assert.deepStrictEqual(maxOut.reasoning, { effort: "high", summary: "auto" });
|
||||
|
||||
const xhighOut = applyThinking({ reasoning: { effort: "xhigh" } });
|
||||
assert.deepStrictEqual(xhighOut.reasoning, { effort: "high" });
|
||||
});
|
||||
|
||||
test("applyThinking: rewrites OpenAI Chat reasoning_effort into reasoning.effort", () => {
|
||||
const req = { reasoning_effort: "medium" };
|
||||
const out = applyThinking(req);
|
||||
@@ -74,6 +82,13 @@ test("applyThinking: rewrites OpenAI Chat reasoning_effort into reasoning.effort
|
||||
assert.equal(out.reasoning_effort, undefined);
|
||||
});
|
||||
|
||||
test("applyThinking: rewrites OpenAI Chat max reasoning_effort into high", () => {
|
||||
const req = { reasoning_effort: "max" };
|
||||
const out = applyThinking(req);
|
||||
assert.deepStrictEqual(out.reasoning, { effort: "high" });
|
||||
assert.equal(out.reasoning_effort, undefined);
|
||||
});
|
||||
|
||||
test("applyThinking: ignores invalid reasoning_effort values", () => {
|
||||
const req = { reasoning_effort: "ultra" };
|
||||
const out = applyThinking(req);
|
||||
@@ -138,9 +153,7 @@ test("chatRequestToXaiResponses: converts system message to instructions", () =>
|
||||
test("chatRequestToXaiResponses: converts tool message to function_call_output", () => {
|
||||
const req = {
|
||||
model: "grok-4",
|
||||
messages: [
|
||||
{ role: "tool", content: "result text", tool_call_id: "call_abc" },
|
||||
],
|
||||
messages: [{ role: "tool", content: "result text", tool_call_id: "call_abc" }],
|
||||
};
|
||||
const out = chatRequestToXaiResponses(req);
|
||||
assert.equal(out.input[0].type, "function_call_output");
|
||||
@@ -154,6 +167,18 @@ test("chatRequestToXaiResponses: promotes reasoning_effort to reasoning field",
|
||||
assert.deepStrictEqual(out.reasoning, { effort: "high" });
|
||||
});
|
||||
|
||||
test("chatRequestToXaiResponses: normalizes max reasoning_effort for xAI", () => {
|
||||
const req = { model: "grok-4.3", messages: [], reasoning_effort: "max" };
|
||||
const out = chatRequestToXaiResponses(req);
|
||||
assert.deepStrictEqual(out.reasoning, { effort: "high" });
|
||||
});
|
||||
|
||||
test("chatRequestToXaiResponses: normalizes reasoning.effort for xAI", () => {
|
||||
const req = { model: "grok-4.3", messages: [], reasoning: { effort: "max", summary: "auto" } };
|
||||
const out = chatRequestToXaiResponses(req);
|
||||
assert.deepStrictEqual(out.reasoning, { effort: "high", summary: "auto" });
|
||||
});
|
||||
|
||||
test("chatRequestToXaiResponses: maps max_tokens to max_output_tokens", () => {
|
||||
const req = { model: "grok-4", messages: [], max_tokens: 512 };
|
||||
const out = chatRequestToXaiResponses(req);
|
||||
|
||||
Reference in New Issue
Block a user