@xynehq/jaf
Version:
Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools
104 lines • 4.82 kB
JavaScript
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import { run } from './engine.js';
import { createRunId, } from './types.js';
import { ToolResponse } from './tool-results.js';
import { getToolRuntime } from './tool-runtime.js';
/**
* Wrap an Agent as a Tool so it can be invoked by another agent.
* The child agent runs in an isolated sub-run, receives only the input text,
* and returns its final output as a string (or via custom extractor).
*/
export function agentAsTool(childAgent, options = {}) {
const { toolName = childAgent.name, toolDescription = `Run the '${childAgent.name}' agent on the supplied input and return its result`, customOutputExtractor, maxTurns = 5, registry, propagateEvents = 'summary', memoryMode = 'none', } = options;
const paramsSchema = z.object({
input: z.string().describe('Input text passed to the sub-agent as a user message')
});
return {
schema: {
name: toolName,
description: toolDescription,
parameters: paramsSchema,
},
execute: async (args, context) => {
// Retrieve current runtime (state + config) from engine bridge
const runtime = getToolRuntime(context);
if (!runtime) {
return ToolResponse.error('EXECUTION_FAILED', 'Agent tool cannot access runtime. Ensure engine installs tool runtime before execution.');
}
const parentState = runtime.state;
const parentConfig = runtime.config;
// Build child run state: new runId, same traceId, single user message
const childState = {
runId: createRunId(uuidv4()),
traceId: parentState.traceId,
messages: [
{ role: 'user', content: args.input }
],
currentAgentName: childAgent.name,
context: parentState.context,
turnCount: 0,
approvals: new Map(),
};
// Build child config derived from parent
const childRegistry = registry ?? new Map([[childAgent.name, childAgent]]);
const childConfig = {
...parentConfig,
agentRegistry: childRegistry,
maxTurns,
// Memory isolation by default
...(memoryMode === 'none' ? { memory: undefined, conversationId: undefined } : {}),
onEvent: (event) => {
if (propagateEvents === 'all') {
parentConfig.onEvent?.(event);
}
else if (propagateEvents === 'summary') {
// For summary, we still forward important boundaries
if (event.type === 'run_start' || event.type === 'run_end' || event.type === 'final_output' || event.type === 'handoff') {
parentConfig.onEvent?.(event);
}
}
}
};
try {
const result = await run(childState, childConfig);
const childRunId = childState.runId;
if (result.outcome.status === 'completed') {
const output = result.outcome.output;
let text;
if (customOutputExtractor) {
text = await customOutputExtractor(output, result.finalState);
}
else {
// Default conversion: strings as-is, objects to JSON
text = typeof output === 'string' ? output : JSON.stringify(output);
}
return ToolResponse.success(text, {
toolName,
childRunId,
childAgent: childAgent.name,
turns: result.finalState.turnCount,
});
}
// Error path
let message;
if (result.outcome.status === 'error') {
const err = result.outcome.error;
message = `${err._tag}${'detail' in err ? `: ${err.detail}` : ''}`;
}
else {
message = 'Sub-agent interrupted';
}
return ToolResponse.error('EXECUTION_FAILED', message, {
toolName,
childRunId,
childAgent: childAgent.name,
});
}
catch (e) {
return ToolResponse.error('EXECUTION_FAILED', e instanceof Error ? e.message : String(e), { toolName, childAgent: childAgent.name });
}
}
};
}
//# sourceMappingURL=agent-as-tool.js.map