pit-manager
Version:
Centralized prompt management system for Human Behavior AI agents
180 lines (179 loc) • 6.17 kB
JavaScript
;
/**
* Simplified prompt loading and rendering for TypeScript
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.prompts = prompts;
exports.getCurrentPromptFile = getCurrentPromptFile;
exports.getParentExecutionId = getParentExecutionId;
exports.clearPromptContext = clearPromptContext;
const fs = require("fs");
const path = require("path");
const yaml = require("js-yaml");
// Global state for tracking current prompt file
let currentPromptFile;
// Global state for tracking parent execution ID from ModelResponse
let parentExecutionId;
/**
* Load and render a prompt template with positional variables.
*
* Variables are matched to {{variable}} placeholders in order of appearance.
*
* @param templateName - Name of the template file (with or without .md extension)
* @param variables - List of values to substitute in order of appearance
* @returns Rendered prompt string
*
* @example
* // If template has {{role}} and {{task}}
* const prompt = prompts("assistant.md", ["expert", "analyze data"]);
*/
function prompts(templateName, variables) {
// Find .pit directory
const pitDir = findPitDirectory();
if (!pitDir) {
throw new Error("No .pit directory found. Run 'pit init' first.");
}
// Ensure template name has .md extension
if (!templateName.endsWith('.md')) {
templateName += '.md';
}
// Load template file
const templatePath = path.join(pitDir, 'prompts', templateName);
if (!fs.existsSync(templatePath)) {
throw new Error(`Template not found: ${templatePath}`);
}
const content = fs.readFileSync(templatePath, 'utf-8');
// Parse frontmatter and template
const { templateContent } = parseTemplate(content);
// Extract variables in order of appearance
const variableNames = extractVariables(templateContent);
// Create variable mapping
if (variables.length !== variableNames.length) {
throw new Error(`Expected ${variableNames.length} variables ${JSON.stringify(variableNames)}, ` +
`but got ${variables.length} values`);
}
// Process variables and detect ModelResponse objects
const processedVariables = [];
parentExecutionId = undefined; // Reset parent ID
for (const variable of variables) {
// Check if this is a ModelResponse object
if (variable && typeof variable === 'object' && '_isPitResponse' in variable && variable._isPitResponse) {
// Extract content from ModelResponse
processedVariables.push(variable.content);
// Store the execution ID for chaining (use the first one found)
if (!parentExecutionId) {
parentExecutionId = variable.executionId;
}
}
else {
processedVariables.push(variable);
}
}
const variableMap = {};
variableNames.forEach((name, index) => {
variableMap[name] = processedVariables[index];
});
// Render template
const rendered = renderTemplate(templateContent, variableMap);
// Track current prompt file for execution tracking
currentPromptFile = templateName;
return rendered;
}
/**
* Find the .pit directory by searching up from current directory
*/
function findPitDirectory() {
let current = process.cwd();
while (current !== path.dirname(current)) {
const pitDir = path.join(current, '.pit');
if (fs.existsSync(pitDir) && fs.statSync(pitDir).isDirectory()) {
return pitDir;
}
current = path.dirname(current);
}
// Check current directory one more time
const pitDir = path.join(process.cwd(), '.pit');
if (fs.existsSync(pitDir) && fs.statSync(pitDir).isDirectory()) {
return pitDir;
}
return null;
}
/**
* Parse template content into frontmatter and body
*/
function parseTemplate(content) {
if (content.startsWith('---')) {
// Find the second --- to properly extract frontmatter
const firstDelimiter = 0;
const secondDelimiter = content.indexOf('---', firstDelimiter + 3);
if (secondDelimiter !== -1) {
const frontmatterStr = content.substring(firstDelimiter + 3, secondDelimiter).trim();
const templateContent = content.substring(secondDelimiter + 3).trim();
// Parse YAML frontmatter
let frontmatter = {};
try {
frontmatter = yaml.load(frontmatterStr) || {};
}
catch (e) {
// Ignore YAML errors
}
return { frontmatter, templateContent };
}
}
return { frontmatter: {}, templateContent: content };
}
/**
* Extract variable names from template in order of appearance
*/
function extractVariables(template) {
// Find all {{variable}} patterns
const pattern = /\{\{(\w+)\}\}/g;
const matches = [...template.matchAll(pattern)];
// Remove duplicates while preserving order
const seen = new Set();
const uniqueVars = [];
for (const match of matches) {
const varName = match[1];
if (!seen.has(varName)) {
seen.add(varName);
uniqueVars.push(varName);
}
}
return uniqueVars;
}
/**
* Render template by replacing variables
*/
function renderTemplate(template, variables) {
let rendered = template;
for (const [varName, value] of Object.entries(variables)) {
const pattern = new RegExp(`\\{\\{${escapeRegExp(varName)}\\}\\}`, 'g');
rendered = rendered.replace(pattern, String(value));
}
return rendered;
}
/**
* Escape special regex characters
*/
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Get the current prompt file being used
*/
function getCurrentPromptFile() {
return currentPromptFile;
}
/**
* Get the parent execution ID if a ModelResponse was passed to prompts()
*/
function getParentExecutionId() {
return parentExecutionId;
}
/**
* Clear the prompt context (used after model execution)
*/
function clearPromptContext() {
currentPromptFile = undefined;
parentExecutionId = undefined;
}