@mastra/clickhouse 1.27.0 → 1.28.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
@@ -6906,7 +6906,17 @@ const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
6906
6906
  const timeToFirstChunkSupported = "timeToFirstChunkMs" in (coreStorage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
6907
6907
  /** Core before the sort rejects `orderBy` on the field, so the sort is only declared when core plans it. */
6908
6908
  const timeToFirstChunkSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstChunkMs") === true;
6909
- const timeToFirstChunkFeatures = timeToFirstChunkSupported ? timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk", "trace-query-time-to-first-chunk-sort"] : ["trace-query-time-to-first-chunk"] : [];
6909
+ /** Same rule for time to first token: returned and sorted only when core knows the field. */
6910
+ const timeToFirstTokenSupported = "timeToFirstTokenMs" in (coreStorage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
6911
+ const timeToFirstTokenSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstTokenMs") === true;
6912
+ const timingFeatures = [
6913
+ ...timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [],
6914
+ ...timeToFirstChunkSupported && timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk-sort"] : [],
6915
+ ...timeToFirstTokenSupported ? ["trace-query-time-to-first-token"] : [],
6916
+ ...timeToFirstTokenSupported && timeToFirstTokenSortSupported ? ["trace-query-time-to-first-token-sort"] : []
6917
+ ];
6918
+ /** Order fields holding numbers that can be empty; the rest are timestamps. */
6919
+ const NUMERIC_ORDER_FIELDS = /* @__PURE__ */ new Set(["timeToFirstChunkMs", "timeToFirstTokenMs"]);
6910
6920
  function durationMsSql(startedAt, endedAt) {
6911
6921
  return `dateDiff('millisecond', ${startedAt}, ${endedAt})`;
6912
6922
  }
@@ -6959,6 +6969,10 @@ const TRACE_FIELDS = {
6959
6969
  sql: "r.timeToFirstChunkMs",
6960
6970
  parameterType: "Float64"
6961
6971
  },
6972
+ timeToFirstTokenMs: {
6973
+ sql: "r.timeToFirstTokenMs",
6974
+ parameterType: "Float64"
6975
+ },
6962
6976
  entityId: {
6963
6977
  sql: "r.entityId",
6964
6978
  parameterType: "String"
@@ -7025,6 +7039,10 @@ const SPAN_FIELDS = {
7025
7039
  sql: "s.timeToFirstChunkMs",
7026
7040
  parameterType: "Float64"
7027
7041
  },
7042
+ timeToFirstTokenMs: {
7043
+ sql: "s.timeToFirstTokenMs",
7044
+ parameterType: "Float64"
7045
+ },
7028
7046
  status: {
7029
7047
  sql: "s.status",
7030
7048
  parameterType: "String"
@@ -7237,6 +7255,7 @@ const TRACE_SELECT = `
7237
7255
  r.entityType AS entityType,
7238
7256
  r.environment AS environment,
7239
7257
  r.timeToFirstChunkMs AS timeToFirstChunkMs,
7258
+ r.timeToFirstTokenMs AS timeToFirstTokenMs,
7240
7259
  ${TRACE_STATUS_SQL} AS status`;
7241
7260
  var ParameterBuilder = class {
7242
7261
  params = {};
@@ -7253,7 +7272,7 @@ function fieldDefinition(registry, field) {
7253
7272
  return definition;
7254
7273
  }
7255
7274
  function resolveOrderField(field) {
7256
- if (field === "startedAt" || field === "endedAt" || field === "timeToFirstChunkMs") return field;
7275
+ if (field === "startedAt" || field === "endedAt" || NUMERIC_ORDER_FIELDS.has(field)) return field;
7257
7276
  throw new Error(`Unsupported trusted trace-query field: ${field}`);
7258
7277
  }
7259
7278
  function isMetadataField(field) {
@@ -7443,6 +7462,7 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
7443
7462
  endedAt,
7444
7463
  ${durationMsSql("startedAt", "endedAt")} AS durationMs,
7445
7464
  timeToFirstChunkMs,
7465
+ timeToFirstTokenMs,
7446
7466
  if(isNotNull(error), 'error', 'success') AS status,
7447
7467
  error,
7448
7468
  entityType,
@@ -7558,7 +7578,7 @@ function traceOrder(plan) {
7558
7578
  function keysetCursorCondition(plan, order, parameters) {
7559
7579
  if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
7560
7580
  if (plan.cursor.sortValue === null) return `(isNull(sortValue) AND traceId > ${parameters.add(plan.cursor.traceId, "String")})`;
7561
- const numeric = order.field === "timeToFirstChunkMs";
7581
+ const numeric = NUMERIC_ORDER_FIELDS.has(order.field);
7562
7582
  const comparison = order.direction === "ASC" ? ">" : "<";
7563
7583
  const sortValue = parameters.add(plan.cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
7564
7584
  return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")})${numeric ? " OR isNull(sortValue)" : ""})`;
@@ -7572,7 +7592,7 @@ function compileClickHouseTraceQueryKeys(plan) {
7572
7592
  if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
7573
7593
  const parameters = new ParameterBuilder();
7574
7594
  const order = traceOrder(plan);
7575
- const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt, r.timeToFirstChunkMs AS timeToFirstChunkMs", parameters, QUERY_TRACES_SCOPE);
7595
+ const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt, r.timeToFirstChunkMs AS timeToFirstChunkMs, r.timeToFirstTokenMs AS timeToFirstTokenMs", parameters, QUERY_TRACES_SCOPE);
7576
7596
  const cursor = keysetCursorCondition(plan, order, parameters);
7577
7597
  const limit = plan.paginationMode === "page" ? `LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}` : `LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`;
7578
7598
  return {
@@ -7973,7 +7993,8 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7973
7993
  entityType: row.entityType == null ? null : String(row.entityType),
7974
7994
  environment: row.environment == null ? null : String(row.environment),
7975
7995
  status: row.status,
7976
- ...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) }
7996
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) },
7997
+ ...timeToFirstTokenSupported && { timeToFirstTokenMs: row.timeToFirstTokenMs == null ? null : Number(row.timeToFirstTokenMs) }
7977
7998
  });
7978
7999
  if (plan.paginationMode === "delta") {
7979
8000
  const rows = await run(compileClickHouseTraceQuery(plan, deltaHead));
@@ -7996,7 +8017,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7996
8017
  });
7997
8018
  }
7998
8019
  const keys = await run(compileClickHouseTraceQueryKeys(plan));
7999
- const sortValue = (value) => plan.orderBy.field !== "timeToFirstChunkMs" ? asIsoTimestamp(value) : value == null ? null : Number(value);
8020
+ const sortValue = (value) => !NUMERIC_ORDER_FIELDS.has(plan.orderBy.field) ? asIsoTimestamp(value) : value == null ? null : Number(value);
8000
8021
  let total = Number(keys[0]?.__total ?? 0);
8001
8022
  if (plan.paginationMode === "page" && keys.length === 0 && plan.page > 0) {
8002
8023
  const [stats] = await run(compileClickHouseTraceQueryTotal(plan));
@@ -8099,6 +8120,7 @@ const displayFields = [
8099
8120
  "provider",
8100
8121
  "durationMs",
8101
8122
  "timeToFirstChunkMs",
8123
+ "timeToFirstTokenMs",
8102
8124
  "status"
8103
8125
  ];
8104
8126
  var Parameters = class {
@@ -8127,7 +8149,7 @@ function afterCursor$1(plan, parameters) {
8127
8149
  const equal = [];
8128
8150
  if (cursor.sortValue === null) equal.push(`isNull(${field})`);
8129
8151
  else {
8130
- const numeric = plan.orderBy.field === "timeToFirstChunkMs";
8152
+ const numeric = plan.orderBy.field === "timeToFirstChunkMs" || plan.orderBy.field === "timeToFirstTokenMs";
8131
8153
  const value = parameters.add(cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
8132
8154
  terms.push(`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${value}`);
8133
8155
  if (numeric) terms.push(`isNull(${field})`);
@@ -8223,6 +8245,7 @@ const TRACE_BRANCHES_SPAN_FIELD_COLUMNS = {
8223
8245
  "startedAt",
8224
8246
  "endedAt",
8225
8247
  "timeToFirstChunkMs",
8248
+ "timeToFirstTokenMs",
8226
8249
  "threadId",
8227
8250
  "runId",
8228
8251
  "sessionId",
@@ -8383,12 +8406,13 @@ async function querySpans(client, plan, timeoutMs) {
8383
8406
  const hydrated = page.flatMap((identity) => {
8384
8407
  const row = byIdentity.get(coreStorage.spanQueryIdentityKey(identity));
8385
8408
  if (!row) return [];
8386
- const { inputPreview: _input, outputPreview: _output, errorPreview: _error, timeToFirstChunkMs, ...metadata } = row;
8409
+ const { inputPreview: _input, outputPreview: _output, errorPreview: _error, timeToFirstChunkMs, timeToFirstTokenMs, ...metadata } = row;
8387
8410
  return [{
8388
8411
  ...metadata,
8389
8412
  ...identity,
8390
8413
  durationMs: Number(row.durationMs),
8391
- ...timeToFirstChunkSupported && { timeToFirstChunkMs: timeToFirstChunkMs == null ? null : Number(timeToFirstChunkMs) }
8414
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs: timeToFirstChunkMs == null ? null : Number(timeToFirstChunkMs) },
8415
+ ...timeToFirstTokenSupported && { timeToFirstTokenMs: timeToFirstTokenMs == null ? null : Number(timeToFirstTokenMs) }
8392
8416
  }];
8393
8417
  });
8394
8418
  let metrics = [];
@@ -8511,7 +8535,11 @@ function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
8511
8535
  const after = afterCursor(plan, parameters);
8512
8536
  const qualify = plan.timeRange.mode === "traces" ? "s.inWindow > 0" : `s.${plan.timeRange.field} >= ${from} AND s.${plan.timeRange.field} < ${to}`;
8513
8537
  const filtered = predicateFields(plan.where);
8514
- const firstContext = [...contextFields, "timeToFirstChunkMs"].filter((field) => field === "resourceId" || filtered.has(field));
8538
+ const firstContext = [
8539
+ ...contextFields,
8540
+ "timeToFirstChunkMs",
8541
+ "timeToFirstTokenMs"
8542
+ ].filter((field) => field === "resourceId" || filtered.has(field));
8515
8543
  const firstFields = [
8516
8544
  "startedAt",
8517
8545
  "traceId",
@@ -8535,7 +8563,7 @@ function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
8535
8563
  ...firstContext,
8536
8564
  ...filtered.has("status") ? ["status"] : []
8537
8565
  ];
8538
- const rootExpression = (field) => field === "timeToFirstChunkMs" ? "if(spanType = 'agent_run', timeToFirstChunkMs, NULL)" : field === "status" ? "if(isNull(error), 'success', 'error')" : field;
8566
+ const rootExpression = (field) => field === "timeToFirstChunkMs" || field === "timeToFirstTokenMs" ? `if(spanType = 'agent_run', ${field}, NULL)` : field === "status" ? "if(isNull(error), 'success', 'error')" : field;
8539
8567
  const sortedRoots = `roots AS (
8540
8568
  SELECT organizationId, traceId, ${rootDataFields.map((field) => `${rootExpression(field)} AS ${field}`).join(", ")}
8541
8569
  FROM ${TABLE_TRACE_ROOTS} ${scope.length || strategy === "candidates" ? `WHERE ${[...scope, ...strategy === "candidates" ? [candidateTracesFilter] : []].join(" AND ")}` : ""}
@@ -8639,6 +8667,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8639
8667
  for (const batch of batches([...roots.values()])) stored.push(...await run({
8640
8668
  query: `SELECT organizationId, traceId, spanId, startedAt, endedAt,
8641
8669
  ${contextFields.join(", ")}, if(spanType = 'agent_run', timeToFirstChunkMs, NULL) AS timeToFirstChunkMs,
8670
+ if(spanType = 'agent_run', timeToFirstTokenMs, NULL) AS timeToFirstTokenMs,
8642
8671
  if(isNull(error), 'success', 'error') AS status, ${PREVIEW_COLUMNS.join(", ")}
8643
8672
  FROM ${TABLE_TRACE_ROOTS} WHERE ${rootWhere}
8644
8673
  ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
@@ -8672,6 +8701,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8672
8701
  endedAt: iso(row.endedAt),
8673
8702
  traceCount: Number(row.traceCount),
8674
8703
  timeToFirstChunkMs: first.timeToFirstChunkMs,
8704
+ ...timeToFirstTokenSupported && { timeToFirstTokenMs: first.timeToFirstTokenMs },
8675
8705
  inputPreview: resolveStoredPreviews(first).inputPreview,
8676
8706
  outputPreview: resolveStoredPreviews(last).outputPreview,
8677
8707
  errorPreview: resolveStoredPreviews(last).errorPreview
@@ -10328,7 +10358,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
10328
10358
  "trace-query-tenant-scope",
10329
10359
  "feedback",
10330
10360
  "trace-query-context-ids",
10331
- ...timeToFirstChunkFeatures,
10361
+ ...timingFeatures,
10332
10362
  "trace-query-root-span-fields",
10333
10363
  "trace-query-root-entity-id",
10334
10364
  "trace-aggregate",
@@ -10353,7 +10383,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
10353
10383
  "trace-query-tenant-scope",
10354
10384
  "feedback",
10355
10385
  "trace-query-context-ids",
10356
- ...timeToFirstChunkFeatures,
10386
+ ...timingFeatures,
10357
10387
  "trace-query-root-span-fields",
10358
10388
  "trace-query-root-entity-id",
10359
10389
  "trace-aggregate",