|
|
|
@@ -265,7 +265,7 @@ class LLM {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private setupAgent(agents: Agent[] = [], allAgents: Agent[], history: LLMMessage[], aborts: (() => void)[], depth = 0, delegateState: {resp: string | null}): AiTool[] {
|
|
|
|
private setupAgent(agents: Agent[] = [], allAgents: Agent[], history: LLMMessage[], aborts: ((keep?: boolean) => void)[], depth = 0, delegateState: {resp: string | null}): AiTool[] {
|
|
|
|
return agents.map(a => {
|
|
|
|
return agents.map(a => {
|
|
|
|
const toolName = `${a.delegate ? '' : 'sub'}agent_${snakeCase(a.name)}`;
|
|
|
|
const toolName = `${a.delegate ? '' : 'sub'}agent_${snakeCase(a.name)}`;
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
@@ -397,11 +397,13 @@ ${a.system}`,
|
|
|
|
if(!this.models[m]) throw new Error(`Model does not exist: ${m}`);
|
|
|
|
if(!this.models[m]) throw new Error(`Model does not exist: ${m}`);
|
|
|
|
let request: AbortablePromise<string> | null = null;
|
|
|
|
let request: AbortablePromise<string> | null = null;
|
|
|
|
let aborted = false;
|
|
|
|
let aborted = false;
|
|
|
|
const nestedAborts: (() => void)[] = [];
|
|
|
|
let keepOnAbort = true;
|
|
|
|
const abort = () => {
|
|
|
|
const nestedAborts: ((keep?: boolean) => void)[] = [];
|
|
|
|
|
|
|
|
const abort = (keep = true) => {
|
|
|
|
aborted = true;
|
|
|
|
aborted = true;
|
|
|
|
request?.abort?.();
|
|
|
|
keepOnAbort = keep;
|
|
|
|
nestedAborts.forEach(a => a());
|
|
|
|
request?.abort?.(keep);
|
|
|
|
|
|
|
|
nestedAborts.forEach(a => a(keep));
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let promise: any;
|
|
|
|
let promise: any;
|
|
|
|
@@ -411,9 +413,25 @@ ${a.system}`,
|
|
|
|
let tools: AiTool[] = options.tools || this.ai.options.llm?.tools || [];
|
|
|
|
let tools: AiTool[] = options.tools || this.ai.options.llm?.tools || [];
|
|
|
|
const prompts: string[] = [];
|
|
|
|
const prompts: string[] = [];
|
|
|
|
let history = options.history || [];
|
|
|
|
let history = options.history || [];
|
|
|
|
|
|
|
|
const historyStart = history.length;
|
|
|
|
const files = options.files || [];
|
|
|
|
const files = options.files || [];
|
|
|
|
if(message || files.length) history.push({role: 'user', content: message || '', timestamp: Date.now()});
|
|
|
|
if(message || files.length) history.push({role: 'user', content: message || '', timestamp: Date.now()});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Accumulate streamed text so it can be committed to history if aborted mid-generation
|
|
|
|
|
|
|
|
let partialText = '';
|
|
|
|
|
|
|
|
const onStream = options.stream;
|
|
|
|
|
|
|
|
const stream = (chunk: {text?: string, tool?: string, done?: true}) => {
|
|
|
|
|
|
|
|
if(chunk.text) partialText += chunk.text;
|
|
|
|
|
|
|
|
return onStream?.(chunk);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Commit (keep) or discard this turn's progress on abort, then throw */
|
|
|
|
|
|
|
|
const abortNow = (): never => {
|
|
|
|
|
|
|
|
if(keepOnAbort) { if(partialText) history.push({role: 'assistant', content: partialText, timestamp: Date.now()}); }
|
|
|
|
|
|
|
|
else history.splice(historyStart, history.length - historyStart);
|
|
|
|
|
|
|
|
throw Object.assign(new Error('Aborted'), {name: 'AbortError'});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// MCP
|
|
|
|
// MCP
|
|
|
|
const mcp = options.mcp || this.ai.options?.llm?.mcp;
|
|
|
|
const mcp = options.mcp || this.ai.options?.llm?.mcp;
|
|
|
|
if(mcp?.length) {
|
|
|
|
if(mcp?.length) {
|
|
|
|
@@ -441,8 +459,8 @@ ${a.system}`,
|
|
|
|
const mems = mem.memory instanceof MemoryCache ? mem.memory.memories : mem.memory;
|
|
|
|
const mems = mem.memory instanceof MemoryCache ? mem.memory.memories : mem.memory;
|
|
|
|
if(mems.length) {
|
|
|
|
if(mems.length) {
|
|
|
|
if(mem.inject) {
|
|
|
|
if(mem.inject) {
|
|
|
|
const pool = 15; // candidates considered, cheap since only refs are listed
|
|
|
|
const pool = 15;
|
|
|
|
const budget = mem.maxTokens ?? 2000; // actual content injected
|
|
|
|
const budget = mem.maxTokens ?? 2000;
|
|
|
|
const relevant = await this.memoryManager.recollect(message, mem.memory, pool);
|
|
|
|
const relevant = await this.memoryManager.recollect(message, mem.memory, pool);
|
|
|
|
|
|
|
|
|
|
|
|
let used = 0;
|
|
|
|
let used = 0;
|
|
|
|
@@ -481,7 +499,7 @@ Linked: ${makeUnique([...r.links, ...r.backlinks]).join(', ')}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(aborted) throw Object.assign(new Error('Aborted'), {name: 'AbortError'});
|
|
|
|
if(aborted) abortNow();
|
|
|
|
|
|
|
|
|
|
|
|
const lastMsg = history[history.length - 1];
|
|
|
|
const lastMsg = history[history.length - 1];
|
|
|
|
if(files.length && lastMsg?.role === 'user') lastMsg.files = files;
|
|
|
|
if(files.length && lastMsg?.role === 'user') lastMsg.files = files;
|
|
|
|
@@ -500,11 +518,17 @@ Linked: ${makeUnique([...r.links, ...r.backlinks]).join(', ')}
|
|
|
|
const toolTimings = new Map<string, {duration: number, tps: number}>();
|
|
|
|
const toolTimings = new Map<string, {duration: number, tps: number}>();
|
|
|
|
tools = this.wrapToolTiming(tools, toolTimings);
|
|
|
|
tools = this.wrapToolTiming(tools, toolTimings);
|
|
|
|
|
|
|
|
|
|
|
|
if(aborted) throw Object.assign(new Error('Aborted'), {name: 'AbortError'});
|
|
|
|
if(aborted) abortNow();
|
|
|
|
|
|
|
|
|
|
|
|
prompts.unshift(options.system || this.ai.options.llm?.system || '');
|
|
|
|
prompts.unshift(options.system || this.ai.options.llm?.system || '');
|
|
|
|
request = this.models[m].ask('', {...options, tools, system: prompts.filter(Boolean).join('\n\n')});
|
|
|
|
request = this.models[m].ask('', {...options, tools, stream, system: prompts.filter(Boolean).join('\n\n')});
|
|
|
|
let resp = await request;
|
|
|
|
let resp: string;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
resp = await request;
|
|
|
|
|
|
|
|
} catch(err: any) {
|
|
|
|
|
|
|
|
if(aborted) return abortNow();
|
|
|
|
|
|
|
|
throw err;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Strip the file injection shim
|
|
|
|
// Strip the file injection shim
|
|
|
|
restores.forEach(({msg, content}) => msg.content = content);
|
|
|
|
restores.forEach(({msg, content}) => msg.content = content);
|
|
|
|
|