@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
550 lines • 19.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamedRunResult = exports.RunResult = void 0;
const _shims_1 = require("@openai/agents-core/_shims");
const items_1 = require("./runner/items.js");
const logger_1 = __importStar(require("./logger.js"));
const protocol_1 = require("./types/protocol.js");
const abortSignals_1 = require("./utils/abortSignals.js");
const finalOutputError_1 = require("./utils/finalOutputError.js");
function createAbortHandlerRef() {
const ref = {};
const handler = () => {
ref.current?.();
};
return { ref, handler };
}
class RunResultBase {
state;
constructor(state) {
this.state = state;
}
/**
* The history of the agent run. This includes the input items and the new items generated during
* the agent run.
*
* This can be used as inputs for the next agent run.
*/
get history() {
return (0, items_1.getTurnInput)(this.input, this.newItems, this.state._reasoningItemIdPolicy);
}
/**
* The new items generated during the agent run. These include things like new messages, tool
* calls and their outputs, etc.
*
* It does not include information about the agents and instead represents the model data.
*
* For the output including the agents, use the `newItems` property.
*/
get output() {
return (0, items_1.getRunOutput)(this.newItems, this.state._reasoningItemIdPolicy);
}
/**
* A copy of the original input items.
*/
get input() {
return this.state._originalInput;
}
/**
* The public run context for this run.
*/
get runContext() {
return this.state._context;
}
/**
* Metadata about the nested `Agent.asTool()` invocation that produced this result, when applicable.
*/
get agentToolInvocation() {
return this.state._agentToolInvocation;
}
/**
* The run items generated during the agent run. This associates the model data with the agents.
*
* For the model data that can be used as inputs for the next agent run, use the `output` property.
*/
get newItems() {
return this.state._generatedItems;
}
/**
* The raw LLM responses generated by the model during the agent run.
*/
get rawResponses() {
return this.state._modelResponses;
}
/**
* The last response ID generated by the model during the agent run.
*/
get lastResponseId() {
const responses = this.rawResponses;
return responses && responses.length > 0
? responses[responses.length - 1].responseId
: undefined;
}
/**
* The last agent that was run
*/
get lastAgent() {
return this.state._currentAgent;
}
/**
* The agent that should handle the next turn.
* This is an alias for the last agent that completed a turn.
*/
get activeAgent() {
return this.lastAgent;
}
/**
* Guardrail results for the input messages.
*/
get inputGuardrailResults() {
return this.state._inputGuardrailResults;
}
/**
* Guardrail results for the final output of the agent.
*/
get outputGuardrailResults() {
return this.state._outputGuardrailResults;
}
/**
* Guardrail results for tool inputs.
*/
get toolInputGuardrailResults() {
return this.state._toolInputGuardrailResults;
}
/**
* Guardrail results for tool outputs.
*/
get toolOutputGuardrailResults() {
return this.state._toolOutputGuardrailResults;
}
/**
* Any interruptions that occurred during the agent run for example for tool approvals.
*/
get interruptions() {
return this.state.getInterruptions();
}
/**
* The final output of the agent. If the output type was set to anything other than `text`,
* this will be parsed either as JSON or using the validation schema you provided.
*/
get finalOutput() {
if (this.state._currentStep?.type === 'next_step_final_output') {
const output = this.state._currentStep.output;
if (this.state._currentAgent.outputType === 'text') {
return this.state._currentAgent.processFinalOutput(output);
}
return (0, finalOutputError_1.processFinalOutputWithRedaction)(() => this.state._currentAgent.processFinalOutput(output));
}
logger_1.default.warn('Accessed finalOutput before agent run is completed.');
return undefined;
}
}
/**
* The result of an agent run.
*/
class RunResult extends RunResultBase {
constructor(state) {
super(state);
}
}
exports.RunResult = RunResult;
/**
* The result of an agent run in streaming mode.
*/
class StreamedRunResult extends RunResultBase {
#finalOutputHidden = false;
get finalOutput() {
if (this.#finalOutputHidden) {
logger_1.default.warn('Accessed finalOutput before agent run is completed.');
return undefined;
}
return super.finalOutput;
}
/** @internal */
_hideFinalOutput() {
this.#finalOutputHidden = true;
}
/** @internal */
_revealFinalOutput() {
this.#finalOutputHidden = false;
}
/**
* The current agent that is running
*/
get currentAgent() {
return this.lastAgent;
}
/**
* The number of model turns admitted so far.
*
* Written by the streaming runner at the point a turn is *admitted* -- after
* the `maxTurns` limit check and any blocking input guardrails have passed,
* immediately before the model request starts. It therefore counts turns that
* actually reached the model, not turns that were merely begun:
*
* - a handled max-turn boundary (e.g. `maxTurns: 0`) leaves this at `0`,
* because the limit check throws before any turn is admitted;
* - a blocking input guardrail that trips on the first turn leaves this at
* `0`, because no model request was made;
* - a resumed run starts from the turn count carried in the resumed state.
*
* `RunState._currentTurn` is incremented at the *start* of a turn, before
* either check. The runner rolls that increment back if the model request
* never starts, so resumed state carries only admitted turns.
*/
currentTurn = 0;
/**
* The maximum number of turns that can be run
*/
maxTurns;
#error = null;
#hasError = false;
#combinedSignal;
#abortSignalSnapshot;
#abortController;
#readableController;
#readableStream;
#queuedStreamEvents = [];
#queuedStreamEventIndex = 0;
#agentToolStreamBuffer;
#streamReadPending = false;
#streamTerminal;
#preserveQueuedItemsOnError = false;
#completedPromise;
#completedPromiseResolve;
#completedPromiseReject;
#cancelled = false;
#streamLoopPromise;
#abortHandler;
#abortHandlerRef;
#combinedSignalCleanup = () => { };
constructor(result = {}) {
super(result.state);
this.#finalOutputHidden =
result.state?._currentStep?.type === 'next_step_final_output';
// Seed from the resumed state so a run continued from a serialized state does
// not restart its public turn count at 0. A fresh run carries `_currentTurn = 0`
// here, so this is a no-op for the common case.
this.currentTurn = result.state?._currentTurn ?? 0;
this.#abortController = new AbortController();
const { signal: combinedSignal, cleanup: cleanupCombinedSignal } = (0, abortSignals_1.combineAbortSignalsWithOptions)([result.signal, this.#abortController.signal], {
onAbortSignalAnyError: (error) => {
(0, logger_1.logModelAndToolActionDebug)(logger_1.default, 'AbortSignal.any failed, falling back:', error);
},
});
this.#combinedSignal = combinedSignal;
this.#combinedSignalCleanup = cleanupCombinedSignal;
this.#abortSignalSnapshot = combinedSignal;
this.#readableStream = new _shims_1.ReadableStream({
start: (controller) => {
this.#readableController = controller;
},
pull: () => {
this.#streamReadPending = true;
this.#drainStreamQueue();
},
cancel: () => {
if (!this.#abortController.signal.aborted) {
this.#abortController.abort();
}
this.#cancelStream();
},
}, {
highWaterMark: 0,
});
this.#completedPromise = new Promise((resolve, reject) => {
this.#completedPromiseResolve = resolve;
this.#completedPromiseReject = reject;
});
if (this.#combinedSignal) {
// Create the handler outside this scope so it cannot capture the run via lexical this.
const { ref: abortRef, handler: handleAbort } = createAbortHandlerRef();
abortRef.current = () => {
this.#handleAbort();
};
this.#abortHandler = handleAbort;
this.#abortHandlerRef = abortRef;
if (this.#combinedSignal.aborted) {
handleAbort();
}
else {
this.#combinedSignal.addEventListener('abort', handleAbort, {
once: true,
});
}
}
}
/**
* @internal
* Adds an item to the stream of output items
*/
_addItem(item) {
if (!this.cancelled && !this.#streamTerminal) {
if (this.#agentToolStreamBuffer &&
this.#queuedStreamEvents.length - this.#queuedStreamEventIndex >=
this.#agentToolStreamBuffer.maxPendingEvents) {
this.#agentToolStreamBuffer.overflow();
}
this.#queuedStreamEvents.push(item);
this.#drainStreamQueue();
}
}
/** @internal Sets the agent-tool budget before its producer starts. */
_setAgentToolStreamBuffer(buffer) {
this.#agentToolStreamBuffer = buffer;
}
/**
* @internal
* Keeps already queued events available if later cleanup fails.
*/
_preserveQueuedItemsOnError() {
this.#preserveQueuedItemsOnError = true;
}
/**
* @internal
* Indicates that the stream has been completed
*/
_done() {
if (!this.cancelled) {
if (!this.#streamTerminal) {
this.#streamTerminal = { type: 'done' };
}
this.#drainStreamQueue();
}
this.#completedPromiseResolve?.();
this.#detachAbortHandler();
}
/**
* @internal
* Handles an error in the stream loop.
*/
_raiseError(err, options) {
if (!this.cancelled) {
const existingPreservedError = this.#streamTerminal?.type === 'error' &&
this.#streamTerminal.preserveQueuedItems
? this.#streamTerminal
: undefined;
const preserveQueuedItems = options?.preserveQueuedItems === true ||
Boolean(existingPreservedError) ||
this.#preserveQueuedItemsOnError;
this.#streamTerminal = {
type: 'error',
error: existingPreservedError ? existingPreservedError.error : err,
preserveQueuedItems,
};
if (!preserveQueuedItems) {
this.#clearStreamQueue();
if (this.#readableController) {
this.#readableController.error(err);
this.#readableController = undefined;
}
}
else {
this.#drainStreamQueue();
}
}
if (!this.#hasError) {
this.#hasError = true;
this.#error = err;
this.#completedPromiseReject?.(err);
this.#completedPromise.catch((e) => {
(0, logger_1.logModelAndToolActionDebug)(logger_1.default, 'Resulted in an error:', e);
});
}
this.#detachAbortHandler();
}
/**
* Returns true if the stream has been cancelled.
*/
get cancelled() {
return this.#cancelled;
}
/**
* Returns the underlying readable stream.
* @returns A readable stream of the agent run.
*/
toStream() {
return this.#readableStream;
}
/**
* Await this promise to ensure that the stream has been completed if you are not consuming the
* stream directly.
*/
get completed() {
return this.#completedPromise;
}
/**
* Error thrown during the run, if any.
*/
get error() {
return this.#error;
}
toTextStream(options = {}) {
const stream = this.#readableStream.pipeThrough(new _shims_1.TransformStream({
transform(event, controller) {
if (event.type === 'raw_model_stream_event' &&
event.data.type === 'output_text_delta') {
const item = protocol_1.StreamEventTextStream.parse(event.data);
controller.enqueue(item.delta);
}
},
}));
if (options.compatibleWithNodeStreams) {
return _shims_1.Readable.fromWeb(stream);
}
return stream;
}
[Symbol.asyncIterator]() {
return this.#readableStream[Symbol.asyncIterator]();
}
/**
* @internal
* Sets the stream loop promise that completes when the internal stream loop finishes.
* This is used to defer trace end until all agent work is complete.
*/
_setStreamLoopPromise(promise) {
this.#streamLoopPromise = promise;
}
/**
* @internal
* Returns a promise that resolves when the stream loop completes.
* This is used by the tracing system to wait for all agent work before ending the trace.
*/
_getStreamLoopPromise() {
return this.#streamLoopPromise;
}
/**
* @internal
* Returns the abort signal that should be used to cancel the streaming run.
*/
_getAbortSignal() {
return this.#abortSignalSnapshot ?? this.#combinedSignal;
}
#handleAbort() {
this.#cancelStream();
}
#cancelStream() {
if (this.#cancelled) {
this.#detachAbortHandler();
return;
}
this.#cancelled = true;
this.#clearStreamQueue();
this.#preserveQueuedItemsOnError = false;
this.#streamTerminal = { type: 'done' };
this.#streamReadPending = false;
const controller = this.#readableController;
this.#readableController = undefined;
if (controller) {
try {
controller.close();
}
catch (err) {
(0, logger_1.logModelAndToolActionDebug)(logger_1.default, 'Failed to close readable stream on abort:', err);
}
}
this.#detachAbortHandler();
}
#drainStreamQueue() {
const controller = this.#readableController;
if (!controller || !this.#streamReadPending) {
return;
}
if (this.#queuedStreamEventIndex < this.#queuedStreamEvents.length) {
const event = this.#queuedStreamEvents[this.#queuedStreamEventIndex];
if (this.#agentToolStreamBuffer) {
// Release delivered notifications without copying the pending backlog.
this.#queuedStreamEvents[this.#queuedStreamEventIndex] = undefined;
}
this.#queuedStreamEventIndex += 1;
this.#compactStreamQueue();
this.#streamReadPending = false;
controller.enqueue(event);
return;
}
if (this.#streamTerminal?.type === 'done') {
this.#streamReadPending = false;
controller.close();
this.#readableController = undefined;
return;
}
if (this.#streamTerminal?.type === 'error') {
this.#streamReadPending = false;
controller.error(this.#streamTerminal.error);
this.#readableController = undefined;
}
}
#clearStreamQueue() {
this.#queuedStreamEvents = [];
this.#queuedStreamEventIndex = 0;
}
#compactStreamQueue() {
if (this.#queuedStreamEventIndex === this.#queuedStreamEvents.length) {
this.#clearStreamQueue();
return;
}
if (this.#queuedStreamEventIndex >= 1024 &&
this.#queuedStreamEventIndex * 2 >= this.#queuedStreamEvents.length) {
this.#queuedStreamEvents = this.#queuedStreamEvents.slice(this.#queuedStreamEventIndex);
this.#queuedStreamEventIndex = 0;
}
}
#detachAbortHandler() {
// Clear the indirection first so a retained signal cannot keep this run alive.
if (this.#abortHandlerRef) {
this.#abortHandlerRef.current = undefined;
}
if (this.#combinedSignal && this.#abortHandler) {
try {
this.#combinedSignal.removeEventListener('abort', this.#abortHandler);
}
catch (err) {
(0, logger_1.logModelAndToolActionDebug)(logger_1.default, 'Failed to remove abort listener:', err);
}
}
try {
this.#combinedSignalCleanup();
}
catch (err) {
(0, logger_1.logModelAndToolActionDebug)(logger_1.default, 'Failed to clean up combined abort listeners:', err);
}
this.#combinedSignalCleanup = () => { };
this.#abortHandler = undefined;
this.#abortHandlerRef = undefined;
}
}
exports.StreamedRunResult = StreamedRunResult;
//# sourceMappingURL=result.js.map