diff --git a/packages/bruno-electron/src/ipc/ai/autocomplete.js b/packages/bruno-electron/src/ipc/ai/autocomplete.js index f214b37f2..a665b6bb5 100644 Binary files a/packages/bruno-electron/src/ipc/ai/autocomplete.js and b/packages/bruno-electron/src/ipc/ai/autocomplete.js differ 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 };