@mastra/observability 1.17.8 → 1.17.9-alpha.1

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.cjs CHANGED
@@ -1870,6 +1870,57 @@ var CardinalityFilter = class {
1870
1870
  };
1871
1871
  //#endregion
1872
1872
  //#region src/usage.ts
1873
+ /**
1874
+ * For non-BYOK requests, `usage.cost` is the complete OpenRouter charge and
1875
+ * `usage.costDetails.upstreamInferenceCost` is only its upstream breakdown. For BYOK requests,
1876
+ * the upstream cost is billed separately and must be added to OpenRouter's own `usage.cost`.
1877
+ *
1878
+ * OpenRouter reports `usage.is_byok` on the wire, but published `@openrouter/ai-sdk-provider`
1879
+ * versions drop it from `providerMetadata.openrouter.usage`. When `isByok` is present it is
1880
+ * authoritative. When it is absent, only the unambiguous shapes are used: a non-BYOK response
1881
+ * reports an upstream cost that is absent, zero, or equal to `cost`, and a BYOK response reports
1882
+ * a zero `cost` with a positive upstream cost. Any other combination falls back to model price
1883
+ * inference rather than double-counting a non-BYOK charge or under-reporting a BYOK one.
1884
+ */
1885
+ function extractOpenRouterCost(providerMetadata) {
1886
+ const usage = providerMetadata?.openrouter?.usage;
1887
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) return void 0;
1888
+ const cost = usage.cost;
1889
+ const isByok = usage.isByok;
1890
+ const costDetails = usage.costDetails;
1891
+ const upstreamCost = costDetails && typeof costDetails === "object" && !Array.isArray(costDetails) ? costDetails.upstreamInferenceCost : void 0;
1892
+ const isValid = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
1893
+ if (cost != null && !isValid(cost) || upstreamCost != null && !isValid(upstreamCost)) return void 0;
1894
+ const validCost = isValid(cost) ? cost : void 0;
1895
+ const validUpstreamCost = isValid(upstreamCost) ? upstreamCost : void 0;
1896
+ const byok = typeof isByok === "boolean" ? isByok : inferByok(validCost, validUpstreamCost);
1897
+ if (byok === void 0) return void 0;
1898
+ if (!byok) {
1899
+ if (validCost === void 0) return void 0;
1900
+ return {
1901
+ total: validCost,
1902
+ usedCost: true,
1903
+ usedUpstreamCost: false
1904
+ };
1905
+ }
1906
+ if (validCost === void 0 && validUpstreamCost === void 0) return void 0;
1907
+ const total = (validCost ?? 0) + (validUpstreamCost ?? 0);
1908
+ if (!Number.isFinite(total)) return void 0;
1909
+ return {
1910
+ total,
1911
+ usedCost: validCost !== void 0,
1912
+ usedUpstreamCost: validUpstreamCost !== void 0
1913
+ };
1914
+ }
1915
+ /**
1916
+ * Infer the BYOK mode from the cost fields alone. Returns `undefined` when the shape is
1917
+ * ambiguous, so the caller falls back to model price inference.
1918
+ */
1919
+ function inferByok(cost, upstreamCost) {
1920
+ if (cost === void 0) return void 0;
1921
+ if (upstreamCost === void 0 || upstreamCost === 0 || upstreamCost === cost) return false;
1922
+ if (cost === 0) return true;
1923
+ }
1873
1924
  function isV3RawUsage(raw) {
1874
1925
  return typeof raw === "object" && raw !== null && "inputTokens" in raw;
1875
1926
  }
@@ -2057,6 +2108,31 @@ function getGatewayCostContext({ stepProviderMetadata }) {
2057
2108
  }
2058
2109
  };
2059
2110
  }
