@mastra/clickhouse 1.26.0 → 1.27.0-alpha.0

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
@@ -3648,6 +3648,9 @@ const ALL_MIGRATIONS = [
3648
3648
  addColumn(TABLE_SPAN_EVENTS, "inputPreview", "Nullable(String)"),
3649
3649
  addColumn(TABLE_SPAN_EVENTS, "outputPreview", "Nullable(String)"),
3650
3650
  addColumn(TABLE_SPAN_EVENTS, "errorPreview", "Nullable(String)"),
3651
+ addColumn(TABLE_TRACE_BRANCHES, "entityVersionId", "Nullable(String)"),
3652
+ addColumn(TABLE_TRACE_BRANCHES, "parentEntityVersionId", "Nullable(String)"),
3653
+ addColumn(TABLE_TRACE_BRANCHES, "rootEntityVersionId", "Nullable(String)"),
3651
3654
  addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
3652
3655
  addColumn(TABLE_SPAN_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3653
3656
  addColumn(TABLE_SPAN_EVENTS, "rootEntityVersionId", "Nullable(String)"),
@@ -7989,8 +7992,7 @@ async function queryThreads(client, plan, timeoutMs) {
7989
7992
  });
7990
7993
  }
7991
7994
  /** Compile a direct span-query filter using its dedicated field mapping. */
