@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.js CHANGED
@@ -1845,6 +1845,57 @@ var CardinalityFilter = class {
1845
1845
  };
1846
1846
  //#endregion
1847
1847
  //#region src/usage.ts
1848
+ /**
1849
+ * For non-BYOK requests, `usage.cost` is the complete OpenRouter charge and
1850
+ * `usage.costDetails.upstreamInferenceCost` is only its upstream breakdown. For BYOK requests,
1851
+ * the upstream cost is billed separately and must be added to OpenRouter's own `usage.cost`.
1852
+ *
1853
+ * OpenRouter reports `usage.is_byok` on the wire, but published `@openrouter/ai-sdk-provider`
1854
+ * versions drop it from `providerMetadata.openrouter.usage`. When `isByok` is present it is
1855
+ * authoritative. When it is absent, only the unambiguous shapes are used: a non-BYOK response
1856
+ * reports an upstream cost that is absent, zero, or equal to `cost`, and a BYOK response reports
1857
+ * a zero `cost` with a positive upstream cost. Any other combination falls back to model price
1858
+ * inference rather than double-counting a non-BYOK charge or under-reporting a BYOK one.
1859
+ */
1860
+ function extractOpenRouterCost(providerMetadata) {
1861
+ const usage = providerMetadata?.openrouter?.usage;
1862
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) return void 0;
1863
+ const cost = usage.cost;
1864
+ const isByok = usage.isByok;
1865
+ const costDetails = usage.costDetails;
1866
+ const upstreamCost = costDetails && typeof costDetails === "object" && !Array.isArray(costDetails) ? costDetails.upstreamInferenceCost : void 0;
1867
+ const isValid = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
1868
+ if (cost != null && !isValid(cost) || upstreamCost != null && !isValid(upstreamCost)) return void 0;
1869
+ const validCost = isValid(cost) ? cost : void 0;
1870
+ const validUpstreamCost = isValid(upstreamCost) ? upstreamCost : void 0;
1871
+ const byok = typeof isByok === "boolean" ? isByok : inferByok(validCost, validUpstreamCost);
1872
+ if (byok === void 0) return void 0;
1873
+ if (!byok) {
1874
+ if (validCost === void 0) return void 0;
1875
+ return {
1876
+ total: validCost,
1877
+ usedCost: true,
1878
+ usedUpstreamCost: false
1879
+ };
1880
+ }
1881
+ if (validCost === void 0 && validUpstreamCost === void 0) return void 0;
1882
+ const total = (validCost ?? 0) + (validUpstreamCost ?? 0);
1883
+ if (!Number.isFinite(total)) return void 0;
1884
+ return {
1885
+ total,
1886
+ usedCost: validCost !== void 0,
1887
+ usedUpstreamCost: validUpstreamCost !== void 0
1888
+ };
1889
+ }
1890
+ /**
1891
+ * Infer the BYOK mode from the cost fields alone. Returns `undefined` when the shape is
1892
+ * ambiguous, so the caller falls back to model price inference.
1893
+ */
1894
+ function inferByok(cost, upstreamCost) {
1895
+ if (cost === void 0) return void 0;
1896
+ if (upstreamCost === void 0 || upstreamCost === 0 || upstreamCost === cost) return false;
1897
+ if (cost === 0) return true;
1898
+ }
1848
1899
  function isV3RawUsage(raw) {
1849
1900
  return typeof raw === "object" && raw !== null && "inputTokens" in raw;
1850
1901
  }
@@ -2032,6 +2083,31 @@ function getGatewayCostContext({ stepProviderMetadata }) {
2032
2083
  }
2033
2084
  };
2034
2085
  }
