feat(grokbuild): curate standalone provider presets

Grok Build previously reused the whole Codex preset list, leaking
cn_official providers and Codex default models into the Grok CLI form.
Replace the inline filter with a standalone, independently maintained
preset module (no data linkage to codexProviderPresets):

- drop cn_official direct providers and open-source-only hosts
  (SiliconFlow/ModelScope/Novita/Nvidia/AtlasCloud/OpenCode Go) that
  have no Grok models upstream
- keep aggregators and third-party relays with grok-4.5 as the default
  model (x-ai/grok-4.5 for OpenRouter-style namespaced routers)
- move the Grok Official seed entry into the presets module
- add standalone integrity tests; retarget the form's chat-mapping test
  since no openai_chat preset remains in the list
This commit is contained in:
Jason
2026-07-21 16:27:14 +08:00
parent a8daf7daad
commit 325ba48486
4 changed files with 700 additions and 45 deletions
@@ -2,7 +2,10 @@ import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { parse as parseToml } from "smol-toml";
import { describe, expect, it, vi } from "vitest";
import { GrokBuildProviderForm } from "@/components/providers/forms/GrokBuildProviderForm";
import {
GrokBuildProviderForm,
grokApiBackendFromApiFormat,
} from "@/components/providers/forms/GrokBuildProviderForm";
vi.mock("@/components/JsonEditor", () => ({
default: ({
@@ -21,7 +24,7 @@ vi.mock("@/components/JsonEditor", () => ({
}));
describe("GrokBuildProviderForm", () => {
it("offers Codex-compatible provider presets and applies one", async () => {
it("offers curated Grok Build presets and applies one", async () => {
const user = userEvent.setup();
const { container } = render(
<GrokBuildProviderForm
@@ -31,6 +34,10 @@ describe("GrokBuildProviderForm", () => {
/>,
);
// 国产官方直连(cn_official)不在 Grok Build 预设列表里
expect(screen.queryByRole("button", { name: /BytePlus/ })).toBeNull();
expect(screen.queryByRole("button", { name: /Kimi/ })).toBeNull();
await user.click(screen.getByRole("button", { name: /PatewayAI/ }));
const baseUrlInput =
@@ -85,7 +92,14 @@ describe("GrokBuildProviderForm", () => {
});
});
it("maps Chat Completions presets into Grok api_backend", async () => {
it("maps preset API formats into Grok api_backend", async () => {
// 预设列表已不含 Chat Completions 条目(国产官方直连被移除),
// chat/messages 映射分支由纯函数覆盖
expect(grokApiBackendFromApiFormat("openai_chat")).toBe("chat_completions");
expect(grokApiBackendFromApiFormat("anthropic")).toBe("messages");
expect(grokApiBackendFromApiFormat("openai_responses")).toBe("responses");
// 组件级接线用带显式 apiFormat 的 Responses 预设验证
const user = userEvent.setup();
const onSubmit = vi.fn();
render(
@@ -96,7 +110,7 @@ describe("GrokBuildProviderForm", () => {
/>,
);
await user.click(screen.getByRole("button", { name: /BytePlus/ }));
await user.click(screen.getByRole("button", { name: /APIKEY\.FUN/ }));
await user.type(screen.getByLabelText("API Key"), "secret-key");
await user.click(screen.getByRole("button", { name: "Save" }));
@@ -104,10 +118,11 @@ describe("GrokBuildProviderForm", () => {
const submitted = onSubmit.mock.calls[0][0];
const settings = JSON.parse(submitted.settingsConfig);
const config = parseToml(settings.config) as any;
expect(submitted.meta.apiFormat).toBe("openai_chat");
expect(config.model[config.models.default].api_backend).toBe(
"chat_completions",
);
expect(submitted.meta.apiFormat).toBe("openai_responses");
const selected = config.model[config.models.default];
expect(selected.api_backend).toBe("responses");
expect(selected.model).toBe("grok-4.5");
expect(selected.base_url).toBe("https://api.apikey.fun/v1");
});
it("renders localized validation feedback for malformed TOML", async () => {