From e5a9af6b24b07d67c6dbb844531d2780dbe1fc2c Mon Sep 17 00:00:00 2001 From: Urjit Chakraborty <135136842+urjitc@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:37:03 -0400 Subject: [PATCH] fix(ai): request readable reasoning summaries for capture --- src/features/workspaces/ai/gateway.test.ts | 52 ++++++++++++++++++++++ src/features/workspaces/ai/gateway.ts | 48 +++++++++++--------- 2 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 src/features/workspaces/ai/gateway.test.ts diff --git a/src/features/workspaces/ai/gateway.test.ts b/src/features/workspaces/ai/gateway.test.ts new file mode 100644 index 00000000..e31a7a54 --- /dev/null +++ b/src/features/workspaces/ai/gateway.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; + +import { getWorkspaceAiGatewayRoutingOptions } from "#/features/workspaces/ai/ai-gateway-routing"; +import { + getAIThreadTitleGatewayProviderOptions, + getWorkspaceAiGatewayProviderOptions, +} from "#/features/workspaces/ai/gateway"; +import { WORKSPACE_AI_CHAT_MODELS } from "#/features/workspaces/ai/models"; + +describe("chat reasoning capture", () => { + it.each(WORKSPACE_AI_CHAT_MODELS)("keeps $id fallback thinking compatible", (model) => { + const options = getWorkspaceAiGatewayProviderOptions({ modelId: model.id }); + const routeModels = [ + model.gatewayModel, + ...getWorkspaceAiGatewayRoutingOptions(model.id).models, + ]; + + expect(options.openai).toMatchObject({ reasoningSummary: "detailed" }); + for (const provider of ["google", "vertex"]) { + expect(options[provider]).toMatchObject({ thinkingConfig: { includeThoughts: true } }); + } + + for (const slug of routeModels) { + if (slug === "anthropic/claude-haiku-4.5") { + expect(options.anthropic).toBeUndefined(); + } + if (slug === "anthropic/claude-sonnet-5.5") { + expect(options.anthropic).toEqual({ + thinking: { type: "adaptive", display: "summarized" }, + }); + } + } + + expect(options.openai?.reasoningEffort).toBe(model.id === "gpt-sol" ? "low" : undefined); + expect(options.google).toEqual({ + thinkingConfig: { + includeThoughts: true, + ...(model.id === "gemini" ? { thinkingLevel: "low" } : {}), + }, + }); + expect(options.vertex).toEqual(options.google); + }); + + it("leaves title generation at minimal effort without requesting summaries", () => { + const { gateway: _gateway, ...providers } = getAIThreadTitleGatewayProviderOptions(); + expect(providers).toEqual({ + google: { thinkingConfig: { thinkingLevel: "minimal" } }, + vertex: { thinkingConfig: { thinkingLevel: "minimal" } }, + openai: { reasoningEffort: "none" }, + }); + }); +}); diff --git a/src/features/workspaces/ai/gateway.ts b/src/features/workspaces/ai/gateway.ts index a944f9a4..dc44d8a6 100644 --- a/src/features/workspaces/ai/gateway.ts +++ b/src/features/workspaces/ai/gateway.ts @@ -130,26 +130,34 @@ export function getAIThreadTitleGatewayProviderOptions(): WorkspaceAiProviderOpt function getWorkspaceAiReasoningOptions( modelId: ReturnType, ): WorkspaceAiProviderOptions { - switch (modelId) { - case "gemini": - return { - google: { - thinkingConfig: { thinkingLevel: "low" }, - }, - vertex: { - thinkingConfig: { thinkingLevel: "low" }, - }, - }; - case "gpt-sol": - return { - openai: { - // Sol 6.1 requires reasoning; low is its minimum supported effort. - reasoningEffort: "low", - }, - }; - default: - return {}; - } + const routeModels = [ + getWorkspaceAiChatModel(modelId), + ...getWorkspaceAiGatewayRoutingOptions(modelId).models, + ]; + + return { + openai: { + reasoningSummary: "detailed", + // Sol 6.1 requires reasoning; preserve its existing minimum effort. + ...(modelId === "gpt-sol" ? { reasoningEffort: "low" } : {}), + }, + google: { + thinkingConfig: { + includeThoughts: true, + ...(modelId === "gemini" ? { thinkingLevel: "low" } : {}), + }, + }, + vertex: { + thinkingConfig: { + includeThoughts: true, + ...(modelId === "gemini" ? { thinkingLevel: "low" } : {}), + }, + }, + // Include Sonnet fallbacks, but never send adaptive thinking to Haiku. + ...(routeModels.includes("anthropic/claude-sonnet-5.5") + ? { anthropic: { thinking: { type: "adaptive", display: "summarized" } } } + : {}), + }; } // Rudimint forwards each call to Vercel AI Gateway unchanged and keeps a copy for