Skip to content

Commit 3dc73a9

Browse files
yyhhyyyyyyzerob13
andauthored
feat: add reasoning support for Grok thinking models (#873)
* feat: add reasoning support for Grok thinking models * fix: code lint * fix: escaping character issue --------- Co-authored-by: zerob13 <zerob13@gmail.com>
1 parent 336a1b2 commit 3dc73a9

File tree

5 files changed

+113
-31
lines changed

5 files changed

+113
-31
lines changed

src/main/presenter/configPresenter/providerModelSettings.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3139,7 +3139,8 @@ export const providerModelSettings: Record<string, { models: ProviderModelSettin
31393139
match: ['grok-3-mini-fast', 'grok-3-mini-fast-latest', 'grok-3-mini-fast-beta'],
31403140
vision: false,
31413141
functionCall: true,
3142-
reasoning: true
3142+
reasoning: true,
3143+
reasoningEffort: 'low'
31433144
},
31443145
{
31453146
id: 'grok-3-mini-beta',
@@ -3150,7 +3151,8 @@ export const providerModelSettings: Record<string, { models: ProviderModelSettin
31503151
match: ['grok-3-mini', 'grok-3-mini-latest', 'grok-3-mini-beta'],
31513152
vision: false,
31523153
functionCall: true,
3153-
reasoning: true
3154+
reasoning: true,
3155+
reasoningEffort: 'low'
31543156
},
31553157
{
31563158
id: 'grok-3-fast-beta',

src/main/presenter/githubCopilotDeviceFlow.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,17 @@ export class GitHubCopilotDeviceFlow {
218218
const githubUrl = GITHUB_DEVICE_URL;
219219
// Try to copy link to clipboard
220220
await window.electronAPI.copyToClipboard(githubUrl);
221-
221+
222222
// Try to open browser
223223
window.electronAPI.openExternal(githubUrl);
224-
224+
225225
// Show fallback message
226226
setTimeout(() => {
227227
const msg = document.createElement('div');
228228
msg.style.fontSize = '12px';
229229
msg.style.color = '#0969da';
230230
msg.style.marginTop = '8px';
231-
msg.innerHTML = 'If the browser didn\'t open automatically, the link has been copied to clipboard. Please paste it into your browser address bar.';
231+
msg.innerHTML = 'If the browser did not open automatically, the link has been copied to clipboard. Please paste it into your browser address bar.';
232232
document.querySelector('.footer').appendChild(msg);
233233
}, 2000);
234234
} catch (error) {

src/main/presenter/llmProviderPresenter/providers/grokProvider.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ export class GrokProvider extends OpenAICompatibleProvider {
77
private static readonly IMAGE_MODEL_ID = 'grok-2-image'
88
// private static readonly IMAGE_ENDPOINT = '/images/generations'
99

10+
// Reasoning models that support reasoning_content
11+
private static readonly REASONING_MODELS: string[] = ['grok-4', 'grok-3-mini', 'grok-3-mini-fast']
12+
13+
// Models that support reasoning_effort parameter (grok-4 does not)
14+
private static readonly REASONING_EFFORT_MODELS: string[] = ['grok-3-mini', 'grok-3-mini-fast']
15+
1016
constructor(provider: LLM_PROVIDER, configPresenter: IConfigPresenter) {
1117
super(provider, configPresenter)
1218
}
@@ -16,6 +22,20 @@ export class GrokProvider extends OpenAICompatibleProvider {
1622
return modelId.startsWith(GrokProvider.IMAGE_MODEL_ID)
1723
}
1824

25+
// Check if model supports reasoning
26+
private isReasoningModel(modelId: string): boolean {
27+
return GrokProvider.REASONING_MODELS.some((model) =>
28+
modelId.toLowerCase().includes(model.toLowerCase())
29+
)
30+
}
31+
32+
// Check if model supports reasoning_effort parameter
33+
private supportsReasoningEffort(modelId: string): boolean {
34+
return GrokProvider.REASONING_EFFORT_MODELS.some((model) =>
35+
modelId.toLowerCase().includes(model.toLowerCase())
36+
)
37+
}
38+
1939
async completions(
2040
messages: ChatMessage[],
2141
modelId: string,
@@ -136,8 +156,12 @@ export class GrokProvider extends OpenAICompatibleProvider {
136156
modelConfig: ModelConfig,
137157
temperature: number,
138158
maxTokens: number,
139-
tools: MCPToolDefinition[]
159+
mcpTools: MCPToolDefinition[]
140160
): AsyncGenerator<LLMCoreStreamEvent> {
161+
if (!this.isInitialized) throw new Error('Provider not initialized')
162+
if (!modelId) throw new Error('Model ID is required')
163+
164+
// Handle image generation models
141165
if (this.isImageModel(modelId)) {
142166
const result = await this.handleImageGeneration(messages)
143167
// Use additional fields directly
@@ -158,8 +182,40 @@ export class GrokProvider extends OpenAICompatibleProvider {
158182
}
159183
// Add brief delay to ensure all RESPONSE events are processed
160184
await new Promise((resolve) => setTimeout(resolve, 300))
185+
return
186+
}
187+
188+
// Handle reasoning models
189+
if (this.isReasoningModel(modelId) && modelConfig?.reasoningEffort) {
190+
const originalCreate = this.openai.chat.completions.create.bind(this.openai.chat.completions)
191+
this.openai.chat.completions.create = ((params: any, options?: any) => {
192+
const modifiedParams = { ...params }
193+
194+
if (this.supportsReasoningEffort(modelId)) {
195+
modifiedParams.reasoning_effort = modelConfig.reasoningEffort
196+
}
197+
198+
return originalCreate(modifiedParams, options)
199+
}) as any
200+
201+
try {
202+
const effectiveModelConfig = {
203+
...modelConfig,
204+
reasoningEffort: undefined
205+
}
206+
yield* super.coreStream(
207+
messages,
208+
modelId,
209+
effectiveModelConfig,
210+
temperature,
211+
maxTokens,
212+
mcpTools
213+
)
214+
} finally {
215+
this.openai.chat.completions.create = originalCreate
216+
}
161217
} else {
162-
yield* super.coreStream(messages, modelId, modelConfig, temperature, maxTokens, tools)
218+
yield* super.coreStream(messages, modelId, modelConfig, temperature, maxTokens, mcpTools)
163219
}
164220
}
165221
}

src/renderer/src/components/ChatConfig.vue

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -646,18 +646,30 @@ const qwen3ThinkingBudgetError = computed(() => {
646646
/>
647647
</SelectTrigger>
648648
<SelectContent>
649-
<SelectItem value="minimal">{{
650-
t('settings.model.modelConfig.reasoningEffort.options.minimal')
651-
}}</SelectItem>
652-
<SelectItem value="low">{{
653-
t('settings.model.modelConfig.reasoningEffort.options.low')
654-
}}</SelectItem>
655-
<SelectItem value="medium">{{
656-
t('settings.model.modelConfig.reasoningEffort.options.medium')
657-
}}</SelectItem>
658-
<SelectItem value="high">{{
659-
t('settings.model.modelConfig.reasoningEffort.options.high')
660-
}}</SelectItem>
649+
<!-- Grok models only support low and high -->
650+
<template v-if="props.providerId === 'grok'">
651+
<SelectItem value="low">{{
652+
t('settings.model.modelConfig.reasoningEffort.options.low')
653+
}}</SelectItem>
654+
<SelectItem value="high">{{
655+
t('settings.model.modelConfig.reasoningEffort.options.high')
656+
}}</SelectItem>
657+
</template>
658+
<!-- Other models support all four options -->
659+
<template v-else>
660+
<SelectItem value="minimal">{{
661+
t('settings.model.modelConfig.reasoningEffort.options.minimal')
662+
}}</SelectItem>
663+
<SelectItem value="low">{{
664+
t('settings.model.modelConfig.reasoningEffort.options.low')
665+
}}</SelectItem>
666+
<SelectItem value="medium">{{
667+
t('settings.model.modelConfig.reasoningEffort.options.medium')
668+
}}</SelectItem>
669+
<SelectItem value="high">{{
670+
t('settings.model.modelConfig.reasoningEffort.options.high')
671+
}}</SelectItem>
672+
</template>
661673
</SelectContent>
662674
</Select>
663675
</div>

src/renderer/src/components/settings/ModelConfigDialog.vue

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,18 +153,30 @@
153153
/>
154154
</SelectTrigger>
155155
<SelectContent>
156-
<SelectItem value="minimal">{{
157-
t('settings.model.modelConfig.reasoningEffort.options.minimal')
158-
}}</SelectItem>
159-
<SelectItem value="low">{{
160-
t('settings.model.modelConfig.reasoningEffort.options.low')
161-
}}</SelectItem>
162-
<SelectItem value="medium">{{
163-
t('settings.model.modelConfig.reasoningEffort.options.medium')
164-
}}</SelectItem>
165-
<SelectItem value="high">{{
166-
t('settings.model.modelConfig.reasoningEffort.options.high')
167-
}}</SelectItem>
156+
<!-- Grok models only support low and high -->
157+
<template v-if="props.providerId === 'grok'">
158+
<SelectItem value="low">{{
159+
t('settings.model.modelConfig.reasoningEffort.options.low')
160+
}}</SelectItem>
161+
<SelectItem value="high">{{
162+
t('settings.model.modelConfig.reasoningEffort.options.high')
163+
}}</SelectItem>
164+
</template>
165+
<!-- Other models support all four options -->
166+
<template v-else>
167+
<SelectItem value="minimal">{{
168+
t('settings.model.modelConfig.reasoningEffort.options.minimal')
169+
}}</SelectItem>
170+
<SelectItem value="low">{{
171+
t('settings.model.modelConfig.reasoningEffort.options.low')
172+
}}</SelectItem>
173+
<SelectItem value="medium">{{
174+
t('settings.model.modelConfig.reasoningEffort.options.medium')
175+
}}</SelectItem>
176+
<SelectItem value="high">{{
177+
t('settings.model.modelConfig.reasoningEffort.options.high')
178+
}}</SelectItem>
179+
</template>
168180
</SelectContent>
169181
</Select>
170182
<p class="text-xs text-muted-foreground">

0 commit comments

Comments
 (0)