feat(grok-cli): add Grok CLI / Grok Build provider with OAuth device-code flow (#2502)

New OAuth provider routing through cli-chat-proxy.grok.com (OpenAI Responses
API), distinct from xai (api.x.ai) and grok-web (cookie SSO):

- Registry + GrokCliExecutor: Chat Completions -> Responses transform, CLI
  fingerprint headers, virtual effort models grok-4.5-{low,medium,high}
- OAuth device-code flow (auth.x.ai) with no-PKCE, shared xAI token refresh
- store=false multi-turn continuity via reasoning encrypted_content
- Quota tracker: on-demand window + prepaid balance on dashboard
- Connection test: 402 spending-limit = soft success (auth OK, out of credits)
- Alias/oauth/provider baselines + unit tests
This commit is contained in:
Fadjrir Herlambang
2026-07-10 11:47:08 +07:00
committed by decolua
parent c73c419d09
commit a11937cdd6
28 changed files with 2695 additions and 376 deletions
+22 -2
View File
@@ -156,8 +156,17 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
try {
setError(null);
// Device code flow providers
const deviceCodeProviders = ["github", "qwen", "kiro", "kimi-coding", "kilocode", "codebuddy-cn", "qoder"];
// Device code flow providers (must match oauth providers with flowType: "device_code")
const deviceCodeProviders = [
"github",
"qwen",
"kiro",
"kimi-coding",
"kilocode",
"codebuddy-cn",
"qoder",
"grok-cli",
];
if (deviceCodeProviders.includes(provider)) {
setIsDeviceCode(true);
setStep("waiting");
@@ -277,6 +286,17 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
setAuthData({ ...data, redirectUri, codexServerSide, xaiServerSide });
// Guard: device_code providers return authUrl:null from /authorize. Never window.open(null)
// (browsers coerce it to the relative path ".../null").
if (!data.authUrl) {
if (data.flowType === "device_code") {
throw new Error(
`Provider ${provider} uses device-code login but is not wired in the OAuth modal device-code list`
);
}
throw new Error("No authorization URL returned from OAuth provider");
}
if (provider === "codex" && codexProxyActive) {
// Proxy active: callback will be handled server-side (auto-exchange) or via channels (fallback)
setStep("waiting");