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 { 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, };