@illuma-ai/agents 1.0.85 → 1.0.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"graph.mjs","sources":["../../../src/types/graph.ts"],"sourcesContent":["// src/types/graph.ts\nimport type {\n START,\n StateType,\n UpdateType,\n StateGraph,\n StateGraphArgs,\n StateDefinition,\n CompiledStateGraph,\n BinaryOperatorAggregate,\n} from '@langchain/langgraph';\nimport type { BindToolsInput } from '@langchain/core/language_models/chat_models';\nimport type {\n BaseMessage,\n AIMessageChunk,\n SystemMessage,\n} from '@langchain/core/messages';\nimport type { RunnableConfig, Runnable } from '@langchain/core/runnables';\nimport type { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { GoogleAIToolType } from '@langchain/google-common';\nimport type { ToolMap, ToolEndEvent, GenericTool, LCTool, ToolApprovalConfig } from '@/types/tools';\nimport type { Providers, Callback, GraphNodeKeys } from '@/common';\nimport type { StandardGraph, MultiAgentGraph } from '@/graphs';\nimport type { ClientOptions } from '@/types/llm';\nimport type {\n RunStep,\n RunStepDeltaEvent,\n MessageDeltaEvent,\n ReasoningDeltaEvent,\n} from '@/types/stream';\nimport type { TokenCounter } from '@/types/run';\n\n/** Interface for bound model with stream and invoke methods */\nexport interface ChatModel {\n stream?: (\n messages: BaseMessage[],\n config?: RunnableConfig\n ) => Promise<AsyncIterable<AIMessageChunk>>;\n invoke: (\n messages: BaseMessage[],\n config?: RunnableConfig\n ) => Promise<AIMessageChunk>;\n}\n\nexport type GraphNode = GraphNodeKeys | typeof START;\nexport type ClientCallback<T extends unknown[]> = (\n graph: StandardGraph,\n ...args: T\n) => void;\n\nexport type ClientCallbacks = {\n [Callback.TOOL_ERROR]?: ClientCallback<[Error, string]>;\n [Callback.TOOL_START]?: ClientCallback<unknown[]>;\n [Callback.TOOL_END]?: ClientCallback<unknown[]>;\n};\n\nexport type SystemCallbacks = {\n [K in keyof ClientCallbacks]: ClientCallbacks[K] extends ClientCallback<\n infer Args\n >\n ? (...args: Args) => void\n : never;\n};\n\nexport type BaseGraphState = {\n messages: BaseMessage[];\n /**\n * Structured response when using structured output mode.\n * Contains the validated JSON response conforming to the configured schema.\n */\n structuredResponse?: Record<string, unknown>;\n};\n\nexport type MultiAgentGraphState = BaseGraphState & {\n agentMessages?: BaseMessage[];\n};\n\nexport type IState = BaseGraphState;\n\nexport interface EventHandler {\n handle(\n event: string,\n data:\n | StreamEventData\n | ModelEndData\n | RunStep\n | RunStepDeltaEvent\n | MessageDeltaEvent\n | ReasoningDeltaEvent\n | { result: ToolEndEvent },\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n ): void | Promise<void>;\n}\n\nexport type GraphStateChannels<T extends BaseGraphState> =\n StateGraphArgs<T>['channels'];\n\nexport type Workflow<\n T extends BaseGraphState = BaseGraphState,\n U extends Partial<T> = Partial<T>,\n N extends string = string,\n> = StateGraph<T, U, N>;\n\nexport type CompiledWorkflow<\n T extends BaseGraphState = BaseGraphState,\n U extends Partial<T> = Partial<T>,\n N extends string = string,\n> = CompiledStateGraph<T, U, N>;\n\nexport type CompiledStateWorkflow = CompiledStateGraph<\n StateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n UpdateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n string,\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n StateDefinition\n>;\n\nexport type CompiledMultiAgentWorkflow = CompiledStateGraph<\n StateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n UpdateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n string,\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n StateDefinition\n>;\n\nexport type CompiledAgentWorfklow = CompiledStateGraph<\n {\n messages: BaseMessage[];\n },\n {\n messages?: BaseMessage[] | undefined;\n },\n '__start__' | `agent=${string}` | `tools=${string}`,\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n StateDefinition,\n {\n [x: `agent=${string}`]: Partial<BaseGraphState>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [x: `tools=${string}`]: any;\n }\n>;\n\nexport type SystemRunnable =\n | Runnable<\n BaseMessage[],\n (BaseMessage | SystemMessage)[],\n RunnableConfig<Record<string, unknown>>\n >\n | undefined;\n\n/**\n * Optional compile options passed to workflow.compile().\n * These are intentionally untyped to avoid coupling to library internals.\n */\nexport type CompileOptions = {\n // A checkpointer instance (e.g., MemorySaver, SQL saver)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n checkpointer?: any;\n interruptBefore?: string[];\n interruptAfter?: string[];\n /**\n * Human-in-the-loop tool approval configuration.\n * When set, tools matching the policy will trigger an interrupt()\n * before execution, pausing the graph for human approval.\n * Requires a checkpointer to be set for interrupt/resume to work.\n */\n toolApprovalConfig?: ToolApprovalConfig;\n};\n\nexport type EventStreamCallbackHandlerInput =\n Parameters<CompiledWorkflow['streamEvents']>[2] extends Omit<\n infer T,\n 'autoClose'\n >\n ? T\n : never;\n\nexport type StreamChunk =\n | (ChatGenerationChunk & {\n message: AIMessageChunk;\n })\n | AIMessageChunk;\n\n/**\n * Data associated with a StreamEvent.\n */\nexport type StreamEventData = {\n /**\n * The input passed to the runnable that generated the event.\n * Inputs will sometimes be available at the *START* of the runnable, and\n * sometimes at the *END* of the runnable.\n * If a runnable is able to stream its inputs, then its input by definition\n * won't be known until the *END* of the runnable when it has finished streaming\n * its inputs.\n */\n input?: unknown;\n /**\n * The output of the runnable that generated the event.\n * Outputs will only be available at the *END* of the runnable.\n * For most runnables, this field can be inferred from the `chunk` field,\n * though there might be some exceptions for special cased runnables (e.g., like\n * chat models), which may return more information.\n */\n output?: unknown;\n /**\n * A streaming chunk from the output that generated the event.\n * chunks support addition in general, and adding them up should result\n * in the output of the runnable that generated the event.\n */\n chunk?: StreamChunk;\n /**\n * Runnable config for invoking other runnables within handlers.\n */\n config?: RunnableConfig;\n /**\n * Custom result from the runnable that generated the event.\n */\n result?: unknown;\n /**\n * Custom field to indicate the event was manually emitted, and may have been handled already\n */\n emitted?: boolean;\n};\n\n/**\n * A streaming event.\n *\n * Schema of a streaming event which is produced from the streamEvents method.\n */\nexport type StreamEvent = {\n /**\n * Event names are of the format: on_[runnable_type]_(start|stream|end).\n *\n * Runnable types are one of:\n * - llm - used by non chat models\n * - chat_model - used by chat models\n * - prompt -- e.g., ChatPromptTemplate\n * - tool -- LangChain tools\n * - chain - most Runnables are of this type\n *\n * Further, the events are categorized as one of:\n * - start - when the runnable starts\n * - stream - when the runnable is streaming\n * - end - when the runnable ends\n *\n * start, stream and end are associated with slightly different `data` payload.\n *\n * Please see the documentation for `EventData` for more details.\n */\n event: string;\n /** The name of the runnable that generated the event. */\n name: string;\n /**\n * An randomly generated ID to keep track of the execution of the given runnable.\n *\n * Each child runnable that gets invoked as part of the execution of a parent runnable\n * is assigned its own unique ID.\n */\n run_id: string;\n /**\n * Tags associated with the runnable that generated this event.\n * Tags are always inherited from parent runnables.\n */\n tags?: string[];\n /** Metadata associated with the runnable that generated this event. */\n metadata: Record<string, unknown>;\n /**\n * Event data.\n *\n * The contents of the event data depend on the event type.\n */\n data: StreamEventData;\n};\n\nexport type GraphConfig = {\n provider: string;\n thread_id?: string;\n run_id?: string;\n};\n\nexport type PartMetadata = {\n progress?: number;\n asset_pointer?: string;\n status?: string;\n action?: boolean;\n output?: string;\n};\n\nexport type ModelEndData =\n | (StreamEventData & { output: AIMessageChunk | undefined })\n | undefined;\nexport type GraphTools = GenericTool[] | BindToolsInput[] | GoogleAIToolType[];\nexport type StandardGraphInput = {\n runId?: string;\n signal?: AbortSignal;\n agents: AgentInputs[];\n tokenCounter?: TokenCounter;\n indexTokenCountMap?: Record<string, number>;\n};\n\nexport type GraphEdge = {\n /** Agent ID, use a list for multiple sources */\n from: string | string[];\n /** Agent ID, use a list for multiple destinations */\n to: string | string[];\n description?: string;\n /** Can return boolean or specific destination(s) */\n condition?: (state: BaseGraphState) => boolean | string | string[];\n /** 'handoff' creates tools for dynamic routing, 'direct' creates direct edges, which also allow parallel execution */\n edgeType?: 'handoff' | 'direct';\n /**\n * For direct edges: Optional prompt to add when transitioning through this edge.\n * String prompts can include variables like {results} which will be replaced with\n * messages from startIndex onwards. When {results} is used, excludeResults defaults to true.\n *\n * For handoff edges: Description for the input parameter that the handoff tool accepts,\n * allowing the supervisor to pass specific instructions/context to the transferred agent.\n */\n prompt?:\n | string\n | ((\n messages: BaseMessage[],\n runStartIndex: number\n ) => string | Promise<string> | undefined);\n /**\n * When true, excludes messages from startIndex when adding prompt.\n * Automatically set to true when {results} variable is used in prompt.\n */\n excludeResults?: boolean;\n /**\n * For handoff edges: Customizes the parameter name for the handoff input.\n * Defaults to \"instructions\" if not specified.\n * Only applies when prompt is provided for handoff edges.\n */\n promptKey?: string;\n};\n\nexport type MultiAgentGraphInput = StandardGraphInput & {\n edges: GraphEdge[];\n};\n\n/**\n * Structured output mode determines how the agent returns structured data.\n * - 'tool': Uses tool calling to return structured output (works with all tool-calling models)\n * - 'provider': Uses provider-native structured output via LangChain's jsonMode (OpenAI, Anthropic, etc.)\n * - 'native': Uses provider's constrained decoding API directly for guaranteed schema compliance\n * (Anthropic output_config.format, OpenAI response_format.json_schema). Falls back to 'tool' for unsupported providers.\n * - 'auto': Automatically selects the best strategy — 'native' for supported providers, 'tool' for others\n */\nexport type StructuredOutputMode = 'tool' | 'provider' | 'native' | 'auto';\n\n/**\n * Resolved method used internally after mode resolution.\n * Maps to LangChain's withStructuredOutput method parameter plus our native path.\n */\nexport type ResolvedStructuredOutputMethod = 'functionCalling' | 'jsonMode' | 'jsonSchema' | 'native' | undefined;\n\n/**\n * Error thrown when the model refuses to produce structured output due to safety policies.\n */\nexport class StructuredOutputRefusalError extends Error {\n constructor(public refusalText: string) {\n super(`Model refused to produce structured output: ${refusalText}`);\n this.name = 'StructuredOutputRefusalError';\n }\n}\n\n/**\n * Error thrown when the structured output response was truncated due to max_tokens.\n */\nexport class StructuredOutputTruncatedError extends Error {\n constructor(public stopReason: string) {\n super(\n `Structured output was truncated (stop_reason: ${stopReason}). ` +\n `Increase max_tokens to allow the full JSON response to be generated.`\n );\n this.name = 'StructuredOutputTruncatedError';\n }\n}\n\n/**\n * Configuration for structured JSON output from agents.\n * When configured, the agent will return a validated JSON response\n * instead of streaming text.\n */\nexport interface StructuredOutputConfig {\n /**\n * JSON Schema defining the output structure.\n * The model will be forced to return data conforming to this schema.\n */\n schema: Record<string, unknown>;\n /**\n * Name for the structured output format (used in tool mode).\n * @default 'StructuredResponse'\n */\n name?: string;\n /**\n * Description of what the structured output represents.\n * Helps the model understand the expected format.\n */\n description?: string;\n /**\n * Output mode strategy.\n * @default 'auto'\n */\n mode?: StructuredOutputMode;\n /**\n * Enable strict schema validation.\n * When true, the response must exactly match the schema.\n * @default true\n */\n strict?: boolean;\n /**\n * Error handling configuration.\n * - true: Auto-retry on validation errors (default)\n * - false: Throw error on validation failure\n * - string: Custom error message for retry\n */\n handleErrors?: boolean | string;\n /**\n * Maximum number of retry attempts on validation failure.\n * @default 2\n */\n maxRetries?: number;\n /**\n * Include the raw AI message along with structured response.\n * Useful for debugging.\n * @default false\n */\n includeRaw?: boolean;\n}\n\n/**\n * Database/API structured output format (snake_case with enabled flag).\n * This matches the format stored in MongoDB and sent from frontends.\n */\nexport interface StructuredOutputInput {\n /** Whether structured output is enabled */\n enabled?: boolean;\n /** JSON Schema defining the expected response structure */\n schema?: Record<string, unknown>;\n /** Name identifier for the structured output */\n name?: string;\n /** Description of what the structured output represents */\n description?: string;\n /** Mode for structured output: 'tool' | 'provider' | 'native' | 'auto' */\n mode?: StructuredOutputMode;\n /** Whether to enforce strict schema validation */\n strict?: boolean;\n}\n\nexport interface AgentInputs {\n agentId: string;\n /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */\n name?: string;\n toolEnd?: boolean;\n toolMap?: ToolMap;\n tools?: GraphTools;\n provider: Providers;\n instructions?: string;\n streamBuffer?: number;\n maxContextTokens?: number;\n clientOptions?: ClientOptions;\n additional_instructions?: string;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n /** Format content blocks as strings (for legacy compatibility i.e. Ollama/Azure Serverless) */\n useLegacyContent?: boolean;\n /**\n * Tool definitions for all tools, including deferred and programmatic.\n * Used for tool search and programmatic tool calling.\n * Maps tool name to LCTool definition.\n */\n toolRegistry?: Map<string, LCTool>;\n /**\n * Dynamic context that changes per-request (e.g., current time, user info).\n * This is injected as a user message rather than system prompt to preserve cache.\n * Keeping this separate from instructions ensures the system message stays static\n * and can be cached by Bedrock/Anthropic prompt caching.\n */\n dynamicContext?: string;\n /**\n * Structured output configuration (camelCase).\n * When set, disables streaming and returns a validated JSON response\n * conforming to the specified schema.\n */\n structuredOutput?: StructuredOutputConfig;\n /**\n * Structured output configuration (snake_case - database/API format).\n * Alternative to structuredOutput for compatibility with MongoDB/frontend.\n * Uses an `enabled` flag to control activation.\n * @deprecated Use structuredOutput instead when possible\n */\n structured_output?: StructuredOutputInput;\n /**\n * Serializable tool definitions for event-driven execution.\n * When provided, ToolNode operates in event-driven mode, dispatching\n * ON_TOOL_EXECUTE events instead of invoking tools directly.\n */\n toolDefinitions?: LCTool[];\n /**\n * Tool names discovered from previous conversation history.\n * These tools will be pre-marked as discovered so they're included\n * in tool binding without requiring tool_search.\n */\n discoveredTools?: string[];\n /**\n * Optional callback for summarizing messages that were pruned from context.\n * When provided, discarded messages are summarized by the caller (e.g., Ranger)\n * using a cheap LLM call, and the summary is prepended to the context.\n */\n summarizeCallback?: (messagesToRefine: import('@langchain/core/messages').BaseMessage[]) => Promise<string | undefined>;\n}\n"],"names":[],"mappings":"AAiYA;;AAEG;AACG,MAAO,4BAA6B,SAAQ,KAAK,CAAA;AAClC,IAAA,WAAA;AAAnB,IAAA,WAAA,CAAmB,WAAmB,EAAA;AACpC,QAAA,KAAK,CAAC,CAAA,4CAAA,EAA+C,WAAW,CAAA,CAAE,CAAC;QADlD,IAAW,CAAA,WAAA,GAAX,WAAW;AAE5B,QAAA,IAAI,CAAC,IAAI,GAAG,8BAA8B;;AAE7C;AAED;;AAEG;AACG,MAAO,8BAA+B,SAAQ,KAAK,CAAA;AACpC,IAAA,UAAA;AAAnB,IAAA,WAAA,CAAmB,UAAkB,EAAA;QACnC,KAAK,CACH,CAAiD,8CAAA,EAAA,UAAU,CAAK,GAAA,CAAA;AAChE,YAAA,CAAA,oEAAA,CAAsE,CACvE;QAJgB,IAAU,CAAA,UAAA,GAAV,UAAU;AAK3B,QAAA,IAAI,CAAC,IAAI,GAAG,gCAAgC;;AAE/C;;;;"}
1
+ {"version":3,"file":"graph.mjs","sources":["../../../src/types/graph.ts"],"sourcesContent":["// src/types/graph.ts\nimport type {\n START,\n StateType,\n UpdateType,\n StateGraph,\n StateGraphArgs,\n StateDefinition,\n CompiledStateGraph,\n BinaryOperatorAggregate,\n} from '@langchain/langgraph';\nimport type { BindToolsInput } from '@langchain/core/language_models/chat_models';\nimport type {\n BaseMessage,\n AIMessageChunk,\n SystemMessage,\n} from '@langchain/core/messages';\nimport type { RunnableConfig, Runnable } from '@langchain/core/runnables';\nimport type { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type { GoogleAIToolType } from '@langchain/google-common';\nimport type { ToolMap, ToolEndEvent, GenericTool, LCTool, ToolApprovalConfig } from '@/types/tools';\nimport type { Providers, Callback, GraphNodeKeys } from '@/common';\nimport type { StandardGraph, MultiAgentGraph } from '@/graphs';\nimport type { ClientOptions } from '@/types/llm';\nimport type {\n RunStep,\n RunStepDeltaEvent,\n MessageDeltaEvent,\n ReasoningDeltaEvent,\n} from '@/types/stream';\nimport type { TokenCounter } from '@/types/run';\n\n/** Interface for bound model with stream and invoke methods */\nexport interface ChatModel {\n stream?: (\n messages: BaseMessage[],\n config?: RunnableConfig\n ) => Promise<AsyncIterable<AIMessageChunk>>;\n invoke: (\n messages: BaseMessage[],\n config?: RunnableConfig\n ) => Promise<AIMessageChunk>;\n}\n\nexport type GraphNode = GraphNodeKeys | typeof START;\nexport type ClientCallback<T extends unknown[]> = (\n graph: StandardGraph,\n ...args: T\n) => void;\n\nexport type ClientCallbacks = {\n [Callback.TOOL_ERROR]?: ClientCallback<[Error, string]>;\n [Callback.TOOL_START]?: ClientCallback<unknown[]>;\n [Callback.TOOL_END]?: ClientCallback<unknown[]>;\n};\n\nexport type SystemCallbacks = {\n [K in keyof ClientCallbacks]: ClientCallbacks[K] extends ClientCallback<\n infer Args\n >\n ? (...args: Args) => void\n : never;\n};\n\nexport type BaseGraphState = {\n messages: BaseMessage[];\n /**\n * Structured response when using structured output mode.\n * Contains the validated JSON response conforming to the configured schema.\n */\n structuredResponse?: Record<string, unknown>;\n};\n\nexport type MultiAgentGraphState = BaseGraphState & {\n agentMessages?: BaseMessage[];\n};\n\nexport type IState = BaseGraphState;\n\nexport interface EventHandler {\n handle(\n event: string,\n data:\n | StreamEventData\n | ModelEndData\n | RunStep\n | RunStepDeltaEvent\n | MessageDeltaEvent\n | ReasoningDeltaEvent\n | { result: ToolEndEvent },\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n ): void | Promise<void>;\n}\n\nexport type GraphStateChannels<T extends BaseGraphState> =\n StateGraphArgs<T>['channels'];\n\nexport type Workflow<\n T extends BaseGraphState = BaseGraphState,\n U extends Partial<T> = Partial<T>,\n N extends string = string,\n> = StateGraph<T, U, N>;\n\nexport type CompiledWorkflow<\n T extends BaseGraphState = BaseGraphState,\n U extends Partial<T> = Partial<T>,\n N extends string = string,\n> = CompiledStateGraph<T, U, N>;\n\nexport type CompiledStateWorkflow = CompiledStateGraph<\n StateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n UpdateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n string,\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n StateDefinition\n>;\n\nexport type CompiledMultiAgentWorkflow = CompiledStateGraph<\n StateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n UpdateType<{\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n }>,\n string,\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n StateDefinition\n>;\n\nexport type CompiledAgentWorfklow = CompiledStateGraph<\n {\n messages: BaseMessage[];\n },\n {\n messages?: BaseMessage[] | undefined;\n },\n '__start__' | `agent=${string}` | `tools=${string}`,\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n {\n messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;\n },\n StateDefinition,\n {\n [x: `agent=${string}`]: Partial<BaseGraphState>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [x: `tools=${string}`]: any;\n }\n>;\n\nexport type SystemRunnable =\n | Runnable<\n BaseMessage[],\n (BaseMessage | SystemMessage)[],\n RunnableConfig<Record<string, unknown>>\n >\n | undefined;\n\n/**\n * Optional compile options passed to workflow.compile().\n * These are intentionally untyped to avoid coupling to library internals.\n */\nexport type CompileOptions = {\n // A checkpointer instance (e.g., MemorySaver, SQL saver)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n checkpointer?: any;\n interruptBefore?: string[];\n interruptAfter?: string[];\n /**\n * Human-in-the-loop tool approval configuration.\n * When set, tools matching the policy will trigger an interrupt()\n * before execution, pausing the graph for human approval.\n * Requires a checkpointer to be set for interrupt/resume to work.\n */\n toolApprovalConfig?: ToolApprovalConfig;\n};\n\nexport type EventStreamCallbackHandlerInput =\n Parameters<CompiledWorkflow['streamEvents']>[2] extends Omit<\n infer T,\n 'autoClose'\n >\n ? T\n : never;\n\nexport type StreamChunk =\n | (ChatGenerationChunk & {\n message: AIMessageChunk;\n })\n | AIMessageChunk;\n\n/**\n * Data associated with a StreamEvent.\n */\nexport type StreamEventData = {\n /**\n * The input passed to the runnable that generated the event.\n * Inputs will sometimes be available at the *START* of the runnable, and\n * sometimes at the *END* of the runnable.\n * If a runnable is able to stream its inputs, then its input by definition\n * won't be known until the *END* of the runnable when it has finished streaming\n * its inputs.\n */\n input?: unknown;\n /**\n * The output of the runnable that generated the event.\n * Outputs will only be available at the *END* of the runnable.\n * For most runnables, this field can be inferred from the `chunk` field,\n * though there might be some exceptions for special cased runnables (e.g., like\n * chat models), which may return more information.\n */\n output?: unknown;\n /**\n * A streaming chunk from the output that generated the event.\n * chunks support addition in general, and adding them up should result\n * in the output of the runnable that generated the event.\n */\n chunk?: StreamChunk;\n /**\n * Runnable config for invoking other runnables within handlers.\n */\n config?: RunnableConfig;\n /**\n * Custom result from the runnable that generated the event.\n */\n result?: unknown;\n /**\n * Custom field to indicate the event was manually emitted, and may have been handled already\n */\n emitted?: boolean;\n};\n\n/**\n * A streaming event.\n *\n * Schema of a streaming event which is produced from the streamEvents method.\n */\nexport type StreamEvent = {\n /**\n * Event names are of the format: on_[runnable_type]_(start|stream|end).\n *\n * Runnable types are one of:\n * - llm - used by non chat models\n * - chat_model - used by chat models\n * - prompt -- e.g., ChatPromptTemplate\n * - tool -- LangChain tools\n * - chain - most Runnables are of this type\n *\n * Further, the events are categorized as one of:\n * - start - when the runnable starts\n * - stream - when the runnable is streaming\n * - end - when the runnable ends\n *\n * start, stream and end are associated with slightly different `data` payload.\n *\n * Please see the documentation for `EventData` for more details.\n */\n event: string;\n /** The name of the runnable that generated the event. */\n name: string;\n /**\n * An randomly generated ID to keep track of the execution of the given runnable.\n *\n * Each child runnable that gets invoked as part of the execution of a parent runnable\n * is assigned its own unique ID.\n */\n run_id: string;\n /**\n * Tags associated with the runnable that generated this event.\n * Tags are always inherited from parent runnables.\n */\n tags?: string[];\n /** Metadata associated with the runnable that generated this event. */\n metadata: Record<string, unknown>;\n /**\n * Event data.\n *\n * The contents of the event data depend on the event type.\n */\n data: StreamEventData;\n};\n\nexport type GraphConfig = {\n provider: string;\n thread_id?: string;\n run_id?: string;\n};\n\nexport type PartMetadata = {\n progress?: number;\n asset_pointer?: string;\n status?: string;\n action?: boolean;\n output?: string;\n};\n\nexport type ModelEndData =\n | (StreamEventData & { output: AIMessageChunk | undefined })\n | undefined;\nexport type GraphTools = GenericTool[] | BindToolsInput[] | GoogleAIToolType[];\nexport type StandardGraphInput = {\n runId?: string;\n signal?: AbortSignal;\n agents: AgentInputs[];\n tokenCounter?: TokenCounter;\n indexTokenCountMap?: Record<string, number>;\n};\n\nexport type GraphEdge = {\n /** Agent ID, use a list for multiple sources */\n from: string | string[];\n /** Agent ID, use a list for multiple destinations */\n to: string | string[];\n description?: string;\n /** Can return boolean or specific destination(s) */\n condition?: (state: BaseGraphState) => boolean | string | string[];\n /** 'handoff' creates tools for dynamic routing, 'direct' creates direct edges, which also allow parallel execution */\n edgeType?: 'handoff' | 'direct';\n /**\n * For direct edges: Optional prompt to add when transitioning through this edge.\n * String prompts can include variables like {results} which will be replaced with\n * messages from startIndex onwards. When {results} is used, excludeResults defaults to true.\n *\n * For handoff edges: Description for the input parameter that the handoff tool accepts,\n * allowing the supervisor to pass specific instructions/context to the transferred agent.\n */\n prompt?:\n | string\n | ((\n messages: BaseMessage[],\n runStartIndex: number\n ) => string | Promise<string> | undefined);\n /**\n * When true, excludes messages from startIndex when adding prompt.\n * Automatically set to true when {results} variable is used in prompt.\n */\n excludeResults?: boolean;\n /**\n * For handoff edges: Customizes the parameter name for the handoff input.\n * Defaults to \"instructions\" if not specified.\n * Only applies when prompt is provided for handoff edges.\n */\n promptKey?: string;\n};\n\nexport type MultiAgentGraphInput = StandardGraphInput & {\n edges: GraphEdge[];\n};\n\n/**\n * Structured output mode determines how the agent returns structured data.\n * - 'tool': Uses tool calling to return structured output (works with all tool-calling models)\n * - 'provider': Uses provider-native structured output via LangChain's jsonMode (OpenAI, Anthropic, etc.)\n * - 'native': Uses provider's constrained decoding API directly for guaranteed schema compliance\n * (Anthropic output_config.format, OpenAI response_format.json_schema). Falls back to 'tool' for unsupported providers.\n * - 'auto': Automatically selects the best strategy — 'native' for supported providers, 'tool' for others\n */\nexport type StructuredOutputMode = 'tool' | 'provider' | 'native' | 'auto';\n\n/**\n * Resolved method used internally after mode resolution.\n * Maps to LangChain's withStructuredOutput method parameter plus our native path.\n */\nexport type ResolvedStructuredOutputMethod = 'functionCalling' | 'jsonMode' | 'jsonSchema' | 'native' | undefined;\n\n/**\n * Error thrown when the model refuses to produce structured output due to safety policies.\n */\nexport class StructuredOutputRefusalError extends Error {\n constructor(public refusalText: string) {\n super(`Model refused to produce structured output: ${refusalText}`);\n this.name = 'StructuredOutputRefusalError';\n }\n}\n\n/**\n * Error thrown when the structured output response was truncated due to max_tokens.\n */\nexport class StructuredOutputTruncatedError extends Error {\n constructor(public stopReason: string) {\n super(\n `Structured output was truncated (stop_reason: ${stopReason}). ` +\n `Increase max_tokens to allow the full JSON response to be generated.`\n );\n this.name = 'StructuredOutputTruncatedError';\n }\n}\n\n/**\n * Configuration for structured JSON output from agents.\n * When configured, the agent will return a validated JSON response\n * instead of streaming text.\n */\nexport interface StructuredOutputConfig {\n /**\n * JSON Schema defining the output structure.\n * The model will be forced to return data conforming to this schema.\n */\n schema: Record<string, unknown>;\n /**\n * Name for the structured output format (used in tool mode).\n * @default 'StructuredResponse'\n */\n name?: string;\n /**\n * Description of what the structured output represents.\n * Helps the model understand the expected format.\n */\n description?: string;\n /**\n * Output mode strategy.\n * @default 'auto'\n */\n mode?: StructuredOutputMode;\n /**\n * Enable strict schema validation.\n * When true, the response must exactly match the schema.\n * @default true\n */\n strict?: boolean;\n /**\n * Error handling configuration.\n * - true: Auto-retry on validation errors (default)\n * - false: Throw error on validation failure\n * - string: Custom error message for retry\n */\n handleErrors?: boolean | string;\n /**\n * Maximum number of retry attempts on validation failure.\n * @default 2\n */\n maxRetries?: number;\n /**\n * Include the raw AI message along with structured response.\n * Useful for debugging.\n * @default false\n */\n includeRaw?: boolean;\n}\n\n/**\n * Database/API structured output format (snake_case with enabled flag).\n * This matches the format stored in MongoDB and sent from frontends.\n */\nexport interface StructuredOutputInput {\n /** Whether structured output is enabled */\n enabled?: boolean;\n /** JSON Schema defining the expected response structure */\n schema?: Record<string, unknown>;\n /** Name identifier for the structured output */\n name?: string;\n /** Description of what the structured output represents */\n description?: string;\n /** Mode for structured output: 'tool' | 'provider' | 'native' | 'auto' */\n mode?: StructuredOutputMode;\n /** Whether to enforce strict schema validation */\n strict?: boolean;\n}\n\nexport interface AgentInputs {\n agentId: string;\n /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */\n name?: string;\n /** Description of what this agent does (used to enrich handoff tool descriptions). */\n description?: string;\n toolEnd?: boolean;\n toolMap?: ToolMap;\n tools?: GraphTools;\n provider: Providers;\n instructions?: string;\n streamBuffer?: number;\n maxContextTokens?: number;\n clientOptions?: ClientOptions;\n additional_instructions?: string;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n /** Format content blocks as strings (for legacy compatibility i.e. Ollama/Azure Serverless) */\n useLegacyContent?: boolean;\n /**\n * Tool definitions for all tools, including deferred and programmatic.\n * Used for tool search and programmatic tool calling.\n * Maps tool name to LCTool definition.\n */\n toolRegistry?: Map<string, LCTool>;\n /**\n * Dynamic context that changes per-request (e.g., current time, user info).\n * This is injected as a user message rather than system prompt to preserve cache.\n * Keeping this separate from instructions ensures the system message stays static\n * and can be cached by Bedrock/Anthropic prompt caching.\n */\n dynamicContext?: string;\n /**\n * Structured output configuration (camelCase).\n * When set, disables streaming and returns a validated JSON response\n * conforming to the specified schema.\n */\n structuredOutput?: StructuredOutputConfig;\n /**\n * Structured output configuration (snake_case - database/API format).\n * Alternative to structuredOutput for compatibility with MongoDB/frontend.\n * Uses an `enabled` flag to control activation.\n * @deprecated Use structuredOutput instead when possible\n */\n structured_output?: StructuredOutputInput;\n /**\n * Serializable tool definitions for event-driven execution.\n * When provided, ToolNode operates in event-driven mode, dispatching\n * ON_TOOL_EXECUTE events instead of invoking tools directly.\n */\n toolDefinitions?: LCTool[];\n /**\n * Tool names discovered from previous conversation history.\n * These tools will be pre-marked as discovered so they're included\n * in tool binding without requiring tool_search.\n */\n discoveredTools?: string[];\n /**\n * Optional callback for summarizing messages that were pruned from context.\n * When provided, discarded messages are summarized by the caller (e.g., Ranger)\n * using a cheap LLM call, and the summary is prepended to the context.\n */\n summarizeCallback?: (messagesToRefine: import('@langchain/core/messages').BaseMessage[]) => Promise<string | undefined>;\n}\n"],"names":[],"mappings":"AAiYA;;AAEG;AACG,MAAO,4BAA6B,SAAQ,KAAK,CAAA;AAClC,IAAA,WAAA;AAAnB,IAAA,WAAA,CAAmB,WAAmB,EAAA;AACpC,QAAA,KAAK,CAAC,CAAA,4CAAA,EAA+C,WAAW,CAAA,CAAE,CAAC;QADlD,IAAW,CAAA,WAAA,GAAX,WAAW;AAE5B,QAAA,IAAI,CAAC,IAAI,GAAG,8BAA8B;;AAE7C;AAED;;AAEG;AACG,MAAO,8BAA+B,SAAQ,KAAK,CAAA;AACpC,IAAA,UAAA;AAAnB,IAAA,WAAA,CAAmB,UAAkB,EAAA;QACnC,KAAK,CACH,CAAiD,8CAAA,EAAA,UAAU,CAAK,GAAA,CAAA;AAChE,YAAA,CAAA,oEAAA,CAAsE,CACvE;QAJgB,IAAU,CAAA,UAAA,GAAV,UAAU;AAK3B,QAAA,IAAI,CAAC,IAAI,GAAG,gCAAgC;;AAE/C;;;;"}
@@ -16,6 +16,8 @@ export declare class AgentContext {
16
16
  agentId: string;
17
17
  /** Human-readable name for this agent (used in handoff context). Falls back to agentId if not provided. */
18
18
  name?: string;
19
+ /** Description of what this agent does (used to enrich handoff tool descriptions). */
20
+ description?: string;
19
21
  /** Provider for this specific agent */
20
22
  provider: Providers;
21
23
  /** Client options for this agent */
@@ -109,9 +111,10 @@ export declare class AgentContext {
109
111
  * When provided, discarded messages are summarized instead of silently dropped.
110
112
  */
111
113
  summarizeCallback?: (messagesToRefine: BaseMessage[]) => Promise<string | undefined>;
112
- constructor({ agentId, name, provider, clientOptions, maxContextTokens, streamBuffer, tokenCounter, tools, toolMap, toolRegistry, toolDefinitions, instructions, additionalInstructions, dynamicContext, reasoningKey, toolEnd, instructionTokens, useLegacyContent, structuredOutput, discoveredTools, }: {
114
+ constructor({ agentId, name, description, provider, clientOptions, maxContextTokens, streamBuffer, tokenCounter, tools, toolMap, toolRegistry, toolDefinitions, instructions, additionalInstructions, dynamicContext, reasoningKey, toolEnd, instructionTokens, useLegacyContent, structuredOutput, discoveredTools, }: {
113
115
  agentId: string;
114
116
  name?: string;
117
+ description?: string;
115
118
  provider: Providers;
116
119
  clientOptions?: t.ClientOptions;
117
120
  maxContextTokens?: number;
@@ -80,6 +80,14 @@ export declare class MultiAgentGraph extends StandardGraph {
80
80
  * @param sourceAgentName - The human-readable name of the source agent
81
81
  */
82
82
  private createHandoffToolsForEdge;
83
+ /**
84
+ * Builds a meaningful default description for a handoff tool when no explicit
85
+ * edge.description is provided. Uses the destination agent's name and description
86
+ * so the LLM can make informed routing decisions.
87
+ * @param destContext - AgentContext of the destination agent (may be undefined)
88
+ * @param destinationId - Raw agent ID (fallback when context unavailable)
89
+ */
90
+ private buildDefaultHandoffDescription;
83
91
  /**
84
92
  * Create a complete agent subgraph (similar to createReactAgent)
85
93
  */
@@ -347,6 +347,8 @@ export interface AgentInputs {
347
347
  agentId: string;
348
348
  /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
349
349
  name?: string;
350
+ /** Description of what this agent does (used to enrich handoff tool descriptions). */
351
+ description?: string;
350
352
  toolEnd?: boolean;
351
353
  toolMap?: ToolMap;
352
354
  tools?: GraphTools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illuma-ai/agents",
3
- "version": "1.0.85",
3
+ "version": "1.0.86",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -29,6 +29,7 @@ export class AgentContext {
29
29
  const {
30
30
  agentId,
31
31
  name,
32
+ description,
32
33
  provider,
33
34
  clientOptions,
34
35
  tools,
@@ -69,6 +70,7 @@ export class AgentContext {
69
70
  const agentContext = new AgentContext({
70
71
  agentId,
71
72
  name: name ?? agentId,
73
+ description,
72
74
  provider,
73
75
  clientOptions,
74
76
  maxContextTokens,
@@ -121,6 +123,8 @@ export class AgentContext {
121
123
  agentId: string;
122
124
  /** Human-readable name for this agent (used in handoff context). Falls back to agentId if not provided. */
123
125
  name?: string;
126
+ /** Description of what this agent does (used to enrich handoff tool descriptions). */
127
+ description?: string;
124
128
  /** Provider for this specific agent */
125
129
  provider: Providers;
126
130
  /** Client options for this agent */
@@ -237,6 +241,7 @@ export class AgentContext {
237
241
  constructor({
238
242
  agentId,
239
243
  name,
244
+ description,
240
245
  provider,
241
246
  clientOptions,
242
247
  maxContextTokens,
@@ -258,6 +263,7 @@ export class AgentContext {
258
263
  }: {
259
264
  agentId: string;
260
265
  name?: string;
266
+ description?: string;
261
267
  provider: Providers;
262
268
  clientOptions?: t.ClientOptions;
263
269
  maxContextTokens?: number;
@@ -279,6 +285,7 @@ export class AgentContext {
279
285
  }) {
280
286
  this.agentId = agentId;
281
287
  this.name = name;
288
+ this.description = description;
282
289
  this.provider = provider;
283
290
  this.clientOptions = clientOptions;
284
291
  this.maxContextTokens = maxContextTokens;
@@ -362,8 +362,9 @@ export class MultiAgentGraph extends StandardGraph {
362
362
  /** Create individual tools for each destination */
363
363
  for (const destination of destinations) {
364
364
  const toolName = `${Constants.LC_TRANSFER_TO_}${destination}`;
365
+ const destContext = this.agentContexts.get(destination);
365
366
  const toolDescription =
366
- edge.description ?? `Transfer control to agent '${destination}'`;
367
+ edge.description ?? this.buildDefaultHandoffDescription(destContext, destination);
367
368
 
368
369
  /** Check if we have a prompt for handoff input */
369
370
  const hasHandoffInput =
@@ -496,6 +497,26 @@ export class MultiAgentGraph extends StandardGraph {
496
497
  return tools;
497
498
  }
498
499
 
500
+ /**
501
+ * Builds a meaningful default description for a handoff tool when no explicit
502
+ * edge.description is provided. Uses the destination agent's name and description
503
+ * so the LLM can make informed routing decisions.
504
+ * @param destContext - AgentContext of the destination agent (may be undefined)
505
+ * @param destinationId - Raw agent ID (fallback when context unavailable)
506
+ */
507
+ private buildDefaultHandoffDescription(
508
+ destContext: import('@/agents/AgentContext').AgentContext | undefined,
509
+ destinationId: string
510
+ ): string {
511
+ const displayName = destContext?.name ?? destinationId;
512
+ const agentDescription = destContext?.description;
513
+
514
+ if (agentDescription) {
515
+ return `Transfer to "${displayName}": ${agentDescription}`;
516
+ }
517
+ return `Transfer control to "${displayName}"`;
518
+ }
519
+
499
520
  /**
500
521
  * Create a complete agent subgraph (similar to createReactAgent)
501
522
  */
@@ -885,4 +885,178 @@ describe('Agent Handoffs Tests', () => {
885
885
  );
886
886
  });
887
887
  });
888
+
889
+ describe('Handoff Tool Description Enrichment', () => {
890
+ it('should use agent name and description when edge.description is not provided', async () => {
891
+ const agents: t.AgentInputs[] = [
892
+ createBasicAgent('supervisor', 'You are a supervisor'),
893
+ {
894
+ ...createBasicAgent('data_analyst', 'You analyze data'),
895
+ name: 'Data Analyst',
896
+ description: 'Analyzes datasets, creates charts, and provides statistical insights',
897
+ },
898
+ ];
899
+
900
+ const edges: t.GraphEdge[] = [
901
+ {
902
+ from: 'supervisor',
903
+ to: 'data_analyst',
904
+ edgeType: 'handoff',
905
+ // No description provided - should auto-generate from agent name + description
906
+ },
907
+ ];
908
+
909
+ const run = await Run.create(createTestConfig(agents, edges));
910
+
911
+ const supervisorContext = (run.Graph as StandardGraph).agentContexts.get(
912
+ 'supervisor'
913
+ );
914
+ const handoffTool = findToolByName(
915
+ supervisorContext?.tools,
916
+ `${Constants.LC_TRANSFER_TO_}data_analyst`
917
+ );
918
+
919
+ expect(handoffTool).toBeDefined();
920
+ const description = getToolDescription(handoffTool!);
921
+ expect(description).toContain('Data Analyst');
922
+ expect(description).toContain('Analyzes datasets');
923
+ });
924
+
925
+ it('should use agent name without description when only name is available', async () => {
926
+ const agents: t.AgentInputs[] = [
927
+ createBasicAgent('supervisor', 'You are a supervisor'),
928
+ {
929
+ ...createBasicAgent('writer', 'You write things'),
930
+ name: 'Content Writer',
931
+ // No description field
932
+ },
933
+ ];
934
+
935
+ const edges: t.GraphEdge[] = [
936
+ {
937
+ from: 'supervisor',
938
+ to: 'writer',
939
+ edgeType: 'handoff',
940
+ },
941
+ ];
942
+
943
+ const run = await Run.create(createTestConfig(agents, edges));
944
+
945
+ const supervisorContext = (run.Graph as StandardGraph).agentContexts.get(
946
+ 'supervisor'
947
+ );
948
+ const handoffTool = findToolByName(
949
+ supervisorContext?.tools,
950
+ `${Constants.LC_TRANSFER_TO_}writer`
951
+ );
952
+
953
+ expect(handoffTool).toBeDefined();
954
+ const description = getToolDescription(handoffTool!);
955
+ expect(description).toContain('Content Writer');
956
+ });
957
+
958
+ it('should prefer explicit edge.description over auto-generated description', async () => {
959
+ const agents: t.AgentInputs[] = [
960
+ createBasicAgent('supervisor', 'You are a supervisor'),
961
+ {
962
+ ...createBasicAgent('agent_b', 'You do things'),
963
+ name: 'Agent B',
964
+ description: 'This is agent B description',
965
+ },
966
+ ];
967
+
968
+ const edges: t.GraphEdge[] = [
969
+ {
970
+ from: 'supervisor',
971
+ to: 'agent_b',
972
+ edgeType: 'handoff',
973
+ description: 'Custom handoff description that takes priority',
974
+ },
975
+ ];
976
+
977
+ const run = await Run.create(createTestConfig(agents, edges));
978
+
979
+ const supervisorContext = (run.Graph as StandardGraph).agentContexts.get(
980
+ 'supervisor'
981
+ );
982
+ const handoffTool = findToolByName(
983
+ supervisorContext?.tools,
984
+ `${Constants.LC_TRANSFER_TO_}agent_b`
985
+ );
986
+
987
+ expect(handoffTool).toBeDefined();
988
+ const description = getToolDescription(handoffTool!);
989
+ expect(description).toBe('Custom handoff description that takes priority');
990
+ });
991
+
992
+ it('should generate meaningful descriptions for multiple handoff targets', async () => {
993
+ const agents: t.AgentInputs[] = [
994
+ createBasicAgent('router', 'You route requests'),
995
+ {
996
+ ...createBasicAgent('sales', 'Handle sales'),
997
+ name: 'Sales Agent',
998
+ description: 'Handles product pricing, quotes, and purchase orders',
999
+ },
1000
+ {
1001
+ ...createBasicAgent('support', 'Handle support'),
1002
+ name: 'Support Agent',
1003
+ description: 'Troubleshoots technical issues and handles bug reports',
1004
+ },
1005
+ {
1006
+ ...createBasicAgent('billing', 'Handle billing'),
1007
+ name: 'Billing Agent',
1008
+ description: 'Manages invoices, payments, and subscription changes',
1009
+ },
1010
+ ];
1011
+
1012
+ const edges: t.GraphEdge[] = [
1013
+ { from: 'router', to: 'sales', edgeType: 'handoff' },
1014
+ { from: 'router', to: 'support', edgeType: 'handoff' },
1015
+ { from: 'router', to: 'billing', edgeType: 'handoff' },
1016
+ ];
1017
+
1018
+ const run = await Run.create(createTestConfig(agents, edges));
1019
+
1020
+ const routerContext = (run.Graph as StandardGraph).agentContexts.get(
1021
+ 'router'
1022
+ );
1023
+
1024
+ const salesTool = findToolByName(
1025
+ routerContext?.tools,
1026
+ `${Constants.LC_TRANSFER_TO_}sales`
1027
+ );
1028
+ const supportTool = findToolByName(
1029
+ routerContext?.tools,
1030
+ `${Constants.LC_TRANSFER_TO_}support`
1031
+ );
1032
+ const billingTool = findToolByName(
1033
+ routerContext?.tools,
1034
+ `${Constants.LC_TRANSFER_TO_}billing`
1035
+ );
1036
+
1037
+ // Each tool should have a meaningful description from the target agent
1038
+ expect(getToolDescription(salesTool!)).toContain('pricing');
1039
+ expect(getToolDescription(supportTool!)).toContain('Troubleshoots');
1040
+ expect(getToolDescription(billingTool!)).toContain('invoices');
1041
+ });
1042
+
1043
+ it('should store description in AgentContext', async () => {
1044
+ const agents: t.AgentInputs[] = [
1045
+ {
1046
+ ...createBasicAgent('agent_a', 'Instructions for A'),
1047
+ name: 'Agent Alpha',
1048
+ description: 'Handles alpha tasks',
1049
+ },
1050
+ ];
1051
+
1052
+ const edges: t.GraphEdge[] = [];
1053
+
1054
+ const run = await Run.create(createTestConfig(agents, edges));
1055
+
1056
+ const context = (run.Graph as StandardGraph).agentContexts.get('agent_a');
1057
+ expect(context).toBeDefined();
1058
+ expect(context?.name).toBe('Agent Alpha');
1059
+ expect(context?.description).toBe('Handles alpha tasks');
1060
+ });
1061
+ });
888
1062
  });
@@ -481,6 +481,8 @@ export interface AgentInputs {
481
481
  agentId: string;
482
482
  /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
483
483
  name?: string;
484
+ /** Description of what this agent does (used to enrich handoff tool descriptions). */
485
+ description?: string;
484
486
  toolEnd?: boolean;
485
487
  toolMap?: ToolMap;
486
488
  tools?: GraphTools;