omk-ai 0.96.0 → 0.96.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/xai.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAYhG,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAmC3C,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,OAAO,CAiB9E;AA0MD,uEAAuE;AACvE,wBAAsB,QAAQ,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgBxF;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2CrF;AAED,eAAO,MAAM,gBAAgB,EAAE,sBAM9B,CAAC","sourcesContent":["/**\n * xAI Grok OAuth (native device authorization flow).\n *\n * RFC 8628 device-authorization grant with OIDC discovery.\n * Uses the official xAI OAuth client id and scopes.\n */\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\nconst XAI_OAUTH_ISSUER = \"https://auth.x.ai\";\nconst XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;\nconst XAI_OAUTH_DEVICE_CODE_URL = `${XAI_OAUTH_ISSUER}/oauth2/device/code`;\nconst XAI_OAUTH_CLIENT_ID = \"b1a00492-073a-47ea-816f-4c329264a828\";\nconst XAI_OAUTH_SCOPE = \"openid profile email offline_access grok-cli:access api:access\";\n\nconst ACCESS_TOKEN_CLIENT_SKEW_MS = 5 * 60 * 1000;\nconst DISCOVERY_TIMEOUT_MS = 15_000;\nconst TOKEN_REQUEST_TIMEOUT_MS = 20_000;\n\nexport const XAI_OAUTH_PROVIDER_ID = \"xai\";\n\ninterface XAIOAuthDiscovery {\n\ttoken_endpoint: string;\n}\n\ninterface XAIDeviceAuthorization {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUriComplete: string;\n\texpiresInSeconds: number;\n\tintervalSeconds: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction validateXAIEndpoint(url: string, field: string): string {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tif (parsed.protocol !== \"https:\") {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tconst host = parsed.hostname.toLowerCase();\n\tif (!host || (host !== \"x.ai\" && !host.endsWith(\".x.ai\"))) {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\treturn url;\n}\n\nexport function isXAIAccessTokenExpiring(jwt: string, skewSeconds = 0): boolean {\n\ttry {\n\t\tif (typeof jwt !== \"string\" || !jwt.includes(\".\")) return false;\n\t\tconst parts = jwt.split(\".\");\n\t\tif (parts.length < 2) return false;\n\t\tconst payloadPart = parts[1];\n\t\tif (!payloadPart) return false;\n\t\tconst decoded = Buffer.from(payloadPart, \"base64url\").toString(\"utf8\");\n\t\tconst payload: unknown = JSON.parse(decoded);\n\t\tif (!isRecord(payload)) return false;\n\t\tconst exp = payload.exp;\n\t\tif (typeof exp !== \"number\" || !Number.isFinite(exp)) return false;\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\treturn exp <= now + Math.max(0, Math.floor(skewSeconds));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function xaiOAuthDiscovery(timeoutMs = DISCOVERY_TIMEOUT_MS): Promise<XAIOAuthDiscovery> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(XAI_OAUTH_DISCOVERY_URL, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`xAI OIDC discovery failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (response.status !== 200) {\n\t\tthrow new Error(`xAI OIDC discovery returned status ${response.status}.`);\n\t}\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI OIDC discovery returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI OIDC discovery response was not a JSON object.\");\n\t}\n\tconst tokenEndpoint = typeof payload.token_endpoint === \"string\" ? payload.token_endpoint.trim() : \"\";\n\tif (!tokenEndpoint) {\n\t\tthrow new Error(\"xAI OIDC discovery response was missing token_endpoint.\");\n\t}\n\tvalidateXAIEndpoint(tokenEndpoint, \"token_endpoint\");\n\treturn { token_endpoint: tokenEndpoint };\n}\n\nfunction parseXAIDeviceAuthorization(payload: unknown): XAIDeviceAuthorization {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI device-code response was not a JSON object.\");\n\t}\n\n\tconst deviceCode = typeof payload.device_code === \"string\" ? payload.device_code.trim() : \"\";\n\tconst userCode = typeof payload.user_code === \"string\" ? payload.user_code.trim() : \"\";\n\tconst verificationUri = typeof payload.verification_uri === \"string\" ? payload.verification_uri.trim() : \"\";\n\tconst verificationUriComplete =\n\t\ttypeof payload.verification_uri_complete === \"string\" ? payload.verification_uri_complete.trim() : \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tconst intervalSeconds = payload.interval;\n\tif (\n\t\t!deviceCode ||\n\t\t!userCode ||\n\t\t!verificationUri ||\n\t\t!verificationUriComplete ||\n\t\ttypeof expiresInSeconds !== \"number\" ||\n\t\t!Number.isFinite(expiresInSeconds) ||\n\t\texpiresInSeconds <= 0 ||\n\t\ttypeof intervalSeconds !== \"number\" ||\n\t\t!Number.isFinite(intervalSeconds) ||\n\t\tintervalSeconds <= 0\n\t) {\n\t\tthrow new Error(\"xAI device-code response missing or invalid required fields.\");\n\t}\n\n\tvalidateXAIEndpoint(verificationUri, \"verification_uri\");\n\tvalidateXAIEndpoint(verificationUriComplete, \"verification_uri_complete\");\n\treturn {\n\t\tdeviceCode,\n\t\tuserCode,\n\t\tverificationUriComplete,\n\t\texpiresInSeconds,\n\t\tintervalSeconds,\n\t};\n}\n\nfunction parseXAITokenResponse(payload: unknown, label: string, refreshTokenFallback?: string): OAuthCredentials {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`${label} was not a JSON object`);\n\t}\n\tconst accessToken = typeof payload.access_token === \"string\" ? payload.access_token : \"\";\n\tconst responseRefreshToken = typeof payload.refresh_token === \"string\" ? payload.refresh_token : \"\";\n\tconst refreshToken = responseRefreshToken || refreshTokenFallback || \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tif (!accessToken) {\n\t\tthrow new Error(`${label} missing access_token`);\n\t}\n\tif (!refreshToken) {\n\t\tthrow new Error(`${label} missing refresh_token`);\n\t}\n\tif (typeof expiresInSeconds !== \"number\" || !Number.isFinite(expiresInSeconds)) {\n\t\tthrow new Error(`${label} missing expires_in`);\n\t}\n\treturn {\n\t\taccess: accessToken,\n\t\trefresh: refreshToken,\n\t\texpires: Date.now() + expiresInSeconds * 1000 - ACCESS_TOKEN_CLIENT_SKEW_MS,\n\t};\n}\n\nasync function requestXAIDeviceAuthorization(signal?: AbortSignal): Promise<XAIDeviceAuthorization> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(XAI_OAUTH_DEVICE_CODE_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tscope: XAI_OAUTH_SCOPE,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(`xAI device-code request failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI device-code request failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code response returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAIDeviceAuthorization(payload);\n}\n\nasync function pollXAIDeviceToken(\n\ttokenEndpoint: string,\n\tdeviceCode: string,\n\tsignal?: AbortSignal,\n): Promise<\n\t| { status: \"pending\" }\n\t| { status: \"slow_down\" }\n\t| { status: \"failed\"; message: string }\n\t| { status: \"complete\"; value: OAuthCredentials }\n> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling returned invalid JSON: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`,\n\t\t);\n\t}\n\n\tif (response.ok) {\n\t\treturn {\n\t\t\tstatus: \"complete\",\n\t\t\tvalue: parseXAITokenResponse(payload, \"xAI device-code token response\"),\n\t\t};\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`xAI device-code token polling failed: ${response.status}`);\n\t}\n\n\tconst errorCode = typeof payload.error === \"string\" ? payload.error : \"\";\n\tif (errorCode === \"authorization_pending\") return { status: \"pending\" };\n\tif (errorCode === \"slow_down\") return { status: \"slow_down\" };\n\n\tconst errorDescription = typeof payload.error_description === \"string\" ? payload.error_description : \"\";\n\tconst detail = errorDescription || errorCode || String(response.status);\n\tthrow new Error(`xAI device-code token polling failed: ${detail}`);\n}\n\n/** Log in to xAI Grok with the RFC 8628 device authorization grant. */\nexport async function loginXAI(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst device = await requestXAIDeviceAuthorization(callbacks.signal);\n\n\tcallbacks.onAuth({\n\t\turl: device.verificationUriComplete,\n\t\tinstructions: `Enter code: ${device.userCode}`,\n\t});\n\tcallbacks.onProgress?.(\"Waiting for xAI device authorization...\");\n\n\treturn pollOAuthDeviceCodeFlow({\n\t\tpoll: () => pollXAIDeviceToken(discovery.token_endpoint, device.deviceCode, callbacks.signal),\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: device.expiresInSeconds,\n\t\tsignal: callbacks.signal,\n\t});\n}\n\n/**\n * Refresh an xAI OAuth access token using a stored refresh_token.\n * Re-runs OIDC discovery and re-validates the token endpoint before sending.\n */\nexport async function refreshXAIToken(refreshToken: string): Promise<OAuthCredentials> {\n\tif (typeof refreshToken !== \"string\" || !refreshToken.trim()) {\n\t\tthrow new Error(\"missing refresh_token\");\n\t}\n\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst tokenEndpoint = validateXAIEndpoint(discovery.token_endpoint, \"token_endpoint\");\n\n\tconst body = new URLSearchParams({\n\t\tgrant_type: \"refresh_token\",\n\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\trefresh_token: refreshToken,\n\t});\n\n\tconst response = await fetch(tokenEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody,\n\t\tsignal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI token refresh failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI token refresh returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAITokenResponse(payload, \"xAI token refresh response\", refreshToken);\n}\n\nexport const xaiOAuthProvider: OAuthProviderInterface = {\n\tid: XAI_OAUTH_PROVIDER_ID,\n\tname: \"xAI Grok (SuperGrok or X Premium+)\",\n\tlogin: loginXAI,\n\trefreshToken: (credentials: OAuthCredentials) => refreshXAIToken(credentials.refresh),\n\tgetApiKey: (credentials: OAuthCredentials) => credentials.access,\n};\n"]}
1
+ {"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/xai.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAehG,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAoC3C,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,OAAO,CAiB9E;AA6MD,uEAAuE;AACvE,wBAAsB,QAAQ,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgBxF;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2CrF;AAED,eAAO,MAAM,gBAAgB,EAAE,sBAa9B,CAAC","sourcesContent":["/**\n * xAI Grok OAuth (native device authorization flow).\n *\n * RFC 8628 device-authorization grant with OIDC discovery.\n * Uses the official xAI OAuth client id and scopes.\n */\nimport { applyGrokThinking } from \"../../providers/grok-thinking.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\nconst XAI_OAUTH_ISSUER = \"https://auth.x.ai\";\nconst XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;\nconst XAI_OAUTH_DEVICE_CODE_URL = `${XAI_OAUTH_ISSUER}/oauth2/device/code`;\nconst XAI_OAUTH_CLIENT_ID = \"b1a00492-073a-47ea-816f-4c329264a828\";\nconst XAI_OAUTH_SCOPE =\n\t\"openid profile email offline_access grok-cli:access api:access conversations:read conversations:write workspaces:read workspaces:write\";\nconst XAI_OAUTH_REFERRER = \"grok-build\";\nconst XAI_OAUTH_CLIENT_SURFACE = \"ui\";\n\nconst ACCESS_TOKEN_CLIENT_SKEW_MS = 5 * 60 * 1000;\nconst DISCOVERY_TIMEOUT_MS = 15_000;\nconst TOKEN_REQUEST_TIMEOUT_MS = 20_000;\n\nexport const XAI_OAUTH_PROVIDER_ID = \"xai\";\nconst XAI_OAUTH_API_BASE_URL = \"https://api.x.ai/v1\";\n\ninterface XAIOAuthDiscovery {\n\ttoken_endpoint: string;\n}\n\ninterface XAIDeviceAuthorization {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUriComplete: string;\n\texpiresInSeconds: number;\n\tintervalSeconds: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction validateXAIEndpoint(url: string, field: string): string {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tif (parsed.protocol !== \"https:\") {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tconst host = parsed.hostname.toLowerCase();\n\tif (!host || (host !== \"x.ai\" && !host.endsWith(\".x.ai\"))) {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\treturn url;\n}\n\nexport function isXAIAccessTokenExpiring(jwt: string, skewSeconds = 0): boolean {\n\ttry {\n\t\tif (typeof jwt !== \"string\" || !jwt.includes(\".\")) return false;\n\t\tconst parts = jwt.split(\".\");\n\t\tif (parts.length < 2) return false;\n\t\tconst payloadPart = parts[1];\n\t\tif (!payloadPart) return false;\n\t\tconst decoded = Buffer.from(payloadPart, \"base64url\").toString(\"utf8\");\n\t\tconst payload: unknown = JSON.parse(decoded);\n\t\tif (!isRecord(payload)) return false;\n\t\tconst exp = payload.exp;\n\t\tif (typeof exp !== \"number\" || !Number.isFinite(exp)) return false;\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\treturn exp <= now + Math.max(0, Math.floor(skewSeconds));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function xaiOAuthDiscovery(timeoutMs = DISCOVERY_TIMEOUT_MS): Promise<XAIOAuthDiscovery> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(XAI_OAUTH_DISCOVERY_URL, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`xAI OIDC discovery failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (response.status !== 200) {\n\t\tthrow new Error(`xAI OIDC discovery returned status ${response.status}.`);\n\t}\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI OIDC discovery returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI OIDC discovery response was not a JSON object.\");\n\t}\n\tconst tokenEndpoint = typeof payload.token_endpoint === \"string\" ? payload.token_endpoint.trim() : \"\";\n\tif (!tokenEndpoint) {\n\t\tthrow new Error(\"xAI OIDC discovery response was missing token_endpoint.\");\n\t}\n\tvalidateXAIEndpoint(tokenEndpoint, \"token_endpoint\");\n\treturn { token_endpoint: tokenEndpoint };\n}\n\nfunction parseXAIDeviceAuthorization(payload: unknown): XAIDeviceAuthorization {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI device-code response was not a JSON object.\");\n\t}\n\n\tconst deviceCode = typeof payload.device_code === \"string\" ? payload.device_code.trim() : \"\";\n\tconst userCode = typeof payload.user_code === \"string\" ? payload.user_code.trim() : \"\";\n\tconst verificationUri = typeof payload.verification_uri === \"string\" ? payload.verification_uri.trim() : \"\";\n\tconst verificationUriComplete =\n\t\ttypeof payload.verification_uri_complete === \"string\" ? payload.verification_uri_complete.trim() : \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tconst intervalSeconds = payload.interval;\n\tif (\n\t\t!deviceCode ||\n\t\t!userCode ||\n\t\t!verificationUri ||\n\t\t!verificationUriComplete ||\n\t\ttypeof expiresInSeconds !== \"number\" ||\n\t\t!Number.isFinite(expiresInSeconds) ||\n\t\texpiresInSeconds <= 0 ||\n\t\ttypeof intervalSeconds !== \"number\" ||\n\t\t!Number.isFinite(intervalSeconds) ||\n\t\tintervalSeconds <= 0\n\t) {\n\t\tthrow new Error(\"xAI device-code response missing or invalid required fields.\");\n\t}\n\n\tvalidateXAIEndpoint(verificationUri, \"verification_uri\");\n\tvalidateXAIEndpoint(verificationUriComplete, \"verification_uri_complete\");\n\treturn {\n\t\tdeviceCode,\n\t\tuserCode,\n\t\tverificationUriComplete,\n\t\texpiresInSeconds,\n\t\tintervalSeconds,\n\t};\n}\n\nfunction parseXAITokenResponse(payload: unknown, label: string, refreshTokenFallback?: string): OAuthCredentials {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`${label} was not a JSON object`);\n\t}\n\tconst accessToken = typeof payload.access_token === \"string\" ? payload.access_token : \"\";\n\tconst responseRefreshToken = typeof payload.refresh_token === \"string\" ? payload.refresh_token : \"\";\n\tconst refreshToken = responseRefreshToken || refreshTokenFallback || \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tif (!accessToken) {\n\t\tthrow new Error(`${label} missing access_token`);\n\t}\n\tif (!refreshToken) {\n\t\tthrow new Error(`${label} missing refresh_token`);\n\t}\n\tif (typeof expiresInSeconds !== \"number\" || !Number.isFinite(expiresInSeconds)) {\n\t\tthrow new Error(`${label} missing expires_in`);\n\t}\n\treturn {\n\t\taccess: accessToken,\n\t\trefresh: refreshToken,\n\t\texpires: Date.now() + expiresInSeconds * 1000 - ACCESS_TOKEN_CLIENT_SKEW_MS,\n\t};\n}\n\nasync function requestXAIDeviceAuthorization(signal?: AbortSignal): Promise<XAIDeviceAuthorization> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(XAI_OAUTH_DEVICE_CODE_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"x-grok-client-surface\": XAI_OAUTH_CLIENT_SURFACE,\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tscope: XAI_OAUTH_SCOPE,\n\t\t\t\treferrer: XAI_OAUTH_REFERRER,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(`xAI device-code request failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI device-code request failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code response returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAIDeviceAuthorization(payload);\n}\n\nasync function pollXAIDeviceToken(\n\ttokenEndpoint: string,\n\tdeviceCode: string,\n\tsignal?: AbortSignal,\n): Promise<\n\t| { status: \"pending\" }\n\t| { status: \"slow_down\" }\n\t| { status: \"failed\"; message: string }\n\t| { status: \"complete\"; value: OAuthCredentials }\n> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"x-grok-client-surface\": XAI_OAUTH_CLIENT_SURFACE,\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling returned invalid JSON: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`,\n\t\t);\n\t}\n\n\tif (response.ok) {\n\t\treturn {\n\t\t\tstatus: \"complete\",\n\t\t\tvalue: parseXAITokenResponse(payload, \"xAI device-code token response\"),\n\t\t};\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`xAI device-code token polling failed: ${response.status}`);\n\t}\n\n\tconst errorCode = typeof payload.error === \"string\" ? payload.error : \"\";\n\tif (errorCode === \"authorization_pending\") return { status: \"pending\" };\n\tif (errorCode === \"slow_down\") return { status: \"slow_down\" };\n\n\tconst errorDescription = typeof payload.error_description === \"string\" ? payload.error_description : \"\";\n\tconst detail = errorDescription || errorCode || String(response.status);\n\tthrow new Error(`xAI device-code token polling failed: ${detail}`);\n}\n\n/** Log in to xAI Grok with the RFC 8628 device authorization grant. */\nexport async function loginXAI(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst device = await requestXAIDeviceAuthorization(callbacks.signal);\n\n\tcallbacks.onAuth({\n\t\turl: device.verificationUriComplete,\n\t\tinstructions: `Enter code: ${device.userCode}`,\n\t});\n\tcallbacks.onProgress?.(\"Waiting for xAI device authorization...\");\n\n\treturn pollOAuthDeviceCodeFlow({\n\t\tpoll: () => pollXAIDeviceToken(discovery.token_endpoint, device.deviceCode, callbacks.signal),\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: device.expiresInSeconds,\n\t\tsignal: callbacks.signal,\n\t});\n}\n\n/**\n * Refresh an xAI OAuth access token using a stored refresh_token.\n * Re-runs OIDC discovery and re-validates the token endpoint before sending.\n */\nexport async function refreshXAIToken(refreshToken: string): Promise<OAuthCredentials> {\n\tif (typeof refreshToken !== \"string\" || !refreshToken.trim()) {\n\t\tthrow new Error(\"missing refresh_token\");\n\t}\n\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst tokenEndpoint = validateXAIEndpoint(discovery.token_endpoint, \"token_endpoint\");\n\n\tconst body = new URLSearchParams({\n\t\tgrant_type: \"refresh_token\",\n\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\trefresh_token: refreshToken,\n\t});\n\n\tconst response = await fetch(tokenEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody,\n\t\tsignal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI token refresh failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI token refresh returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAITokenResponse(payload, \"xAI token refresh response\", refreshToken);\n}\n\nexport const xaiOAuthProvider: OAuthProviderInterface = {\n\tid: XAI_OAUTH_PROVIDER_ID,\n\tname: \"xAI Grok (SuperGrok or X Premium+)\",\n\tlogin: loginXAI,\n\trefreshToken: (credentials: OAuthCredentials) => refreshXAIToken(credentials.refresh),\n\tgetApiKey: (credentials: OAuthCredentials) => credentials.access,\n\tmodifyModels(models) {\n\t\treturn models.map((model) =>\n\t\t\tmodel.provider === XAI_OAUTH_PROVIDER_ID\n\t\t\t\t? applyGrokThinking({ ...model, baseUrl: XAI_OAUTH_API_BASE_URL })\n\t\t\t\t: model,\n\t\t);\n\t},\n};\n"]}
@@ -4,16 +4,20 @@
4
4
  * RFC 8628 device-authorization grant with OIDC discovery.
