mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-07 06:04:53 +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,112 @@
|
||||
import type {
|
||||
ProviderInvokeInput,
|
||||
ProviderOutput,
|
||||
ProviderResult,
|
||||
ProviderSupport,
|
||||
} from "cursor-byok:provider";
|
||||
import type { PluginContext } from "cursor-byok:plugin";
|
||||
import { HttpError, streamOpenAiChat } from "cursor-byok:protocol/openai-chat";
|
||||
import { grokModels } from "./models.ts";
|
||||
import { type AccountData, accountData, quotaExhaustedPatch, RESOURCE_TYPE } from "./resources.ts";
|
||||
|
||||
const CHAT_URL = "https://api.x.ai/v1/chat/completions";
|
||||
|
||||
/** 流内错误只有文本可用,按积分/额度关键词分类。 */
|
||||
export function isQuotaError(error: string): boolean {
|
||||
const message = error.toLowerCase();
|
||||
return message.includes("insufficient_quota") ||
|
||||
message.includes("credits exhausted") ||
|
||||
message.includes("out of credits") ||
|
||||
message.includes("quota_exceeded") ||
|
||||
(message.includes("429") &&
|
||||
(message.includes("quota") || message.includes("credit") ||
|
||||
message.includes("insufficient")));
|
||||
}
|
||||
|
||||
/** HTTP 失败携带结构化状态码,429 一律按额度耗尽处理并冷却账号。 */
|
||||
function isQuotaHttpError(error: HttpError): boolean {
|
||||
if (error.status === 429) return true;
|
||||
const body = error.body.toLowerCase();
|
||||
return body.includes("insufficient_quota") ||
|
||||
body.includes("credits exhausted") ||
|
||||
body.includes("out of credits") ||
|
||||
// 免费账号触达消费上限时返回 403 spending-limit,属于额度而非授权问题。
|
||||
body.includes("spending-limit") ||
|
||||
body.includes("run out of credits") ||
|
||||
body.includes("quota_exceeded");
|
||||
}
|
||||
|
||||
function invalidResult(message: string, stateMessage: string): ProviderResult {
|
||||
return {
|
||||
status: "resource-error",
|
||||
message,
|
||||
patch: { state: { status: "invalid", message: stateMessage } },
|
||||
};
|
||||
}
|
||||
|
||||
async function invoke(
|
||||
input: ProviderInvokeInput,
|
||||
output: ProviderOutput,
|
||||
context: PluginContext,
|
||||
): Promise<ProviderResult> {
|
||||
if (!input.resource) {
|
||||
return { status: "request-error", message: "add a Grok account before calling Grok" };
|
||||
}
|
||||
let data: AccountData;
|
||||
try {
|
||||
data = accountData(input.resource);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return invalidResult(message, message);
|
||||
}
|
||||
try {
|
||||
await streamOpenAiChat(
|
||||
{
|
||||
url: CHAT_URL,
|
||||
model: input.model.id,
|
||||
// xAI 不接受 reasoning_effort 与 service_tier;思考由模型自身决定。
|
||||
request: {
|
||||
...input.request,
|
||||
reasoning: { enabled: false, effort: null },
|
||||
latency: "standard",
|
||||
},
|
||||
headers: { authorization: `Bearer ${data.accessToken}` },
|
||||
},
|
||||
output,
|
||||
context,
|
||||
);
|
||||
return { status: "completed" };
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
if ((error.status === 401 || error.status === 403) && !isQuotaHttpError(error)) {
|
||||
return invalidResult(error.message, "Grok authorization expired; sign in again");
|
||||
}
|
||||
if (isQuotaHttpError(error)) {
|
||||
return {
|
||||
status: "resource-error",
|
||||
message: error.message,
|
||||
patch: quotaExhaustedPatch(data),
|
||||
};
|
||||
}
|
||||
return { status: "request-error", message: error.message };
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (isQuotaError(message)) {
|
||||
return { status: "resource-error", message, patch: quotaExhaustedPatch(data) };
|
||||
}
|
||||
return { status: "request-error", message };
|
||||
}
|
||||
}
|
||||
|
||||
export const grokProvider: ProviderSupport = {
|
||||
id: "grok",
|
||||
displayName: "xAI Grok",
|
||||
description: {
|
||||
"en-US": "SuperGrok subscription access through the official Grok CLI endpoint.",
|
||||
"zh-CN": "通过官方 Grok CLI 接口使用 SuperGrok 订阅。",
|
||||
},
|
||||
providerType: "xai",
|
||||
resourceType: RESOURCE_TYPE,
|
||||
models: grokModels,
|
||||
invoke,
|
||||
};
|
||||
Reference in New Issue
Block a user