@nomyx/assistant
Version:
A powerful assistant library and cli for your AI projects. works with Vertex AI (Claude and Gemini)
185 lines (184 loc) • 7.63 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PromptExecution = void 0;
const events_1 = require("events");
const uuid_1 = require("uuid");
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const ajv_1 = __importDefault(require("ajv"));
const expr_eval_1 = require("expr-eval");
class PromptExecution extends events_1.EventEmitter {
constructor(registry, initialPrompt, provider, availablePrompts, options = {}, logger) {
super();
this.registry = registry;
this.initialPrompt = initialPrompt;
this.provider = provider;
this.availablePrompts = availablePrompts;
this.options = options;
this.logger = logger;
this.state = {
id: (0, uuid_1.v4)(),
variables: {},
history: [],
currentPrompt: initialPrompt.definition.name
};
this.ajv = new ajv_1.default();
this.toolManager = {
registerTool: (tool) => { },
getTool: (name) => undefined,
getAllTools: () => []
};
}
async execute(initialReplacements = {}) {
this.state.variables = { ...this.state.variables, ...initialReplacements };
while (true) {
try {
await this.loadState();
const currentPrompt = this.registry.get(this.state.currentPrompt);
const { shouldContinue, nextPrompt } = await this.executePromptWithRetry(currentPrompt);
if (!shouldContinue)
break;
if (nextPrompt)
this.state.currentPrompt = nextPrompt.definition.name;
await this.saveState();
}
catch (error) {
this.emit('error', error);
break;
}
}
this.emit('executionComplete', this.state);
}
registerTool(tool) {
this.toolManager.registerTool(tool);
}
async executePromptWithRetry(prompt) {
let retries = 0;
while (retries <= (this.options.maxRetries || 3)) {
try {
return await this.executePromptWithTimeout(prompt);
}
catch (error) {
if (error instanceof Error && error.name === 'TimeoutError') {
this.emit('promptTimeout', { prompt: prompt.definition.name, retries });
}
else {
this.emit('promptError', { prompt: prompt.definition.name, error, retries });
}
retries++;
if (retries <= (this.options.maxRetries || 3)) {
await new Promise(resolve => setTimeout(resolve, this.options.retryDelayMs || 1000));
}
else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}
async executePromptWithTimeout(prompt) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Prompt execution timed out'));
}, this.options.timeoutMs || 30000);
this.executePrompt(prompt)
.then(resolve)
.catch(reject)
.finally(() => clearTimeout(timeout));
});
}
async executePrompt(prompt) {
let renderedPrompt = prompt.render(this.state.variables, this.availablePrompts);
this.emit('promptStart', { prompt: prompt.definition.name, renderedPrompt });
const messages = [{ role: 'user', content: renderedPrompt }];
const chatOptions = {
...this.options,
...prompt.getConfig()
};
const response = await this.provider.chat(messages, chatOptions);
let parsedResponse;
try {
parsedResponse = JSON.parse(response.content);
}
catch (error) {
throw new Error(`Invalid JSON response for prompt: ${prompt.definition.name}`);
}
if (!this.validateResponse(parsedResponse, prompt.definition.responseFormat)) {
throw new Error(`Invalid response format for prompt: ${prompt.definition.name}`);
}
this.state.history.push({ prompt: renderedPrompt, response: parsedResponse });
this.emit('promptComplete', { prompt: prompt.definition.name, response: parsedResponse });
// Process tool calls
if (response.toolCalls) {
for (const toolCall of response.toolCalls) {
await this.processToolCall(toolCall);
}
}
// Update state variables
this.state.variables = { ...this.state.variables, ...parsedResponse.response };
// Handle next steps
if (parsedResponse.next) {
if (parsedResponse.next.if) {
const nextPromptName = this.evaluateCondition(parsedResponse.next.if) ? parsedResponse.next.then : parsedResponse.next.else;
return { shouldContinue: true, nextPrompt: this.registry.get(nextPromptName) };
}
else if (parsedResponse.next.each) {
for (const item of parsedResponse.next.each) {
const nextPromptName = this.evaluateCondition(item.if) ? item.then : item.else;
await this.executePrompt(this.registry.get(nextPromptName));
}
return { shouldContinue: true };
}
}
return { shouldContinue: false };
}
async processToolCall(toolCall) {
this.emit('toolCallStart', toolCall);
try {
const tool = this.toolManager.getTool(toolCall.name);
if (!tool) {
throw new Error(`Tool not found: ${toolCall.name}`);
}
const result = await tool.execute(toolCall.arguments, { user: { id: this.state.id, name: 'User' }, session: { id: this.state.id, timestamp: new Date().toISOString() } });
this.state.variables[toolCall.name] = result;
this.emit('toolCallComplete', { toolCall, result });
}
catch (error) {
this.emit('toolCallError', { toolCall, error });
throw error;
}
}
validateResponse(response, schema) {
const validate = this.ajv.compile(JSON.parse(schema));
return validate(response);
}
evaluateCondition(condition) {
const parser = new expr_eval_1.Parser();
const expr = parser.parse(condition);
return expr.evaluate(this.state.variables);
}
async saveState() {
if (this.options.statePersistenceDir) {
const filePath = path_1.default.join(this.options.statePersistenceDir, `${this.state.id}.json`);
await promises_1.default.writeFile(filePath, JSON.stringify(this.state), 'utf8');
}
}
async loadState() {
if (this.options.statePersistenceDir) {
const filePath = path_1.default.join(this.options.statePersistenceDir, `${this.state.id}.json`);
try {
const data = await promises_1.default.readFile(filePath, 'utf8');
this.state = JSON.parse(data);
}
catch (error) {
if (error instanceof Error && error.code !== 'ENOENT') {
throw error;
}
}
}
}
}
exports.PromptExecution = PromptExecution;