feat(xai): prefilled OAuth device link, SuperGrok login label, trimmed model list (#6734)

- Use verification_uri_complete from the xAI device-code response so the
  login link opens with the user code prefilled (https-validated)
- Add OAuthAuth.loginLabel and show "Sign in with SuperGrok or X Premium"
  in the xAI auth-type selector (threaded through lazyOAuth)
- Remove grok-3, grok-3-fast, grok-4.20 variants and grok-code-fast-1
  from the built-in xAI catalog and default to grok-4.5

Co-authored-by: Jaaneek <Jaaneek@users.noreply.github.com>
This commit is contained in:
Milosz Jankiewicz
2026-07-16 17:59:54 +01:00
committed by GitHub
parent c2c32febb0
commit a01baaaea7
15 changed files with 75 additions and 102 deletions
+2 -1
View File
@@ -31,7 +31,7 @@ export function envApiKeyAuth(name: string, envVars: readonly string[]): ApiKeyA
* of bundles by loading through a bundler-opaque dynamic import (variable
* specifier, see the bedrock lazy wrapper).
*/
export function lazyOAuth(input: { name: string; load: () => Promise<OAuthAuth> }): OAuthAuth {
export function lazyOAuth(input: { name: string; loginLabel?: string; load: () => Promise<OAuthAuth> }): OAuthAuth {
let promise: Promise<OAuthAuth> | undefined;
const loaded = () => {
promise ??= input.load();
@@ -39,6 +39,7 @@ export function lazyOAuth(input: { name: string; load: () => Promise<OAuthAuth>
};
return {
name: input.name,
loginLabel: input.loginLabel,
login: async (interaction) => (await loaded()).login(interaction),
refresh: async (credential) => (await loaded()).refresh(credential),
toAuth: async (credential) => (await loaded()).toAuth(credential),
+8 -1
View File
@@ -25,6 +25,7 @@ type XaiDeviceCode = {
deviceCode: string;
userCode: string;
verificationUri: string;
verificationUriComplete?: string;
intervalSeconds?: number;
expiresInSeconds: number;
};
@@ -110,10 +111,15 @@ function parseDeviceCode(body: JsonObject): XaiDeviceCode {
const interval = body.interval;
const intervalSeconds =
typeof interval === "number" && Number.isFinite(interval) && interval > 0 ? interval : undefined;
const verificationUriComplete =
typeof body.verification_uri_complete === "string" && body.verification_uri_complete.length > 0
? validateVerificationUri(body.verification_uri_complete)
: undefined;
return {
deviceCode: requiredString(body, "device_code"),
userCode: requiredString(body, "user_code"),
verificationUri: validateVerificationUri(requiredString(body, "verification_uri")),
verificationUriComplete,
intervalSeconds,
expiresInSeconds: positiveNumber(body, "expires_in"),
};
@@ -197,7 +203,7 @@ async function loginXai(interaction: AuthInteraction): Promise<OAuthCredential>
interaction.notify({
type: "device_code",
userCode: device.userCode,
verificationUri: device.verificationUri,
verificationUri: device.verificationUriComplete ?? device.verificationUri,
intervalSeconds: device.intervalSeconds,
expiresInSeconds: device.expiresInSeconds,
});
@@ -222,6 +228,7 @@ async function refreshXaiToken(refreshToken: string, signal?: AbortSignal): Prom
export const xaiOAuth: OAuthAuth = {
name: "xAI (Grok/X subscription)",
loginLabel: "Sign in with SuperGrok or X Premium",
login: loginXai,
refresh: (credential, signal) => refreshXaiToken(credential.refresh, signal),
+3
View File
@@ -190,6 +190,9 @@ export interface OAuthAuth {
/** Display name, e.g. "Anthropic (Claude Pro/Max)". */
name: string;
/** Selector label for the subscription login option, e.g. "Sign in with SuperGrok or X Premium". */
loginLabel?: string;
login(interaction: AuthInteraction): Promise<OAuthCredential>;
/**
-90
View File
@@ -4,78 +4,6 @@
import type { Model } from "../types.ts";
export const XAI_MODELS = {
"grok-3": {
id: "grok-3",
name: "Grok 3",
api: "openai-completions",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
reasoning: false,
input: ["text"],
cost: {
input: 3,
output: 15,
cacheRead: 0.75,
cacheWrite: 0,
},
contextWindow: 131072,
maxTokens: 8192,
} satisfies Model<"openai-completions">,
"grok-3-fast": {
id: "grok-3-fast",
name: "Grok 3 Fast",
api: "openai-completions",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
reasoning: false,
input: ["text"],
cost: {
input: 5,
output: 25,
cacheRead: 1.25,
cacheWrite: 0,
},
contextWindow: 131072,
maxTokens: 8192,
} satisfies Model<"openai-completions">,
"grok-4.20-0309-non-reasoning": {
id: "grok-4.20-0309-non-reasoning",
name: "Grok 4.20 (Non-Reasoning)",
api: "openai-completions",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
reasoning: false,
input: ["text", "image"],
cost: {
input: 1.25,
output: 2.5,
cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
maxTokens: 30000,
} satisfies Model<"openai-completions">,
"grok-4.20-0309-reasoning": {
id: "grok-4.20-0309-reasoning",
name: "Grok 4.20 (Reasoning)",
api: "openai-completions",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
reasoning: true,
input: ["text", "image"],
cost: {
input: 1.25,
output: 2.5,
cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
maxTokens: 30000,
} satisfies Model<"openai-completions">,
"grok-4.3": {
id: "grok-4.3",
name: "Grok 4.3",
@@ -131,22 +59,4 @@ export const XAI_MODELS = {
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"openai-completions">,
"grok-code-fast-1": {
id: "grok-code-fast-1",
name: "Grok Code Fast 1",
api: "openai-completions",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
reasoning: false,
input: ["text"],
cost: {
input: 0.2,
output: 1.5,
cacheRead: 0.02,
cacheWrite: 0,
},
contextWindow: 32768,
maxTokens: 8192,
} satisfies Model<"openai-completions">,
} as const;
+5 -1
View File
@@ -12,7 +12,11 @@ export function xaiProvider(): Provider<"openai-completions" | "openai-responses
baseUrl: "https://api.x.ai/v1",
auth: {
apiKey: envApiKeyAuth("xAI API key", ["XAI_API_KEY"]),
oauth: lazyOAuth({ name: "xAI (Grok/X subscription)", load: loadXaiOAuth }),
oauth: lazyOAuth({
name: "xAI (Grok/X subscription)",
loginLabel: "Sign in with SuperGrok or X Premium",
load: loadXaiOAuth,
}),
},
models: Object.values(XAI_MODELS),
api: {
+1 -1
View File
@@ -263,7 +263,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!process.env.XAI_API_KEY)("xAI", () => {
it("grok-3-fast - should detect overflow via isContextOverflow", async () => {
const model = getModel("xai", "grok-3-fast");
const model = getModel("xai", "grok-4.3");
const result = await testContextOverflow(model, process.env.XAI_API_KEY!);
logResult(result);
+1 -1
View File
@@ -247,7 +247,7 @@ describe("AI Providers Empty Message Tests", () => {
});
describe.skipIf(!process.env.XAI_API_KEY)("xAI Provider Empty Messages", () => {
const llm = getModel("xai", "grok-3");
const llm = getModel("xai", "grok-4.3");
it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => {
await testEmptyMessage(llm);
+1 -1
View File
@@ -543,7 +543,7 @@ describe("Generate E2E Tests", () => {
});
describe.skipIf(!process.env.XAI_API_KEY)("xAI Provider (grok-code-fast-1 via OpenAI Completions)", () => {
const llm = getModel("xai", "grok-code-fast-1");
const llm = getModel("xai", "grok-4.3");
it("should complete basic text generation", { retry: 3 }, async () => {
await basicTextGeneration(llm);
+1 -1
View File
@@ -132,7 +132,7 @@ describe("Token Statistics on Abort", () => {
});
describe.skipIf(!process.env.XAI_API_KEY)("xAI Provider", () => {
const llm = getModel("xai", "grok-3-fast");
const llm = getModel("xai", "grok-4.3");
it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => {
await testTokensOnAbort(llm);
@@ -143,7 +143,7 @@ describe("Tool Call Without Result Tests", () => {
});
describe.skipIf(!process.env.XAI_API_KEY)("xAI Provider", () => {
const model = getModel("xai", "grok-3-fast");
const model = getModel("xai", "grok-4.3");
it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => {
await testToolCallWithoutResult(model);
+1 -1
View File
@@ -248,7 +248,7 @@ describe("totalTokens field", () => {
"grok-3-fast - should return totalTokens equal to sum of components",
{ retry: 3, timeout: 60000 },
async () => {
const llm = getModel("xai", "grok-3-fast");
const llm = getModel("xai", "grok-4.3");
console.log(`\nxAI / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.XAI_API_KEY });
+1 -1
View File
@@ -450,7 +450,7 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => {
});
describe.skipIf(!process.env.XAI_API_KEY)("xAI Provider Unicode Handling", () => {
const llm = getModel("xai", "grok-3");
const llm = getModel("xai", "grok-4.3");
it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => {
await testEmojiInToolResults(llm);
+45
View File
@@ -173,6 +173,51 @@ describe("xAI OAuth device flow", () => {
expect(pollTimes).toEqual([startTime.getTime() + 5000]);
});
it("prefers verification_uri_complete when the server provides it", async () => {
vi.useFakeTimers();
vi.stubGlobal(
"fetch",
vi.fn(async (input: unknown) => {
if (requestUrl(input) === "https://auth.x.ai/oauth2/device/code") {
return jsonResponse(
deviceCodeResponse({
verification_uri_complete: "https://accounts.x.ai/oauth2/device?user_code=ABCD-1234",
}),
);
}
return jsonResponse(tokenResponse());
}),
);
const deviceCodes: DeviceCodeInfo[] = [];
const loginPromise = loginXaiForTest({ onDeviceCode: (info) => deviceCodes.push(info) });
await vi.advanceTimersByTimeAsync(5000);
await loginPromise;
expect(deviceCodes).toEqual([
{
userCode: "ABCD-1234",
verificationUri: "https://accounts.x.ai/oauth2/device?user_code=ABCD-1234",
intervalSeconds: 5,
expiresInSeconds: 900,
},
]);
});
it("rejects a non-https verification_uri_complete", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () =>
jsonResponse(
deviceCodeResponse({
verification_uri_complete: "http://accounts.x.ai/oauth2/device?user_code=ABCD-1234",
}),
),
),
);
await expect(loginXaiForTest({ onDeviceCode: () => {} })).rejects.toThrow("Untrusted verification URI");
});
it.each(["http://accounts.x.ai/oauth2/device", "file:///etc/passwd", "not a url"])(
"rejects a non-https verification URI: %s",
async (verificationUri) => {
@@ -26,7 +26,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
"github-copilot": "gpt-5.4",
openrouter: "moonshotai/kimi-k2.6",
"vercel-ai-gateway": "zai/glm-5.1",
xai: "grok-4.20-0309-reasoning",
xai: "grok-4.5",
groq: "openai/gpt-oss-120b",
cerebras: "zai-glm-4.7",
zai: "glm-5.1",
@@ -4877,7 +4877,10 @@ export class InteractiveMode {
}
private showLoginAuthTypeSelector(providerOptions?: AuthSelectorProvider[]): void {
const subscriptionLabel = "Sign in with an account";
const oauthProvider = providerOptions?.find((provider) => provider.authType === "oauth");
const oauthLoginLabel =
oauthProvider?.method && "loginLabel" in oauthProvider.method ? oauthProvider.method.loginLabel : undefined;
const subscriptionLabel = oauthLoginLabel ?? "Sign in with an account";
const apiKeyLabel = "Sign in with an API key";
const availableAuthTypes = providerOptions
? new Set(providerOptions.map((provider) => provider.authType))