From ec8a2bf9a1a635684b7e1c8be71cc912cfb1cbbb Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 7 Apr 2026 16:11:31 -0700 Subject: [PATCH 1/4] v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha --- apps/sim/app/workspace/[workspaceId]/home/home.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index 1687a1973d..ec053fd6ce 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -229,6 +229,14 @@ export function Home({ chatId }: HomeProps = {}) { void stopGeneration().catch(() => {}) }, [stopGeneration, workspaceId]) + const handleStopGeneration = useCallback(() => { + captureEvent(posthogRef.current, 'task_generation_aborted', { + workspace_id: workspaceId, + view: 'mothership', + }) + stopGeneration() + }, [stopGeneration, workspaceId]) + const handleSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() From fc62dc5936d28c122f65d49c4dd382f2a6e07ca1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 15 Apr 2026 12:07:31 -0700 Subject: [PATCH 2/4] fix(gemini): support structured output with tools on Gemini 3 models --- apps/sim/providers/gemini/core.ts | 36 +++++++++++++++++++++---------- apps/sim/providers/utils.ts | 15 +++++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/apps/sim/providers/gemini/core.ts b/apps/sim/providers/gemini/core.ts index c35f38dc78..cba0a8a6fd 100644 --- a/apps/sim/providers/gemini/core.ts +++ b/apps/sim/providers/gemini/core.ts @@ -29,6 +29,7 @@ import type { FunctionCallResponse, ProviderRequest, ProviderResponse } from '@/ import { calculateCost, isDeepResearchModel, + isGemini3Model, prepareToolExecution, prepareToolsWithUsageControl, sumToolCosts, @@ -295,7 +296,8 @@ function buildNextConfig( state: ExecutionState, forcedTools: string[], request: ProviderRequest, - logger: ReturnType + logger: ReturnType, + model: string ): GenerateContentConfig { const nextConfig = { ...baseConfig } const allForcedToolsUsed = @@ -304,9 +306,13 @@ function buildNextConfig( if (allForcedToolsUsed && request.responseFormat) { nextConfig.tools = undefined nextConfig.toolConfig = undefined - nextConfig.responseMimeType = 'application/json' - nextConfig.responseSchema = cleanSchemaForGemini(request.responseFormat.schema) as Schema - logger.info('Using structured output for final response after tool execution') + if (isGemini3Model(model)) { + logger.info('Gemini 3: Stripping tools after forced tool execution, schema already set') + } else { + nextConfig.responseMimeType = 'application/json' + nextConfig.responseSchema = cleanSchemaForGemini(request.responseFormat.schema) as Schema + logger.info('Using structured output for final response after tool execution') + } } else if (state.currentToolConfig) { nextConfig.toolConfig = state.currentToolConfig } else { @@ -921,13 +927,19 @@ export async function executeGeminiRequest( geminiConfig.systemInstruction = systemInstruction } - // Handle response format (only when no tools) + // Handle response format if (request.responseFormat && !tools?.length) { geminiConfig.responseMimeType = 'application/json' geminiConfig.responseSchema = cleanSchemaForGemini(request.responseFormat.schema) as Schema logger.info('Using Gemini native structured output format') + } else if (request.responseFormat && tools?.length && isGemini3Model(model)) { + geminiConfig.responseMimeType = 'application/json' + geminiConfig.responseJsonSchema = request.responseFormat.schema + logger.info('Using Gemini 3 structured output with tools (responseJsonSchema)') } else if (request.responseFormat && tools?.length) { - logger.warn('Gemini does not support responseFormat with tools. Structured output ignored.') + logger.warn( + 'Gemini 2 does not support responseFormat with tools. Structured output will be applied after tool execution.' + ) } // Configure thinking only when the user explicitly selects a thinking level @@ -1099,7 +1111,7 @@ export async function executeGeminiRequest( } state = { ...updatedState, iterationCount: updatedState.iterationCount + 1 } - const nextConfig = buildNextConfig(geminiConfig, state, forcedTools, request, logger) + const nextConfig = buildNextConfig(geminiConfig, state, forcedTools, request, logger, model) // Stream final response if requested if (request.stream) { @@ -1120,10 +1132,12 @@ export async function executeGeminiRequest( if (request.responseFormat) { nextConfig.tools = undefined nextConfig.toolConfig = undefined - nextConfig.responseMimeType = 'application/json' - nextConfig.responseSchema = cleanSchemaForGemini( - request.responseFormat.schema - ) as Schema + if (!isGemini3Model(model)) { + nextConfig.responseMimeType = 'application/json' + nextConfig.responseSchema = cleanSchemaForGemini( + request.responseFormat.schema + ) as Schema + } } // Capture accumulated cost before streaming diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index f4f5d4c9f0..6a85c6f243 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -1064,6 +1064,21 @@ export function isDeepResearchModel(model: string): boolean { return MODELS_WITH_DEEP_RESEARCH.includes(model.toLowerCase()) } +const GEMINI_3_MODELS = [ + 'gemini-3.1-pro-preview', + 'gemini-3.1-flash-lite-preview', + 'gemini-3-flash-preview', + 'gemini-3-pro-preview', + 'vertex/gemini-3.1-pro-preview', + 'vertex/gemini-3.1-flash-lite-preview', + 'vertex/gemini-3-pro-preview', + 'vertex/gemini-3-flash-preview', +] + +export function isGemini3Model(model: string): boolean { + return GEMINI_3_MODELS.includes(model.toLowerCase()) +} + /** * Get the maximum temperature value for a model * @returns Maximum temperature value (1 or 2) or undefined if temperature not supported From 13eaaeeb152a60f3322ee4a4741f20275ce86f69 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 15 Apr 2026 12:09:23 -0700 Subject: [PATCH 3/4] fix(home): remove duplicate handleStopGeneration declaration --- apps/sim/app/workspace/[workspaceId]/home/home.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index ec053fd6ce..1687a1973d 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -229,14 +229,6 @@ export function Home({ chatId }: HomeProps = {}) { void stopGeneration().catch(() => {}) }, [stopGeneration, workspaceId]) - const handleStopGeneration = useCallback(() => { - captureEvent(posthogRef.current, 'task_generation_aborted', { - workspace_id: workspaceId, - view: 'mothership', - }) - stopGeneration() - }, [stopGeneration, workspaceId]) - const handleSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() From 8e93c05cd771315270b1e0369e2c302c9f5e2597 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 15 Apr 2026 12:12:00 -0700 Subject: [PATCH 4/4] refactor(gemini): use prefix-based Gemini 3 model detection --- apps/sim/providers/utils.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index 6a85c6f243..0d5b7be022 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -1064,19 +1064,9 @@ export function isDeepResearchModel(model: string): boolean { return MODELS_WITH_DEEP_RESEARCH.includes(model.toLowerCase()) } -const GEMINI_3_MODELS = [ - 'gemini-3.1-pro-preview', - 'gemini-3.1-flash-lite-preview', - 'gemini-3-flash-preview', - 'gemini-3-pro-preview', - 'vertex/gemini-3.1-pro-preview', - 'vertex/gemini-3.1-flash-lite-preview', - 'vertex/gemini-3-pro-preview', - 'vertex/gemini-3-flash-preview', -] - export function isGemini3Model(model: string): boolean { - return GEMINI_3_MODELS.includes(model.toLowerCase()) + const normalized = model.toLowerCase().replace(/^vertex\//, '') + return normalized.startsWith('gemini-3') } /**