5
5
  * Uses the official xAI OAuth client id and scopes.
6
6
  */
7
+ import { applyGrokThinking } from "../../providers/grok-thinking.js";
7
8
  import { pollOAuthDeviceCodeFlow } from "./device-code.js";
8
9
  const XAI_OAUTH_ISSUER = "https://auth.x.ai";
9
10
  const XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;
10
11
  const XAI_OAUTH_DEVICE_CODE_URL = `${XAI_OAUTH_ISSUER}/oauth2/device/code`;
11
12
  const XAI_OAUTH_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
12
- const XAI_OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
13
+ const XAI_OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access conversations:read conversations:write workspaces:read workspaces:write";
14
+ const XAI_OAUTH_REFERRER = "grok-build";
15
+ const XAI_OAUTH_CLIENT_SURFACE = "ui";
13
16
  const ACCESS_TOKEN_CLIENT_SKEW_MS = 5 * 60 * 1000;
14
17
  const DISCOVERY_TIMEOUT_MS = 15_000;
15
18
  const TOKEN_REQUEST_TIMEOUT_MS = 20_000;
16
19
  export const XAI_OAUTH_PROVIDER_ID = "xai";
20
+ const XAI_OAUTH_API_BASE_URL = "https://api.x.ai/v1";
17
21
  function isRecord(value) {
18
22
  return typeof value === "object" && value !== null;
19
23
  }
