@mastra/observability 1.16.6-alpha.2 → 1.16.6-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2056,13 +2056,15 @@ var ModelSpanTracker = class {
2056
2056
  if (!this.#currentInferenceSpan) return;
2057
2057
  const { usage: rawUsage, ...otherOutput } = payload.output;
2058
2058
  const usage = extractUsageMetrics(rawUsage, payload.metadata?.providerMetadata);
2059
+ const responseModel = typeof payload.metadata?.modelId === "string" ? payload.metadata.modelId : void 0;
2059
2060
  this.#currentInferenceSpan.end({
2060
2061
  output: otherOutput,
2061
2062
  attributes: {
2062
2063
  usage,
2063
2064
  finishReason: payload.stepResult.reason,
2064
2065
  warnings: payload.stepResult.warnings,
2065
- completionStartTime: this.#completionStartTime
2066
+ completionStartTime: this.#completionStartTime,
2067
+ ...responseModel?.trim() ? { responseModel } : {}
2066
2068
  }
2067
2069
  });
2068
2070
  this.#currentInferenceSpan = void 0;
@@ -5176,15 +5178,15 @@ function isAuthFailureError(error) {
5176
5178
  function isAuthFailureStatus(status) {
5177
5179
  return AUTH_FAILURE_STATUSES.has(status);
5178
5180
  }
5179
- async function fetchWithAuthFailureHandling(url, options, maxRetries) {
5181
+ async function fetchWithAuthFailureHandling(url, options, maxRetries, callerShouldRetry) {
5180
5182
  let authFailureStatus;
5181
5183
  try {
5182
- await fetchWithRetry(url, options, maxRetries, { shouldRetryResponse: (response) => {
5184
+ return await fetchWithRetry(url, options, maxRetries, { shouldRetryResponse: (response) => {
5183
5185
  if (isAuthFailureStatus(response.status)) {
5184
5186
  authFailureStatus = response.status;
5185
5187
  return false;
5186
5188
  }
5187
- return true;
5189
+ return callerShouldRetry?.(response) ?? true;
5188
5190
  } });
5189
5191
  } catch (error) {
5190
5192
  if (authFailureStatus !== void 0) throw new AuthFailureError(authFailureStatus, error);
@@ -6314,6 +6316,25 @@ const SIGNAL_PUBLISH_SUFFIXES = {
6314
6316
  feedback: "/feedback/publish"
6315
6317
  };
6316
6318
  const DEFAULT_PLATFORM_SPAN_FILTER = (span) => span.type !== SpanType.MODEL_CHUNK;
6319
+ const QUOTA_EXCEEDED_STATUS = 402;
6320
+ const OBSERVABILITY_STATUS_HEADER = "x-mastra-observability";
6321
+ const OBSERVABILITY_DISABLED_VALUE = "disabled";
6322
+ const OBSERVABILITY_RETRY_AFTER_HEADER = "x-mastra-observability-retry-after";
6323
+ const DEFAULT_QUOTA_PROBE_INTERVAL_SECONDS = 300;
6324
+ const MAX_QUOTA_PROBE_INTERVAL_SECONDS = Math.floor(2147483647 / 1e3);
6325
+ function isObservabilityDisabled(response) {
6326
+ return response.headers.get(OBSERVABILITY_STATUS_HEADER) === OBSERVABILITY_DISABLED_VALUE;
6327
+ }
6328
+ function isQuotaExceededResponse(response) {
6329
+ return response.status === QUOTA_EXCEEDED_STATUS || isObservabilityDisabled(response);
6330
+ }
6331
+ function parseQuotaRetryAfterSeconds(response) {
6332
+ const raw = response.headers.get(OBSERVABILITY_RETRY_AFTER_HEADER);
6333
+ if (!raw || !/^\d+$/.test(raw.trim())) return DEFAULT_QUOTA_PROBE_INTERVAL_SECONDS;
6334
+ const parsed = Number.parseInt(raw, 10);
6335
+ if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_QUOTA_PROBE_INTERVAL_SECONDS;
6336
+ return Math.min(parsed, MAX_QUOTA_PROBE_INTERVAL_SECONDS);
6337
+ }
6317
6338
  const SIGNAL_PUBLISH_SEGMENTS = {
6318
6339
  traces: "spans",
6319
6340
  logs: "logs",
@@ -6403,6 +6424,10 @@ var MastraPlatformExporter = class extends BaseExporter {
6403
6424
  buffer;
6404
6425
  flushTimer = null;
6405
6426
  inFlightFlushes = /* @__PURE__ */ new Set();
6427
+ quotaPaused = false;
6428
+ quotaProbeTimer = null;
6429
+ quotaProbeIntervalSeconds = DEFAULT_QUOTA_PROBE_INTERVAL_SECONDS;
6430
+ shuttingDown = false;
6406
6431
  constructor(config = {}) {
6407
6432
  super(config);
6408
6433
  if (config.projectId !== void 0 && !VALID_PROJECT_ID.test(config.projectId)) throw createInvalidProjectIdError(config.projectId);
@@ -6451,31 +6476,31 @@ var MastraPlatformExporter = class extends BaseExporter {
6451
6476
  async _exportTracingEvent(event) {
6452
6477
  if (event.type !== TracingEventType.SPAN_ENDED) return;
6453
6478
  if (!DEFAULT_PLATFORM_SPAN_FILTER(event.exportedSpan)) return;
6454
- if (this.authFailureCooldown.dropEventIfCoolingDown()) return;
6479
+ if (this.quotaPaused || this.authFailureCooldown.dropEventIfCoolingDown()) return;
6455
6480
  this.addToBuffer(event);
6456
6481
  await this.handleBufferedEvent();
6457
6482
  }
6458
6483
  async onLogEvent(event) {
6459
6484
  if (this.isDisabled) return;
6460
- if (this.authFailureCooldown.dropEventIfCoolingDown()) return;
6485
+ if (this.quotaPaused || this.authFailureCooldown.dropEventIfCoolingDown()) return;
6461
6486
  this.addLogToBuffer(event);
6462
6487
  await this.handleBufferedEvent();
6463
6488
  }
6464
6489
  async onMetricEvent(event) {
6465
6490
  if (this.isDisabled) return;
6466
- if (this.authFailureCooldown.dropEventIfCoolingDown()) return;
6491
+ if (this.quotaPaused || this.authFailureCooldown.dropEventIfCoolingDown()) return;
6467
6492
  this.addMetricToBuffer(event);
6468
6493
  await this.handleBufferedEvent();
6469
6494
  }
6470
6495
  async onScoreEvent(event) {
6471
6496
  if (this.isDisabled) return;
6472
- if (this.authFailureCooldown.dropEventIfCoolingDown()) return;
6497
+ if (this.quotaPaused || this.authFailureCooldown.dropEventIfCoolingDown()) return;
6473
6498
  this.addScoreToBuffer(event);
6474
6499
  await this.handleBufferedEvent();
6475
6500
  }
6476
6501
  async onFeedbackEvent(event) {
6477
6502
  if (this.isDisabled) return;
6478
- if (this.authFailureCooldown.dropEventIfCoolingDown()) return;
6503
+ if (this.quotaPaused || this.authFailureCooldown.dropEventIfCoolingDown()) return;
6479
6504
  this.addFeedbackToBuffer(event);
6480
6505
  await this.handleBufferedEvent();
6481
6506
  }
@@ -6565,6 +6590,10 @@ var MastraPlatformExporter = class extends BaseExporter {
6565
6590
  this.flushTimer = null;
6566
6591
  }
6567
6592
  if (this.buffer.totalSize === 0) return;
6593
+ if (this.quotaPaused) {
6594
+ this.resetBuffer();
6595
+ return;
6596
+ }
6568
6597
  if (this.authFailureCooldown.dropEventsIfCoolingDown(this.buffer.totalSize)) {
6569
6598
  this.resetBuffer();
6570
6599
  return;
@@ -6614,11 +6643,16 @@ var MastraPlatformExporter = class extends BaseExporter {
6614
6643
  /**
6615
6644
  * Uploads a signal batch to the configured Mastra Observability API using fetchWithRetry.
6616
6645
  */
6617
- async batchUpload(signal, records) {
6618
- const headers = {
6646
+ buildPublishHeaders() {
6647
+ return {
6619
6648
  Authorization: `Bearer ${this.platformConfig.accessToken}`,
6620
6649
  "Content-Type": "application/json"
6621
6650
  };
6651
+ }
6652
+ buildPublishBody(signal, records) {
6653
+ return JSON.stringify({ [SIGNAL_PUBLISH_SEGMENTS[signal]]: records });
6654
+ }
6655
+ async batchUpload(signal, records) {
6622
6656
  const endpointMap = {
6623
6657
  traces: this.platformConfig.tracesEndpoint,
6624
6658
  logs: this.platformConfig.logsEndpoint,
@@ -6628,10 +6662,85 @@ var MastraPlatformExporter = class extends BaseExporter {
6628
6662
  };
6629
6663
  const options = {
6630
6664
  method: "POST",
6631
- headers,
6632
- body: JSON.stringify({ [SIGNAL_PUBLISH_SEGMENTS[signal]]: records })
6665
+ headers: this.buildPublishHeaders(),
6666
+ body: this.buildPublishBody(signal, records)
6633
6667
  };
6634
- await fetchWithAuthFailureHandling(endpointMap[signal], options, this.platformConfig.maxRetries);
6668
+ let quotaResponse;
6669
+ let response;
6670
+ try {
6671
+ response = await fetchWithAuthFailureHandling(endpointMap[signal], options, this.platformConfig.maxRetries, (res) => {
6672
+ if (isQuotaExceededResponse(res)) {
6673
+ quotaResponse = res;
6674
+ return false;
6675
+ }
6676
+ return true;
6677
+ });
6678
+ } catch (error) {
6679
+ if (quotaResponse) {
6680
+ this.enterQuotaPause(parseQuotaRetryAfterSeconds(quotaResponse));
6681
+ return;
6682
+ }
6683
+ throw error;
6684
+ }
6685
+ if (isObservabilityDisabled(response)) this.enterQuotaPause(parseQuotaRetryAfterSeconds(response));
6686
+ }
6687
+ /**
6688
+ * Enter the quota-exhausted paused state: drop buffered events, stop the
6689
+ * flush timer, and start probing until the collector stops rejecting with
6690
+ * 402. The gate is per-org, so a signal on any publish route pauses all
6691
+ * five signal types.
6692
+ */
6693
+ enterQuotaPause(retryAfterSeconds) {
6694
+ this.quotaProbeIntervalSeconds = retryAfterSeconds;
6695
+ if (this.quotaPaused) return;
6696
+ this.quotaPaused = true;
6697
+ if (this.flushTimer) {
6698
+ clearTimeout(this.flushTimer);
6699
+ this.flushTimer = null;
6700
+ }
6701
+ this.resetBuffer();
6702
+ this.logger.warn(`Mastra observability paused: quota exhausted, dropping telemetry and probing every ${retryAfterSeconds}s`);
6703
+ this.scheduleQuotaProbe();
6704
+ }
6705
+ scheduleQuotaProbe() {
6706
+ if (this.shuttingDown) return;
6707
+ if (this.quotaProbeTimer) clearTimeout(this.quotaProbeTimer);
6708
+ this.quotaProbeTimer = setTimeout(() => {
6709
+ this.quotaProbeTimer = null;
6710
+ this.probeQuotaStatus();
6711
+ }, this.quotaProbeIntervalSeconds * 1e3);
6712
+ this.quotaProbeTimer.unref?.();
6713
+ }
6714
+ /**
6715
+ * Send an empty spans batch as a free probe. The collector treats an empty
6716
+ * batch as a no-op, and an exhausted org still gets the 402 on it. A 402
6717
+ * throws out of fetchWithRetry without the Response, so the retry predicate
6718
+ * captures it to keep the retry-after hint.
6719
+ */
6720
+ async probeQuotaStatus() {
6721
+ if (this.shuttingDown || !this.quotaPaused) return;
6722
+ let quotaResponse;
6723
+ try {
6724
+ const response = await fetchWithRetry(this.platformConfig.tracesEndpoint, {
6725
+ method: "POST",
6726
+ headers: this.buildPublishHeaders(),
6727
+ body: this.buildPublishBody("traces", [])
6728
+ }, 1, { shouldRetryResponse: (res) => {
6729
+ if (isQuotaExceededResponse(res)) quotaResponse = res;
6730
+ return false;
6731
+ } });
6732
+ if (this.shuttingDown) return;
6733
+ if (!isObservabilityDisabled(response)) {
6734
+ this.quotaPaused = false;
6735
+ this.quotaProbeIntervalSeconds = DEFAULT_QUOTA_PROBE_INTERVAL_SECONDS;
6736
+ this.logger.warn("Mastra observability resumed: quota restored, exports re-enabled");
6737
+ return;
6738
+ }
6739
+ this.quotaProbeIntervalSeconds = parseQuotaRetryAfterSeconds(response);
6740
+ } catch {
6741
+ if (quotaResponse) this.quotaProbeIntervalSeconds = parseQuotaRetryAfterSeconds(quotaResponse);
6742
+ }
6743
+ this.scheduleQuotaProbe();
6635
6744
  }
6636
6745
  async flushSignalBatch(signal, records) {
6637
6746
  if (records.length === 0) return {
@@ -6699,11 +6808,16 @@ var MastraPlatformExporter = class extends BaseExporter {
6699
6808
  }
6700
6809
  }
6701
6810
  async shutdown() {
6811
+ this.shuttingDown = true;
6702
6812
  if (this.isDisabled) return;
6703
6813
  if (this.flushTimer) {
6704
6814
  clearTimeout(this.flushTimer);
6705
6815
  this.flushTimer = null;
6706
6816
  }
6817
+ if (this.quotaProbeTimer) {
6818
+ clearTimeout(this.quotaProbeTimer);
6819
+ this.quotaProbeTimer = null;
6820
+ }
6707
6821
  try {
6708
6822
  await this.flush();
6709
6823
  } catch (error) {