Compare commits

..
2 Commits
Author SHA1 Message Date
ztimson 6bed8f20b5 Recursive agents update
Publish Library / Build NPM Project (push) Successful in 36s
Publish Library / Tag Version (push) Successful in 7s
2026-09-20 00:43:52 -04:00
ztimson dc45a99b04 Bump 1.6.13
Publish Library / Build NPM Project (push) Successful in 41s
Publish Library / Tag Version (push) Successful in 14s
2026-09-19 19:30:06 -04:00
2 changed files with 20 additions and 14 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ztimson/ai-utils",
"version": "1.6.12",
"version": "1.7.0",
"description": "AI Utility library",
"author": "Zak Timson",
"license": "MIT",
+19 -13
View File
@@ -19,6 +19,13 @@ const PDF_OCR_PAGE_THRESHOLD = 12; // above this many pages, OCR scanned pages i
export type AnthropicConfig = {proto: 'anthropic', token: string | string[]};
export type OpenAiConfig = {proto: 'openai', host?: string, token: string | string[]};
export type AgentRef = {
name: string;
description?: string;
delegate?: boolean;
fn: () => Agent | null | Promise<Agent | null>;
}
export type Agent = {
name: string;
description?: string;
@@ -29,7 +36,7 @@ export type Agent = {
skills?: Skill[] | null;
tools?: AiTool[] | null;
mcp?: McpServer[] | null;
agents?: string[] | null;
agents?: AgentRef[] | null;
}
export type LLMFile = {
@@ -106,8 +113,8 @@ export type LLMRequest = {
skills?: Skill[];
/** MCP servers to connect and expose as tools */
mcp?: McpServer[];
/** Subagents exposed as delegatable/wrapped tools */
agents?: Agent[];
/** Subagents exposed as delegatable/wrapped tools, resolved lazily via their `fn` */
agents?: AgentRef[];
/** Attach files to request */
files?: LLMFile[];
/** @internal recursion guard for nested agent delegation */
@@ -265,22 +272,21 @@ class LLM {
};
}
private setupAgent(agents: Agent[] = [], allAgents: Agent[], history: LLMMessage[], aborts: ((keep?: boolean) => void)[], depth = 0, delegateState: {resp: string | null}): AiTool[] {
return agents.map(a => {
const toolName = `${a.delegate ? '' : 'sub'}agent_${snakeCase(a.name)}`;
private setupAgent(stubs: AgentRef[] = [], history: LLMMessage[], aborts: ((keep?: boolean) => void)[], depth = 0, delegateState: {resp: string | null}): AiTool[] {
return stubs.map(stub => {
const toolName = `${stub.delegate ? '' : 'sub'}agent_${snakeCase(stub.name)}`;
return {
name: toolName,
description: `${a.delegate ? 'Delegate to ' : ''}Subagent: ${a.description || a.name}`,
description: `${stub.delegate ? 'Delegate to ' : ''}Subagent: ${stub.description || stub.name}`,
args: clean<any>({
context: !a.delegate ? {type: 'string', description: 'Summary of related messages, samples, files, etc...', required: true} : undefined,
context: !stub.delegate ? {type: 'string', description: 'Summary of related messages, samples, files, etc...', required: true} : undefined,
instructions: {type: 'string', description: 'Detailed instructions for subagent to complete', required: true},
}),
fn: async (args: any, stream: any, ai: any, id?: string) => {
if(depth >= MAX_AGENT_DEPTH) return 'Max agent delegation depth exceeded';
const nested = (a.agents || [])
.map(name => allAgents.find(x => x.name === name))
.filter((x): x is Agent => !!x && x.name !== a.name);
const a = await stub.fn();
if(!a) return `Agent "${stub.name}" could not be resolved`;
const q = a.delegate ? '' : `${args.instructions}${args.context ? `\n\n<context>${args.context}</context>` : ''}`;
@@ -297,7 +303,7 @@ ${a.system}`,
mcp: a.mcp || undefined,
skills: a.skills || undefined,
tools: a.tools || undefined,
agents: nested,
agents: a.agents || [],
_agentDepth: depth + 1,
} as any);
aborts.push(request.abort);
@@ -451,7 +457,7 @@ ${a.system}`,
// Agents
const agents = options.agents || this.ai.options?.llm?.agents;
const delegateState: {resp: string | null} = {resp: null};
if(agents?.length) tools.push(...this.setupAgent(agents, agents, history, nestedAborts, options._agentDepth || 0, delegateState));
if(agents?.length) tools.push(...this.setupAgent(agents, history, nestedAborts, options._agentDepth || 0, delegateState));
// Memory
const mem = MemoryManager.normalize(options.memory);