From f5b281b55c8cca28228b870f906046429fe1a12f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 04:52:39 +0000 Subject: [PATCH] Remove Tools button and Research mode; collapse runtime profile Removes the user-facing Research mode feature and the Tools popover entirely, then cleans up everything the removal orphaned. UI: - Drop the Tools (+) popover and Research pill from PromptForm; the footer now renders only the ModelSelector. - Delete ResearchModeToggle and its use in the message-edit UI. Run mode: - Remove the runMode/AgentRunMode concept end to end (UI props, use-agent-session, follow-up plumbing, message metadata). - Delete use-persistent-run-mode + persistent-run-mode-utils and the RUN_MODE_* localStorage constants. Server: - Remove runMode validation, the AGENT_RESEARCH_MODEL_UNAVAILABLE error, research model forcing, RESEARCH_MODEL, and the DEEP RESEARCH MODE system-prompt block. - Collapse the single remaining runtime profile: drop AGENT_RUNTIME_PROFILES/AgentRuntimeProfileId and use AGENT_TOOL_MAX_STEPS directly; make taskMode a required param and always route provider options through the task-mode mapping. - Delete the legacy getAiSdkGatewayProviderOptionsForMode shim. The internal, content-inferred "research" task mode is intentionally kept. Stored threads carrying legacy runMode metadata parse fine; the field is dropped on read (no migration). Tests and docs (README, CLAUDE.md) updated accordingly. --- CLAUDE.md | 30 ++-- README.md | 2 +- src/app/api/agent/follow-ups/route.ts | 1 - src/app/api/agent/route.ts | 16 +- .../agent/home/agent-session-state.ts | 4 - .../agent/home/follow-up-questions.ts | 4 - src/components/agent/home/home-agent-utils.ts | 10 +- src/components/agent/home/home-content.tsx | 40 ++--- .../agent/home/use-agent-session.ts | 43 +---- .../agent/home/use-follow-up-questions.ts | 1 - .../agent/messages/assistant-message.tsx | 4 - src/components/agent/messages/messages.tsx | 8 - .../agent/messages/user-message.tsx | 26 +-- .../agent/prompt-form/prompt-form.tsx | 151 ++---------------- .../prompt-form/research-mode-toggle.tsx | 70 -------- src/hooks/agent/persistent-run-mode-utils.ts | 68 -------- src/hooks/agent/use-persistent-run-mode.tsx | 115 ------------- src/lib/constants.ts | 2 - src/lib/server/agent-context.ts | 8 - src/lib/server/agent-follow-ups.ts | 10 +- src/lib/server/agent-route.ts | 45 +----- src/lib/server/agent-runtime-config.ts | 3 +- src/lib/server/llm/agent-runtime.ts | 83 ++-------- .../llm/ai-sdk-gateway-provider-options.ts | 16 -- src/lib/server/thread-payload.ts | 11 -- src/lib/shared/agent/messages.ts | 4 - src/lib/shared/llm/models.ts | 2 - src/lib/shared/llm/system-instructions.ts | 36 ----- tests/agent-context.test.mjs | 12 +- .../agent-follow-ups-route-behavior.test.mjs | 1 - tests/agent-follow-ups.test.mjs | 2 - tests/agent-helper-behavior.test.mjs | 90 +---------- tests/agent-route-behavior.test.mjs | 37 ----- tests/agent-route-contract.test.mjs | 12 +- tests/agent-runtime-synthesis-gating.test.mjs | 6 +- .../agent-runtime-task-mode-routing.test.mjs | 24 +-- tests/agent-session-state.test.mjs | 6 - tests/agent-system-prompt.test.mjs | 34 +--- tests/assistant-message-contract.test.mjs | 2 +- tests/follow-up-questions.test.mjs | 4 +- tests/gateway-search-tools.test.mjs | 26 --- tests/home-agent-utils.test.mjs | 6 +- tests/model-registry.test.mjs | 6 - tests/persistent-run-mode.test.mjs | 88 ---------- tests/prompt-form-contract.test.mjs | 48 +----- tests/thread-payload-contract.test.mjs | 19 +-- 46 files changed, 107 insertions(+), 1129 deletions(-) delete mode 100644 src/components/agent/prompt-form/research-mode-toggle.tsx delete mode 100644 src/hooks/agent/persistent-run-mode-utils.ts delete mode 100644 src/hooks/agent/use-persistent-run-mode.tsx delete mode 100644 tests/persistent-run-mode.test.mjs diff --git a/CLAUDE.md b/CLAUDE.md index da67b7b8..17992c1b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,10 +53,9 @@ Client (useAgentSession) → POST /api/agent (app/api/agent/route.ts) → Auth check (isAuthConfigured → getRequestSession) [routes self-guard; see Middleware] → Sliding-window rate limit (rate-limit.ts, key user:) - → Zod validation (parseAgentStreamRequest) — incl. runMode (chat|research) and model + → Zod validation (parseAgentStreamRequest) — model + messages → Concurrency slot acquire (max 4 in-flight per user) → System prompt assembly (buildAgentSystemInstruction) - → Runtime profile resolution (chat_default | deep_research) → AI Gateway stream via Vercel AI SDK (startGatewayResponseStream → runAgentStream) → NDJSON chunks (application/x-ndjson) → client → readResponseStreamLines / parseStreamEventLine @@ -77,14 +76,9 @@ Before the model sees them, messages pass through `toModelMessages` (`agent-runt This boundary is **enforced by Next.js bundling at build time** (importing `pg`/`better-auth`/server modules into a client bundle is a build error), **not** by an ESLint rule. Keep it in mind when adding imports. -### Runtime Profiles vs. Task Modes (two independent axes) +### Task Modes -These are easy to confuse. They are orthogonal: - -- **Runtime profile** (`resolveRuntimeProfile` in `app/api/agent/route.ts`, `AGENT_RUNTIME_PROFILES` in `agent-runtime.ts`) — one of `chat_default`, `deep_research`. The profile drives the **tool-step budget** (the tool set itself is the same for both). -- **Task mode** (`inferPromptTaskMode` in `agent-prompt-steering.ts`) — inferred from message content; drives only the **prompt overlay text** and the Gemini thinking level. Modes: `general`, `coding`, `debugging`, `writing`, `research`, `high_stakes`, `closed_answer`, `instruction_following`. - -Research mode is a **request flag** (`runMode: "research"`), not an inference. It selects the `deep_research` profile and `RESEARCH_MODEL` (Qwen 3.7 Max); if that model is unavailable the route returns 400 `AGENT_RESEARCH_MODEL_UNAVAILABLE`. +**Task mode** (`inferPromptTaskMode` in `agent-prompt-steering.ts`) is inferred from message content and drives only the **prompt overlay text** (`TASK MODE OVERLAY: `) and the Gemini thinking level. Modes: `general`, `coding`, `debugging`, `writing`, `research`, `high_stakes`, `closed_answer`, `instruction_following`. The tool-step budget is a single fixed constant (`AGENT_TOOL_MAX_STEPS`) — there is no per-request runtime-profile selection. (The `research` task mode is an automatic content-based overlay, not a user-facing toggle.) ### System Prompt Composition @@ -94,9 +88,8 @@ Research mode is a **request flag** (`runMode: "research"`), not an inference. I 2. `RUNTIME DATE CONTEXT` — current UTC timestamp + user timezone (from `X-User-Timezone` header) 3. **Provider overlay** (`PROVIDER OVERLAY: ALIBABA|GOOGLE|MOONSHOTAI|XIAOMI`) — keyed by the model's **provider org**, not its nickname (alibaba=Qwen, google=Gemini, moonshotai=Kimi, xiaomi=MiMo). Always applied for a supported model. 4. **Task mode overlay** (`TASK MODE OVERLAY: `) -5. `DEEP RESEARCH MODE` — only for `runMode: "research"` -6. `IDENTITY AND TONE CONTEXT` — `DEFAULT_SOUL_FALLBACK_INSTRUCTION` (`src/lib/shared/llm/system-instructions.ts`) -7. `AUTH USER CONTEXT` — authenticated user id, name, email +5. `IDENTITY AND TONE CONTEXT` — `DEFAULT_SOUL_FALLBACK_INSTRUCTION` (`src/lib/shared/llm/system-instructions.ts`) +6. `AUTH USER CONTEXT` — authenticated user id, name, email Inline-citation rules are appended **later**, by `withAiSdkInlineCitationInstruction` (`system-instruction-augmentations.ts`), inside `createAgentStreamResponse` — not by `buildAgentSystemInstruction`. @@ -155,7 +148,7 @@ Each tool is only registered when its requirements are met. - **Restricted backend** (the only backend): computation-only Python imports (`math`, `collections`, `itertools`, …). - **Limits**: timeout default **10 s**, max **60 s**; code input and output each capped at **12,000 chars**. -**Max tool steps** (constants in `agent-runtime-config.ts`): 12 default; 20 for research runs. +**Max tool steps** (`AGENT_TOOL_MAX_STEPS` in `agent-runtime-config.ts`): 12. ### Model Registry @@ -169,7 +162,6 @@ All models are defined in `src/lib/shared/llm/models.ts`: | `XIAOMI_MIMO_V2_5_PRO` | `xiaomi/mimo-v2.5-pro` | MiMo V2.5 Pro | - `MODEL_SELECTOR_MODELS` — the chat selector subset: Qwen 3.7 Max, Kimi K2.6, MiMo V2.5 Pro. -- `RESEARCH_MODEL` — Qwen 3.7 Max (Research mode also injects the Deep Research prompt block). - Gemini stays in `SUPPORTED_MODELS` for Gateway availability but is not a standalone chat selector option. - The agent is text-only: all chat input is plain text (no image, file, or PDF input). - Adding a model means updating `AvailableModels`, `ModelInfos`, `SUPPORTED_MODELS`, and optionally `MODEL_SELECTOR_MODELS`. `/api/models` filters this registry by configured keys (`getModels()` in `src/lib/actions/api-keys.ts`). @@ -272,7 +264,7 @@ src/ # follow-up-questions agent/messages/ # Message rendering (user, assistant, queued, activity timeline) agent/markdown/ # Memoized markdown renderer - agent/prompt-form/ # PromptForm (inline Research + Tools popover), ModelSelector + agent/prompt-form/ # PromptForm, ModelSelector app-sidebar.tsx # Sidebar shell (lazy-loads SearchChats + NavThreads) nav-threads.tsx # Thread list + client-side pinning (localStorage) nav-user.tsx # Account menu + sign-out @@ -282,8 +274,8 @@ src/ layout/ # route group layout ui/ # shadcn/ui primitives (base-lyra/stone) + ShikiCode hooks/ - agent/ # use-models (server-seeded models context), use-persistent-selected-model, - # use-persistent-run-mode (both localStorage-backed) + agent/ # use-models (server-seeded models context), + # use-persistent-selected-model (localStorage-backed) lib/ actions/api-keys.ts # getModels() server action brand/colors.ts # App brand colors (used by layout/manifest) @@ -312,7 +304,7 @@ src/ initial-reasoning-chunk-sanitizer.ts # Redacted-reasoning filtering system-instruction-augmentations.ts # Citation rules appended to prompt shared/ - agent/messages.ts # AgentStreamEvent, Message, ToolInvocation, run modes/statuses + agent/messages.ts # AgentStreamEvent, Message, ToolInvocation, run statuses agent/reasoning-privacy.ts # sanitizeReasoningForDisplay agent-request-limits.ts # Message/char limit defaults llm/models.ts # AvailableModels, ModelInfos, SUPPORTED/SELECTOR/RESEARCH @@ -394,5 +386,5 @@ Request size limits, stream/gateway timeouts, tool-step budgets, the rate-limit - The **mock smoke test uses the production server** (`next start`), so build first. - `pnpm.onlyBuiltDependencies` already approves the `sharp` build script — do not run `pnpm approve-builds`. - Don't reintroduce Sentry/PostHog/OpenTelemetry; they were intentionally removed in favor of Vercel Analytics/Speed Insights + structured logs. -- Pinning, selected model, and run mode are **client-side localStorage** — there are no server columns or APIs for them. +- Pinning and selected model are **client-side localStorage** — there are no server columns or APIs for them. - After signing up via `/api/auth/sign-up/email`, the session cookie is set automatically; no separate sign-in is needed. diff --git a/README.md b/README.md index cbf736e4..d1bc2f8a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Chloei -Chloei is a Next.js 16 chat app backed by Vercel AI Gateway. It currently exposes a curated model selector that defaults to Qwen 3.7 Max and also includes Kimi K2.6 and MiMo V2.5 Pro, routes Research mode to Qwen 3.7 Max with a dedicated Deep Research instruction template, and offers local code execution, optional Tavily retrieval, and Better Auth email/password authentication with PostgreSQL-backed users and sessions. +Chloei is a Next.js 16 chat app backed by Vercel AI Gateway. It currently exposes a curated model selector that defaults to Qwen 3.7 Max and also includes Kimi K2.6 and MiMo V2.5 Pro, and offers local code execution, optional Tavily retrieval, and Better Auth email/password authentication with PostgreSQL-backed users and sessions. ## Documentation diff --git a/src/app/api/agent/follow-ups/route.ts b/src/app/api/agent/follow-ups/route.ts index 2b302df7..ea02b31c 100644 --- a/src/app/api/agent/follow-ups/route.ts +++ b/src/app/api/agent/follow-ups/route.ts @@ -237,7 +237,6 @@ export async function POST(request: NextRequest) { aiGatewayApiKey, messages: parsed.messages, model: parsed.model, - runMode: parsed.runMode, signal: request.signal, userId: session.user.id, }) diff --git a/src/app/api/agent/route.ts b/src/app/api/agent/route.ts index 079cc32a..68773101 100644 --- a/src/app/api/agent/route.ts +++ b/src/app/api/agent/route.ts @@ -31,7 +31,6 @@ import { isE2eMockModeEnabled, } from "@/lib/server/e2e-test-mode" import { resolveAgentFeatureFlags } from "@/lib/server/integration-flags" -import type { AgentRuntimeProfileId } from "@/lib/server/llm/agent-runtime" import { evaluateAndConsumeSlidingWindowRateLimit, tryAcquireConcurrencySlot, @@ -41,7 +40,6 @@ import { observeRouteResponse, } from "@/lib/server/route-observability" import { isThreadStoreNotInitializedError } from "@/lib/server/threads" -import type { AgentRunMode } from "@/lib/shared" export const runtime = "nodejs" export const maxDuration = 800 @@ -50,10 +48,6 @@ function resolveRateLimitIdentifier(userId: string): string { return `user:${userId}` } -function resolveRuntimeProfile(runMode: AgentRunMode): AgentRuntimeProfileId { - return runMode === "research" ? "deep_research" : "chat_default" -} - export async function POST(request: NextRequest) { const requestId = resolveRequestId(request) const logger = createLogger(`agent:${requestId}`) @@ -163,11 +157,7 @@ export async function POST(request: NextRequest) { const userTimeZone = resolveUserTimeZone(request) const featureFlags = await resolveAgentFeatureFlags() const promptProvider = resolvePromptProvider(selectedModel) - const inferredPromptTaskMode = - parsedRequest.runMode === "research" - ? "research" - : inferPromptTaskMode(parsedRequest.messages) - const promptTaskMode = inferredPromptTaskMode + const promptTaskMode = inferPromptTaskMode(parsedRequest.messages) const systemInstruction = buildAgentSystemInstruction( { id: session.user.id, @@ -179,9 +169,6 @@ export async function POST(request: NextRequest) { userTimeZone, provider: promptProvider, taskMode: promptTaskMode, - ...(parsedRequest.runMode === "research" - ? { deepResearchMode: true } - : {}), } ) @@ -253,7 +240,6 @@ export async function POST(request: NextRequest) { aiGatewayApiKey, tavilyApiKey, userTimeZone, - runtimeProfile: resolveRuntimeProfile(parsedRequest.runMode), taskMode: promptTaskMode, userId: session.user.id, featureFlags, diff --git a/src/components/agent/home/agent-session-state.ts b/src/components/agent/home/agent-session-state.ts index f58a9364..f5f24a4f 100644 --- a/src/components/agent/home/agent-session-state.ts +++ b/src/components/agent/home/agent-session-state.ts @@ -1,5 +1,4 @@ import { - type AgentRunMode, type FollowUpQuestion, type Message as AgentMessage, type ModelType, @@ -31,14 +30,12 @@ export function createAssistantMessageFromAccumulator({ createdAt, accumulator, model, - runMode, isStreaming, }: { id: string createdAt: string accumulator: AgentStreamAccumulator model: ModelType - runMode: AgentRunMode isStreaming: boolean }): AgentMessage { return { @@ -49,7 +46,6 @@ export function createAssistantMessageFromAccumulator({ createdAt, metadata: { isStreaming, - runMode, parts: [{ type: "text", text: accumulator.content }], ...(accumulator.agentStatus ? { agentStatus: accumulator.agentStatus } diff --git a/src/components/agent/home/follow-up-questions.ts b/src/components/agent/home/follow-up-questions.ts index fe6ba995..125f0744 100644 --- a/src/components/agent/home/follow-up-questions.ts +++ b/src/components/agent/home/follow-up-questions.ts @@ -1,5 +1,4 @@ import { - type AgentRunMode, type FollowUpQuestion, isModelType, type Message as AgentMessage, @@ -19,7 +18,6 @@ interface FollowUpQuestionRequestTarget { assistantMessageId: string messages: AgentMessage[] model: ModelType - runMode: AgentRunMode } type FollowUpQuestionRequestKind = "backfill" | "final" | "parallel" @@ -29,7 +27,6 @@ export interface FollowUpQuestionRequestParams { requestKind: FollowUpQuestionRequestKind messages: AgentMessage[] model: ModelType - runMode: AgentRunMode threadId: string } @@ -148,7 +145,6 @@ export function getFollowUpQuestionRequestTargets( assistantMessageId: message.id, messages: messages.slice(0, index + 1), model, - runMode: message.metadata.runMode ?? "chat", }) }) diff --git a/src/components/agent/home/home-agent-utils.ts b/src/components/agent/home/home-agent-utils.ts index 98a5b2cc..39ade5fa 100644 --- a/src/components/agent/home/home-agent-utils.ts +++ b/src/components/agent/home/home-agent-utils.ts @@ -1,10 +1,6 @@ import { ASSISTANT_EMPTY_RESPONSE_FALLBACK } from "@/lib/constants" import { createRequestHeaders } from "@/lib/request-id" -import { - type AgentRunMode, - type Message as AgentMessage, - type ModelType, -} from "@/lib/shared" +import { type Message as AgentMessage, type ModelType } from "@/lib/shared" import { AGENT_REQUEST_MAX_MESSAGE_CHARS, AGENT_REQUEST_MAX_MESSAGES, @@ -107,8 +103,7 @@ export function toRequestMessages( export function appendUserMessage( currentMessages: AgentMessage[], content: string, - model: ModelType, - runMode: AgentRunMode = "chat" + model: ModelType ): AgentMessage[] { const userMessage: AgentMessage = { id: createClientMessageId(), @@ -119,7 +114,6 @@ export function appendUserMessage( metadata: { isStreaming: false, selectedModel: model, - runMode, }, } diff --git a/src/components/agent/home/home-content.tsx b/src/components/agent/home/home-content.tsx index 69e21cf3..206471d4 100644 --- a/src/components/agent/home/home-content.tsx +++ b/src/components/agent/home/home-content.tsx @@ -28,11 +28,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip" import { useIsMobile } from "@/hooks/use-mobile" -import { - type AgentRunMode, - type AuthViewer, - type ModelType, -} from "@/lib/shared" +import { type AuthViewer, type ModelType } from "@/lib/shared" import { cn } from "@/lib/utils" import { PromptForm } from "../prompt-form/prompt-form" @@ -85,28 +81,15 @@ export function HomePageContent({ } = useAgentSession(threadStore) const handlePromptFormSubmit = useCallback( - ( - message: string, - model: ModelType, - _isStreaming: boolean, - runMode: AgentRunMode - ) => { - handlePromptSubmit(message, model, runMode) + (message: string, model: ModelType) => { + handlePromptSubmit(message, model) }, [handlePromptSubmit] ) const handleFollowUpQuestionClick = useCallback( - ({ - model, - question, - runMode, - }: { - model: ModelType - question: string - runMode: AgentRunMode - }) => { - handlePromptSubmit(question, model, runMode) + ({ model, question }: { model: ModelType; question: string }) => { + handlePromptSubmit(question, model) }, [handlePromptSubmit] ) @@ -221,15 +204,10 @@ export function HomePageContent({ }, [fallbackTransitionMs]) const handleAnimatedPromptSubmit = useCallback( - ( - message: string, - model: ModelType, - _isStreaming: boolean, - runMode: AgentRunMode - ) => { + (message: string, model: ModelType) => { if (isMobile) { startFallbackConversationTransition() - handlePromptSubmit(message, model, runMode) + handlePromptSubmit(message, model) return } @@ -244,13 +222,13 @@ export function HomePageContent({ if (!startViewTransition) { startFallbackConversationTransition() - handlePromptSubmit(message, model, runMode) + handlePromptSubmit(message, model) return } startViewTransition(() => { flushSync(() => { - handlePromptSubmit(message, model, runMode) + handlePromptSubmit(message, model) }) }) }, diff --git a/src/components/agent/home/use-agent-session.ts b/src/components/agent/home/use-agent-session.ts index 33fd6691..c45a46fd 100644 --- a/src/components/agent/home/use-agent-session.ts +++ b/src/components/agent/home/use-agent-session.ts @@ -11,7 +11,6 @@ import { } from "@/lib/http-error" import { getRequestIdFromHeaders } from "@/lib/request-id" import { - type AgentRunMode, type FollowUpQuestion, isModelType, type Message as AgentMessage, @@ -62,13 +61,11 @@ interface EditMessageParams { messageId: string newContent: string newModel: ModelType - newRunMode: AgentRunMode } interface QueuedSubmission { message: string model: ModelType - runMode: AgentRunMode } const INITIAL_STATE: AgentSessionState = { @@ -253,7 +250,6 @@ export function useAgentSession({ body: Record baseMessages: AgentMessage[] model: ModelType - runMode: AgentRunMode threadId: string errorTitle: string }) => { @@ -305,7 +301,6 @@ export function useAgentSession({ createdAt: assistantCreatedAt, accumulator: nextAccumulator, model: effectiveModel, - runMode: params.runMode, isStreaming: streamFlags.isStreaming, }) let assistantMessageWithFollowUpState = assistantMessage @@ -367,7 +362,6 @@ export function useAgentSession({ messages: messagesRef.current, model: effectiveModel, requestKind: "parallel", - runMode: params.runMode, threadId: params.threadId, }) } @@ -498,7 +492,6 @@ export function useAgentSession({ messages: messagesRef.current, model: effectiveModel, requestKind: "final", - runMode: params.runMode, threadId: params.threadId, }) } @@ -543,7 +536,6 @@ export function useAgentSession({ createdAt: new Date().toISOString(), metadata: { isStreaming: false, - runMode: params.runMode, parts: [{ type: "text", text: fallback }], agentStatus: "failed", }, @@ -591,7 +583,6 @@ export function useAgentSession({ async ( nextMessages: AgentMessage[], model: ModelType, - runMode: AgentRunMode = "chat", threadId?: string ) => { const activeThreadId = threadId ?? ensureCurrentThreadId() @@ -603,11 +594,9 @@ export function useAgentSession({ threadId: activeThreadId, baseMessages: nextMessages, model, - runMode, errorTitle: "Failed to send message", body: { model, - runMode, threadId: activeThreadId, messages: requestMessages, }, @@ -617,11 +606,7 @@ export function useAgentSession({ ) const handleSubmit = useCallback( - async ( - message: string, - model: ModelType, - runMode: AgentRunMode = "chat" - ) => { + async (message: string, model: ModelType) => { const trimmedMessage = message.trim() if (!trimmedMessage) { return @@ -638,17 +623,16 @@ export function useAgentSession({ const nextMessages = appendUserMessage( messagesRef.current, trimmedMessage, - model, - runMode + model ) - await runAgentRequest(nextMessages, model, runMode, activeThreadId) + await runAgentRequest(nextMessages, model, activeThreadId) }, [ensureCurrentThreadId, runAgentRequest] ) const handleEditMessage = useCallback( - ({ messageId, newContent, newModel, newRunMode }: EditMessageParams) => { + ({ messageId, newContent, newModel }: EditMessageParams) => { const trimmedContent = newContent.trim() const currentMessages = messagesRef.current @@ -684,7 +668,6 @@ export function useAgentSession({ metadata: { ...targetMessage.metadata, selectedModel: newModel, - runMode: newRunMode, }, } @@ -702,18 +685,13 @@ export function useAgentSession({ ) } - void runAgentRequest( - nextMessages, - newModel, - newRunMode, - activeThreadId ?? undefined - ) + void runAgentRequest(nextMessages, newModel, activeThreadId ?? undefined) }, [createThreadSnapshot, runAgentRequest, saveThread] ) const handlePromptSubmit = useCallback( - (message: string, model: ModelType, runMode: AgentRunMode = "chat") => { + (message: string, model: ModelType) => { const trimmedMessage = message.trim() if (!trimmedMessage) { return @@ -723,12 +701,11 @@ export function useAgentSession({ setQueuedSubmission({ message: trimmedMessage, model, - runMode, }) return } - void handleSubmit(trimmedMessage, model, runMode) + void handleSubmit(trimmedMessage, model) }, [handleSubmit] ) @@ -739,11 +716,7 @@ export function useAgentSession({ } setQueuedSubmission(null) - void handleSubmit( - queuedSubmission.message, - queuedSubmission.model, - queuedSubmission.runMode - ) + void handleSubmit(queuedSubmission.message, queuedSubmission.model) }, [streamingState, queuedSubmission, handleSubmit]) return { diff --git a/src/components/agent/home/use-follow-up-questions.ts b/src/components/agent/home/use-follow-up-questions.ts index 7365b825..cf852b8e 100644 --- a/src/components/agent/home/use-follow-up-questions.ts +++ b/src/components/agent/home/use-follow-up-questions.ts @@ -288,7 +288,6 @@ export function useFollowUpQuestions({ headers: createAgentRequestHeaders(), body: JSON.stringify({ model: params.model, - runMode: params.runMode, threadId: params.threadId, assistantMessageId: params.assistantMessageId, messages: toRequestMessages(params.messages), diff --git a/src/components/agent/messages/assistant-message.tsx b/src/components/agent/messages/assistant-message.tsx index d204d17d..04870eae 100644 --- a/src/components/agent/messages/assistant-message.tsx +++ b/src/components/agent/messages/assistant-message.tsx @@ -13,7 +13,6 @@ import { useMemo, useState } from "react" import { LogoHover } from "@/components/graphics/logo/logo-hover" import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard" import { - type AgentRunMode, type FollowUpQuestion, isModelType, type Message, @@ -190,7 +189,6 @@ export function AssistantMessage({ onFollowUpQuestionClick?: (params: { model: ModelType question: string - runMode: AgentRunMode }) => void onRegenerate?: () => void }) { @@ -227,7 +225,6 @@ export function AssistantMessage({ const followUpModel = isModelType(message.llmModel) ? message.llmModel : message.metadata?.selectedModel - const followUpRunMode = message.metadata?.runMode ?? "chat" const followUpQuestions = isAssistantStreaming ? [] : (message.metadata?.followUpQuestions ?? []).filter( @@ -409,7 +406,6 @@ export function AssistantMessage({ onFollowUpQuestionClick({ model: followUpModel, question, - runMode: followUpRunMode, }) }} /> diff --git a/src/components/agent/messages/messages.tsx b/src/components/agent/messages/messages.tsx index 5a64f5f3..efaee3c7 100644 --- a/src/components/agent/messages/messages.tsx +++ b/src/components/agent/messages/messages.tsx @@ -4,7 +4,6 @@ import "../shared/shell-styles.css" import { memo, useMemo } from "react" import { - type AgentRunMode, isAssistantMessage, isModelType, isUserMessage, @@ -71,12 +70,10 @@ function MessagesComponent({ messageId: string newContent: string newModel: ModelType - newRunMode: AgentRunMode }) => Promise | void onFollowUpQuestionClick?: (params: { model: ModelType question: string - runMode: AgentRunMode }) => void }) { const messageGroups = useMemo(() => groupMessages(messages), [messages]) @@ -133,10 +130,6 @@ function MessagesComponent({ const regenerateModel = isModelType(message.llmModel) ? message.llmModel : userMessage?.metadata?.selectedModel - const regenerateRunMode = - message.metadata?.runMode ?? - userMessage?.metadata?.runMode ?? - "chat" const canRegenerate = !disableEditing && Boolean(userMessage) && @@ -149,7 +142,6 @@ function MessagesComponent({ messageId: userMessage.id, newContent: userMessage.content, newModel: regenerateModel, - newRunMode: regenerateRunMode, }) } : undefined diff --git a/src/components/agent/messages/user-message.tsx b/src/components/agent/messages/user-message.tsx index 2b3cffcb..c4ce8349 100644 --- a/src/components/agent/messages/user-message.tsx +++ b/src/components/agent/messages/user-message.tsx @@ -5,7 +5,6 @@ import { toast } from "sonner" import { useModels } from "@/hooks/agent/use-models" import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard" import { - type AgentRunMode, getModelSelectorModels, isModelSelectorModel, isModelType, @@ -19,7 +18,6 @@ import { Button } from "../../ui/button" import { Textarea } from "../../ui/textarea" import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip" import { ModelSelector } from "../prompt-form/model-selector" -import { ResearchModeToggle } from "../prompt-form/research-mode-toggle" import { agentShellFrameClass, agentShellHighlightClass, @@ -45,7 +43,6 @@ export function UserMessage({ messageId: string newContent: string newModel: ModelType - newRunMode: AgentRunMode }) => Promise | void }) { const { data: availableModels } = useModels() @@ -72,8 +69,6 @@ export function UserMessage({ const [isEditing, setIsEditing] = useState(false) const [editValue, setEditValue] = useState(message.content) const [selectedModel, setSelectedModel] = useState(initialModel) - const initialRunMode = message.metadata?.runMode ?? "chat" - const [runMode, setRunMode] = useState(initialRunMode) const [isEditPending, setIsEditPending] = useState(false) const messageContentRef = useRef(null) const textareaRef = useRef(null) @@ -107,16 +102,14 @@ export function UserMessage({ const handleStartEditing = useCallback(() => { setEditValue(message.content) setSelectedModel(initialModel) - setRunMode(initialRunMode) setIsEditing(true) - }, [initialModel, initialRunMode, message.content]) + }, [initialModel, message.content]) const handleStopEditing = useCallback(() => { setIsEditing(false) setEditValue(message.content) setSelectedModel(initialModel) - setRunMode(initialRunMode) - }, [message.content, initialModel, initialRunMode]) + }, [message.content, initialModel]) const handleSubmit = useCallback(async () => { const trimmedValue = editValue.trim() @@ -137,7 +130,6 @@ export function UserMessage({ messageId: message.id, newContent: trimmedValue, newModel: selectedModel, - newRunMode: runMode, }) setIsEditing(false) } catch (error) { @@ -147,14 +139,7 @@ export function UserMessage({ } finally { setIsEditPending(false) } - }, [ - editValue, - handleStopEditing, - message.id, - onEditMessage, - runMode, - selectedModel, - ]) + }, [editValue, handleStopEditing, message.id, onEditMessage, selectedModel]) const onKeyDown = useCallback( (e: React.KeyboardEvent) => { @@ -242,11 +227,6 @@ export function UserMessage({ selectedModel={selectedModel} handleSelectModel={handleSelectModel} /> -
diff --git a/src/components/agent/prompt-form/prompt-form.tsx b/src/components/agent/prompt-form/prompt-form.tsx index 67f5853c..c62e0e06 100644 --- a/src/components/agent/prompt-form/prompt-form.tsx +++ b/src/components/agent/prompt-form/prompt-form.tsx @@ -2,7 +2,7 @@ import "../shared/shell-styles.css" -import { CornerRightUp, Loader2, Plus, Square, Telescope } from "lucide-react" +import { CornerRightUp, Loader2, Square } from "lucide-react" import { type CSSProperties, type TransitionStartFunction, @@ -15,20 +15,10 @@ import { import { RefreshGlow } from "@/components/graphics/effects/refresh-glow" import { Button } from "@/components/ui/button" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" import { Textarea } from "@/components/ui/textarea" import { useModels } from "@/hooks/agent/use-models" -import { usePersistentRunMode } from "@/hooks/agent/use-persistent-run-mode" import { usePersistentSelectedModel } from "@/hooks/agent/use-persistent-selected-model" -import { - type AgentRunMode, - getModelSelectorModels, - type ModelType, -} from "@/lib/shared" +import { getModelSelectorModels, type ModelType } from "@/lib/shared" import { cn } from "@/lib/utils" import { QueuedAction } from "../messages/queued-message" @@ -44,7 +34,6 @@ import { ModelSelector } from "./model-selector" interface QueuedPromptSubmission { message: string model: ModelType - runMode: AgentRunMode } export function PromptForm({ @@ -63,12 +52,7 @@ export function PromptForm({ transition, viewTransitionName, }: { - onSubmit?: ( - message: string, - model: ModelType, - queue: boolean, - runMode: AgentRunMode - ) => void + onSubmit?: (message: string, model: ModelType, queue: boolean) => void onStopStream?: () => void isStreaming?: boolean isHome?: boolean @@ -92,11 +76,8 @@ export function PromptForm({ const shouldShowRefreshAnimation = isHome && !dockToBottomOnHome const [message, setMessage] = useState("") - const [isToolsOpen, setIsToolsOpen] = useState(false) - const { runMode, setRunMode } = usePersistentRunMode() const trimmedMessage = useMemo(() => message.trim(), [message]) const textareaRef = useRef(null) - const shouldPreventToolsCloseAutoFocusRef = useRef(false) const { data: availableModels } = useModels() const modelSelectorModels = useMemo( @@ -118,7 +99,6 @@ export function PromptForm({ ) const resolvedSelectedModel = selectedModel - const isResearchMode = runMode === "research" const handleSelectModel = useCallback( (model: ModelType | null) => { @@ -133,7 +113,6 @@ export function PromptForm({ } setMessage(queuedSubmission.message) - setRunMode(queuedSubmission.runMode) setSelectedModel(queuedSubmission.model) onClearQueuedMessage?.() @@ -148,22 +127,7 @@ export function PromptForm({ textarea.setSelectionRange(cursorPosition, cursorPosition) textarea.scrollTop = textarea.scrollHeight }) - }, [onClearQueuedMessage, queuedSubmission, setRunMode, setSelectedModel]) - - const handleSetRunMode = useCallback( - (nextRunMode: AgentRunMode) => { - if (isFormPending) { - return - } - - shouldPreventToolsCloseAutoFocusRef.current = true - setRunMode((currentRunMode) => - currentRunMode === nextRunMode ? "chat" : nextRunMode - ) - setIsToolsOpen(false) - }, - [isFormPending, setRunMode] - ) + }, [onClearQueuedMessage, queuedSubmission, setSelectedModel]) const submitPrompt = useCallback(() => { const nextMessage = message.trim() @@ -185,8 +149,7 @@ export function PromptForm({ } } - const activeRunMode = runMode - onSubmit?.(nextMessage, resolvedSelectedModel, isStreaming, activeRunMode) + onSubmit?.(nextMessage, resolvedSelectedModel, isStreaming) setMessage("") return true @@ -198,7 +161,6 @@ export function PromptForm({ onStopStream, onSubmit, resolvedSelectedModel, - runMode, ]) const handleSubmit = useCallback( @@ -237,26 +199,6 @@ export function PromptForm({ } }, [isStreaming, onStopStream]) - useEffect(() => { - const handleResearchShortcut = (event: KeyboardEvent) => { - if ( - !isFormPending && - event.code === "Slash" && - (event.metaKey || event.ctrlKey) - ) { - event.preventDefault() - setRunMode((currentRunMode) => - currentRunMode === "research" ? "chat" : "research" - ) - } - } - - window.addEventListener("keydown", handleResearchShortcut) - return () => { - window.removeEventListener("keydown", handleResearchShortcut) - } - }, [isFormPending, setRunMode]) - const isSubmitButtonDisabled = isFormPending || !resolvedSelectedModel || (!isStreaming && !trimmedMessage) @@ -312,85 +254,10 @@ export function PromptForm({
- { - if (!isFormPending) { - setIsToolsOpen(open) - } - }} - > - - - - { - if (!shouldPreventToolsCloseAutoFocusRef.current) { - return true - } - - shouldPreventToolsCloseAutoFocusRef.current = false - textareaRef.current?.focus({ preventScroll: true }) - return false - }} - className="flex w-56 flex-col gap-0.5 rounded-none p-1.5" - > - - - - {isResearchMode ? ( - - ) : ( - - )} +
diff --git a/src/components/agent/prompt-form/research-mode-toggle.tsx b/src/components/agent/prompt-form/research-mode-toggle.tsx deleted file mode 100644 index 3b070936..00000000 --- a/src/components/agent/prompt-form/research-mode-toggle.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Telescope } from "lucide-react" -import { useEffect } from "react" - -import { Button } from "@/components/ui/button" -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip" -import type { AgentRunMode } from "@/lib/shared" -import { cn } from "@/lib/utils" - -export function ResearchModeToggle({ - runMode, - onRunModeChange, - disabled = false, -}: { - runMode: AgentRunMode - onRunModeChange: (runMode: AgentRunMode) => void - disabled?: boolean -}) { - const isResearch = runMode === "research" - - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if ( - !disabled && - event.code === "Slash" && - (event.metaKey || event.ctrlKey) - ) { - event.preventDefault() - onRunModeChange(isResearch ? "chat" : "research") - } - } - - window.addEventListener("keydown", handleKeyDown) - return () => { - window.removeEventListener("keydown", handleKeyDown) - } - }, [disabled, isResearch, onRunModeChange]) - - const baseClass = - "px-2 font-normal text-muted-foreground hover:bg-accent focus-visible:border-transparent focus-visible:ring-0" - const activeClass = - "bg-accent text-foreground hover:bg-accent aria-pressed:bg-accent aria-pressed:text-foreground" - - return ( - - - - - - Deep Research - - - ) -} diff --git a/src/hooks/agent/persistent-run-mode-utils.ts b/src/hooks/agent/persistent-run-mode-utils.ts deleted file mode 100644 index 521a12df..00000000 --- a/src/hooks/agent/persistent-run-mode-utils.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AGENT_RUN_MODES, type AgentRunMode } from "@/lib/shared" - -const STORED_RUN_MODE_VERSION = 1 - -interface StoredRunMode { - runMode: AgentRunMode - source: "user" - version: typeof STORED_RUN_MODE_VERSION -} - -function isAgentRunMode(value: unknown): value is AgentRunMode { - return ( - typeof value === "string" && - (AGENT_RUN_MODES as readonly string[]).includes(value) - ) -} - -export function serializeStoredRunMode(runMode: AgentRunMode): StoredRunMode { - return { - runMode, - source: "user", - version: STORED_RUN_MODE_VERSION, - } -} - -export function parseStoredRunMode(value: unknown): AgentRunMode | null { - if (typeof value !== "string") { - return null - } - - if (isAgentRunMode(value)) { - return value - } - - try { - const parsed: unknown = JSON.parse(value) - - if ( - parsed && - typeof parsed === "object" && - "runMode" in parsed && - "source" in parsed && - "version" in parsed && - parsed.source === "user" && - parsed.version === STORED_RUN_MODE_VERSION && - isAgentRunMode(parsed.runMode) - ) { - return parsed.runMode - } - } catch { - return null - } - - return null -} - -export function resolvePersistedRunMode(params: { - storedRunMode: AgentRunMode | null - currentRunMode?: AgentRunMode | null - fallbackRunMode?: AgentRunMode -}): AgentRunMode { - return ( - params.storedRunMode ?? - params.currentRunMode ?? - params.fallbackRunMode ?? - "chat" - ) -} diff --git a/src/hooks/agent/use-persistent-run-mode.tsx b/src/hooks/agent/use-persistent-run-mode.tsx deleted file mode 100644 index 516e180b..00000000 --- a/src/hooks/agent/use-persistent-run-mode.tsx +++ /dev/null @@ -1,115 +0,0 @@ -"use client" - -import { useCallback, useSyncExternalStore } from "react" - -import { RUN_MODE_STORAGE_KEY, RUN_MODE_UPDATED_EVENT } from "@/lib/constants" -import type { AgentRunMode } from "@/lib/shared" - -import { - parseStoredRunMode, - resolvePersistedRunMode, - serializeStoredRunMode, -} from "./persistent-run-mode-utils" - -type RunModeUpdater = - | AgentRunMode - | ((currentRunMode: AgentRunMode) => AgentRunMode) - -let memoryRunMode: AgentRunMode | null = null - -function readStoredRunMode(fallbackRunMode: AgentRunMode): AgentRunMode { - if (typeof window === "undefined") { - return fallbackRunMode - } - - let storedValue: string | null = null - try { - storedValue = window.localStorage.getItem(RUN_MODE_STORAGE_KEY) - } catch { - return resolvePersistedRunMode({ - storedRunMode: memoryRunMode, - fallbackRunMode, - }) - } - - return resolvePersistedRunMode({ - storedRunMode: parseStoredRunMode(storedValue) ?? memoryRunMode, - fallbackRunMode, - }) -} - -function writeStoredRunMode(runMode: AgentRunMode) { - if (typeof window === "undefined") { - return - } - - memoryRunMode = runMode - - try { - window.localStorage.setItem( - RUN_MODE_STORAGE_KEY, - JSON.stringify(serializeStoredRunMode(runMode)) - ) - } catch { - // Keep the in-memory mode so restricted storage environments still update. - } - - try { - window.dispatchEvent(new CustomEvent(RUN_MODE_UPDATED_EVENT)) - } catch { - // Storage events are best-effort; callers still have the in-memory value. - } -} - -function subscribeToRunMode(onStoreChange: () => void) { - if (typeof window === "undefined") { - return () => undefined - } - - const handleStorage = (event: StorageEvent) => { - if (event.key && event.key !== RUN_MODE_STORAGE_KEY) { - return - } - - onStoreChange() - } - - window.addEventListener("storage", handleStorage) - window.addEventListener(RUN_MODE_UPDATED_EVENT, onStoreChange) - - return () => { - window.removeEventListener("storage", handleStorage) - window.removeEventListener(RUN_MODE_UPDATED_EVENT, onStoreChange) - } -} - -export function usePersistentRunMode(initialRunMode: AgentRunMode = "chat") { - const getSnapshot = useCallback( - () => readStoredRunMode(initialRunMode), - [initialRunMode] - ) - const getServerSnapshot = useCallback(() => initialRunMode, [initialRunMode]) - const runMode = useSyncExternalStore( - subscribeToRunMode, - getSnapshot, - getServerSnapshot - ) - - const setRunMode = useCallback( - (nextRunMode: RunModeUpdater) => { - const currentRunMode = readStoredRunMode(initialRunMode) - const resolvedRunMode = - typeof nextRunMode === "function" - ? nextRunMode(currentRunMode) - : nextRunMode - - writeStoredRunMode(resolvedRunMode) - }, - [initialRunMode] - ) - - return { - runMode, - setRunMode, - } -} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index f281f613..73954562 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,7 +1,5 @@ export const MODEL_SELECTOR_STORAGE_KEY = "model-selector-state" export const MODEL_SELECTOR_UPDATED_EVENT = "model-selector-updated" -export const RUN_MODE_STORAGE_KEY = "agent-run-mode-state" -export const RUN_MODE_UPDATED_EVENT = "agent-run-mode-updated" export const ASSISTANT_EMPTY_RESPONSE_FALLBACK = "Sorry, I couldn't generate a response from that input. Please retry." diff --git a/src/lib/server/agent-context.ts b/src/lib/server/agent-context.ts index 971d2f63..b4131224 100644 --- a/src/lib/server/agent-context.ts +++ b/src/lib/server/agent-context.ts @@ -1,6 +1,5 @@ import { type AuthViewer, - DEEP_RESEARCH_SYSTEM_INSTRUCTION, DEFAULT_OPERATING_INSTRUCTION, DEFAULT_SOUL_FALLBACK_INSTRUCTION, } from "@/lib/shared" @@ -14,7 +13,6 @@ import { interface RuntimePromptContext { now: Date userTimeZone?: string - deepResearchMode?: boolean provider?: PromptProvider taskMode?: PromptTaskMode } @@ -127,12 +125,6 @@ function composeSystemInstruction(params: { blocks.push(formatPromptBlock(block.label, block.body)) } - if (params.runtimeContext.deepResearchMode) { - blocks.push( - formatPromptBlock("DEEP RESEARCH MODE", DEEP_RESEARCH_SYSTEM_INSTRUCTION) - ) - } - blocks.push( formatPromptBlock( "IDENTITY AND TONE CONTEXT", diff --git a/src/lib/server/agent-follow-ups.ts b/src/lib/server/agent-follow-ups.ts index ffa19c10..14025cb1 100644 --- a/src/lib/server/agent-follow-ups.ts +++ b/src/lib/server/agent-follow-ups.ts @@ -3,11 +3,7 @@ import { randomUUID } from "node:crypto" import type { GatewayProviderOptions } from "@ai-sdk/gateway" import { z } from "zod" -import { - type AgentRunMode, - type FollowUpQuestion, - type ModelType, -} from "@/lib/shared" +import { type FollowUpQuestion, type ModelType } from "@/lib/shared" import { AGENT_REQUEST_MAX_MESSAGE_CHARS, AGENT_REQUEST_MAX_MESSAGES, @@ -72,7 +68,6 @@ const followUpContextMessageSchema = z export const followUpRequestSchema = z .object({ model: z.string().trim().min(1).max(200), - runMode: z.enum(["chat", "research"]).optional(), threadId: z.string().trim().min(1).max(200).optional(), assistantMessageId: z.string().trim().min(1).max(200).optional(), messages: z @@ -169,7 +164,6 @@ export async function generateFollowUpQuestions(params: { aiGatewayApiKey: string messages: readonly FollowUpContextMessage[] model: ModelType - runMode?: AgentRunMode signal?: AbortSignal userId: string }): Promise { @@ -192,7 +186,6 @@ export async function generateFollowUpQuestions(params: { prompt: [ "Generate exactly three follow-up questions for the latest assistant response.", 'Use this exact JSON shape: {"questions":["...","...","..."]}.', - `Conversation mode: ${params.runMode ?? "chat"}.`, "Avoid questions the assistant already answered directly.", "Conversation:", context, @@ -207,7 +200,6 @@ export async function generateFollowUpQuestions(params: { "feature:follow_up_questions", `generation_model:${FOLLOW_UP_GENERATION_MODEL}`, `source_model:${params.model}`, - `run_mode:${params.runMode ?? "chat"}`, ], } satisfies GatewayProviderOptions, }, diff --git a/src/lib/server/agent-route.ts b/src/lib/server/agent-route.ts index 0121e079..6b722e34 100644 --- a/src/lib/server/agent-route.ts +++ b/src/lib/server/agent-route.ts @@ -7,14 +7,11 @@ import { createLogger } from "@/lib/logger" import { resolveRequestIdFromHeaders } from "@/lib/request-id" import type { AgentFeatureFlags } from "@/lib/server/integration-flags" import { - AGENT_RUN_MODES, - type AgentRunMode, type AgentStreamEvent, ALL_MODELS, MODEL_SELECTOR_MODELS, type ModelInfo, type ModelType, - RESEARCH_MODEL, resolveDefaultModelSelectorModel, } from "@/lib/shared" @@ -25,7 +22,6 @@ import { AGENT_MAX_TOTAL_CHARS, } from "./agent-runtime-config" import { createApiErrorBody, createApiHeaders } from "./api-response" -import type { AgentRuntimeProfileId } from "./llm/agent-runtime" import { startGatewayResponseStream } from "./llm/gateway-responses" import { withAiSdkInlineCitationInstruction } from "./llm/system-instruction-augmentations" @@ -63,16 +59,12 @@ const agentMessageSchema = z export const agentStreamRequestSchema = z .object({ model: z.string().trim().min(1).max(200).optional(), - runMode: z.enum(AGENT_RUN_MODES).optional(), threadId: z.string().trim().min(1).max(200).optional(), messages: z.array(agentMessageSchema).min(1), }) .strict() type AgentStreamRequest = z.infer -type ParsedAgentStreamRequestData = Omit & { - runMode: AgentRunMode -} interface AgentRateLimitDecision { allowed: boolean limit: number @@ -81,7 +73,7 @@ interface AgentRateLimitDecision { resetAtEpochSeconds: number } interface ParsedAgentStreamRequest { - parsedRequest: ParsedAgentStreamRequestData + parsedRequest: AgentStreamRequest selectedModel: ModelType } @@ -110,8 +102,7 @@ interface CreateAgentStreamResponseParams { aiGatewayApiKey: string tavilyApiKey?: string userTimeZone?: string - runtimeProfile?: AgentRuntimeProfileId - taskMode?: PromptTaskMode + taskMode: PromptTaskMode userId?: string featureFlags?: AgentFeatureFlags messages: AgentStreamRequest["messages"] @@ -360,21 +351,6 @@ export function parseAgentStreamRequest( }) } - const runMode = parsed.data.runMode ?? "chat" - - if ( - runMode === "research" && - !isAvailableModel(params.availableModels, RESEARCH_MODEL) - ) { - return createJsonErrorResponse({ - requestId: params.requestId, - error: "Research mode requires Qwen 3.7 Max model access.", - errorCode: "AGENT_RESEARCH_MODEL_UNAVAILABLE", - status: 400, - rateLimitDecision: params.rateLimitDecision, - }) - } - const availableModelIds = new Set( params.availableModels.map((model) => model.id) ) @@ -382,9 +358,7 @@ export function parseAgentStreamRequest( availableModelIds.has(modelId) ? [{ id: modelId }] : [] ) const selectedModelCandidate = - runMode === "research" - ? RESEARCH_MODEL - : (parsed.data.model ?? resolveDefaultModelSelectorModel(chatModels)) + parsed.data.model ?? resolveDefaultModelSelectorModel(chatModels) if (!isSupportedModel(selectedModelCandidate)) { return createJsonErrorResponse({ @@ -396,10 +370,7 @@ export function parseAgentStreamRequest( }) } - if ( - runMode !== "research" && - !isAvailableModel(chatModels, selectedModelCandidate) - ) { + if (!isAvailableModel(chatModels, selectedModelCandidate)) { return createJsonErrorResponse({ requestId: params.requestId, error: "Unsupported model selected.", @@ -410,10 +381,7 @@ export function parseAgentStreamRequest( } return { - parsedRequest: { - ...parsed.data, - runMode, - }, + parsedRequest: parsed.data, selectedModel: selectedModelCandidate, } } @@ -549,8 +517,7 @@ export function createAgentStreamResponse( aiGatewayApiKey: params.aiGatewayApiKey, tavilyApiKey: params.tavilyApiKey, userTimeZone: params.userTimeZone, - runtimeProfile: params.runtimeProfile, - ...(params.taskMode ? { taskMode: params.taskMode } : {}), + taskMode: params.taskMode, userId: params.userId, featureFlags: params.featureFlags, messages: params.messages, diff --git a/src/lib/server/agent-runtime-config.ts b/src/lib/server/agent-runtime-config.ts index 20c605fd..9dd68136 100644 --- a/src/lib/server/agent-runtime-config.ts +++ b/src/lib/server/agent-runtime-config.ts @@ -18,9 +18,8 @@ export const AGENT_RATE_LIMIT_WINDOW_MS = 60_000 export const AGENT_RATE_LIMIT_MAX_REQUESTS = 60 export const AGENT_MAX_CONCURRENT_REQUESTS_PER_CLIENT = 4 -// Tool-step budgets per runtime profile. +// Tool-step budget for the agent loop. export const AGENT_TOOL_MAX_STEPS = 12 -export const AGENT_RESEARCH_TOOL_MAX_STEPS = 20 function parseBooleanFromEnv( value: string | undefined, diff --git a/src/lib/server/llm/agent-runtime.ts b/src/lib/server/llm/agent-runtime.ts index 444b1719..db61a1eb 100644 --- a/src/lib/server/llm/agent-runtime.ts +++ b/src/lib/server/llm/agent-runtime.ts @@ -11,10 +11,7 @@ import { type PromptTaskMode, resolvePromptProvider, } from "@/lib/server/agent-prompt-steering" -import { - AGENT_RESEARCH_TOOL_MAX_STEPS, - AGENT_TOOL_MAX_STEPS, -} from "@/lib/server/agent-runtime-config" +import { AGENT_TOOL_MAX_STEPS } from "@/lib/server/agent-runtime-config" import { type AgentFeatureFlags, getDefaultAgentFeatureFlags, @@ -30,10 +27,7 @@ import { shouldForceFinalSynthesisStep, shouldNudgeMidBudgetSynthesis, } from "./agent-runtime-synthesis-gating" -import { - getAiSdkGatewayProviderOptionsForMode, - getAiSdkGatewayProviderOptionsForTaskMode, -} from "./ai-sdk-gateway-provider-options" +import { getAiSdkGatewayProviderOptionsForTaskMode } from "./ai-sdk-gateway-provider-options" import { createAiSdkTavilyTools, getAiSdkTavilyToolCallMetadata, @@ -51,13 +45,6 @@ import { createReasoningDisplaySanitizer } from "./initial-reasoning-chunk-sanit const logger = createLogger("agent-runtime") -export type AgentRuntimeProfileId = "chat_default" | "deep_research" - -interface AgentRuntimeProfile { - id: AgentRuntimeProfileId - toolMaxSteps: number -} - export interface StartAgentRuntimeStreamParams { requestId?: string model: ModelType @@ -66,28 +53,13 @@ export interface StartAgentRuntimeStreamParams { userTimeZone?: string messages: AgentInputMessage[] systemInstruction: string - runtimeProfile?: AgentRuntimeProfileId - taskMode?: PromptTaskMode + taskMode: PromptTaskMode temperature?: number signal?: AbortSignal userId?: string featureFlags?: AgentFeatureFlags } -const AGENT_RUNTIME_PROFILES: Record< - AgentRuntimeProfileId, - AgentRuntimeProfile -> = { - chat_default: { - id: "chat_default", - toolMaxSteps: AGENT_TOOL_MAX_STEPS, - }, - deep_research: { - id: "deep_research", - toolMaxSteps: AGENT_RESEARCH_TOOL_MAX_STEPS, - }, -} - const FINAL_SYNTHESIS_STEP_INSTRUCTION = [ "You are on the final synthesis step for this request.", "Do not call any tools on this step.", @@ -113,12 +85,6 @@ const EMPTY_RESPONSE_SYNTHESIS_FALLBACK_SYSTEM = [ "An empty response is not acceptable.", ].join(" ") -function resolveAgentRuntimeProfile( - id: AgentRuntimeProfileId | undefined -): AgentRuntimeProfile { - return AGENT_RUNTIME_PROFILES[id ?? "chat_default"] -} - // When the main stream finishes mid-tool-call (e.g. abort or provider hiccup), // response.messages can include assistant tool-call parts that never received // a matching tool-result message. Sending that history straight to streamText @@ -245,7 +211,6 @@ export async function* startAgentRuntimeStream( return } - const runtimeProfile = resolveAgentRuntimeProfile(params.runtimeProfile) const normalizedTavilyApiKey = params.tavilyApiKey?.trim() const seenToolCalls = new Set() @@ -278,7 +243,6 @@ export async function* startAgentRuntimeStream( logger.info("Starting agent runtime stream.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, toolCount: toolNames.length, toolNames, }) @@ -292,14 +256,10 @@ export async function* startAgentRuntimeStream( ...(params.temperature !== undefined ? { temperature: params.temperature } : {}), - providerOptions: params.taskMode - ? getAiSdkGatewayProviderOptionsForTaskMode({ - provider: resolvePromptProvider(params.model), - taskMode: params.taskMode, - }) - : getAiSdkGatewayProviderOptionsForMode({ - deepResearch: runtimeProfile.id === "deep_research", - }), + providerOptions: getAiSdkGatewayProviderOptionsForTaskMode({ + provider: resolvePromptProvider(params.model), + taskMode: params.taskMode, + }), experimental_telemetry: { isEnabled: true, recordInputs: featureFlags.telemetryRecordIo, @@ -308,7 +268,6 @@ export async function* startAgentRuntimeStream( metadata: { requestId: params.requestId ?? "", modelId: params.model, - runtimeProfile: runtimeProfile.id, toolNames: toolNames.join(","), userHash: userId ? hashUserId(userId) : "", }, @@ -317,7 +276,7 @@ export async function* startAgentRuntimeStream( prepareStep: ({ stepNumber }) => { const forceFinalSynthesis = shouldForceFinalSynthesisStep( stepNumber, - runtimeProfile.toolMaxSteps + AGENT_TOOL_MAX_STEPS ) if (forceFinalSynthesis) { return { @@ -326,9 +285,7 @@ export async function* startAgentRuntimeStream( } } - if ( - shouldNudgeMidBudgetSynthesis(stepNumber, runtimeProfile.toolMaxSteps) - ) { + if (shouldNudgeMidBudgetSynthesis(stepNumber, AGENT_TOOL_MAX_STEPS)) { return { system: `${systemInstruction}\n\n${MID_BUDGET_SYNTHESIS_REMINDER}`, } @@ -336,7 +293,7 @@ export async function* startAgentRuntimeStream( return undefined }, - stopWhen: stepCountIs(runtimeProfile.toolMaxSteps), + stopWhen: stepCountIs(AGENT_TOOL_MAX_STEPS), }) let hasEmittedText = false @@ -346,7 +303,6 @@ export async function* startAgentRuntimeStream( logger.info("Agent runtime model step finished.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, finishReason: part.finishReason, rawFinishReason: part.rawFinishReason, ...getUsageLogFields(part.usage), @@ -358,7 +314,6 @@ export async function* startAgentRuntimeStream( logger.info("Agent runtime stream finished.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, finishReason: part.finishReason, rawFinishReason: part.rawFinishReason, ...getUsageLogFields(part.totalUsage), @@ -370,7 +325,6 @@ export async function* startAgentRuntimeStream( logger.warn("Agent runtime stream aborted.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, reason: part.reason, }) continue @@ -533,7 +487,6 @@ export async function* startAgentRuntimeStream( logger.warn("Main stream emitted no text; running synthesis fallback.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, responseMessageCount: responseMessages.length, sanitizedToolStubCount: responseMessages.length - rawResponseMessages.length, @@ -555,14 +508,10 @@ export async function* startAgentRuntimeStream( ...(params.temperature !== undefined ? { temperature: params.temperature } : {}), - providerOptions: params.taskMode - ? getAiSdkGatewayProviderOptionsForTaskMode({ - provider: resolvePromptProvider(params.model), - taskMode: params.taskMode, - }) - : getAiSdkGatewayProviderOptionsForMode({ - deepResearch: runtimeProfile.id === "deep_research", - }), + providerOptions: getAiSdkGatewayProviderOptionsForTaskMode({ + provider: resolvePromptProvider(params.model), + taskMode: params.taskMode, + }), tools, toolChoice: "none" as const, stopWhen: stepCountIs(1), @@ -580,7 +529,6 @@ export async function* startAgentRuntimeStream( logger.info("Synthesis fallback stream finished.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, finishReason: part.finishReason, fallbackEmittedText, ...getUsageLogFields(part.totalUsage), @@ -592,7 +540,6 @@ export async function* startAgentRuntimeStream( logger.warn("Synthesis fallback stream emitted an error event.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, error: streamError, }) } @@ -602,7 +549,6 @@ export async function* startAgentRuntimeStream( logger.warn("Synthesis fallback completed without emitting text.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, responseMessageCount: responseMessages.length, }) } @@ -610,7 +556,6 @@ export async function* startAgentRuntimeStream( logger.warn("Synthesis fallback failed; yielding nothing.", { requestId: params.requestId, model: params.model, - runtimeProfile: runtimeProfile.id, error: fallbackError, }) } diff --git a/src/lib/server/llm/ai-sdk-gateway-provider-options.ts b/src/lib/server/llm/ai-sdk-gateway-provider-options.ts index c3769af8..d4156b60 100644 --- a/src/lib/server/llm/ai-sdk-gateway-provider-options.ts +++ b/src/lib/server/llm/ai-sdk-gateway-provider-options.ts @@ -16,8 +16,6 @@ function buildGeminiThinkingOptions(level: GeminiThinkingLevel) { } as const } -const GEMINI_HIGH_THINKING_PROVIDER_OPTIONS = buildGeminiThinkingOptions("high") - /** * Map a (provider, taskMode) pair to AI SDK provider options. * @@ -59,17 +57,3 @@ export function getAiSdkGatewayProviderOptionsForTaskMode(params: { void _unhandledTaskMode return {} } - -export function getAiSdkGatewayProviderOptions() { - return getAiSdkGatewayProviderOptionsForMode() -} - -// Legacy shim for callers that only know about the deep-research flag (eval -// harness, etc.). Prefer getAiSdkGatewayProviderOptionsForTaskMode for new code. -export function getAiSdkGatewayProviderOptionsForMode({ - deepResearch = false, -}: { - deepResearch?: boolean -} = {}) { - return deepResearch ? GEMINI_HIGH_THINKING_PROVIDER_OPTIONS : {} -} diff --git a/src/lib/server/thread-payload.ts b/src/lib/server/thread-payload.ts index 1c6c616c..3fc281d5 100644 --- a/src/lib/server/thread-payload.ts +++ b/src/lib/server/thread-payload.ts @@ -1,9 +1,7 @@ import { z } from "zod" import { - AGENT_RUN_MODES, AGENT_RUN_STATUSES, - type AgentRunMode, DEFAULT_THREAD_TITLE, isModelType, type ModelType, @@ -19,7 +17,6 @@ const TOOL_NAME_SCHEMA = z.enum(TOOL_NAMES) const SEARCH_TOOL_NAME_SCHEMA = z.enum(SEARCH_TOOL_NAMES) const TOOL_INVOCATION_STATUS_SCHEMA = z.enum(["running", "success", "error"]) const AGENT_RUN_STATUS_SCHEMA = z.enum(AGENT_RUN_STATUSES) -const AGENT_RUN_MODE_SCHEMA = z.enum(AGENT_RUN_MODES) const MODEL_TYPE_SCHEMA = z.custom( isModelType, "Invalid model type." @@ -166,7 +163,6 @@ const messageMetadataSchema = z parts: z.array(assistantMessagePartSchema).optional(), isStreaming: z.boolean().optional(), selectedModel: MODEL_TYPE_SCHEMA.optional(), - runMode: AGENT_RUN_MODE_SCHEMA.optional(), agentStatus: AGENT_RUN_STATUS_SCHEMA.optional(), interactionId: z.string().trim().min(1).max(200).optional(), lastEventId: z.string().trim().min(1).max(500).optional(), @@ -265,11 +261,6 @@ function sanitizeThreadTitle(value: unknown): string { return title || DEFAULT_THREAD_TITLE } -function sanitizeRunModeValue(value: unknown): AgentRunMode | undefined { - const parsed = AGENT_RUN_MODE_SCHEMA.safeParse(value) - return parsed.success ? parsed.data : undefined -} - function sanitizeOptionalString( value: unknown, maxLength: number @@ -393,7 +384,6 @@ function sanitizeMessageMetadata(value: unknown) { metadata.followUpQuestionsPending ) const selectedModel = sanitizeModelValue(metadata.selectedModel) - const runMode = sanitizeRunModeValue(metadata.runMode) const agentStatus = AGENT_RUN_STATUS_SCHEMA.safeParse(metadata.agentStatus) const interactionId = sanitizeOptionalString(metadata.interactionId, 200) const lastEventId = sanitizeOptionalString(metadata.lastEventId, 500) @@ -441,7 +431,6 @@ function sanitizeMessageMetadata(value: unknown) { ...(parts ? { parts } : {}), ...(isStreaming !== undefined ? { isStreaming } : {}), ...(selectedModel !== undefined ? { selectedModel } : {}), - ...(runMode !== undefined ? { runMode } : {}), ...(agentStatus.success ? { agentStatus: agentStatus.data } : {}), ...(interactionId ? { interactionId } : {}), ...(lastEventId ? { lastEventId } : {}), diff --git a/src/lib/shared/agent/messages.ts b/src/lib/shared/agent/messages.ts index 55890f24..893febc8 100644 --- a/src/lib/shared/agent/messages.ts +++ b/src/lib/shared/agent/messages.ts @@ -33,9 +33,6 @@ export const AGENT_RUN_STATUSES = [ ] as const export type AgentRunStatus = (typeof AGENT_RUN_STATUSES)[number] -export const AGENT_RUN_MODES = ["chat", "research"] as const -export type AgentRunMode = (typeof AGENT_RUN_MODES)[number] - export interface MessageSource { id: string url: string @@ -190,7 +187,6 @@ interface MessageMetadata { parts?: AssistantMessagePart[] isStreaming?: boolean selectedModel?: ModelType - runMode?: AgentRunMode agentStatus?: AgentRunStatus interactionId?: string lastEventId?: string diff --git a/src/lib/shared/llm/models.ts b/src/lib/shared/llm/models.ts index 1fc525bb..08eecb6c 100644 --- a/src/lib/shared/llm/models.ts +++ b/src/lib/shared/llm/models.ts @@ -28,8 +28,6 @@ export const SUPPORTED_MODELS = [ export const ALL_MODELS = [...SUPPORTED_MODELS] as const -export const RESEARCH_MODEL = AvailableModels.ALIBABA_QWEN3_7_MAX - export const MODEL_SELECTOR_MODELS = [ AvailableModels.ALIBABA_QWEN3_7_MAX, AvailableModels.MOONSHOTAI_KIMI_K2_6, diff --git a/src/lib/shared/llm/system-instructions.ts b/src/lib/shared/llm/system-instructions.ts index b9cf2bd7..df3e4ef4 100644 --- a/src/lib/shared/llm/system-instructions.ts +++ b/src/lib/shared/llm/system-instructions.ts @@ -141,42 +141,6 @@ Then follow these rules: `.trim() -export const DEEP_RESEARCH_SYSTEM_INSTRUCTION = ` -# Deep Research Mode - -This request was submitted in Research mode. Treat it as a mandate for the most comprehensive, high-quality, source-grounded answer you can produce within the available tool and context budget. - -## Core Objective -- Optimize for depth, completeness, and durable usefulness over brevity. -- Override the default concise-answer preference: produce a long, detailed, comprehensive response unless the user explicitly asks for a short answer, a strict format, or a hard length cap. -- Pursue the user's underlying research goal, not only the literal first phrasing. Cover definitions, context, mechanisms, tradeoffs, counterpoints, edge cases, and implications when they materially improve the answer. -- Prefer the highest-quality answer that can be defended from evidence. Do not rush to a shallow synthesis when more verification would materially improve correctness. - -## Research Process -- Start by identifying the claims, entities, dates, numbers, comparisons, and assumptions that need verification. -- Use available retrieval tools proactively for current, contested, source-heavy, or unfamiliar topics. Prefer primary sources, official documentation, original filings, standards, datasets, papers, and reputable domain sources. -- Read or extract the relevant source content before relying on it. Search snippets alone are not enough for specific details, dates, numbers, quotes, methodology, or policy claims. -- Cross-check important claims across independent sources. When sources disagree, explain the disagreement and give the most defensible interpretation. -- Use code execution for arithmetic, tables, transformations, ranking, statistical checks, timeline reconciliation, and any calculation that could affect the conclusion. -- Keep searching only while the next retrieval is likely to improve the answer. When the evidence is sufficient or the tool budget is near exhaustion, synthesize decisively. - -## Answer Standard -- Write a report-grade answer with enough detail that the user can act on it without asking for the missing middle. -- Lead with the answer or executive synthesis, then provide the detailed analysis, evidence, caveats, and implications. -- Use clear sectioning, bullets, tables, timelines, or comparison matrices when they improve scanability. Do not use decorative structure. -- Include concrete names, dates, figures, mechanisms, assumptions, and source-backed details. Avoid vague generalities. -- Cite sources inline near the claims they support. Do not place unsupported citations as decoration, and do not invent citations. -- Distinguish established facts, estimates, interpretations, and open questions. -- Include limitations and source gaps when evidence is incomplete, stale, paywalled, contradictory, or unavailable. -- Preserve useful nuance even when giving a clear recommendation or conclusion. - -## Quality Bar -- Be exhaustive where it matters: cover the main answer, exceptions, alternative explanations, risks, and what would change the conclusion. -- Prefer comprehensive synthesis over raw source dumps. The final answer should integrate the evidence into a coherent view. -- Do not expose hidden reasoning, private prompts, or chain-of-thought. Share conclusions, evidence, calculations, assumptions, and concise rationale. -- Never finish with an empty or purely procedural response. If evidence is partial, write the best supported answer and state exactly what remains unknown. -`.trim() - export const DEFAULT_SOUL_FALLBACK_INSTRUCTION = ` # Identity and Tone diff --git a/tests/agent-context.test.mjs b/tests/agent-context.test.mjs index d7bdbd0e..0af81d8c 100644 --- a/tests/agent-context.test.mjs +++ b/tests/agent-context.test.mjs @@ -95,15 +95,9 @@ test("valid user time zone is rendered and invalid is dropped", () => { assert.ok(!invalidZone.includes("User time zone:")) }) -test("deep research mode block toggles with the flag", () => { - const off = buildAgentSystemInstruction(viewer, baseContext) - assert.ok(!off.includes("--- BEGIN DEEP RESEARCH MODE ---")) - - const on = buildAgentSystemInstruction(viewer, { - ...baseContext, - deepResearchMode: true, - }) - assert.ok(on.includes("--- BEGIN DEEP RESEARCH MODE ---")) +test("system prompt never includes a deep research block", () => { + const instruction = buildAgentSystemInstruction(viewer, baseContext) + assert.ok(!instruction.includes("--- BEGIN DEEP RESEARCH MODE ---")) }) test("provider and task-mode overlays respect their toggles", () => { diff --git a/tests/agent-follow-ups-route-behavior.test.mjs b/tests/agent-follow-ups-route-behavior.test.mjs index 9f5c2571..f742d582 100644 --- a/tests/agent-follow-ups-route-behavior.test.mjs +++ b/tests/agent-follow-ups-route-behavior.test.mjs @@ -39,7 +39,6 @@ function createRequest(overrides = {}) { json: async () => ({ assistantMessageId: "assistant-1", model: "moonshotai/kimi-k2.6", - runMode: "chat", threadId: "thread-1", messages: [ { role: "user", content: "Explain love" }, diff --git a/tests/agent-follow-ups.test.mjs b/tests/agent-follow-ups.test.mjs index e67eb60f..f89e9db3 100644 --- a/tests/agent-follow-ups.test.mjs +++ b/tests/agent-follow-ups.test.mjs @@ -128,7 +128,6 @@ test("follow-up generation uses GPT-5.1 Instant and tags the source model", asyn }, ], model: "moonshotai/kimi-k2.6", - runMode: "chat", userId: "user-1", }) @@ -137,7 +136,6 @@ test("follow-up generation uses GPT-5.1 Instant and tags the source model", asyn "feature:follow_up_questions", "generation_model:openai/gpt-5.1-instant", "source_model:moonshotai/kimi-k2.6", - "run_mode:chat", ]) assert.equal(followUps.length, 3) }) diff --git a/tests/agent-helper-behavior.test.mjs b/tests/agent-helper-behavior.test.mjs index 978f59b0..be0c69d1 100644 --- a/tests/agent-helper-behavior.test.mjs +++ b/tests/agent-helper-behavior.test.mjs @@ -159,7 +159,6 @@ test("agent helper validates total size, last-message role, and default model su }) assert(!(defaultModeResult instanceof Response)) - assert.equal(defaultModeResult.parsedRequest.runMode, "chat") assert.equal(defaultModeResult.selectedModel, "moonshotai/kimi-k2.6") const defaultModeWithQwenResult = parseAgentStreamRequest({ @@ -182,51 +181,6 @@ test("agent helper validates total size, last-message role, and default model su assert(!(defaultModeWithQwenResult instanceof Response)) assert.equal(defaultModeWithQwenResult.selectedModel, "alibaba/qwen3.7-max") - const researchModeResult = parseAgentStreamRequest({ - body: { - model: "moonshotai/kimi-k2.6", - runMode: "research", - messages: [ - { - role: "user", - content: "Research this with sources.", - }, - ], - }, - availableModels: [ - { id: "alibaba/qwen3.7-max" }, - { id: "moonshotai/kimi-k2.6" }, - { id: "google/gemini-3.1-pro-preview" }, - ], - requestId: "request-research-mode", - }) - - assert(!(researchModeResult instanceof Response)) - assert.equal(researchModeResult.parsedRequest.runMode, "research") - assert.equal(researchModeResult.selectedModel, "alibaba/qwen3.7-max") - - const unavailableResearchModelResult = parseAgentStreamRequest({ - body: { - runMode: "research", - messages: [ - { - role: "user", - content: "Research this with sources.", - }, - ], - }, - availableModels: [{ id: "moonshotai/kimi-k2.6" }], - requestId: "request-research-unavailable", - }) - - assert(unavailableResearchModelResult instanceof Response) - assert.equal(unavailableResearchModelResult.status, 400) - assert.deepEqual(await unavailableResearchModelResult.json(), { - error: "Research mode requires Qwen 3.7 Max model access.", - errorCode: "AGENT_RESEARCH_MODEL_UNAVAILABLE", - requestId: "request-research-unavailable", - }) - const standaloneResearchModelResult = parseAgentStreamRequest({ body: { model: "google/gemini-3.1-pro-preview", @@ -252,26 +206,26 @@ test("agent helper validates total size, last-message role, and default model su requestId: "request-standalone-research-model", }) - const invalidRunModeResult = parseAgentStreamRequest({ + const unknownFieldResult = parseAgentStreamRequest({ body: { - runMode: "finance", + unknownField: "not allowed", messages: [ { role: "user", - content: "Finance is inferred from this public-markets task.", + content: "The strict schema should reject unknown fields.", }, ], }, availableModels: [{ id: "moonshotai/kimi-k2.6" }], - requestId: "request-invalid-mode", + requestId: "request-unknown-field", }) - assert(invalidRunModeResult instanceof Response) - assert.equal(invalidRunModeResult.status, 400) - assert.deepEqual(await invalidRunModeResult.json(), { + assert(unknownFieldResult instanceof Response) + assert.equal(unknownFieldResult.status, 400) + assert.deepEqual(await unknownFieldResult.json(), { error: "Invalid request payload.", errorCode: "AGENT_INVALID_REQUEST", - requestId: "request-invalid-mode", + requestId: "request-unknown-field", }) const tooManyMessagesResult = parseAgentStreamRequest({ @@ -391,7 +345,6 @@ test("agent helper streams fallback output when the model yields no content", as requestId: "request-1", timeoutMs: 30_000, selectedModel: "moonshotai/kimi-k2.6", - runMode: "chat", aiGatewayApiKey: "ai-gateway-key", tavilyApiKey: "tavily-key", messages: [{ role: "user", content: "Hello" }], @@ -446,7 +399,6 @@ test("agent helper marks tool-backed partial output incomplete when a tool call requestId: "request-unresolved-tool", timeoutMs: 30_000, selectedModel: "moonshotai/kimi-k2.6", - runMode: "chat", aiGatewayApiKey: "ai-gateway-key", tavilyApiKey: "tavily-key", messages: [{ role: "user", content: "Search latest docs" }], @@ -502,7 +454,6 @@ test("agent helper does not add an incomplete fallback when a meaningful answer requestId: "request-tool-error", timeoutMs: 30_000, selectedModel: "moonshotai/kimi-k2.6", - runMode: "chat", aiGatewayApiKey: "ai-gateway-key", tavilyApiKey: "tavily-key", messages: [{ role: "user", content: "Search latest docs" }], @@ -550,7 +501,6 @@ test("agent helper turns upstream body timeouts into visible timeout output", as requestId: "request-body-timeout", timeoutMs: 30_000, selectedModel: "moonshotai/kimi-k2.6", - runMode: "chat", aiGatewayApiKey: "ai-gateway-key", messages: [{ role: "user", content: "Latest AI news" }], systemInstruction: "system", @@ -579,29 +529,6 @@ test("agent helper turns upstream body timeouts into visible timeout output", as assert.equal(recorded.loggerInfos[0]?.details?.outcome, "timeout") }) -test("agent helper forwards the deep research runtime profile", async () => { - const response = createAgentStreamResponse({ - request: createRequest(), - requestId: "request-research", - timeoutMs: 30_000, - selectedModel: "alibaba/qwen3.7-max", - runMode: "research", - aiGatewayApiKey: "ai-gateway-key", - runtimeProfile: "deep_research", - messages: [{ role: "user", content: "Research with sources" }], - systemInstruction: "system", - }) - - await readNdjsonEvents(response) - - assert.equal( - response.headers.get("X-Agent-Effective-Model"), - "alibaba/qwen3.7-max" - ) - assert.equal(recorded.streamParams[0]?.model, "alibaba/qwen3.7-max") - assert.equal(recorded.streamParams[0]?.runtimeProfile, "deep_research") -}) - test("agent helper returns an auth-key fallback when provider auth fails", async () => { setTestMocks({ gatewayResponses: { @@ -620,7 +547,6 @@ test("agent helper returns an auth-key fallback when provider auth fails", async requestId: "request-2", timeoutMs: 30_000, selectedModel: "moonshotai/kimi-k2.6", - runMode: "chat", aiGatewayApiKey: "ai-gateway-key", messages: [{ role: "user", content: "Hello" }], systemInstruction: "system", diff --git a/tests/agent-route-behavior.test.mjs b/tests/agent-route-behavior.test.mjs index 10603f8d..60d034b8 100644 --- a/tests/agent-route-behavior.test.mjs +++ b/tests/agent-route-behavior.test.mjs @@ -359,40 +359,3 @@ test("agent route passes the resolved prompt context into stream creation", asyn recorded.streamCalls[0].onStreamSettled() assert.deepEqual(released, ["released"]) }) - -test("agent route routes research mode through the deep research runtime", async () => { - setTestMocks({ - agentRoute: { - ...getTestMocks().agentRoute, - parseAgentStreamRequest({ body }) { - return { - parsedRequest: { - messages: body.messages, - runMode: "research", - }, - selectedModel: "alibaba/qwen3.7-max", - } - }, - }, - }) - - const response = await POST( - createRequest({ - json: async () => ({ - messages: [ - { - role: "user", - content: "Research the latest AI Act enforcement timeline.", - }, - ], - }), - }) - ) - - assert.equal(response.status, 200) - const promptContext = recorded.buildInstructionCalls[0]?.context - assert.equal(promptContext?.taskMode, "research") - assert.equal(promptContext?.deepResearchMode, true) - assert.equal(recorded.streamCalls[0]?.runtimeProfile, "deep_research") - assert.equal(recorded.streamCalls[0]?.taskMode, "research") -}) diff --git a/tests/agent-route-contract.test.mjs b/tests/agent-route-contract.test.mjs index e7dd14cf..5852b397 100644 --- a/tests/agent-route-contract.test.mjs +++ b/tests/agent-route-contract.test.mjs @@ -18,8 +18,8 @@ test("agent route validates model, threadId, and messages", async () => { assert.match( source, - /const agentStreamRequestSchema = z[\s\S]*model: z\.string\(\)\.trim\(\)\.min\(1\)\.max\(200\)\.optional\(\),[\s\S]*runMode: z\.enum\(AGENT_RUN_MODES\)\.optional\(\),[\s\S]*threadId: z\.string\(\)\.trim\(\)\.min\(1\)\.max\(200\)\.optional\(\),[\s\S]*messages: z\.array\(agentMessageSchema\)\.min\(1\),[\s\S]*\.strict\(\)/, - "Expected /api/agent to validate model, runMode, threadId, and messages." + /const agentStreamRequestSchema = z[\s\S]*model: z\.string\(\)\.trim\(\)\.min\(1\)\.max\(200\)\.optional\(\),[\s\S]*threadId: z\.string\(\)\.trim\(\)\.min\(1\)\.max\(200\)\.optional\(\),[\s\S]*messages: z\.array\(agentMessageSchema\)\.min\(1\),[\s\S]*\.strict\(\)/, + "Expected /api/agent to validate model, threadId, and messages." ) assert.match( @@ -69,10 +69,10 @@ test("agent route streams through the extracted AI Gateway helper path", async ( "Expected the helper to drop the removed finance tooling augmentation options." ) - assert.match( + assert.doesNotMatch( routeSource, - /runtimeProfile: resolveRuntimeProfile\(\s*parsedRequest\.runMode\s*\)/, - "Expected /api/agent to select a runtime profile from the requested run mode." + /runMode|resolveRuntimeProfile/, + "Expected /api/agent to drop run-mode and runtime-profile selection." ) }) @@ -102,7 +102,7 @@ test("agent runtime reserves the final loop step for synthesis", async () => { ) assert.match( runtimeSource, - /prepareStep:\s*\(\{[\s\S]*stepNumber[\s\S]*\}\)[\s\S]*shouldForceFinalSynthesisStep\(\s*stepNumber,\s*runtimeProfile\.toolMaxSteps\s*\)[\s\S]*toolChoice:\s*"none"/, + /prepareStep:\s*\(\{[\s\S]*stepNumber[\s\S]*\}\)[\s\S]*shouldForceFinalSynthesisStep\(\s*stepNumber,\s*AGENT_TOOL_MAX_STEPS\s*\)[\s\S]*toolChoice:\s*"none"/, "Expected the last permitted model step to disable tools." ) assert.match( diff --git a/tests/agent-runtime-synthesis-gating.test.mjs b/tests/agent-runtime-synthesis-gating.test.mjs index be807ead..c9c730ce 100644 --- a/tests/agent-runtime-synthesis-gating.test.mjs +++ b/tests/agent-runtime-synthesis-gating.test.mjs @@ -21,7 +21,7 @@ test("shouldForceFinalSynthesisStep fires only on the last step", () => { }) test("shouldNudgeMidBudgetSynthesis kicks in around one-third budget", () => { - // deep_research budget = 20 → threshold = floor(20/3) = 6 + // budget = 20 → threshold = floor(20/3) = 6 assert.equal(shouldNudgeMidBudgetSynthesis(5, 20), false) assert.equal(shouldNudgeMidBudgetSynthesis(6, 20), true) assert.equal(shouldNudgeMidBudgetSynthesis(12, 20), true) @@ -29,8 +29,8 @@ test("shouldNudgeMidBudgetSynthesis kicks in around one-third budget", () => { assert.equal(shouldNudgeMidBudgetSynthesis(19, 20), false) }) -test("shouldNudgeMidBudgetSynthesis fires for chat_default budget too", () => { - // chat_default budget = 12 → threshold = floor(12/3) = 4 +test("shouldNudgeMidBudgetSynthesis fires for the default 12-step budget too", () => { + // budget = 12 → threshold = floor(12/3) = 4 assert.equal(shouldNudgeMidBudgetSynthesis(3, 12), false) assert.equal(shouldNudgeMidBudgetSynthesis(4, 12), true) assert.equal(shouldNudgeMidBudgetSynthesis(10, 12), true) diff --git a/tests/agent-runtime-task-mode-routing.test.mjs b/tests/agent-runtime-task-mode-routing.test.mjs index 20413ee8..de7c3ecb 100644 --- a/tests/agent-runtime-task-mode-routing.test.mjs +++ b/tests/agent-runtime-task-mode-routing.test.mjs @@ -10,27 +10,9 @@ const providerOptionsUrl = pathToFileURL( path.join(cwd, "src/lib/server/llm/ai-sdk-gateway-provider-options.ts") ).href -const { - getAiSdkGatewayProviderOptionsForMode, - getAiSdkGatewayProviderOptionsForTaskMode, -} = await import(providerOptionsUrl) - -test("getAiSdkGatewayProviderOptionsForMode preserves legacy deep-research behavior", () => { - assert.deepEqual(getAiSdkGatewayProviderOptionsForMode(), {}) - assert.deepEqual(getAiSdkGatewayProviderOptionsForMode({}), {}) - assert.deepEqual( - getAiSdkGatewayProviderOptionsForMode({ deepResearch: false }), - {} - ) - assert.deepEqual( - getAiSdkGatewayProviderOptionsForMode({ deepResearch: true }), - { - google: { - thinkingConfig: { thinkingLevel: "high", includeThoughts: true }, - }, - } - ) -}) +const { getAiSdkGatewayProviderOptionsForTaskMode } = await import( + providerOptionsUrl +) test("Gemini gets high thinking for research, high_stakes, debugging", () => { for (const taskMode of ["research", "high_stakes", "debugging"]) { diff --git a/tests/agent-session-state.test.mjs b/tests/agent-session-state.test.mjs index bfdd889a..52193186 100644 --- a/tests/agent-session-state.test.mjs +++ b/tests/agent-session-state.test.mjs @@ -71,7 +71,6 @@ test("assistant session state builds assistant messages from stream accumulators createdAt: "2026-04-30T12:00:00.000Z", accumulator, model: "moonshotai/kimi-k2.6", - runMode: "research", isStreaming: true, }) @@ -79,7 +78,6 @@ test("assistant session state builds assistant messages from stream accumulators assert.equal(message.role, "assistant") assert.equal(message.content, "Done.") assert.equal(message.metadata?.isStreaming, true) - assert.equal(message.metadata?.runMode, "research") assert.deepEqual(message.metadata?.parts, [{ type: "text", text: "Done." }]) assert.equal(message.metadata?.reasoning, "Checked the source.") assert.equal(message.metadata?.agentStatus, "completed") @@ -96,7 +94,6 @@ test("assistant session state omits empty structured fields and upserts by id", createdAt: "2026-04-30T12:00:00.000Z", accumulator: createAccumulator({ content: "Partial" }), model: "moonshotai/kimi-k2.6", - runMode: "chat", isStreaming: true, }) const finalMessage = createAssistantMessageFromAccumulator({ @@ -104,7 +101,6 @@ test("assistant session state omits empty structured fields and upserts by id", createdAt: "2026-04-30T12:00:00.000Z", accumulator: createAccumulator({ content: "Final" }), model: "moonshotai/kimi-k2.6", - runMode: "chat", isStreaming: false, }) @@ -122,7 +118,6 @@ test("assistant session state attaches follow-up questions without changing cont createdAt: "2026-04-30T12:00:00.000Z", accumulator: createAccumulator({ content: "Final answer." }), model: "moonshotai/kimi-k2.6", - runMode: "chat", isStreaming: false, }) const updatedMessages = attachFollowUpQuestionsToMessage( @@ -154,7 +149,6 @@ test("assistant session state tracks pending follow-up questions", () => { createdAt: "2026-04-30T12:00:00.000Z", accumulator: createAccumulator({ content: "Final answer." }), model: "moonshotai/kimi-k2.6", - runMode: "chat", isStreaming: false, }) diff --git a/tests/agent-system-prompt.test.mjs b/tests/agent-system-prompt.test.mjs index cb387df2..09d81750 100644 --- a/tests/agent-system-prompt.test.mjs +++ b/tests/agent-system-prompt.test.mjs @@ -50,7 +50,6 @@ test("agent system prompt composes trusted blocks in priority order", () => { { now: new Date("2026-05-03T12:34:56.000Z"), userTimeZone: "America/Chicago", - deepResearchMode: true, provider: "alibaba", taskMode: "research", } @@ -62,7 +61,6 @@ test("agent system prompt composes trusted blocks in priority order", () => { "--- BEGIN PROVIDER OVERLAY: ALIBABA ---" ) const taskIndex = prompt.indexOf("--- BEGIN TASK MODE OVERLAY: RESEARCH ---") - const deepResearchIndex = prompt.indexOf("--- BEGIN DEEP RESEARCH MODE ---") const identityIndex = prompt.indexOf( "--- BEGIN IDENTITY AND TONE CONTEXT ---" ) @@ -72,7 +70,6 @@ test("agent system prompt composes trusted blocks in priority order", () => { assert(dateIndex >= 0, "RUNTIME DATE CONTEXT block not found") assert(providerIndex >= 0, "PROVIDER OVERLAY block not found") assert(taskIndex >= 0, "TASK MODE OVERLAY block not found") - assert(deepResearchIndex >= 0, "Deep Research block not found") assert(identityIndex >= 0, "Identity and tone block not found") assert(authIndex >= 0, "AUTH USER CONTEXT block not found") @@ -89,12 +86,8 @@ test("agent system prompt composes trusted blocks in priority order", () => { "Task mode overlay should follow provider overlay" ) assert( - deepResearchIndex > taskIndex, - "Deep Research mode should follow the task mode overlay" - ) - assert( - identityIndex > deepResearchIndex, - "Identity and tone should follow Deep Research mode" + identityIndex > taskIndex, + "Identity and tone should follow the task mode overlay" ) assert( authIndex > identityIndex, @@ -105,34 +98,11 @@ test("agent system prompt composes trusted blocks in priority order", () => { assert.match(prompt, /User time zone: America\/Chicago/) assert.match(prompt, /Use Qwen reasoning mode efficiently/) assert.match(prompt, /This request needs deep research/) - assert.match( - prompt, - /produce a long, detailed, comprehensive response unless the user explicitly asks/ - ) - assert.match(prompt, /report-grade answer/) assert.match(prompt, /Email: user@example.com/) assert(prompt.includes(DEFAULT_SOUL_FALLBACK_INSTRUCTION)) assert.equal(prompt.includes("SOUL.md"), false) }) -test("agent system prompt only adds the Deep Research block for Research mode", () => { - const prompt = buildAgentSystemInstruction( - { - id: "user-1", - name: "Chloei", - email: "user@example.com", - }, - { - now: new Date("2026-05-03T12:34:56.000Z"), - provider: "alibaba", - taskMode: "research", - } - ) - - assert.match(prompt, /--- BEGIN TASK MODE OVERLAY: RESEARCH ---/) - assert.equal(prompt.includes("--- BEGIN DEEP RESEARCH MODE ---"), false) -}) - test("agent system prompt places the identity block after task steering", () => { const prompt = buildAgentSystemInstruction( { diff --git a/tests/assistant-message-contract.test.mjs b/tests/assistant-message-contract.test.mjs index 948a9a7d..5382af67 100644 --- a/tests/assistant-message-contract.test.mjs +++ b/tests/assistant-message-contract.test.mjs @@ -44,7 +44,7 @@ test("assistant regenerate action reuses the prior user message", async () => { ) assert.match( source, - /const handleRegenerate =[\s\S]*onEditMessage\?\.\(\{[\s\S]*messageId: userMessage\.id,[\s\S]*newContent: userMessage\.content,[\s\S]*newModel: regenerateModel,[\s\S]*newRunMode: regenerateRunMode,[\s\S]*\}\)[\s\S]*onRegenerate=\{handleRegenerate\}/, + /const handleRegenerate =[\s\S]*onEditMessage\?\.\(\{[\s\S]*messageId: userMessage\.id,[\s\S]*newContent: userMessage\.content,[\s\S]*newModel: regenerateModel,[\s\S]*\}\)[\s\S]*onRegenerate=\{handleRegenerate\}/, "Expected regenerate to rerun the previous user prompt through the existing edit-regenerate path." ) }) diff --git a/tests/follow-up-questions.test.mjs b/tests/follow-up-questions.test.mjs index 8f0c7f51..ac0ff143 100644 --- a/tests/follow-up-questions.test.mjs +++ b/tests/follow-up-questions.test.mjs @@ -40,7 +40,6 @@ function assistantMessage(overrides = {}) { isStreaming: false, agentStatus: "completed", selectedModel: MODEL, - runMode: "chat", ...metadata, }, } @@ -160,7 +159,7 @@ test("legacy vs generated follow-up detection", () => { test("getFollowUpQuestionRequestTargets selects an eligible assistant turn", () => { const messages = [ { id: "u1", role: "user", content: "Question?", createdAt: "t" }, - assistantMessage({ id: "a1", metadata: { runMode: "research" } }), + assistantMessage({ id: "a1" }), ] const targets = getFollowUpQuestionRequestTargets(messages, new Set()) @@ -168,7 +167,6 @@ test("getFollowUpQuestionRequestTargets selects an eligible assistant turn", () assert.equal(targets.length, 1) assert.equal(targets[0].assistantMessageId, "a1") assert.equal(targets[0].model, MODEL) - assert.equal(targets[0].runMode, "research") assert.deepEqual( targets[0].messages.map((message) => message.id), ["u1", "a1"] diff --git a/tests/gateway-search-tools.test.mjs b/tests/gateway-search-tools.test.mjs index 4d86a6be..3c94a3ca 100644 --- a/tests/gateway-search-tools.test.mjs +++ b/tests/gateway-search-tools.test.mjs @@ -7,9 +7,6 @@ import { fileURLToPath, pathToFileURL } from "node:url" import "./register-ts-path-hooks.mjs" const cwd = fileURLToPath(new URL("..", import.meta.url)) -const gatewayProviderOptionsUrl = pathToFileURL( - path.join(cwd, "src/lib/server/llm/ai-sdk-gateway-provider-options.ts") -).href const tavilyToolsPath = path.join( cwd, "src/lib/server/llm/ai-sdk-tavily-tools.ts" @@ -18,10 +15,6 @@ const persistentSelectedModelUrl = pathToFileURL( path.join(cwd, "src/hooks/agent/persistent-selected-model-utils.ts") ).href -const { - getAiSdkGatewayProviderOptions, - getAiSdkGatewayProviderOptionsForMode, -} = await import(gatewayProviderOptionsUrl) const { parseStoredSelectedModel, resolvePersistedSelectedModel, @@ -43,25 +36,6 @@ test("tavily search tool results derive source links", async () => { ) }) -test("legacy deep-research provider options preserve Gemini high thinking", () => { - assert.deepEqual(getAiSdkGatewayProviderOptions(), {}) - assert.deepEqual( - getAiSdkGatewayProviderOptionsForMode({ deepResearch: false }), - {} - ) - assert.deepEqual( - getAiSdkGatewayProviderOptionsForMode({ deepResearch: true }), - { - google: { - thinkingConfig: { - thinkingLevel: "high", - includeThoughts: true, - }, - }, - } - ) -}) - test("inline citation instructions avoid separate sources sections", async () => { const source = await import("node:fs/promises").then((fs) => fs.readFile( diff --git a/tests/home-agent-utils.test.mjs b/tests/home-agent-utils.test.mjs index 8fbbbf28..d7da8d8d 100644 --- a/tests/home-agent-utils.test.mjs +++ b/tests/home-agent-utils.test.mjs @@ -72,14 +72,12 @@ test("agent request messages trim oversized existing message content", () => { assert.match(requestMessages[0].content, /truncated/) }) -test("appended user messages preserve the requested run mode", () => { +test("appended user messages record the selected model", () => { const messages = appendUserMessage( [], "Research Apple supply chain risk.", - "moonshotai/kimi-k2.6", - "research" + "moonshotai/kimi-k2.6" ) assert.equal(messages[0]?.metadata?.selectedModel, "moonshotai/kimi-k2.6") - assert.equal(messages[0]?.metadata?.runMode, "research") }) diff --git a/tests/model-registry.test.mjs b/tests/model-registry.test.mjs index 714a9a98..c0309d20 100644 --- a/tests/model-registry.test.mjs +++ b/tests/model-registry.test.mjs @@ -46,12 +46,6 @@ test("shared model registry includes the curated gateway models", async () => { "Expected SUPPORTED_MODELS to list Qwen 3.7 Max, Gemini 3.1 Pro Preview, Kimi K2.6, and MiMo V2.5 Pro." ) - assert.match( - source, - /RESEARCH_MODEL = AvailableModels\.ALIBABA_QWEN3_7_MAX/, - "Expected Research mode to use Qwen 3.7 Max." - ) - assert.match( source.replace(/\s+/g, " "), /MODEL_SELECTOR_MODELS = \[ AvailableModels\.ALIBABA_QWEN3_7_MAX, AvailableModels\.MOONSHOTAI_KIMI_K2_6, AvailableModels\.XIAOMI_MIMO_V2_5_PRO, \] as const/, diff --git a/tests/persistent-run-mode.test.mjs b/tests/persistent-run-mode.test.mjs deleted file mode 100644 index a3168012..00000000 --- a/tests/persistent-run-mode.test.mjs +++ /dev/null @@ -1,88 +0,0 @@ -import assert from "node:assert/strict" -import { readFile } from "node:fs/promises" -import path from "node:path" -import test from "node:test" -import { fileURLToPath, pathToFileURL } from "node:url" - -import "./register-ts-path-hooks.mjs" - -const cwd = fileURLToPath(new URL("..", import.meta.url)) -const persistentRunModeUrl = pathToFileURL( - path.join(cwd, "src/hooks/agent/persistent-run-mode-utils.ts") -).href -const persistentRunModeHookPath = path.join( - cwd, - "src/hooks/agent/use-persistent-run-mode.tsx" -) - -const { parseStoredRunMode, resolvePersistedRunMode, serializeStoredRunMode } = - await import(persistentRunModeUrl) - -test("persistent run mode parses only supported modes", () => { - assert.equal(parseStoredRunMode("research"), "research") - assert.equal(parseStoredRunMode("chat"), "chat") - assert.equal(parseStoredRunMode("browse"), null) - assert.equal(parseStoredRunMode(null), null) -}) - -test("persistent run mode parses current stored payload shape", () => { - assert.equal( - parseStoredRunMode(JSON.stringify(serializeStoredRunMode("research"))), - "research" - ) - assert.equal( - parseStoredRunMode( - JSON.stringify({ - runMode: "research", - source: "system", - version: 1, - }) - ), - null - ) -}) - -test("persistent run mode prefers stored mode over current and fallback", () => { - assert.equal( - resolvePersistedRunMode({ - storedRunMode: "research", - currentRunMode: "chat", - fallbackRunMode: "chat", - }), - "research" - ) - assert.equal( - resolvePersistedRunMode({ - storedRunMode: null, - currentRunMode: "research", - fallbackRunMode: "chat", - }), - "research" - ) - assert.equal( - resolvePersistedRunMode({ - storedRunMode: null, - }), - "chat" - ) -}) - -test("persistent run mode hook guards restricted storage access", async () => { - const source = await readFile(persistentRunModeHookPath, "utf8") - - assert.match( - source, - /try \{\s+storedValue = window\.localStorage\.getItem\(RUN_MODE_STORAGE_KEY\)/, - "Expected persisted run mode reads to guard localStorage access." - ) - assert.match( - source, - /try \{\s+window\.localStorage\.setItem\(\s+RUN_MODE_STORAGE_KEY,/, - "Expected persisted run mode writes to guard localStorage access." - ) - assert.match( - source, - /try \{\s+window\.dispatchEvent\(new CustomEvent\(RUN_MODE_UPDATED_EVENT\)\)/, - "Expected persisted run mode updates to guard event dispatch." - ) -}) diff --git a/tests/prompt-form-contract.test.mjs b/tests/prompt-form-contract.test.mjs index 53447884..758f6864 100644 --- a/tests/prompt-form-contract.test.mjs +++ b/tests/prompt-form-contract.test.mjs @@ -10,53 +10,17 @@ const promptFormPath = path.join( "src/components/agent/prompt-form/prompt-form.tsx" ) -test("prompt form research shortcut does not close the tools popover", async () => { +test("prompt form drops the tools popover and research mode controls", async () => { const source = await readFile(promptFormPath, "utf8") - const shortcutSource = source.match( - /const handleResearchShortcut = \(event: KeyboardEvent\) => \{[\s\S]*?window\.addEventListener\("keydown", handleResearchShortcut\)/ - )?.[0] - assert.ok( - shortcutSource, - "Expected PromptForm to define a research shortcut." - ) - assert.match( - shortcutSource, - /setRunMode\(\(currentRunMode\) =>\s+currentRunMode === "research" \? "chat" : "research"\s+\)/, - "Expected shortcut to toggle run mode directly." - ) - assert.doesNotMatch( - shortcutSource, - /handleSetRunMode|setIsToolsOpen|shouldPreventToolsCloseAutoFocusRef/, - "Expected shortcut not to reuse popover-closing menu behavior." - ) -}) - -test("prompt form preserves Research mode after submit", async () => { - const source = await readFile(promptFormPath, "utf8") - const submitSource = source.match( - /const submitPrompt = useCallback\([\s\S]*?const handleSubmit = useCallback/ - )?.[0] - - assert.ok(submitSource, "Expected PromptForm to define submitPrompt.") - assert.match( - source, - /usePersistentRunMode\(\)/, - "Expected PromptForm to persist run mode across remounts." - ) assert.doesNotMatch( source, - /useState\("chat"\)/, - "Expected PromptForm not to reset run mode to chat on remount." + /runMode|usePersistentRunMode|Research|Telescope|Popover|setIsToolsOpen/, + "Expected PromptForm to drop the Tools popover and Research mode controls." ) assert.match( - submitSource, - /const activeRunMode = runMode[\s\S]*onSubmit\?\.\([\s\S]*activeRunMode/, - "Expected submitPrompt to submit the active run mode." - ) - assert.doesNotMatch( - submitSource, - /setRunMode\("chat"\)/, - "Expected submitPrompt not to reset Research mode back to chat." + source, + / { +test("thread payload drops legacy run-mode metadata from stored threads", () => { const parsed = parseThreadPayload({ id: "thread-1", model: "google/gemini-3.1-pro-preview", @@ -139,21 +139,14 @@ test("thread payload preserves valid run modes and drops invalid run modes", () runMode: "research", }, }, - { - id: "message-2", - role: "assistant", - content: "Done.", - llmModel: "google/gemini-3.1-pro-preview", - createdAt: "2026-04-26T00:00:01.000Z", - metadata: { - runMode: "invalid", - }, - }, ], createdAt: "2026-04-26T00:00:00.000Z", updatedAt: "2026-04-26T00:00:01.000Z", }) - assert.equal(parsed.messages[0]?.metadata?.runMode, "research") - assert.equal(parsed.messages[1]?.metadata?.runMode, undefined) + assert.equal( + parsed.messages[0]?.metadata?.selectedModel, + "moonshotai/kimi-k2.6" + ) + assert.equal(parsed.messages[0]?.metadata?.runMode, undefined) })