Skip to content

Commit 84bcd50

Browse files
feat(xai): route OAuth login through device-code flow (#97249)
Route xAI OAuth through device-code sign-in so remote and headless hosts do not need a localhost callback. Preserve the legacy manual `xai-device-code` auth choice/method as a compatibility alias to the same device-code flow. Also migrate stale xAI token endpoints on refresh and fail fast on structured refresh errors while keeping retries scoped to detected HTML/Cloudflare challenge responses. Verification: - `node scripts/run-vitest.mjs extensions/xai/index.test.ts extensions/xai/xai-oauth.test.ts` - `node scripts/run-vitest.mjs src/cli/models-cli.test.ts -t 'maps --device-code'` - `node scripts/run-vitest.mjs src/commands/auth-choice.test.ts -t 'removed provider auth choice'` - Crabbox local-container live smoke on exact head `fef3cb24afb01cd1f69cf04ef67ed11d71dfadb3`: xAI discovery and device authorization returned 200. - `$autoreview` after the live smoke: clean. Co-authored-by: Jaaneek <[email protected]> Co-authored-by: fuller-stack-dev <[email protected]>
1 parent f857e8d commit 84bcd50

6 files changed

Lines changed: 353 additions & 394 deletions

File tree

docs/providers/xai.md

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ Use the path that matches your OpenClaw install state:
2929
openclaw onboard --install-daemon
3030
```
3131

32-
On a VPS or over SSH, use device-code during onboarding:
32+
On a VPS or over SSH, select xAI OAuth directly; OpenClaw uses device-code
33+
verification and does not require a localhost callback:
3334

3435
```bash
35-
openclaw onboard --install-daemon --auth-choice xai-device-code
36+
openclaw onboard --install-daemon --auth-choice xai-oauth
3637
```
3738

3839
OAuth does not require an xAI API key. OpenClaw does not require the Grok
@@ -48,13 +49,6 @@ Use the path that matches your OpenClaw install state:
4849
openclaw models auth login --provider xai --method oauth
4950
```
5051

51-
Use the device-code flow instead when the Gateway runs over SSH, Docker, or
52-
a VPS and a localhost browser callback is awkward:
53-
54-
```bash
55-
openclaw models auth login --provider xai --device-code
56-
```
57-
5852
To make Grok the default model after signing in, apply it separately:
5953

6054
```bash
@@ -86,8 +80,7 @@ Use the path that matches your OpenClaw install state:
8680

8781
<Note>
8882
OpenClaw uses the xAI Responses API as the bundled xAI transport. The same
89-
credential from `openclaw models auth login --provider xai --method oauth`,
90-
`openclaw models auth login --provider xai --device-code`, or
83+
credential from `openclaw models auth login --provider xai --method oauth` or
9184
`openclaw models auth login --provider xai --method api-key` can also power first-class
9285
`web_search`, `x_search`, remote `code_execution`, and xAI image/video generation.
9386
Speech and transcription currently require `XAI_API_KEY` or provider config.
@@ -102,8 +95,9 @@ and, by default, `x_search` through an operator xAI Responses proxy.
10295

10396
## OAuth troubleshooting
10497

105-
- If browser OAuth cannot reach `127.0.0.1:56121`, use
106-
`openclaw models auth login --provider xai --device-code`.
98+
- For SSH, Docker, VPS, or other remote setups, use
99+
`openclaw models auth login --provider xai --method oauth`; xAI OAuth uses
100+
device-code verification instead of a localhost callback.
107101
- If sign-in succeeds but Grok is not the default model, run
108102
`openclaw models set xai/grok-4.3`.
109103
- To inspect saved xAI auth profiles, run:
@@ -117,9 +111,9 @@ and, by default, `x_search` through an operator xAI Responses proxy.
117111
eligible, try the API-key path or check the subscription on xAI's side.
118112

119113
<Tip>
120-
Use `xai-device-code` when signing in from SSH, Docker, or a VPS. OpenClaw
121-
prints an xAI URL and short code; finish sign-in in any local browser while the
122-
remote process polls xAI for the completed token exchange.
114+
Use `xai-oauth` when signing in from SSH, Docker, or a VPS. OpenClaw prints an
115+
xAI URL and short code; finish sign-in in any local browser while the remote
116+
process polls xAI for the completed token exchange.
123117
</Tip>
124118

125119
## Built-in catalog
@@ -498,12 +492,10 @@ Legacy aliases still normalize to the canonical bundled ids:
498492

499493
<Accordion title="Known limits">
500494
- xAI auth can use an API key, environment variable, plugin config fallback,
501-
browser OAuth, or device-code OAuth with an eligible xAI account. Browser
502-
OAuth uses a local callback on `127.0.0.1:56121`; for remote hosts, use
503-
`xai-device-code` unless you want to forward that port before opening the
504-
sign-in URL. xAI decides which accounts can receive OAuth API tokens, and
505-
the consent page may show Grok Build even though OpenClaw does not require
506-
the Grok Build app.
495+
or OAuth with an eligible xAI account. OAuth uses device-code verification
496+
without a localhost callback. xAI decides which accounts can receive OAuth
497+
API tokens, and the consent page may show Grok Build even though OpenClaw
498+
does not require the Grok Build app.
507499
- OpenClaw does not currently expose the xAI multi-agent model family. xAI
508500
serves these models through the Responses API, but they do not accept the
509501
client-side or custom tools used by OpenClaw's shared agent loop. See the

docs/tools/code-execution.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,21 @@ Do **not** use it when you need local files, your shell, your repo, or paired de
3838
<Steps>
3939
<Step title="Provide xAI credentials">
4040
Sign in with Grok OAuth using an eligible SuperGrok or X Premium subscription,
41-
use the remote-friendly device-code flow, or store an API key. OAuth works
42-
for `code_execution` and `x_search`; `XAI_API_KEY` or plugin web-search
43-
config can also power Grok `web_search`.
41+
or store an API key. xAI OAuth uses device-code verification, so it works
42+
from remote hosts without a localhost callback. OAuth works for
43+
`code_execution` and `x_search`; `XAI_API_KEY` or plugin web-search config
44+
can also power Grok `web_search`.
4445

4546
```bash
4647
openclaw models auth login --provider xai --method oauth
47-
openclaw models auth login --provider xai --device-code
4848
```
4949

5050
During a fresh install, the same auth choices are available inside
5151
onboarding:
5252

5353
```bash
5454
openclaw onboard --install-daemon
55-
openclaw onboard --install-daemon --auth-choice xai-device-code
55+
openclaw onboard --install-daemon --auth-choice xai-oauth
5656
```
5757

5858
Or use an API key:

extensions/xai/index.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const providerAuthRuntimeMocks = vi.hoisted(() => ({
1818
vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => providerAuthRuntimeMocks);
1919

2020
import plugin from "./index.js";
21+
import manifest from "./openclaw.plugin.json" with { type: "json" };
2122
import { buildLiveXaiProvider } from "./provider-catalog.js";
2223
import setupPlugin from "./setup-api.js";
2324
import {
@@ -82,13 +83,24 @@ describe("xai provider plugin", () => {
8283
vi.unstubAllGlobals();
8384
});
8485

85-
it("exposes OAuth and device-code auth choices", async () => {
86+
it("exposes xAI OAuth and preserves the explicit device-code alias", async () => {
8687
const provider = await registerSingleProviderPlugin(plugin);
8788

8889
expect(provider.auth?.map((method) => method.id)).toEqual(["api-key", "oauth", "device-code"]);
90+
const oauth = provider.auth?.find((method) => method.id === "oauth");
91+
expect(oauth?.kind).toBe("oauth");
92+
expect(oauth?.wizard?.choiceId).toBe("xai-oauth");
8993
const deviceCode = provider.auth?.find((method) => method.id === "device-code");
9094
expect(deviceCode?.kind).toBe("device_code");
9195
expect(deviceCode?.wizard?.choiceId).toBe("xai-device-code");
96+
expect(deviceCode?.wizard?.assistantVisibility).toBe("manual-only");
97+
expect(manifest.providerAuthChoices).toContainEqual(
98+
expect.objectContaining({
99+
assistantVisibility: "manual-only",
100+
choiceId: "xai-device-code",
101+
method: "device-code",
102+
}),
103+
);
92104
});
93105

94106
it("filters the xAI API-key catalog against live model ids", async () => {

extensions/xai/openclaw.plugin.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"choiceLabel": "xAI API key",
8888
"groupId": "xai",
8989
"groupLabel": "xAI (Grok)",
90-
"groupHint": "API key or browser OAuth",
90+
"groupHint": "API key or OAuth",
9191
"onboardingFeatured": true,
9292
"optionKey": "xaiApiKey",
9393
"cliFlag": "--xai-api-key",
@@ -99,22 +99,22 @@
9999
"method": "oauth",
100100
"choiceId": "xai-oauth",
101101
"choiceLabel": "xAI OAuth",
102-
"choiceHint": "Browser sign-in for eligible xAI accounts",
102+
"choiceHint": "Remote-friendly browser sign-in without a localhost callback",
103103
"groupId": "xai",
104104
"groupLabel": "xAI (Grok)",
105-
"groupHint": "API key or browser OAuth",
105+
"groupHint": "API key or OAuth",
106106
"onboardingFeatured": true
107107
},
108108
{
109109
"provider": "xai",
110110
"method": "device-code",
111111
"choiceId": "xai-device-code",
112112
"choiceLabel": "xAI device code",
113-
"choiceHint": "Remote-friendly browser sign-in without a localhost callback",
113+
"choiceHint": "Compatibility alias for xAI OAuth device-code sign-in",
114+
"assistantVisibility": "manual-only",
114115
"groupId": "xai",
115116
"groupLabel": "xAI (Grok)",
116-
"groupHint": "API key or browser OAuth",
117-
"onboardingFeatured": true
117+
"groupHint": "API key or OAuth"
118118
}
119119
],
120120
"uiHints": {

0 commit comments

Comments
 (0)