@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
252 lines • 10.1 kB
JavaScript
import { ModelBehaviorError, UserError } from "./errors.mjs";
import { toFunctionToolName } from "./utils/tools.mjs";
import { getSchemaAndParserFromInputType } from "./utils/tools.mjs";
import { addErrorToCurrentSpan } from "./tracing/context.mjs";
import logger from "./logger.mjs";
import { isAbortError } from "./utils/abortSignals.mjs";
import { isAsyncStandardSchemaValidationError } from "./utils/standardSchema.mjs";
// Direct invocations retain their released parse error. Runner can propagate a fatal
// validator failure without attaching its potentially sensitive cause to that error.
const fatalHandoffInputErrors = new WeakMap();
/** @internal */
export function getFatalHandoffInputError(error) {
return error instanceof ModelBehaviorError
? fatalHandoffInputErrors.get(error)
: undefined;
}
/**
* Generates the message that will be given as tool output to the model that requested the handoff.
*
* @param agent The agent to transfer to
* @returns The message that will be given as tool output to the model that requested the handoff
*/
export function getTransferMessage(agent) {
return JSON.stringify({ assistant: agent.name });
}
/**
* The default name of the tool that represents the handoff.
*
* @param agent The agent to transfer to
* @returns The name of the tool that represents the handoff
*/
function defaultHandoffToolName(agent) {
return `transfer_to_${toFunctionToolName(agent.name)}`;
}
/**
* Generates the description of the tool that represents the handoff.
*
* @param agent The agent to transfer to
* @returns The description of the tool that represents the handoff
*/
function defaultHandoffToolDescription(agent) {
return `Handoff to the ${agent.name} agent to handle the request. ${agent.handoffDescription ?? ''}`;
}
export class Handoff {
/**
* The name of the tool that represents the handoff.
*/
toolName;
/**
* The description of the tool that represents the handoff.
*/
toolDescription;
/**
* The JSON schema for the handoff input. Can be empty if the handoff does not take an input
*/
inputJsonSchema = {
type: 'object',
properties: {},
required: [],
additionalProperties: false,
};
/**
* Whether the input JSON schema is in strict mode. We **strongly** recommend setting this to
* true, as it increases the likelihood of correct JSON input.
*/
strictJsonSchema = true;
/**
* The function that invokes the handoff. The parameters passed are:
* 1. The handoff run context
* 2. The arguments from the LLM, as a JSON string. Empty string if inputJsonSchema is empty.
*
* Must return an agent
*/
onInvokeHandoff;
/**
* The name of the agent that is being handed off to.
*/
agentName;
/**
* A function that filters the inputs that are passed to the next agent. By default, the new agent
* sees the entire conversation history. In some cases, you may want to filter inputs e.g. to
* remove older inputs, or remove tools from existing inputs.
*
* The function will receive the entire conversation history so far, including the input item
* that triggered the handoff and a tool call output item representing the handoff tool's output.
*
* You are free to modify the input history or new items as you see fit. The next agent that runs
* will receive `handoffInputData.allItems`.
* Filtered handoffs cannot use `conversationId` or `previousResponseId`, including no-op filters.
* Use client-managed history or a Session without either continuation option instead.
*/
inputFilter;
/**
* Lets Runner continue on the source agent after a failed invocation. Return only
* application-approved feedback for the model, or throw to propagate the failure.
* Omitting this callback propagates invocation errors. Cancellation, transfer hooks,
* input filters and Realtime handoffs retain their existing behavior. Recovery
* preserves any external effects already performed by the invocation.
*/
errorFunction;
/**
* The agent that is being handed off to.
*/
agent;
/**
* Returns a function tool definition that can be used to invoke the handoff.
*/
getHandoffAsFunctionTool() {
return {
type: 'function',
name: this.toolName,
description: this.toolDescription,
parameters: this.inputJsonSchema,
strict: this.strictJsonSchema,
};
}
/**
* Makes a copy of this handoff, with the given fields changed.
*
* Tool name and description default to the original handoff's values so adapters can replace
* the runtime target without changing the model-facing tool contract.
*/
clone(overrides = {}) {
const agent = overrides.agent ?? this.agent;
const onInvokeHandoff = overrides.onInvokeHandoff ??
(overrides.agent
? async (context, args) => {
await this.onInvokeHandoff(context, args);
return agent;
}
: this.onInvokeHandoff);
const cloned = new Handoff(agent, onInvokeHandoff);
cloned.agentName =
overrides.agentName ??
(overrides.agent ? overrides.agent.name : this.agentName);
cloned.toolName = overrides.toolName ?? this.toolName;
cloned.toolDescription = overrides.toolDescription ?? this.toolDescription;
cloned.inputJsonSchema = overrides.inputJsonSchema ?? this.inputJsonSchema;
cloned.strictJsonSchema =
overrides.strictJsonSchema ?? this.strictJsonSchema;
cloned.inputFilter = overrides.inputFilter ?? this.inputFilter;
cloned.errorFunction = overrides.errorFunction ?? this.errorFunction;
cloned.isEnabled = overrides.isEnabled ?? this.isEnabled;
return cloned;
}
isEnabled = async () => true;
constructor(agent, onInvokeHandoff) {
this.agentName = agent.name;
this.onInvokeHandoff = onInvokeHandoff;
this.toolName = defaultHandoffToolName(agent);
this.toolDescription = defaultHandoffToolDescription(agent);
this.agent = agent;
}
}
/**
* Creates a handoff from an agent. Handoffs are automatically created when you pass an agent
* into the `handoffs` option of the `Agent` constructor. Alternatively, you can use this function
* to create a handoff manually, giving you more control over configuration.
*
* @template TContext The context of the handoff
* @template TOutput The output type of the handoff
* @template TInputType The input type of the handoff
*/
export function handoff(agent, config = {}) {
let parser = undefined;
const inputType = config.inputType;
const hasInputType = inputType != null;
if (hasInputType && config.onHandoff == null) {
throw new UserError('You must provide `onHandoff` when `inputType` is provided.');
}
async function onInvokeHandoff(context, inputJsonString) {
if (parser) {
if (!inputJsonString) {
addErrorToCurrentSpan({
message: `Handoff function expected non empty input but got: ${inputJsonString}`,
data: {
details: `input is empty`,
},
});
throw new ModelBehaviorError('Handoff function expected non empty input');
}
let parsed;
try {
parsed = await parser(inputJsonString);
}
catch (error) {
addErrorToCurrentSpan({
message: `Invalid JSON provided`,
data: {},
});
if (!logger.dontLogToolData) {
logger.error(`Invalid JSON when parsing: ${inputJsonString}. Error: ${error}`);
}
const invalidInput = new ModelBehaviorError('Invalid JSON provided');
if (isAbortError(error) ||
isAsyncStandardSchemaValidationError(error)) {
fatalHandoffInputErrors.set(invalidInput, error);
}
throw invalidInput;
}
if (config.onHandoff) {
await config.onHandoff(context, parsed);
}
}
else {
await config.onHandoff?.(context);
}
return agent;
}
const handoff = new Handoff(agent, onInvokeHandoff);
handoff.errorFunction = config.errorFunction;
if (typeof config.isEnabled === 'function') {
const predicate = config.isEnabled;
handoff.isEnabled = async ({ runContext, agent }) => {
const result = await predicate({ runContext, agent });
return Boolean(result);
};
}
else if (typeof config.isEnabled === 'boolean') {
handoff.isEnabled = async () => config.isEnabled;
}
if (hasInputType) {
const result = getSchemaAndParserFromInputType(inputType, handoff.toolName, { strict: true });
handoff.inputJsonSchema = result.schema;
handoff.strictJsonSchema = true;
parser = result.parser;
}
if (config.toolNameOverride) {
handoff.toolName = config.toolNameOverride;
}
if (config.toolDescriptionOverride) {
handoff.toolDescription = config.toolDescriptionOverride;
}
if (config.inputFilter != null) {
handoff.inputFilter = config.inputFilter;
}
return handoff;
}
/**
* Returns a handoff for the given agent. If the agent is already wrapped into a handoff,
* it will be returned as is. Otherwise, a new handoff instance will be created.
*
* @template TContext The context of the handoff
* @template TOutput The output type of the handoff
*/
export function getHandoff(agent) {
if (agent instanceof Handoff) {
return agent;
}
return handoff(agent);
}
//# sourceMappingURL=handoff.mjs.map