mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
feat(xai): add Grok 4.6 support
This commit is contained in:
@@ -298,7 +298,7 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
|
||||
| Venice | `venice` | `VENICE_API_KEY` | - |
|
||||
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
|
||||
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
|
||||
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | OAuth: `xai/auto`; API key: `xai/grok-4.5` |
|
||||
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | OAuth: `xai/auto`; API key: `xai/grok-4.6` |
|
||||
| Xiaomi | `xiaomi` / `xiaomi-token-plan` | `XIAOMI_API_KEY` / `XIAOMI_TOKEN_PLAN_API_KEY` | `xiaomi/mimo-v2.5` / `xiaomi-token-plan/mimo-v2.5-pro` |
|
||||
|
||||
#### Quirks worth knowing
|
||||
@@ -317,7 +317,7 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
|
||||
Model ids use a `nvidia/<vendor>/<model>` namespace (for example `nvidia/nvidia/nemotron-...`); pickers preserve the literal `<provider>/<model-id>` composition while the canonical key sent to the API stays single-prefixed.
|
||||
</Accordion>
|
||||
<Accordion title="xAI">
|
||||
Uses the xAI Responses path. The recommended path is SuperGrok/X Premium OAuth; fresh setup selects `xai/auto`, which follows xAI's authenticated default model without an OpenClaw update. Existing concrete model ids stay pinned. API keys still work via `XAI_API_KEY` or plugin config and use `grok-4.5` as the setup default. Grok `web_search` reuses the same auth profile before API-key fallback. Older `/fast` and `params.fastMode: true` configurations still resolve through xAI's Grok 4.3 compatibility redirects, but new configurations should select a current model directly. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
|
||||
Uses the xAI Responses path. The recommended path is SuperGrok/X Premium OAuth; fresh setup selects `xai/auto`, which follows xAI's authenticated default model without an OpenClaw update. Existing concrete model ids stay pinned. API keys still work via `XAI_API_KEY` or plugin config and use `grok-4.6` as the setup default. Grok `web_search` reuses the same auth profile before API-key fallback. Older `/fast` and `params.fastMode: true` configurations still resolve through xAI's Grok 4.3 compatibility redirects, but new configurations should select a current model directly. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ cannot be read, account-wide exposure fails closed.
|
||||
- `timeoutSeconds`: scrape request timeout in seconds (default: `60`).
|
||||
- `plugins.entries.xai.config.xSearch`: xAI X Search (Grok web search) settings.
|
||||
- `enabled`: enable the X Search provider.
|
||||
- `model`: Grok model to use for search (e.g. `"grok-4.5"`).
|
||||
- `model`: Grok model to use for search (e.g. `"grok-4.6"`).
|
||||
- `plugins.entries.memory-core.config.dreaming`: memory dreaming settings. See [Dreaming](/concepts/dreaming) for phases and thresholds.
|
||||
- `enabled`: master dreaming switch (default `false`).
|
||||
- `frequency`: cron cadence for each full dreaming sweep (`"0 3 * * *"` by default).
|
||||
|
||||
@@ -603,7 +603,7 @@ Live is opt-in, so there is no fixed "CI model list." `OPENCLAW_LIVE_MODELS=mode
|
||||
| `openrouter/minimax/minimax-m2.7` | |
|
||||
| `opencode-go/glm-5` | |
|
||||
| `openrouter/ai21/jamba-large-1.7` | |
|
||||
| `xai/grok-4.5` | |
|
||||
| `xai/grok-4.6` | |
|
||||
| `xai/grok-4.20-0309-reasoning` | |
|
||||
| `zai/glm-5.1` | |
|
||||
| `fireworks/accounts/fireworks/models/glm-5p1` | |
|
||||
|
||||
+12
-11
@@ -56,7 +56,7 @@ OAuth client.
|
||||
</Step>
|
||||
<Step title="API-key path">
|
||||
API-key setup still works for xAI Console keys and for media surfaces
|
||||
that need key-backed provider config. It uses Grok 4.5 as the setup
|
||||
that need key-backed provider config. It uses Grok 4.6 as the setup
|
||||
default:
|
||||
|
||||
```bash
|
||||
@@ -115,6 +115,7 @@ see [legacy compatibility and moving aliases](#legacy-compatibility-and-moving-a
|
||||
|
||||
| Family | Model ids |
|
||||
| -------------- | ------------------------------------------------------------ |
|
||||
| Grok 4.6 | `grok-4.6` |
|
||||
| Grok 4.5 | `grok-4.5` (aliases: `grok-4.5-latest`, `grok-build-latest`) |
|
||||
| Grok Build 0.1 | `grok-build-0.1` |
|
||||
| Grok 4.3 | `grok-4.3` (aliases: `grok-4.3-latest`, `grok-latest`) |
|
||||
@@ -122,9 +123,9 @@ see [legacy compatibility and moving aliases](#legacy-compatibility-and-moving-a
|
||||
|
||||
<Tip>
|
||||
Use `xai/auto` to follow xAI's authenticated OAuth default, or select a concrete
|
||||
id such as `xai/grok-4.5` to remain pinned. API-key setup uses Grok 4.5 by
|
||||
default; `grok-build-0.1`, Grok 4.3, and both dated Grok 4.20 variants remain
|
||||
selectable.
|
||||
id such as `xai/grok-4.6` to remain pinned. API-key setup uses Grok 4.6 by
|
||||
default; Grok 4.5, `grok-build-0.1`, Grok 4.3, and both dated Grok 4.20 variants
|
||||
remain selectable.
|
||||
</Tip>
|
||||
|
||||
Catalog context and token-cost metadata follows xAI's live
|
||||
@@ -537,7 +538,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
|
||||
| Key | Type | Default | Description |
|
||||
| ----------------- | ------- | ------------------------- | ------------------------------------------------ |
|
||||
| `enabled` | boolean | Automatic for xAI models | Disable, or opt in for a known non-xAI provider |
|
||||
| `model` | string | `grok-4.5` | Model used for x_search requests |
|
||||
| `model` | string | `grok-4.6` | Model used for x_search requests |
|
||||
| `baseUrl` | string | - | xAI Responses base URL override |
|
||||
| `inlineCitations` | boolean | - | Include inline citations in results |
|
||||
| `maxTurns` | number | - | Maximum conversation turns |
|
||||
@@ -552,7 +553,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
|
||||
config: {
|
||||
xSearch: {
|
||||
enabled: true,
|
||||
model: "grok-4.5",
|
||||
model: "grok-4.6",
|
||||
baseUrl: "https://api.x.ai/v1",
|
||||
inlineCitations: true,
|
||||
},
|
||||
@@ -574,7 +575,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
|
||||
| Key | Type | Default | Description |
|
||||
| ---------------- | ------- | ------------------------ | ------------------------------------------------ |
|
||||
| `enabled` | boolean | Automatic for xAI models | Disable, or opt in for a known non-xAI provider |
|
||||
| `model` | string | `grok-4.5` | Model used for code execution requests |
|
||||
| `model` | string | `grok-4.6` | Model used for code execution requests |
|
||||
| `maxTurns` | number | - | Maximum conversation turns |
|
||||
| `timeoutSeconds` | number | `30` | Request timeout in seconds |
|
||||
|
||||
@@ -590,7 +591,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
|
||||
config: {
|
||||
codeExecution: {
|
||||
enabled: true,
|
||||
model: "grok-4.5",
|
||||
model: "grok-4.6",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -628,8 +629,8 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
|
||||
to disable it.
|
||||
- The bundled xAI wrapper strips unsupported contains-count schema bounds
|
||||
and unsupported reasoning *effort* payload keys before sending native
|
||||
xAI requests. Grok 4.5 supports low, medium, and
|
||||
high effort (default high). Grok 4.3 supports none, low, medium, and high
|
||||
xAI requests. Grok 4.6 and Grok 4.5 support low, medium, and high effort
|
||||
(default high). Grok 4.3 supports none, low, medium, and high
|
||||
effort (default low). Other reasoning-capable xAI models do not expose a
|
||||
configurable effort control, but still request
|
||||
`include: ["reasoning.encrypted_content"]` so prior encrypted reasoning
|
||||
@@ -659,7 +660,7 @@ OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_TEST_QUIET=1 pnpm test:live -- extensions/xai
|
||||
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_XAI_VIDEO=1 pnpm test:live -- extensions/xai/xai.live.test.ts -t "classic Grok Imagine"
|
||||
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_XAI_VIDEO=1 pnpm test:live -- extensions/xai/xai.live.test.ts -t "Grok Imagine Video 1.5"
|
||||
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_TEST_QUIET=1 pnpm test:live -- extensions/xai/x-search.live.test.ts
|
||||
OPENCLAW_LIVE_GATEWAY_MODELS="xai/grok-4.5,xai/grok-build-0.1,xai/grok-4.3,xai/grok-4.20-0309-reasoning,xai/grok-4.20-0309-non-reasoning" OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0 OPENCLAW_LIVE_GATEWAY_SMOKE=0 pnpm test:live -- src/gateway/gateway-models.profiles.live.test.ts
|
||||
OPENCLAW_LIVE_GATEWAY_MODELS="xai/grok-4.6,xai/grok-4.5,xai/grok-build-0.1,xai/grok-4.3,xai/grok-4.20-0309-reasoning,xai/grok-4.20-0309-non-reasoning" OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0 OPENCLAW_LIVE_GATEWAY_SMOKE=0 pnpm test:live -- src/gateway/gateway-models.profiles.live.test.ts
|
||||
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_TEST_QUIET=1 OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS=xai pnpm test:live -- test/image-generation.runtime.live.test.ts
|
||||
```
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ registered by the bundled `xai` plugin under the `tools` contract.
|
||||
| Tool name | `code_execution` |
|
||||
| Provider plugin | `xai` (bundled, `enabledByDefault: true`) |
|
||||
| Auth | xAI auth profile, `XAI_API_KEY`, or `plugins.entries.xai.config.webSearch.apiKey` |
|
||||
| Default model | `grok-4.5` |
|
||||
| Default model | `grok-4.6` |
|
||||
| Default timeout | 30 seconds |
|
||||
| Default `maxTurns` | unset (xAI applies its own internal limit) |
|
||||
|
||||
@@ -100,7 +100,7 @@ For local execution, use [`exec`](/tools/exec) instead.
|
||||
config: {
|
||||
codeExecution: {
|
||||
enabled: true, // required for a known non-xAI model provider
|
||||
model: "grok-4.5", // optional explicit model pin
|
||||
model: "grok-4.6", // optional explicit model pin
|
||||
maxTurns: 2, // optional cap on internal tool turns
|
||||
timeoutSeconds: 30, // request timeout (default: 30)
|
||||
},
|
||||
|
||||
+1
-1
@@ -532,7 +532,7 @@ every provider. xAI credentials are always required.
|
||||
config: {
|
||||
xSearch: {
|
||||
enabled: true, // required for a known non-xAI model provider
|
||||
model: "grok-4.5",
|
||||
model: "grok-4.6",
|
||||
baseUrl: "https://api.x.ai/v1", // optional, overrides webSearch.baseUrl
|
||||
inlineCitations: false,
|
||||
maxTurns: 2,
|
||||
|
||||
@@ -58,7 +58,7 @@ describe("xai api helpers", () => {
|
||||
providerId: "xai",
|
||||
ctx: {
|
||||
provider,
|
||||
modelId: "grok-4.5",
|
||||
modelId: "grok-4.6",
|
||||
modelRegistry: { find: () => null } as never,
|
||||
providerConfig: { baseUrl: "", models: [] },
|
||||
},
|
||||
|
||||
@@ -155,7 +155,7 @@ describe("xai code_execution tool", () => {
|
||||
expect(mockFetch).toHaveBeenCalled();
|
||||
expect(firstFetchUrl(mockFetch)).toContain("api.x.ai/v1/responses");
|
||||
const body = parseFirstRequestBody(mockFetch);
|
||||
expect(body.model).toBe("grok-4.5");
|
||||
expect(body.model).toBe("grok-4.6");
|
||||
expect(body.store).toBe(false);
|
||||
expect(body.reasoning).toEqual({ effort: "low" });
|
||||
expect(body.max_turns).toBe(2);
|
||||
|
||||
@@ -64,8 +64,8 @@ describe("xAI doctor contract", () => {
|
||||
expect(result.changes).toHaveLength(4);
|
||||
expect(result.config).not.toBe(config);
|
||||
expect(result.config.plugins?.entries?.xai?.config).toEqual({
|
||||
webSearch: { model: "grok-4.5", timeoutSeconds: 60 },
|
||||
xSearch: { model: "grok-4.5", inlineCitations: true },
|
||||
webSearch: { model: "grok-4.6", timeoutSeconds: 60 },
|
||||
xSearch: { model: "grok-4.6", inlineCitations: true },
|
||||
codeExecution: { model: "grok-build-0.1", maxTurns: 2 },
|
||||
});
|
||||
expect(result.config.tools?.web).toEqual(config.tools?.web);
|
||||
|
||||
@@ -206,6 +206,7 @@ describe("xai provider plugin", () => {
|
||||
const fetchGuard: LiveModelCatalogFetchGuard = vi.fn(async () => ({
|
||||
response: Response.json({
|
||||
data: [
|
||||
{ id: "grok-4.6", object: "model" },
|
||||
{ id: "grok-4.5", object: "model" },
|
||||
{ id: "grok-4.20-0309-reasoning", object: "model" },
|
||||
{ id: "grok-4.20-0309-non-reasoning", object: "model" },
|
||||
@@ -222,6 +223,7 @@ describe("xai provider plugin", () => {
|
||||
});
|
||||
|
||||
expect(provider.apiKey).toBe("xai-key");
|
||||
expect(provider.models.map((model) => model.id)).toContain("grok-4.6");
|
||||
expect(provider.models.map((model) => model.id)).toContain("grok-4.5");
|
||||
expect(provider.models.map((model) => model.id)).toContain("grok-4.20-0309-reasoning");
|
||||
expect(provider.models.map((model) => model.id)).toContain("grok-4.20-0309-non-reasoning");
|
||||
@@ -802,7 +804,7 @@ describe("xai provider plugin", () => {
|
||||
expect(
|
||||
provider.isModernModelRef?.({
|
||||
provider: "xai",
|
||||
modelId: "grok-4.3",
|
||||
modelId: "grok-4.6",
|
||||
} as never),
|
||||
).toBe(true);
|
||||
expect(
|
||||
|
||||
@@ -4,16 +4,16 @@ import {
|
||||
asOptionalRecord,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeXaiModelId } from "./model-id.js";
|
||||
import { isXaiFrontierModelId, normalizeXaiModelId } from "./model-id.js";
|
||||
|
||||
export const XAI_BASE_URL = "https://api.x.ai/v1";
|
||||
export const XAI_DEFAULT_IMAGE_MODEL = "grok-imagine-image";
|
||||
export const XAI_IMAGE_MODELS = ["grok-imagine-image", "grok-imagine-image-quality"] as const;
|
||||
export const XAI_DEFAULT_CONTEXT_WINDOW = 1_000_000;
|
||||
const XAI_GROK_45_CONTEXT_WINDOW = 500_000;
|
||||
const XAI_FRONTIER_CONTEXT_WINDOW = 500_000;
|
||||
const XAI_CODE_CONTEXT_WINDOW = 256_000;
|
||||
export const XAI_DEFAULT_MAX_TOKENS = 64_000;
|
||||
export const XAI_DEFAULT_MODEL_ID = "grok-4.5";
|
||||
export const XAI_DEFAULT_MODEL_ID = "grok-4.6";
|
||||
|
||||
type XaiCost = ModelDefinitionConfig["cost"];
|
||||
|
||||
@@ -54,7 +54,7 @@ const XAI_GROK_43_COST = {
|
||||
cacheWrite: 0,
|
||||
} satisfies XaiCost;
|
||||
|
||||
const XAI_GROK_45_COST = {
|
||||
const XAI_FRONTIER_COST = {
|
||||
input: 2,
|
||||
output: 6,
|
||||
cacheRead: 0.5,
|
||||
@@ -69,12 +69,19 @@ const XAI_GROK_BUILD_COST = {
|
||||
} satisfies XaiCost;
|
||||
|
||||
const XAI_MODEL_CATALOG_ROWS: readonly XaiCatalogModelRow[] = [
|
||||
[
|
||||
"grok-4.6",
|
||||
"Grok 4.6",
|
||||
true,
|
||||
["text", "image"],
|
||||
{ contextWindow: XAI_FRONTIER_CONTEXT_WINDOW, cost: XAI_FRONTIER_COST },
|
||||
],
|
||||
[
|
||||
"grok-4.5",
|
||||
"Grok 4.5",
|
||||
true,
|
||||
["text", "image"],
|
||||
{ contextWindow: XAI_GROK_45_CONTEXT_WINDOW, cost: XAI_GROK_45_COST },
|
||||
{ contextWindow: XAI_FRONTIER_CONTEXT_WINDOW, cost: XAI_FRONTIER_COST },
|
||||
],
|
||||
[
|
||||
"grok-build-0.1",
|
||||
@@ -128,6 +135,7 @@ const XAI_MODEL_CATALOG: readonly XaiCatalogEntry[] = XAI_MODEL_CATALOG_ROWS.map
|
||||
);
|
||||
|
||||
const XAI_SELECTABLE_MODEL_IDS = new Set<string>([
|
||||
"grok-4.6",
|
||||
"grok-4.5",
|
||||
"grok-build-0.1",
|
||||
"grok-4.3",
|
||||
@@ -254,12 +262,12 @@ export function buildXaiModelDefinition(): ModelDefinitionConfig {
|
||||
return toModelDefinition(
|
||||
XAI_MODEL_CATALOG.find((entry) => entry.id === XAI_DEFAULT_MODEL_ID) ?? {
|
||||
id: XAI_DEFAULT_MODEL_ID,
|
||||
name: "Grok 4.5",
|
||||
name: "Grok 4.6",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: XAI_GROK_45_CONTEXT_WINDOW,
|
||||
contextWindow: XAI_FRONTIER_CONTEXT_WINDOW,
|
||||
maxTokens: XAI_DEFAULT_MAX_TOKENS,
|
||||
cost: XAI_GROK_45_COST,
|
||||
cost: XAI_FRONTIER_COST,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -310,7 +318,7 @@ export function resolveXaiCatalogEntry(modelId: string) {
|
||||
});
|
||||
}
|
||||
if (
|
||||
lower.startsWith("grok-4.5") ||
|
||||
isXaiFrontierModelId(lower) ||
|
||||
lower.startsWith("grok-4.3") ||
|
||||
lower.startsWith("grok-4.20") ||
|
||||
lower.startsWith("grok-4-1") ||
|
||||
@@ -321,17 +329,17 @@ export function resolveXaiCatalogEntry(modelId: string) {
|
||||
name: trimmed,
|
||||
reasoning: !lower.includes("non-reasoning"),
|
||||
input: ["text", "image"],
|
||||
contextWindow: lower.startsWith("grok-4.5")
|
||||
? XAI_GROK_45_CONTEXT_WINDOW
|
||||
contextWindow: isXaiFrontierModelId(lower)
|
||||
? XAI_FRONTIER_CONTEXT_WINDOW
|
||||
: XAI_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens:
|
||||
lower.startsWith("grok-4.5") || lower.startsWith("grok-4.3")
|
||||
isXaiFrontierModelId(lower) || lower.startsWith("grok-4.3")
|
||||
? XAI_DEFAULT_MAX_TOKENS
|
||||
: lower.startsWith("grok-4.20")
|
||||
? 30_000
|
||||
: XAI_DEFAULT_MAX_TOKENS,
|
||||
cost: lower.startsWith("grok-4.5")
|
||||
? XAI_GROK_45_COST
|
||||
cost: isXaiFrontierModelId(lower)
|
||||
? XAI_FRONTIER_COST
|
||||
: lower.startsWith("grok-4.3")
|
||||
? XAI_GROK_43_COST
|
||||
: lower.startsWith("grok-4.20")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Xai tests cover model id plugin behavior.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeXaiModelId } from "./api.js";
|
||||
import { isXaiFrontierModelId } from "./model-id.js";
|
||||
|
||||
describe("normalizeXaiModelId", () => {
|
||||
it("normalizes family-specific aliases but preserves the global alias", () => {
|
||||
@@ -33,10 +34,18 @@ describe("normalizeXaiModelId", () => {
|
||||
});
|
||||
|
||||
it("leaves current xai model ids unchanged", () => {
|
||||
expect(normalizeXaiModelId("grok-4.6")).toBe("grok-4.6");
|
||||
expect(normalizeXaiModelId("grok-4.20-beta-latest-reasoning")).toBe(
|
||||
"grok-4.20-beta-latest-reasoning",
|
||||
);
|
||||
expect(normalizeXaiModelId("grok-4.20-0309-reasoning")).toBe("grok-4.20-0309-reasoning");
|
||||
expect(normalizeXaiModelId("grok-4")).toBe("grok-4");
|
||||
});
|
||||
|
||||
it("identifies current frontier model ids and aliases", () => {
|
||||
expect(isXaiFrontierModelId("grok-4.6")).toBe(true);
|
||||
expect(isXaiFrontierModelId("grok-4.5-latest")).toBe(true);
|
||||
expect(isXaiFrontierModelId("grok-build-latest")).toBe(true);
|
||||
expect(isXaiFrontierModelId("grok-4.3")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
// Xai plugin module implements model id behavior.
|
||||
export const XAI_OAUTH_AUTO_MODEL_ID = "auto";
|
||||
|
||||
export function isXaiFrontierModelId(id: string): boolean {
|
||||
const normalized = normalizeXaiModelId(id.trim().toLowerCase());
|
||||
return ["grok-4.6", "grok-4.5"].some(
|
||||
(prefix) => normalized === prefix || normalized.startsWith(`${prefix}-`),
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeXaiModelId(id: string): string {
|
||||
if (id === "grok-4.3-latest") {
|
||||
return "grok-4.3";
|
||||
|
||||
@@ -22,7 +22,7 @@ describe("xai onboard", () => {
|
||||
const cfg = applyXaiConfig({});
|
||||
expect(cfg.models?.providers?.xai?.baseUrl).toBe("https://api.x.ai/v1");
|
||||
expect(cfg.models?.providers?.xai?.api).toBe("openai-responses");
|
||||
expect(XAI_DEFAULT_MODEL_REF).toBe("xai/grok-4.5");
|
||||
expect(XAI_DEFAULT_MODEL_REF).toBe("xai/grok-4.6");
|
||||
expect(resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model)).toBe(XAI_DEFAULT_MODEL_REF);
|
||||
});
|
||||
|
||||
@@ -77,6 +77,7 @@ describe("xai onboard", () => {
|
||||
"grok-3",
|
||||
"grok-code-fast-1",
|
||||
"grok-4.20-beta-latest-reasoning",
|
||||
"grok-4.6",
|
||||
"grok-4.5",
|
||||
"grok-build-0.1",
|
||||
"grok-4.3",
|
||||
@@ -96,6 +97,7 @@ describe("xai onboard", () => {
|
||||
expect(cfg.models?.providers?.xai?.baseUrl).toBe("https://api.x.ai/v1");
|
||||
expect(cfg.models?.providers?.xai?.api).toBe("openai-responses");
|
||||
expect(cfg.models?.providers?.xai?.models.map((m) => m.id)).toEqual([
|
||||
"grok-4.6",
|
||||
"grok-4.5",
|
||||
"grok-build-0.1",
|
||||
"grok-4.3",
|
||||
|
||||
@@ -12,7 +12,13 @@ import { resolveXaiCatalogEntry, XAI_BASE_URL } from "./model-definitions.js";
|
||||
import { normalizeXaiModelId, XAI_OAUTH_AUTO_MODEL_ID } from "./model-id.js";
|
||||
import { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js";
|
||||
|
||||
const XAI_MODERN_MODEL_PREFIXES = ["grok-4.5", "grok-build-0.1", "grok-4.3", "grok-4.20"] as const;
|
||||
const XAI_MODERN_MODEL_PREFIXES = [
|
||||
"grok-4.6",
|
||||
"grok-4.5",
|
||||
"grok-build-0.1",
|
||||
"grok-4.3",
|
||||
"grok-4.20",
|
||||
] as const;
|
||||
|
||||
export function isModernXaiModel(modelId: string): boolean {
|
||||
const normalized = normalizeXaiModelId(modelId.trim());
|
||||
|
||||
@@ -27,9 +27,11 @@ describe("xai provider thinking policy", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
["xai", "grok-4.6"],
|
||||
["xai", "grok-4.5"],
|
||||
["xai", "grok-4.5-latest"],
|
||||
["xai", "grok-build-latest"],
|
||||
["x-ai", "grok-4.6"],
|
||||
["x-ai", "grok-4.5"],
|
||||
["x-ai", "grok-4.5-latest"],
|
||||
["x-ai", "grok-build-latest"],
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
ProviderThinkingProfile,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { resolveXaiCatalogEntry } from "./model-definitions.js";
|
||||
import { normalizeXaiModelId } from "./model-id.js";
|
||||
import { isXaiFrontierModelId, normalizeXaiModelId } from "./model-id.js";
|
||||
import { isXaiProviderId } from "./provider-id.js";
|
||||
|
||||
export function resolveThinkingProfile(
|
||||
@@ -15,8 +15,7 @@ export function resolveThinkingProfile(
|
||||
return { levels: [{ id: "off" }], defaultLevel: "off" };
|
||||
}
|
||||
const modelId = normalizeXaiModelId(ctx.modelId.trim().toLowerCase());
|
||||
const isGrok45 = modelId === "grok-4.5" || modelId.startsWith("grok-4.5-");
|
||||
if (isGrok45) {
|
||||
if (isXaiFrontierModelId(modelId)) {
|
||||
return {
|
||||
levels: [{ id: "low" }, { id: "medium" }, { id: "high" }],
|
||||
defaultLevel: "high",
|
||||
|
||||
@@ -24,26 +24,29 @@ describe("xai runtime model compat", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("maps Grok 4.5 thinking levels to its supported reasoning efforts", () => {
|
||||
const model = applyXaiRuntimeModelCompat({
|
||||
id: "grok-4.5",
|
||||
provider: "xai",
|
||||
reasoning: true,
|
||||
});
|
||||
it.each(["grok-4.6", "grok-4.5"])(
|
||||
"maps %s thinking levels to its supported reasoning efforts",
|
||||
(modelId) => {
|
||||
const model = applyXaiRuntimeModelCompat({
|
||||
id: modelId,
|
||||
provider: "xai",
|
||||
reasoning: true,
|
||||
});
|
||||
|
||||
expect(model.compat).toMatchObject({
|
||||
supportsReasoningEffort: true,
|
||||
supportedReasoningEfforts: ["low", "medium", "high"],
|
||||
});
|
||||
expect(model.thinkingLevelMap).toEqual({
|
||||
off: null,
|
||||
minimal: "low",
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
high: "high",
|
||||
xhigh: "high",
|
||||
});
|
||||
});
|
||||
expect(model.compat).toMatchObject({
|
||||
supportsReasoningEffort: true,
|
||||
supportedReasoningEfforts: ["low", "medium", "high"],
|
||||
});
|
||||
expect(model.thinkingLevelMap).toEqual({
|
||||
off: null,
|
||||
minimal: "low",
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
high: "high",
|
||||
xhigh: "high",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("suppresses reasoning efforts for non-reasoning models", () => {
|
||||
const model = applyXaiRuntimeModelCompat({
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Reasoning effort is configurable only for current flagship Grok models; encrypted reasoning
|
||||
// include/replay is handled separately in stream.ts for every reasoning-capable xAI model.
|
||||
import { applyXaiModelCompat } from "./model-compat.js";
|
||||
import { isXaiFrontierModelId } from "./model-id.js";
|
||||
|
||||
type XaiRuntimeModelCompat = {
|
||||
compat?: unknown;
|
||||
@@ -43,7 +44,7 @@ function normalizeXaiCompatModelId(id: unknown): string {
|
||||
|
||||
function supportsConfigurableXaiReasoningEffort(model: XaiRuntimeModelCompat): boolean {
|
||||
const id = normalizeXaiCompatModelId(model.id);
|
||||
const isConfigurableModel = isGrok43Model(id) || id === "grok-4.5" || id.startsWith("grok-4.5-");
|
||||
const isConfigurableModel = isGrok43Model(id) || isXaiFrontierModelId(id);
|
||||
return model.reasoning === true && isConfigurableModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ function runXaiToolPayloadWrapper(params: {
|
||||
|
||||
async function captureXaiResponsesPayloadWithThinking(
|
||||
reasoning: ModelThinkingLevel = "low",
|
||||
modelId = "grok-4.5",
|
||||
modelId = "grok-4.6",
|
||||
): Promise<Record<string, unknown>> {
|
||||
const model = applyXaiRuntimeModelCompat({
|
||||
api: "openai-responses",
|
||||
@@ -545,7 +545,7 @@ describe("xai stream wrappers", () => {
|
||||
expect(payload.include).toEqual(["reasoning.encrypted_content"]);
|
||||
}, 10_000);
|
||||
|
||||
it("clamps unsupported Grok 4.5 off reasoning to low", async () => {
|
||||
it("clamps unsupported Grok 4.6 off reasoning to low", async () => {
|
||||
const payload = await captureXaiResponsesPayloadWithThinking("off");
|
||||
|
||||
expect(payload.reasoning).toEqual({ effort: "low", summary: "auto" });
|
||||
|
||||
@@ -244,7 +244,7 @@ describe("xai web search config resolution", () => {
|
||||
|
||||
it("prefers configured api keys and resolves grok scoped defaults", () => {
|
||||
expect(resolveXaiWebSearchCredential({ grok: { apiKey: "xai-secret" } })).toBe("xai-secret");
|
||||
expect(resolveXaiWebSearchModel()).toBe("grok-4.5");
|
||||
expect(resolveXaiWebSearchModel()).toBe("grok-4.6");
|
||||
expect(resolveXaiInlineCitations()).toBe(false);
|
||||
});
|
||||
|
||||
@@ -644,8 +644,8 @@ describe("xai web search config resolution", () => {
|
||||
});
|
||||
|
||||
it("uses default model when not specified", () => {
|
||||
expect(resolveXaiWebSearchModel({})).toBe("grok-4.5");
|
||||
expect(resolveXaiWebSearchModel(undefined)).toBe("grok-4.5");
|
||||
expect(resolveXaiWebSearchModel({})).toBe("grok-4.6");
|
||||
expect(resolveXaiWebSearchModel(undefined)).toBe("grok-4.6");
|
||||
});
|
||||
|
||||
it("uses a Grok-specific 60s default timeout while preserving overrides", () => {
|
||||
@@ -676,7 +676,7 @@ describe("xai web search config resolution", () => {
|
||||
|
||||
expect(firstFetchUrl(mockFetch)).toBe("https://api.x.ai/proxy/v1/responses");
|
||||
expect(firstFetchBody(mockFetch)).toMatchObject({
|
||||
model: "grok-4.5",
|
||||
model: "grok-4.6",
|
||||
store: false,
|
||||
reasoning: { effort: "low" },
|
||||
tools: [{ type: "web_search" }],
|
||||
@@ -924,6 +924,7 @@ describe("xai web search response parsing", () => {
|
||||
describe("xai provider models", () => {
|
||||
it("publishes only current selectable chat models newest first", () => {
|
||||
expect(buildXaiCatalogModels().map((model) => model.id)).toEqual([
|
||||
"grok-4.6",
|
||||
"grok-4.5",
|
||||
"grok-build-0.1",
|
||||
"grok-4.3",
|
||||
@@ -932,6 +933,17 @@ describe("xai provider models", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("publishes Grok 4.6 with its current metadata", () => {
|
||||
expectCatalogEntry("grok-4.6", {
|
||||
id: "grok-4.6",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 500_000,
|
||||
maxTokens: 64_000,
|
||||
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("publishes Grok 4.5 with its current metadata", () => {
|
||||
expectCatalogEntry("grok-4.5", {
|
||||
id: "grok-4.5",
|
||||
@@ -1051,6 +1063,7 @@ describe("xai provider models", () => {
|
||||
});
|
||||
|
||||
it("marks current Grok families as modern while excluding multi-agent ids", () => {
|
||||
expect(isModernXaiModel("grok-4.6")).toBe(true);
|
||||
expect(isModernXaiModel("grok-4.5")).toBe(true);
|
||||
expect(isModernXaiModel("grok-4.3")).toBe(true);
|
||||
expect(isModernXaiModel("grok-build-0.1")).toBe(true);
|
||||
|
||||
@@ -67,7 +67,7 @@ describeLive("xai x_search live", () => {
|
||||
|
||||
expect(details.error, details.message).toBeUndefined();
|
||||
expect(details.provider).toBe("xai");
|
||||
expect(details.model).toBe("grok-4.5");
|
||||
expect(details.model).toBe("grok-4.6");
|
||||
expect(details.content?.trim().length ?? 0).toBeGreaterThan(0);
|
||||
|
||||
const citationCount =
|
||||
|
||||
@@ -291,7 +291,7 @@ describe("xai x_search tool", () => {
|
||||
expect(mockFetch).toHaveBeenCalled();
|
||||
expect(firstFetchUrl(mockFetch)).toContain("api.x.ai/v1/responses");
|
||||
const body = parseFirstRequestBody(mockFetch);
|
||||
expect(body.model).toBe("grok-4.5");
|
||||
expect(body.model).toBe("grok-4.6");
|
||||
expect(body.store).toBe(false);
|
||||
expect(body.reasoning).toEqual({ effort: "low" });
|
||||
expect(body.max_turns).toBe(2);
|
||||
|
||||
@@ -222,7 +222,7 @@ describeLive("xai plugin live", () => {
|
||||
usedCodeExecution?: boolean;
|
||||
};
|
||||
|
||||
expect(details.model).toBe("grok-4.5");
|
||||
expect(details.model).toBe("grok-4.6");
|
||||
expect(details.usedCodeExecution).toBe(true);
|
||||
expect(details.content).toContain("42");
|
||||
});
|
||||
@@ -257,7 +257,7 @@ describeLive("xai plugin live", () => {
|
||||
usedCodeExecution?: boolean;
|
||||
};
|
||||
|
||||
expect(details.model).toBe("grok-4.5");
|
||||
expect(details.model).toBe("grok-4.6");
|
||||
expect(details.usedCodeExecution).toBe(true);
|
||||
expect(details.content).toContain("5050");
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ const HIGH_SIGNAL_LIVE_MODEL_PRIORITY = [
|
||||
"openrouter/minimax/minimax-m2.7",
|
||||
"opencode-go/glm-5",
|
||||
"openrouter/ai21/jamba-large-1.7",
|
||||
"xai/grok-4.5",
|
||||
"xai/grok-4.6",
|
||||
"xai/grok-4.20-0309-reasoning",
|
||||
"zai/glm-5.1",
|
||||
"fireworks/accounts/fireworks/models/glm-5p1",
|
||||
|
||||
@@ -646,7 +646,8 @@ describe("isHighSignalLiveModelRef", () => {
|
||||
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.20-0309-reasoning" })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.5" })).toBe(true);
|
||||
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.6" })).toBe(true);
|
||||
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.5" })).toBe(false);
|
||||
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.3" })).toBe(false);
|
||||
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-3" })).toBe(false);
|
||||
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4-1-fast-non-reasoning" })).toBe(
|
||||
@@ -707,7 +708,7 @@ describe("isPrioritizedHighSignalLiveModelRef", () => {
|
||||
{ provider: "openrouter", id: "minimax/minimax-m2.7" },
|
||||
{ provider: "opencode-go", id: "glm-5" },
|
||||
{ provider: "openrouter", id: "ai21/jamba-large-1.7" },
|
||||
{ provider: "xai", id: "grok-4.5" },
|
||||
{ provider: "xai", id: "grok-4.6" },
|
||||
{ provider: "xai", id: "grok-4.20-0309-reasoning" },
|
||||
{ provider: "zai", id: "glm-5.1" },
|
||||
{ provider: "fireworks", id: "accounts/fireworks/models/glm-5p1" },
|
||||
|
||||
@@ -43,23 +43,23 @@ function getToolFunction(tool: Record<string, unknown>): Record<string, unknown>
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveLiveXaiModel(modelId: "grok-4.3" | "grok-4.5") {
|
||||
const isGrok45 = modelId === "grok-4.5";
|
||||
function resolveLiveXaiModel(modelId: "grok-4.3" | "grok-4.5" | "grok-4.6") {
|
||||
const isFrontier = modelId === "grok-4.5" || modelId === "grok-4.6";
|
||||
return {
|
||||
id: modelId,
|
||||
name: isGrok45 ? "Grok 4.5" : "Grok 4.3",
|
||||
name: isFrontier ? `Grok ${modelId.slice("grok-".length)}` : "Grok 4.3",
|
||||
api: "openai-responses",
|
||||
provider: "xai",
|
||||
baseUrl: "https://api.x.ai/v1",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: isGrok45
|
||||
cost: isFrontier
|
||||
? { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 }
|
||||
: { input: 1.25, output: 2.5, cacheRead: 0.2, cacheWrite: 0 },
|
||||
contextWindow: isGrok45 ? 500_000 : 1_000_000,
|
||||
contextWindow: isFrontier ? 500_000 : 1_000_000,
|
||||
maxTokens: 64_000,
|
||||
thinkingLevelMap: {
|
||||
off: isGrok45 ? null : "none",
|
||||
off: isFrontier ? null : "none",
|
||||
minimal: "low",
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
@@ -123,7 +123,7 @@ async function collectDoneMessage(
|
||||
}
|
||||
|
||||
describeLive("xai live", () => {
|
||||
for (const modelId of ["grok-4.3", "grok-4.5"] as const) {
|
||||
for (const modelId of ["grok-4.3", "grok-4.5", "grok-4.6"] as const) {
|
||||
it(
|
||||
`returns assistant text for ${modelId}`,
|
||||
async () => {
|
||||
@@ -150,7 +150,7 @@ describeLive("xai live", () => {
|
||||
);
|
||||
}
|
||||
|
||||
for (const modelId of ["grok-4.3", "grok-4.5"] as const) {
|
||||
for (const modelId of ["grok-4.3", "grok-4.5", "grok-4.6"] as const) {
|
||||
it(`sends wrapped ${modelId} tool payloads live`, async () => {
|
||||
await runXaiLiveCase("tool-call", async () => {
|
||||
const model = requireLiveValue(resolveLiveXaiModel(modelId), "xAI model");
|
||||
|
||||
Reference in New Issue
Block a user