mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-05 04:07:36 +08:00
feat: add Grok authentication plugin with OAuth support
- Introduced a new Grok authentication plugin, including essential files such as main.ts, provider.ts, and resources.ts. - Implemented OAuth2 device authorization flow in oauth.ts, allowing users to sign in with xAI. - Added model discovery and quota management functionalities in models.ts and resources.ts. - Created a JSON configuration file (plugin.json) for plugin metadata and permissions. - Included SVG asset for the Grok icon. - Developed comprehensive tests in grok_test.ts to ensure functionality and reliability of the plugin.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
import type { JsonValue, PluginContext } from "cursor-byok:plugin";
|
||||
import type { OAuth2AddMethod, OAuth2Begin, OAuth2Poll } from "cursor-byok:resource";
|
||||
import { credentialDraft } from "./resources.ts";
|
||||
|
||||
const CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
||||
const DEVICE_CODE_URL = "https://auth.x.ai/oauth2/device/code";
|
||||
const TOKEN_URL = "https://auth.x.ai/oauth2/token";
|
||||
const SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
||||
|
||||
type Session = {
|
||||
deviceCode: string;
|
||||
};
|
||||
|
||||
function object(value: unknown): Record<string, unknown> | null {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value)
|
||||
? value as Record<string, unknown>
|
||||
: null;
|
||||
}
|
||||
|
||||
function text(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : null;
|
||||
}
|
||||
|
||||
function number(value: unknown): number | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseBody(body: string): Record<string, unknown> {
|
||||
try {
|
||||
return object(JSON.parse(body)) ?? {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function parseSession(value: JsonValue): Session {
|
||||
const session = object(value);
|
||||
const deviceCode = text(session?.deviceCode);
|
||||
if (!deviceCode) throw new Error("Grok OAuth session is invalid");
|
||||
return { deviceCode };
|
||||
}
|
||||
|
||||
async function begin(context: PluginContext): Promise<OAuth2Begin> {
|
||||
const response = await context.network.fetch(DEVICE_CODE_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({ client_id: CLIENT_ID, scope: SCOPE }).toString(),
|
||||
});
|
||||
const body = parseBody(response.body);
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
throw new Error(
|
||||
`Failed to request xAI device code (HTTP ${response.status}): ${response.body}`,
|
||||
);
|
||||
}
|
||||
const deviceCode = text(body.device_code);
|
||||
const userCode = text(body.user_code);
|
||||
const verificationUrl = text(body.verification_uri);
|
||||
if (!deviceCode || !userCode || !verificationUrl) {
|
||||
throw new Error("xAI device authorization response is incomplete");
|
||||
}
|
||||
const session: Session = { deviceCode };
|
||||
return {
|
||||
session: session as unknown as JsonValue,
|
||||
userCode,
|
||||
verificationUrl,
|
||||
...(text(body.verification_uri_complete)
|
||||
? { verificationUrlComplete: text(body.verification_uri_complete)! }
|
||||
: {}),
|
||||
expiresAtMs: Date.now() + Math.max(1, number(body.expires_in) ?? 900) * 1000,
|
||||
pollIntervalMs: Math.max(1, number(body.interval) ?? 5) * 1000,
|
||||
};
|
||||
}
|
||||
|
||||
async function poll(sessionValue: JsonValue, context: PluginContext): Promise<OAuth2Poll> {
|
||||
const session = parseSession(sessionValue);
|
||||
const response = await context.network.fetch(TOKEN_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
||||
client_id: CLIENT_ID,
|
||||
device_code: session.deviceCode,
|
||||
}).toString(),
|
||||
});
|
||||
const body = parseBody(response.body);
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
const accessToken = text(body.access_token);
|
||||
if (!accessToken) {
|
||||
return { status: "failed", message: "xAI token response is missing access_token" };
|
||||
}
|
||||
return {
|
||||
status: "completed",
|
||||
resources: [
|
||||
await credentialDraft({
|
||||
accessToken,
|
||||
refreshToken: text(body.refresh_token),
|
||||
displayName: null,
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
const code = text(body.error) ?? "";
|
||||
const message = text(body.error_description);
|
||||
switch (code) {
|
||||
case "authorization_pending":
|
||||
return { status: "pending" };
|
||||
case "slow_down":
|
||||
return { status: "slow-down" };
|
||||
case "expired_token":
|
||||
return { status: "failed", message: message ?? "Device authorization code expired" };
|
||||
case "access_denied":
|
||||
return { status: "denied", ...(message ? { message } : {}) };
|
||||
default:
|
||||
return {
|
||||
status: "failed",
|
||||
message: message ??
|
||||
(code
|
||||
? `OAuth error: ${code}`
|
||||
: `xAI device authorization failed (HTTP ${response.status})`),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const grokDeviceOAuth: OAuth2AddMethod = {
|
||||
type: "oauth2.0",
|
||||
id: "xai-device",
|
||||
displayName: {
|
||||
"en-US": "Sign in with xAI",
|
||||
"zh-CN": "使用 xAI 登录",
|
||||
},
|
||||
description: {
|
||||
"en-US": "Authorize this device with xAI, then add the resulting Grok account.",
|
||||
"zh-CN": "在 xAI 完成设备授权后,自动添加对应的 Grok 账号。",
|
||||
},
|
||||
begin,
|
||||
poll,
|
||||
};
|
||||
Reference in New Issue
Block a user