@@ -154,10 +158,12 @@ async function requestXAIDeviceAuthorization(signal) {
154
158
  headers: {
155
159
  "Content-Type": "application/x-www-form-urlencoded",
156
160
  Accept: "application/json",
161
+ "x-grok-client-surface": XAI_OAUTH_CLIENT_SURFACE,
157
162
  },
158
163
  body: new URLSearchParams({
159
164
  client_id: XAI_OAUTH_CLIENT_ID,
160
165
  scope: XAI_OAUTH_SCOPE,
166
+ referrer: XAI_OAUTH_REFERRER,
161
167
  }),
162
168
  signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
163
169
  });
@@ -195,6 +201,7 @@ async function pollXAIDeviceToken(tokenEndpoint, deviceCode, signal) {
195
201
  headers: {
196
202
  "Content-Type": "application/x-www-form-urlencoded",
197
203
  Accept: "application/json",
204
+ "x-grok-client-surface": XAI_OAUTH_CLIENT_SURFACE,
198
205
  },
199
206
  body: new URLSearchParams({
200
207
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
@@ -299,5 +306,10 @@ export const xaiOAuthProvider = {
299
306
  login: loginXAI,
300
307
  refreshToken: (credentials) => refreshXAIToken(credentials.refresh),
301
308
  getApiKey: (credentials) => credentials.access,
309
+ modifyModels(models) {
310
+ return models.map((model) => model.provider === XAI_OAUTH_PROVIDER_ID
311
+ ? applyGrokThinking({ ...model, baseUrl: XAI_OAUTH_API_BASE_URL })
312
+ : model);
313
+ },
302
314
  };
303
315
  //# sourceMappingURL=xai.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"xai.js","sourceRoot":"","sources":["../../../src/utils/oauth/xai.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * xAI Grok OAuth (native device authorization flow).\n *\n * RFC 8628 device-authorization grant with OIDC discovery.\n * Uses the official xAI OAuth client id and scopes.\n */\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\nconst XAI_OAUTH_ISSUER = \"https://auth.x.ai\";\nconst XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;\nconst XAI_OAUTH_DEVICE_CODE_URL = `${XAI_OAUTH_ISSUER}/oauth2/device/code`;\nconst XAI_OAUTH_CLIENT_ID = \"b1a00492-073a-47ea-816f-4c329264a828\";\nconst XAI_OAUTH_SCOPE = \"openid profile email offline_access grok-cli:access api:access\";\n\nconst ACCESS_TOKEN_CLIENT_SKEW_MS = 5 * 60 * 1000;\nconst DISCOVERY_TIMEOUT_MS = 15_000;\nconst TOKEN_REQUEST_TIMEOUT_MS = 20_000;\n\nexport const XAI_OAUTH_PROVIDER_ID = \"xai\";\n\ninterface XAIOAuthDiscovery {\n\ttoken_endpoint: string;\n}\n\ninterface XAIDeviceAuthorization {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUriComplete: string;\n\texpiresInSeconds: number;\n\tintervalSeconds: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction validateXAIEndpoint(url: string, field: string): string {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tif (parsed.protocol !== \"https:\") {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tconst host = parsed.hostname.toLowerCase();\n\tif (!host || (host !== \"x.ai\" && !host.endsWith(\".x.ai\"))) {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\treturn url;\n}\n\nexport function isXAIAccessTokenExpiring(jwt: string, skewSeconds = 0): boolean {\n\ttry {\n\t\tif (typeof jwt !== \"string\" || !jwt.includes(\".\")) return false;\n\t\tconst parts = jwt.split(\".\");\n\t\tif (parts.length < 2) return false;\n\t\tconst payloadPart = parts[1];\n\t\tif (!payloadPart) return false;\n\t\tconst decoded = Buffer.from(payloadPart, \"base64url\").toString(\"utf8\");\n\t\tconst payload: unknown = JSON.parse(decoded);\n\t\tif (!isRecord(payload)) return false;\n\t\tconst exp = payload.exp;\n\t\tif (typeof exp !== \"number\" || !Number.isFinite(exp)) return false;\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\treturn exp <= now + Math.max(0, Math.floor(skewSeconds));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function xaiOAuthDiscovery(timeoutMs = DISCOVERY_TIMEOUT_MS): Promise<XAIOAuthDiscovery> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(XAI_OAUTH_DISCOVERY_URL, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`xAI OIDC discovery failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (response.status !== 200) {\n\t\tthrow new Error(`xAI OIDC discovery returned status ${response.status}.`);\n\t}\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI OIDC discovery returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI OIDC discovery response was not a JSON object.\");\n\t}\n\tconst tokenEndpoint = typeof payload.token_endpoint === \"string\" ? payload.token_endpoint.trim() : \"\";\n\tif (!tokenEndpoint) {\n\t\tthrow new Error(\"xAI OIDC discovery response was missing token_endpoint.\");\n\t}\n\tvalidateXAIEndpoint(tokenEndpoint, \"token_endpoint\");\n\treturn { token_endpoint: tokenEndpoint };\n}\n\nfunction parseXAIDeviceAuthorization(payload: unknown): XAIDeviceAuthorization {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI device-code response was not a JSON object.\");\n\t}\n\n\tconst deviceCode = typeof payload.device_code === \"string\" ? payload.device_code.trim() : \"\";\n\tconst userCode = typeof payload.user_code === \"string\" ? payload.user_code.trim() : \"\";\n\tconst verificationUri = typeof payload.verification_uri === \"string\" ? payload.verification_uri.trim() : \"\";\n\tconst verificationUriComplete =\n\t\ttypeof payload.verification_uri_complete === \"string\" ? payload.verification_uri_complete.trim() : \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tconst intervalSeconds = payload.interval;\n\tif (\n\t\t!deviceCode ||\n\t\t!userCode ||\n\t\t!verificationUri ||\n\t\t!verificationUriComplete ||\n\t\ttypeof expiresInSeconds !== \"number\" ||\n\t\t!Number.isFinite(expiresInSeconds) ||\n\t\texpiresInSeconds <= 0 ||\n\t\ttypeof intervalSeconds !== \"number\" ||\n\t\t!Number.isFinite(intervalSeconds) ||\n\t\tintervalSeconds <= 0\n\t) {\n\t\tthrow new Error(\"xAI device-code response missing or invalid required fields.\");\n\t}\n\n\tvalidateXAIEndpoint(verificationUri, \"verification_uri\");\n\tvalidateXAIEndpoint(verificationUriComplete, \"verification_uri_complete\");\n\treturn {\n\t\tdeviceCode,\n\t\tuserCode,\n\t\tverificationUriComplete,\n\t\texpiresInSeconds,\n\t\tintervalSeconds,\n\t};\n}\n\nfunction parseXAITokenResponse(payload: unknown, label: string, refreshTokenFallback?: string): OAuthCredentials {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`${label} was not a JSON object`);\n\t}\n\tconst accessToken = typeof payload.access_token === \"string\" ? payload.access_token : \"\";\n\tconst responseRefreshToken = typeof payload.refresh_token === \"string\" ? payload.refresh_token : \"\";\n\tconst refreshToken = responseRefreshToken || refreshTokenFallback || \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tif (!accessToken) {\n\t\tthrow new Error(`${label} missing access_token`);\n\t}\n\tif (!refreshToken) {\n\t\tthrow new Error(`${label} missing refresh_token`);\n\t}\n\tif (typeof expiresInSeconds !== \"number\" || !Number.isFinite(expiresInSeconds)) {\n\t\tthrow new Error(`${label} missing expires_in`);\n\t}\n\treturn {\n\t\taccess: accessToken,\n\t\trefresh: refreshToken,\n\t\texpires: Date.now() + expiresInSeconds * 1000 - ACCESS_TOKEN_CLIENT_SKEW_MS,\n\t};\n}\n\nasync function requestXAIDeviceAuthorization(signal?: AbortSignal): Promise<XAIDeviceAuthorization> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(XAI_OAUTH_DEVICE_CODE_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tscope: XAI_OAUTH_SCOPE,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(`xAI device-code request failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI device-code request failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code response returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAIDeviceAuthorization(payload);\n}\n\nasync function pollXAIDeviceToken(\n\ttokenEndpoint: string,\n\tdeviceCode: string,\n\tsignal?: AbortSignal,\n): Promise<\n\t| { status: \"pending\" }\n\t| { status: \"slow_down\" }\n\t| { status: \"failed\"; message: string }\n\t| { status: \"complete\"; value: OAuthCredentials }\n> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling returned invalid JSON: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`,\n\t\t);\n\t}\n\n\tif (response.ok) {\n\t\treturn {\n\t\t\tstatus: \"complete\",\n\t\t\tvalue: parseXAITokenResponse(payload, \"xAI device-code token response\"),\n\t\t};\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`xAI device-code token polling failed: ${response.status}`);\n\t}\n\n\tconst errorCode = typeof payload.error === \"string\" ? payload.error : \"\";\n\tif (errorCode === \"authorization_pending\") return { status: \"pending\" };\n\tif (errorCode === \"slow_down\") return { status: \"slow_down\" };\n\n\tconst errorDescription = typeof payload.error_description === \"string\" ? payload.error_description : \"\";\n\tconst detail = errorDescription || errorCode || String(response.status);\n\tthrow new Error(`xAI device-code token polling failed: ${detail}`);\n}\n\n/** Log in to xAI Grok with the RFC 8628 device authorization grant. */\nexport async function loginXAI(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst device = await requestXAIDeviceAuthorization(callbacks.signal);\n\n\tcallbacks.onAuth({\n\t\turl: device.verificationUriComplete,\n\t\tinstructions: `Enter code: ${device.userCode}`,\n\t});\n\tcallbacks.onProgress?.(\"Waiting for xAI device authorization...\");\n\n\treturn pollOAuthDeviceCodeFlow({\n\t\tpoll: () => pollXAIDeviceToken(discovery.token_endpoint, device.deviceCode, callbacks.signal),\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: device.expiresInSeconds,\n\t\tsignal: callbacks.signal,\n\t});\n}\n\n/**\n * Refresh an xAI OAuth access token using a stored refresh_token.\n * Re-runs OIDC discovery and re-validates the token endpoint before sending.\n */\nexport async function refreshXAIToken(refreshToken: string): Promise<OAuthCredentials> {\n\tif (typeof refreshToken !== \"string\" || !refreshToken.trim()) {\n\t\tthrow new Error(\"missing refresh_token\");\n\t}\n\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst tokenEndpoint = validateXAIEndpoint(discovery.token_endpoint, \"token_endpoint\");\n\n\tconst body = new URLSearchParams({\n\t\tgrant_type: \"refresh_token\",\n\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\trefresh_token: refreshToken,\n\t});\n\n\tconst response = await fetch(tokenEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody,\n\t\tsignal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI token refresh failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI token refresh returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAITokenResponse(payload, \"xAI token refresh response\", refreshToken);\n}\n\nexport const xaiOAuthProvider: OAuthProviderInterface = {\n\tid: XAI_OAUTH_PROVIDER_ID,\n\tname: \"xAI Grok (SuperGrok or X Premium+)\",\n\tlogin: loginXAI,\n\trefreshToken: (credentials: OAuthCredentials) => refreshXAIToken(credentials.refresh),\n\tgetApiKey: (credentials: OAuthCredentials) => credentials.access,\n};\n"]}
1
+ {"version":3,"file":"xai.js","sourceRoot":"","sources":["../../../src/utils/oauth/xai.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * xAI Grok OAuth (native device authorization flow).\n *\n * RFC 8628 device-authorization grant with OIDC discovery.\n * Uses the official xAI OAuth client id and scopes.\n */\nimport { applyGrokThinking } from \"../../providers/grok-thinking.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\nconst XAI_OAUTH_ISSUER = \"https://auth.x.ai\";\nconst XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;\nconst XAI_OAUTH_DEVICE_CODE_URL = `${XAI_OAUTH_ISSUER}/oauth2/device/code`;\nconst XAI_OAUTH_CLIENT_ID = \"b1a00492-073a-47ea-816f-4c329264a828\";\nconst XAI_OAUTH_SCOPE =\n\t\"openid profile email offline_access grok-cli:access api:access conversations:read conversations:write workspaces:read workspaces:write\";\nconst XAI_OAUTH_REFERRER = \"grok-build\";\nconst XAI_OAUTH_CLIENT_SURFACE = \"ui\";\n\nconst ACCESS_TOKEN_CLIENT_SKEW_MS = 5 * 60 * 1000;\nconst DISCOVERY_TIMEOUT_MS = 15_000;\nconst TOKEN_REQUEST_TIMEOUT_MS = 20_000;\n\nexport const XAI_OAUTH_PROVIDER_ID = \"xai\";\nconst XAI_OAUTH_API_BASE_URL = \"https://api.x.ai/v1\";\n\ninterface XAIOAuthDiscovery {\n\ttoken_endpoint: string;\n}\n\ninterface XAIDeviceAuthorization {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUriComplete: string;\n\texpiresInSeconds: number;\n\tintervalSeconds: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction validateXAIEndpoint(url: string, field: string): string {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tif (parsed.protocol !== \"https:\") {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\tconst host = parsed.hostname.toLowerCase();\n\tif (!host || (host !== \"x.ai\" && !host.endsWith(\".x.ai\"))) {\n\t\tthrow new Error(`Invalid xAI ${field}: ${url}`);\n\t}\n\treturn url;\n}\n\nexport function isXAIAccessTokenExpiring(jwt: string, skewSeconds = 0): boolean {\n\ttry {\n\t\tif (typeof jwt !== \"string\" || !jwt.includes(\".\")) return false;\n\t\tconst parts = jwt.split(\".\");\n\t\tif (parts.length < 2) return false;\n\t\tconst payloadPart = parts[1];\n\t\tif (!payloadPart) return false;\n\t\tconst decoded = Buffer.from(payloadPart, \"base64url\").toString(\"utf8\");\n\t\tconst payload: unknown = JSON.parse(decoded);\n\t\tif (!isRecord(payload)) return false;\n\t\tconst exp = payload.exp;\n\t\tif (typeof exp !== \"number\" || !Number.isFinite(exp)) return false;\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\treturn exp <= now + Math.max(0, Math.floor(skewSeconds));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function xaiOAuthDiscovery(timeoutMs = DISCOVERY_TIMEOUT_MS): Promise<XAIOAuthDiscovery> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(XAI_OAUTH_DISCOVERY_URL, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`xAI OIDC discovery failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (response.status !== 200) {\n\t\tthrow new Error(`xAI OIDC discovery returned status ${response.status}.`);\n\t}\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI OIDC discovery returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI OIDC discovery response was not a JSON object.\");\n\t}\n\tconst tokenEndpoint = typeof payload.token_endpoint === \"string\" ? payload.token_endpoint.trim() : \"\";\n\tif (!tokenEndpoint) {\n\t\tthrow new Error(\"xAI OIDC discovery response was missing token_endpoint.\");\n\t}\n\tvalidateXAIEndpoint(tokenEndpoint, \"token_endpoint\");\n\treturn { token_endpoint: tokenEndpoint };\n}\n\nfunction parseXAIDeviceAuthorization(payload: unknown): XAIDeviceAuthorization {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(\"xAI device-code response was not a JSON object.\");\n\t}\n\n\tconst deviceCode = typeof payload.device_code === \"string\" ? payload.device_code.trim() : \"\";\n\tconst userCode = typeof payload.user_code === \"string\" ? payload.user_code.trim() : \"\";\n\tconst verificationUri = typeof payload.verification_uri === \"string\" ? payload.verification_uri.trim() : \"\";\n\tconst verificationUriComplete =\n\t\ttypeof payload.verification_uri_complete === \"string\" ? payload.verification_uri_complete.trim() : \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tconst intervalSeconds = payload.interval;\n\tif (\n\t\t!deviceCode ||\n\t\t!userCode ||\n\t\t!verificationUri ||\n\t\t!verificationUriComplete ||\n\t\ttypeof expiresInSeconds !== \"number\" ||\n\t\t!Number.isFinite(expiresInSeconds) ||\n\t\texpiresInSeconds <= 0 ||\n\t\ttypeof intervalSeconds !== \"number\" ||\n\t\t!Number.isFinite(intervalSeconds) ||\n\t\tintervalSeconds <= 0\n\t) {\n\t\tthrow new Error(\"xAI device-code response missing or invalid required fields.\");\n\t}\n\n\tvalidateXAIEndpoint(verificationUri, \"verification_uri\");\n\tvalidateXAIEndpoint(verificationUriComplete, \"verification_uri_complete\");\n\treturn {\n\t\tdeviceCode,\n\t\tuserCode,\n\t\tverificationUriComplete,\n\t\texpiresInSeconds,\n\t\tintervalSeconds,\n\t};\n}\n\nfunction parseXAITokenResponse(payload: unknown, label: string, refreshTokenFallback?: string): OAuthCredentials {\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`${label} was not a JSON object`);\n\t}\n\tconst accessToken = typeof payload.access_token === \"string\" ? payload.access_token : \"\";\n\tconst responseRefreshToken = typeof payload.refresh_token === \"string\" ? payload.refresh_token : \"\";\n\tconst refreshToken = responseRefreshToken || refreshTokenFallback || \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tif (!accessToken) {\n\t\tthrow new Error(`${label} missing access_token`);\n\t}\n\tif (!refreshToken) {\n\t\tthrow new Error(`${label} missing refresh_token`);\n\t}\n\tif (typeof expiresInSeconds !== \"number\" || !Number.isFinite(expiresInSeconds)) {\n\t\tthrow new Error(`${label} missing expires_in`);\n\t}\n\treturn {\n\t\taccess: accessToken,\n\t\trefresh: refreshToken,\n\t\texpires: Date.now() + expiresInSeconds * 1000 - ACCESS_TOKEN_CLIENT_SKEW_MS,\n\t};\n}\n\nasync function requestXAIDeviceAuthorization(signal?: AbortSignal): Promise<XAIDeviceAuthorization> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(XAI_OAUTH_DEVICE_CODE_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"x-grok-client-surface\": XAI_OAUTH_CLIENT_SURFACE,\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tscope: XAI_OAUTH_SCOPE,\n\t\t\t\treferrer: XAI_OAUTH_REFERRER,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(`xAI device-code request failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI device-code request failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code response returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAIDeviceAuthorization(payload);\n}\n\nasync function pollXAIDeviceToken(\n\ttokenEndpoint: string,\n\tdeviceCode: string,\n\tsignal?: AbortSignal,\n): Promise<\n\t| { status: \"pending\" }\n\t| { status: \"slow_down\" }\n\t| { status: \"failed\"; message: string }\n\t| { status: \"complete\"; value: OAuthCredentials }\n> {\n\tlet response: Response;\n\ttry {\n\t\tconst timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);\n\t\tresponse = await fetch(tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"x-grok-client-surface\": XAI_OAUTH_CLIENT_SURFACE,\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t}),\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI device-code token polling returned invalid JSON: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`,\n\t\t);\n\t}\n\n\tif (response.ok) {\n\t\treturn {\n\t\t\tstatus: \"complete\",\n\t\t\tvalue: parseXAITokenResponse(payload, \"xAI device-code token response\"),\n\t\t};\n\t}\n\tif (!isRecord(payload)) {\n\t\tthrow new Error(`xAI device-code token polling failed: ${response.status}`);\n\t}\n\n\tconst errorCode = typeof payload.error === \"string\" ? payload.error : \"\";\n\tif (errorCode === \"authorization_pending\") return { status: \"pending\" };\n\tif (errorCode === \"slow_down\") return { status: \"slow_down\" };\n\n\tconst errorDescription = typeof payload.error_description === \"string\" ? payload.error_description : \"\";\n\tconst detail = errorDescription || errorCode || String(response.status);\n\tthrow new Error(`xAI device-code token polling failed: ${detail}`);\n}\n\n/** Log in to xAI Grok with the RFC 8628 device authorization grant. */\nexport async function loginXAI(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst device = await requestXAIDeviceAuthorization(callbacks.signal);\n\n\tcallbacks.onAuth({\n\t\turl: device.verificationUriComplete,\n\t\tinstructions: `Enter code: ${device.userCode}`,\n\t});\n\tcallbacks.onProgress?.(\"Waiting for xAI device authorization...\");\n\n\treturn pollOAuthDeviceCodeFlow({\n\t\tpoll: () => pollXAIDeviceToken(discovery.token_endpoint, device.deviceCode, callbacks.signal),\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: device.expiresInSeconds,\n\t\tsignal: callbacks.signal,\n\t});\n}\n\n/**\n * Refresh an xAI OAuth access token using a stored refresh_token.\n * Re-runs OIDC discovery and re-validates the token endpoint before sending.\n */\nexport async function refreshXAIToken(refreshToken: string): Promise<OAuthCredentials> {\n\tif (typeof refreshToken !== \"string\" || !refreshToken.trim()) {\n\t\tthrow new Error(\"missing refresh_token\");\n\t}\n\n\tconst discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS);\n\tconst tokenEndpoint = validateXAIEndpoint(discovery.token_endpoint, \"token_endpoint\");\n\n\tconst body = new URLSearchParams({\n\t\tgrant_type: \"refresh_token\",\n\t\tclient_id: XAI_OAUTH_CLIENT_ID,\n\t\trefresh_token: refreshToken,\n\t});\n\n\tconst response = await fetch(tokenEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody,\n\t\tsignal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tlet detail = \"\";\n\t\ttry {\n\t\t\tdetail = (await response.text()).trim();\n\t\t} catch {\n\t\t\t// Ignore body-read failures\n\t\t}\n\t\tthrow new Error(`xAI token refresh failed: ${response.status}${detail ? ` ${detail}` : \"\"}`);\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = await response.json();\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`xAI token refresh returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\treturn parseXAITokenResponse(payload, \"xAI token refresh response\", refreshToken);\n}\n\nexport const xaiOAuthProvider: OAuthProviderInterface = {\n\tid: XAI_OAUTH_PROVIDER_ID,\n\tname: \"xAI Grok (SuperGrok or X Premium+)\",\n\tlogin: loginXAI,\n\trefreshToken: (credentials: OAuthCredentials) => refreshXAIToken(credentials.refresh),\n\tgetApiKey: (credentials: OAuthCredentials) => credentials.access,\n\tmodifyModels(models) {\n\t\treturn models.map((model) =>\n\t\t\tmodel.provider === XAI_OAUTH_PROVIDER_ID\n\t\t\t\t? applyGrokThinking({ ...model, baseUrl: XAI_OAUTH_API_BASE_URL })\n\t\t\t\t: model,\n\t\t);\n\t},\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omk-ai",
3
- "version": "0.96.0",
3
+ "version": "0.96.2",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,18 +0,0 @@
1
- /**
2
- * Grok (xAI) via the local grok-oauth-proxy.
3
- *
4
- * OMK reaches xAI/Grok through a local, OpenAI-compatible proxy that owns the
5
- * single OAuth refresher (see `~/.omk/agent/grok.md`). This provider deliberately
6
- * does NOT run a competing xAI OAuth flow; "login" only verifies the local proxy
7
- * is reachable and records a local marker credential. Chat requests use the
8
- * proxy's local API key (dummy by default) against the proxy base URL.
9
- */
10
- import type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from "./types.ts";
11
- export declare const GROK_PROXY_PROVIDER_ID = "grok-oauth-proxy";
12
- /** Verify the local Grok proxy is reachable and return a marker credential. */
13
- export declare function loginGrokProxy(options: {
14
- onProgress?: OAuthLoginCallbacks["onProgress"];
15
- signal?: AbortSignal;
16
- }): Promise<OAuthCredentials>;
17
- export declare const grokProxyOAuthProvider: OAuthProviderInterface;
18
- //# sourceMappingURL=grok-proxy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grok-proxy.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/grok-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEhG,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AA4CzD,+EAA+E;AAC/E,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC7C,UAAU,CAAC,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAK5B;AAkBD,eAAO,MAAM,sBAAsB,EAAE,sBA2BpC,CAAC","sourcesContent":["/**\n * Grok (xAI) via the local grok-oauth-proxy.\n *\n * OMK reaches xAI/Grok through a local, OpenAI-compatible proxy that owns the\n * single OAuth refresher (see `~/.omk/agent/grok.md`). This provider deliberately\n * does NOT run a competing xAI OAuth flow; \"login\" only verifies the local proxy\n * is reachable and records a local marker credential. Chat requests use the\n * proxy's local API key (dummy by default) against the proxy base URL.\n */\n\nimport type { Api, Model } from \"../../types.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\nexport const GROK_PROXY_PROVIDER_ID = \"grok-oauth-proxy\";\n\nconst DEFAULT_BASE_URL = \"http://127.0.0.1:9996/v1\";\nconst DEFAULT_API_KEY = \"dummy\";\n/** Marker credential lifetime; the proxy owns real token refresh, we just re-check health. */\nconst MARKER_TTL_MS = 365 * 24 * 60 * 60 * 1000;\n\nfunction getBaseUrl(): string {\n\tconst fromEnv = typeof process !== \"undefined\" ? process.env.OMK_GROK_PROXY_BASE_URL : undefined;\n\treturn fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_BASE_URL;\n}\n\nfunction getLocalApiKey(): string {\n\tconst fromEnv = typeof process !== \"undefined\" ? process.env.OMK_GROK_PROXY_API_KEY : undefined;\n\treturn fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_API_KEY;\n}\n\nfunction startHint(baseUrl: string): string {\n\treturn `Grok proxy not reachable at ${baseUrl}. Start it with: systemctl --user start grok-oauth-proxy`;\n}\n\nasync function checkProxyHealth(signal?: AbortSignal): Promise<void> {\n\tconst baseUrl = getBaseUrl();\n\tconst healthUrl = new URL(\"/health\", baseUrl).href;\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(healthUrl, { method: \"GET\", signal });\n\t} catch (error) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tthrow new Error(`${startHint(baseUrl)} (${error instanceof Error ? error.message : String(error)})`);\n\t}\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Grok proxy health check failed (HTTP ${response.status}) at ${healthUrl}. ${startHint(baseUrl)}`,\n\t\t);\n\t}\n}\n\nfunction markerCredentials(): OAuthCredentials {\n\treturn { access: GROK_PROXY_PROVIDER_ID, refresh: GROK_PROXY_PROVIDER_ID, expires: Date.now() + MARKER_TTL_MS };\n}\n\n/** Verify the local Grok proxy is reachable and return a marker credential. */\nexport async function loginGrokProxy(options: {\n\tonProgress?: OAuthLoginCallbacks[\"onProgress\"];\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\toptions.onProgress?.(`Checking local Grok proxy health at ${getBaseUrl()}...`);\n\tawait checkProxyHealth(options.signal);\n\toptions.onProgress?.(\"Grok proxy is reachable.\");\n\treturn markerCredentials();\n}\n\nfunction grokModels(baseUrl: string): Model<\"openai-completions\">[] {\n\tconst shared = {\n\t\tapi: \"openai-completions\" as const,\n\t\tprovider: GROK_PROXY_PROVIDER_ID,\n\t\tbaseUrl,\n\t\tinput: [\"text\", \"image\"] as (\"text\" | \"image\")[],\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n\t\tcontextWindow: 256_000,\n\t\tmaxTokens: 32_768,\n\t};\n\treturn [\n\t\t{ ...shared, id: \"grok-4.5\", name: \"Grok 4.5\", reasoning: true },\n\t\t{ ...shared, id: \"grok-4.3\", name: \"Grok 4.3\", reasoning: true },\n\t];\n}\n\nexport const grokProxyOAuthProvider: OAuthProviderInterface = {\n\tid: GROK_PROXY_PROVIDER_ID,\n\tname: \"Grok (xAI OAuth Proxy)\",\n\tusesCallbackServer: false,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGrokProxy({ onProgress: callbacks.onProgress, signal: callbacks.signal });\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\t// The proxy owns real token refresh; we only re-verify reachability.\n\t\tawait checkProxyHealth();\n\t\treturn { ...credentials, expires: Date.now() + MARKER_TTL_MS };\n\t},\n\n\tgetApiKey(): string {\n\t\treturn getLocalApiKey();\n\t},\n\n\tmodifyModels(models: Model<Api>[]): Model<Api>[] {\n\t\t// Non-destructive: keep any user-configured grok-oauth-proxy models, only add missing defaults.\n\t\tconst existingIds = new Set(\n\t\t\tmodels.filter((model) => model.provider === GROK_PROXY_PROVIDER_ID).map((model) => model.id),\n\t\t);\n\t\tconst additions = grokModels(getBaseUrl()).filter((model) => !existingIds.has(model.id));\n\t\treturn [...models, ...additions];\n\t},\n};\n"]}
@@ -1,90 +0,0 @@
1
- /**
2
- * Grok (xAI) via the local grok-oauth-proxy.
3
- *
4
- * OMK reaches xAI/Grok through a local, OpenAI-compatible proxy that owns the
5
- * single OAuth refresher (see `~/.omk/agent/grok.md`). This provider deliberately
6
- * does NOT run a competing xAI OAuth flow; "login" only verifies the local proxy
7
- * is reachable and records a local marker credential. Chat requests use the
8
- * proxy's local API key (dummy by default) against the proxy base URL.
9
- */
10
- export const GROK_PROXY_PROVIDER_ID = "grok-oauth-proxy";
11
- const DEFAULT_BASE_URL = "http://127.0.0.1:9996/v1";
12
- const DEFAULT_API_KEY = "dummy";
13
- /** Marker credential lifetime; the proxy owns real token refresh, we just re-check health. */
14
- const MARKER_TTL_MS = 365 * 24 * 60 * 60 * 1000;
15
- function getBaseUrl() {
16
- const fromEnv = typeof process !== "undefined" ? process.env.OMK_GROK_PROXY_BASE_URL : undefined;
17
- return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_BASE_URL;
18
- }
19
- function getLocalApiKey() {
20
- const fromEnv = typeof process !== "undefined" ? process.env.OMK_GROK_PROXY_API_KEY : undefined;
21
- return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_API_KEY;
22
- }
23
- function startHint(baseUrl) {
24
- return `Grok proxy not reachable at ${baseUrl}. Start it with: systemctl --user start grok-oauth-proxy`;
25
- }
26
- async function checkProxyHealth(signal) {
27
- const baseUrl = getBaseUrl();
28
- const healthUrl = new URL("/health", baseUrl).href;
29
- let response;
30
- try {
31
- response = await fetch(healthUrl, { method: "GET", signal });
32
- }
33
- catch (error) {
34
- if (signal?.aborted) {
35
- throw new Error("Login cancelled");
36
- }
37
- throw new Error(`${startHint(baseUrl)} (${error instanceof Error ? error.message : String(error)})`);
38
- }
39
- if (!response.ok) {
40
- throw new Error(`Grok proxy health check failed (HTTP ${response.status}) at ${healthUrl}. ${startHint(baseUrl)}`);
41
- }
42
- }
43
- function markerCredentials() {
44
- return { access: GROK_PROXY_PROVIDER_ID, refresh: GROK_PROXY_PROVIDER_ID, expires: Date.now() + MARKER_TTL_MS };
45
- }
46
- /** Verify the local Grok proxy is reachable and return a marker credential. */
47
- export async function loginGrokProxy(options) {
48
- options.onProgress?.(`Checking local Grok proxy health at ${getBaseUrl()}...`);
49
- await checkProxyHealth(options.signal);
50
- options.onProgress?.("Grok proxy is reachable.");
51
- return markerCredentials();
52
- }
53
- function grokModels(baseUrl) {
54
- const shared = {
55
- api: "openai-completions",
56
- provider: GROK_PROXY_PROVIDER_ID,
57
- baseUrl,
58
- input: ["text", "image"],
59
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
60
- contextWindow: 256_000,
61
- maxTokens: 32_768,
62
- };
63
- return [
64
- { ...shared, id: "grok-4.5", name: "Grok 4.5", reasoning: true },
65
- { ...shared, id: "grok-4.3", name: "Grok 4.3", reasoning: true },
66
- ];
67
- }
68
- export const grokProxyOAuthProvider = {
69
- id: GROK_PROXY_PROVIDER_ID,
70
- name: "Grok (xAI OAuth Proxy)",
71
- usesCallbackServer: false,
72
- async login(callbacks) {
73
- return loginGrokProxy({ onProgress: callbacks.onProgress, signal: callbacks.signal });
74
- },
75
- async refreshToken(credentials) {
76
- // The proxy owns real token refresh; we only re-verify reachability.
77
- await checkProxyHealth();
78
- return { ...credentials, expires: Date.now() + MARKER_TTL_MS };
79
- },
80
- getApiKey() {
81
- return getLocalApiKey();
82
- },
83
- modifyModels(models) {
84
- // Non-destructive: keep any user-configured grok-oauth-proxy models, only add missing defaults.
85
- const existingIds = new Set(models.filter((model) => model.provider === GROK_PROXY_PROVIDER_ID).map((model) => model.id));
86
- const additions = grokModels(getBaseUrl()).filter((model) => !existingIds.has(model.id));
87
- return [...models, ...additions];
88
- },
89
- };
90
- //# sourceMappingURL=grok-proxy.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grok-proxy.js","sourceRoot":"","sources":["../../../src/utils/oauth/grok-proxy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Grok (xAI) via the local grok-oauth-proxy.\n *\n * OMK reaches xAI/Grok through a local, OpenAI-compatible proxy that owns the\n * single OAuth refresher (see `~/.omk/agent/grok.md`). This provider deliberately\n * does NOT run a competing xAI OAuth flow; \"login\" only verifies the local proxy\n * is reachable and records a local marker credential. Chat requests use the\n * proxy's local API key (dummy by default) against the proxy base URL.\n */\n\nimport type { Api, Model } from \"../../types.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\nexport const GROK_PROXY_PROVIDER_ID = \"grok-oauth-proxy\";\n\nconst DEFAULT_BASE_URL = \"http://127.0.0.1:9996/v1\";\nconst DEFAULT_API_KEY = \"dummy\";\n/** Marker credential lifetime; the proxy owns real token refresh, we just re-check health. */\nconst MARKER_TTL_MS = 365 * 24 * 60 * 60 * 1000;\n\nfunction getBaseUrl(): string {\n\tconst fromEnv = typeof process !== \"undefined\" ? process.env.OMK_GROK_PROXY_BASE_URL : undefined;\n\treturn fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_BASE_URL;\n}\n\nfunction getLocalApiKey(): string {\n\tconst fromEnv = typeof process !== \"undefined\" ? process.env.OMK_GROK_PROXY_API_KEY : undefined;\n\treturn fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_API_KEY;\n}\n\nfunction startHint(baseUrl: string): string {\n\treturn `Grok proxy not reachable at ${baseUrl}. Start it with: systemctl --user start grok-oauth-proxy`;\n}\n\nasync function checkProxyHealth(signal?: AbortSignal): Promise<void> {\n\tconst baseUrl = getBaseUrl();\n\tconst healthUrl = new URL(\"/health\", baseUrl).href;\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(healthUrl, { method: \"GET\", signal });\n\t} catch (error) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tthrow new Error(`${startHint(baseUrl)} (${error instanceof Error ? error.message : String(error)})`);\n\t}\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Grok proxy health check failed (HTTP ${response.status}) at ${healthUrl}. ${startHint(baseUrl)}`,\n\t\t);\n\t}\n}\n\nfunction markerCredentials(): OAuthCredentials {\n\treturn { access: GROK_PROXY_PROVIDER_ID, refresh: GROK_PROXY_PROVIDER_ID, expires: Date.now() + MARKER_TTL_MS };\n}\n\n/** Verify the local Grok proxy is reachable and return a marker credential. */\nexport async function loginGrokProxy(options: {\n\tonProgress?: OAuthLoginCallbacks[\"onProgress\"];\n\tsignal?: AbortSignal;\n}): Promise<OAuthCredentials> {\n\toptions.onProgress?.(`Checking local Grok proxy health at ${getBaseUrl()}...`);\n\tawait checkProxyHealth(options.signal);\n\toptions.onProgress?.(\"Grok proxy is reachable.\");\n\treturn markerCredentials();\n}\n\nfunction grokModels(baseUrl: string): Model<\"openai-completions\">[] {\n\tconst shared = {\n\t\tapi: \"openai-completions\" as const,\n\t\tprovider: GROK_PROXY_PROVIDER_ID,\n\t\tbaseUrl,\n\t\tinput: [\"text\", \"image\"] as (\"text\" | \"image\")[],\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n\t\tcontextWindow: 256_000,\n\t\tmaxTokens: 32_768,\n\t};\n\treturn [\n\t\t{ ...shared, id: \"grok-4.5\", name: \"Grok 4.5\", reasoning: true },\n\t\t{ ...shared, id: \"grok-4.3\", name: \"Grok 4.3\", reasoning: true },\n\t];\n}\n\nexport const grokProxyOAuthProvider: OAuthProviderInterface = {\n\tid: GROK_PROXY_PROVIDER_ID,\n\tname: \"Grok (xAI OAuth Proxy)\",\n\tusesCallbackServer: false,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGrokProxy({ onProgress: callbacks.onProgress, signal: callbacks.signal });\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\t// The proxy owns real token refresh; we only re-verify reachability.\n\t\tawait checkProxyHealth();\n\t\treturn { ...credentials, expires: Date.now() + MARKER_TTL_MS };\n\t},\n\n\tgetApiKey(): string {\n\t\treturn getLocalApiKey();\n\t},\n\n\tmodifyModels(models: Model<Api>[]): Model<Api>[] {\n\t\t// Non-destructive: keep any user-configured grok-oauth-proxy models, only add missing defaults.\n\t\tconst existingIds = new Set(\n\t\t\tmodels.filter((model) => model.provider === GROK_PROXY_PROVIDER_ID).map((model) => model.id),\n\t\t);\n\t\tconst additions = grokModels(getBaseUrl()).filter((model) => !existingIds.has(model.id));\n\t\treturn [...models, ...additions];\n\t},\n};\n"]}