From 12961db95de43cb840ed2b3a43fd9257caf3423c Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Wed, 8 Jul 2026 22:04:35 +0530 Subject: [PATCH] feat(ai): AI model handling with reasoning support (#8539) --- .../bruno-electron/src/ipc/ai/autocomplete.js | Bin 6380 -> 6876 bytes packages/bruno-electron/src/ipc/ai/chat.js | 7 +++++ packages/bruno-electron/src/ipc/ai/index.js | 5 ++-- .../bruno-electron/src/ipc/ai/providers.js | 27 ++++++++++++++---- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/bruno-electron/src/ipc/ai/autocomplete.js b/packages/bruno-electron/src/ipc/ai/autocomplete.js index f214b37f2f81d2939b3bc097ca42447656faa09e..a665b6bb5d54b63d962f2c9bc65e55e4b92a4086 100644 GIT binary patch delta 597 zcmZvYu}UOC5QY&o&_EDRF_J&qU6~t4!DL0`0s{qIIN_4xg5I5)wQ#0;=TRQ$m-=#SA`0iuKK^f>VMq)yM6Pp9)aC=QqE}B{Uei79)3C{>2Eyw zKYX~Gx!Tyk;YsKCE9suh!k-c{raW~}QX0xyHGngr!xmjr=~P)JX<9<1Rng^sWh#e% z*LU-P@U%hCXiwUsCxpz{K0(1zsjTwG3O1FAJm-`WT(CctZpv9kHfYXh5DUnXudE&~ z7dvm|7|sUGpSQ!smH7xxih93Vqv0$TSnQk^#s90Z`yqp75t2AaTRVbA*iQszGL*6G zv!GtZqx+?~spI#$J#20#3EJ37l4#5i9&sEuUsu}0Wi1nz@Klr}IaHR~NaC&N_UX&o z73$Wc@}0*E6%Z0iYtAJ-+bKQEDS;aVio@&0bU%~qeH)E}H5V#xMB@J2{~+5nj{QJ7 Wxw&Yd#!`*?jfGI^)@XJ4&-62D|HwB0 delta 120 zcmca(`o?g=>&>E!Q&^0OQcFsU@)QzOs&n&GQgb|0Y8h0ji<65o3ra#N3sOP60QgaJZixPpxrCKQ%=o!IfCP#?LZRQZa!VCb^ C@Fem8 diff --git a/packages/bruno-electron/src/ipc/ai/chat.js b/packages/bruno-electron/src/ipc/ai/chat.js index 67278b815..5ca7c98a5 100644 --- a/packages/bruno-electron/src/ipc/ai/chat.js +++ b/packages/bruno-electron/src/ipc/ai/chat.js @@ -245,6 +245,7 @@ const registerChatIpc = ({ mainWindow, resolveModel, pickDefaultModelId, isAiEna abortSignal: controller.signal }); + let streamError = null; for await (const part of result.fullStream) { if (controller.signal.aborted) break; switch (part.type) { @@ -271,11 +272,17 @@ const registerChatIpc = ({ mainWindow, resolveModel, pickDefaultModelId, isAiEna send('main:ai-chat-tool-done', { requestId, toolName: part.toolName }); break; } + case 'error': { + streamError = part.error; + break; + } default: break; } } + if (streamError) throw streamError; + activeStreams.delete(requestId); if (controller.signal.aborted) { diff --git a/packages/bruno-electron/src/ipc/ai/index.js b/packages/bruno-electron/src/ipc/ai/index.js index a2e46305f..e8ca4998c 100644 --- a/packages/bruno-electron/src/ipc/ai/index.js +++ b/packages/bruno-electron/src/ipc/ai/index.js @@ -12,6 +12,7 @@ const { clearSdkCache, isKnownProviderId, isBuiltInModelId, + isReasoningModel, validateApiKeyForProvider, providerLabel } = require('./providers'); @@ -162,7 +163,7 @@ const registerAiIpc = (mainWindow) => { system, prompt, maxOutputTokens: maxTokens ?? 1024, - temperature: temperature ?? 0.3 + ...(isReasoningModel(modelId) ? {} : { temperature: temperature ?? 0.3 }) }); return { text }; } catch (err) { @@ -344,7 +345,7 @@ const registerAiIpc = (mainWindow) => { model, system, maxOutputTokens: maxTokens ?? 2048, - temperature: temperature ?? 0.7, + ...(isReasoningModel(modelId) ? {} : { temperature: temperature ?? 0.7 }), abortSignal: controller.signal }; if (messages) { diff --git a/packages/bruno-electron/src/ipc/ai/providers.js b/packages/bruno-electron/src/ipc/ai/providers.js index 44dc5a8d4..ea8274c5f 100644 --- a/packages/bruno-electron/src/ipc/ai/providers.js +++ b/packages/bruno-electron/src/ipc/ai/providers.js @@ -40,16 +40,27 @@ const PROVIDERS = { * Static model catalog for built-in providers. User-defined custom models for * OpenAI-compatible endpoints are layered on top at lookup time. */ +// `reasoning: true` marks models whose SDK path rejects temperature/stopSequences +// (OpenAI Responses API reasoning models) or accepts them only when thinking is +// off (Anthropic Claude 4+). Callers doing latency-critical work like +// autocomplete drop those params for reasoning models to silence warnings. const MODEL_DEFINITIONS = { // OpenAI 'gpt-4o-mini': { provider: 'openai', modelId: 'gpt-4o-mini', label: 'GPT-4o Mini' }, 'gpt-4o': { provider: 'openai', modelId: 'gpt-4o', label: 'GPT-4o' }, - 'gpt-5': { provider: 'openai', modelId: 'gpt-5', label: 'GPT-5' }, - 'gpt-5-mini': { provider: 'openai', modelId: 'gpt-5-mini', label: 'GPT-5 Mini' }, + 'gpt-5': { provider: 'openai', modelId: 'gpt-5', label: 'GPT-5', reasoning: true }, + 'gpt-5-mini': { provider: 'openai', modelId: 'gpt-5-mini', label: 'GPT-5 Mini', reasoning: true }, // Anthropic - 'claude-opus-4-7': { provider: 'anthropic', modelId: 'claude-opus-4-7', label: 'Claude Opus 4.7' }, - 'claude-sonnet-4-6': { provider: 'anthropic', modelId: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' }, - 'claude-haiku-4-5': { provider: 'anthropic', modelId: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' } + 'claude-opus-4-7': { provider: 'anthropic', modelId: 'claude-opus-4-7', label: 'Claude Opus 4.7', reasoning: true }, + 'claude-sonnet-4-6': { provider: 'anthropic', modelId: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', reasoning: true }, + 'claude-haiku-4-5': { provider: 'anthropic', modelId: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', reasoning: true } +}; + +const isReasoningModel = (modelId) => Boolean(MODEL_DEFINITIONS[modelId]?.reasoning); + +const isOpenAiReasoningModel = (modelId) => { + const def = MODEL_DEFINITIONS[modelId]; + return Boolean(def?.reasoning && def?.provider === 'openai'); }; // Cache SDK instances. Built-in keyed by `${providerId}:${apiKey}`; compat @@ -208,7 +219,9 @@ const getModel = (modelId, { aiPreferences, getApiKey }) => { throw new Error(`${providerLabel(def.providerId, aiPreferences)} is missing a Base URL. Set one in Preferences > AI.`); } - return getSdk({ providerId: def.providerId, apiKey, baseURL: def.baseURL })(def.sdkModelId); + const sdk = getSdk({ providerId: def.providerId, apiKey, baseURL: def.baseURL }); + if (isOpenAiCompatibleProviderId(def.providerId)) return sdk.chat(def.sdkModelId); + return sdk(def.sdkModelId); }; /** @@ -273,6 +286,8 @@ module.exports = { getCompatEndpoint, isKnownProviderId, isBuiltInModelId, + isReasoningModel, + isOpenAiReasoningModel, validateApiKeyForProvider, providerLabel };