UNPKG

@openai/agents-core

Version:

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

394 lines • 15.4 kB
import { getCurrentSpan, getCurrentTrace } from "./context.mjs"; import { supportsProcessLifecycleEvents } from '@openai/agents-core/_shims'; import { tracing } from "../config.mjs"; import logger, { logModelAndToolActionError } from "../logger.mjs"; import { MultiTracingProcessor } from "./processor.mjs"; import { NoopSpan, Span } from "./spans.mjs"; import { NoopTrace, Trace } from "./traces.mjs"; import { defaultTracingIdGenerator, NOOP_TRACE_OR_SPAN_ID, } from "./utils.mjs"; function resolveTracingIdGenerator(idGenerator) { return { generateTraceId: idGenerator?.generateTraceId ?? defaultTracingIdGenerator.generateTraceId, generateSpanId: idGenerator?.generateSpanId ?? defaultTracingIdGenerator.generateSpanId, }; } export class TraceProvider { #multiProcessor; #disabled; #shutdownPromise; #idGenerator; constructor(options = {}) { this.#multiProcessor = new MultiTracingProcessor(); this.#disabled = tracing.disabled; this.#shutdownPromise = null; this.#idGenerator = resolveTracingIdGenerator(options.idGenerator); this.#addCleanupListeners(); } /** * Add a processor to the list of processors. Each processor will receive all traces/spans. * * @param processor - The processor to add. */ registerProcessor(processor) { this.#shutdownPromise = null; this.#multiProcessor.addTraceProcessor(processor); } /** * Set the list of processors. This will replace any existing processors. * * @param processors - The list of processors to set. */ setProcessors(processors) { this.#shutdownPromise = null; this.#multiProcessor.setProcessors(processors); } /** * Get the current trace. * * @returns The current trace. */ getCurrentTrace() { return getCurrentTrace(); } getCurrentSpan() { return getCurrentSpan(); } setDisabled(disabled) { this.#disabled = disabled; } setIdGenerator(idGenerator) { this.#idGenerator = resolveTracingIdGenerator(idGenerator); } /** * Generate a new trace ID. * * Override this in custom providers to take precedence over configured ID generators. */ generateTraceId() { return this.#idGenerator.generateTraceId(); } /** * Generate a new span ID. * * Override this in custom providers to take precedence over configured ID generators. */ generateSpanId() { return this.#idGenerator.generateSpanId(); } startExportLoop() { this.#multiProcessor.start(); } /** * Dispatches a completed trace lifecycle to all registered processors. * * This is useful when an integration receives a trace that was started and * ended outside this process and needs to fan out the lifecycle without * mutating the trace state. */ async dispatchTrace(trace) { if (this.#disabled) { if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Tracing is disabled. Not dispatching trace.'); } else { logger.debug('Tracing is disabled, Not dispatching trace %o', trace); } return; } await this.#multiProcessor.dispatchTrace(trace); } /** * Dispatches a completed span lifecycle to all registered processors. * * This is useful when an integration receives a span that already has * lifecycle timestamps and needs to fan out the lifecycle without mutating * those timestamps. */ async dispatchSpan(span) { if (this.#disabled) { if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Tracing is disabled. Not dispatching span.'); } else { logger.debug('Tracing is disabled, Not dispatching span %o', span); } return; } await this.#multiProcessor.dispatchSpan(span); } /** * Dispatches a span start event to all registered processors. * * This is useful when an integration receives a span start outside this * process and needs to fan out the start without mutating the span state. */ async dispatchSpanStart(span) { if (this.#disabled) { if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Tracing is disabled. Not dispatching span start.'); } else { logger.debug('Tracing is disabled, Not dispatching span start %o', span); } return; } await this.#multiProcessor.dispatchSpanStart(span); } /** * Dispatches a span end event to all registered processors. * * This is useful when an integration receives a span end outside this process * and needs to fan out the end without mutating the span state. */ async dispatchSpanEnd(span) { if (this.#disabled) { if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Tracing is disabled. Not dispatching span end.'); } else { logger.debug('Tracing is disabled, Not dispatching span end %o', span); } return; } await this.#multiProcessor.dispatchSpanEnd(span); } createTrace(traceOptions) { if (this.#disabled) { if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Tracing is disabled. Not creating trace.'); } else { logger.debug('Tracing is disabled, Not creating trace %o', traceOptions); } return new NoopTrace(); } const traceId = traceOptions.traceId ?? this.generateTraceId(); const name = traceOptions.name ?? 'Agent workflow'; if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Creating trace. Trace data is redacted.'); } else { logger.debug('Creating trace %s with name %s', traceId, name); } return new Trace({ ...traceOptions, name, traceId }, this.#multiProcessor); } createSpan(spanOptions, parent) { if (this.#disabled || spanOptions.disabled) { if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Tracing is disabled. Not creating span.'); } else { logger.debug('Tracing is disabled, Not creating span %o', spanOptions); } return new NoopSpan(spanOptions.data, this.#multiProcessor); } if (spanOptions.spanId === NOOP_TRACE_OR_SPAN_ID) { logger.debug('Span id is no-op, returning NoopSpan'); return new NoopSpan(spanOptions.data, this.#multiProcessor); } let parentId; let traceId; let tracingApiKey; let traceMetadata; if (!parent) { const currentTrace = getCurrentTrace(); const currentSpan = getCurrentSpan(); if (!currentTrace) { logger.debug('No active trace. Make sure to start a trace with `withTrace()` first. Returning NoopSpan.'); return new NoopSpan(spanOptions.data, this.#multiProcessor); } if (currentSpan instanceof NoopSpan || currentSpan?.spanId === NOOP_TRACE_OR_SPAN_ID || currentTrace instanceof NoopTrace || currentTrace.traceId === NOOP_TRACE_OR_SPAN_ID) { logger.debug(`Parent ${currentSpan} or ${currentTrace} is no-op, returning NoopSpan`); return new NoopSpan(spanOptions.data, this.#multiProcessor); } traceId = currentTrace.traceId; tracingApiKey = currentTrace.tracingApiKey; traceMetadata = currentTrace.metadata; if (currentSpan) { logger.debug('Using parent span %s', currentSpan.spanId); parentId = currentSpan.spanId; } else { logger.debug('No parent span, using current trace %s', currentTrace.traceId); } } else if (parent instanceof Trace) { if (parent instanceof NoopTrace || parent.traceId === NOOP_TRACE_OR_SPAN_ID) { logger.debug('Parent trace is no-op, returning NoopSpan'); return new NoopSpan(spanOptions.data, this.#multiProcessor); } traceId = parent.traceId; tracingApiKey = parent.tracingApiKey; traceMetadata = parent.metadata; } else if (parent instanceof Span) { if (parent instanceof NoopSpan || parent.spanId === NOOP_TRACE_OR_SPAN_ID || parent.traceId === NOOP_TRACE_OR_SPAN_ID) { logger.debug('Parent span is no-op, returning NoopSpan'); return new NoopSpan(spanOptions.data, this.#multiProcessor); } parentId = parent.spanId; traceId = parent.traceId; tracingApiKey = parent.tracingApiKey; traceMetadata = parent.traceMetadata; } if (!traceId || traceId === NOOP_TRACE_OR_SPAN_ID) { logger.error('No traceId found. Make sure to start a trace with `withTrace()` first. Returning NoopSpan.'); return new NoopSpan(spanOptions.data, this.#multiProcessor); } const spanId = spanOptions.spanId ?? this.generateSpanId(); if (spanId === NOOP_TRACE_OR_SPAN_ID) { logger.debug('Span id is no-op, returning NoopSpan'); return new NoopSpan(spanOptions.data, this.#multiProcessor); } if (logger.dontLogModelData || logger.dontLogToolData) { logger.debug('Creating span. Span data is redacted.'); } else { logger.debug(`Creating span ${JSON.stringify(spanOptions.data)} with id ${spanId}`); } return new Span({ ...spanOptions, spanId, traceId, parentId, traceMetadata: traceMetadata ?? spanOptions.traceMetadata, tracingApiKey: tracingApiKey ?? spanOptions.tracingApiKey, }, this.#multiProcessor); } async shutdown(timeout) { if (!this.#shutdownPromise) { this.#shutdownPromise = (async () => { try { logger.debug('Shutting down tracing provider'); await this.#multiProcessor.shutdown(timeout); } catch (error) { logModelAndToolActionError(logger, 'Error shutting down tracing provider', error); } })(); } await this.#shutdownPromise; } /** Adds listeners to `process` to ensure `shutdown` occurs before exit. */ #addCleanupListeners() { if (supportsProcessLifecycleEvents() && typeof process !== 'undefined' && typeof process.on === 'function') { // handling Node.js process termination const cleanup = async () => { const timeoutMs = 5000; let timeout; try { await Promise.race([ this.shutdown(timeoutMs), new Promise((resolve) => { timeout = setTimeout(() => { console.warn('Tracing cleanup timed out; continuing exit'); resolve(); }, timeoutMs); }), ]); } finally { if (timeout) { clearTimeout(timeout); } } }; // Handle normal termination process.once('beforeExit', cleanup); // Handle CTRL+C (SIGINT) process.on('SIGINT', async () => { await cleanup(); if (!hasOtherListenersForSignals('SIGINT')) { // Only when there are no other listeners, exit the process on this SDK side process.exit(130); } }); // Handle termination (SIGTERM) process.on('SIGTERM', async () => { await cleanup(); if (!hasOtherListenersForSignals('SIGTERM')) { // Only when there are no other listeners, exit the process on this SDK side process.exit(0); } }); process.on('unhandledRejection', async (reason, promise) => { logModelAndToolActionError(logger, 'Unhandled rejection', reason, promise); await cleanup(); if (!hasOtherListenersForEvents('unhandledRejection')) { // Only when there are no other listeners, exit the process on this SDK side process.exit(1); } }); } } async forceFlush() { await this.#multiProcessor.forceFlush(); } } let moduleTraceProvider; function hasOtherListenersForSignals(event) { return process.listeners(event).length > 1; } function hasOtherListenersForEvents(event) { return process.listeners(event).length > 1; } export function getGlobalTraceProvider() { const symbol = Symbol.for('openai.agents.core.traceProvider'); try { const globalHolder = globalThis; // Avoid constructing extra providers when globalThis is frozen/sealed. We // first short-circuit on existing instances, then check writability before // instantiation so hardened runtimes do not leak constructors or listeners. const existing = globalHolder[symbol]; if (existing) { return existing; } const descriptor = Object.getOwnPropertyDescriptor(globalHolder, symbol); if (descriptor && descriptor.writable === false && descriptor.configurable === false && !descriptor.set) { return getModuleTraceProvider(); } if (!descriptor) { try { Object.defineProperty(globalHolder, symbol, { value: undefined, writable: true, configurable: true, }); } catch { return getModuleTraceProvider(); } } try { const provider = new TraceProvider(); globalHolder[symbol] = provider; return provider; } catch { return getModuleTraceProvider(); } } catch { // Hardened runtimes can freeze or seal globalThis; fall back to a // module-local singleton instead of throwing so tracing still works. return getModuleTraceProvider(); } } function getModuleTraceProvider() { if (!moduleTraceProvider) { moduleTraceProvider = new TraceProvider(); } return moduleTraceProvider; } //# sourceMappingURL=provider.mjs.map