import * as z from 'zod'; import { httpClient } from '~~/server/lib/triplit'; import { renamePrompt } from '~~/prompts'; import { schema } from '~~/triplit/schema'; import { type Entity } from '@triplit/client'; import { generateText } from 'ai'; import { GatewayFetchError, getProviderDetails, type ModelGateway } from '~~/server/utils/ai-provider'; import { addPendingRename } from '~~/server/utils/renames'; import { assert } from '~~/utils/assert'; export default defineEventHandler(async (event) => { await protectRoute(event); const userId = event.context.user!.id; const topicId = getRouterParam(event, 'topicId')!; const result = await readValidatedBody(event, (body) => z .object({ modelId: z.string(), prompt: z.string(), providerApiKey: z.string().optional(), }) .safeParse(body), ); if (!result.success) { throw createError({ statusCode: 400, message: result.error.issues[0]!.message, data: { code: 'INVALID_BODY', ok: false, } }); } const { modelId, prompt, providerApiKey } = result.data; const model = await httpClient.fetchOne(httpClient.query('models').Where('id', '=', modelId).Include('provider')); if (model === null || model.providerId !== model.providerId || model.userId !== userId || model.provider === null) { throw createError({ statusCode: 400, statusMessage: 'Invalid model', data: { code: 'INVALID_MODEL', ok: false, } }); } const providerDetails = await getProviderDetails(model.provider, providerApiKey, model); if (!providerDetails.ok) { switch (providerDetails.error) { case GatewayFetchError.NoProviderApiKey: { setResponseStatus(event, 400, "No provider API key"); throw createError({ statusCode: 400, statusMessage: `${model.provider.type} provider requires an API key`, data: { code: 'NO_PROVIDER_API_KEY', ok: false, } }); } case GatewayFetchError.NoProviderBaseUrl: { throw createError({ statusCode: 400, statusMessage: 'Invalid provider URL', data: { code: 'BAD_PROVIDER_URL', ok: false, } }); } } } const { gateway } = providerDetails.data; if (gateway === null) { throw createError({ statusCode: 500, statusMessage: 'Invalid gateway', data: { code: 'INVALID_GATEWAY', ok: false, } }); } const [renameId, pendingRename] = addPendingRename(topicId); event.waitUntil(autoRename(topicId, renameId, pendingRename.abortController, { gateway: gateway.gateway, model }, gateway.textTransformer, prompt)); return { renameId, ok: true, }; }); const autoRename = async ( topicId: string, renameId: string, abortController: AbortController, model: { gateway: ModelGateway, model: Entity, }, textTransformer: ((text: string) => string) | ((text: string) => string)[] | undefined, prompt: string, ) => { try { const response = await generateText({ model: model.gateway(model.model.externalId), system: renamePrompt, prompt, timeout: 90 * 1000, abortSignal: abortController.signal, }) let text = response.text; if (textTransformer !== undefined) { if (Array.isArray(textTransformer)) { for (const transformer of textTransformer) { text = transformer(text); } } else { text = textTransformer(text); } } await httpClient.update('topics', topicId, { name: text, }); } catch (error) { console.error('Failed to auto-rename:', error); } finally { completeRename(renameId); await httpClient.update('topics', topicId, { renaming: false }); } }