/** * Prompt runner — executes a PromptTemplate against note content via @bytelyst/llm. */ import { llm } from '../../lib/llm.js'; import { config } from '../../lib/config.js'; import { buildVisionMessage, hasVisionContent, type ChatMessage, } from '@bytelyst/llm'; import type { PromptTemplateDoc, RunPromptInput, RunPromptOutput } from './types.js'; /** * Interpolate {{variable}} placeholders in a template string. */ function interpolate(template: string, vars: Record): string { return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`); } /** * Run a prompt template against provided input. * Handles text-only, image-only, and text+image inputs. */ export async function executePrompt( template: PromptTemplateDoc, input: RunPromptInput, noteBody: string, ): Promise { const provider = llm(); // Build variables map const vars: Record = { ...input.variables, noteBody, noteId: input.noteId, workspaceId: input.workspaceId, }; if (input.inputText) vars.inputText = input.inputText; const userPrompt = interpolate(template.userPromptTemplate, vars); // Build messages const messages: ChatMessage[] = [ { role: 'system', content: template.systemPrompt }, ]; if (input.imageUrl && (template.inputType === 'image' || template.inputType === 'text+image')) { messages.push(buildVisionMessage(userPrompt, input.imageUrl)); } else { messages.push({ role: 'user', content: userPrompt }); } // Select model: vision model for image content, custom model, or default const req = { messages }; let model = template.model || config.LLM_DEFAULT_MODEL; if (hasVisionContent(req)) { model = config.LLM_VISION_MODEL; } const result = await provider.chatCompletion({ messages, model, temperature: template.temperature ?? 0.7, maxTokens: template.maxTokens ?? 4096, }); return { content: result.content, model: result.model, usage: result.usage, templateSlug: template.slug, outputType: template.outputType, }; }