@mastra/react 1.13.0-alpha.3 → 1.13.0-alpha.5
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/{accumulator-BmD8-x9I.cjs → accumulator-Bjfckb5t.cjs} +5 -9
- package/dist/{accumulator-BmD8-x9I.cjs.map → accumulator-Bjfckb5t.cjs.map} +1 -1
- package/dist/{accumulator-D9jAph99.js → accumulator-DysrPe6U.js} +5 -9
- package/dist/{accumulator-D9jAph99.js.map → accumulator-DysrPe6U.js.map} +1 -1
- package/dist/chat/index.cjs +1 -1
- package/dist/chat/index.js +1 -1
- package/dist/{combine-by-ids-CcaZKGT9.js → combine-by-ids-C-0oe4eH.js} +2 -3
- package/dist/{combine-by-ids-CcaZKGT9.js.map → combine-by-ids-C-0oe4eH.js.map} +1 -1
- package/dist/{combine-by-ids-D_lp6Dvq.cjs → combine-by-ids-ERVPXXBi.cjs} +2 -3
- package/dist/{combine-by-ids-D_lp6Dvq.cjs.map → combine-by-ids-ERVPXXBi.cjs.map} +1 -1
- package/dist/{experiments-CJw1N3FB.cjs → experiments-Duv0RgkM.cjs} +12 -19
- package/dist/{experiments-CJw1N3FB.cjs.map → experiments-Duv0RgkM.cjs.map} +1 -1
- package/dist/{experiments-B7vRMK5T.js → experiments-DyecjEut.js} +12 -19
- package/dist/{experiments-B7vRMK5T.js.map → experiments-DyecjEut.js.map} +1 -1
- package/dist/{feedback-refetch-interval-XD5FVc2k.cjs → feedback-refetch-interval-BBcT9r8s.cjs} +3 -4
- package/dist/{feedback-refetch-interval-XD5FVc2k.cjs.map → feedback-refetch-interval-BBcT9r8s.cjs.map} +1 -1
- package/dist/{feedback-refetch-interval-DLHzQoVV.js → feedback-refetch-interval-C12Nzb2r.js} +3 -4
- package/dist/{feedback-refetch-interval-DLHzQoVV.js.map → feedback-refetch-interval-C12Nzb2r.js.map} +1 -1
- package/dist/hooks/agents/index.cjs +19 -37
- package/dist/hooks/agents/index.cjs.map +1 -1
- package/dist/hooks/agents/index.js +19 -37
- package/dist/hooks/agents/index.js.map +1 -1
- package/dist/hooks/auth/index.cjs +11 -21
- package/dist/hooks/auth/index.cjs.map +1 -1
- package/dist/hooks/auth/index.js +11 -21
- package/dist/hooks/auth/index.js.map +1 -1
- package/dist/hooks/capabilities/index.cjs +11 -21
- package/dist/hooks/capabilities/index.cjs.map +1 -1
- package/dist/hooks/capabilities/index.js +11 -21
- package/dist/hooks/capabilities/index.js.map +1 -1
- package/dist/hooks/configuration/index.cjs +2 -3
- package/dist/hooks/configuration/index.js +2 -3
- package/dist/hooks/datasets/index.cjs +12 -21
- package/dist/hooks/datasets/index.cjs.map +1 -1
- package/dist/hooks/datasets/index.js +12 -21
- package/dist/hooks/datasets/index.js.map +1 -1
- package/dist/hooks/embedders/index.cjs +2 -3
- package/dist/hooks/embedders/index.js +2 -3
- package/dist/hooks/experiments/index.cjs +1 -1
- package/dist/hooks/experiments/index.js +1 -1
- package/dist/hooks/feedback/index.cjs +3 -4
- package/dist/hooks/feedback/index.js +3 -4
- package/dist/hooks/llm/index.cjs +2 -3
- package/dist/hooks/llm/index.js +2 -3
- package/dist/hooks/logs/index.cjs +4 -5
- package/dist/hooks/logs/index.js +4 -5
- package/dist/hooks/mcps/index.cjs +8 -15
- package/dist/hooks/mcps/index.cjs.map +1 -1
- package/dist/hooks/mcps/index.js +8 -15
- package/dist/hooks/mcps/index.js.map +1 -1
- package/dist/hooks/memory/index.cjs +5 -9
- package/dist/hooks/memory/index.cjs.map +1 -1
- package/dist/hooks/memory/index.js +5 -9
- package/dist/hooks/memory/index.js.map +1 -1
- package/dist/hooks/metrics/index.cjs +19 -36
- package/dist/hooks/metrics/index.cjs.map +1 -1
- package/dist/hooks/metrics/index.js +19 -36
- package/dist/hooks/metrics/index.js.map +1 -1
- package/dist/hooks/processors/index.cjs +2 -3
- package/dist/hooks/processors/index.js +2 -3
- package/dist/hooks/prompt-blocks/index.cjs +3 -5
- package/dist/hooks/prompt-blocks/index.cjs.map +1 -1
- package/dist/hooks/prompt-blocks/index.js +3 -5
- package/dist/hooks/prompt-blocks/index.js.map +1 -1
- package/dist/hooks/query/index.cjs +2 -3
- package/dist/hooks/query/index.js +2 -3
- package/dist/hooks/review/index.cjs +4 -6
- package/dist/hooks/review/index.cjs.map +1 -1
- package/dist/hooks/review/index.js +4 -6
- package/dist/hooks/review/index.js.map +1 -1
- package/dist/hooks/schedules/index.cjs +6 -10
- package/dist/hooks/schedules/index.cjs.map +1 -1
- package/dist/hooks/schedules/index.js +6 -10
- package/dist/hooks/schedules/index.js.map +1 -1
- package/dist/hooks/scores/index.cjs +8 -13
- package/dist/hooks/scores/index.cjs.map +1 -1
- package/dist/hooks/scores/index.js +8 -13
- package/dist/hooks/scores/index.js.map +1 -1
- package/dist/hooks/templates/index.cjs +2 -3
- package/dist/hooks/templates/index.js +2 -3
- package/dist/hooks/tool-providers/index.cjs +10 -19
- package/dist/hooks/tool-providers/index.cjs.map +1 -1
- package/dist/hooks/tool-providers/index.js +10 -19
- package/dist/hooks/tool-providers/index.js.map +1 -1
- package/dist/hooks/tools/index.cjs +3 -5
- package/dist/hooks/tools/index.cjs.map +1 -1
- package/dist/hooks/tools/index.js +3 -5
- package/dist/hooks/tools/index.js.map +1 -1
- package/dist/hooks/traces/index.cjs +71 -54
- package/dist/hooks/traces/index.cjs.map +1 -1
- package/dist/hooks/traces/index.d.ts +2 -0
- package/dist/hooks/traces/index.d.ts.map +1 -1
- package/dist/hooks/traces/index.js +70 -55
- package/dist/hooks/traces/index.js.map +1 -1
- package/dist/hooks/traces/use-span-aggregate.d.ts +16 -0
- package/dist/hooks/traces/use-span-aggregate.d.ts.map +1 -0
- package/dist/hooks/traces/use-trace-aggregate.d.ts +17 -0
- package/dist/hooks/traces/use-trace-aggregate.d.ts.map +1 -0
- package/dist/hooks/traces/use-trace-column-preferences-storage-key.d.ts +4 -2
- package/dist/hooks/traces/use-trace-column-preferences-storage-key.d.ts.map +1 -1
- package/dist/hooks/vectors/index.cjs +2 -3
- package/dist/hooks/vectors/index.js +2 -3
- package/dist/hooks/voice/index.cjs +3 -4
- package/dist/hooks/voice/index.js +3 -4
- package/dist/hooks/workflows/index.cjs +7 -11
- package/dist/hooks/workflows/index.cjs.map +1 -1
- package/dist/hooks/workflows/index.js +7 -11
- package/dist/hooks/workflows/index.js.map +1 -1
- package/dist/hooks/workspace/index.cjs +12 -22
- package/dist/hooks/workspace/index.cjs.map +1 -1
- package/dist/hooks/workspace/index.js +12 -22
- package/dist/hooks/workspace/index.js.map +1 -1
- package/dist/{hooks-XYsmqFCz.cjs → hooks-Bt15IwzP.cjs} +6 -9
- package/dist/{hooks-XYsmqFCz.cjs.map → hooks-Bt15IwzP.cjs.map} +1 -1
- package/dist/{hooks-DG-Y1KFe.js → hooks-Btt7PyI-.js} +6 -9
- package/dist/{hooks-DG-Y1KFe.js.map → hooks-Btt7PyI-.js.map} +1 -1
- package/dist/{hooks-BfysjY_K.js → hooks-CEikbhrN.js} +9 -15
- package/dist/{hooks-BfysjY_K.js.map → hooks-CEikbhrN.js.map} +1 -1
- package/dist/{hooks-BbUMSab8.cjs → hooks-D6LDSEcm.cjs} +9 -15
- package/dist/{hooks-BbUMSab8.cjs.map → hooks-D6LDSEcm.cjs.map} +1 -1
- package/dist/index.cjs +7 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -9
- package/dist/index.js.map +1 -1
- package/dist/{map-CDv4fbu4.js → map-5-4U5rwA.js} +2 -3
- package/dist/{map-CDv4fbu4.js.map → map-5-4U5rwA.js.map} +1 -1
- package/dist/{map-wjmL1y5O.cjs → map-BGoWBTGy.cjs} +2 -3
- package/dist/{map-wjmL1y5O.cjs.map → map-BGoWBTGy.cjs.map} +1 -1
- package/dist/{mastra-client-context-Bx_g84YB.js → mastra-client-context-DQ5z_ze9.js} +2 -3
- package/dist/{mastra-client-context-Bx_g84YB.js.map → mastra-client-context-DQ5z_ze9.js.map} +1 -1
- package/dist/{mastra-client-context-DzX3f0RF.cjs → mastra-client-context-DX8wJM9p.cjs} +2 -3
- package/dist/{mastra-client-context-DzX3f0RF.cjs.map → mastra-client-context-DX8wJM9p.cjs.map} +1 -1
- package/dist/{query-utils-CcrChYgz.cjs → query-utils-BVgE_xxj.cjs} +2 -3
- package/dist/{query-utils-CcrChYgz.cjs.map → query-utils-BVgE_xxj.cjs.map} +1 -1
- package/dist/{query-utils-D1NgFnWU.js → query-utils-DvMERIwf.js} +2 -3
- package/dist/{query-utils-D1NgFnWU.js.map → query-utils-DvMERIwf.js.map} +1 -1
- package/dist/ui/index.cjs +15 -30
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +14 -28
- package/dist/ui/index.js.map +1 -1
- package/dist/{use-in-view-B6eJO9Zi.cjs → use-in-view-BxdrrnCw.cjs} +2 -3
- package/dist/{use-in-view-B6eJO9Zi.cjs.map → use-in-view-BxdrrnCw.cjs.map} +1 -1
- package/dist/{use-in-view-C9sYPGRm.js → use-in-view-CyjKdplM.js} +2 -3
- package/dist/{use-in-view-C9sYPGRm.js.map → use-in-view-CyjKdplM.js.map} +1 -1
- package/dist/{voice-BOtUdMi8.cjs → voice-Didej2E1.cjs} +5 -8
- package/dist/{voice-BOtUdMi8.cjs.map → voice-Didej2E1.cjs.map} +1 -1
- package/dist/{voice-DgirjEyH.js → voice-ugGu7VyA.js} +5 -8
- package/dist/{voice-DgirjEyH.js.map → voice-ugGu7VyA.js.map} +1 -1
- package/package.json +19 -19
package/dist/chat/index.cjs
CHANGED
package/dist/chat/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as useChat } from "../hooks-
|
|
1
|
+
export { t as useChat } from "../hooks-CEikbhrN.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
|
-
|
|
2
|
+
// src/hooks/shared/combine-by-ids.ts
|
|
3
3
|
/**
|
|
4
4
|
* Stable `combine` for `useQueries`: keyed on the ids' contents (callers pass a fresh array every
|
|
5
5
|
* render), so TanStack only recombines when a query result actually changes.
|
|
@@ -18,7 +18,6 @@ const useCombineByIds = (ids) => {
|
|
|
18
18
|
};
|
|
19
19
|
}, [idsKey]);
|
|
20
20
|
};
|
|
21
|
-
//#endregion
|
|
22
21
|
export { useCombineByIds as t };
|
|
23
22
|
|
|
24
|
-
//# sourceMappingURL=combine-by-ids-
|
|
23
|
+
//# sourceMappingURL=combine-by-ids-C-0oe4eH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combine-by-ids-
|
|
1
|
+
{"version":3,"file":"combine-by-ids-C-0oe4eH.js","names":[],"sources":["../src/hooks/shared/combine-by-ids.ts"],"sourcesContent":["import type { UseQueryResult } from '@tanstack/react-query';\nimport { useCallback } from 'react';\n\n/** One entry per requested experiment id, in the order the ids were given. */\nexport interface ExperimentQueriesResult<T> {\n data: Record<string, T | undefined>;\n /** True while any experiment has not loaded yet. */\n isLoading: boolean;\n /** First error among the experiments, if any. */\n error: Error | null;\n}\n\n/**\n * Stable `combine` for `useQueries`: keyed on the ids' contents (callers pass a fresh array every\n * render), so TanStack only recombines when a query result actually changes.\n */\nexport const useCombineByIds = <T>(ids: string[]) => {\n const idsKey = JSON.stringify(ids);\n return useCallback(\n (results: Array<UseQueryResult<T, Error>>): ExperimentQueriesResult<T> => {\n const data: Record<string, T | undefined> = {};\n (JSON.parse(idsKey) as string[]).forEach((id, index) => {\n data[id] = results[index]?.data;\n });\n return {\n data,\n isLoading: results.some(result => result.isLoading),\n error: results.find(result => result.error)?.error ?? null,\n };\n },\n [idsKey],\n );\n};\n"],"mappings":";;;;;;AAgBA,MAAa,mBAAsB,QAAkB;CACnD,MAAM,SAAS,KAAK,UAAU,GAAG;CACjC,OAAO,aACJ,YAAyE;EACxE,MAAM,OAAsC,CAAC;EAC7C,KAAM,MAAM,MAAM,CAAC,CAAc,SAAS,IAAI,UAAU;GACtD,KAAK,MAAM,QAAQ,MAAM,EAAE;EAC7B,CAAC;EACD,OAAO;GACL;GACA,WAAW,QAAQ,MAAK,WAAU,OAAO,SAAS;GAClD,OAAO,QAAQ,MAAK,WAAU,OAAO,KAAK,CAAC,EAAE,SAAS;EACxD;CACF,GACA,CAAC,MAAM,CACT;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
let react = require("react");
|
|
2
|
-
|
|
2
|
+
// src/hooks/shared/combine-by-ids.ts
|
|
3
3
|
/**
|
|
4
4
|
* Stable `combine` for `useQueries`: keyed on the ids' contents (callers pass a fresh array every
|
|
5
5
|
* render), so TanStack only recombines when a query result actually changes.
|
|
@@ -18,7 +18,6 @@ const useCombineByIds = (ids) => {
|
|
|
18
18
|
};
|
|
19
19
|
}, [idsKey]);
|
|
20
20
|
};
|
|
21
|
-
//#endregion
|
|
22
21
|
Object.defineProperty(exports, "useCombineByIds", {
|
|
23
22
|
enumerable: true,
|
|
24
23
|
get: function() {
|
|
@@ -26,4 +25,4 @@ Object.defineProperty(exports, "useCombineByIds", {
|
|
|
26
25
|
}
|
|
27
26
|
});
|
|
28
27
|
|
|
29
|
-
//# sourceMappingURL=combine-by-ids-
|
|
28
|
+
//# sourceMappingURL=combine-by-ids-ERVPXXBi.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combine-by-ids-
|
|
1
|
+
{"version":3,"file":"combine-by-ids-ERVPXXBi.cjs","names":["useCallback"],"sources":["../src/hooks/shared/combine-by-ids.ts"],"sourcesContent":["import type { UseQueryResult } from '@tanstack/react-query';\nimport { useCallback } from 'react';\n\n/** One entry per requested experiment id, in the order the ids were given. */\nexport interface ExperimentQueriesResult<T> {\n data: Record<string, T | undefined>;\n /** True while any experiment has not loaded yet. */\n isLoading: boolean;\n /** First error among the experiments, if any. */\n error: Error | null;\n}\n\n/**\n * Stable `combine` for `useQueries`: keyed on the ids' contents (callers pass a fresh array every\n * render), so TanStack only recombines when a query result actually changes.\n */\nexport const useCombineByIds = <T>(ids: string[]) => {\n const idsKey = JSON.stringify(ids);\n return useCallback(\n (results: Array<UseQueryResult<T, Error>>): ExperimentQueriesResult<T> => {\n const data: Record<string, T | undefined> = {};\n (JSON.parse(idsKey) as string[]).forEach((id, index) => {\n data[id] = results[index]?.data;\n });\n return {\n data,\n isLoading: results.some(result => result.isLoading),\n error: results.find(result => result.error)?.error ?? null,\n };\n },\n [idsKey],\n );\n};\n"],"mappings":";;;;;;AAgBA,MAAa,mBAAsB,QAAkB;CACnD,MAAM,SAAS,KAAK,UAAU,GAAG;CACjC,QAAA,GAAOA,MAAAA,YAAAA,EACJ,YAAyE;EACxE,MAAM,OAAsC,CAAC;EAC7C,KAAM,MAAM,MAAM,CAAC,CAAc,SAAS,IAAI,UAAU;GACtD,KAAK,MAAM,QAAQ,MAAM,EAAE;EAC7B,CAAC;EACD,OAAO;GACL;GACA,WAAW,QAAQ,MAAK,WAAU,OAAO,SAAS;GAClD,OAAO,QAAQ,MAAK,WAAU,OAAO,KAAK,CAAC,EAAE,SAAS;EACxD;CACF,GACA,CAAC,MAAM,CACT;AACF"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const require_mastra_client_context = require("./mastra-client-context-
|
|
2
|
-
const require_use_in_view = require("./use-in-view-
|
|
3
|
-
const require_combine_by_ids = require("./combine-by-ids-
|
|
4
|
-
const require_map = require("./map-
|
|
1
|
+
const require_mastra_client_context = require("./mastra-client-context-DX8wJM9p.cjs");
|
|
2
|
+
const require_use_in_view = require("./use-in-view-BxdrrnCw.cjs");
|
|
3
|
+
const require_combine_by_ids = require("./combine-by-ids-ERVPXXBi.cjs");
|
|
4
|
+
const require_map = require("./map-BGoWBTGy.cjs");
|
|
5
5
|
let react = require("react");
|
|
6
6
|
let _tanstack_react_query = require("@tanstack/react-query");
|
|
7
|
-
|
|
7
|
+
// src/hooks/experiments/usage-accumulator.ts
|
|
8
8
|
const INPUT_TOKEN_METRIC = "mastra_model_total_input_tokens";
|
|
9
9
|
const OUTPUT_TOKEN_METRIC = "mastra_model_total_output_tokens";
|
|
10
10
|
const createAccumulator = () => ({
|
|
@@ -34,8 +34,7 @@ function toUsage(accumulator) {
|
|
|
34
34
|
costUnit: isPriced ? [...accumulator.costUnits][0] : void 0
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
//#region src/hooks/experiments/use-experiment-item-usage.ts
|
|
37
|
+
// src/hooks/experiments/use-experiment-item-usage.ts
|
|
39
38
|
const POLL_INTERVAL_MS$1 = 2e3;
|
|
40
39
|
/** Builds the experiment totals and the per-trace usage in a single pass over the groups. */
|
|
41
40
|
function toExperimentUsage(response) {
|
|
@@ -91,8 +90,7 @@ const useExperimentsItemUsage = ({ experiments, supportsMetrics }) => {
|
|
|
91
90
|
combine
|
|
92
91
|
});
|
|
93
92
|
};
|
|
94
|
-
|
|
95
|
-
//#region src/hooks/experiments/use-experiment-metrics.ts
|
|
93
|
+
// src/hooks/experiments/use-experiment-metrics.ts
|
|
96
94
|
/**
|
|
97
95
|
* Experiment-scoped metrics (tokens, cost, avg agent latency).
|
|
98
96
|
* Filters only by experimentId — no time window, since the experiment already bounds the row set.
|
|
@@ -142,8 +140,7 @@ const useExperimentMetrics = ({ experimentId, experimentStatus, supportsMetrics,
|
|
|
142
140
|
isEnabled
|
|
143
141
|
};
|
|
144
142
|
};
|
|
145
|
-
|
|
146
|
-
//#region src/hooks/experiments/use-experiment-trace.ts
|
|
143
|
+
// src/hooks/experiments/use-experiment-trace.ts
|
|
147
144
|
/**
|
|
148
145
|
* Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.
|
|
149
146
|
*/
|
|
@@ -158,8 +155,7 @@ const useExperimentTrace = ({ traceId, queryOptions }) => {
|
|
|
158
155
|
...queryOptions
|
|
159
156
|
});
|
|
160
157
|
};
|
|
161
|
-
|
|
162
|
-
//#region src/hooks/experiments/use-experiments-for-dataset-filter.ts
|
|
158
|
+
// src/hooks/experiments/use-experiments-for-dataset-filter.ts
|
|
163
159
|
/** Explicit page size: server defaults are 20 (global) / 10 (per dataset), which is too small for the list. */
|
|
164
160
|
const EXPERIMENTS_PAGE_SIZE = 100;
|
|
165
161
|
function useExperimentsForDatasetFilter({ datasetId, target, queryOptions }) {
|
|
@@ -177,8 +173,7 @@ function useExperimentsForDatasetFilter({ datasetId, target, queryOptions }) {
|
|
|
177
173
|
...queryOptions
|
|
178
174
|
});
|
|
179
175
|
}
|
|
180
|
-
|
|
181
|
-
//#region src/hooks/experiments/use-experiments-list-metrics.ts
|
|
176
|
+
// src/hooks/experiments/use-experiments-list-metrics.ts
|
|
182
177
|
const AGENT_DURATION_METRIC = "mastra_agent_duration_ms";
|
|
183
178
|
const EXPERIMENT_IDS_PER_REQUEST = 500;
|
|
184
179
|
const POLL_INTERVAL_MS = 5e3;
|
|
@@ -247,8 +242,7 @@ const useExperimentsListMetrics = ({ experiments, supportsMetrics }) => {
|
|
|
247
242
|
refetchInterval: hasActiveExperiment ? POLL_INTERVAL_MS : false
|
|
248
243
|
});
|
|
249
244
|
};
|
|
250
|
-
|
|
251
|
-
//#region src/hooks/experiments/use-infinite-experiments.ts
|
|
245
|
+
// src/hooks/experiments/use-infinite-experiments.ts
|
|
252
246
|
const EXPERIMENTS_PER_PAGE = 100;
|
|
253
247
|
function useInfiniteExperiments({ datasetId, target, orderBy, queryOptions }) {
|
|
254
248
|
const client = require_mastra_client_context.useMastraClient();
|
|
@@ -300,7 +294,6 @@ function useInfiniteExperiments({ datasetId, target, orderBy, queryOptions }) {
|
|
|
300
294
|
setEndOfListElement
|
|
301
295
|
};
|
|
302
296
|
}
|
|
303
|
-
//#endregion
|
|
304
297
|
Object.defineProperty(exports, "EXPERIMENTS_PAGE_SIZE", {
|
|
305
298
|
enumerable: true,
|
|
306
299
|
get: function() {
|
|
@@ -350,4 +343,4 @@ Object.defineProperty(exports, "useInfiniteExperiments", {
|
|
|
350
343
|
}
|
|
351
344
|
});
|
|
352
345
|
|
|
353
|
-
//# sourceMappingURL=experiments-
|
|
346
|
+
//# sourceMappingURL=experiments-Duv0RgkM.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"experiments-CJw1N3FB.cjs","names":["POLL_INTERVAL_MS","getOrCreate","useMastraClient","useCombineByIds","useQueries","useMastraClient","useQuery","useMastraClient","useQuery","useMastraClient","useQuery","getOrCreate","useMastraClient","useQuery","keepPreviousData","useMastraClient","useInView","useInfiniteQuery"],"sources":["../src/hooks/experiments/usage-accumulator.ts","../src/hooks/experiments/use-experiment-item-usage.ts","../src/hooks/experiments/use-experiment-metrics.ts","../src/hooks/experiments/use-experiment-trace.ts","../src/hooks/experiments/use-experiments-for-dataset-filter.ts","../src/hooks/experiments/use-experiments-list-metrics.ts","../src/hooks/experiments/use-infinite-experiments.ts"],"sourcesContent":["import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\n\nexport const INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nexport const OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\n\nexport interface ExperimentItemUsage {\n inputTokens?: number;\n outputTokens?: number;\n /** Undefined when no group is priced or when groups use different cost units. */\n estimatedCost?: number;\n costUnit?: string;\n}\n\nexport type UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nexport const createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\n/** The token field a breakdown group feeds, or undefined when the group is not a token metric. */\nexport function tokenFieldOf(group: BreakdownGroup): 'inputTokens' | 'outputTokens' | undefined {\n const metricName = group.dimensions.name;\n if (metricName === INPUT_TOKEN_METRIC) return 'inputTokens';\n if (metricName === OUTPUT_TOKEN_METRIC) return 'outputTokens';\n return undefined;\n}\n\nexport function accumulate(\n accumulator: UsageAccumulator,\n tokenField: 'inputTokens' | 'outputTokens',\n group: BreakdownGroup,\n) {\n accumulator[tokenField] = (accumulator[tokenField] ?? 0) + group.value;\n\n if (group.estimatedCost == null) return;\n accumulator.estimatedCost += group.estimatedCost;\n if (group.costUnit) {\n accumulator.costUnits.add(group.costUnit.toLowerCase());\n } else {\n accumulator.hasUnknownCostUnit = true;\n }\n}\n\nexport function toUsage(accumulator: UsageAccumulator): ExperimentItemUsage {\n // A single known unit implies at least one priced group.\n const isPriced = !accumulator.hasUnknownCostUnit && accumulator.costUnits.size === 1;\n return {\n inputTokens: accumulator.inputTokens,\n outputTokens: accumulator.outputTokens,\n estimatedCost: isPriced ? accumulator.estimatedCost : undefined,\n costUnit: isPriced ? [...accumulator.costUnits][0] : undefined,\n };\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { useQueries } from '@tanstack/react-query';\nimport type { Query } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { useCombineByIds } from '../shared/combine-by-ids';\nimport type { ExperimentQueriesResult } from '../shared/combine-by-ids';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nexport type { ExperimentItemUsage } from './usage-accumulator';\n\nconst POLL_INTERVAL_MS = 2000;\n\n/** Usage of one experiment: its totals, and each item's usage keyed by the trace id of its run. */\nexport interface ExperimentUsage {\n /** Totals across every item. The cost sums priced groups only. */\n summary: ExperimentItemUsage;\n traces: Record<string, ExperimentItemUsage>;\n}\n\n/** Builds the experiment totals and the per-trace usage in a single pass over the groups. */\nfunction toExperimentUsage(response: GetMetricBreakdownResponse): ExperimentUsage {\n const summary = createAccumulator();\n const accumulators = new Map<string, UsageAccumulator>();\n\n for (const group of response.groups) {\n const tokenField = tokenFieldOf(group);\n if (!tokenField) continue;\n\n accumulate(summary, tokenField, group);\n\n const traceId = group.dimensions.traceId;\n if (traceId) accumulate(getOrCreate(accumulators, traceId, createAccumulator), tokenField, group);\n }\n\n const traces: Record<string, ExperimentItemUsage> = {};\n for (const [traceId, accumulator] of accumulators) {\n traces[traceId] = toUsage(accumulator);\n }\n return { summary: toUsage(summary), traces };\n}\n\n/** How many breakdowns to fetch after completion while the exporter may still be flushing metrics. */\nconst MAX_EMPTY_ATTEMPTS = 5;\n\nconst isEmptyUsage = (usage: ExperimentUsage | undefined) =>\n !usage ||\n (Object.keys(usage.traces).length === 0 &&\n usage.summary.inputTokens === undefined &&\n usage.summary.outputTokens === undefined);\n\n/**\n * Token and cost usage of several experiments, keyed by experiment id: each entry holds the\n * experiment totals (`summary`) and every item's usage keyed by trace id (`traces`).\n * Sends one metric breakdown request per experiment, filtered by its id rather than by trace ids,\n * so it does not wait for the experiment results. Usage is only fetched once an experiment has\n * finished; while the breakdown is still empty (metrics flushing), it retries every 2 seconds up to\n * 5 attempts. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsItemUsage = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): ExperimentQueriesResult<ExperimentUsage> => {\n const client = useMastraClient();\n const combine = useCombineByIds<ExperimentUsage>(experiments.map(experiment => experiment.id));\n\n return useQueries({\n queries: experiments.map(({ id, status }) => {\n const finished = status !== 'running' && status !== 'pending';\n return {\n queryKey: ['experiment-item-usage', id, finished],\n queryFn: async () =>\n toExperimentUsage(\n await client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['traceId', 'name'],\n filters: { experimentId: id },\n }),\n ),\n enabled: supportsMetrics && finished,\n refetchInterval: (query: Query<ExperimentUsage>) =>\n isEmptyUsage(query.state.data) && query.state.dataUpdateCount < MAX_EMPTY_ATTEMPTS ? POLL_INTERVAL_MS : false,\n };\n }),\n combine,\n });\n};\n","import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\nexport interface ExperimentMetrics {\n totalTokens: number | null;\n estimatedCost: number | null;\n costUnit: string | null;\n avgAgentDurationMs: number | null;\n agentRuns: number | null;\n}\n\ninterface UseExperimentMetricsArgs {\n experimentId: string | undefined;\n experimentStatus: DatasetExperiment['status'] | undefined;\n /** Whether the observability store can serve metrics; no requests are made when false. */\n supportsMetrics: boolean;\n queryOptions?: MastraQueryOptions<ExperimentMetrics>;\n}\n\n/**\n * Experiment-scoped metrics (tokens, cost, avg agent latency).\n * Filters only by experimentId — no time window, since the experiment already bounds the row set.\n * Polls every 2 seconds while the experiment is running or pending.\n *\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentMetrics = ({\n experimentId,\n experimentStatus,\n supportsMetrics,\n queryOptions,\n}: UseExperimentMetricsArgs) => {\n const client = useMastraClient();\n const isEnabled = Boolean(experimentId) && supportsMetrics;\n const isActive = experimentStatus === 'running' || experimentStatus === 'pending';\n\n const query = useQuery({\n queryKey: ['experiment-metrics', experimentId],\n refetchInterval: isActive ? 2000 : false,\n queryFn: async (): Promise<ExperimentMetrics> => {\n // `enabled` already guards this; the check exists to narrow the type.\n if (!experimentId) throw new Error('experimentId is required');\n const filters = { experimentId };\n\n const [tokens, avgDuration, runs] = await Promise.all([\n client.getMetricAggregate({\n name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'],\n aggregation: 'sum',\n filters,\n }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'avg', filters }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'count', filters }),\n ]);\n\n return {\n totalTokens: tokens.value ?? null,\n estimatedCost: tokens.estimatedCost ?? null,\n costUnit: tokens.costUnit ?? null,\n avgAgentDurationMs: avgDuration.value ?? null,\n agentRuns: runs.value ?? null,\n };\n },\n ...queryOptions,\n });\n\n return { data: query.data, isLoading: query.isLoading, isEnabled };\n};\n","import type { MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\ntype TraceLightResponse = Awaited<ReturnType<MastraClient['getTraceLight']>>;\n\n/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentTrace = <TData = TraceLightResponse>({\n traceId,\n queryOptions,\n}: {\n traceId: string | null | undefined;\n queryOptions?: MastraQueryOptions<TraceLightResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery({\n queryKey: ['experiment-trace-light', traceId],\n queryFn: async () => {\n if (!traceId) {\n throw new Error('Trace ID is required');\n }\n return client.getTraceLight(traceId);\n },\n ...queryOptions,\n });\n};\n","import type { ExperimentTargetType, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\n/** Explicit page size: server defaults are 20 (global) / 10 (per dataset), which is too small for the list. */\nexport const EXPERIMENTS_PAGE_SIZE = 100;\n\nexport interface ExperimentTargetFilter {\n targetType?: ExperimentTargetType | '';\n targetId?: string;\n}\n\n/**\n * Experiments for the list page: the global list, or the dataset-scoped list when a dataset filter is active.\n * A dataset's runs may not be in the first page of the global list, so filtering client-side is not enough.\n * The optional target filter is applied server-side for the same reason.\n */\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useExperimentsForDatasetFilter<TData = ListExperimentsResponse>({\n datasetId,\n target,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n queryOptions?: MastraQueryOptions<ListExperimentsResponse, TData>;\n}): UseQueryResult<TData, Error> {\n const client = useMastraClient();\n const params: ListExperimentsParams = { perPage: EXPERIMENTS_PAGE_SIZE };\n if (target?.targetType) params.targetType = target.targetType;\n if (target?.targetId) params.targetId = target.targetId;\n\n return useQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId ? ['dataset-experiments', datasetId, params] : ['experiments', params],\n queryFn: () => (datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params)),\n ...queryOptions,\n });\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nconst AGENT_DURATION_METRIC = 'mastra_agent_duration_ms';\n// Each experiment can return two token groups, while the breakdown endpoint allows at most 1,000.\nconst EXPERIMENT_IDS_PER_REQUEST = 500;\nconst POLL_INTERVAL_MS = 5000;\n\n/** Totals of one experiment: token usage, its estimated cost, and the average agent run duration. */\nexport interface ExperimentListMetrics extends ExperimentItemUsage {\n avgDurationMs?: number;\n}\n\nfunction chunk(ids: readonly string[]): string[][] {\n const chunks: string[][] = [];\n for (let index = 0; index < ids.length; index += EXPERIMENT_IDS_PER_REQUEST) {\n chunks.push(ids.slice(index, index + EXPERIMENT_IDS_PER_REQUEST));\n }\n return chunks;\n}\n\nfunction settledValues<T>(results: PromiseSettledResult<T>[]): T[] {\n return results.flatMap(result => (result.status === 'fulfilled' ? [result.value] : []));\n}\n\n/**\n * Builds each requested experiment's metrics. Groups for other experiments are skipped,\n * in case a store ignores the `experimentIds` filter.\n */\nfunction toListMetrics(\n experimentIds: readonly string[],\n tokenResponses: GetMetricBreakdownResponse[],\n durationResponses: GetMetricBreakdownResponse[],\n): Map<string, ExperimentListMetrics> {\n const requested = new Set(experimentIds);\n const accumulators = new Map<string, UsageAccumulator>();\n for (const group of tokenResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n const tokenField = tokenFieldOf(group);\n if (!experimentId || !requested.has(experimentId) || !tokenField) continue;\n accumulate(getOrCreate(accumulators, experimentId, createAccumulator), tokenField, group);\n }\n\n const metrics = new Map<string, ExperimentListMetrics>();\n for (const [experimentId, accumulator] of accumulators) {\n metrics.set(experimentId, toUsage(accumulator));\n }\n for (const group of durationResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n if (!experimentId || !requested.has(experimentId)) continue;\n getOrCreate(metrics, experimentId, (): ExperimentListMetrics => ({})).avgDurationMs = group.value;\n }\n return metrics;\n}\n\n/**\n * Token usage, estimated cost and average agent duration of many experiments, keyed by experiment id.\n * Sends two metric breakdowns per 500 experiments (tokens grouped by experiment and metric name,\n * durations grouped by experiment) instead of one request per experiment.\n * Experiments without any metric are absent from the map. Polls while an experiment is running\n * or pending. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsListMetrics = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): UseQueryResult<Map<string, ExperimentListMetrics>, Error> => {\n const client = useMastraClient();\n const experimentIds = [...new Set(experiments.map(experiment => experiment.id))].toSorted();\n const hasActiveExperiment = experiments.some(({ status }) => status === 'running' || status === 'pending');\n\n return useQuery({\n queryKey: ['experiments-list-metrics', experimentIds],\n enabled: supportsMetrics && experimentIds.length > 0,\n queryFn: async () => {\n const batches = chunk(experimentIds);\n const [tokenResults, durationResults] = await Promise.all([\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['experimentId', 'name'],\n filters: { experimentIds: batch },\n limit: batch.length * 2,\n }),\n ),\n ),\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [AGENT_DURATION_METRIC],\n aggregation: 'avg',\n groupBy: ['experimentId'],\n filters: { experimentIds: batch },\n limit: batch.length,\n }),\n ),\n ),\n ]);\n return toListMetrics(experimentIds, settledValues(tokenResults), settledValues(durationResults));\n },\n // Loading another page changes the ids; keep the rows already shown filled meanwhile.\n placeholderData: keepPreviousData,\n refetchInterval: hasActiveExperiment ? POLL_INTERVAL_MS : false,\n });\n};\n","import type { DatasetExperiment, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';\nimport { useEffect } from 'react';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraInfiniteQueryOptions } from '../shared/query-options';\nimport { useInView } from '../shared/use-in-view';\nimport type { ExperimentTargetFilter } from './use-experiments-for-dataset-filter';\n\nexport const EXPERIMENTS_PER_PAGE = 100;\n\n/**\n * Infinite-scroll experiments for the list page: the global list, or the dataset-scoped list when a\n * dataset filter is active (a dataset's runs may not be in the first pages of the global list). The\n * optional target filter is applied server-side for the same reason.\n */\nexport type ExperimentsOrderBy = NonNullable<ListExperimentsParams['orderBy']>;\n\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useInfiniteExperiments<TData = DatasetExperiment[]>({\n datasetId,\n target,\n orderBy,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n orderBy?: ExperimentsOrderBy;\n queryOptions?: MastraInfiniteQueryOptions<ListExperimentsResponse, TData, QueryKey, number>;\n}): UseInfiniteQueryResult<TData, Error> & {\n setEndOfListElement: (element: HTMLDivElement | null) => void;\n} {\n const client = useMastraClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n const targetType = target?.targetType || undefined;\n const targetId = target?.targetId || undefined;\n\n const query = useInfiniteQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId\n ? ['dataset-experiments', datasetId, 'infinite', { targetType, targetId, orderBy }]\n : ['experiments', 'infinite', { targetType, targetId, orderBy }],\n queryFn: ({ pageParam }) => {\n const params: ListExperimentsParams = { page: pageParam, perPage: EXPERIMENTS_PER_PAGE };\n if (targetType) params.targetType = targetType;\n if (targetId) params.targetId = targetId;\n if (orderBy) params.orderBy = orderBy;\n return datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params);\n },\n initialPageParam: 0,\n getNextPageParam: (lastPage, _, lastPageParam) => {\n if (!lastPage?.experiments?.length || !lastPage.pagination?.hasMore) {\n return undefined;\n }\n return lastPageParam + 1;\n },\n select: (data: InfiniteData<ListExperimentsResponse, number>) =>\n data.pages.flatMap(page => page?.experiments ?? []) as TData,\n ...queryOptions,\n });\n\n useEffect(() => {\n if (isEndOfListInView && query.hasNextPage && !query.isFetchingNextPage) {\n void query.fetchNextPage();\n }\n }, [isEndOfListInView, query]);\n\n return { ...query, setEndOfListElement };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"experiments-Duv0RgkM.cjs","names":["POLL_INTERVAL_MS","getOrCreate","useMastraClient","useCombineByIds","useQueries","useMastraClient","useQuery","useMastraClient","useQuery","useMastraClient","useQuery","getOrCreate","useMastraClient","useQuery","keepPreviousData","useMastraClient","useInView","useInfiniteQuery"],"sources":["../src/hooks/experiments/usage-accumulator.ts","../src/hooks/experiments/use-experiment-item-usage.ts","../src/hooks/experiments/use-experiment-metrics.ts","../src/hooks/experiments/use-experiment-trace.ts","../src/hooks/experiments/use-experiments-for-dataset-filter.ts","../src/hooks/experiments/use-experiments-list-metrics.ts","../src/hooks/experiments/use-infinite-experiments.ts"],"sourcesContent":["import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\n\nexport const INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nexport const OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\n\nexport interface ExperimentItemUsage {\n inputTokens?: number;\n outputTokens?: number;\n /** Undefined when no group is priced or when groups use different cost units. */\n estimatedCost?: number;\n costUnit?: string;\n}\n\nexport type UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nexport const createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\n/** The token field a breakdown group feeds, or undefined when the group is not a token metric. */\nexport function tokenFieldOf(group: BreakdownGroup): 'inputTokens' | 'outputTokens' | undefined {\n const metricName = group.dimensions.name;\n if (metricName === INPUT_TOKEN_METRIC) return 'inputTokens';\n if (metricName === OUTPUT_TOKEN_METRIC) return 'outputTokens';\n return undefined;\n}\n\nexport function accumulate(\n accumulator: UsageAccumulator,\n tokenField: 'inputTokens' | 'outputTokens',\n group: BreakdownGroup,\n) {\n accumulator[tokenField] = (accumulator[tokenField] ?? 0) + group.value;\n\n if (group.estimatedCost == null) return;\n accumulator.estimatedCost += group.estimatedCost;\n if (group.costUnit) {\n accumulator.costUnits.add(group.costUnit.toLowerCase());\n } else {\n accumulator.hasUnknownCostUnit = true;\n }\n}\n\nexport function toUsage(accumulator: UsageAccumulator): ExperimentItemUsage {\n // A single known unit implies at least one priced group.\n const isPriced = !accumulator.hasUnknownCostUnit && accumulator.costUnits.size === 1;\n return {\n inputTokens: accumulator.inputTokens,\n outputTokens: accumulator.outputTokens,\n estimatedCost: isPriced ? accumulator.estimatedCost : undefined,\n costUnit: isPriced ? [...accumulator.costUnits][0] : undefined,\n };\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { useQueries } from '@tanstack/react-query';\nimport type { Query } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { useCombineByIds } from '../shared/combine-by-ids';\nimport type { ExperimentQueriesResult } from '../shared/combine-by-ids';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nexport type { ExperimentItemUsage } from './usage-accumulator';\n\nconst POLL_INTERVAL_MS = 2000;\n\n/** Usage of one experiment: its totals, and each item's usage keyed by the trace id of its run. */\nexport interface ExperimentUsage {\n /** Totals across every item. The cost sums priced groups only. */\n summary: ExperimentItemUsage;\n traces: Record<string, ExperimentItemUsage>;\n}\n\n/** Builds the experiment totals and the per-trace usage in a single pass over the groups. */\nfunction toExperimentUsage(response: GetMetricBreakdownResponse): ExperimentUsage {\n const summary = createAccumulator();\n const accumulators = new Map<string, UsageAccumulator>();\n\n for (const group of response.groups) {\n const tokenField = tokenFieldOf(group);\n if (!tokenField) continue;\n\n accumulate(summary, tokenField, group);\n\n const traceId = group.dimensions.traceId;\n if (traceId) accumulate(getOrCreate(accumulators, traceId, createAccumulator), tokenField, group);\n }\n\n const traces: Record<string, ExperimentItemUsage> = {};\n for (const [traceId, accumulator] of accumulators) {\n traces[traceId] = toUsage(accumulator);\n }\n return { summary: toUsage(summary), traces };\n}\n\n/** How many breakdowns to fetch after completion while the exporter may still be flushing metrics. */\nconst MAX_EMPTY_ATTEMPTS = 5;\n\nconst isEmptyUsage = (usage: ExperimentUsage | undefined) =>\n !usage ||\n (Object.keys(usage.traces).length === 0 &&\n usage.summary.inputTokens === undefined &&\n usage.summary.outputTokens === undefined);\n\n/**\n * Token and cost usage of several experiments, keyed by experiment id: each entry holds the\n * experiment totals (`summary`) and every item's usage keyed by trace id (`traces`).\n * Sends one metric breakdown request per experiment, filtered by its id rather than by trace ids,\n * so it does not wait for the experiment results. Usage is only fetched once an experiment has\n * finished; while the breakdown is still empty (metrics flushing), it retries every 2 seconds up to\n * 5 attempts. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsItemUsage = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): ExperimentQueriesResult<ExperimentUsage> => {\n const client = useMastraClient();\n const combine = useCombineByIds<ExperimentUsage>(experiments.map(experiment => experiment.id));\n\n return useQueries({\n queries: experiments.map(({ id, status }) => {\n const finished = status !== 'running' && status !== 'pending';\n return {\n queryKey: ['experiment-item-usage', id, finished],\n queryFn: async () =>\n toExperimentUsage(\n await client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['traceId', 'name'],\n filters: { experimentId: id },\n }),\n ),\n enabled: supportsMetrics && finished,\n refetchInterval: (query: Query<ExperimentUsage>) =>\n isEmptyUsage(query.state.data) && query.state.dataUpdateCount < MAX_EMPTY_ATTEMPTS ? POLL_INTERVAL_MS : false,\n };\n }),\n combine,\n });\n};\n","import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\nexport interface ExperimentMetrics {\n totalTokens: number | null;\n estimatedCost: number | null;\n costUnit: string | null;\n avgAgentDurationMs: number | null;\n agentRuns: number | null;\n}\n\ninterface UseExperimentMetricsArgs {\n experimentId: string | undefined;\n experimentStatus: DatasetExperiment['status'] | undefined;\n /** Whether the observability store can serve metrics; no requests are made when false. */\n supportsMetrics: boolean;\n queryOptions?: MastraQueryOptions<ExperimentMetrics>;\n}\n\n/**\n * Experiment-scoped metrics (tokens, cost, avg agent latency).\n * Filters only by experimentId — no time window, since the experiment already bounds the row set.\n * Polls every 2 seconds while the experiment is running or pending.\n *\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentMetrics = ({\n experimentId,\n experimentStatus,\n supportsMetrics,\n queryOptions,\n}: UseExperimentMetricsArgs) => {\n const client = useMastraClient();\n const isEnabled = Boolean(experimentId) && supportsMetrics;\n const isActive = experimentStatus === 'running' || experimentStatus === 'pending';\n\n const query = useQuery({\n queryKey: ['experiment-metrics', experimentId],\n refetchInterval: isActive ? 2000 : false,\n queryFn: async (): Promise<ExperimentMetrics> => {\n // `enabled` already guards this; the check exists to narrow the type.\n if (!experimentId) throw new Error('experimentId is required');\n const filters = { experimentId };\n\n const [tokens, avgDuration, runs] = await Promise.all([\n client.getMetricAggregate({\n name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'],\n aggregation: 'sum',\n filters,\n }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'avg', filters }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'count', filters }),\n ]);\n\n return {\n totalTokens: tokens.value ?? null,\n estimatedCost: tokens.estimatedCost ?? null,\n costUnit: tokens.costUnit ?? null,\n avgAgentDurationMs: avgDuration.value ?? null,\n agentRuns: runs.value ?? null,\n };\n },\n ...queryOptions,\n });\n\n return { data: query.data, isLoading: query.isLoading, isEnabled };\n};\n","import type { MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\ntype TraceLightResponse = Awaited<ReturnType<MastraClient['getTraceLight']>>;\n\n/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentTrace = <TData = TraceLightResponse>({\n traceId,\n queryOptions,\n}: {\n traceId: string | null | undefined;\n queryOptions?: MastraQueryOptions<TraceLightResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery({\n queryKey: ['experiment-trace-light', traceId],\n queryFn: async () => {\n if (!traceId) {\n throw new Error('Trace ID is required');\n }\n return client.getTraceLight(traceId);\n },\n ...queryOptions,\n });\n};\n","import type { ExperimentTargetType, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\n/** Explicit page size: server defaults are 20 (global) / 10 (per dataset), which is too small for the list. */\nexport const EXPERIMENTS_PAGE_SIZE = 100;\n\nexport interface ExperimentTargetFilter {\n targetType?: ExperimentTargetType | '';\n targetId?: string;\n}\n\n/**\n * Experiments for the list page: the global list, or the dataset-scoped list when a dataset filter is active.\n * A dataset's runs may not be in the first page of the global list, so filtering client-side is not enough.\n * The optional target filter is applied server-side for the same reason.\n */\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useExperimentsForDatasetFilter<TData = ListExperimentsResponse>({\n datasetId,\n target,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n queryOptions?: MastraQueryOptions<ListExperimentsResponse, TData>;\n}): UseQueryResult<TData, Error> {\n const client = useMastraClient();\n const params: ListExperimentsParams = { perPage: EXPERIMENTS_PAGE_SIZE };\n if (target?.targetType) params.targetType = target.targetType;\n if (target?.targetId) params.targetId = target.targetId;\n\n return useQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId ? ['dataset-experiments', datasetId, params] : ['experiments', params],\n queryFn: () => (datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params)),\n ...queryOptions,\n });\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nconst AGENT_DURATION_METRIC = 'mastra_agent_duration_ms';\n// Each experiment can return two token groups, while the breakdown endpoint allows at most 1,000.\nconst EXPERIMENT_IDS_PER_REQUEST = 500;\nconst POLL_INTERVAL_MS = 5000;\n\n/** Totals of one experiment: token usage, its estimated cost, and the average agent run duration. */\nexport interface ExperimentListMetrics extends ExperimentItemUsage {\n avgDurationMs?: number;\n}\n\nfunction chunk(ids: readonly string[]): string[][] {\n const chunks: string[][] = [];\n for (let index = 0; index < ids.length; index += EXPERIMENT_IDS_PER_REQUEST) {\n chunks.push(ids.slice(index, index + EXPERIMENT_IDS_PER_REQUEST));\n }\n return chunks;\n}\n\nfunction settledValues<T>(results: PromiseSettledResult<T>[]): T[] {\n return results.flatMap(result => (result.status === 'fulfilled' ? [result.value] : []));\n}\n\n/**\n * Builds each requested experiment's metrics. Groups for other experiments are skipped,\n * in case a store ignores the `experimentIds` filter.\n */\nfunction toListMetrics(\n experimentIds: readonly string[],\n tokenResponses: GetMetricBreakdownResponse[],\n durationResponses: GetMetricBreakdownResponse[],\n): Map<string, ExperimentListMetrics> {\n const requested = new Set(experimentIds);\n const accumulators = new Map<string, UsageAccumulator>();\n for (const group of tokenResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n const tokenField = tokenFieldOf(group);\n if (!experimentId || !requested.has(experimentId) || !tokenField) continue;\n accumulate(getOrCreate(accumulators, experimentId, createAccumulator), tokenField, group);\n }\n\n const metrics = new Map<string, ExperimentListMetrics>();\n for (const [experimentId, accumulator] of accumulators) {\n metrics.set(experimentId, toUsage(accumulator));\n }\n for (const group of durationResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n if (!experimentId || !requested.has(experimentId)) continue;\n getOrCreate(metrics, experimentId, (): ExperimentListMetrics => ({})).avgDurationMs = group.value;\n }\n return metrics;\n}\n\n/**\n * Token usage, estimated cost and average agent duration of many experiments, keyed by experiment id.\n * Sends two metric breakdowns per 500 experiments (tokens grouped by experiment and metric name,\n * durations grouped by experiment) instead of one request per experiment.\n * Experiments without any metric are absent from the map. Polls while an experiment is running\n * or pending. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsListMetrics = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): UseQueryResult<Map<string, ExperimentListMetrics>, Error> => {\n const client = useMastraClient();\n const experimentIds = [...new Set(experiments.map(experiment => experiment.id))].toSorted();\n const hasActiveExperiment = experiments.some(({ status }) => status === 'running' || status === 'pending');\n\n return useQuery({\n queryKey: ['experiments-list-metrics', experimentIds],\n enabled: supportsMetrics && experimentIds.length > 0,\n queryFn: async () => {\n const batches = chunk(experimentIds);\n const [tokenResults, durationResults] = await Promise.all([\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['experimentId', 'name'],\n filters: { experimentIds: batch },\n limit: batch.length * 2,\n }),\n ),\n ),\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [AGENT_DURATION_METRIC],\n aggregation: 'avg',\n groupBy: ['experimentId'],\n filters: { experimentIds: batch },\n limit: batch.length,\n }),\n ),\n ),\n ]);\n return toListMetrics(experimentIds, settledValues(tokenResults), settledValues(durationResults));\n },\n // Loading another page changes the ids; keep the rows already shown filled meanwhile.\n placeholderData: keepPreviousData,\n refetchInterval: hasActiveExperiment ? POLL_INTERVAL_MS : false,\n });\n};\n","import type { DatasetExperiment, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';\nimport { useEffect } from 'react';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraInfiniteQueryOptions } from '../shared/query-options';\nimport { useInView } from '../shared/use-in-view';\nimport type { ExperimentTargetFilter } from './use-experiments-for-dataset-filter';\n\nexport const EXPERIMENTS_PER_PAGE = 100;\n\n/**\n * Infinite-scroll experiments for the list page: the global list, or the dataset-scoped list when a\n * dataset filter is active (a dataset's runs may not be in the first pages of the global list). The\n * optional target filter is applied server-side for the same reason.\n */\nexport type ExperimentsOrderBy = NonNullable<ListExperimentsParams['orderBy']>;\n\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useInfiniteExperiments<TData = DatasetExperiment[]>({\n datasetId,\n target,\n orderBy,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n orderBy?: ExperimentsOrderBy;\n queryOptions?: MastraInfiniteQueryOptions<ListExperimentsResponse, TData, QueryKey, number>;\n}): UseInfiniteQueryResult<TData, Error> & {\n setEndOfListElement: (element: HTMLDivElement | null) => void;\n} {\n const client = useMastraClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n const targetType = target?.targetType || undefined;\n const targetId = target?.targetId || undefined;\n\n const query = useInfiniteQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId\n ? ['dataset-experiments', datasetId, 'infinite', { targetType, targetId, orderBy }]\n : ['experiments', 'infinite', { targetType, targetId, orderBy }],\n queryFn: ({ pageParam }) => {\n const params: ListExperimentsParams = { page: pageParam, perPage: EXPERIMENTS_PER_PAGE };\n if (targetType) params.targetType = targetType;\n if (targetId) params.targetId = targetId;\n if (orderBy) params.orderBy = orderBy;\n return datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params);\n },\n initialPageParam: 0,\n getNextPageParam: (lastPage, _, lastPageParam) => {\n if (!lastPage?.experiments?.length || !lastPage.pagination?.hasMore) {\n return undefined;\n }\n return lastPageParam + 1;\n },\n select: (data: InfiniteData<ListExperimentsResponse, number>) =>\n data.pages.flatMap(page => page?.experiments ?? []) as TData,\n ...queryOptions,\n });\n\n useEffect(() => {\n if (isEndOfListInView && query.hasNextPage && !query.isFetchingNextPage) {\n void query.fetchNextPage();\n }\n }, [isEndOfListInView, query]);\n\n return { ...query, setEndOfListElement };\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { n as useMastraClient } from "./mastra-client-context-
|
|
2
|
-
import { t as useInView } from "./use-in-view-
|
|
3
|
-
import { t as useCombineByIds } from "./combine-by-ids-
|
|
4
|
-
import { t as getOrCreate } from "./map-
|
|
1
|
+
import { n as useMastraClient } from "./mastra-client-context-DQ5z_ze9.js";
|
|
2
|
+
import { t as useInView } from "./use-in-view-CyjKdplM.js";
|
|
3
|
+
import { t as useCombineByIds } from "./combine-by-ids-C-0oe4eH.js";
|
|
4
|
+
import { t as getOrCreate } from "./map-5-4U5rwA.js";
|
|
5
5
|
import { useEffect } from "react";
|
|
6
6
|
import { keepPreviousData, useInfiniteQuery, useQueries, useQuery } from "@tanstack/react-query";
|
|
7
|
-
|
|
7
|
+
// src/hooks/experiments/usage-accumulator.ts
|
|
8
8
|
const INPUT_TOKEN_METRIC = "mastra_model_total_input_tokens";
|
|
9
9
|
const OUTPUT_TOKEN_METRIC = "mastra_model_total_output_tokens";
|
|
10
10
|
const createAccumulator = () => ({
|
|
@@ -34,8 +34,7 @@ function toUsage(accumulator) {
|
|
|
34
34
|
costUnit: isPriced ? [...accumulator.costUnits][0] : void 0
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
//#region src/hooks/experiments/use-experiment-item-usage.ts
|
|
37
|
+
// src/hooks/experiments/use-experiment-item-usage.ts
|
|
39
38
|
const POLL_INTERVAL_MS$1 = 2e3;
|
|
40
39
|
/** Builds the experiment totals and the per-trace usage in a single pass over the groups. */
|
|
41
40
|
function toExperimentUsage(response) {
|
|
@@ -91,8 +90,7 @@ const useExperimentsItemUsage = ({ experiments, supportsMetrics }) => {
|
|
|
91
90
|
combine
|
|
92
91
|
});
|
|
93
92
|
};
|
|
94
|
-
|
|
95
|
-
//#region src/hooks/experiments/use-experiment-metrics.ts
|
|
93
|
+
// src/hooks/experiments/use-experiment-metrics.ts
|
|
96
94
|
/**
|
|
97
95
|
* Experiment-scoped metrics (tokens, cost, avg agent latency).
|
|
98
96
|
* Filters only by experimentId — no time window, since the experiment already bounds the row set.
|
|
@@ -142,8 +140,7 @@ const useExperimentMetrics = ({ experimentId, experimentStatus, supportsMetrics,
|
|
|
142
140
|
isEnabled
|
|
143
141
|
};
|
|
144
142
|
};
|
|
145
|
-
|
|
146
|
-
//#region src/hooks/experiments/use-experiment-trace.ts
|
|
143
|
+
// src/hooks/experiments/use-experiment-trace.ts
|
|
147
144
|
/**
|
|
148
145
|
* Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.
|
|
149
146
|
*/
|
|
@@ -158,8 +155,7 @@ const useExperimentTrace = ({ traceId, queryOptions }) => {
|
|
|
158
155
|
...queryOptions
|
|
159
156
|
});
|
|
160
157
|
};
|
|
161
|
-
|
|
162
|
-
//#region src/hooks/experiments/use-experiments-for-dataset-filter.ts
|
|
158
|
+
// src/hooks/experiments/use-experiments-for-dataset-filter.ts
|
|
163
159
|
/** Explicit page size: server defaults are 20 (global) / 10 (per dataset), which is too small for the list. */
|
|
164
160
|
const EXPERIMENTS_PAGE_SIZE = 100;
|
|
165
161
|
function useExperimentsForDatasetFilter({ datasetId, target, queryOptions }) {
|
|
@@ -177,8 +173,7 @@ function useExperimentsForDatasetFilter({ datasetId, target, queryOptions }) {
|
|
|
177
173
|
...queryOptions
|
|
178
174
|
});
|
|
179
175
|
}
|
|
180
|
-
|
|
181
|
-
//#region src/hooks/experiments/use-experiments-list-metrics.ts
|
|
176
|
+
// src/hooks/experiments/use-experiments-list-metrics.ts
|
|
182
177
|
const AGENT_DURATION_METRIC = "mastra_agent_duration_ms";
|
|
183
178
|
const EXPERIMENT_IDS_PER_REQUEST = 500;
|
|
184
179
|
const POLL_INTERVAL_MS = 5e3;
|
|
@@ -247,8 +242,7 @@ const useExperimentsListMetrics = ({ experiments, supportsMetrics }) => {
|
|
|
247
242
|
refetchInterval: hasActiveExperiment ? POLL_INTERVAL_MS : false
|
|
248
243
|
});
|
|
249
244
|
};
|
|
250
|
-
|
|
251
|
-
//#region src/hooks/experiments/use-infinite-experiments.ts
|
|
245
|
+
// src/hooks/experiments/use-infinite-experiments.ts
|
|
252
246
|
const EXPERIMENTS_PER_PAGE = 100;
|
|
253
247
|
function useInfiniteExperiments({ datasetId, target, orderBy, queryOptions }) {
|
|
254
248
|
const client = useMastraClient();
|
|
@@ -300,7 +294,6 @@ function useInfiniteExperiments({ datasetId, target, orderBy, queryOptions }) {
|
|
|
300
294
|
setEndOfListElement
|
|
301
295
|
};
|
|
302
296
|
}
|
|
303
|
-
//#endregion
|
|
304
297
|
export { useExperimentsForDatasetFilter as a, useExperimentsItemUsage as c, EXPERIMENTS_PAGE_SIZE as i, useInfiniteExperiments as n, useExperimentTrace as o, useExperimentsListMetrics as r, useExperimentMetrics as s, EXPERIMENTS_PER_PAGE as t };
|
|
305
298
|
|
|
306
|
-
//# sourceMappingURL=experiments-
|
|
299
|
+
//# sourceMappingURL=experiments-DyecjEut.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"experiments-B7vRMK5T.js","names":["POLL_INTERVAL_MS"],"sources":["../src/hooks/experiments/usage-accumulator.ts","../src/hooks/experiments/use-experiment-item-usage.ts","../src/hooks/experiments/use-experiment-metrics.ts","../src/hooks/experiments/use-experiment-trace.ts","../src/hooks/experiments/use-experiments-for-dataset-filter.ts","../src/hooks/experiments/use-experiments-list-metrics.ts","../src/hooks/experiments/use-infinite-experiments.ts"],"sourcesContent":["import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\n\nexport const INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nexport const OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\n\nexport interface ExperimentItemUsage {\n inputTokens?: number;\n outputTokens?: number;\n /** Undefined when no group is priced or when groups use different cost units. */\n estimatedCost?: number;\n costUnit?: string;\n}\n\nexport type UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nexport const createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\n/** The token field a breakdown group feeds, or undefined when the group is not a token metric. */\nexport function tokenFieldOf(group: BreakdownGroup): 'inputTokens' | 'outputTokens' | undefined {\n const metricName = group.dimensions.name;\n if (metricName === INPUT_TOKEN_METRIC) return 'inputTokens';\n if (metricName === OUTPUT_TOKEN_METRIC) return 'outputTokens';\n return undefined;\n}\n\nexport function accumulate(\n accumulator: UsageAccumulator,\n tokenField: 'inputTokens' | 'outputTokens',\n group: BreakdownGroup,\n) {\n accumulator[tokenField] = (accumulator[tokenField] ?? 0) + group.value;\n\n if (group.estimatedCost == null) return;\n accumulator.estimatedCost += group.estimatedCost;\n if (group.costUnit) {\n accumulator.costUnits.add(group.costUnit.toLowerCase());\n } else {\n accumulator.hasUnknownCostUnit = true;\n }\n}\n\nexport function toUsage(accumulator: UsageAccumulator): ExperimentItemUsage {\n // A single known unit implies at least one priced group.\n const isPriced = !accumulator.hasUnknownCostUnit && accumulator.costUnits.size === 1;\n return {\n inputTokens: accumulator.inputTokens,\n outputTokens: accumulator.outputTokens,\n estimatedCost: isPriced ? accumulator.estimatedCost : undefined,\n costUnit: isPriced ? [...accumulator.costUnits][0] : undefined,\n };\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { useQueries } from '@tanstack/react-query';\nimport type { Query } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { useCombineByIds } from '../shared/combine-by-ids';\nimport type { ExperimentQueriesResult } from '../shared/combine-by-ids';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nexport type { ExperimentItemUsage } from './usage-accumulator';\n\nconst POLL_INTERVAL_MS = 2000;\n\n/** Usage of one experiment: its totals, and each item's usage keyed by the trace id of its run. */\nexport interface ExperimentUsage {\n /** Totals across every item. The cost sums priced groups only. */\n summary: ExperimentItemUsage;\n traces: Record<string, ExperimentItemUsage>;\n}\n\n/** Builds the experiment totals and the per-trace usage in a single pass over the groups. */\nfunction toExperimentUsage(response: GetMetricBreakdownResponse): ExperimentUsage {\n const summary = createAccumulator();\n const accumulators = new Map<string, UsageAccumulator>();\n\n for (const group of response.groups) {\n const tokenField = tokenFieldOf(group);\n if (!tokenField) continue;\n\n accumulate(summary, tokenField, group);\n\n const traceId = group.dimensions.traceId;\n if (traceId) accumulate(getOrCreate(accumulators, traceId, createAccumulator), tokenField, group);\n }\n\n const traces: Record<string, ExperimentItemUsage> = {};\n for (const [traceId, accumulator] of accumulators) {\n traces[traceId] = toUsage(accumulator);\n }\n return { summary: toUsage(summary), traces };\n}\n\n/** How many breakdowns to fetch after completion while the exporter may still be flushing metrics. */\nconst MAX_EMPTY_ATTEMPTS = 5;\n\nconst isEmptyUsage = (usage: ExperimentUsage | undefined) =>\n !usage ||\n (Object.keys(usage.traces).length === 0 &&\n usage.summary.inputTokens === undefined &&\n usage.summary.outputTokens === undefined);\n\n/**\n * Token and cost usage of several experiments, keyed by experiment id: each entry holds the\n * experiment totals (`summary`) and every item's usage keyed by trace id (`traces`).\n * Sends one metric breakdown request per experiment, filtered by its id rather than by trace ids,\n * so it does not wait for the experiment results. Usage is only fetched once an experiment has\n * finished; while the breakdown is still empty (metrics flushing), it retries every 2 seconds up to\n * 5 attempts. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsItemUsage = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): ExperimentQueriesResult<ExperimentUsage> => {\n const client = useMastraClient();\n const combine = useCombineByIds<ExperimentUsage>(experiments.map(experiment => experiment.id));\n\n return useQueries({\n queries: experiments.map(({ id, status }) => {\n const finished = status !== 'running' && status !== 'pending';\n return {\n queryKey: ['experiment-item-usage', id, finished],\n queryFn: async () =>\n toExperimentUsage(\n await client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['traceId', 'name'],\n filters: { experimentId: id },\n }),\n ),\n enabled: supportsMetrics && finished,\n refetchInterval: (query: Query<ExperimentUsage>) =>\n isEmptyUsage(query.state.data) && query.state.dataUpdateCount < MAX_EMPTY_ATTEMPTS ? POLL_INTERVAL_MS : false,\n };\n }),\n combine,\n });\n};\n","import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\nexport interface ExperimentMetrics {\n totalTokens: number | null;\n estimatedCost: number | null;\n costUnit: string | null;\n avgAgentDurationMs: number | null;\n agentRuns: number | null;\n}\n\ninterface UseExperimentMetricsArgs {\n experimentId: string | undefined;\n experimentStatus: DatasetExperiment['status'] | undefined;\n /** Whether the observability store can serve metrics; no requests are made when false. */\n supportsMetrics: boolean;\n queryOptions?: MastraQueryOptions<ExperimentMetrics>;\n}\n\n/**\n * Experiment-scoped metrics (tokens, cost, avg agent latency).\n * Filters only by experimentId — no time window, since the experiment already bounds the row set.\n * Polls every 2 seconds while the experiment is running or pending.\n *\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentMetrics = ({\n experimentId,\n experimentStatus,\n supportsMetrics,\n queryOptions,\n}: UseExperimentMetricsArgs) => {\n const client = useMastraClient();\n const isEnabled = Boolean(experimentId) && supportsMetrics;\n const isActive = experimentStatus === 'running' || experimentStatus === 'pending';\n\n const query = useQuery({\n queryKey: ['experiment-metrics', experimentId],\n refetchInterval: isActive ? 2000 : false,\n queryFn: async (): Promise<ExperimentMetrics> => {\n // `enabled` already guards this; the check exists to narrow the type.\n if (!experimentId) throw new Error('experimentId is required');\n const filters = { experimentId };\n\n const [tokens, avgDuration, runs] = await Promise.all([\n client.getMetricAggregate({\n name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'],\n aggregation: 'sum',\n filters,\n }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'avg', filters }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'count', filters }),\n ]);\n\n return {\n totalTokens: tokens.value ?? null,\n estimatedCost: tokens.estimatedCost ?? null,\n costUnit: tokens.costUnit ?? null,\n avgAgentDurationMs: avgDuration.value ?? null,\n agentRuns: runs.value ?? null,\n };\n },\n ...queryOptions,\n });\n\n return { data: query.data, isLoading: query.isLoading, isEnabled };\n};\n","import type { MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\ntype TraceLightResponse = Awaited<ReturnType<MastraClient['getTraceLight']>>;\n\n/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentTrace = <TData = TraceLightResponse>({\n traceId,\n queryOptions,\n}: {\n traceId: string | null | undefined;\n queryOptions?: MastraQueryOptions<TraceLightResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery({\n queryKey: ['experiment-trace-light', traceId],\n queryFn: async () => {\n if (!traceId) {\n throw new Error('Trace ID is required');\n }\n return client.getTraceLight(traceId);\n },\n ...queryOptions,\n });\n};\n","import type { ExperimentTargetType, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\n/** Explicit page size: server defaults are 20 (global) / 10 (per dataset), which is too small for the list. */\nexport const EXPERIMENTS_PAGE_SIZE = 100;\n\nexport interface ExperimentTargetFilter {\n targetType?: ExperimentTargetType | '';\n targetId?: string;\n}\n\n/**\n * Experiments for the list page: the global list, or the dataset-scoped list when a dataset filter is active.\n * A dataset's runs may not be in the first page of the global list, so filtering client-side is not enough.\n * The optional target filter is applied server-side for the same reason.\n */\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useExperimentsForDatasetFilter<TData = ListExperimentsResponse>({\n datasetId,\n target,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n queryOptions?: MastraQueryOptions<ListExperimentsResponse, TData>;\n}): UseQueryResult<TData, Error> {\n const client = useMastraClient();\n const params: ListExperimentsParams = { perPage: EXPERIMENTS_PAGE_SIZE };\n if (target?.targetType) params.targetType = target.targetType;\n if (target?.targetId) params.targetId = target.targetId;\n\n return useQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId ? ['dataset-experiments', datasetId, params] : ['experiments', params],\n queryFn: () => (datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params)),\n ...queryOptions,\n });\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nconst AGENT_DURATION_METRIC = 'mastra_agent_duration_ms';\n// Each experiment can return two token groups, while the breakdown endpoint allows at most 1,000.\nconst EXPERIMENT_IDS_PER_REQUEST = 500;\nconst POLL_INTERVAL_MS = 5000;\n\n/** Totals of one experiment: token usage, its estimated cost, and the average agent run duration. */\nexport interface ExperimentListMetrics extends ExperimentItemUsage {\n avgDurationMs?: number;\n}\n\nfunction chunk(ids: readonly string[]): string[][] {\n const chunks: string[][] = [];\n for (let index = 0; index < ids.length; index += EXPERIMENT_IDS_PER_REQUEST) {\n chunks.push(ids.slice(index, index + EXPERIMENT_IDS_PER_REQUEST));\n }\n return chunks;\n}\n\nfunction settledValues<T>(results: PromiseSettledResult<T>[]): T[] {\n return results.flatMap(result => (result.status === 'fulfilled' ? [result.value] : []));\n}\n\n/**\n * Builds each requested experiment's metrics. Groups for other experiments are skipped,\n * in case a store ignores the `experimentIds` filter.\n */\nfunction toListMetrics(\n experimentIds: readonly string[],\n tokenResponses: GetMetricBreakdownResponse[],\n durationResponses: GetMetricBreakdownResponse[],\n): Map<string, ExperimentListMetrics> {\n const requested = new Set(experimentIds);\n const accumulators = new Map<string, UsageAccumulator>();\n for (const group of tokenResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n const tokenField = tokenFieldOf(group);\n if (!experimentId || !requested.has(experimentId) || !tokenField) continue;\n accumulate(getOrCreate(accumulators, experimentId, createAccumulator), tokenField, group);\n }\n\n const metrics = new Map<string, ExperimentListMetrics>();\n for (const [experimentId, accumulator] of accumulators) {\n metrics.set(experimentId, toUsage(accumulator));\n }\n for (const group of durationResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n if (!experimentId || !requested.has(experimentId)) continue;\n getOrCreate(metrics, experimentId, (): ExperimentListMetrics => ({})).avgDurationMs = group.value;\n }\n return metrics;\n}\n\n/**\n * Token usage, estimated cost and average agent duration of many experiments, keyed by experiment id.\n * Sends two metric breakdowns per 500 experiments (tokens grouped by experiment and metric name,\n * durations grouped by experiment) instead of one request per experiment.\n * Experiments without any metric are absent from the map. Polls while an experiment is running\n * or pending. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsListMetrics = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): UseQueryResult<Map<string, ExperimentListMetrics>, Error> => {\n const client = useMastraClient();\n const experimentIds = [...new Set(experiments.map(experiment => experiment.id))].toSorted();\n const hasActiveExperiment = experiments.some(({ status }) => status === 'running' || status === 'pending');\n\n return useQuery({\n queryKey: ['experiments-list-metrics', experimentIds],\n enabled: supportsMetrics && experimentIds.length > 0,\n queryFn: async () => {\n const batches = chunk(experimentIds);\n const [tokenResults, durationResults] = await Promise.all([\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['experimentId', 'name'],\n filters: { experimentIds: batch },\n limit: batch.length * 2,\n }),\n ),\n ),\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [AGENT_DURATION_METRIC],\n aggregation: 'avg',\n groupBy: ['experimentId'],\n filters: { experimentIds: batch },\n limit: batch.length,\n }),\n ),\n ),\n ]);\n return toListMetrics(experimentIds, settledValues(tokenResults), settledValues(durationResults));\n },\n // Loading another page changes the ids; keep the rows already shown filled meanwhile.\n placeholderData: keepPreviousData,\n refetchInterval: hasActiveExperiment ? POLL_INTERVAL_MS : false,\n });\n};\n","import type { DatasetExperiment, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';\nimport { useEffect } from 'react';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraInfiniteQueryOptions } from '../shared/query-options';\nimport { useInView } from '../shared/use-in-view';\nimport type { ExperimentTargetFilter } from './use-experiments-for-dataset-filter';\n\nexport const EXPERIMENTS_PER_PAGE = 100;\n\n/**\n * Infinite-scroll experiments for the list page: the global list, or the dataset-scoped list when a\n * dataset filter is active (a dataset's runs may not be in the first pages of the global list). The\n * optional target filter is applied server-side for the same reason.\n */\nexport type ExperimentsOrderBy = NonNullable<ListExperimentsParams['orderBy']>;\n\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useInfiniteExperiments<TData = DatasetExperiment[]>({\n datasetId,\n target,\n orderBy,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n orderBy?: ExperimentsOrderBy;\n queryOptions?: MastraInfiniteQueryOptions<ListExperimentsResponse, TData, QueryKey, number>;\n}): UseInfiniteQueryResult<TData, Error> & {\n setEndOfListElement: (element: HTMLDivElement | null) => void;\n} {\n const client = useMastraClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n const targetType = target?.targetType || undefined;\n const targetId = target?.targetId || undefined;\n\n const query = useInfiniteQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId\n ? ['dataset-experiments', datasetId, 'infinite', { targetType, targetId, orderBy }]\n : ['experiments', 'infinite', { targetType, targetId, orderBy }],\n queryFn: ({ pageParam }) => {\n const params: ListExperimentsParams = { page: pageParam, perPage: EXPERIMENTS_PER_PAGE };\n if (targetType) params.targetType = targetType;\n if (targetId) params.targetId = targetId;\n if (orderBy) params.orderBy = orderBy;\n return datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params);\n },\n initialPageParam: 0,\n getNextPageParam: (lastPage, _, lastPageParam) => {\n if (!lastPage?.experiments?.length || !lastPage.pagination?.hasMore) {\n return undefined;\n }\n return lastPageParam + 1;\n },\n select: (data: InfiniteData<ListExperimentsResponse, number>) =>\n data.pages.flatMap(page => page?.experiments ?? []) as TData,\n ...queryOptions,\n });\n\n useEffect(() => {\n if (isEndOfListInView && query.hasNextPage && !query.isFetchingNextPage) {\n void query.fetchNextPage();\n }\n }, [isEndOfListInView, query]);\n\n return { ...query, setEndOfListElement };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"experiments-DyecjEut.js","names":["POLL_INTERVAL_MS"],"sources":["../src/hooks/experiments/usage-accumulator.ts","../src/hooks/experiments/use-experiment-item-usage.ts","../src/hooks/experiments/use-experiment-metrics.ts","../src/hooks/experiments/use-experiment-trace.ts","../src/hooks/experiments/use-experiments-for-dataset-filter.ts","../src/hooks/experiments/use-experiments-list-metrics.ts","../src/hooks/experiments/use-infinite-experiments.ts"],"sourcesContent":["import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\n\nexport const INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nexport const OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\n\nexport interface ExperimentItemUsage {\n inputTokens?: number;\n outputTokens?: number;\n /** Undefined when no group is priced or when groups use different cost units. */\n estimatedCost?: number;\n costUnit?: string;\n}\n\nexport type UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nexport const createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\n/** The token field a breakdown group feeds, or undefined when the group is not a token metric. */\nexport function tokenFieldOf(group: BreakdownGroup): 'inputTokens' | 'outputTokens' | undefined {\n const metricName = group.dimensions.name;\n if (metricName === INPUT_TOKEN_METRIC) return 'inputTokens';\n if (metricName === OUTPUT_TOKEN_METRIC) return 'outputTokens';\n return undefined;\n}\n\nexport function accumulate(\n accumulator: UsageAccumulator,\n tokenField: 'inputTokens' | 'outputTokens',\n group: BreakdownGroup,\n) {\n accumulator[tokenField] = (accumulator[tokenField] ?? 0) + group.value;\n\n if (group.estimatedCost == null) return;\n accumulator.estimatedCost += group.estimatedCost;\n if (group.costUnit) {\n accumulator.costUnits.add(group.costUnit.toLowerCase());\n } else {\n accumulator.hasUnknownCostUnit = true;\n }\n}\n\nexport function toUsage(accumulator: UsageAccumulator): ExperimentItemUsage {\n // A single known unit implies at least one priced group.\n const isPriced = !accumulator.hasUnknownCostUnit && accumulator.costUnits.size === 1;\n return {\n inputTokens: accumulator.inputTokens,\n outputTokens: accumulator.outputTokens,\n estimatedCost: isPriced ? accumulator.estimatedCost : undefined,\n costUnit: isPriced ? [...accumulator.costUnits][0] : undefined,\n };\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { useQueries } from '@tanstack/react-query';\nimport type { Query } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { useCombineByIds } from '../shared/combine-by-ids';\nimport type { ExperimentQueriesResult } from '../shared/combine-by-ids';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nexport type { ExperimentItemUsage } from './usage-accumulator';\n\nconst POLL_INTERVAL_MS = 2000;\n\n/** Usage of one experiment: its totals, and each item's usage keyed by the trace id of its run. */\nexport interface ExperimentUsage {\n /** Totals across every item. The cost sums priced groups only. */\n summary: ExperimentItemUsage;\n traces: Record<string, ExperimentItemUsage>;\n}\n\n/** Builds the experiment totals and the per-trace usage in a single pass over the groups. */\nfunction toExperimentUsage(response: GetMetricBreakdownResponse): ExperimentUsage {\n const summary = createAccumulator();\n const accumulators = new Map<string, UsageAccumulator>();\n\n for (const group of response.groups) {\n const tokenField = tokenFieldOf(group);\n if (!tokenField) continue;\n\n accumulate(summary, tokenField, group);\n\n const traceId = group.dimensions.traceId;\n if (traceId) accumulate(getOrCreate(accumulators, traceId, createAccumulator), tokenField, group);\n }\n\n const traces: Record<string, ExperimentItemUsage> = {};\n for (const [traceId, accumulator] of accumulators) {\n traces[traceId] = toUsage(accumulator);\n }\n return { summary: toUsage(summary), traces };\n}\n\n/** How many breakdowns to fetch after completion while the exporter may still be flushing metrics. */\nconst MAX_EMPTY_ATTEMPTS = 5;\n\nconst isEmptyUsage = (usage: ExperimentUsage | undefined) =>\n !usage ||\n (Object.keys(usage.traces).length === 0 &&\n usage.summary.inputTokens === undefined &&\n usage.summary.outputTokens === undefined);\n\n/**\n * Token and cost usage of several experiments, keyed by experiment id: each entry holds the\n * experiment totals (`summary`) and every item's usage keyed by trace id (`traces`).\n * Sends one metric breakdown request per experiment, filtered by its id rather than by trace ids,\n * so it does not wait for the experiment results. Usage is only fetched once an experiment has\n * finished; while the breakdown is still empty (metrics flushing), it retries every 2 seconds up to\n * 5 attempts. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsItemUsage = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): ExperimentQueriesResult<ExperimentUsage> => {\n const client = useMastraClient();\n const combine = useCombineByIds<ExperimentUsage>(experiments.map(experiment => experiment.id));\n\n return useQueries({\n queries: experiments.map(({ id, status }) => {\n const finished = status !== 'running' && status !== 'pending';\n return {\n queryKey: ['experiment-item-usage', id, finished],\n queryFn: async () =>\n toExperimentUsage(\n await client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['traceId', 'name'],\n filters: { experimentId: id },\n }),\n ),\n enabled: supportsMetrics && finished,\n refetchInterval: (query: Query<ExperimentUsage>) =>\n isEmptyUsage(query.state.data) && query.state.dataUpdateCount < MAX_EMPTY_ATTEMPTS ? POLL_INTERVAL_MS : false,\n };\n }),\n combine,\n });\n};\n","import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\nexport interface ExperimentMetrics {\n totalTokens: number | null;\n estimatedCost: number | null;\n costUnit: string | null;\n avgAgentDurationMs: number | null;\n agentRuns: number | null;\n}\n\ninterface UseExperimentMetricsArgs {\n experimentId: string | undefined;\n experimentStatus: DatasetExperiment['status'] | undefined;\n /** Whether the observability store can serve metrics; no requests are made when false. */\n supportsMetrics: boolean;\n queryOptions?: MastraQueryOptions<ExperimentMetrics>;\n}\n\n/**\n * Experiment-scoped metrics (tokens, cost, avg agent latency).\n * Filters only by experimentId — no time window, since the experiment already bounds the row set.\n * Polls every 2 seconds while the experiment is running or pending.\n *\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentMetrics = ({\n experimentId,\n experimentStatus,\n supportsMetrics,\n queryOptions,\n}: UseExperimentMetricsArgs) => {\n const client = useMastraClient();\n const isEnabled = Boolean(experimentId) && supportsMetrics;\n const isActive = experimentStatus === 'running' || experimentStatus === 'pending';\n\n const query = useQuery({\n queryKey: ['experiment-metrics', experimentId],\n refetchInterval: isActive ? 2000 : false,\n queryFn: async (): Promise<ExperimentMetrics> => {\n // `enabled` already guards this; the check exists to narrow the type.\n if (!experimentId) throw new Error('experimentId is required');\n const filters = { experimentId };\n\n const [tokens, avgDuration, runs] = await Promise.all([\n client.getMetricAggregate({\n name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'],\n aggregation: 'sum',\n filters,\n }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'avg', filters }),\n client.getMetricAggregate({ name: ['mastra_agent_duration_ms'], aggregation: 'count', filters }),\n ]);\n\n return {\n totalTokens: tokens.value ?? null,\n estimatedCost: tokens.estimatedCost ?? null,\n costUnit: tokens.costUnit ?? null,\n avgAgentDurationMs: avgDuration.value ?? null,\n agentRuns: runs.value ?? null,\n };\n },\n ...queryOptions,\n });\n\n return { data: query.data, isLoading: query.isLoading, isEnabled };\n};\n","import type { MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\ntype TraceLightResponse = Awaited<ReturnType<MastraClient['getTraceLight']>>;\n\n/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useExperimentTrace = <TData = TraceLightResponse>({\n traceId,\n queryOptions,\n}: {\n traceId: string | null | undefined;\n queryOptions?: MastraQueryOptions<TraceLightResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery({\n queryKey: ['experiment-trace-light', traceId],\n queryFn: async () => {\n if (!traceId) {\n throw new Error('Trace ID is required');\n }\n return client.getTraceLight(traceId);\n },\n ...queryOptions,\n });\n};\n","import type { ExperimentTargetType, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useQuery } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraQueryOptions } from '../shared/query-options';\n\n/** Explicit page size: server defaults are 20 (global) / 10 (per dataset), which is too small for the list. */\nexport const EXPERIMENTS_PAGE_SIZE = 100;\n\nexport interface ExperimentTargetFilter {\n targetType?: ExperimentTargetType | '';\n targetId?: string;\n}\n\n/**\n * Experiments for the list page: the global list, or the dataset-scoped list when a dataset filter is active.\n * A dataset's runs may not be in the first page of the global list, so filtering client-side is not enough.\n * The optional target filter is applied server-side for the same reason.\n */\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useExperimentsForDatasetFilter<TData = ListExperimentsResponse>({\n datasetId,\n target,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n queryOptions?: MastraQueryOptions<ListExperimentsResponse, TData>;\n}): UseQueryResult<TData, Error> {\n const client = useMastraClient();\n const params: ListExperimentsParams = { perPage: EXPERIMENTS_PAGE_SIZE };\n if (target?.targetType) params.targetType = target.targetType;\n if (target?.targetId) params.targetId = target.targetId;\n\n return useQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId ? ['dataset-experiments', datasetId, params] : ['experiments', params],\n queryFn: () => (datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params)),\n ...queryOptions,\n });\n}\n","import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { ExperimentRef } from '../datasets/use-dataset-experiments';\nimport { getOrCreate } from '../shared/map';\nimport {\n INPUT_TOKEN_METRIC,\n OUTPUT_TOKEN_METRIC,\n accumulate,\n createAccumulator,\n tokenFieldOf,\n toUsage,\n} from './usage-accumulator';\nimport type { ExperimentItemUsage, UsageAccumulator } from './usage-accumulator';\n\nconst AGENT_DURATION_METRIC = 'mastra_agent_duration_ms';\n// Each experiment can return two token groups, while the breakdown endpoint allows at most 1,000.\nconst EXPERIMENT_IDS_PER_REQUEST = 500;\nconst POLL_INTERVAL_MS = 5000;\n\n/** Totals of one experiment: token usage, its estimated cost, and the average agent run duration. */\nexport interface ExperimentListMetrics extends ExperimentItemUsage {\n avgDurationMs?: number;\n}\n\nfunction chunk(ids: readonly string[]): string[][] {\n const chunks: string[][] = [];\n for (let index = 0; index < ids.length; index += EXPERIMENT_IDS_PER_REQUEST) {\n chunks.push(ids.slice(index, index + EXPERIMENT_IDS_PER_REQUEST));\n }\n return chunks;\n}\n\nfunction settledValues<T>(results: PromiseSettledResult<T>[]): T[] {\n return results.flatMap(result => (result.status === 'fulfilled' ? [result.value] : []));\n}\n\n/**\n * Builds each requested experiment's metrics. Groups for other experiments are skipped,\n * in case a store ignores the `experimentIds` filter.\n */\nfunction toListMetrics(\n experimentIds: readonly string[],\n tokenResponses: GetMetricBreakdownResponse[],\n durationResponses: GetMetricBreakdownResponse[],\n): Map<string, ExperimentListMetrics> {\n const requested = new Set(experimentIds);\n const accumulators = new Map<string, UsageAccumulator>();\n for (const group of tokenResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n const tokenField = tokenFieldOf(group);\n if (!experimentId || !requested.has(experimentId) || !tokenField) continue;\n accumulate(getOrCreate(accumulators, experimentId, createAccumulator), tokenField, group);\n }\n\n const metrics = new Map<string, ExperimentListMetrics>();\n for (const [experimentId, accumulator] of accumulators) {\n metrics.set(experimentId, toUsage(accumulator));\n }\n for (const group of durationResponses.flatMap(response => response.groups)) {\n const experimentId = group.dimensions.experimentId;\n if (!experimentId || !requested.has(experimentId)) continue;\n getOrCreate(metrics, experimentId, (): ExperimentListMetrics => ({})).avgDurationMs = group.value;\n }\n return metrics;\n}\n\n/**\n * Token usage, estimated cost and average agent duration of many experiments, keyed by experiment id.\n * Sends two metric breakdowns per 500 experiments (tokens grouped by experiment and metric name,\n * durations grouped by experiment) instead of one request per experiment.\n * Experiments without any metric are absent from the map. Polls while an experiment is running\n * or pending. No request is made when the observability store cannot serve metrics.\n */\nexport const useExperimentsListMetrics = ({\n experiments,\n supportsMetrics,\n}: {\n experiments: ExperimentRef[];\n supportsMetrics: boolean;\n}): UseQueryResult<Map<string, ExperimentListMetrics>, Error> => {\n const client = useMastraClient();\n const experimentIds = [...new Set(experiments.map(experiment => experiment.id))].toSorted();\n const hasActiveExperiment = experiments.some(({ status }) => status === 'running' || status === 'pending');\n\n return useQuery({\n queryKey: ['experiments-list-metrics', experimentIds],\n enabled: supportsMetrics && experimentIds.length > 0,\n queryFn: async () => {\n const batches = chunk(experimentIds);\n const [tokenResults, durationResults] = await Promise.all([\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],\n aggregation: 'sum',\n groupBy: ['experimentId', 'name'],\n filters: { experimentIds: batch },\n limit: batch.length * 2,\n }),\n ),\n ),\n Promise.allSettled(\n batches.map(batch =>\n client.getMetricBreakdown({\n name: [AGENT_DURATION_METRIC],\n aggregation: 'avg',\n groupBy: ['experimentId'],\n filters: { experimentIds: batch },\n limit: batch.length,\n }),\n ),\n ),\n ]);\n return toListMetrics(experimentIds, settledValues(tokenResults), settledValues(durationResults));\n },\n // Loading another page changes the ids; keep the rows already shown filled meanwhile.\n placeholderData: keepPreviousData,\n refetchInterval: hasActiveExperiment ? POLL_INTERVAL_MS : false,\n });\n};\n","import type { DatasetExperiment, ListExperimentsParams, MastraClient } from '@mastra/client-js';\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';\nimport { useEffect } from 'react';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraInfiniteQueryOptions } from '../shared/query-options';\nimport { useInView } from '../shared/use-in-view';\nimport type { ExperimentTargetFilter } from './use-experiments-for-dataset-filter';\n\nexport const EXPERIMENTS_PER_PAGE = 100;\n\n/**\n * Infinite-scroll experiments for the list page: the global list, or the dataset-scoped list when a\n * dataset filter is active (a dataset's runs may not be in the first pages of the global list). The\n * optional target filter is applied server-side for the same reason.\n */\nexport type ExperimentsOrderBy = NonNullable<ListExperimentsParams['orderBy']>;\n\ntype ListExperimentsResponse = Awaited<ReturnType<MastraClient['listExperiments']>>;\n\nexport function useInfiniteExperiments<TData = DatasetExperiment[]>({\n datasetId,\n target,\n orderBy,\n queryOptions,\n}: {\n datasetId: string | undefined;\n target?: ExperimentTargetFilter;\n orderBy?: ExperimentsOrderBy;\n queryOptions?: MastraInfiniteQueryOptions<ListExperimentsResponse, TData, QueryKey, number>;\n}): UseInfiniteQueryResult<TData, Error> & {\n setEndOfListElement: (element: HTMLDivElement | null) => void;\n} {\n const client = useMastraClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n const targetType = target?.targetType || undefined;\n const targetId = target?.targetId || undefined;\n\n const query = useInfiniteQuery({\n // Prefixes match the keys invalidated by dataset/experiment mutations.\n queryKey: datasetId\n ? ['dataset-experiments', datasetId, 'infinite', { targetType, targetId, orderBy }]\n : ['experiments', 'infinite', { targetType, targetId, orderBy }],\n queryFn: ({ pageParam }) => {\n const params: ListExperimentsParams = { page: pageParam, perPage: EXPERIMENTS_PER_PAGE };\n if (targetType) params.targetType = targetType;\n if (targetId) params.targetId = targetId;\n if (orderBy) params.orderBy = orderBy;\n return datasetId ? client.listDatasetExperiments(datasetId, params) : client.listExperiments(params);\n },\n initialPageParam: 0,\n getNextPageParam: (lastPage, _, lastPageParam) => {\n if (!lastPage?.experiments?.length || !lastPage.pagination?.hasMore) {\n return undefined;\n }\n return lastPageParam + 1;\n },\n select: (data: InfiniteData<ListExperimentsResponse, number>) =>\n data.pages.flatMap(page => page?.experiments ?? []) as TData,\n ...queryOptions,\n });\n\n useEffect(() => {\n if (isEndOfListInView && query.hasNextPage && !query.isFetchingNextPage) {\n void query.fetchNextPage();\n }\n }, [isEndOfListInView, query]);\n\n return { ...query, setEndOfListElement };\n}\n"],"mappings":"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"}
|
package/dist/{feedback-refetch-interval-XD5FVc2k.cjs → feedback-refetch-interval-BBcT9r8s.cjs}
RENAMED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
const require_query_utils = require("./query-utils-
|
|
2
|
-
|
|
1
|
+
const require_query_utils = require("./query-utils-BVgE_xxj.cjs");
|
|
2
|
+
// src/hooks/feedback/feedback-refetch-interval.ts
|
|
3
3
|
const FEEDBACK_REFETCH_INTERVAL_MS = 3e4;
|
|
4
4
|
/** Disables polling for unsupported or unavailable feedback storage; otherwise retries every thirty seconds. */
|
|
5
5
|
function getFeedbackRefetchInterval(query) {
|
|
6
6
|
if (require_query_utils.isUnsupportedObservabilityOperationError(query.state.error, "feedback") || require_query_utils.isObservabilityUnavailableError(query.state.error)) return false;
|
|
7
7
|
return FEEDBACK_REFETCH_INTERVAL_MS;
|
|
8
8
|
}
|
|
9
|
-
//#endregion
|
|
10
9
|
Object.defineProperty(exports, "getFeedbackRefetchInterval", {
|
|
11
10
|
enumerable: true,
|
|
12
11
|
get: function() {
|
|
@@ -14,4 +13,4 @@ Object.defineProperty(exports, "getFeedbackRefetchInterval", {
|
|
|
14
13
|
}
|
|
15
14
|
});
|
|
16
15
|
|
|
17
|
-
//# sourceMappingURL=feedback-refetch-interval-
|
|
16
|
+
//# sourceMappingURL=feedback-refetch-interval-BBcT9r8s.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feedback-refetch-interval-
|
|
1
|
+
{"version":3,"file":"feedback-refetch-interval-BBcT9r8s.cjs","names":["isUnsupportedObservabilityOperationError","isObservabilityUnavailableError"],"sources":["../src/hooks/feedback/feedback-refetch-interval.ts"],"sourcesContent":["import { isObservabilityUnavailableError, isUnsupportedObservabilityOperationError } from '../query/query-utils';\n\nconst FEEDBACK_REFETCH_INTERVAL_MS = 30_000;\n\n/** Disables polling for unsupported or unavailable feedback storage; otherwise retries every thirty seconds. */\nexport function getFeedbackRefetchInterval(query: { state: { error: unknown } }) {\n if (\n isUnsupportedObservabilityOperationError(query.state.error, 'feedback') ||\n isObservabilityUnavailableError(query.state.error)\n ) {\n return false;\n }\n return FEEDBACK_REFETCH_INTERVAL_MS;\n}\n"],"mappings":";;AAEA,MAAM,+BAA+B;;AAGrC,SAAgB,2BAA2B,OAAsC;CAC/E,IACEA,oBAAAA,yCAAyC,MAAM,MAAM,OAAO,UAAU,KACtEC,oBAAAA,gCAAgC,MAAM,MAAM,KAAK,GAEjD,OAAO;CAET,OAAO;AACT"}
|
package/dist/{feedback-refetch-interval-DLHzQoVV.js → feedback-refetch-interval-C12Nzb2r.js}
RENAMED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { o as isObservabilityUnavailableError, s as isUnsupportedObservabilityOperationError } from "./query-utils-
|
|
2
|
-
|
|
1
|
+
import { o as isObservabilityUnavailableError, s as isUnsupportedObservabilityOperationError } from "./query-utils-DvMERIwf.js";
|
|
2
|
+
// src/hooks/feedback/feedback-refetch-interval.ts
|
|
3
3
|
const FEEDBACK_REFETCH_INTERVAL_MS = 3e4;
|
|
4
4
|
/** Disables polling for unsupported or unavailable feedback storage; otherwise retries every thirty seconds. */
|
|
5
5
|
function getFeedbackRefetchInterval(query) {
|
|
6
6
|
if (isUnsupportedObservabilityOperationError(query.state.error, "feedback") || isObservabilityUnavailableError(query.state.error)) return false;
|
|
7
7
|
return FEEDBACK_REFETCH_INTERVAL_MS;
|
|
8
8
|
}
|
|
9
|
-
//#endregion
|
|
10
9
|
export { getFeedbackRefetchInterval as t };
|
|
11
10
|
|
|
12
|
-
//# sourceMappingURL=feedback-refetch-interval-
|
|
11
|
+
//# sourceMappingURL=feedback-refetch-interval-C12Nzb2r.js.map
|
package/dist/{feedback-refetch-interval-DLHzQoVV.js.map → feedback-refetch-interval-C12Nzb2r.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feedback-refetch-interval-
|
|
1
|
+
{"version":3,"file":"feedback-refetch-interval-C12Nzb2r.js","names":[],"sources":["../src/hooks/feedback/feedback-refetch-interval.ts"],"sourcesContent":["import { isObservabilityUnavailableError, isUnsupportedObservabilityOperationError } from '../query/query-utils';\n\nconst FEEDBACK_REFETCH_INTERVAL_MS = 30_000;\n\n/** Disables polling for unsupported or unavailable feedback storage; otherwise retries every thirty seconds. */\nexport function getFeedbackRefetchInterval(query: { state: { error: unknown } }) {\n if (\n isUnsupportedObservabilityOperationError(query.state.error, 'feedback') ||\n isObservabilityUnavailableError(query.state.error)\n ) {\n return false;\n }\n return FEEDBACK_REFETCH_INTERVAL_MS;\n}\n"],"mappings":";;AAEA,MAAM,+BAA+B;;AAGrC,SAAgB,2BAA2B,OAAsC;CAC/E,IACE,yCAAyC,MAAM,MAAM,OAAO,UAAU,KACtE,gCAAgC,MAAM,MAAM,KAAK,GAEjD,OAAO;CAET,OAAO;AACT"}
|