diff --git a/src/converters/register-converters.js b/src/converters/register-converters.js index 54286410..cc849021 100644 --- a/src/converters/register-converters.js +++ b/src/converters/register-converters.js @@ -11,6 +11,7 @@ import { ClaudeConverter } from './strategies/ClaudeConverter.js'; import { GeminiConverter } from './strategies/GeminiConverter.js'; import { CodexConverter } from './strategies/CodexConverter.js'; import { GrokConverter } from './strategies/GrokConverter.js'; +import { SuperGrokConverter } from './strategies/SuperGrokConverter.js'; /** * 注册所有转换器到工厂 @@ -23,7 +24,8 @@ export function registerAllConverters() { ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.GEMINI, GeminiConverter); ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.CODEX, CodexConverter); ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.GROK, GrokConverter); + ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.SUPERGROK, SuperGrokConverter); } // 自动注册所有转换器 -registerAllConverters(); \ No newline at end of file +registerAllConverters(); diff --git a/src/converters/strategies/SuperGrokConverter.js b/src/converters/strategies/SuperGrokConverter.js new file mode 100644 index 00000000..818f6ae2 --- /dev/null +++ b/src/converters/strategies/SuperGrokConverter.js @@ -0,0 +1 @@ +export { GrokConverter as SuperGrokConverter } from './GrokConverter.js'; diff --git a/src/providers/adapter.js b/src/providers/adapter.js index bfaceacd..4c57f357 100644 --- a/src/providers/adapter.js +++ b/src/providers/adapter.js @@ -9,6 +9,7 @@ import { IFlowApiService } from './openai/iflow-core.js'; import { CodexApiService } from './openai/codex-core.js'; import { ForwardApiService } from './forward/forward-core.js'; import { GrokApiService } from './grok/grok-core.js'; +import { SuperGrokApiService } from './grok/supergrok-core.js'; import { MODEL_PROVIDER } from '../utils/common.js'; import logger from '../utils/logger.js'; @@ -688,6 +689,54 @@ export class GrokApiServiceAdapter extends ApiServiceAdapter { } } +// SuperGrok API 服务适配器 +export class SuperGrokApiServiceAdapter extends ApiServiceAdapter { + constructor(config) { + super(); + this.superGrokApiService = new SuperGrokApiService(config); + } + + async generateContent(model, requestBody) { + if (!this.superGrokApiService.isInitialized) { + await this.superGrokApiService.initialize(); + } + return this.superGrokApiService.generateContent(model, requestBody); + } + + async *generateContentStream(model, requestBody) { + if (!this.superGrokApiService.isInitialized) { + await this.superGrokApiService.initialize(); + } + yield* this.superGrokApiService.generateContentStream(model, requestBody); + } + + async listModels() { + if (!this.superGrokApiService.isInitialized) { + await this.superGrokApiService.initialize(); + } + return this.superGrokApiService.listModels(); + } + + async refreshToken() { + return this.superGrokApiService.refreshToken(); + } + + async forceRefreshToken() { + return this.superGrokApiService.refreshToken(); + } + + isExpiryDateNear() { + return this.superGrokApiService.isExpiryDateNear(); + } + + async getUsageLimits() { + if (!this.superGrokApiService.isInitialized) { + await this.superGrokApiService.initialize(); + } + return this.superGrokApiService.getUsageLimits(); + } +} + // 注册所有内置适配器 registerAdapter(MODEL_PROVIDER.OPENAI_CUSTOM, OpenAIApiServiceAdapter); registerAdapter(MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES, OpenAIResponsesApiServiceAdapter); @@ -699,6 +748,7 @@ registerAdapter(MODEL_PROVIDER.QWEN_API, QwenApiServiceAdapter); // registerAdapter(MODEL_PROVIDER.IFLOW_API, IFlowApiServiceAdapter); registerAdapter(MODEL_PROVIDER.CODEX_API, CodexApiServiceAdapter); registerAdapter(MODEL_PROVIDER.GROK_CUSTOM, GrokApiServiceAdapter); +registerAdapter(MODEL_PROVIDER.SUPERGROK_CUSTOM, SuperGrokApiServiceAdapter); // registerAdapter(MODEL_PROVIDER.FORWARD_API, ForwardApiServiceAdapter); // 用于存储服务适配器单例的映射 @@ -721,4 +771,3 @@ export function getServiceAdapter(config) { } return serviceInstances[providerKey]; } - diff --git a/src/providers/grok/grok-core.js b/src/providers/grok/grok-core.js index 5e854ad6..2eabdeb0 100644 --- a/src/providers/grok/grok-core.js +++ b/src/providers/grok/grok-core.js @@ -45,7 +45,8 @@ const CORE_MODEL_MAPPING = { 'grok-4.1-fast': { name: 'grok-4-1-thinking-1129', mode: 'MODEL_MODE_FAST' }, 'grok-4.1-expert': { name: 'grok-4-1-thinking-1129', mode: 'MODEL_MODE_EXPERT' }, 'grok-4.1-thinking': { name: 'grok-4-1-thinking-1129', mode: 'MODEL_MODE_GROK_4_1_THINKING' }, - 'grok-4.20-beta': { name: 'grok-420', mode: 'MODEL_MODE_GROK_420' }, + 'grok-4.20-beta': { name: 'grok-420', mode: 'MODEL_MODE_EXPERT', modeId: 'expert' }, + 'grok-4.20-fast': { name: 'grok-420', mode: 'MODEL_MODE_FAST', modeId: 'fast' }, 'grok-imagine-1.0': { name: 'grok-3', mode: 'MODEL_MODE_FAST' }, 'grok-imagine-1.0-edit': { name: 'imagine-image-edit', mode: 'MODEL_MODE_FAST' }, 'grok-imagine-1.0-video': { name: 'grok-3', mode: 'MODEL_MODE_FAST' } @@ -421,6 +422,14 @@ export class GrokApiService { "returnImageBytes": false, "returnRawGrokInXaiRequest": false, "sendFinalMetadata": true, "temporary": true, "toolOverrides": toolOverrides, }; + if (mapping.name === 'grok-420') { + payload.responseMetadata = {}; + delete payload.modelName; + delete payload.modelMode; + payload.modeId = mapping.modeId || 'expert'; + payload.enable420 = true; + } + if (isMediaModel && !modelLower.includes('video')) { payload.enable_nsfw = isNsfw; if (requestBody.aspect_ratio || requestBody.aspectRatio) { diff --git a/src/providers/grok/supergrok-core.js b/src/providers/grok/supergrok-core.js new file mode 100644 index 00000000..764e7d4d --- /dev/null +++ b/src/providers/grok/supergrok-core.js @@ -0,0 +1,14 @@ +import { GrokApiService } from './grok-core.js'; + +export class SuperGrokApiService extends GrokApiService { + constructor(config) { + const mergedConfig = { + ...config, + GROK_COOKIE_TOKEN: config.GROK_COOKIE_TOKEN || config.SUPERGROK_COOKIE_TOKEN, + GROK_CF_CLEARANCE: config.GROK_CF_CLEARANCE || config.SUPERGROK_CF_CLEARANCE, + GROK_USER_AGENT: config.GROK_USER_AGENT || config.SUPERGROK_USER_AGENT, + GROK_BASE_URL: config.GROK_BASE_URL || config.SUPERGROK_BASE_URL, + }; + super(mergedConfig); + } +} diff --git a/src/providers/grok/supergrok-strategy.js b/src/providers/grok/supergrok-strategy.js new file mode 100644 index 00000000..ae474551 --- /dev/null +++ b/src/providers/grok/supergrok-strategy.js @@ -0,0 +1 @@ +export { GrokStrategy as SuperGrokStrategy } from './grok-strategy.js'; diff --git a/src/providers/provider-models.js b/src/providers/provider-models.js index 4998f714..00bb0579 100644 --- a/src/providers/provider-models.js +++ b/src/providers/provider-models.js @@ -103,6 +103,25 @@ export const PROVIDER_MODELS = { 'grok-4.1-expert', 'grok-4.1-thinking', 'grok-4.20-beta', + 'grok-4.20-fast', + 'grok-imagine-1.0', + 'grok-imagine-1.0-edit', + 'grok-imagine-1.0-video' + ], + 'supergrok-custom': [ + 'grok-3', + 'grok-3-mini', + 'grok-3-thinking', + 'grok-4', + 'grok-4-mini', + 'grok-4-thinking', + 'grok-4-heavy', + 'grok-4.1-mini', + 'grok-4.1-fast', + 'grok-4.1-expert', + 'grok-4.1-thinking', + 'grok-4.20-beta', + 'grok-4.20-fast', 'grok-imagine-1.0', 'grok-imagine-1.0-edit', 'grok-imagine-1.0-video' diff --git a/src/providers/provider-pool-manager.js b/src/providers/provider-pool-manager.js index a6a67835..c97d66e0 100644 --- a/src/providers/provider-pool-manager.js +++ b/src/providers/provider-pool-manager.js @@ -24,6 +24,8 @@ export class ProviderPoolManager { 'openai-codex-oauth': 'gpt-5-codex-mini', 'openaiResponses-custom': 'gpt-4o-mini', 'forward-api': 'gpt-4o-mini', + 'grok-custom': 'grok-3', + 'supergrok-custom': 'grok-3', }; constructor(providerPools, options = {}) { @@ -1911,4 +1913,3 @@ export class ProviderPoolManager { } } - diff --git a/src/services/service-manager.js b/src/services/service-manager.js index 747518f4..48c26b0c 100644 --- a/src/services/service-manager.js +++ b/src/services/service-manager.js @@ -563,7 +563,8 @@ export async function getProviderStatus(config, options = {}) { 'gemini-antigravity': 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH', 'openai-iflow': 'IFLOW_TOKEN_FILE_PATH', 'forward-api': 'FORWARD_BASE_URL', - 'grok-custom': 'GROK_COOKIE_TOKEN' + 'grok-custom': 'GROK_COOKIE_TOKEN', + 'supergrok-custom': 'SUPERGROK_COOKIE_TOKEN' }; let providerPoolsSlim = []; let unhealthyProvideIdentifyList = []; diff --git a/src/services/usage-service.js b/src/services/usage-service.js index e4603470..2cdf8944 100644 --- a/src/services/usage-service.js +++ b/src/services/usage-service.js @@ -19,6 +19,7 @@ export class UsageService { [MODEL_PROVIDER.ANTIGRAVITY]: this.getAntigravityUsage.bind(this), [MODEL_PROVIDER.CODEX_API]: this.getCodexUsage.bind(this), [MODEL_PROVIDER.GROK_CUSTOM]: this.getGrokUsage.bind(this), + [MODEL_PROVIDER.SUPERGROK_CUSTOM]: this.getSuperGrokUsage.bind(this), }; } @@ -208,6 +209,27 @@ export class UsageService { throw new Error(`Grok 服务实例不支持用量查询: ${providerKey}`); } + /** + * 获取 SuperGrok 提供商的用量信息 + * @param {string} [uuid] - 可选的提供商实例 UUID + * @returns {Promise} SuperGrok 用量信息 + */ + async getSuperGrokUsage(uuid = null) { + const providerKey = uuid ? MODEL_PROVIDER.SUPERGROK_CUSTOM + uuid : MODEL_PROVIDER.SUPERGROK_CUSTOM; + const adapter = serviceInstances[providerKey]; + + if (!adapter) { + throw new Error(`SuperGrok 服务实例未找到: ${providerKey}`); + } + + if (typeof adapter.getUsageLimits === 'function') { + const rawUsage = await adapter.getUsageLimits(); + return formatGrokUsage(rawUsage); + } + + throw new Error(`SuperGrok 服务实例不支持用量查询: ${providerKey}`); + } + /** * 获取支持用量查询的提供商列表 diff --git a/src/ui-modules/usage-api.js b/src/ui-modules/usage-api.js index e9c6329d..37eed35b 100644 --- a/src/ui-modules/usage-api.js +++ b/src/ui-modules/usage-api.js @@ -6,7 +6,7 @@ import { readUsageCache, writeUsageCache, readProviderUsageCache, updateProvider import { PROVIDER_MAPPINGS } from '../utils/provider-utils.js'; import path from 'path'; -const supportedProviders = ['claude-kiro-oauth', 'gemini-cli-oauth', 'gemini-antigravity', 'openai-codex-oauth', 'grok-custom']; +const supportedProviders = ['claude-kiro-oauth', 'gemini-cli-oauth', 'gemini-antigravity', 'openai-codex-oauth', 'grok-custom', 'supergrok-custom']; /** @@ -190,6 +190,14 @@ async function getAdapterUsage(adapter, providerType) { } throw new Error('This adapter does not support usage query'); } + + if (providerType === 'supergrok-custom') { + if (typeof adapter.getUsageLimits === 'function') { + const rawUsage = await adapter.getUsageLimits(); + return formatGrokUsage(rawUsage); + } + throw new Error('This adapter does not support usage query'); + } throw new Error(`Unsupported provider type: ${providerType}`); } @@ -353,4 +361,4 @@ export async function handleGetProviderUsage(req, res, currentConfig, providerPo })); return true; } -} \ No newline at end of file +} diff --git a/src/utils/common.js b/src/utils/common.js index c031ea42..e5d3f5b0 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -58,6 +58,7 @@ export const MODEL_PROTOCOL_PREFIX = { CODEX: 'codex', FORWARD: 'forward', GROK: 'grok', + SUPERGROK: 'supergrok', } export const MODEL_PROVIDER = { @@ -73,6 +74,7 @@ export const MODEL_PROVIDER = { CODEX_API: 'openai-codex-oauth', FORWARD_API: 'forward-api', GROK_CUSTOM: 'grok-custom', + SUPERGROK_CUSTOM: 'supergrok-custom', AUTO: 'auto', } diff --git a/src/utils/provider-strategies.js b/src/utils/provider-strategies.js index fd875f58..8fa5763c 100644 --- a/src/utils/provider-strategies.js +++ b/src/utils/provider-strategies.js @@ -6,6 +6,7 @@ import { ResponsesAPIStrategy } from '../providers/openai/openai-responses-strat import { CodexResponsesAPIStrategy } from '../providers/openai/codex-responses-strategy.js'; import { ForwardStrategy } from '../providers/forward/forward-strategy.js'; import { GrokStrategy } from '../providers/grok/grok-strategy.js'; +import { SuperGrokStrategy } from '../providers/grok/supergrok-strategy.js'; /** * Strategy factory that returns the appropriate strategy instance based on the provider protocol. @@ -27,6 +28,8 @@ class ProviderStrategyFactory { return new ForwardStrategy(); case MODEL_PROTOCOL_PREFIX.GROK: return new GrokStrategy(); + case MODEL_PROTOCOL_PREFIX.SUPERGROK: + return new SuperGrokStrategy(); default: throw new Error(`Unsupported provider protocol: ${providerProtocol}`); } diff --git a/src/utils/provider-utils.js b/src/utils/provider-utils.js index 8f1f8549..1f74eb1b 100644 --- a/src/utils/provider-utils.js +++ b/src/utils/provider-utils.js @@ -88,6 +88,17 @@ export const PROVIDER_MAPPINGS = [ displayName: 'Grok Reverse', needsProjectId: false, urlKeys: ['GROK_BASE_URL', 'GROK_CF_CLEARANCE', 'GROK_USER_AGENT'] + }, + { + // SuperGrok Reverse 配置 + dirName: 'supergrok', + patterns: ['configs/supergrok/', '/supergrok/'], + providerType: 'supergrok-custom', + credPathKey: 'SUPERGROK_COOKIE_TOKEN', + defaultCheckModel: 'grok-3', + displayName: 'SuperGrok Reverse', + needsProjectId: false, + urlKeys: ['SUPERGROK_BASE_URL', 'SUPERGROK_CF_CLEARANCE', 'SUPERGROK_USER_AGENT'] } ]; @@ -385,4 +396,4 @@ export function isPathLinked(relativePath, linkedPaths) { return linkedPaths.has(relativePath) || linkedPaths.has('./' + relativePath) || linkedPaths.has(relativePath.replace(/^\.\//, '')); -} \ No newline at end of file +} diff --git a/static/app/i18n.js b/static/app/i18n.js index 2d1739b6..23f7d105 100644 --- a/static/app/i18n.js +++ b/static/app/i18n.js @@ -91,6 +91,7 @@ const translations = { 'dashboard.routing.nodeName.iflow': 'iFlow OAuth', 'dashboard.routing.nodeName.codex': 'OpenAI Codex OAuth', 'dashboard.routing.nodeName.grok': 'Grok Reverse', + 'dashboard.routing.nodeName.supergrok': 'SuperGrok Reverse', 'dashboard.contact.title': '联系与赞助', 'dashboard.contact.wechat': '扫码进群,注明来意', 'dashboard.contact.wechatDesc': '添加微信获取更多技术支持和交流', @@ -936,6 +937,7 @@ const translations = { 'dashboard.routing.nodeName.iflow': 'iFlow OAuth', 'dashboard.routing.nodeName.codex': 'OpenAI Codex OAuth', 'dashboard.routing.nodeName.grok': 'Grok Reverse', + 'dashboard.routing.nodeName.supergrok': 'SuperGrok Reverse', 'dashboard.contact.title': 'Contact & Support', 'dashboard.contact.wechat': 'Scan to Join Group', 'dashboard.contact.wechatDesc': 'Add WeChat for more technical support and communication', diff --git a/static/app/modal.js b/static/app/modal.js index f9060307..a6cddf71 100644 --- a/static/app/modal.js +++ b/static/app/modal.js @@ -689,6 +689,7 @@ function getFieldOrder(provider) { 'openai-iflow': ['IFLOW_OAUTH_CREDS_FILE_PATH', 'IFLOW_BASE_URL'], 'openai-codex-oauth': ['CODEX_OAUTH_CREDS_FILE_PATH', 'CODEX_EMAIL', 'CODEX_BASE_URL'], 'grok-custom': ['GROK_COOKIE_TOKEN', 'GROK_CF_CLEARANCE', 'GROK_USER_AGENT', 'GROK_BASE_URL'], + 'supergrok-custom': ['SUPERGROK_COOKIE_TOKEN', 'SUPERGROK_CF_CLEARANCE', 'SUPERGROK_USER_AGENT', 'SUPERGROK_BASE_URL'], 'forward-api': ['FORWARD_API_KEY', 'FORWARD_BASE_URL', 'FORWARD_HEADER_NAME', 'FORWARD_HEADER_VALUE_PREFIX'] }; @@ -713,6 +714,8 @@ function getFieldOrder(provider) { providerType = 'openai-codex-oauth'; } else if (provider.GROK_COOKIE_TOKEN) { providerType = 'grok-custom'; + } else if (provider.SUPERGROK_COOKIE_TOKEN) { + providerType = 'supergrok-custom'; } else if (provider.FORWARD_API_KEY) { providerType = 'forward-api'; } @@ -1638,4 +1641,4 @@ window.performHealthCheck = performHealthCheck; window.deleteUnhealthyProviders = deleteUnhealthyProviders; window.refreshUnhealthyUuids = refreshUnhealthyUuids; window.goToProviderPage = goToProviderPage; -window.refreshProviderUuid = refreshProviderUuid; \ No newline at end of file +window.refreshProviderUuid = refreshProviderUuid; diff --git a/static/app/usage-manager.js b/static/app/usage-manager.js index 62c2b20d..968f28a5 100644 --- a/static/app/usage-manager.js +++ b/static/app/usage-manager.js @@ -898,7 +898,8 @@ function getProviderDisplayName(providerType) { 'gemini-antigravity': 'Gemini Antigravity', 'openai-codex-oauth': 'Codex OAuth', 'openai-qwen-oauth': 'Qwen OAuth', - 'grok-custom': 'Grok Reverse' + 'grok-custom': 'Grok Reverse', + 'supergrok-custom': 'SuperGrok Reverse' }; return names[providerType] || providerType; } @@ -924,7 +925,8 @@ function getProviderIcon(providerType) { 'gemini-antigravity': 'fas fa-rocket', 'openai-codex-oauth': 'fas fa-terminal', 'openai-qwen-oauth': 'fas fa-code', - 'grok-custom': 'fas fa-brain' + 'grok-custom': 'fas fa-brain', + 'supergrok-custom': 'fas fa-user-astronaut' }; return icons[providerType] || 'fas fa-server'; } @@ -996,4 +998,4 @@ function formatDate(dateStr) { } catch (e) { return dateStr; } -} \ No newline at end of file +} diff --git a/static/app/utils.js b/static/app/utils.js index f796c454..75cd6754 100644 --- a/static/app/utils.js +++ b/static/app/utils.js @@ -63,6 +63,12 @@ function getProviderConfigs(supportedProviders = []) { icon: 'fa-user-secret', visible: supportedProviders.includes('grok-custom') }, + { + id: 'supergrok-custom', + name: t('dashboard.routing.nodeName.supergrok'), + icon: 'fa-user-astronaut', + visible: supportedProviders.includes('supergrok-custom') + }, { id: 'openai-custom', name: t('dashboard.routing.nodeName.openai'), @@ -392,6 +398,32 @@ function getProviderTypeFields(providerType) { placeholder: 'https://grok.com' } ], + 'supergrok-custom': [ + { + id: 'SUPERGROK_COOKIE_TOKEN', + label: t('modal.provider.field.ssoToken'), + type: 'password', + placeholder: 'sso cookie token' + }, + { + id: 'SUPERGROK_CF_CLEARANCE', + label: `${t('modal.provider.field.cfClearance')} ${t('config.optional')}`, + type: 'text', + placeholder: 'cf_clearance cookie value' + }, + { + id: 'SUPERGROK_USER_AGENT', + label: `${t('modal.provider.field.userAgent')} ${t('config.optional')}`, + type: 'text', + placeholder: 'Mozilla/5.0 ...' + }, + { + id: 'SUPERGROK_BASE_URL', + label: `${t('modal.provider.field.grokBaseUrl')} ${t('config.optional')}`, + type: 'text', + placeholder: 'https://grok.com' + } + ], 'forward-api': [ { id: 'FORWARD_API_KEY', @@ -463,4 +495,4 @@ export { getProviderConfigs, getProviderStats, apiRequest -}; \ No newline at end of file +}; diff --git a/static/components/section-config.html b/static/components/section-config.html index 37e0fe18..c71313b7 100644 --- a/static/components/section-config.html +++ b/static/components/section-config.html @@ -74,6 +74,10 @@

基础设置

Grok Reverse + 点击选择启动时初始化的模型提供商 (必须至少选择一个) @@ -130,6 +134,10 @@

代理设置

Grok Reverse + 点击选择需要通过代理访问的提供商,未选中的提供商将直接连接 diff --git a/tests/grok-420-payload.test.js b/tests/grok-420-payload.test.js new file mode 100644 index 00000000..887975fc --- /dev/null +++ b/tests/grok-420-payload.test.js @@ -0,0 +1,19 @@ +import fs from 'fs'; +import path from 'path'; +const grokCorePath = path.join(process.cwd(), 'src/providers/grok/grok-core.js'); + +describe('Grok 4.20 payload format implementation', () => { + const content = fs.readFileSync(grokCorePath, 'utf8'); + + test('adds grok-4.20-fast model mapping', () => { + expect(content).toContain("'grok-4.20-fast': { name: 'grok-420', mode: 'MODEL_MODE_FAST', modeId: 'fast' }"); + }); + + test('uses modeId format for grok-420 payload', () => { + expect(content).toContain("if (mapping.name === 'grok-420')"); + expect(content).toContain("delete payload.modelName;"); + expect(content).toContain("delete payload.modelMode;"); + expect(content).toContain("payload.modeId = mapping.modeId || 'expert';"); + expect(content).toContain("payload.enable420 = true;"); + }); +}); diff --git a/tests/supergrok-provider.test.js b/tests/supergrok-provider.test.js new file mode 100644 index 00000000..396c2165 --- /dev/null +++ b/tests/supergrok-provider.test.js @@ -0,0 +1,33 @@ +import fs from 'fs'; +import path from 'path'; +import { MODEL_PROTOCOL_PREFIX, MODEL_PROVIDER } from '../src/utils/common.js'; +import { getProviderModels } from '../src/providers/provider-models.js'; +import { ConverterFactory } from '../src/converters/ConverterFactory.js'; +import '../src/converters/register-converters.js'; + +describe('SuperGrok provider integration', () => { + test('registers SuperGrok provider constants', () => { + expect(MODEL_PROTOCOL_PREFIX.SUPERGROK).toBe('supergrok'); + expect(MODEL_PROVIDER.SUPERGROK_CUSTOM).toBe('supergrok-custom'); + }); + + test('registers SuperGrok model list and converter', () => { + const models = getProviderModels('supergrok-custom'); + expect(models).toContain('grok-4.20-beta'); + expect(models).toContain('grok-4.20-fast'); + + const converter = ConverterFactory.getConverter('supergrok'); + expect(converter).toBeTruthy(); + expect(typeof converter.convertRequest).toBe('function'); + }); + + test('wires SuperGrok adapter registration and core alias', () => { + const adapterPath = path.join(process.cwd(), 'src/providers/adapter.js'); + const superGrokCorePath = path.join(process.cwd(), 'src/providers/grok/supergrok-core.js'); + const adapterContent = fs.readFileSync(adapterPath, 'utf8'); + const superGrokCoreContent = fs.readFileSync(superGrokCorePath, 'utf8'); + + expect(adapterContent).toContain('registerAdapter(MODEL_PROVIDER.SUPERGROK_CUSTOM, SuperGrokApiServiceAdapter);'); + expect(superGrokCoreContent).toContain('export class SuperGrokApiService extends GrokApiService'); + }); +});