@mastra/clickhouse 1.23.0-alpha.2 → 1.23.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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +101 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +101 -7
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +5 -0
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1689,6 +1689,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
|
|
|
1689
1689
|
});
|
|
1690
1690
|
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
1691
1691
|
const updatePromises = [];
|
|
1692
|
+
const writtenContent = /* @__PURE__ */ new Map();
|
|
1692
1693
|
for (const existingMessage of parsedExistingMessages) {
|
|
1693
1694
|
const updatePayload = messages.find((m) => m.id === existingMessage.id);
|
|
1694
1695
|
if (!updatePayload) continue;
|
|
@@ -1715,6 +1716,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
|
|
|
1715
1716
|
};
|
|
1716
1717
|
setClauses.push(`content = {var_content_${paramIdx}:String}`);
|
|
1717
1718
|
values[`var_content_${paramIdx}`] = JSON.stringify(newContent);
|
|
1719
|
+
writtenContent.set(id, values[`var_content_${paramIdx}`]);
|
|
1718
1720
|
paramIdx++;
|
|
1719
1721
|
delete updatableFields.content;
|
|
1720
1722
|
}
|
|
@@ -1731,7 +1733,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
|
|
|
1731
1733
|
UPDATE ${setClauses.join(", ")}
|
|
1732
1734
|
WHERE id = {var_id_${paramIdx}:String}
|
|
1733
1735
|
`;
|
|
1734
|
-
|
|
1736
|
+
this.logger?.debug?.("Updating message", { id });
|
|
1735
1737
|
updatePromises.push(this.client.command({
|
|
1736
1738
|
query: updateQuery,
|
|
1737
1739
|
query_params: values,
|
|
@@ -1767,7 +1769,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
|
|
|
1767
1769
|
if (updatedMessage) {
|
|
1768
1770
|
let needsRetry = false;
|
|
1769
1771
|
for (const [key, value] of Object.entries(fieldsToUpdate)) if (key === "content") {
|
|
1770
|
-
const expectedContent =
|
|
1772
|
+
const expectedContent = writtenContent.get(id);
|
|
1771
1773
|
if ((typeof updatedMessage.content === "string" ? updatedMessage.content : JSON.stringify(updatedMessage.content)) !== expectedContent) {
|
|
1772
1774
|
needsRetry = true;
|
|
1773
1775
|
break;
|
|
@@ -1777,7 +1779,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
|
|
|
1777
1779
|
break;
|
|
1778
1780
|
}
|
|
1779
1781
|
if (needsRetry) {
|
|
1780
|
-
|
|
1782
|
+
this.logger?.warn?.("Update not applied correctly, retrying with DELETE + INSERT", { id });
|
|
1781
1783
|
await this.client.command({
|
|
1782
1784
|
query: `DELETE FROM ${TABLE_MESSAGES} WHERE id = {messageId:String}`,
|
|
1783
1785
|
query_params: { messageId: id },
|
|
@@ -2412,7 +2414,7 @@ var ObservabilityStorageClickhouse = class extends ObservabilityStorage {
|
|
|
2412
2414
|
if (filters.hasChildError !== void 0) {
|
|
2413
2415
|
const finalClause = (TABLE_ENGINES[TABLE_SPANS] ?? "MergeTree()").startsWith("ReplacingMergeTree") ? "FINAL" : "";
|
|
2414
2416
|
conditions.push(`traceId ${filters.hasChildError ? "IN" : "NOT IN"} (
|
|
2415
|
-
SELECT c.traceId FROM ${TABLE_SPANS} ${finalClause}
|
|
2417
|
+
SELECT c.traceId FROM ${TABLE_SPANS} c ${finalClause}
|
|
2416
2418
|
WHERE c.error IS NOT NULL AND c.error != ''
|
|
2417
2419
|
)`);
|
|
2418
2420
|
}
|
|
@@ -7861,12 +7863,31 @@ function compileClickHouseTraceAggregate(plan) {
|
|
|
7861
7863
|
if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
|
|
7862
7864
|
factColumns.push(`fromUnixTimestamp64Milli(intDiv(toUnixTimestamp64Milli(r.startedAt), ${intervalMs}) * ${intervalMs}, 'UTC') AS bucket`);
|
|
7863
7865
|
}
|
|
7866
|
+
const measureKinds = new Set(plan.measures.map((measure) => measureRule(measure.name)?.kind));
|
|
7867
|
+
const hasCost = measureKinds.has("cost");
|
|
7868
|
+
let usageJoin = "";
|
|
7869
|
+
if (hasCost || measureKinds.has("tokens")) {
|
|
7870
|
+
ctes.push(compileUsageCte(plan, parameters));
|
|
7871
|
+
usageJoin = "\n LEFT JOIN usage u ON u.traceId = r.traceId";
|
|
7872
|
+
USAGE_METRIC_NAMES.forEach((_, index) => factColumns.push(`if(u.hasUsage = 1, u.t${index}, NULL) AS t${index}`));
|
|
7873
|
+
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`);
|
|
7874
|
+
}
|
|
7864
7875
|
ctes.push(`facts AS (
|
|
7865
7876
|
SELECT ${factColumns.join(",\n ")}
|
|
7866
|
-
FROM candidates r
|
|
7877
|
+
FROM candidates r${usageJoin}
|
|
7867
7878
|
)`);
|
|
7879
|
+
const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
|
|
7868
7880
|
const measureSql = (name) => {
|
|
7869
7881
|
if (name === "count") return "toFloat64(count())";
|
|
7882
|
+
const rule = measureRule(name);
|
|
7883
|
+
if (rule?.kind === "tokens" && rule.metricNames) {
|
|
7884
|
+
const value = rule.metricNames.map((metricName) => `t${usageMetricIndex(metricName)}`).join(" + ");
|
|
7885
|
+
return `toFloat64(${rule.statistic === "avg" ? "avgOrNull" : "sumOrNull"}(${value}))`;
|
|
7886
|
+
}
|
|
7887
|
+
if (rule?.kind === "cost") {
|
|
7888
|
+
const aggregate = rule.statistic === "avg" ? "avgOrNull" : "sumOrNull";
|
|
7889
|
+
return `if(${mixedUnitsSql}, NULL, toFloat64(${aggregate}(traceCost)))`;
|
|
7890
|
+
}
|
|
7870
7891
|
if (name === "errorCount") return "toFloat64(countIf(isError))";
|
|
7871
7892
|
if (name === "errorRate") return "countIf(isError) / count()";
|
|
7872
7893
|
if (name === "duration.avg") return "avg(durationMs)";
|
|
@@ -7904,6 +7925,11 @@ function compileClickHouseTraceAggregate(plan) {
|
|
|
7904
7925
|
const tiebreakSql = dimensionColumns.map((column) => `${column} ASC NULLS LAST`);
|
|
7905
7926
|
const measureColumns = plan.measures.map((measure, index) => `${measureSql(measure.name)} AS m${index}`);
|
|
7906
7927
|
const measureNames = plan.measures.map((_, index) => `m${index}`);
|
|
7928
|
+
if (hasCost) {
|
|
7929
|
+
const mixedUnit = parameters.add(coreStorage.TRACE_AGGREGATE_MIXED_COST_UNIT, "String");
|
|
7930
|
+
measureColumns.push(`countIf(covered) / nullIf(countIf(usageBearing), 0) AS costCoverage`, `if(${mixedUnitsSql}, ${mixedUnit}, minOrNull(unitMin)) AS costUnit`);
|
|
7931
|
+
measureNames.push("costCoverage", "costUnit");
|
|
7932
|
+
}
|
|
7907
7933
|
if (plan.interval === void 0) {
|
|
7908
7934
|
const limit = parameters.add(plan.limit + 1, "UInt64");
|
|
7909
7935
|
return {
|
|
@@ -7925,7 +7951,7 @@ LIMIT ${limit}`,
|
|
|
7925
7951
|
"bucket",
|
|
7926
7952
|
"grouping(bucket) = 1 AS __isGroup",
|
|
7927
7953
|
...measureColumns,
|
|
7928
|
-
`(${havingSql}) AS __keep`,
|
|
7954
|
+
`ifNull(${havingSql}, 0) AS __keep`,
|
|
7929
7955
|
`${orderTarget} AS __order`
|
|
7930
7956
|
].join(", ")}
|
|
7931
7957
|
FROM facts
|
|
@@ -7953,6 +7979,70 @@ ORDER BY __rank ASC, bucket ASC`,
|
|
|
7953
7979
|
query_params: parameters.params
|
|
7954
7980
|
};
|
|
7955
7981
|
}
|
|
7982
|
+
const USAGE_METRIC_NAMES = coreStorage.TRACE_AGGREGATE_USAGE_METRIC_NAMES;
|
|
7983
|
+
function measureRule(name) {
|
|
7984
|
+
return coreStorage.isTraceAggregateCanonicalMeasure(name) ? coreStorage.TRACE_AGGREGATE_MEASURE_REGISTRY[name] : void 0;
|
|
7985
|
+
}
|
|
7986
|
+
function usageMetricIndex(name) {
|
|
7987
|
+
const index = USAGE_METRIC_NAMES.indexOf(name);
|
|
7988
|
+
if (index === -1) throw new Error(`Unsupported trusted trace-aggregate token metric: ${name}`);
|
|
7989
|
+
return index;
|
|
7990
|
+
}
|
|
7991
|
+
/**
|
|
7992
|
+
* Builds `usage`: one row per candidate trace that has at least one token metric row, holding
|
|
7993
|
+
* per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and the trace's
|
|
7994
|
+
* pricing state. Metric rows are matched by traceId, usage metric name, tenant scope, and
|
|
7995
|
+
* `timestamp >= from` only: a trace that starts in the window can emit metrics after `to`, and
|
|
7996
|
+
* the candidates semi-join already bounds the scan.
|
|
7997
|
+
*
|
|
7998
|
+
* Rows are deduplicated with `LIMIT 1 BY metricId` at query time. `ReplacingMergeTree` only
|
|
7999
|
+
* collapses rows with equal sort keys `(name, timestamp, metricId)`, so retried copies with
|
|
8000
|
+
* different timestamps never merge, and copies with equal keys stay separate until a background
|
|
8001
|
+
* merge runs.
|
|
8002
|
+
*/
|
|
8003
|
+
function compileUsageCte(plan, parameters) {
|
|
8004
|
+
const names = USAGE_METRIC_NAMES.map((name) => parameters.add(name, "String"));
|
|
8005
|
+
const costRow = `name IN (${coreStorage.TRACE_AGGREGATE_COST_METRIC_NAMES.map((name) => names[usageMetricIndex(name)]).join(", ")})`;
|
|
8006
|
+
const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
|
|
8007
|
+
const tenant = compileTenantScope(plan.scope, parameters);
|
|
8008
|
+
const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND NOT hasError`;
|
|
8009
|
+
const failed = `${costRow} AND (hasError OR (isNotNull(estimatedCost) AND isNull(costUnit)))`;
|
|
8010
|
+
return `usage AS (
|
|
8011
|
+
SELECT traceId,
|
|
8012
|
+
toUInt8(1) AS hasUsage,
|
|
8013
|
+
${names.map((name, index) => `sumIf(value, name = ${name}) AS t${index}`).join(",\n ")},
|
|
8014
|
+
sumIf(assumeNotNull(estimatedCost), ${priced}) AS cost,
|
|
8015
|
+
toUInt8(countIf(${priced}) > 0) AS priced,
|
|
8016
|
+
toUInt8(countIf(${failed}) > 0) AS pricingFailure,
|
|
8017
|
+
minIf(assumeNotNull(costUnit), ${priced}) AS unitMin,
|
|
8018
|
+
maxIf(assumeNotNull(costUnit), ${priced}) AS unitMax
|
|
8019
|
+
FROM (
|
|
8020
|
+
SELECT traceId,
|
|
8021
|
+
latest.1 AS name,
|
|
8022
|
+
latest.2 AS value,
|
|
8023
|
+
latest.3 AS estimatedCost,
|
|
8024
|
+
latest.4 AS costUnit,
|
|
8025
|
+
latest.5 AS hasError
|
|
8026
|
+
FROM (
|
|
8027
|
+
SELECT traceId,
|
|
8028
|
+
argMax(tuple(name, value, estimatedCost, costUnit, hasError), timestamp) AS latest
|
|
8029
|
+
FROM (
|
|
8030
|
+
SELECT traceId, metricId, timestamp, name, value, estimatedCost, costUnit,
|
|
8031
|
+
if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError
|
|
8032
|
+
FROM ${TABLE_METRIC_EVENTS}
|
|
8033
|
+
WHERE traceId IN (SELECT traceId FROM candidates)
|
|
8034
|
+
AND name IN (${names.join(", ")})
|
|
8035
|
+
AND timestamp >= ${from}${tenant}
|
|
8036
|
+
)
|
|
8037
|
+
GROUP BY traceId, metricId
|
|
8038
|
+
)
|
|
8039
|
+
)
|
|
8040
|
+
GROUP BY traceId
|
|
8041
|
+
)`;
|
|
8042
|
+
}
|
|
8043
|
+
function nullableNumber(value) {
|
|
8044
|
+
return value === null || value === void 0 ? null : Number(value);
|
|
8045
|
+
}
|
|
7956
8046
|
async function aggregateTraces(client, plan, timeoutMs) {
|
|
7957
8047
|
const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceAggregate(plan));
|
|
7958
8048
|
let truncated;
|
|
@@ -7966,7 +8056,11 @@ async function aggregateTraces(client, plan, timeoutMs) {
|
|
|
7966
8056
|
}
|
|
7967
8057
|
return {
|
|
7968
8058
|
rows: resultRows.map((row) => {
|
|
7969
|
-
const shaped = { measures: Object.fromEntries(plan.measures.map((measure, index) => [measure.name,
|
|
8059
|
+
const shaped = { measures: Object.fromEntries(plan.measures.map((measure, index) => [measure.name, nullableNumber(row[`m${index}`])])) };
|
|
8060
|
+
if ("costCoverage" in row) shaped.cost = {
|
|
8061
|
+
coverage: nullableNumber(row.costCoverage),
|
|
8062
|
+
unit: row.costUnit ?? null
|
|
8063
|
+
};
|
|
7970
8064
|
if (plan.dimensions.length > 0) shaped.dimensions = Object.fromEntries(plan.dimensions.map((dimension, index) => [dimension, row[`d${index}`] ?? null]));
|
|
7971
8065
|
if (plan.interval !== void 0) shaped.bucket = new Date(row.bucket).toISOString();
|
|
7972
8066
|
return shaped;
|