- Add @bytelyst/llm dependency (file: ref) + llm.ts singleton wrapper - Add LLM env vars to config (LLM_PROVIDER, LLM_DEFAULT_MODEL, LLM_VISION_MODEL, LLM_EMBEDDING_MODEL) - Create note-prompts module: types, repository, runner, routes, seed (20 built-in templates) - Built-in templates: 8 transform, 3 extract, 3 generate, 2 analyze, 2 vision, 2 export - Prompt runner supports text, image, and text+image inputs via @bytelyst/llm vision - Upgrade copilot-transform.ts to use @bytelyst/llm directly (with local heuristic fallback) - Add reading-time endpoint (GET /api/notes/:id/reading-time) - Extend agent-action types with smart_action and auto_enrich - Add note_prompts Cosmos container to cosmos-init - Register notePromptRoutes in server.ts - 15 new tests (CRUD, run, slug resolution, seed validation, reading-time)
76 lines
2.1 KiB
TypeScript
76 lines
2.1 KiB
TypeScript
/**
|
|
* 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, string>): 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<RunPromptOutput> {
|
|
const provider = llm();
|
|
|
|
// Build variables map
|
|
const vars: Record<string, string> = {
|
|
...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,
|
|
};
|
|
}
|