UNPKG

@openai/agents-core

Version:

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

590 lines • 29.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Agent = void 0; const lifecycle_1 = require("./lifecycle.js"); const mcp_1 = require("./mcp.js"); const defaultModel_1 = require("./defaultModel.js"); const runContext_1 = require("./runContext.js"); const tool_1 = require("./tool.js"); const handoff_1 = require("./handoff.js"); const run_1 = require("./run.js"); const runState_1 = require("./runState.js"); const agentToolConfiguration_1 = require("./agentToolConfiguration.js"); const modelSettings_1 = require("./runner/modelSettings.js"); const tools_1 = require("./utils/tools.js"); const messages_1 = require("./utils/messages.js"); const typeGuards_1 = require("./utils/typeGuards.js"); const abortSignals_1 = require("./utils/abortSignals.js"); const errors_1 = require("./errors.js"); const logger_1 = __importDefault(require("./logger.js")); const agentToolInput_1 = require("./agentToolInput.js"); const agentToolRunConfig_1 = require("./agentToolRunConfig.js"); const standardSchema_1 = require("./utils/standardSchema.js"); const strictToolSchema_1 = require("./utils/strictToolSchema.js"); const agentToolRunResults_1 = require("./agentToolRunResults.js"); const agentToolSourceRegistry_1 = require("./agentToolSourceRegistry.js"); const usageTracking_1 = require("./runner/usageTracking.js"); const invocationContext_1 = require("./runner/invocationContext.js"); const agentToolStream_1 = require("./runner/agentToolStream.js"); const agentOutputTypeWarning_1 = require("./agentOutputTypeWarning.js"); function getInitialModelSettingsForAgentModel(model) { if (model === undefined || model === Agent.DEFAULT_MODEL_PLACEHOLDER) { return (0, defaultModel_1.getDefaultModelSettings)(); } if (typeof model === 'string' && model !== Agent.DEFAULT_MODEL_PLACEHOLDER) { return (0, defaultModel_1.getDefaultModelSettings)(model); } return {}; } /** * The class representing an AI agent configured with instructions, tools, guardrails, handoffs and more. * * We strongly recommend passing `instructions`, which is the "system prompt" for the agent. In * addition, you can pass `handoffDescription`, which is a human-readable description of the * agent, used when the agent is used inside tools/handoffs. * * Agents are generic on the context type. The context is a (mutable) object you create. It is * passed to tool functions, handoffs, guardrails, etc. */ class Agent extends lifecycle_1.AgentHooks { /** * Create an Agent with handoffs and automatically infer the union type for TOutput from the handoff agents' output types. */ static create(config) { return new Agent({ ...config, handoffs: config.handoffs, outputType: config.outputType, handoffOutputTypeWarningEnabled: false, }); } static DEFAULT_MODEL_PLACEHOLDER = ''; name; instructions; prompt; handoffDescription; handoffs; model; modelSettings; tools; mcpServers; mcpConfig; inputGuardrails; outputGuardrails; outputType = 'text'; toolUseBehavior; resetToolChoice; _toolsExplicitlyConfigured; _modelSettingsExplicitlyConfigured; constructor(config) { super(); if (typeof config.name !== 'string' || config.name.trim() === '') { throw new errors_1.UserError('Agent must have a name.'); } this.name = config.name; this.instructions = config.instructions ?? Agent.DEFAULT_MODEL_PLACEHOLDER; this.prompt = config.prompt; this.handoffDescription = config.handoffDescription ?? ''; this.handoffs = config.handoffs ?? []; this.model = config.model ?? ''; this._modelSettingsExplicitlyConfigured = config.modelSettings !== undefined; this.modelSettings = config.modelSettings ?? getInitialModelSettingsForAgentModel(config.model); this.tools = config.tools ?? []; this._toolsExplicitlyConfigured = config.tools !== undefined; this.mcpServers = config.mcpServers ?? []; this.mcpConfig = config.mcpConfig ?? {}; this.inputGuardrails = config.inputGuardrails ?? []; this.outputGuardrails = config.outputGuardrails ?? []; if (config.outputType) { if (!(0, typeGuards_1.isZodObject)(config.outputType) && (0, standardSchema_1.hasStandardSchemaMarker)(config.outputType) && !(0, standardSchema_1.isStandardSchemaWithJSON)(config.outputType)) { throw (0, standardSchema_1.unsupportedStandardSchemaError)(); } if (!(0, typeGuards_1.isZodObject)(config.outputType) && (0, standardSchema_1.isStandardSchemaWithJSON)(config.outputType)) { const inputSchema = (0, standardSchema_1.standardSchemaToJsonSchema)(config.outputType, 'input'); (0, standardSchema_1.assertStandardSchemaObjectRoot)(inputSchema, 'Agent output'); (0, strictToolSchema_1.prepareOpenAIStrictToolSchema)(inputSchema); } this.outputType = config.outputType; } this.toolUseBehavior = config.toolUseBehavior ?? 'run_llm_again'; this.resetToolChoice = config.resetToolChoice ?? true; if ( // The user sets a non-default model. config.model !== undefined && config.model !== Agent.DEFAULT_MODEL_PLACEHOLDER && // The default model uses GPT-5-and-newer settings. (0, defaultModel_1.isGpt5OrNewerDefault)() && // The specified model is outside that settings family. (typeof config.model !== 'string' || !(0, defaultModel_1.isGpt5OrNewerReasoningModel)(config.model)) && // The model settings are not customized for the specified model. config.modelSettings === undefined) { // Avoid applying reasoning-family defaults to an unrelated model. this.modelSettings = {}; } // --- Runtime warning for handoff output type compatibility --- if (config.handoffOutputTypeWarningEnabled === undefined || config.handoffOutputTypeWarningEnabled) { if (this.handoffs && this.outputType) { const outputTypes = [this.outputType]; for (const h of this.handoffs) { if ('outputType' in h && h.outputType) { outputTypes.push(h.outputType); } else if ('agent' in h && h.agent.outputType) { outputTypes.push(h.agent.outputType); } } if (logger_1.default.dontLogModelData) { if ((0, agentOutputTypeWarning_1.hasDefinitelyDifferentOutputTypes)(outputTypes)) { logger_1.default.warn('[Agent] Warning: Handoff agents have different output types. Output type details are redacted. You can make it type-safe by using Agent.create({ ... }) method instead.'); } } else { const serializedOutputTypes = outputTypes.map((type) => JSON.stringify(type)); if (new Set(serializedOutputTypes).size > 1) { logger_1.default.warn(`[Agent] Warning: Handoff agents have different output types: ${serializedOutputTypes.join(', ')}. You can make it type-safe by using Agent.create({ ... }) method instead.`); } } } } } /** @internal */ hasExplicitModelSettings() { return this._modelSettingsExplicitlyConfigured; } /** * Output schema name. */ get outputSchemaName() { if (this.outputType === 'text') { return 'text'; } else if ((0, typeGuards_1.isZodObject)(this.outputType)) { return 'ZodOutput'; } else if ((0, standardSchema_1.isStandardSchemaWithJSON)(this.outputType)) { return 'StandardSchemaOutput'; } else if (typeof this.outputType === 'object') { return this.outputType.name; } throw new Error(`Unknown output type: ${this.outputType}`); } /** * Makes a copy of the agent, with the given arguments changed. For example, you could do: * * ``` * const newAgent = agent.clone({ instructions: 'New instructions' }) * ``` * * This method never copies a list property such as `tools`, `handoffs`, `mcpServers`, * `inputGuardrails`, or `outputGuardrails`. It merges `config` over this agent's current * property values and constructs a new agent from the result, so each list is whatever that * merged configuration holds. Two consequences follow: * * - A property `config` leaves out arrives as the original agent's own array, which both agents * then share, entries included. * - A property `config` includes is taken exactly as given, so it shares an array or an entry * with the original agent only where you reused one. Including the property with the value * `undefined` still counts as including it, and the new agent starts that list empty instead * of inheriting it. * * Because two agents can hold one array, mutating it through either one also changes the other. * After `const clonedAgent = agent.clone({ name: 'Clone' })`, calling * `clonedAgent.tools.push(extraTool)` adds that tool to `agent` as well. To give the clone a * list that no other agent holds, pass a new array: * * ``` * const newAgent = agent.clone({ tools: [...agent.tools, extraTool] }) * ``` * * The entries copied into that new array, such as tool and handoff objects, remain the same * objects the original agent holds. * * @param config - A partial configuration to change. * @returns A new agent with the given changes. */ clone(config) { const modelSettings = 'modelSettings' in config ? config.modelSettings : this.hasExplicitModelSettings() ? this.modelSettings : undefined; const cloned = new Agent({ ...this, ...config, modelSettings, }); if (!('modelSettings' in config) && !this.hasExplicitModelSettings() && Object.prototype.hasOwnProperty.call(this.modelSettings, 'store')) { // Preserve storage policy without making generated model defaults explicit. cloned.modelSettings.store = this.modelSettings.store; } return cloned; } asTool(options) { const { toolName, toolDescription, customOutputExtractor, needsApproval, parameters, inputBuilder, includeInputSchema, runConfig, runOptions, resumeState, isEnabled, onStream, onStreamMaxPendingEvents = 1024, } = options; if (onStreamMaxPendingEvents !== null && (!Number.isSafeInteger(onStreamMaxPendingEvents) || onStreamMaxPendingEvents <= 0)) { throw new errors_1.UserError('onStreamMaxPendingEvents must be a positive safe integer or null.'); } // Event handlers are scoped to this agent tool instance and are not shared; we only support registration (no removal) to keep the API surface small. const eventHandlers = new Map(); const emitEvent = async (event) => { // We intentionally keep only add semantics (no off) to reduce surface area; handlers are scoped to this agent tool instance. const specific = eventHandlers.get(event.event.type); const wildcard = eventHandlers.get('*'); const candidates = [ ...(onStream ? [onStream] : []), ...(specific ? Array.from(specific) : []), ...(wildcard ? Array.from(wildcard) : []), ]; // Run all handlers in parallel so a slow onStream callback does not block on(...) handlers (and vice versa). await Promise.allSettled(candidates.map((handler) => Promise.resolve().then(() => handler(event)))); }; const resolvedToolName = toolName ?? (0, tools_1.toFunctionToolName)(this.name); const toolParameters = (parameters ?? agentToolInput_1.AgentAsToolInputSchema); const hasCustomParameters = typeof parameters !== 'undefined'; const includeSchema = includeInputSchema === true && hasCustomParameters; const shouldCaptureToolInput = hasCustomParameters || includeSchema || typeof inputBuilder === 'function'; const schemaInfo = shouldCaptureToolInput ? (0, agentToolInput_1.buildStructuredInputSchemaInfo)(toolParameters, resolvedToolName, includeSchema) : undefined; const baseTool = (0, tool_1.tool)({ name: resolvedToolName, description: toolDescription ?? '', parameters: toolParameters, strict: true, needsApproval: needsApproval, isEnabled, execute: async (params, context, details) => { const typedParams = params; const runContextBase = runOptions?.context instanceof runContext_1.RunContext ? runOptions.context : typeof runOptions?.context !== 'undefined' ? new runContext_1.RunContext(runOptions.context) : context instanceof runContext_1.RunContext ? context : typeof context !== 'undefined' ? new runContext_1.RunContext(context) : new runContext_1.RunContext(); const agentToolInvocation = { toolName: details?.toolCall?.name ?? baseTool.name, toolCallId: details?.toolCall?.callId, toolArguments: details?.toolCall?.arguments, }; const shouldClearToolInput = !shouldCaptureToolInput && typeof runContextBase.toolInput !== 'undefined'; const runContext = shouldCaptureToolInput && typeof runContextBase._forkWithToolInput === 'function' ? runContextBase._forkWithToolInput(typedParams) : shouldClearToolInput && typeof runContextBase._forkWithoutToolInput === 'function' ? runContextBase._forkWithoutToolInput() : runContextBase; const resolvedInput = await (0, agentToolInput_1.resolveAgentToolInput)({ params: typedParams, schemaInfo, inputBuilder, }); if (typeof resolvedInput !== 'string' && !Array.isArray(resolvedInput)) { throw new errors_1.ModelBehaviorError('Agent tool called with invalid input'); } const inheritedRunConfig = (0, agentToolRunConfig_1.getInheritedAgentToolRunConfig)((0, agentToolRunConfig_1.getAgentToolParentRunConfigFromDetails)(details), runConfig, (0, modelSettings_1.selectModel)(this.model, runConfig?.model)); const nestedRunConfig = (0, agentToolRunConfig_1.mergeAgentToolRunConfig)(inheritedRunConfig, runConfig); const runner = new run_1.Runner(nestedRunConfig); (0, usageTracking_1.setRunnerParentUsageRecorder)(runner, (0, usageTracking_1.getToolUsageRecorder)(details)); (0, invocationContext_1.setRunnerInvocationSpanParent)(runner, (0, agentToolRunConfig_1.getToolCallParentSpanFromDetails)(details)); const resumeContextStrategy = resumeState?.contextStrategy ?? 'merge'; const resumeContext = resumeContextStrategy === 'preferSerialized' ? undefined : runContext; let runInput = resolvedInput; if (details?.resumeState) { if (resumeContextStrategy === 'preferSerialized' || !resumeContext) { runInput = await runState_1.RunState.fromString(this, details.resumeState); } else { if (resumeContextStrategy === 'merge' && context && resumeContext !== context) { resumeContext._mergeApprovalState(context); } runInput = await runState_1.RunState.fromStringWithContext(this, details.resumeState, resumeContext, { contextStrategy: resumeContextStrategy === 'replace' ? 'replace' : 'merge', }); } } // Only flip to streaming mode when a handler is provided to avoid extra overhead for callers that do not need events. // Flip to streaming if either a legacy onStream callback or event handlers are registered; otherwise stay on the non-stream path to avoid extra overhead. const shouldStream = typeof onStream === 'function' || eventHandlers.size > 0; const configuredSignal = runOptions?.signal; const toolCallSignal = details?.signal; const streamController = shouldStream ? new AbortController() : undefined; if (streamController && onStreamMaxPendingEvents !== null) { (0, agentToolStream_1.setAgentToolStreamBuffer)(runner, (0, agentToolStream_1.createAgentToolStreamBuffer)(onStreamMaxPendingEvents, streamController)); } const { signal: combinedSignal, cleanup: cleanupSignalListeners } = streamController || (configuredSignal && toolCallSignal) ? (0, abortSignals_1.combineAbortSignals)(configuredSignal, toolCallSignal, streamController?.signal) : { signal: toolCallSignal ?? configuredSignal, cleanup: () => { }, }; const runOptionsWithContext = { ...(runOptions ?? {}), context: runContext, ...(combinedSignal ? { signal: combinedSignal } : {}), }; try { const result = shouldStream ? await runner.run(this, runInput, { ...runOptionsWithContext, stream: true, }) : await runner.run(this, runInput, { ...runOptionsWithContext, }); const streamPayload = { agent: this, toolCall: details?.toolCall, }; if (shouldStream) { // Cast through unknown: the async iterator shape matches and we want to drain the stream for side effects while keeping the public API stable. const streamResult = result; // Drain the stream to deliver every event to registered handlers; ensure completion awaited so the nested run finishes before returning. await (0, agentToolStream_1.drainAgentToolStream)(streamResult, (event) => emitEvent({ event, ...streamPayload }), combinedSignal, streamController); if (streamResult.cancelled) { const currentStep = streamResult.state._currentStep; const hasCommittedOutcome = (currentStep?.type === 'next_step_final_output' && streamResult.state._currentTurnInProgress === false) || (streamResult.interruptions?.length ?? 0) > 0; if (!hasCommittedOutcome) { combinedSignal?.throwIfAborted(); throw new Error('Nested agent run was cancelled.'); } } } const completedResult = result; if (completedResult.state instanceof runState_1.RunState) { completedResult.state._agentToolInvocation = agentToolInvocation; } const completedResultWithAgentToolInvocation = completedResult; const usesStopAtToolNames = typeof this.toolUseBehavior === 'object' && this.toolUseBehavior !== null && 'stopAtToolNames' in this.toolUseBehavior; if (typeof customOutputExtractor !== 'function' && usesStopAtToolNames) { logger_1.default.debug(`You're passing the agent (name: ${this.name}) with toolUseBehavior.stopAtToolNames configured as a tool to a different agent; this may not work as you expect. You may want to have a wrapper function tool to consistently return the final output.`); } let outputText; if (typeof customOutputExtractor === 'function') { outputText = await customOutputExtractor(completedResultWithAgentToolInvocation); } else if ((completedResult.interruptions?.length ?? 0) > 0) { outputText = ''; } else { const outputGuardrailResult = completedResult.outputGuardrailResults?.at(-1); // Reuse the checked value; reading finalOutput can rerun schema transforms. const finalOutput = outputGuardrailResult ? outputGuardrailResult.agentOutput : completedResult.finalOutput; const finalOutputType = outputGuardrailResult ? outputGuardrailResult.agent.outputType : this.outputType; const finalOutputText = typeof finalOutput !== 'undefined' ? finalOutputType === 'text' ? String(finalOutput) : JSON.stringify(finalOutput) : undefined; const rawResponses = completedResult.rawResponses; const rawOutputText = rawResponses && rawResponses.length > 0 ? (0, messages_1.getOutputText)(rawResponses[rawResponses.length - 1]) : undefined; const normalizedRawOutputText = typeof rawOutputText === 'string' && rawOutputText.trim() === '' ? undefined : rawOutputText; const prefersFinalOutput = completedResult.state?._finalOutputSource === 'error_handler' || outputGuardrailResult !== undefined; outputText = prefersFinalOutput ? (finalOutputText ?? normalizedRawOutputText ?? '') : (normalizedRawOutputText ?? finalOutputText ?? ''); } if (details?.toolCall) { (0, agentToolRunResults_1.saveAgentToolRunResult)(details.toolCall, completedResultWithAgentToolInvocation); } return outputText; } finally { cleanupSignalListeners(); } }, }); const agentTool = { ...baseTool, on: (name, handler) => { const set = eventHandlers.get(name) ?? new Set(); set.add(handler); eventHandlers.set(name, set); return agentTool; }, }; (0, agentToolSourceRegistry_1.registerAgentToolSourceAgent)(agentTool, this); return agentTool; } /** * Returns the system prompt for the agent. * * If the agent has a function as its instructions, this function will be called with the * runContext and the agent instance. */ async getSystemPrompt(runContext) { if (typeof this.instructions === 'function') { return await this.instructions(runContext, this); } return this.instructions; } /** * Returns the prompt template for the agent, if defined. * * If the agent has a function as its prompt, this function will be called with the * runContext and the agent instance. */ async getPrompt(runContext) { if (typeof this.prompt === 'function') { return await this.prompt(runContext, this); } return this.prompt; } /** * Fetches the available tools from the MCP servers. * @returns the MCP powered tools */ async getMcpTools(runContext, tracingParent) { if (this.mcpServers.length > 0) { const includeServerInToolNames = this.mcpConfig.includeServerInToolNames === true; return (0, mcp_1.getAllMcpTools)({ mcpServers: this.mcpServers, runContext, agent: (0, agentToolConfiguration_1.getPublicAgent)(this), convertSchemasToStrict: this.mcpConfig.convertSchemasToStrict === true, errorFunction: this.mcpConfig.errorFunction, includeServerInToolNames, reservedToolNames: includeServerInToolNames ? await this.getMcpToolReservedNames(runContext) : undefined, tracingParent, }); } return []; } async getMcpToolReservedNames(runContext) { const reservedToolNames = new Set(this.tools .filter((tool) => tool.type === 'function') .map((tool) => tool.name)); const enabledHandoffs = await this.getEnabledHandoffs(runContext); for (const handoff of enabledHandoffs) { reservedToolNames.add(handoff.toolName); } return reservedToolNames; } /** * All agent tools, including the MCP and function tools. * * @returns all configured tools */ async getAllTools(runContext, tracingParent) { const mcpTools = await this.getMcpTools(runContext, tracingParent); const enabledTools = []; for (const candidate of [...this.tools]) { if (candidate.type === 'function') { const maybeIsEnabled = candidate.isEnabled; const enabled = typeof maybeIsEnabled === 'function' ? await maybeIsEnabled(runContext, (0, agentToolConfiguration_1.getPublicAgent)(this)) : typeof maybeIsEnabled === 'boolean' ? maybeIsEnabled : true; if (!enabled) { continue; } } enabledTools.push(candidate); } return [...mcpTools, ...enabledTools]; } hasExplicitToolConfig() { return this._toolsExplicitlyConfigured; } /** * Returns the handoffs that should be exposed to the model for the current run. * * Handoffs that provide an `isEnabled` function returning `false` are omitted. */ async getEnabledHandoffs(runContext) { const handoffs = this.handoffs?.map((h) => (0, handoff_1.getHandoff)(h)) ?? []; const enabled = []; for (const handoff of handoffs) { if (await handoff.isEnabled({ runContext, agent: this })) { enabled.push(handoff); } } return enabled; } /** * Processes the final output of the agent. * * @param output - The output of the agent. * @returns The parsed out. */ processFinalOutput(output) { if (this.outputType === 'text') { return output; } if (typeof this.outputType === 'object' || typeof this.outputType === 'function') { const parsed = JSON.parse(output); if ((0, typeGuards_1.isZodObject)(this.outputType)) { return this.outputType.parse(parsed); } if ((0, standardSchema_1.isStandardSchemaWithJSON)(this.outputType)) { const inputSchema = (0, standardSchema_1.standardSchemaToJsonSchema)(this.outputType, 'input'); (0, standardSchema_1.assertStandardSchemaObjectRoot)(inputSchema, 'Agent output'); const normalized = (0, strictToolSchema_1.prepareOpenAIStrictToolSchema)(inputSchema).normalizeInput(parsed); return (0, standardSchema_1.validateStandardSchema)(this.outputType, normalized); } if ((0, standardSchema_1.hasStandardSchemaMarker)(this.outputType)) { throw (0, standardSchema_1.unsupportedStandardSchemaError)(); } return parsed; } throw new Error(`Unknown output type: ${this.outputType}`); } /** * Returns a JSON representation of the agent, which is serializable. * * @returns A JSON object containing the agent's name. */ toJSON() { return { name: this.name, }; } } exports.Agent = Agent; //# sourceMappingURL=agent.js.map