mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-05 03:56:45 +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,110 @@
|
||||
import type { ModelDefinition, ModelSupport } from "cursor-byok:model";
|
||||
import { accountData } from "./resources.ts";
|
||||
|
||||
const LANGUAGE_MODELS_URL = "https://api.x.ai/v1/language-models";
|
||||
const MODELS_URL = "https://api.x.ai/v1/models";
|
||||
|
||||
/** 免费账号无权调用模型列表接口(403 spending-limit);退回已知模型。 */
|
||||
export const FALLBACK_MODELS: ModelDefinition[] = [
|
||||
{
|
||||
id: "grok-4.6",
|
||||
displayName: "Grok 4.6",
|
||||
capabilities: { thinking: false, images: true },
|
||||
},
|
||||
{
|
||||
id: "grok-4.5",
|
||||
displayName: "Grok 4.5",
|
||||
capabilities: { thinking: false, images: true },
|
||||
},
|
||||
];
|
||||
|
||||
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 positiveInteger(value: unknown): number | null {
|
||||
const parsed = typeof value === "number"
|
||||
? value
|
||||
: typeof value === "string"
|
||||
? Number(value)
|
||||
: NaN;
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : null;
|
||||
}
|
||||
|
||||
function modalities(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.flatMap((item) => (typeof item === "string" ? [item.toLowerCase()] : []))
|
||||
: [];
|
||||
}
|
||||
|
||||
/** 把模型 ID 变成可读名称,如 grok-4-fast → Grok 4 Fast。 */
|
||||
function displayName(id: string): string {
|
||||
return id
|
||||
.split("-")
|
||||
.map((part) => (/^\d/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1)))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
/** 兼容 /v1/language-models 的 models 数组与 /v1/models 的 data 数组。 */
|
||||
export function parseGrokModels(body: unknown): ModelDefinition[] {
|
||||
const root = object(body);
|
||||
const source = root?.models ?? root?.data ?? body;
|
||||
if (!Array.isArray(source)) {
|
||||
throw new Error("Grok model discovery response does not contain a model list");
|
||||
}
|
||||
const seen = new Set<string>();
|
||||
const models: ModelDefinition[] = [];
|
||||
for (const raw of source) {
|
||||
const model = object(raw);
|
||||
const id = model ? text(model.id ?? model.name) : null;
|
||||
if (!id || seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
const inputs = modalities(model?.input_modalities ?? model?.inputModalities);
|
||||
const contextWindowTokens = positiveInteger(
|
||||
model?.context_window ?? model?.contextWindow ?? model?.max_prompt_length,
|
||||
);
|
||||
models.push({
|
||||
id,
|
||||
displayName: displayName(id),
|
||||
...(contextWindowTokens !== null ? { contextWindowTokens } : {}),
|
||||
capabilities: {
|
||||
thinking: false,
|
||||
images: inputs.length === 0 || inputs.includes("image"),
|
||||
},
|
||||
});
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
export const grokModels: ModelSupport = {
|
||||
list: async ({ resource }, context): Promise<ModelDefinition[]> => {
|
||||
if (!resource) throw new Error("add a Grok account before syncing models");
|
||||
const data = accountData(resource);
|
||||
const headers = {
|
||||
accept: "application/json",
|
||||
authorization: `Bearer ${data.accessToken}`,
|
||||
};
|
||||
// language-models 带模态与上下文元数据;不可用时回退到标准列表。
|
||||
let response = await context.network.fetch(LANGUAGE_MODELS_URL, { method: "GET", headers });
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
response = await context.network.fetch(MODELS_URL, { method: "GET", headers });
|
||||
}
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
return FALLBACK_MODELS;
|
||||
}
|
||||
let body: unknown;
|
||||
try {
|
||||
body = JSON.parse(response.body);
|
||||
} catch {
|
||||
throw new Error("Grok model discovery returned invalid JSON");
|
||||
}
|
||||
const models = parseGrokModels(body);
|
||||
return models.length > 0 ? models : FALLBACK_MODELS;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user