Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/background/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
isUsingAimlApiModel,
isUsingDeepSeekApiModel,
isUsingGoogleApiModel,
isUsingXaiApiModel,
} from '../config/index.mjs'
import '../_locales/i18n'
import { openUrl } from '../utils/open-url'
Expand Down Expand Up @@ -402,6 +403,7 @@ function isUsingOpenAICompatibleApiSession(session) {
isUsingOpenRouterApiModel(session) ||
isUsingAimlApiModel(session) ||
isUsingGoogleApiModel(session) ||
isUsingXaiApiModel(session) ||
isUsingGptCompletionApiModel(session)
)
}
Expand Down
19 changes: 19 additions & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export const googleApiModelKeys = [
'googleGemini2_5Flash',
'googleGemini2_5FlashLite',
]
export const xaiApiModelKeys = ['xaiGrok4_5', 'xaiGrok4_3']

export const AlwaysCustomGroups = [
'ollamaApiModelKeys',
Expand Down Expand Up @@ -265,6 +266,10 @@ export const ModelGroups = {
value: googleApiModelKeys,
desc: 'Google (API)',
},
xaiApiModelKeys: {
value: xaiApiModelKeys,
desc: 'xAI (API)',
},
customApiModelKeys: {
value: customApiModelKeys,
desc: 'Custom Model',
Expand Down Expand Up @@ -641,6 +646,14 @@ export const Models = {
value: 'gemini-2.5-flash-lite',
desc: 'Google (Gemini 2.5 Flash-Lite)',
},
xaiGrok4_5: {
value: 'grok-4.5',
desc: 'xAI (Grok 4.5)',
},
xaiGrok4_3: {
value: 'grok-4.3',
desc: 'xAI (Grok 4.3)',
},
}

for (const modelName in Models) {
Expand Down Expand Up @@ -710,6 +723,7 @@ export const defaultConfig = {
openRouterApiKey: '',
aimlApiKey: '',
googleApiKey: '',
xaiApiKey: '',

// advanced

Expand Down Expand Up @@ -745,6 +759,7 @@ export const defaultConfig = {
'chatgptApi5_6Sol',
'chatgptApi5_6Terra',
'chatgptApi5_6Luna',
'xaiGrok4_5',
'claudeOpus48Api',
'claudeSonnet5Api',
'claudeHaiku45Api',
Expand Down Expand Up @@ -911,6 +926,10 @@ export function isUsingGoogleApiModel(configOrSession) {
return isInApiModeGroup(googleApiModelKeys, configOrSession)
}

export function isUsingXaiApiModel(configOrSession) {
return isInApiModeGroup(xaiApiModelKeys, configOrSession)
}

export function isUsingChatGLMApiModel(configOrSession) {
return isInApiModeGroup(chatglmApiModelKeys, configOrSession)
}
Expand Down
2 changes: 2 additions & 0 deletions src/config/openai-provider-mappings.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const LEGACY_API_KEY_FIELD_BY_PROVIDER_ID = {
chatglm: 'chatglmApiKey',
ollama: 'ollamaApiKey',
google: 'googleApiKey',
xai: 'xaiApiKey',
'legacy-custom-default': 'customApiKey',
}

Expand All @@ -28,5 +29,6 @@ export const OPENAI_COMPATIBLE_GROUP_TO_PROVIDER_ID = {
chatglmApiModelKeys: 'chatglm',
ollamaApiModelKeys: 'ollama',
googleApiModelKeys: 'google',
xaiApiModelKeys: 'xai',
customApiModelKeys: 'legacy-custom-default',
}
2 changes: 2 additions & 0 deletions src/popup/sections/GeneralPart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ function getProviderApiKeySetupUrl(providerId) {
switch (String(providerId || '').trim()) {
case 'openai':
return 'https://platform.openai.com/account/api-keys'
case 'xai':
return 'https://console.x.ai/team/default/api-keys'
case 'moonshot':
return 'https://platform.moonshot.cn/console/api-keys'
default:
Expand Down
9 changes: 9 additions & 0 deletions src/services/apis/provider-registry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ const BUILTIN_PROVIDER_TEMPLATE = [
builtin: true,
enabled: true,
},
{
id: 'xai',
name: 'xAI',
baseUrl: 'https://api.x.ai/v1',
chatCompletionsPath: '/chat/completions',
builtin: true,
enabled: true,
},
{
id: 'legacy-custom-default',
name: 'Custom Model (Legacy)',
Expand Down Expand Up @@ -120,6 +128,7 @@ function resolveProviderIdFromLegacyModelName(modelName) {
}
if (preset.startsWith('chatglm') || preset === 'chatglmApiModelKeys') return 'chatglm'
if (preset.startsWith('googleGemini') || preset === 'googleApiModelKeys') return 'google'
if (preset.startsWith('xaiGrok') || preset === 'xaiApiModelKeys') return 'xai'
if (preset === 'customApiModelKeys') return 'legacy-custom-default'

return null
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/config/config-predicates.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
claudeApiModelKeys,
openRouterApiModelKeys,
aimlApiModelKeys,
xaiApiModelKeys,
isUsingAimlApiModel,
isUsingAzureOpenAiApiModel,
isUsingBingWebModel,
Expand All @@ -28,6 +29,7 @@ import {
isUsingOpenAiApiModel,
isUsingGptCompletionApiModel,
isUsingOpenRouterApiModel,
isUsingXaiApiModel,
} from '../../../src/config/index.mjs'
import {
LEGACY_MODEL_KEY_MIGRATIONS,
Expand Down Expand Up @@ -242,6 +244,13 @@ test('isUsingOpenRouterApiModel accepts exported OpenRouter API model keys', ()
}
})

test('isUsingXaiApiModel accepts exported xAI API model keys', () => {
for (const modelName of xaiApiModelKeys) {
assert.equal(isUsingXaiApiModel({ modelName }), true)
}
assert.equal(isUsingXaiApiModel({ modelName: 'chatgptApi4oMini' }), false)
})

test('isUsingAimlApiModel matches representative AI/ML API keys', () => {
for (const modelName of representativeAimlApiModelNames) {
assert.equal(isUsingAimlApiModel({ modelName }), true)
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/services/apis/provider-registry.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2578,6 +2578,11 @@ test('resolveOpenAICompatibleRequest avoids duplicate /v1 for custom provider ba
assert.equal(resolved.requestUrl, 'https://proxy.example.com/v1/chat/completions')
})

test('resolveProviderIdForSession resolves legacy xAI model names to xai provider', () => {
assert.equal(resolveProviderIdForSession({ modelName: 'xaiGrok4_5' }), 'xai')
assert.equal(resolveProviderIdForSession({ modelName: 'xaiApiModelKeys-custom' }), 'xai')
})

Comment thread
coderabbitai[bot] marked this conversation as resolved.
test('resolveOpenAICompatibleRequest preserves /v1 for custom provider baseUrl with explicit non-default paths', () => {
const config = {
customOpenAIProviders: [
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/services/apis/thin-adapters.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ const adapters = [
expectedBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',
expectedApiKey: 'glm-key',
},
{
name: 'xai-api',
apiMode: { groupName: 'xaiApiModelKeys', itemName: 'xaiGrok4_5' },
providerId: 'xai',
expectedBaseUrl: 'https://api.x.ai/v1',
expectedApiKey: 'xai-key',
},
]

for (const adapter of adapters) {
Expand Down