2086
+ function getOpenRouterCostContext({ providerMetadata, stepProviderMetadata }, model) {
2087
+ const metadata = stepProviderMetadata ?? (providerMetadata ? [providerMetadata] : void 0);
2088
+ if (!metadata?.some((step) => step?.openrouter !== void 0)) return void 0;
2089
+ const results = metadata.map(extractOpenRouterCost);
2090
+ if (results.some((result) => result === void 0)) return void 0;
2091
+ const costs = results;
2092
+ const estimatedCost = costs.reduce((total, result) => total + result.total, 0);
2093
+ if (!Number.isFinite(estimatedCost)) return void 0;
2094
+ const sdkCostFields = [];
2095
+ if (costs.some((result) => result.usedCost)) sdkCostFields.push("openrouter.usage.cost");
2096
+ if (costs.some((result) => result.usedUpstreamCost)) sdkCostFields.push("openrouter.usage.costDetails.upstreamInferenceCost");
2097
+ return {
2098
+ provider: "openrouter",
2099
+ model,
2100
+ estimatedCost,
2101
+ costUnit: "USD",
2102
+ costMetadata: {
2103
+ source: "provider_reported",
2104
+ sdkProvider: "openrouter",
2105
+ sdkCostField: sdkCostFields.join("+"),
2106
+ scope: "query_total",
2107
+ reportedStepCount: costs.length
2108
+ }
2109
+ };
2110
+ }
2035
2111
  function formatPreviewLabel(label, fallback) {
2036
2112
  return typeof label === "string" && label.length > 0 ? label : fallback;
2037
2113
  }
@@ -2214,9 +2290,16 @@ var ModelSpanTracker = class {
2214
2290
  */
2215
2291
  endGeneration(options) {
2216
2292
  const { usage, providerMetadata, stepProviderMetadata, ...spanOptions } = options ?? {};
2293
+ const model = spanOptions.attributes?.responseModel ?? this.#modelSpan?.attributes?.responseModel ?? this.#modelSpan?.attributes?.model;
2294
+ const providerCostContext = getOpenRouterCostContext({
2295
+ providerMetadata,
2296
+ stepProviderMetadata
2297
+ }, model);
2298
+ if (providerCostContext && !spanOptions.attributes) spanOptions.attributes = {};
2217
2299
  if (spanOptions.attributes) {
2218
2300
  spanOptions.attributes.completionStartTime = this.#completionStartTime;
2219
2301
  spanOptions.attributes.usage = extractUsageMetrics(usage, providerMetadata);
2302
+ spanOptions.attributes.costContext = providerCostContext ?? spanOptions.attributes.costContext;
2220
2303
  if (!spanOptions.attributes.costContext) spanOptions.attributes.costContext = getGatewayCostContext({ stepProviderMetadata });
2221
2304
  }
2222
2305
  this.#modelSpan?.end(spanOptions);
@@ -4905,7 +4988,12 @@ var BaseObservabilityInstance = class extends MastraBase {
4905
4988
  for (const processor of this.spanOutputProcessors) {
4906
4989
  if (!span) break;
4907
4990
  try {
4908
- span = processor.process(span);
4991
+ const processed = processor.process(span);
4992
+ if (processed !== void 0 && processed !== span) {
4993
+ 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.`);
4994
+ return;
4995
+ }
4996
+ span = processed;
4909
4997
  } catch (error) {
4910
4998
  this.logger.error(`[Observability] Processor error [name=${processor.name}]`, error);
4911
4999
  }
@@ -9205,9 +9293,10 @@ var SensitiveDataFilter = class {
9205
9293
  /**
9206
9294
  * Process a span by filtering sensitive data across its key fields.
9207
9295
  * Fields processed: attributes, metadata, input, output, errorInfo, requestContext.
9296
+ * The span is mutated in place, as the SpanOutputProcessor contract requires.
9208
9297
  *
9209
9298
  * @param span - The input span to filter
9210
- * @returns A new span with sensitive values redacted
9299
+ * @returns The same span instance with sensitive values redacted
9211
9300
  */
9212
9301
  process(span) {
9213
9302
  const indexedState = this.redactionStyle === "indexed" ? this.getTraceState(span.traceId) : void 0;