2111
+ function getOpenRouterCostContext({ providerMetadata, stepProviderMetadata }, model) {
2112
+ const metadata = stepProviderMetadata ?? (providerMetadata ? [providerMetadata] : void 0);
2113
+ if (!metadata?.some((step) => step?.openrouter !== void 0)) return void 0;
2114
+ const results = metadata.map(extractOpenRouterCost);
2115
+ if (results.some((result) => result === void 0)) return void 0;
2116
+ const costs = results;
2117
+ const estimatedCost = costs.reduce((total, result) => total + result.total, 0);
2118
+ if (!Number.isFinite(estimatedCost)) return void 0;
2119
+ const sdkCostFields = [];
2120
+ if (costs.some((result) => result.usedCost)) sdkCostFields.push("openrouter.usage.cost");
2121
+ if (costs.some((result) => result.usedUpstreamCost)) sdkCostFields.push("openrouter.usage.costDetails.upstreamInferenceCost");
2122
+ return {
2123
+ provider: "openrouter",
2124
+ model,
2125
+ estimatedCost,
2126
+ costUnit: "USD",
2127
+ costMetadata: {
2128
+ source: "provider_reported",
2129
+ sdkProvider: "openrouter",
2130
+ sdkCostField: sdkCostFields.join("+"),
2131
+ scope: "query_total",
2132
+ reportedStepCount: costs.length
2133
+ }
2134
+ };
2135
+ }
2060
2136
  function formatPreviewLabel(label, fallback) {
2061
2137
  return typeof label === "string" && label.length > 0 ? label : fallback;
2062
2138
  }
@@ -2239,9 +2315,16 @@ var ModelSpanTracker = class {
2239
2315
  */
2240
2316
  endGeneration(options) {
2241
2317
  const { usage, providerMetadata, stepProviderMetadata, ...spanOptions } = options ?? {};
2318
+ const model = spanOptions.attributes?.responseModel ?? this.#modelSpan?.attributes?.responseModel ?? this.#modelSpan?.attributes?.model;
2319
+ const providerCostContext = getOpenRouterCostContext({
2320
+ providerMetadata,
2321
+ stepProviderMetadata
2322
+ }, model);
2323
+ if (providerCostContext && !spanOptions.attributes) spanOptions.attributes = {};
2242
2324
  if (spanOptions.attributes) {
2243
2325
  spanOptions.attributes.completionStartTime = this.#completionStartTime;
2244
2326
  spanOptions.attributes.usage = extractUsageMetrics(usage, providerMetadata);
2327
+ spanOptions.attributes.costContext = providerCostContext ?? spanOptions.attributes.costContext;
2245
2328
  if (!spanOptions.attributes.costContext) spanOptions.attributes.costContext = getGatewayCostContext({ stepProviderMetadata });
2246
2329
  }
2247
2330
  this.#modelSpan?.end(spanOptions);
@@ -4930,7 +5013,12 @@ var BaseObservabilityInstance = class extends _mastra_core_base.MastraBase {
4930
5013
  for (const processor of this.spanOutputProcessors) {
4931
5014
  if (!span) break;
4932
5015
  try {
4933
- span = processor.process(span);
5016
+ const processed = processor.process(span);
5017
+ if (processed !== void 0 && processed !== span) {
5018
+ this.logger.error(`[Observability] Processor error [name=${processor.name}]: process() must return the span it received (or undefined to drop it), not a copy. Span dropped.`);
5019
+ return;
5020
+ }
5021
+ span = processed;
4934
5022
  } catch (error) {
4935
5023
  this.logger.error(`[Observability] Processor error [name=${processor.name}]`, error);
4936
5024
  }
@@ -9230,9 +9318,10 @@ var SensitiveDataFilter = class {
9230
9318
  /**
9231
9319
  * Process a span by filtering sensitive data across its key fields.
9232
9320
  * Fields processed: attributes, metadata, input, output, errorInfo, requestContext.
9321
+ * The span is mutated in place, as the SpanOutputProcessor contract requires.
9233
9322
  *
9234
9323
  * @param span - The input span to filter
9235
- * @returns A new span with sensitive values redacted
9324
+ * @returns The same span instance with sensitive values redacted
9236
9325
  */
9237
9326
  process(span) {
9238
9327
  const indexedState = this.redactionStyle === "indexed" ? this.getTraceState(span.traceId) : void 0;