Files
CC-Switch/tests/config/xaiOauthProviderPresets.test.ts
T
Jason dbb5bd1537 feat(codex): xAI (Grok) OAuth managed provider with native Responses compat
Add a managed "xAI (Grok) OAuth" Codex provider that routes through the
local proxy to api.x.ai via the shared Grok CLI OAuth identity, plus the
native-Responses compatibility layer that makes Codex 0.142+ traffic work
against xAI's strict upstream serde parser.

Provider:
- codex.rs: recognize the xai_oauth placeholder in extract_auth, hard-pin
  the base URL to api.x.ai and the tool profile to native Responses
- forwarder.rs: treat xAI OAuth auth failures as non-retryable
- presets + ProviderForm/CodexFormFields: managed OAuth preset that hides
  the api key/endpoint fields and derives the provider type across apps

Native Responses compatibility (gated on is_xai_oauth, so no other
provider is affected):
- transform_codex_responses_namespace: flatten Codex's private
  namespace/plugin tool declarations into top-level function tools on the
  request; restore the flat function_call names back to {name, namespace}
  on the response (streaming and non-streaming) so the client matches its
  own namespaced tool registry
- transform_codex_responses_xai_sanitize: strip the OpenAI-backend-private
  fields xAI rejects (external_web_access, prompt_cache_retention,
  safety_identifier, the additional_tools carrier, tool_search, ...) with
  deterministic removals that keep the prompt-cache prefix stable
- wire both into the native passthrough after the request transform;
  response restore runs in a dedicated handler so the generic passthrough
  hot path is untouched

Ports the proven approach of sub2api's Grok Responses gateway. Verified
with a 4-round codex -> xAI OAuth workload: all tasks green, zero upstream
errors.
2026-07-21 16:39:34 +08:00

112 lines
3.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { claudeDesktopProviderPresets } from "@/config/claudeDesktopProviderPresets";
import { providerPresets } from "@/config/claudeProviderPresets";
import { codexProviderPresets } from "@/config/codexProviderPresets";
import {
extractCodexBaseUrl,
extractCodexModelName,
extractCodexWireApi,
} from "@/utils/providerConfigUtils";
describe("xAI OAuth provider presets", () => {
it("pins the Claude Code preset to managed Responses auth", () => {
const preset = providerPresets.find((entry) => entry.name === "xAI (Grok)");
expect(preset).toBeDefined();
expect(preset).toMatchObject({
category: "third_party",
apiFormat: "openai_responses",
providerType: "xai_oauth",
requiresOAuth: true,
icon: "xai",
});
expect((preset!.settingsConfig as any).env).toMatchObject({
ANTHROPIC_BASE_URL: "https://api.x.ai/v1",
ANTHROPIC_MODEL: "grok-4.5",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "grok-4.5",
ANTHROPIC_DEFAULT_SONNET_MODEL: "grok-4.5",
ANTHROPIC_DEFAULT_OPUS_MODEL: "grok-4.5",
});
expect((preset!.settingsConfig as any).env).not.toHaveProperty(
"ANTHROPIC_API_KEY",
);
expect((preset!.settingsConfig as any).env).not.toHaveProperty(
"ANTHROPIC_AUTH_TOKEN",
);
});
it("pins the Claude Desktop preset to proxy Responses mode without 1M", () => {
const preset = claudeDesktopProviderPresets.find(
(entry) => entry.name === "xAI (Grok)",
);
expect(preset).toMatchObject({
category: "third_party",
baseUrl: "https://api.x.ai/v1",
mode: "proxy",
apiFormat: "openai_responses",
providerType: "xai_oauth",
requiresOAuth: true,
icon: "xai",
});
expect(preset!.modelRoutes).toEqual([
expect.objectContaining({
upstreamModel: "grok-4.5",
supports1m: false,
}),
]);
});
it("pins the Codex preset to native Responses via API key (no managed OAuth)", () => {
const preset = codexProviderPresets.find(
(entry) => entry.name === "xAI (Grok)",
);
expect(preset).toBeDefined();
expect(preset).toMatchObject({
category: "third_party",
apiFormat: "openai_responses",
icon: "xai",
});
// API-key preset: managed-account OAuth is Claude-side only for now.
expect(preset).not.toHaveProperty("providerType");
expect(preset!.auth).toEqual({ OPENAI_API_KEY: "" });
expect(extractCodexBaseUrl(preset!.config)).toBe("https://api.x.ai/v1");
expect(extractCodexWireApi(preset!.config)).toBe("responses");
expect(extractCodexModelName(preset!.config)).toBe("grok-4.5");
expect(preset!.modelCatalog).toEqual([
expect.objectContaining({
model: "grok-4.5",
contextWindow: 500000,
supportsParallelToolCalls: true,
inputModalities: ["text", "image"],
}),
]);
});
it("pins the Codex OAuth preset to managed native Responses", () => {
const preset = codexProviderPresets.find(
(entry) => entry.name === "xAI (Grok) OAuth",
);
expect(preset).toBeDefined();
expect(preset).toMatchObject({
category: "third_party",
apiFormat: "openai_responses",
providerType: "xai_oauth",
requiresOAuth: true,
icon: "xai",
});
// Managed OAuth: auth.json keeps an empty key; the forwarder injects the
// real access token per request and the adapter pins the base URL.
expect(preset!.auth).toEqual({ OPENAI_API_KEY: "" });
expect(extractCodexBaseUrl(preset!.config)).toBe("https://api.x.ai/v1");
expect(extractCodexWireApi(preset!.config)).toBe("responses");
expect(extractCodexModelName(preset!.config)).toBe("grok-4.5");
expect(preset!.modelCatalog).toEqual([
expect.objectContaining({
model: "grok-4.5",
contextWindow: 500000,
supportsParallelToolCalls: true,
inputModalities: ["text", "image"],
}),
]);
});
});