import { preprocessMessages } from "../endpoints/preprocessMessages"; import { generateTitleForConversation } from "./title"; import { injectArtifactsPrompt } from "./artifacts"; import { type MessageUpdate, MessageUpdateType, MessageUpdateStatus, } from "$lib/types/MessageUpdate"; import { generate } from "./generate"; import { runMcpFlow } from "./mcp/runMcpFlow"; import { mergeAsyncGenerators } from "$lib/utils/mergeAsyncGenerators"; import type { TextGenerationContext } from "./types"; async function* keepAlive(done: AbortSignal): AsyncGenerator { while (!done.aborted) { yield { type: MessageUpdateType.Status, status: MessageUpdateStatus.KeepAlive, }; await new Promise((resolve) => setTimeout(resolve, 100)); } } export async function* textGeneration(ctx: TextGenerationContext) { const done = new AbortController(); const titleGen = generateTitleForConversation(ctx.conv, ctx.locals); const textGen = textGenerationWithoutTitle(ctx, done); const keepAliveGen = keepAlive(done.signal); // keep alive until textGen is done yield* mergeAsyncGenerators([titleGen, textGen, keepAliveGen]); } async function* textGenerationWithoutTitle( ctx: TextGenerationContext, done: AbortController ): AsyncGenerator { yield { type: MessageUpdateType.Status, status: MessageUpdateStatus.Started, }; const { conv, messages } = ctx; const convId = conv._id; // Artifacts are opt-in per model (supportsArtifacts in the MODELS overrides), // with a per-model user override from the model settings page const preprompt = (ctx.artifactsOverride ?? ctx.model.supportsArtifacts) ? injectArtifactsPrompt(conv.preprompt) : conv.preprompt; const processedMessages = await preprocessMessages(messages, convId); // Try MCP tool flow first; fall back to default generation if not selected/available try { const mcpGen = runMcpFlow({ model: ctx.model, conv, messages: processedMessages, assistant: ctx.assistant, forceMultimodal: ctx.forceMultimodal, forceTools: ctx.forceTools, provider: ctx.provider, reasoningEffort: ctx.reasoningEffort, locals: ctx.locals, preprompt, abortSignal: ctx.abortController.signal, abortController: ctx.abortController, promptedAt: ctx.promptedAt, }); let step = await mcpGen.next(); while (!step.done) { yield step.value; step = await mcpGen.next(); } const mcpResult = step.value; if (mcpResult === "not_applicable") { // fallback to normal text generation yield* generate({ ...ctx, messages: processedMessages }, preprompt); } // If mcpResult is "completed" or "aborted", don't fall back } catch (err) { // Don't fall back on abort errors - user intentionally stopped const isAbort = ctx.abortController.signal.aborted || (err instanceof Error && (err.name === "AbortError" || err.name === "APIUserAbortError" || err.message.includes("Request was aborted"))); if (!isAbort) { // On non-abort MCP error, fall back to normal generation yield* generate({ ...ctx, messages: processedMessages }, preprompt); } } done.abort(); }