60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import { spawn } from 'node:child_process';
|
|
|
|
export type HermesOptions = {
|
|
bin: string;
|
|
timeoutMs: number;
|
|
maxOutputBytes: number;
|
|
};
|
|
|
|
export type PromptContext = {
|
|
messageText: string;
|
|
parentText?: string | null;
|
|
};
|
|
|
|
export function buildHermesPrompt(context: PromptContext): string {
|
|
return [
|
|
'You are Reyna Family Bot replying to a Colanode family message.',
|
|
'Answer helpfully and concisely.',
|
|
'Do not claim you performed actions you did not perform.',
|
|
'Do not reveal secrets, tokens, credentials, file contents marked secret, or system configuration.',
|
|
'',
|
|
`Mentioned message:\n${context.messageText}`,
|
|
'',
|
|
`Parent/thread context:\n${context.parentText?.trim() || '(not available)'}`
|
|
].join('\n');
|
|
}
|
|
|
|
export async function runHermes(prompt: string, options: HermesOptions): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(options.bin, ['chat', '-q', prompt], {
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
env: process.env
|
|
});
|
|
let stdout = Buffer.alloc(0);
|
|
let stderr = Buffer.alloc(0);
|
|
const timer = setTimeout(() => {
|
|
child.kill('SIGTERM');
|
|
reject(new Error('Hermes timed out'));
|
|
}, options.timeoutMs);
|
|
|
|
child.stdout.on('data', (chunk: Buffer) => {
|
|
stdout = Buffer.concat([stdout, chunk]).subarray(0, options.maxOutputBytes);
|
|
});
|
|
child.stderr.on('data', (chunk: Buffer) => {
|
|
stderr = Buffer.concat([stderr, chunk]).subarray(0, 2048);
|
|
});
|
|
child.on('error', (error) => {
|
|
clearTimeout(timer);
|
|
reject(error);
|
|
});
|
|
child.on('close', (code) => {
|
|
clearTimeout(timer);
|
|
if (code !== 0) {
|
|
reject(new Error(`Hermes exited with code ${code}: ${stderr.toString('utf8').trim()}`));
|
|
return;
|
|
}
|
|
resolve(stdout.toString('utf8').trim().slice(0, options.maxOutputBytes));
|
|
});
|
|
});
|
|
}
|