feat: add Grok (grok.com) video provider; runway nano-banana image; unify runway/grok pool policy

- grok provider: imagine text/image-to-video (media.post.create → conversations/new),
  GetGrokCreditsConfig credit query (remaining = 100 - used) + weekly reset, spoofed
  x-statsig-id (no Cloudflare clearance needed), /api/auth/session email lookup,
  6 reference images, 10 concurrent jobs/account, no token refresh (dead = dead)
- runway: nano-banana-2 image flow (Nano Banana 2); drop pre-deduct + post-success
  reconcile; out-of-credits/403 → dead (no revive); 10-ratio support
- imagine: drop post-success credit reconcile (consistent with krea)
- account gate: per-account N-concurrency (grok=10, others=1)
- admin: provider health lists all 7 providers; frontend import auto-detects Grok SSO
- docs: README (CN/EN) updated to 7 providers + Grok

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 04:01:06 +08:00
co-authored by Claude Opus 4.8
parent 8633addf64
commit 17cd289dfd
16 changed files with 1442 additions and 166 deletions
+17 -3
View File
@@ -28,6 +28,16 @@ export function looksLikeRunwayJwt(s) {
return 'sso' in claims && claims.id != null
}
// Grok website "sso" JWTs carry ONLY a session_id claim (no openai claims, no
// runway id/sso) — that's what tells them apart from a ChatGPT/Runway JWT.
export function looksLikeGrokJwt(s) {
const claims = decodeJwtPayload(s)
if (!claims || typeof claims !== 'object') return false
if (Object.keys(claims).some((k) => k.startsWith('https://api.openai.com/'))) return false
if ('sso' in claims || claims.id != null) return false
return 'session_id' in claims
}
// Leonardo cookies carry the better-auth session cookie — that's what tells them
// apart from an Adobe cookie (both are otherwise opaque cookie strings).
export function looksLikeLeonardoCookie(s) {
@@ -104,11 +114,15 @@ export function parseImportInput(text) {
// treated as an Adobe cookie string.
const lines = text.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
return lines.map((line) => {
if (looksLikeJwt(line)) {
const value = line.replace(/^Bearer\s+/i, '')
// Accept a bare JWT or one with a leading `sso=` (grok cookie value form).
const stripped = line.replace(/^Bearer\s+/i, '').replace(/^sso=/, '')
if (looksLikeJwt(stripped)) {
const value = stripped
return looksLikeRunwayJwt(value)
? { type: 'runway', value }
: { type: 'openai', value }
: looksLikeGrokJwt(value)
? { type: 'grok', value }
: { type: 'openai', value }
}
return { type: cookieType(line), value: line }
})