7992
- function compileSpanQueryPredicate(predicate) {
7993
- const parameters = new ParameterBuilder();
7995
+ function compileSpanQueryPredicate(predicate, parameters = new ParameterBuilder()) {
7994
7996
  return {
7995
7997
  sql: compileScalarPredicate(predicate, SPAN_QUERY_FIELDS, parameters),
7996
7998
  params: parameters.params
@@ -8151,6 +8153,131 @@ function compileClickHouseSpanQuery(plan) {
8151
8153
  }
8152
8154
  };
8153
8155
  }
8156
+ const branchSpanTypes = new Set(BRANCH_SPAN_TYPE_VALUES);
8157
+ const sameColumn = (field) => [field];
8158
+ /**
8159
+ * Span fields `trace_branches` can serve, as the `trace_branches` columns each one reads. The two
8160
+ * tables are expected to diverge, so this list is explicit: a selection whose fields are not all
8161
+ * listed reads `span_events`. A new span-query field has to be classified here deliberately, and a
8162
+ * missing column then costs speed, not correctness.
8163
+ */
8164
+ const TRACE_BRANCHES_SPAN_FIELD_COLUMNS = {
8165
+ ...Object.fromEntries([
8166
+ "organizationId",
8167
+ "resourceId",
8168
+ "traceId",
8169
+ "spanId",
8170
+ "parentSpanId",
8171
+ "name",
8172
+ "spanType",
8173
+ "startedAt",
8174
+ "endedAt",
8175
+ "timeToFirstChunkMs",
8176
+ "threadId",
8177
+ "runId",
8178
+ "sessionId",
8179
+ "userId",
8180
+ "requestId",
8181
+ "serviceName",
8182
+ "experimentId",
8183
+ "environment",
8184
+ "executionSource",
8185
+ "entityType",
8186
+ "entityId",
8187
+ "entityName",
8188
+ "entityVersionId",
8189
+ "parentEntityType",
8190
+ "parentEntityId",
8191
+ "parentEntityName",
8192
+ "parentEntityVersionId",
8193
+ "rootEntityType",
8194
+ "rootEntityId",
8195
+ "rootEntityName",
8196
+ "rootEntityVersionId"
8197
+ ].map((field) => [field, sameColumn(field)])),
8198
+ status: ["error"],
8199
+ error: ["error"],
8200
+ durationMs: ["startedAt", "endedAt"]
8201
+ };
8202
+ /**
8203
+ * Whether a span selection reads `trace_branches`: `where` must restrict `spanType` to branch
8204
+ * types, and `trace_branches` must serve every selected field (`served`, for tests).
8205
+ */
8206
+ function readsTraceBranches(where, fields, served = TRACE_BRANCHES_SPAN_FIELD_COLUMNS) {
8207
+ return positiveSpanConjuncts(where).some(({ field, values }) => field === "spanType" && values.length > 0 && values.every((value) => branchSpanTypes.has(String(value)))) && [...fields].every((field) => Object.hasOwn(served, field));
8208
+ }
8209
+ /**
8210
+ * Top-level `spanType` / `name` equality or `in` conditions. Every span the predicate can match
8211
+ * satisfies them, and copies of a span never differ in either field, so they can be applied
8212
+ * before collapsing copies.
8213
+ */
8214
+ function positiveSpanConjuncts(where) {
8215
+ return (where?.type === "boolean" && where.operator === "and" ? where.args : where ? [where] : []).flatMap((conjunct) => {
8216
+ if (conjunct.type !== "comparison" && conjunct.type !== "membership") return [];
8217
+ if (conjunct.field !== "spanType" && conjunct.field !== "name") return [];
8218
+ if (conjunct.type === "comparison" && conjunct.operator === "eq") return [{
8219
+ field: conjunct.field,
8220
+ values: [conjunct.value]
8221
+ }];
8222
+ if (conjunct.type === "membership" && conjunct.operator === "in") return [{
8223
+ field: conjunct.field,
8224
+ values: conjunct.values
8225
+ }];
8226
+ return [];
8227
+ });
8228
+ }
8229
+ /**
8230
+ * The spans `querySpans()` selects, as CTEs ending in `selected` (alias each row as you like):
8231
+ * one row per current completed span with `startedAt` in the window, matching `where`, carrying
8232
+ * the identity, `startedAt`, `endedAt`, `status`, and `fields`.
8233
+ *
8234
+ * Copies of a span collapse to the copy with the latest `endedAt`. When `where` requires branch
8235
+ * span types (agent and workflow runs, tool calls, …) and `trace_branches` serves every selected
8236
+ * field, the spans are read from `trace_branches`, whose sort key starts with `spanType`, instead of
8237
+ * every span in the window. That path can differ from `querySpans()` in two ways:
8238
+ * - `trace_branches` is filled only by its materialized view, with no backfill, so an install that
8239
+ * upgraded from @mastra/clickhouse 1.4–1.6 has no branch rows for spans written before then.
8240
+ * - It is a `ReplacingMergeTree` without a version column, so once a background merge collapses a
8241
+ * retried span's copies it keeps the last-inserted copy, not the one with the latest `endedAt`.
8242
+ * Counts are unaffected; `status` and duration of an out-of-order retry can differ.
8243
+ *
8244
+ * Like `querySpans()`, the read has no upper bound on `endedAt` (`PREWHERE endedAt >= from` only,
8245
+ * since a span that starts in the window can end any time later), so a historical window reads
8246
+ * every span that ended since `from`.
8247
+ */
8248
+ function compileClickHouseSpanSelection(plan, fields, parameters) {
8249
+ const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
8250
+ const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
8251
+ const prefilter = positiveSpanConjuncts(plan.where).map(({ field, values }) => values.length ? `\n AND e.${field} IN (${values.map((value) => parameters.add(String(value), "String")).join(", ")})` : "\n AND 0").join("");
8252
+ const predicate = plan.where ? compileSpanQueryPredicate(plan.where, parameters).sql : "1";
8253
+ let tenant = "";
8254
+ if (plan.scope) {
8255
+ tenant += `\n AND e.organizationId = ${parameters.add(plan.scope.organizationId, "String")}`;
8256
+ if (plan.scope.resourceId !== void 0) tenant += `\n AND e.resourceId = ${parameters.add(plan.scope.resourceId, "String")}`;
8257
+ }
8258
+ const selectionFields = [.../* @__PURE__ */ new Set([
8259
+ "startedAt",
8260
+ "endedAt",
8261
+ "status",
8262
+ ...fields,
8263
+ ...predicateFields$1(plan.where)
8264
+ ])].filter((field) => !new Set(identityFields).has(field));
8265
+ const branches = readsTraceBranches(plan.where, selectionFields);
8266
+ const scan = `FROM ${branches ? TABLE_TRACE_BRANCHES : TABLE_SPAN_EVENTS} e
8267
+ PREWHERE e.endedAt >= ${from}
8268
+ WHERE e.startedAt >= ${from} AND e.startedAt < ${to}${tenant}${prefilter}`;
8269
+ return [`span_candidates AS (
8270
+ ${branches ? `SELECT ${identityFields.map((field) => `e.${field} AS ${field}`).join(", ")}, ${selectionFields.map((field) => field === "endedAt" ? "max(e.endedAt) AS endedAt" : `argMax(tuple(${fieldExpression(field)}), e.endedAt).1 AS ${field}`).join(", ")}
8271
+ ${scan}
8272
+ GROUP BY ${identityFields.map((field) => `e.${field}`).join(", ")}` : `SELECT ${identities}, ${selectionFields.map((field) => `${fieldExpression(field)} AS ${field}`).join(", ")}
8273
+ ${scan}
8274
+ ORDER BY ${identities}, e.endedAt DESC
8275
+ LIMIT 1 BY ${identities}`}
8276
+ )`, `selected AS (
8277
+ SELECT * FROM span_candidates s
8278
+ WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate})
8279
+ )`];
8280
+ }
8154
8281
  const LEGACY_PAYLOAD_BATCH_ROWS = 32;
8155
8282
  function iso$1(value) {
8156
8283
  return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
@@ -8554,6 +8681,28 @@ const DIMENSION_SQL = {
8554
8681
  organizationId: "r.organizationId",
8555
8682
  experimentId: "r.experimentId"
8556
8683
  };
8684
+ /** Span column expressions (alias `r`, a `selected` span row) for each groupable span dimension. */
8685
+ const SPAN_DIMENSION_SQL = Object.fromEntries([
8686
+ "name",
8687
+ "spanType",
8688
+ "entityType",
8689
+ "entityId",
8690
+ "entityName",
8691
+ "parentEntityType",
8692
+ "parentEntityName",
8693
+ "rootEntityType",
8694
+ "rootEntityName",
8695
+ "environment",
8696
+ "status",
8697
+ "serviceName",
8698
+ "executionSource",
8699
+ "threadId",
8700
+ "resourceId",
8701
+ "userId",
8702
+ "sessionId",
8703
+ "organizationId",
8704
+ "experimentId"
8705
+ ].map((field) => [field, `r.${field}`]));
8557
8706
  const PERCENTILES = {
8558
8707
  "duration.p50": .5,
8559
8708
  "duration.p90": .9,
@@ -8580,13 +8729,41 @@ const COMPARISON_SQL = {
8580
8729
  */
8581
8730
  function compileClickHouseTraceAggregate(plan) {
8582
8731
  const parameters = new ParameterBuilder();
8583
- const ctes = compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true });
8584
- const dimensionSql = (field) => {
8585
- if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
8586
- const sql = Object.hasOwn(DIMENSION_SQL, field) ? DIMENSION_SQL[field] : void 0;
8587
- if (sql === void 0) throw new Error(`Unsupported trusted trace-aggregate field: ${field}`);
8588
- return sql;
8589
- };
8732
+ return compileClickHouseAggregate(plan, parameters, {
8733
+ ctes: compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true }),
8734
+ from: "candidates",
8735
+ dimensionSql: (field) => {
8736
+ if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
8737
+ return lookupDimensionSql(DIMENSION_SQL, field);
8738
+ },
8739
+ isErrorSql: "isNotNull(r.error)",
8740
+ usageKey: ["traceId"],
8741
+ usageBound: "traceId IN (SELECT traceId FROM candidates)"
8742
+ });
8743
+ }
8744
+ /**
8745
+ * Compiles a trusted span aggregate plan over the spans `querySpans()` selects. Usage is
8746
+ * attributed by `(traceId, spanId)`, so only the span that recorded a metric row carries it.
8747
+ */
8748
+ function compileClickHouseSpanAggregate(plan) {
8749
+ const parameters = new ParameterBuilder();
8750
+ return compileClickHouseAggregate(plan, parameters, {
8751
+ ctes: compileClickHouseSpanSelection(plan, [...plan.dimensions, ...plan.measures.flatMap((measure) => measure.type === "countDistinct" && measure.field !== "traceId" ? [measure.field] : [])], parameters),
8752
+ from: "selected",
8753
+ dimensionSql: (field) => lookupDimensionSql(SPAN_DIMENSION_SQL, field),
8754
+ isErrorSql: "r.status = 'error'",
8755
+ usageKey: ["traceId", "spanId"],
8756
+ usageBound: "isNotNull(spanId)"
8757
+ });
8758
+ }
8759
+ function lookupDimensionSql(columns, field) {
8760
+ const sql = Object.hasOwn(columns, field) ? columns[field] : void 0;
8761
+ if (sql === void 0) throw new Error(`Unsupported trusted aggregate field: ${field}`);
8762
+ return sql;
8763
+ }
8764
+ function compileClickHouseAggregate(plan, parameters, source) {
8765
+ const { ctes } = source;
8766
+ const dimensionSql = source.dimensionSql;
8590
8767
  const distinctSql = (field) => field === "traceId" ? "r.traceId" : dimensionSql(field);
8591
8768
  const dimensionColumns = plan.dimensions.map((_, index) => `d${index}`);
8592
8769
  const distinctColumns = /* @__PURE__ */ new Map();
@@ -8597,7 +8774,7 @@ function compileClickHouseTraceAggregate(plan) {
8597
8774
  distinctColumns.set(measure.field, column);
8598
8775
  factColumns.push(`CAST(${distinctSql(measure.field)} AS Nullable(String)) AS ${column}`);
8599
8776
  }
8600
- factColumns.push(`toFloat64(${durationMsSql("r.startedAt", "r.endedAt")}) AS durationMs`, `isNotNull(r.error) AS isError`, `cityHash64(r.traceId) AS traceSeed`);
8777
+ factColumns.push(`toFloat64(${durationMsSql("r.startedAt", "r.endedAt")}) AS durationMs`, `${source.isErrorSql} AS isError`, `cityHash64(${source.usageKey.map((column) => `r.${column}`).join(", ")}) AS traceSeed`);
8601
8778
  if (plan.interval !== void 0) {
8602
8779
  const intervalMs = coreStorage.TRACE_AGGREGATE_INTERVAL_MS[plan.interval];
8603
8780
  if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
@@ -8607,14 +8784,14 @@ function compileClickHouseTraceAggregate(plan) {
8607
8784
  const hasCost = measureKinds.has("cost");
8608
8785
  let usageJoin = "";
8609
8786
  if (hasCost || measureKinds.has("tokens")) {
8610
- ctes.push(compileUsageCte(plan, parameters));
8611
- usageJoin = "\n LEFT JOIN usage u ON u.traceId = r.traceId";
8787
+ ctes.push(compileUsageCte(plan, parameters, source));
8788
+ usageJoin = `\n LEFT JOIN usage u ON ${source.usageKey.map((column) => `u.${column} = r.${column}`).join(" AND ")}`;
8612
8789
  USAGE_METRIC_NAMES.forEach((_, index) => factColumns.push(`if(u.hasUsage = 1, u.t${index}, NULL) AS t${index}`));
8613
8790
  factColumns.push(`u.hasUsage = 1 AS usageBearing`, `if(u.hasUsage = 1 AND u.priced = 1, u.cost, NULL) AS traceCost`, `u.hasUsage = 1 AND u.priced = 1 AND u.pricingFailure = 0 AS covered`, `if(u.hasUsage = 1 AND u.priced = 1, u.unitMin, NULL) AS unitMin`, `if(u.hasUsage = 1 AND u.priced = 1, u.unitMax, NULL) AS unitMax`);
8614
8791
  }
8615
8792
  ctes.push(`facts AS (
8616
8793
  SELECT ${factColumns.join(",\n ")}
8617
- FROM candidates r${usageJoin}
8794
+ FROM ${source.from} r${usageJoin}
8618
8795
  )`);
8619
8796
  const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
8620
8797
  const measureSql = (name) => {
@@ -8729,26 +8906,28 @@ function usageMetricIndex(name) {
8729
8906
  return index;
8730
8907
  }
8731
8908
  /**
8732
- * Builds `usage`: one row per candidate trace that has at least one token metric row, holding
8733
- * per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and the trace's
8734
- * pricing state. Metric rows are matched by traceId, usage metric name, tenant scope, and
8735
- * `timestamp >= from` only: a trace that starts in the window can emit metrics after `to`, and
8736
- * the candidates semi-join already bounds the scan.
8909
+ * Builds `usage`: one row per usage key (a trace, or a span) that has at least one token metric
8910
+ * row, holding per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and
8911
+ * its pricing state. Metric rows are matched by the source's usage bound, usage metric name,
8912
+ * tenant scope, and `timestamp >= from` only: a trace or span that starts in the window can emit
8913
+ * metrics after `to`. For spans that makes the read cost grow with the age of `from` (see
8914
+ * `usageBound`).
8737
8915
  *
8738
8916
  * Rows are deduplicated with `LIMIT 1 BY metricId` at query time. `ReplacingMergeTree` only
8739
8917
  * collapses rows with equal sort keys `(name, timestamp, metricId)`, so retried copies with
8740
8918
  * different timestamps never merge, and copies with equal keys stay separate until a background
8741
8919
  * merge runs.
8742
8920
  */
8743
- function compileUsageCte(plan, parameters) {
8921
+ function compileUsageCte(plan, parameters, source) {
8922
+ const key = source.usageKey.join(", ");
8744
8923
  const names = USAGE_METRIC_NAMES.map((name) => parameters.add(name, "String"));
8745
8924
  const costRow = `name IN (${coreStorage.TRACE_AGGREGATE_COST_METRIC_NAMES.map((name) => names[usageMetricIndex(name)]).join(", ")})`;
8746
8925
  const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
8747
8926
  const tenant = compileTenantScope(plan.scope, parameters);
8748
- const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND NOT hasError`;
8927
+ const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND (NOT hasError OR partialCost)`;
8749
8928
  const failed = `${costRow} AND (hasError OR (isNotNull(estimatedCost) AND isNull(costUnit)))`;
8750
8929
  return `usage AS (
8751
- SELECT traceId,
8930
+ SELECT ${key},
8752
8931
  toUInt8(1) AS hasUsage,
8753
8932
  ${names.map((name, index) => `sumIf(value, name = ${name}) AS t${index}`).join(",\n ")},
8754
8933
  sumIf(assumeNotNull(estimatedCost), ${priced}) AS cost,
@@ -8757,34 +8936,42 @@ function compileUsageCte(plan, parameters) {
8757
8936
  minIf(assumeNotNull(costUnit), ${priced}) AS unitMin,
8758
8937
  maxIf(assumeNotNull(costUnit), ${priced}) AS unitMax
8759
8938
  FROM (
8760
- SELECT traceId,
8939
+ SELECT ${key},
8761
8940
  latest.1 AS name,
8762
8941
  latest.2 AS value,
8763
8942
  latest.3 AS estimatedCost,
8764
8943
  latest.4 AS costUnit,
8765
- latest.5 AS hasError
8944
+ latest.5 AS hasError,
8945
+ latest.6 AS partialCost
8766
8946
  FROM (
8767
- SELECT traceId,
8768
- argMax(tuple(name, value, estimatedCost, costUnit, hasError), timestamp) AS latest
8947
+ SELECT ${key},
8948
+ argMax(tuple(name, value, estimatedCost, costUnit, hasError, partialCost), timestamp) AS latest
8769
8949
  FROM (
8770
- SELECT traceId, metricId, timestamp, name, value, estimatedCost, costUnit,
8771
- if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError
8950
+ SELECT ${key}, metricId, timestamp, name, value, estimatedCost, costUnit,
8951
+ if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError,
8952
+ if(${costRow}, ifNull(JSONExtractString(costMetadata, 'error') = 'partial_cost', 0), 0) AS partialCost
8772
8953
  FROM ${TABLE_METRIC_EVENTS}
8773
- WHERE traceId IN (SELECT traceId FROM candidates)
8954
+ WHERE ${source.usageBound}
8774
8955
  AND name IN (${names.join(", ")})
8775
8956
  AND timestamp >= ${from}${tenant}
8776
8957
  )
8777
- GROUP BY traceId, metricId
8958
+ GROUP BY ${key}, metricId
8778
8959
  )
8779
8960
  )
8780
- GROUP BY traceId
8961
+ GROUP BY ${key}
8781
8962
  )`;
8782
8963
  }
8783
8964
  function nullableNumber(value) {
8784
8965
  return value === null || value === void 0 ? null : Number(value);
8785
8966
  }
8786
- async function aggregateTraces(client, plan, timeoutMs) {
8787
- const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceAggregate(plan));
8967
+ function aggregateTraces(client, plan, timeoutMs) {
8968
+ return runAggregate(client, plan, compileClickHouseTraceAggregate(plan), timeoutMs);
8969
+ }
8970
+ async function aggregateSpans(client, plan, timeoutMs) {
8971
+ return await runAggregate(client, plan, compileClickHouseSpanAggregate(plan), timeoutMs);
8972
+ }
8973
+ async function runAggregate(client, plan, compiled, timeoutMs) {
8974
+ const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compiled);
8788
8975
  let truncated;
8789
8976
  let resultRows;
8790
8977
  if (plan.interval === void 0) {
@@ -9680,6 +9867,7 @@ function buildBranchCursor(row) {
9680
9867
  //#endregion
9681
9868
  //#region src/storage/domains/observability/v-next/index.ts
9682
9869
  const spanQueryFeatures = typeof coreStorage.planSpanQuery === "function" ? ["span-query", "span-query-context-fields"] : [];
9870
+ const spanAggregateFeatures = typeof coreStorage.planSpanAggregate === "function" ? ["span-aggregate"] : [];
9683
9871
  const threadQueryFeatures = typeof coreStorage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [];
9684
9872
  function buildSignalMigrationRequiredMessage(args) {
9685
9873
  const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
@@ -10123,6 +10311,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
10123
10311
  "trace-query-root-entity-id",
10124
10312
  "trace-aggregate",
10125
10313
  ...spanQueryFeatures,
10314
+ ...spanAggregateFeatures,
10126
10315
  ...threadQueryFeatures
10127
10316
  ];
10128
10317
  return [
@@ -10147,6 +10336,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
10147
10336
  "trace-query-root-entity-id",
10148
10337
  "trace-aggregate",
10149
10338
  ...spanQueryFeatures,
10339
+ ...spanAggregateFeatures,
10150
10340
  ...threadQueryFeatures
10151
10341
  ];
10152
10342
  }
@@ -10298,6 +10488,18 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
10298
10488
  }, error);
10299
10489
  }
10300
10490
  }
10491
+ async aggregateSpans(plan) {
10492
+ try {
10493
+ return await aggregateSpans(this.#client, plan, this.#traceQueryTimeoutMs);
10494
+ } catch (error) {
10495
+ if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
10496
+ throw new MastraError({
10497
+ id: createStorageErrorId("CLICKHOUSE", "AGGREGATE_SPANS", "FAILED"),
10498
+ domain: ErrorDomain.STORAGE,
10499
+ category: ErrorCategory.THIRD_PARTY
10500
+ }, error);
10501
+ }
10502
+ }
10301
10503
  async getTraceQueryObservedFields(plan) {
10302
10504
  try {
10303
10505
  return await getTraceQueryObservedFields(this.#client, plan, this.#traceQueryDiscoveryLimits);