From 7a51f95b380cd96c00d5743727b9ab1513e88c0b Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 12 Jul 2026 19:39:42 -0700 Subject: [PATCH] FN-7901: persist thinkingLevel for insight model selection Adds a persisted Thinking Level (reasoning-effort) selector to manual insight generation, threading the selection through the dashboard API, insight run metadata, and retries. - Add inline Thinking Level selector to the InsightsView model-config popover, persisted to localStorage (fusion-insight-thinking) - Thread thinkingLevel through triggerInsightRun (legacy API client) and useInsights.runInsights - Validate and store thinkingLevel in insight run inputMetadata.metadata on the POST /insights/run route; resolve it via resolvePlanningThinkingLevel for the actual generation call - Recover and reapply the original run's thinkingLevel on retry (retryInsightRunLifecycle) so retries reuse the same reasoning-effort setting - Export resolvePlanningThinkingLevel from @fusion/engine - Document the new Thinking Level selector in docs/dashboard-guide.md - Add a minor changeset for @runfusion/fusion Files changed: .changeset/fn-7901-insight-thinking-level.md | 7 ++ docs/dashboard-guide.md | 1 + .../app/__tests__/insight-model-selector.test.tsx | 41 ++++++++++- packages/dashboard/app/api/legacy.ts | 2 + packages/dashboard/app/components/InsightsView.tsx | 24 +++++- .../app/hooks/__tests__/useInsights.test.ts | 36 ++++++++- packages/dashboard/app/hooks/useInsights.ts | 6 +- .../src/__tests__/insights-routes.test.ts | 86 ++++++++++++++++++++++ packages/dashboard/src/insights-routes.ts | 36 ++++++++- packages/engine/src/index.ts | 1 + 10 files changed, 227 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-7901 Fusion-Task-Lineage: a6249526-e97d-403e-b853-e497d16f425b Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7901-insight-thinking-level.md | 7 ++ docs/dashboard-guide.md | 1 + .../__tests__/insight-model-selector.test.tsx | 41 ++++++++- packages/dashboard/app/api/legacy.ts | 2 + .../dashboard/app/components/InsightsView.tsx | 24 +++++- .../app/hooks/__tests__/useInsights.test.ts | 36 +++++++- packages/dashboard/app/hooks/useInsights.ts | 6 +- .../src/__tests__/insights-routes.test.ts | 86 +++++++++++++++++++ packages/dashboard/src/insights-routes.ts | 36 +++++++- packages/engine/src/index.ts | 1 + 10 files changed, 227 insertions(+), 13 deletions(-) create mode 100644 .changeset/fn-7901-insight-thinking-level.md diff --git a/.changeset/fn-7901-insight-thinking-level.md b/.changeset/fn-7901-insight-thinking-level.md new file mode 100644 index 0000000000..a781b20c6d --- /dev/null +++ b/.changeset/fn-7901-insight-thinking-level.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add a persisted Thinking Level selector for manual insight generation. +category: feature +dev: Threads insight run thinkingLevel through dashboard API metadata and retry generation. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 3c6f868e24..71c443fc78 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -1107,6 +1107,7 @@ Navigation: Features: - Category-based insight browser with run metadata and status indicators - Manual insight generation plus refresh actions for latest insight runs +- The model gear beside **Generate Insights** opens a model picker with an inline **Thinking Level** selector. Both the model override and reasoning-effort choice persist in the browser, and each insight run records the selected reasoning effort so retries reuse the same setting. - Dismiss/archive/unarchive insight records as they age - Create triage tasks from selected insights directly from the view diff --git a/packages/dashboard/app/__tests__/insight-model-selector.test.tsx b/packages/dashboard/app/__tests__/insight-model-selector.test.tsx index deff02259a..ba6eababde 100644 --- a/packages/dashboard/app/__tests__/insight-model-selector.test.tsx +++ b/packages/dashboard/app/__tests__/insight-model-selector.test.tsx @@ -20,6 +20,8 @@ import { render, screen, fireEvent, cleanup } from "@testing-library/react"; import * as jestDomMatchers from "@testing-library/jest-dom/matchers"; import { InsightsView } from "../components/InsightsView"; +const mockRunInsights = vi.hoisted(() => vi.fn()); + // Register jest-dom matchers (setup files not running in this environment) expect.extend(jestDomMatchers); @@ -50,6 +52,7 @@ beforeAll(() => { // Mock useInsights hook vi.mock("../hooks/useInsights", () => ({ useInsights: () => ({ + sections: [], loading: false, error: null, @@ -57,7 +60,7 @@ vi.mock("../hooks/useInsights", () => ({ isRunInFlight: false, runError: null, refresh: vi.fn(), - runInsights: vi.fn(), + runInsights: mockRunInsights, dismiss: vi.fn(), createTask: vi.fn(), archive: vi.fn(), @@ -76,10 +79,17 @@ vi.mock("../hooks/useInsights", () => ({ // Mock CustomModelDropdown since it has complex portal behavior vi.mock("../components/CustomModelDropdown", () => ({ - CustomModelDropdown: ({ value, onChange, placeholder }: any) => ( -
+ CustomModelDropdown: ({ value, onChange, placeholder, thinkingLevel, onThinkingLevelChange, showThinkingLevel, disabled }: any) => ( +
{value || placeholder}
+ )}
), })); @@ -89,6 +99,7 @@ const mockAddToast = vi.fn(); describe("Insight model selector", () => { beforeEach(() => { localStorage.clear(); + mockRunInsights.mockReset(); }); afterEach(() => { @@ -143,4 +154,28 @@ describe("Insight model selector", () => { screen.getByTestId("toggle-model-config").querySelector(".insights-model-indicator"), ).toBeInTheDocument(); }); + + it("persists selected thinking level to localStorage", () => { + render(); + fireEvent.click(screen.getByTestId("toggle-model-config")); + expect(screen.getByTestId("thinking-control")).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId("thinking-change")); + expect(localStorage.getItem("fusion-insight-thinking")).toBe("high"); + expect(screen.getByTestId("thinking-value")).toHaveTextContent("high"); + + fireEvent.click(screen.getByTestId("thinking-clear")); + expect(localStorage.getItem("fusion-insight-thinking")).toBeNull(); + }); + + it("forwards persisted thinking level when generating insights", () => { + render(); + fireEvent.click(screen.getByTestId("toggle-model-config")); + fireEvent.click(screen.getByTestId("model-change")); + fireEvent.click(screen.getByTestId("thinking-change")); + + fireEvent.click(screen.getByTestId("run-insights")); + + expect(mockRunInsights).toHaveBeenCalledWith("openai", "gpt-4o", "high"); + }); }); diff --git a/packages/dashboard/app/api/legacy.ts b/packages/dashboard/app/api/legacy.ts index f5a03f1c22..0c592a3d6b 100644 --- a/packages/dashboard/app/api/legacy.ts +++ b/packages/dashboard/app/api/legacy.ts @@ -11162,10 +11162,12 @@ export function triggerInsightRun( projectId?: string, modelProvider?: string, modelId?: string, + thinkingLevel?: string, ): Promise { const body: Record = { trigger, inputMetadata }; if (modelProvider) body.modelProvider = modelProvider; if (modelId) body.modelId = modelId; + if (thinkingLevel) body.thinkingLevel = thinkingLevel; return api(withProjectId("/insights/run", projectId), { method: "POST", body: JSON.stringify(body), diff --git a/packages/dashboard/app/components/InsightsView.tsx b/packages/dashboard/app/components/InsightsView.tsx index be3234b606..2db0ecb0e9 100644 --- a/packages/dashboard/app/components/InsightsView.tsx +++ b/packages/dashboard/app/components/InsightsView.tsx @@ -93,6 +93,9 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model const [selectedModel, setSelectedModel] = useState( () => localStorage.getItem("fusion-insight-model") ?? "" ); + const [selectedThinking, setSelectedThinking] = useState( + () => localStorage.getItem("fusion-insight-thinking") ?? "" + ); // Fetch models internally if not provided via prop const [fetchedModels, setFetchedModels] = useState([]); @@ -166,6 +169,15 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model } }, []); + const handleThinkingChange = useCallback((value: string) => { + setSelectedThinking(value); + if (value) { + localStorage.setItem("fusion-insight-thinking", value); + } else { + localStorage.removeItem("fusion-insight-thinking"); + } + }, []); + const populatedSections = useMemo( () => sections.filter((section) => section.items.length > 0), [sections], @@ -234,7 +246,7 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model } } - await runInsights(modelProvider, modelId); + await runInsights(modelProvider, modelId, selectedThinking || undefined); setStatusMessage(t("insights.generationStarted", "Insight generation started")); setStatusType("success"); addToast(t("insights.generationStarted", "Insight generation started"), "success"); @@ -250,7 +262,7 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model setStatusType("error"); addToast(message, "error"); } - }, [runInsights, addToast, selectedModel, t]); + }, [runInsights, addToast, selectedModel, selectedThinking, t]); const handleDismiss = useCallback( async (id: string, title: string) => { @@ -577,6 +589,10 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model {showModelConfig && (
+ {/* + FNXC:Insights-ThinkingLevel 2026-07-12-19:24: + The insight model-config popover must expose the shared inline reasoning-effort selector and persist the operator's choice next to the model override so Generate Insights can send a real per-run selection. + */} @@ -592,6 +608,10 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model favoriteModels={favoriteModels} onToggleFavorite={handleToggleProviderFavorite} onToggleModelFavorite={handleToggleModelFavorite} + showThinkingLevel + thinkingLevel={selectedThinking} + onThinkingLevelChange={handleThinkingChange} + defaultThinkingLevel="off" />
)} diff --git a/packages/dashboard/app/hooks/__tests__/useInsights.test.ts b/packages/dashboard/app/hooks/__tests__/useInsights.test.ts index 9e9afddc32..b2a0304f87 100644 --- a/packages/dashboard/app/hooks/__tests__/useInsights.test.ts +++ b/packages/dashboard/app/hooks/__tests__/useInsights.test.ts @@ -379,13 +379,47 @@ describe("useInsights", () => { await result.current.runInsights(); }); - expect(mockTriggerInsightRun).toHaveBeenCalledWith("manual", undefined, "project-1", undefined, undefined); + expect(mockTriggerInsightRun).toHaveBeenCalledWith("manual", undefined, "project-1", undefined, undefined, undefined); expect(result.current.latestRun?.status).toBe("completed"); expect(result.current.isRunInFlight).toBe(false); expect(mockFetchInsights.mock.calls.length).toBeGreaterThanOrEqual(2); expect(mockFetchInsightRuns.mock.calls.length).toBeGreaterThanOrEqual(2); }); + it("should forward a selected thinking level into manual insight runs", async () => { + const runningRun = { + id: "RUN-THINKING", + projectId: "project-1", + trigger: "manual" as const, + status: "running" as const, + summary: null, + error: null, + insightsCreated: 0, + insightsUpdated: 0, + inputMetadata: {}, + outputMetadata: {}, + createdAt: "2024-01-01T00:00:00Z", + startedAt: "2024-01-01T00:00:10Z", + completedAt: null, + }; + + mockFetchInsights.mockResolvedValue({ insights: [], count: 0 }); + mockFetchInsightRuns.mockResolvedValue({ runs: [] }); + mockTriggerInsightRun.mockResolvedValue(runningRun); + + const { result } = renderHook(() => useInsights("project-1")); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + await act(async () => { + await result.current.runInsights("anthropic", "claude-sonnet-4-5", "high"); + }); + + expect(mockTriggerInsightRun).toHaveBeenCalledWith("manual", undefined, "project-1", "anthropic", "claude-sonnet-4-5", "high"); + }); + it("should surface failed run errors from triggerInsightRun response", async () => { const failedRun = { id: "RUN-1", diff --git a/packages/dashboard/app/hooks/useInsights.ts b/packages/dashboard/app/hooks/useInsights.ts index 34b89e2091..11ad3b1edb 100644 --- a/packages/dashboard/app/hooks/useInsights.ts +++ b/packages/dashboard/app/hooks/useInsights.ts @@ -103,7 +103,7 @@ export interface UseInsightsResult { // Actions refresh: () => Promise; - runInsights: (modelProvider?: string, modelId?: string) => Promise; + runInsights: (modelProvider?: string, modelId?: string, thinkingLevel?: string) => Promise; dismiss: (id: string) => Promise; createTask: (id: string) => Promise<{ title: string; description: string } | null>; archive: (id: string) => Promise; @@ -216,12 +216,12 @@ export function useInsights(projectId?: string): UseInsightsResult { }, []); // Run insights generation - const runInsights = useCallback(async (modelProvider?: string, modelId?: string) => { + const runInsights = useCallback(async (modelProvider?: string, modelId?: string, thinkingLevel?: string) => { setIsRunInFlight(true); setRunError(null); try { - const run = await triggerInsightRun("manual", undefined, projectId, modelProvider, modelId); + const run = await triggerInsightRun("manual", undefined, projectId, modelProvider, modelId, thinkingLevel); setLatestRun(run); if (run.status === "completed") { diff --git a/packages/dashboard/src/__tests__/insights-routes.test.ts b/packages/dashboard/src/__tests__/insights-routes.test.ts index a0f102e445..e8ca6e88aa 100644 --- a/packages/dashboard/src/__tests__/insights-routes.test.ts +++ b/packages/dashboard/src/__tests__/insights-routes.test.ts @@ -15,6 +15,7 @@ const piMocks = vi.hoisted(() => ({ createFnAgent: vi.fn(), promptWithFallback: vi.fn(), resolveMcpServersForStore: vi.fn(), + resolvePlanningThinkingLevel: vi.fn(), })); const resolverMocks = vi.hoisted(() => ({ @@ -56,6 +57,7 @@ vi.mock("@fusion/engine", () => ({ createFnAgent: piMocks.createFnAgent, promptWithFallback: piMocks.promptWithFallback, resolveMcpServersForStore: piMocks.resolveMcpServersForStore, + resolvePlanningThinkingLevel: piMocks.resolvePlanningThinkingLevel, })); /* @@ -206,6 +208,7 @@ describe("Insights routes", () => { })); piMocks.promptWithFallback.mockResolvedValue(undefined); piMocks.resolveMcpServersForStore.mockResolvedValue({ servers: [] }); + piMocks.resolvePlanningThinkingLevel.mockImplementation((_settings: unknown, thinkingLevel?: string) => thinkingLevel); }); afterEach(() => { @@ -565,6 +568,56 @@ describe("Insights routes", () => { expect(persisted?.inputMetadata).toEqual({ source: "route-test" }); }); + it("POST /api/insights/run accepts and stores a valid thinkingLevel", async () => { + const res = await request( + app, + "POST", + "/api/insights/run", + JSON.stringify({ trigger: "manual", thinkingLevel: "high" }), + { "Content-Type": "application/json" }, + ); + + expect(res.status).toBe(201); + const run = res.body as { id: string; inputMetadata: { metadata?: Record } }; + expect(run.inputMetadata.metadata).toMatchObject({ thinkingLevel: "high" }); + expect(storeA.getInsightStore().getRun(run.id)?.inputMetadata?.metadata).toMatchObject({ thinkingLevel: "high" }); + expect(piMocks.resolvePlanningThinkingLevel).toHaveBeenCalledWith(expect.any(Object), "high"); + expect(piMocks.createFnAgent).toHaveBeenCalledWith( + expect.objectContaining({ + defaultThinkingLevel: "high", + }), + ); + }); + + it("POST /api/insights/run rejects an invalid thinkingLevel", async () => { + const res = await request( + app, + "POST", + "/api/insights/run", + JSON.stringify({ trigger: "manual", thinkingLevel: "turbo" }), + { "Content-Type": "application/json" }, + ); + + expect(res.status).toBe(400); + expect((res.body as { error: string }).error).toContain("Invalid thinkingLevel"); + expect(storeA.getInsightStore().listRuns({})).toHaveLength(0); + }); + + it("POST /api/insights/run omits thinkingLevel metadata when inheriting defaults", async () => { + const res = await request( + app, + "POST", + "/api/insights/run", + JSON.stringify({ trigger: "manual" }), + { "Content-Type": "application/json" }, + ); + + expect(res.status).toBe(201); + const run = res.body as { inputMetadata: { metadata?: Record } }; + expect(run.inputMetadata.metadata).toBeUndefined(); + expect(piMocks.resolvePlanningThinkingLevel).toHaveBeenCalledWith(expect.any(Object), undefined); + }); + it("POST /api/insights/run marks run failed when AI execution throws", async () => { piMocks.promptWithFallback.mockRejectedValue(new Error("AI blew up")); @@ -696,6 +749,39 @@ describe("Insights routes", () => { ); }); + it("POST /api/insights/runs/:id/retry preserves the original run's thinkingLevel", async () => { + piMocks.promptWithFallback.mockRejectedValue(new Error("HTTP 503")); + const failedRes = await request( + app, + "POST", + "/api/insights/run", + JSON.stringify({ + trigger: "manual", + thinkingLevel: "xhigh", + }), + { "Content-Type": "application/json" }, + ); + expect(failedRes.status).toBe(201); + const failedRun = failedRes.body as { id: string; inputMetadata: { metadata?: Record } }; + expect(failedRun.inputMetadata.metadata).toMatchObject({ thinkingLevel: "xhigh" }); + + piMocks.promptWithFallback.mockResolvedValue(undefined); + piMocks.createFnAgent.mockClear(); + piMocks.resolvePlanningThinkingLevel.mockClear(); + + const retriedRes = await request(app, "POST", `/api/insights/runs/${failedRun.id}/retry`, JSON.stringify({}), { + "Content-Type": "application/json", + }); + expect(retriedRes.status).toBe(201); + + expect(piMocks.resolvePlanningThinkingLevel).toHaveBeenCalledWith(expect.any(Object), "xhigh"); + expect(piMocks.createFnAgent).toHaveBeenCalledWith( + expect.objectContaining({ + defaultThinkingLevel: "xhigh", + }), + ); + }); + it("POST /api/insights/run passes explicit model override to createFnAgent", async () => { const res = await request( app, diff --git a/packages/dashboard/src/insights-routes.ts b/packages/dashboard/src/insights-routes.ts index 0050d3316e..ea1822aca9 100644 --- a/packages/dashboard/src/insights-routes.ts +++ b/packages/dashboard/src/insights-routes.ts @@ -17,6 +17,7 @@ import type { TaskStore } from "@fusion/core"; import { InsightLifecycleError, InsightStore, + THINKING_LEVELS, executeInsightRunLifecycle, resolvePlanningSettingsModel, retryInsightRunLifecycle, @@ -28,6 +29,7 @@ import { type InsightRunListOptions, type InsightRunStatus, type Settings, + type ThinkingLevel, } from "@fusion/core"; import { ApiError, @@ -41,7 +43,7 @@ import { startInsightRunSweeper, sweepStaleInsightRuns, } from "./insight-run-sweeper.js"; -import { createFnAgent, promptWithFallback, resolveMcpServersForStore } from "@fusion/engine"; +import { createFnAgent, promptWithFallback, resolveMcpServersForStore, resolvePlanningThinkingLevel } from "@fusion/engine"; /** * Re-throws an error as an ApiError, converting unknown errors to internal errors. @@ -133,6 +135,7 @@ async function executeInsightAttempt(params: { settings: Settings; modelProvider?: string; modelId?: string; + thinkingLevel?: ThinkingLevel; }): Promise<{ summary: string; insightsCreated: number; insightsUpdated: number }> { const { readWorkingMemory, @@ -157,6 +160,7 @@ async function executeInsightAttempt(params: { const hasCustomModel = params.modelProvider && params.modelId; const fallbackProvider = hasCustomModel ? settingsProvider : undefined; const fallbackModelId = hasCustomModel ? settingsModelId : undefined; + const effectiveThinkingLevel = resolvePlanningThinkingLevel(params.settings, params.thinkingLevel); const existingInsights = await readInsightsMemory(params.rootDir); const mcpServers = (await resolveMcpServersForStore(params.taskStore ?? {})).servers; @@ -172,6 +176,11 @@ async function executeInsightAttempt(params: { defaultModelId: finalModelId, fallbackProvider, fallbackModelId, + /* + * FNXC:Insights-ThinkingLevel 2026-07-12-19:24: + * Insight generation now persists a per-run reasoning-effort selection in inputMetadata.metadata.thinkingLevel and resolves it through the planning lane so manual runs and retries honor the same operator choice. + */ + defaultThinkingLevel: effectiveThinkingLevel, systemPrompt: [ "You extract durable project insights from working memory notes.", "Return only valid JSON that matches the requested schema.", @@ -385,20 +394,33 @@ export function createInsightsRouter(store: TaskStore): Router { const settings = await taskStore.getSettings(); const rawProvider = typeof req.body.modelProvider === "string" ? req.body.modelProvider.trim() : undefined; const rawModelId = typeof req.body.modelId === "string" ? req.body.modelId.trim() : undefined; + const rawThinkingLevel = typeof req.body.thinkingLevel === "string" ? req.body.thinkingLevel.trim() : undefined; + const thinkingLevel = rawThinkingLevel + ? THINKING_LEVELS.includes(rawThinkingLevel as ThinkingLevel) + ? rawThinkingLevel as ThinkingLevel + : undefined + : undefined; + if (rawThinkingLevel && !thinkingLevel) { + throw badRequest(`Invalid thinkingLevel: ${rawThinkingLevel}`); + } // Require both provider and model ID together — partial values are discarded const modelProvider = rawProvider && rawModelId ? rawProvider : undefined; const modelId = rawProvider && rawModelId ? rawModelId : undefined; const controller = new AbortController(); - // Stash model selection in inputMetadata.metadata so retries can recover it + /* + * FNXC:Insights-ThinkingLevel 2026-07-12-19:24: + * Insight runs store the operator's model and reasoning-effort selection in inputMetadata.metadata instead of adding schema columns, because retries must recover exactly the per-run values used for generation. + */ const inputMetadata = typeof req.body.inputMetadata === "object" && req.body.inputMetadata !== null ? { ...req.body.inputMetadata } : {}; - if (modelProvider || modelId) { + if (modelProvider || modelId || thinkingLevel) { inputMetadata.metadata = { ...(typeof inputMetadata.metadata === "object" && inputMetadata.metadata !== null ? inputMetadata.metadata : {}), ...(modelProvider ? { modelProvider } : {}), ...(modelId ? { modelId } : {}), + ...(thinkingLevel ? { thinkingLevel } : {}), }; } @@ -434,6 +456,7 @@ export function createInsightsRouter(store: TaskStore): Router { settings, modelProvider, modelId, + thinkingLevel, }); }, }); @@ -612,7 +635,7 @@ export function createInsightsRouter(store: TaskStore): Router { const settings = await taskStore.getSettings(); const controller = new AbortController(); - // Recover model selection from the original run's inputMetadata + // Recover model and reasoning-effort selection from the original run's inputMetadata. const originalMetadata = existing.inputMetadata?.metadata; const retryModelProvider = typeof (originalMetadata as Record | undefined)?.modelProvider === "string" ? (originalMetadata as Record).modelProvider as string @@ -620,6 +643,10 @@ export function createInsightsRouter(store: TaskStore): Router { const retryModelId = typeof (originalMetadata as Record | undefined)?.modelId === "string" ? (originalMetadata as Record).modelId as string : undefined; + const retryThinkingLevel = typeof (originalMetadata as Record | undefined)?.thinkingLevel === "string" + && THINKING_LEVELS.includes((originalMetadata as Record).thinkingLevel as ThinkingLevel) + ? (originalMetadata as Record).thinkingLevel as ThinkingLevel + : undefined; const { run } = await retryInsightRunLifecycle({ store, @@ -640,6 +667,7 @@ export function createInsightsRouter(store: TaskStore): Router { settings, modelProvider: retryModelProvider, modelId: retryModelId, + thinkingLevel: retryThinkingLevel, }); }, }); diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts index d0e23407a6..bfcfbd9743 100644 --- a/packages/engine/src/index.ts +++ b/packages/engine/src/index.ts @@ -858,6 +858,7 @@ export { describeAgentModel, resolveExecutorThinkingLevel, resolveExecutorFallbackThinkingLevel, + resolvePlanningThinkingLevel, resolvePlanningFallbackThinkingLevel, resolveValidatorFallbackThinkingLevel, resolveTitleSummarizerFallbackThinkingLevel,