UNPKG

agents

Version:

A home for your AI agents

416 lines (415 loc) • 17.9 kB
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js"; import { t as nativeTracing } from "../cloudflare-CUUm_9q9.js"; import { t as channels } from "../diagnostics-BzvaX2UT.js"; import { t as genericObservability } from "../emitter-DPOVcCln.js"; import { AsyncLocalStorage } from "node:async_hooks"; import { subscribe as subscribe$1, unsubscribe } from "node:diagnostics_channel"; import { INVALID_SPAN_CONTEXT, ROOT_CONTEXT, SpanStatusCode, context, createContextKey, diag, isSpanContextValid, trace } from "@opentelemetry/api"; //#region src/observability/otel/bridge.ts /** * The OpenTelemetry API on Workers native tracing. * * Every recording OTel span is a native span. Native tracing has no manual * parent wiring: a span's parent is whatever native span is active (in the * async context) when it opens, and a span can only be active inside the * callback that opened it. The bridge works within that: * * - Every span opens with `tracing.startActiveSpan`, so it is native-active for * at least a moment. Inside that moment the bridge captures an async-context * snapshot: a way back into "this span is the active native parent". * - `startActiveSpan` runs the caller's function inside the native callback. * - `startSpan` returns from the native callback at once (the span stays open * and inactive, as OTel requires). When the span later becomes active through * `context.with(trace.setSpan(ctx, span), fn)` — the pattern the MCP SDK and * other instrumentation use — `fn` runs inside the snapshot, so platform * spans (fetch, KV, D1, Durable Objects) and child spans nest under it. * - A span is only opened where its native parent is the span the caller asked * for. Anything native tracing cannot represent faithfully (remote parents, * new roots, explicit start times, parents from another request) returns a * non-recording span instead of a span under a substitute parent. */ const reported = /* @__PURE__ */ new Set(); /** * Marks a context taken where no native span was visible. Durable Object * constructors and `blockConcurrencyWhile` callbacks report no active span, * yet native spans opened there still parent to the request; at module scope * they record nothing. Either way the runtime, not the bridge, picks. */ const AMBIENT = createContextKey("agents: ambient native span"); /** * Reports a dropped or refused feature once per isolate through OTel's `diag` * logger. Messages name the feature only, never span names or values. */ function unsupported(feature) { if (reported.has(feature)) return; reported.add(feature); diag.warn(`agents/observability: ${feature} cannot be represented by Workers tracing.`); } /** Telemetry must never replace the application's result or error. */ function safely(write, feature) { try { write(); } catch { unsupported(`${feature} (the runtime rejected the value)`); } } var _spans = /* @__PURE__ */ new WeakMap(); var _tracer = /* @__PURE__ */ new WeakMap(); var _snapshot = /* @__PURE__ */ new WeakMap(); var Bridge = class { constructor(native, snapshot) { this.native = native; _classPrivateFieldInitSpec(this, _spans, /* @__PURE__ */ new WeakMap()); _classPrivateFieldInitSpec(this, _tracer, void 0); _classPrivateFieldInitSpec(this, _snapshot, void 0); _classPrivateFieldSet2(_snapshot, this, snapshot); this.contextManager = new WorkersContextManager(this).enable(); _classPrivateFieldSet2(_tracer, this, new WorkersTracer(this)); const tracer = _classPrivateFieldGet2(_tracer, this); this.tracerProvider = { getTracer: () => tracer }; } /** The OTel span for a native span the bridge did not open (root, agent spans). */ wrap(native) { let span = _classPrivateFieldGet2(_spans, this).get(native); if (span === void 0) { span = new WorkersSpan(this, native, void 0); _classPrivateFieldGet2(_spans, this).set(native, span); } return span; } /** * Opens a native span where `placement` says, seeds its attributes, and * calls `body` while it is the active native span. */ open(name, attributes, placement, body) { const start = () => this.native.startActiveSpan(name, (native) => { const span = new WorkersSpan(this, native, _classPrivateFieldGet2(_snapshot, this).call(this)); _classPrivateFieldGet2(_spans, this).set(native, span); if (attributes !== void 0) span.setAttributes(attributes); return body(span); }); return placement.in === void 0 ? start() : placement.in(start); } /** * Decides whether a span can open natively under the requested parent, and * from which async context. */ place(options, parentContext) { const parent = options.root ? void 0 : trace.getSpan(parentContext); const current = this.native.getActiveSpan(); const refuse = (feature) => { unsupported(feature); const ids = parent?.spanContext(); return { refused: ids !== void 0 && isSpanContextValid(ids) ? ids : INVALID_SPAN_CONTEXT }; }; if (current === void 0) return parent === void 0 && !options.root && options.startTime === void 0 && parentContext.getValue(AMBIENT) === true ? { in: void 0 } : refuse("Spans outside a request"); if (parent === void 0) return refuse("Root spans and spans without a parent"); if (!(parent instanceof WorkersSpan) || parent.bridge !== this) return refuse("Spans under remote or foreign parents"); if (options.startTime !== void 0) return refuse("Explicit span start timestamps"); if (options.links !== void 0 && options.links.length > 0) unsupported("Span links (dropped)"); if (parent.native === current) return { in: void 0 }; const reentry = parent.reentry(current); return reentry === void 0 ? refuse("Spans under a parent from another request or scope") : { in: reentry }; } /** Runs `fn` with `context` as the OTel context and `native` as its native span. */ run(context, native, fn) { return this.contextManager.run({ context, native }, fn); } }; var _traceId = /* @__PURE__ */ new WeakMap(); var WorkersSpan = class { constructor(bridge, native, snapshot) { this.bridge = bridge; this.native = native; this.snapshot = snapshot; _classPrivateFieldInitSpec(this, _traceId, void 0); _classPrivateFieldSet2(_traceId, this, snapshot === void 0 ? void 0 : traceIdOf(native)); } /** * The snapshot to re-enter so this span is the active native parent again, * or `undefined` when that would cross into another request. */ reentry(current) { if (this.snapshot === void 0) return; const currentTraceId = traceIdOf(current); if (_classPrivateFieldGet2(_traceId, this) !== void 0 && currentTraceId !== void 0 && _classPrivateFieldGet2(_traceId, this) !== currentTraceId) return; return this.snapshot; } spanContext() { let ids; safely(() => { ids = this.native.spanContext?.(); }, "Span context"); if (ids === void 0) return INVALID_SPAN_CONTEXT; const { traceId, spanId, traceFlags } = ids; return { traceId, spanId, traceFlags }; } isRecording() { return this.native.isTraced; } setAttribute(key, value) { if (key === "" || value === void 0 || !this.native.isTraced) return this; if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") safely(() => this.native.setAttribute(key, value), "Attribute"); else unsupported("Array and non-scalar attribute values (dropped)"); return this; } setAttributes(attributes) { for (const [key, value] of Object.entries(attributes)) this.setAttribute(key, value); return this; } setStatus(status) { if (status.code !== SpanStatusCode.OK && status.code !== SpanStatusCode.ERROR) return this; const setStatus = this.native.setStatus; if (setStatus === void 0) { unsupported("Span status on this runtime version"); return this; } safely(() => setStatus.call(this.native, status.code === SpanStatusCode.ERROR ? status.message === void 0 ? { code: "error" } : { code: "error", message: status.message } : { code: "ok" }), "Span status"); return this; } updateName(name) { const updateName = this.native.updateName; if (updateName === void 0) { unsupported("Span renames on this runtime version"); return this; } safely(() => updateName.call(this.native, name), "Span rename"); return this; } recordException(exception, time) { if (time !== void 0) unsupported("Explicit exception timestamps (recorded at runtime time)"); safely(() => this.native.recordException(exception), "Exception"); } end(endTime) { if (endTime !== void 0) unsupported("Explicit end timestamps (ended at runtime time)"); safely(() => this.native.end(), "Span end"); } addEvent(_name, _attributesOrStartTime, _startTime) { unsupported("Span events other than exceptions (dropped)"); return this; } addLink(_link) { unsupported("Span links (dropped)"); return this; } addLinks(links) { if (links.length > 0) unsupported("Span links (dropped)"); return this; } }; function traceIdOf(native) { try { const traceId = native.spanContext?.().traceId; return traceId === void 0 || /^0+$/.test(traceId) ? void 0 : traceId; } catch { return; } } var WorkersTracer = class { constructor(bridge) { this.bridge = bridge; } startSpan(name, options = {}, parentContext = this.bridge.contextManager.active()) { const placement = this.bridge.place(options, parentContext); if ("refused" in placement) return trace.wrapSpanContext(placement.refused); return this.bridge.open(name, options.attributes, placement, (span) => span); } startActiveSpan(name, optionsOrFn, contextOrFn, maybeFn) { const options = typeof optionsOrFn === "function" ? {} : optionsOrFn; const parentContext = typeof contextOrFn === "object" ? contextOrFn : this.bridge.contextManager.active(); const fn = typeof optionsOrFn === "function" ? optionsOrFn : typeof contextOrFn === "function" ? contextOrFn : maybeFn; if (fn === void 0) throw new TypeError("startActiveSpan requires a callback"); const placement = this.bridge.place(options, parentContext); if ("refused" in placement) { const span = trace.wrapSpanContext(placement.refused); return this.bridge.contextManager.with(trace.setSpan(parentContext, span), () => fn(span)); } return this.bridge.open(name, options.attributes, placement, (span) => this.bridge.run(trace.setSpan(parentContext, span), span.native, () => fn(span))); } }; var _storage = /* @__PURE__ */ new WeakMap(); var WorkersContextManager = class { constructor(bridge) { this.bridge = bridge; _classPrivateFieldInitSpec(this, _storage, void 0); } active() { const storage = _classPrivateFieldGet2(_storage, this); if (storage === void 0) return ROOT_CONTEXT; const scope = storage.getStore(); const native = this.bridge.native.getActiveSpan(); if (scope !== void 0 && scope.native === native) return scope.context; if (native === void 0) return scope?.context ?? ROOT_CONTEXT.setValue(AMBIENT, true); return trace.setSpan(scope?.context ?? ROOT_CONTEXT, this.bridge.wrap(native)); } with(context, fn, thisArg, ...args) { const call = () => fn.apply(thisArg, args); if (_classPrivateFieldGet2(_storage, this) === void 0) return call(); const current = this.bridge.native.getActiveSpan(); const span = trace.getSpan(context); if (span instanceof WorkersSpan && span.bridge === this.bridge && current !== void 0 && span.native !== current) { const reentry = span.reentry(current); if (reentry !== void 0) return reentry(() => this.run({ context, native: span.native }, call)); unsupported("Activating a span from another request or scope"); } return this.run({ context, native: current }, call); } /** INTERNAL: runs `fn` in `scope` without native activation. */ run(scope, fn) { const storage = _classPrivateFieldGet2(_storage, this); return storage === void 0 ? fn() : storage.run(scope, fn); } bind(context, target) { if (typeof target !== "function") { unsupported("context.bind on non-functions (left unchanged)"); return target; } const manager = this; const callable = target; return function(...args) { return manager.with(context, () => callable.apply(this, args)); }; } enable() { _classPrivateFieldGet2(_storage, this) ?? _classPrivateFieldSet2(_storage, this, new AsyncLocalStorage()); return this; } /** Workers has no `AsyncLocalStorage.disable()`; drop access to old scopes instead. */ disable() { _classPrivateFieldSet2(_storage, this, void 0); return this; } }; /** Creates (but does not register) the OTel components for native tracing. */ function createOpenTelemetryBridge(runtime) { return new Bridge(runtime.tracing, runtime.snapshot ?? (() => AsyncLocalStorage.snapshot())); } //#endregion //#region src/observability/otel/register.ts /** Where `@opentelemetry/api` 1.x keeps its global registrations. */ const OTEL_GLOBAL = Symbol.for("opentelemetry.js.api.1"); function otelGlobal() { return globalThis[OTEL_GLOBAL]; } /** The provider behind the global proxy, when the proxy exposes it. */ function delegateOf(provider) { const proxy = provider; return typeof proxy?.getDelegate === "function" ? proxy.getDelegate() : provider; } /** * Registers `bridge` as the global OTel context manager and tracer provider. * * - Idempotent while the bridge is registered. * - Steps aside (`"deferred"`) when both globals are already registered by * someone else: an OTel SDK the application set up, or the Workers runtime * once it registers the OTel API natively. The existing setup keeps working. * - Throws when only one of the two is registered, or the registry belongs to * an incompatible `@opentelemetry/api` copy: either would leave OTel spans * silently detached from native tracing. */ function registerBridge(bridge) { const registry = otelGlobal(); const contextOwner = registry?.context; const traceOwner = registry?.trace; const ownsContext = contextOwner === bridge.contextManager; const ownsTrace = traceOwner !== void 0 && delegateOf(traceOwner) === bridge.tracerProvider; if (ownsContext && ownsTrace) return "already-registered"; if (contextOwner !== void 0 && traceOwner !== void 0 && !ownsContext && !ownsTrace) { diag.info("agents/observability: OpenTelemetry is already registered; experimental_registerOpenTelemetry() left it in place."); return "deferred"; } if (contextOwner !== void 0 || traceOwner !== void 0) throw new Error(`experimental_registerOpenTelemetry(): an OpenTelemetry ${contextOwner === void 0 ? "tracer provider" : "context manager"} is already registered without its ${contextOwner === void 0 ? "context manager" : "tracer provider"}. Remove the partial OpenTelemetry setup, or register both before calling experimental_registerOpenTelemetry().`); if (!context.setGlobalContextManager(bridge.contextManager)) throw incompatible(registry?.version); bridge.contextManager.enable(); if (!trace.setGlobalTracerProvider(bridge.tracerProvider)) { context.disable(); throw incompatible(registry?.version); } return "registered"; } function incompatible(version) { return /* @__PURE__ */ new Error(`experimental_registerOpenTelemetry(): the global OpenTelemetry API rejected the registration${version === void 0 ? "" : ` (globals were created by @opentelemetry/api ${version})`}. Install a single version of @opentelemetry/api.`); } //#endregion //#region src/observability/otel/index.ts let bridge; /** * Connects the OpenTelemetry API (`@opentelemetry/api`) to Workers tracing. * Call once at module scope: * * ```ts * import { experimental_registerOpenTelemetry } from "agents/observability"; * * experimental_registerOpenTelemetry(); * ``` * * Afterwards, libraries instrumented with the OpenTelemetry API (the MCP SDK, * AI SDK telemetry, your own `trace.getTracer(...)` spans) record native * Workers spans. They nest with the Agents SDK's spans and the automatic * fetch, KV, D1 and Durable Object spans, and export through * `observability.traces.destinations`. Enable `observability.traces` in your * Wrangler configuration for anything to be recorded. * * Safe to call more than once. When OpenTelemetry is already fully registered, * by your own SDK setup or natively by the Workers runtime, this leaves it in * place and does nothing. Throws when only part of an OpenTelemetry setup is * registered. * * @experimental Undocumented and subject to change: a temporary bridge until * Workers supports the OpenTelemetry API natively. See * `design/rfc-opentelemetry-bridge.md` for what it records and refuses. */ function experimental_registerOpenTelemetry() { if (nativeTracing === void 0) { diag.warn("agents/observability: this runtime has no native tracing; experimental_registerOpenTelemetry() did nothing."); return; } bridge ??= createOpenTelemetryBridge({ tracing: nativeTracing }); registerBridge(bridge); } //#endregion //#region src/observability/index.ts /** * Channel keys whose diagnostics channel name differs from `agents:${key}`. * Keep this in sync with {@link channels} for any camelCase key that maps to a * snake_case diagnostics channel. */ const CHANNEL_DIAGNOSTIC_NAME_OVERRIDES = { agentTool: "agents:agent_tool" }; /** * Subscribe to a typed observability channel. * * ```ts * import { subscribe } from "agents/observability"; * * const unsub = subscribe("rpc", (event) => { * console.log(event.payload.method); // fully typed * }); * ``` * * @returns A function that unsubscribes the callback. */ function subscribe(channelKey, callback) { const name = CHANNEL_DIAGNOSTIC_NAME_OVERRIDES[channelKey] ?? `agents:${channelKey}`; const handler = (message, _name) => callback(message); subscribe$1(name, handler); return () => unsubscribe(name, handler); } //#endregion export { channels, experimental_registerOpenTelemetry, genericObservability, subscribe }; //# sourceMappingURL=index.js.map