@mastra/react 1.11.1-alpha.2 → 1.11.1-alpha.4
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/agent/hooks.d.ts +13 -1
- package/dist/agent/hooks.d.ts.map +1 -1
- package/dist/chat/index.cjs +1 -1
- package/dist/chat/index.js +1 -1
- package/dist/hooks/experiments/index.cjs +24 -13
- package/dist/hooks/experiments/index.cjs.map +1 -1
- package/dist/hooks/experiments/index.js +24 -13
- package/dist/hooks/experiments/index.js.map +1 -1
- package/dist/hooks/experiments/use-experiment-item-usage.d.ts +3 -2
- package/dist/hooks/experiments/use-experiment-item-usage.d.ts.map +1 -1
- package/dist/hooks/review/index.cjs.map +1 -1
- package/dist/hooks/review/index.js.map +1 -1
- package/dist/hooks/review/use-dataset-review-items.d.ts +2 -2
- package/dist/hooks/review/use-dataset-review-items.d.ts.map +1 -1
- package/dist/hooks/schedules/index.cjs.map +1 -1
- package/dist/hooks/schedules/index.js.map +1 -1
- package/dist/hooks/schedules/use-schedule-triggers.d.ts +1 -1
- package/dist/hooks/schedules/use-schedule-triggers.d.ts.map +1 -1
- package/dist/{hooks-RBXwAb6P.cjs → hooks-CIwCEruG.cjs} +23 -8
- package/dist/hooks-CIwCEruG.cjs.map +1 -0
- package/dist/{hooks-CpIYiEYu.js → hooks-CbX7Q4A3.js} +23 -8
- package/dist/hooks-CbX7Q4A3.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -12
- package/dist/hooks-CpIYiEYu.js.map +0 -1
- package/dist/hooks-RBXwAb6P.cjs.map +0 -1
package/dist/agent/hooks.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SendAgentSignalParams } from '@mastra/client-js';
|
|
1
2
|
import type { MastraDBMessage } from '@mastra/core/agent/message-list';
|
|
2
3
|
import type { CoreUserMessage } from '@mastra/core/llm';
|
|
3
4
|
import type { TracingOptions } from '@mastra/core/observability';
|
|
@@ -45,9 +46,18 @@ interface SharedArgs {
|
|
|
45
46
|
signal?: AbortSignal;
|
|
46
47
|
tracingOptions?: TracingOptions;
|
|
47
48
|
}
|
|
49
|
+
/** JSON-serializable metadata attached to a user message sent through `useChat`. */
|
|
50
|
+
export type UserMessageMetadata = NonNullable<SendAgentSignalParams['signal']['metadata']>;
|
|
48
51
|
export type SendMessageArgs = {
|
|
49
52
|
message: string;
|
|
50
53
|
coreUserMessages?: CoreUserMessage[];
|
|
54
|
+
/**
|
|
55
|
+
* Custom metadata for the user message. It is merged into the optimistic
|
|
56
|
+
* message's `content.metadata` and, on the thread-signals path, sent with the
|
|
57
|
+
* outgoing message. Internal keys (`mode`, `status`, `clientMessageId`) take
|
|
58
|
+
* precedence over user-supplied values.
|
|
59
|
+
*/
|
|
60
|
+
metadata?: UserMessageMetadata;
|
|
51
61
|
} & (({
|
|
52
62
|
mode: 'generate';
|
|
53
63
|
} & Omit<GenerateArgs, 'coreUserMessages'>) | ({
|
|
@@ -72,13 +82,15 @@ export type StreamArgs = SharedArgs & {
|
|
|
72
82
|
* and the outgoing message metadata so the server echo can reconcile them.
|
|
73
83
|
*/
|
|
74
84
|
clientMessageId?: string;
|
|
85
|
+
/** Custom user-message metadata sent alongside the thread signal. */
|
|
86
|
+
metadata?: UserMessageMetadata;
|
|
75
87
|
};
|
|
76
88
|
export type NetworkArgs = SharedArgs & {
|
|
77
89
|
onNetworkChunk?: (chunk: NetworkChunkType) => Promise<void>;
|
|
78
90
|
};
|
|
79
91
|
export declare const useChat: ({ agentId, resourceId, threadId, initialMessages, requestContext: propsRequestContext, clientTools: hookClientTools, onSignalSent, onSignalEcho, onThreadSignalsUnsupported, enableThreadSignals, streamPath, withInitialHistory, }: MastraChatProps) => {
|
|
80
92
|
setMessages: import("react").Dispatch<import("react").SetStateAction<MastraDBMessage[]>>;
|
|
81
|
-
sendMessage: ({ mode, ...args }: SendMessageArgs) => Promise<void>;
|
|
93
|
+
sendMessage: ({ mode, metadata: userMetadata, ...args }: SendMessageArgs) => Promise<void>;
|
|
82
94
|
isRunning: boolean;
|
|
83
95
|
activeRunId: string | undefined;
|
|
84
96
|
isAwaitingToolApproval: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/agent/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAY,eAAe,EAA4B,MAAM,iCAAiC,CAAC;AAE3G,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAmBtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA2JpF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,eAAe,EAAE,CAAC;IACpC,6FAA6F;IAC7F,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IACxC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,MAAM,eAAe,GAAG;
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/agent/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAY,eAAe,EAA4B,MAAM,iCAAiC,CAAC;AAE3G,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAmBtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA2JpF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,eAAe,EAAE,CAAC;IACpC,6FAA6F;IAC7F,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IACxC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,oFAAoF;AACpF,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AAE3F,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,CACA,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG,IAAI,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,GAC/D,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC,GAC3D,CAAC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,GAC7D,CAAC;IAAE,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAChE,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG;IACtC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IACrC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAoDF,eAAO,MAAM,OAAO,wOAajB,eAAe;;6DAyhCiE,eAAe;;;;;;;;;;;kCAzPrD,MAAM,eAAe,OAAO;kCAuE5B,MAAM;0CAoDE,MAAM;0CA2BN,MAAM;;;;oBA95BvB,UAAU,GAAG,UAAU;;;uCAy7BT,MAAM,UAAU,MAAM;uCAoCtB,MAAM,UAAU,MAAM;;;oBA19BtC,UAAU,GAAG,UAAU;;;CA8kCxD,CAAC"}
|
package/dist/chat/index.cjs
CHANGED
package/dist/chat/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as useChat } from "../hooks-
|
|
1
|
+
import { t as useChat } from "../hooks-CbX7Q4A3.js";
|
|
2
2
|
export { useChat };
|
|
@@ -49,28 +49,39 @@ function toExperimentUsage(response) {
|
|
|
49
49
|
traces
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
/** How many breakdowns to fetch after completion while the exporter may still be flushing metrics. */
|
|
53
|
+
const MAX_EMPTY_ATTEMPTS = 5;
|
|
54
|
+
const isEmptyUsage = (usage) => !usage || Object.keys(usage.traces).length === 0 && usage.summary.inputTokens === void 0 && usage.summary.outputTokens === void 0;
|
|
52
55
|
/**
|
|
53
56
|
* Token and cost usage of several experiments, keyed by experiment id: each entry holds the
|
|
54
57
|
* experiment totals (`summary`) and every item's usage keyed by trace id (`traces`).
|
|
55
58
|
* Sends one metric breakdown request per experiment, filtered by its id rather than by trace ids,
|
|
56
|
-
* so it does not wait for the experiment results.
|
|
57
|
-
*
|
|
59
|
+
* so it does not wait for the experiment results. Usage is only fetched once an experiment has
|
|
60
|
+
* finished; while the breakdown is still empty (metrics flushing), it retries every 2 seconds up to
|
|
61
|
+
* 5 attempts. No request is made when the observability store cannot serve metrics.
|
|
58
62
|
*/
|
|
59
63
|
const useExperimentsItemUsage = ({ experiments, supportsMetrics }) => {
|
|
60
64
|
const client = require_mastra_client_context.useMastraClient();
|
|
61
65
|
const combine = require_combine_by_ids.useCombineByIds(experiments.map((experiment) => experiment.id));
|
|
62
66
|
return (0, _tanstack_react_query.useQueries)({
|
|
63
|
-
queries: experiments.map(({ id, status }) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
queries: experiments.map(({ id, status }) => {
|
|
68
|
+
const finished = status !== "running" && status !== "pending";
|
|
69
|
+
return {
|
|
70
|
+
queryKey: [
|
|
71
|
+
"experiment-item-usage",
|
|
72
|
+
id,
|
|
73
|
+
finished
|
|
74
|
+
],
|
|
75
|
+
queryFn: async () => toExperimentUsage(await client.getMetricBreakdown({
|
|
76
|
+
name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],
|
|
77
|
+
aggregation: "sum",
|
|
78
|
+
groupBy: ["traceId", "name"],
|
|
79
|
+
filters: { experimentId: id }
|
|
80
|
+
})),
|
|
81
|
+
enabled: supportsMetrics && finished,
|
|
82
|
+
refetchInterval: (query) => isEmptyUsage(query.state.data) && query.state.dataUpdateCount < MAX_EMPTY_ATTEMPTS ? POLL_INTERVAL_MS : false
|
|
83
|
+
};
|
|
84
|
+
}),
|
|
74
85
|
combine
|
|
75
86
|
});
|
|
76
87
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["getOrCreate","useMastraClient","useCombineByIds","useMastraClient","useMastraClient","useMastraClient","useMastraClient","useInView"],"sources":["../../../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-infinite-experiments.ts"],"sourcesContent":["import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { useQueries } 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';\n\nconst INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nconst OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\nconst POLL_INTERVAL_MS = 2000;\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\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\ntype UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nconst createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\nfunction accumulate(accumulator: UsageAccumulator, tokenField: 'inputTokens' | 'outputTokens', group: BreakdownGroup) {\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\nfunction 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\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 metricName = group.dimensions.name;\n if (metricName !== INPUT_TOKEN_METRIC && metricName !== OUTPUT_TOKEN_METRIC) continue;\n\n const tokenField = metricName === INPUT_TOKEN_METRIC ? 'inputTokens' : 'outputTokens';\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/**\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. Polls every 2 seconds while an experiment is\n * running or pending. 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 queryKey: ['experiment-item-usage', id],\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,\n refetchInterval: status === 'running' || status === 'pending' ? POLL_INTERVAL_MS : false,\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 { 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":"index.cjs","names":["getOrCreate","useMastraClient","useCombineByIds","useMastraClient","useMastraClient","useMastraClient","useMastraClient","useInView"],"sources":["../../../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-infinite-experiments.ts"],"sourcesContent":["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';\n\nconst INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nconst OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\nconst POLL_INTERVAL_MS = 2000;\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\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\ntype UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nconst createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\nfunction accumulate(accumulator: UsageAccumulator, tokenField: 'inputTokens' | 'outputTokens', group: BreakdownGroup) {\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\nfunction 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\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 metricName = group.dimensions.name;\n if (metricName !== INPUT_TOKEN_METRIC && metricName !== OUTPUT_TOKEN_METRIC) continue;\n\n const tokenField = metricName === INPUT_TOKEN_METRIC ? 'inputTokens' : 'outputTokens';\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 { 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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"}
|
|
@@ -48,28 +48,39 @@ function toExperimentUsage(response) {
|
|
|
48
48
|
traces
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
/** How many breakdowns to fetch after completion while the exporter may still be flushing metrics. */
|
|
52
|
+
const MAX_EMPTY_ATTEMPTS = 5;
|
|
53
|
+
const isEmptyUsage = (usage) => !usage || Object.keys(usage.traces).length === 0 && usage.summary.inputTokens === void 0 && usage.summary.outputTokens === void 0;
|
|
51
54
|
/**
|
|
52
55
|
* Token and cost usage of several experiments, keyed by experiment id: each entry holds the
|
|
53
56
|
* experiment totals (`summary`) and every item's usage keyed by trace id (`traces`).
|
|
54
57
|
* Sends one metric breakdown request per experiment, filtered by its id rather than by trace ids,
|
|
55
|
-
* so it does not wait for the experiment results.
|
|
56
|
-
*
|
|
58
|
+
* so it does not wait for the experiment results. Usage is only fetched once an experiment has
|
|
59
|
+
* finished; while the breakdown is still empty (metrics flushing), it retries every 2 seconds up to
|
|
60
|
+
* 5 attempts. No request is made when the observability store cannot serve metrics.
|
|
57
61
|
*/
|
|
58
62
|
const useExperimentsItemUsage = ({ experiments, supportsMetrics }) => {
|
|
59
63
|
const client = useMastraClient();
|
|
60
64
|
const combine = useCombineByIds(experiments.map((experiment) => experiment.id));
|
|
61
65
|
return useQueries({
|
|
62
|
-
queries: experiments.map(({ id, status }) =>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
queries: experiments.map(({ id, status }) => {
|
|
67
|
+
const finished = status !== "running" && status !== "pending";
|
|
68
|
+
return {
|
|
69
|
+
queryKey: [
|
|
70
|
+
"experiment-item-usage",
|
|
71
|
+
id,
|
|
72
|
+
finished
|
|
73
|
+
],
|
|
74
|
+
queryFn: async () => toExperimentUsage(await client.getMetricBreakdown({
|
|
75
|
+
name: [INPUT_TOKEN_METRIC, OUTPUT_TOKEN_METRIC],
|
|
76
|
+
aggregation: "sum",
|
|
77
|
+
groupBy: ["traceId", "name"],
|
|
78
|
+
filters: { experimentId: id }
|
|
79
|
+
})),
|
|
80
|
+
enabled: supportsMetrics && finished,
|
|
81
|
+
refetchInterval: (query) => isEmptyUsage(query.state.data) && query.state.dataUpdateCount < MAX_EMPTY_ATTEMPTS ? POLL_INTERVAL_MS : false
|
|
82
|
+
};
|
|
83
|
+
}),
|
|
73
84
|
combine
|
|
74
85
|
});
|
|
75
86
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../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-infinite-experiments.ts"],"sourcesContent":["import type { GetMetricBreakdownResponse } from '@mastra/core/storage';\nimport { useQueries } 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';\n\nconst INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nconst OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\nconst POLL_INTERVAL_MS = 2000;\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\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\ntype UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nconst createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\nfunction accumulate(accumulator: UsageAccumulator, tokenField: 'inputTokens' | 'outputTokens', group: BreakdownGroup) {\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\nfunction 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\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 metricName = group.dimensions.name;\n if (metricName !== INPUT_TOKEN_METRIC && metricName !== OUTPUT_TOKEN_METRIC) continue;\n\n const tokenField = metricName === INPUT_TOKEN_METRIC ? 'inputTokens' : 'outputTokens';\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/**\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. Polls every 2 seconds while an experiment is\n * running or pending. 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 queryKey: ['experiment-item-usage', id],\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,\n refetchInterval: status === 'running' || status === 'pending' ? POLL_INTERVAL_MS : false,\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 { 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":"index.js","names":[],"sources":["../../../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-infinite-experiments.ts"],"sourcesContent":["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';\n\nconst INPUT_TOKEN_METRIC = 'mastra_model_total_input_tokens';\nconst OUTPUT_TOKEN_METRIC = 'mastra_model_total_output_tokens';\nconst POLL_INTERVAL_MS = 2000;\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\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\ntype UsageAccumulator = {\n inputTokens?: number;\n outputTokens?: number;\n estimatedCost: number;\n costUnits: Set<string>;\n hasUnknownCostUnit: boolean;\n};\n\nconst createAccumulator = (): UsageAccumulator => ({\n estimatedCost: 0,\n costUnits: new Set(),\n hasUnknownCostUnit: false,\n});\n\ntype BreakdownGroup = GetMetricBreakdownResponse['groups'][number];\n\nfunction accumulate(accumulator: UsageAccumulator, tokenField: 'inputTokens' | 'outputTokens', group: BreakdownGroup) {\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\nfunction 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\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 metricName = group.dimensions.name;\n if (metricName !== INPUT_TOKEN_METRIC && metricName !== OUTPUT_TOKEN_METRIC) continue;\n\n const tokenField = metricName === INPUT_TOKEN_METRIC ? 'inputTokens' : 'outputTokens';\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 { 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":";;;;;;;AASA,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,mBAAmB;AAyBzB,MAAM,2BAA6C;CACjD,eAAe;CACf,2BAAW,IAAI,IAAI;CACnB,oBAAoB;AACtB;AAIA,SAAS,WAAW,aAA+B,YAA4C,OAAuB;CACpH,YAAY,eAAe,YAAY,eAAe,KAAK,MAAM;CAEjE,IAAI,MAAM,iBAAiB,MAAM;CACjC,YAAY,iBAAiB,MAAM;CACnC,IAAI,MAAM,UACR,YAAY,UAAU,IAAI,MAAM,SAAS,YAAY,CAAC;MAEtD,YAAY,qBAAqB;AAErC;AAEA,SAAS,QAAQ,aAAoD;CAEnE,MAAM,WAAW,CAAC,YAAY,sBAAsB,YAAY,UAAU,SAAS;CACnF,OAAO;EACL,aAAa,YAAY;EACzB,cAAc,YAAY;EAC1B,eAAe,WAAW,YAAY,gBAAgB,KAAA;EACtD,UAAU,WAAW,CAAC,GAAG,YAAY,SAAS,CAAC,CAAC,KAAK,KAAA;CACvD;AACF;;AAGA,SAAS,kBAAkB,UAAuD;CAChF,MAAM,UAAU,kBAAkB;CAClC,MAAM,+BAAe,IAAI,IAA8B;CAEvD,KAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,MAAM,aAAa,MAAM,WAAW;EACpC,IAAI,eAAe,sBAAsB,eAAe,qBAAqB;EAE7E,MAAM,aAAa,eAAe,qBAAqB,gBAAgB;EACvE,WAAW,SAAS,YAAY,KAAK;EAErC,MAAM,UAAU,MAAM,WAAW;EACjC,IAAI,SAAS,WAAW,YAAY,cAAc,SAAS,iBAAiB,GAAG,YAAY,KAAK;CAClG;CAEA,MAAM,SAA8C,CAAC;CACrD,KAAK,MAAM,CAAC,SAAS,gBAAgB,cACnC,OAAO,WAAW,QAAQ,WAAW;CAEvC,OAAO;EAAE,SAAS,QAAQ,OAAO;EAAG;CAAO;AAC7C;;AAGA,MAAM,qBAAqB;AAE3B,MAAM,gBAAgB,UACpB,CAAC,SACA,OAAO,KAAK,MAAM,MAAM,CAAC,CAAC,WAAW,KACpC,MAAM,QAAQ,gBAAgB,KAAA,KAC9B,MAAM,QAAQ,iBAAiB,KAAA;;;;;;;;;AAUnC,MAAa,2BAA2B,EACtC,aACA,sBAI8C;CAC9C,MAAM,SAAS,gBAAgB;CAC/B,MAAM,UAAU,gBAAiC,YAAY,KAAI,eAAc,WAAW,EAAE,CAAC;CAE7F,OAAO,WAAW;EAChB,SAAS,YAAY,KAAK,EAAE,IAAI,aAAa;GAC3C,MAAM,WAAW,WAAW,aAAa,WAAW;GACpD,OAAO;IACL,UAAU;KAAC;KAAyB;KAAI;IAAQ;IAChD,SAAS,YACP,kBACE,MAAM,OAAO,mBAAmB;KAC9B,MAAM,CAAC,oBAAoB,mBAAmB;KAC9C,aAAa;KACb,SAAS,CAAC,WAAW,MAAM;KAC3B,SAAS,EAAE,cAAc,GAAG;IAC9B,CAAC,CACH;IACF,SAAS,mBAAmB;IAC5B,kBAAkB,UAChB,aAAa,MAAM,MAAM,IAAI,KAAK,MAAM,MAAM,kBAAkB,qBAAqB,mBAAmB;GAC5G;EACF,CAAC;EACD;CACF,CAAC;AACH;;;;;;;;;;AC9GA,MAAa,wBAAwB,EACnC,cACA,kBACA,iBACA,mBAC8B;CAC9B,MAAM,SAAS,gBAAgB;CAC/B,MAAM,YAAY,QAAQ,YAAY,KAAK;CAG3C,MAAM,QAAQ,SAAS;EACrB,UAAU,CAAC,sBAAsB,YAAY;EAC7C,iBAJe,qBAAqB,aAAa,qBAAqB,YAI1C,MAAO;EACnC,SAAS,YAAwC;GAE/C,IAAI,CAAC,cAAc,MAAM,IAAI,MAAM,0BAA0B;GAC7D,MAAM,UAAU,EAAE,aAAa;GAE/B,MAAM,CAAC,QAAQ,aAAa,QAAQ,MAAM,QAAQ,IAAI;IACpD,OAAO,mBAAmB;KACxB,MAAM,CAAC,mCAAmC,kCAAkC;KAC5E,aAAa;KACb;IACF,CAAC;IACD,OAAO,mBAAmB;KAAE,MAAM,CAAC,0BAA0B;KAAG,aAAa;KAAO;IAAQ,CAAC;IAC7F,OAAO,mBAAmB;KAAE,MAAM,CAAC,0BAA0B;KAAG,aAAa;KAAS;IAAQ,CAAC;GACjG,CAAC;GAED,OAAO;IACL,aAAa,OAAO,SAAS;IAC7B,eAAe,OAAO,iBAAiB;IACvC,UAAU,OAAO,YAAY;IAC7B,oBAAoB,YAAY,SAAS;IACzC,WAAW,KAAK,SAAS;GAC3B;EACF;EACA,GAAG;CACL,CAAC;CAED,OAAO;EAAE,MAAM,MAAM;EAAM,WAAW,MAAM;EAAW;CAAU;AACnE;;;;;;ACzDA,MAAa,sBAAkD,EAC7D,SACA,mBAIkC;CAClC,MAAM,SAAS,gBAAgB;CAE/B,OAAO,SAAS;EACd,UAAU,CAAC,0BAA0B,OAAO;EAC5C,SAAS,YAAY;GACnB,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,sBAAsB;GAExC,OAAO,OAAO,cAAc,OAAO;EACrC;EACA,GAAG;CACL,CAAC;AACH;;;;ACvBA,MAAa,wBAAwB;AAcrC,SAAgB,+BAAgE,EAC9E,WACA,QACA,gBAK+B;CAC/B,MAAM,SAAS,gBAAgB;CAC/B,MAAM,SAAgC,EAAE,SAAA,IAA+B;CACvE,IAAI,QAAQ,YAAY,OAAO,aAAa,OAAO;CACnD,IAAI,QAAQ,UAAU,OAAO,WAAW,OAAO;CAE/C,OAAO,SAAS;EAEd,UAAU,YAAY;GAAC;GAAuB;GAAW;EAAM,IAAI,CAAC,eAAe,MAAM;EACzF,eAAgB,YAAY,OAAO,uBAAuB,WAAW,MAAM,IAAI,OAAO,gBAAgB,MAAM;EAC5G,GAAG;CACL,CAAC;AACH;;;AChCA,MAAa,uBAAuB;AAWpC,SAAgB,uBAAoD,EAClE,WACA,QACA,SACA,gBAQA;CACA,MAAM,SAAS,gBAAgB;CAC/B,MAAM,EAAE,QAAQ,mBAAmB,QAAQ,wBAAwB,UAAU;CAC7E,MAAM,aAAa,QAAQ,cAAc,KAAA;CACzC,MAAM,WAAW,QAAQ,YAAY,KAAA;CAErC,MAAM,QAAQ,iBAAiB;EAE7B,UAAU,YACN;GAAC;GAAuB;GAAW;GAAY;IAAE;IAAY;IAAU;GAAQ;EAAC,IAChF;GAAC;GAAe;GAAY;IAAE;IAAY;IAAU;GAAQ;EAAC;EACjE,UAAU,EAAE,gBAAgB;GAC1B,MAAM,SAAgC;IAAE,MAAM;IAAW,SAAA;GAA8B;GACvF,IAAI,YAAY,OAAO,aAAa;GACpC,IAAI,UAAU,OAAO,WAAW;GAChC,IAAI,SAAS,OAAO,UAAU;GAC9B,OAAO,YAAY,OAAO,uBAAuB,WAAW,MAAM,IAAI,OAAO,gBAAgB,MAAM;EACrG;EACA,kBAAkB;EAClB,mBAAmB,UAAU,GAAG,kBAAkB;GAChD,IAAI,CAAC,UAAU,aAAa,UAAU,CAAC,SAAS,YAAY,SAC1D;GAEF,OAAO,gBAAgB;EACzB;EACA,SAAS,SACP,KAAK,MAAM,SAAQ,SAAQ,MAAM,eAAe,CAAC,CAAC;EACpD,GAAG;CACL,CAAC;CAED,gBAAgB;EACd,IAAI,qBAAqB,MAAM,eAAe,CAAC,MAAM,oBACnD,MAAW,cAAc;CAE7B,GAAG,CAAC,mBAAmB,KAAK,CAAC;CAE7B,OAAO;EAAE,GAAG;EAAO;CAAoB;AACzC"}
|
|
@@ -17,8 +17,9 @@ export interface ExperimentUsage {
|
|
|
17
17
|
* Token and cost usage of several experiments, keyed by experiment id: each entry holds the
|
|
18
18
|
* experiment totals (`summary`) and every item's usage keyed by trace id (`traces`).
|
|
19
19
|
* Sends one metric breakdown request per experiment, filtered by its id rather than by trace ids,
|
|
20
|
-
* so it does not wait for the experiment results.
|
|
21
|
-
*
|
|
20
|
+
* so it does not wait for the experiment results. Usage is only fetched once an experiment has
|
|
21
|
+
* finished; while the breakdown is still empty (metrics flushing), it retries every 2 seconds up to
|
|
22
|
+
* 5 attempts. No request is made when the observability store cannot serve metrics.
|
|
22
23
|
*/
|
|
23
24
|
export declare const useExperimentsItemUsage: ({ experiments, supportsMetrics, }: {
|
|
24
25
|
experiments: ExperimentRef[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-experiment-item-usage.d.ts","sourceRoot":"","sources":["../../../src/hooks/experiments/use-experiment-item-usage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-experiment-item-usage.d.ts","sourceRoot":"","sources":["../../../src/hooks/experiments/use-experiment-item-usage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEzE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAOxE,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,mGAAmG;AACnG,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC7C;AAyED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,sCAGjC;IACD,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,eAAe,EAAE,OAAO,CAAC;CAC1B,KAAG,uBAAuB,CAAC,eAAe,CAyB1C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["useMastraClient","useExperimentsForDatasetFilter","useMastraClient"],"sources":["../../../src/hooks/review/use-dataset-review-items.ts","../../../src/hooks/review/use-review-summary.ts"],"sourcesContent":["import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["useMastraClient","useExperimentsForDatasetFilter","useMastraClient"],"sources":["../../../src/hooks/review/use-dataset-review-items.ts","../../../src/hooks/review/use-review-summary.ts"],"sourcesContent":["import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery } from '@tanstack/react-query';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport { useExperimentsForDatasetFilter } from '../experiments';\nimport type { ExperimentTargetFilter } from '../experiments';\nimport type { MastraQueryOptions } from '../shared/query-options';\nimport type { ReviewItem } from './types';\n\ntype ReviewStatus = 'needs-review' | 'complete';\n\nexport interface ReviewItemsOptions extends ExperimentTargetFilter {\n /** When set, only this experiment's results are loaded; otherwise every experiment in the project. */\n experimentId?: string;\n /** Explicit source owned by the caller; an empty list disables discovery too. */\n experiments?: DatasetExperiment[];\n isLoadingExperiments?: boolean;\n /** TanStack overrides spread last into the review-items query. */\n queryOptions?: MastraQueryOptions<ReviewItem[]>;\n}\n\n/**\n * Loads experiment results with the given review status, across the project, scoped to a target,\n * or scoped to one experiment.\n */\nconst useReviewItemsByStatus = (\n status: ReviewStatus,\n {\n experimentId,\n targetType,\n targetId,\n experiments: suppliedExperiments,\n isLoadingExperiments = false,\n queryOptions,\n }: ReviewItemsOptions,\n): UseQueryResult<ReviewItem[], Error> => {\n const client = useMastraClient();\n const hasSuppliedExperiments = suppliedExperiments !== undefined;\n const { data: experimentsData, isLoading: isDiscoveringExperiments } = useExperimentsForDatasetFilter({\n datasetId: undefined,\n target: { targetType, targetId },\n queryOptions: { enabled: !hasSuppliedExperiments },\n });\n const experiments = hasSuppliedExperiments ? suppliedExperiments : experimentsData?.experiments;\n const isLoadingSource = hasSuppliedExperiments ? isLoadingExperiments : isDiscoveringExperiments;\n const scopedExperiments = experimentId ? experiments?.filter(exp => exp.id === experimentId) : experiments;\n\n const query = useQuery({\n queryKey: [\n 'review-items',\n status,\n experimentId ?? 'all',\n hasSuppliedExperiments ? 'supplied' : 'discovered',\n hasSuppliedExperiments ? undefined : targetType || 'all',\n hasSuppliedExperiments ? undefined : targetId || 'all',\n scopedExperiments?.map(e => [e.datasetId, e.id]),\n ],\n queryFn: async () => {\n if (!scopedExperiments || scopedExperiments.length === 0) return [] as ReviewItem[];\n\n const allResults = await Promise.all(\n scopedExperiments.map(async exp => {\n if (!exp.datasetId) return [];\n try {\n const { results } = await client.listDatasetExperimentResults(exp.datasetId, exp.id);\n return results\n .filter(r => r.status === status)\n .map(r => ({\n id: r.id,\n input: r.input,\n output: r.output,\n error: r.error,\n itemId: r.itemId,\n experimentId: r.experimentId,\n datasetId: exp.datasetId ?? undefined,\n traceId: r.traceId ?? undefined,\n scores: r.scores ? Object.fromEntries(r.scores.map(s => [s.scorerId, s.score ?? 0])) : {},\n tags: r.tags ?? [],\n targetType: r.targetType,\n createdAt: r.createdAt,\n startedAt: r.startedAt,\n completedAt: r.completedAt,\n status: r.status,\n groundTruth: r.groundTruth,\n toolMockReport: r.toolMockReport,\n }));\n } catch {\n return [];\n }\n }),\n );\n\n return allResults.flat() as ReviewItem[];\n },\n enabled: Boolean(experiments),\n refetchOnWindowFocus: false,\n ...queryOptions,\n });\n\n // The results query is disabled until experiments arrive, so its own `isLoading`\n // is false during that window; surface the upstream load to avoid an empty flash.\n return { ...query, isLoading: query.isLoading || isLoadingSource } as UseQueryResult<ReviewItem[], Error>;\n};\n\n/** Loads persisted review items (status='needs-review'), project-wide, per target or for one experiment. */\nexport const useReviewItems = (options: ReviewItemsOptions = {}) => useReviewItemsByStatus('needs-review', options);\n\n/** Loads completed review items (status='complete'), project-wide, per target or for one experiment. */\nexport const useCompletedItems = (options: ReviewItemsOptions = {}) => useReviewItemsByStatus('complete', options);\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 ReviewSummaryResponse = Awaited<ReturnType<MastraClient['getExperimentReviewSummary']>>;\n\nexport function useReviewSummary<TData = ReviewSummaryResponse>({\n queryOptions,\n}: {\n queryOptions?: MastraQueryOptions<ReviewSummaryResponse, TData>;\n} = {}): UseQueryResult<TData, Error> {\n const client = useMastraClient();\n\n return useQuery({\n queryKey: ['experiment-review-summary'],\n queryFn: () => client.getExperimentReviewSummary(),\n ...queryOptions,\n });\n}\n"],"mappings":";;;;;;;;;AAyBA,MAAM,0BACJ,QACA,EACE,cACA,YACA,UACA,aAAa,qBACb,uBAAuB,OACvB,mBAEsC;CACxC,MAAM,SAASA,8BAAAA,gBAAgB;CAC/B,MAAM,yBAAyB,wBAAwB,KAAA;CACvD,MAAM,EAAE,MAAM,iBAAiB,WAAW,6BAA6BC,gCAAAA,+BAA+B;EACpG,WAAW,KAAA;EACX,QAAQ;GAAE;GAAY;EAAS;EAC/B,cAAc,EAAE,SAAS,CAAC,uBAAuB;CACnD,CAAC;CACD,MAAM,cAAc,yBAAyB,sBAAsB,iBAAiB;CACpF,MAAM,kBAAkB,yBAAyB,uBAAuB;CACxE,MAAM,oBAAoB,eAAe,aAAa,QAAO,QAAO,IAAI,OAAO,YAAY,IAAI;CAE/F,MAAM,SAAA,GAAA,sBAAA,SAAA,CAAiB;EACrB,UAAU;GACR;GACA;GACA,gBAAgB;GAChB,yBAAyB,aAAa;GACtC,yBAAyB,KAAA,IAAY,cAAc;GACnD,yBAAyB,KAAA,IAAY,YAAY;GACjD,mBAAmB,KAAI,MAAK,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC;EACjD;EACA,SAAS,YAAY;GACnB,IAAI,CAAC,qBAAqB,kBAAkB,WAAW,GAAG,OAAO,CAAC;GAkClE,QAAO,MAhCkB,QAAQ,IAC/B,kBAAkB,IAAI,OAAM,QAAO;IACjC,IAAI,CAAC,IAAI,WAAW,OAAO,CAAC;IAC5B,IAAI;KACF,MAAM,EAAE,YAAY,MAAM,OAAO,6BAA6B,IAAI,WAAW,IAAI,EAAE;KACnF,OAAO,QACJ,QAAO,MAAK,EAAE,WAAW,MAAM,CAAC,CAChC,KAAI,OAAM;MACT,IAAI,EAAE;MACN,OAAO,EAAE;MACT,QAAQ,EAAE;MACV,OAAO,EAAE;MACT,QAAQ,EAAE;MACV,cAAc,EAAE;MAChB,WAAW,IAAI,aAAa,KAAA;MAC5B,SAAS,EAAE,WAAW,KAAA;MACtB,QAAQ,EAAE,SAAS,OAAO,YAAY,EAAE,OAAO,KAAI,MAAK,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;MACxF,MAAM,EAAE,QAAQ,CAAC;MACjB,YAAY,EAAE;MACd,WAAW,EAAE;MACb,WAAW,EAAE;MACb,aAAa,EAAE;MACf,QAAQ,EAAE;MACV,aAAa,EAAE;MACf,gBAAgB,EAAE;KACpB,EAAE;IACN,QAAQ;KACN,OAAO,CAAC;IACV;GACF,CAAC,CACH,EAAA,CAEkB,KAAK;EACzB;EACA,SAAS,QAAQ,WAAW;EAC5B,sBAAsB;EACtB,GAAG;CACL,CAAC;CAID,OAAO;EAAE,GAAG;EAAO,WAAW,MAAM,aAAa;CAAgB;AACnE;;AAGA,MAAa,kBAAkB,UAA8B,CAAC,MAAM,uBAAuB,gBAAgB,OAAO;;AAGlH,MAAa,qBAAqB,UAA8B,CAAC,MAAM,uBAAuB,YAAY,OAAO;;;ACpGjH,SAAgB,iBAAgD,EAC9D,iBAGE,CAAC,GAAiC;CACpC,MAAM,SAASC,8BAAAA,gBAAgB;CAE/B,QAAA,GAAA,sBAAA,SAAA,CAAgB;EACd,UAAU,CAAC,2BAA2B;EACtC,eAAe,OAAO,2BAA2B;EACjD,GAAG;CACL,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/hooks/review/use-dataset-review-items.ts","../../../src/hooks/review/use-review-summary.ts"],"sourcesContent":["import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/hooks/review/use-dataset-review-items.ts","../../../src/hooks/review/use-review-summary.ts"],"sourcesContent":["import type { DatasetExperiment } from '@mastra/client-js';\nimport { useQuery } from '@tanstack/react-query';\nimport type { UseQueryResult } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport { useExperimentsForDatasetFilter } from '../experiments';\nimport type { ExperimentTargetFilter } from '../experiments';\nimport type { MastraQueryOptions } from '../shared/query-options';\nimport type { ReviewItem } from './types';\n\ntype ReviewStatus = 'needs-review' | 'complete';\n\nexport interface ReviewItemsOptions extends ExperimentTargetFilter {\n /** When set, only this experiment's results are loaded; otherwise every experiment in the project. */\n experimentId?: string;\n /** Explicit source owned by the caller; an empty list disables discovery too. */\n experiments?: DatasetExperiment[];\n isLoadingExperiments?: boolean;\n /** TanStack overrides spread last into the review-items query. */\n queryOptions?: MastraQueryOptions<ReviewItem[]>;\n}\n\n/**\n * Loads experiment results with the given review status, across the project, scoped to a target,\n * or scoped to one experiment.\n */\nconst useReviewItemsByStatus = (\n status: ReviewStatus,\n {\n experimentId,\n targetType,\n targetId,\n experiments: suppliedExperiments,\n isLoadingExperiments = false,\n queryOptions,\n }: ReviewItemsOptions,\n): UseQueryResult<ReviewItem[], Error> => {\n const client = useMastraClient();\n const hasSuppliedExperiments = suppliedExperiments !== undefined;\n const { data: experimentsData, isLoading: isDiscoveringExperiments } = useExperimentsForDatasetFilter({\n datasetId: undefined,\n target: { targetType, targetId },\n queryOptions: { enabled: !hasSuppliedExperiments },\n });\n const experiments = hasSuppliedExperiments ? suppliedExperiments : experimentsData?.experiments;\n const isLoadingSource = hasSuppliedExperiments ? isLoadingExperiments : isDiscoveringExperiments;\n const scopedExperiments = experimentId ? experiments?.filter(exp => exp.id === experimentId) : experiments;\n\n const query = useQuery({\n queryKey: [\n 'review-items',\n status,\n experimentId ?? 'all',\n hasSuppliedExperiments ? 'supplied' : 'discovered',\n hasSuppliedExperiments ? undefined : targetType || 'all',\n hasSuppliedExperiments ? undefined : targetId || 'all',\n scopedExperiments?.map(e => [e.datasetId, e.id]),\n ],\n queryFn: async () => {\n if (!scopedExperiments || scopedExperiments.length === 0) return [] as ReviewItem[];\n\n const allResults = await Promise.all(\n scopedExperiments.map(async exp => {\n if (!exp.datasetId) return [];\n try {\n const { results } = await client.listDatasetExperimentResults(exp.datasetId, exp.id);\n return results\n .filter(r => r.status === status)\n .map(r => ({\n id: r.id,\n input: r.input,\n output: r.output,\n error: r.error,\n itemId: r.itemId,\n experimentId: r.experimentId,\n datasetId: exp.datasetId ?? undefined,\n traceId: r.traceId ?? undefined,\n scores: r.scores ? Object.fromEntries(r.scores.map(s => [s.scorerId, s.score ?? 0])) : {},\n tags: r.tags ?? [],\n targetType: r.targetType,\n createdAt: r.createdAt,\n startedAt: r.startedAt,\n completedAt: r.completedAt,\n status: r.status,\n groundTruth: r.groundTruth,\n toolMockReport: r.toolMockReport,\n }));\n } catch {\n return [];\n }\n }),\n );\n\n return allResults.flat() as ReviewItem[];\n },\n enabled: Boolean(experiments),\n refetchOnWindowFocus: false,\n ...queryOptions,\n });\n\n // The results query is disabled until experiments arrive, so its own `isLoading`\n // is false during that window; surface the upstream load to avoid an empty flash.\n return { ...query, isLoading: query.isLoading || isLoadingSource } as UseQueryResult<ReviewItem[], Error>;\n};\n\n/** Loads persisted review items (status='needs-review'), project-wide, per target or for one experiment. */\nexport const useReviewItems = (options: ReviewItemsOptions = {}) => useReviewItemsByStatus('needs-review', options);\n\n/** Loads completed review items (status='complete'), project-wide, per target or for one experiment. */\nexport const useCompletedItems = (options: ReviewItemsOptions = {}) => useReviewItemsByStatus('complete', options);\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 ReviewSummaryResponse = Awaited<ReturnType<MastraClient['getExperimentReviewSummary']>>;\n\nexport function useReviewSummary<TData = ReviewSummaryResponse>({\n queryOptions,\n}: {\n queryOptions?: MastraQueryOptions<ReviewSummaryResponse, TData>;\n} = {}): UseQueryResult<TData, Error> {\n const client = useMastraClient();\n\n return useQuery({\n queryKey: ['experiment-review-summary'],\n queryFn: () => client.getExperimentReviewSummary(),\n ...queryOptions,\n });\n}\n"],"mappings":";;;;;;;;AAyBA,MAAM,0BACJ,QACA,EACE,cACA,YACA,UACA,aAAa,qBACb,uBAAuB,OACvB,mBAEsC;CACxC,MAAM,SAAS,gBAAgB;CAC/B,MAAM,yBAAyB,wBAAwB,KAAA;CACvD,MAAM,EAAE,MAAM,iBAAiB,WAAW,6BAA6B,+BAA+B;EACpG,WAAW,KAAA;EACX,QAAQ;GAAE;GAAY;EAAS;EAC/B,cAAc,EAAE,SAAS,CAAC,uBAAuB;CACnD,CAAC;CACD,MAAM,cAAc,yBAAyB,sBAAsB,iBAAiB;CACpF,MAAM,kBAAkB,yBAAyB,uBAAuB;CACxE,MAAM,oBAAoB,eAAe,aAAa,QAAO,QAAO,IAAI,OAAO,YAAY,IAAI;CAE/F,MAAM,QAAQ,SAAS;EACrB,UAAU;GACR;GACA;GACA,gBAAgB;GAChB,yBAAyB,aAAa;GACtC,yBAAyB,KAAA,IAAY,cAAc;GACnD,yBAAyB,KAAA,IAAY,YAAY;GACjD,mBAAmB,KAAI,MAAK,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC;EACjD;EACA,SAAS,YAAY;GACnB,IAAI,CAAC,qBAAqB,kBAAkB,WAAW,GAAG,OAAO,CAAC;GAkClE,QAAO,MAhCkB,QAAQ,IAC/B,kBAAkB,IAAI,OAAM,QAAO;IACjC,IAAI,CAAC,IAAI,WAAW,OAAO,CAAC;IAC5B,IAAI;KACF,MAAM,EAAE,YAAY,MAAM,OAAO,6BAA6B,IAAI,WAAW,IAAI,EAAE;KACnF,OAAO,QACJ,QAAO,MAAK,EAAE,WAAW,MAAM,CAAC,CAChC,KAAI,OAAM;MACT,IAAI,EAAE;MACN,OAAO,EAAE;MACT,QAAQ,EAAE;MACV,OAAO,EAAE;MACT,QAAQ,EAAE;MACV,cAAc,EAAE;MAChB,WAAW,IAAI,aAAa,KAAA;MAC5B,SAAS,EAAE,WAAW,KAAA;MACtB,QAAQ,EAAE,SAAS,OAAO,YAAY,EAAE,OAAO,KAAI,MAAK,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;MACxF,MAAM,EAAE,QAAQ,CAAC;MACjB,YAAY,EAAE;MACd,WAAW,EAAE;MACb,WAAW,EAAE;MACb,aAAa,EAAE;MACf,QAAQ,EAAE;MACV,aAAa,EAAE;MACf,gBAAgB,EAAE;KACpB,EAAE;IACN,QAAQ;KACN,OAAO,CAAC;IACV;GACF,CAAC,CACH,EAAA,CAEkB,KAAK;EACzB;EACA,SAAS,QAAQ,WAAW;EAC5B,sBAAsB;EACtB,GAAG;CACL,CAAC;CAID,OAAO;EAAE,GAAG;EAAO,WAAW,MAAM,aAAa;CAAgB;AACnE;;AAGA,MAAa,kBAAkB,UAA8B,CAAC,MAAM,uBAAuB,gBAAgB,OAAO;;AAGlH,MAAa,qBAAqB,UAA8B,CAAC,MAAM,uBAAuB,YAAY,OAAO;;;ACpGjH,SAAgB,iBAAgD,EAC9D,iBAGE,CAAC,GAAiC;CACpC,MAAM,SAAS,gBAAgB;CAE/B,OAAO,SAAS;EACd,UAAU,CAAC,2BAA2B;EACtC,eAAe,OAAO,2BAA2B;EACjD,GAAG;CACL,CAAC;AACH"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatasetExperiment } from '@mastra/client-js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { UseQueryResult } from '@tanstack/react-query';
|
|
3
|
+
import type { ExperimentTargetFilter } from '../experiments/index.js';
|
|
4
4
|
import type { MastraQueryOptions } from '../shared/query-options.js';
|
|
5
5
|
import type { ReviewItem } from './types.js';
|
|
6
6
|
export interface ReviewItemsOptions extends ExperimentTargetFilter {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-dataset-review-items.d.ts","sourceRoot":"","sources":["../../../src/hooks/review/use-dataset-review-items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"use-dataset-review-items.d.ts","sourceRoot":"","sources":["../../../src/hooks/review/use-dataset-review-items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAI1C,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IAChE,sGAAsG;IACtG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,kEAAkE;IAClE,YAAY,CAAC,EAAE,kBAAkB,CAAC,UAAU,EAAE,CAAC,CAAC;CACjD;AAqFD,4GAA4G;AAC5G,eAAO,MAAM,cAAc,aAAa,kBAAkB,wCAAyD,CAAC;AAEpH,wGAAwG;AACxG,eAAO,MAAM,iBAAiB,aAAa,kBAAkB,wCAAqD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["useMastraClient","useMastraClient","useMastraClient","useInView","useMastraClient"],"sources":["../../../src/hooks/schedules/use-schedule.ts","../../../src/hooks/schedules/use-schedules.ts","../../../src/hooks/schedules/use-schedule-triggers.ts","../../../src/hooks/schedules/use-toggle-schedule.ts"],"sourcesContent":["import type { ScheduleResponse } 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/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useSchedule = <TData = ScheduleResponse>({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraQueryOptions<ScheduleResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse, Error, TData>({\n queryKey: ['schedule', scheduleId],\n queryFn: async () => {\n if (!scheduleId) throw new Error('scheduleId is required');\n return client.getSchedule(scheduleId);\n },\n ...queryOptions,\n });\n};\n","import type { ListSchedulesParams, ScheduleResponse } 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\nexport const useSchedules = <TData = ScheduleResponse[]>({\n queryOptions,\n ...params\n}: ListSchedulesParams & { queryOptions?: MastraQueryOptions<ScheduleResponse[], TData> } = {}): UseQueryResult<\n TData,\n Error\n> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse[], Error, TData>({\n queryKey: ['schedules', params],\n queryFn: async () => {\n const result = await client.listSchedules(params);\n return result.schedules;\n },\n ...queryOptions,\n });\n};\n","import type { ScheduleTriggerResponse, MastraClient } from '@mastra/client-js';\nimport { useInfiniteQuery
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["useMastraClient","useMastraClient","useMastraClient","useInView","useMastraClient"],"sources":["../../../src/hooks/schedules/use-schedule.ts","../../../src/hooks/schedules/use-schedules.ts","../../../src/hooks/schedules/use-schedule-triggers.ts","../../../src/hooks/schedules/use-toggle-schedule.ts"],"sourcesContent":["import type { ScheduleResponse } 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/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useSchedule = <TData = ScheduleResponse>({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraQueryOptions<ScheduleResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse, Error, TData>({\n queryKey: ['schedule', scheduleId],\n queryFn: async () => {\n if (!scheduleId) throw new Error('scheduleId is required');\n return client.getSchedule(scheduleId);\n },\n ...queryOptions,\n });\n};\n","import type { ListSchedulesParams, ScheduleResponse } 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\nexport const useSchedules = <TData = ScheduleResponse[]>({\n queryOptions,\n ...params\n}: ListSchedulesParams & { queryOptions?: MastraQueryOptions<ScheduleResponse[], TData> } = {}): UseQueryResult<\n TData,\n Error\n> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse[], Error, TData>({\n queryKey: ['schedules', params],\n queryFn: async () => {\n const result = await client.listSchedules(params);\n return result.schedules;\n },\n ...queryOptions,\n });\n};\n","import type { ScheduleTriggerResponse, 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';\n\nexport type UseScheduleTriggersResult = Omit<UseInfiniteQueryResult<unknown, Error>, 'data'> & {\n data: ScheduleTriggerResponse[];\n setEndOfListElement: (element: HTMLDivElement | null) => void;\n};\n\nconst PER_PAGE = 25;\n\ntype ListScheduleTriggersResponse = Awaited<ReturnType<MastraClient['listScheduleTriggers']>>;\n\n/** `queryOptions` spread last; the hook flattens pages itself, so `select` is not applied to `data`. */\n/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useScheduleTriggers = ({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraInfiniteQueryOptions<\n ListScheduleTriggersResponse,\n InfiniteData<ListScheduleTriggersResponse, number | undefined>,\n QueryKey,\n number | undefined\n >;\n}): UseScheduleTriggersResult => {\n const client = useMastraClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n\n const query = useInfiniteQuery({\n queryKey: ['schedule-triggers', scheduleId],\n initialPageParam: undefined as number | undefined,\n queryFn: async ({ pageParam }) => {\n if (!scheduleId) return { triggers: [] } as unknown as ListScheduleTriggersResponse;\n return client.listScheduleTriggers(scheduleId, {\n limit: PER_PAGE,\n toActualFireAt: pageParam,\n });\n },\n getNextPageParam: lastPage => {\n if (!lastPage?.triggers?.length || lastPage.triggers.length < PER_PAGE) {\n return undefined;\n }\n // triggers come back ordered by actualFireAt desc; cursor for next page\n // is the oldest timestamp on the current page (exclusive upper bound).\n return lastPage.triggers[lastPage.triggers.length - 1]!.actualFireAt;\n },\n refetchInterval: query => {\n const triggers = query.state.data?.pages.flatMap(p => p.triggers) ?? [];\n const hasActive = triggers.some(t => {\n if (!t.run) return t.outcome === 'published';\n return t.run.status === 'pending' || t.run.status === 'running' || t.run.status === 'waiting';\n });\n return hasActive ? 5_000 : false;\n },\n ...queryOptions,\n });\n\n const triggers = query.data?.pages.flatMap(page => page?.triggers ?? []) ?? [];\n\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;\n useEffect(() => {\n if (isEndOfListInView && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage();\n }\n }, [isEndOfListInView, hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n return { ...query, data: triggers, setEndOfListElement };\n};\n","import type { ScheduleResponse } from '@mastra/client-js';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraMutationOptions } from '../shared/query-options';\n\n/**\n * Pause/resume a schedule. Used by the schedule detail page button.\n *\n * On success invalidates `['schedule', scheduleId]` and `['schedules']` so the\n * detail meta strip and the list view both refresh.\n */\nexport const useToggleSchedule = ({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraMutationOptions<ScheduleResponse, 'pause' | 'resume'>;\n}) => {\n const client = useMastraClient();\n const queryClient = useQueryClient();\n\n return useMutation<ScheduleResponse, Error, 'pause' | 'resume'>({\n mutationFn: async action => {\n if (!scheduleId) throw new Error('scheduleId is required');\n return action === 'pause' ? client.pauseSchedule(scheduleId) : client.resumeSchedule(scheduleId);\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['schedule', scheduleId] });\n void queryClient.invalidateQueries({ queryKey: ['schedules'] });\n },\n ...queryOptions,\n });\n};\n"],"mappings":";;;;;;;;;AASA,MAAa,eAAyC,EACpD,YACA,mBAIkC;CAClC,MAAM,SAASA,8BAAAA,gBAAgB;CAE/B,QAAA,GAAA,sBAAA,SAAA,CAAgD;EAC9C,UAAU,CAAC,YAAY,UAAU;EACjC,SAAS,YAAY;GACnB,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,wBAAwB;GACzD,OAAO,OAAO,YAAY,UAAU;EACtC;EACA,GAAG;CACL,CAAC;AACH;;;ACpBA,MAAa,gBAA4C,EACvD,cACA,GAAG,WACuF,CAAC,MAGxF;CACH,MAAM,SAASC,8BAAAA,gBAAgB;CAE/B,QAAA,GAAA,sBAAA,SAAA,CAAkD;EAChD,UAAU,CAAC,aAAa,MAAM;EAC9B,SAAS,YAAY;GAEnB,QAAO,MADc,OAAO,cAAc,MAAM,EAAA,CAClC;EAChB;EACA,GAAG;CACL,CAAC;AACH;;;ACVA,MAAM,WAAW;;;;;AAQjB,MAAa,uBAAuB,EAClC,YACA,mBAS+B;CAC/B,MAAM,SAASC,8BAAAA,gBAAgB;CAC/B,MAAM,EAAE,QAAQ,mBAAmB,QAAQ,wBAAwBC,oBAAAA,UAAU;CAE7E,MAAM,SAAA,GAAA,sBAAA,iBAAA,CAAyB;EAC7B,UAAU,CAAC,qBAAqB,UAAU;EAC1C,kBAAkB,KAAA;EAClB,SAAS,OAAO,EAAE,gBAAgB;GAChC,IAAI,CAAC,YAAY,OAAO,EAAE,UAAU,CAAC,EAAE;GACvC,OAAO,OAAO,qBAAqB,YAAY;IAC7C,OAAO;IACP,gBAAgB;GAClB,CAAC;EACH;EACA,mBAAkB,aAAY;GAC5B,IAAI,CAAC,UAAU,UAAU,UAAU,SAAS,SAAS,SAAS,UAC5D;GAIF,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS,EAAE,CAAE;EAC1D;EACA,kBAAiB,UAAS;GAMxB,QALiB,MAAM,MAAM,MAAM,MAAM,SAAQ,MAAK,EAAE,QAAQ,KAAK,CAAC,EAAA,CAC3C,MAAK,MAAK;IACnC,IAAI,CAAC,EAAE,KAAK,OAAO,EAAE,YAAY;IACjC,OAAO,EAAE,IAAI,WAAW,aAAa,EAAE,IAAI,WAAW,aAAa,EAAE,IAAI,WAAW;GACtF,CACe,IAAI,MAAQ;EAC7B;EACA,GAAG;CACL,CAAC;CAED,MAAM,WAAW,MAAM,MAAM,MAAM,SAAQ,SAAQ,MAAM,YAAY,CAAC,CAAC,KAAK,CAAC;CAE7E,MAAM,EAAE,aAAa,oBAAoB,kBAAkB;CAC3D,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,qBAAqB,eAAe,CAAC,oBACvC,cAAmB;CAEvB,GAAG;EAAC;EAAmB;EAAa;EAAoB;CAAa,CAAC;CAEtE,OAAO;EAAE,GAAG;EAAO,MAAM;EAAU;CAAoB;AACzD;;;;;;;;;AChEA,MAAa,qBAAqB,EAChC,YACA,mBAII;CACJ,MAAM,SAASC,8BAAAA,gBAAgB;CAC/B,MAAM,eAAA,GAAA,sBAAA,eAAA,CAA6B;CAEnC,QAAA,GAAA,sBAAA,YAAA,CAAgE;EAC9D,YAAY,OAAM,WAAU;GAC1B,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,wBAAwB;GACzD,OAAO,WAAW,UAAU,OAAO,cAAc,UAAU,IAAI,OAAO,eAAe,UAAU;EACjG;EACA,iBAAiB;GACf,YAAiB,kBAAkB,EAAE,UAAU,CAAC,YAAY,UAAU,EAAE,CAAC;GACzE,YAAiB,kBAAkB,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC;EAChE;EACA,GAAG;CACL,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/hooks/schedules/use-schedule.ts","../../../src/hooks/schedules/use-schedules.ts","../../../src/hooks/schedules/use-schedule-triggers.ts","../../../src/hooks/schedules/use-toggle-schedule.ts"],"sourcesContent":["import type { ScheduleResponse } 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/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useSchedule = <TData = ScheduleResponse>({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraQueryOptions<ScheduleResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse, Error, TData>({\n queryKey: ['schedule', scheduleId],\n queryFn: async () => {\n if (!scheduleId) throw new Error('scheduleId is required');\n return client.getSchedule(scheduleId);\n },\n ...queryOptions,\n });\n};\n","import type { ListSchedulesParams, ScheduleResponse } 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\nexport const useSchedules = <TData = ScheduleResponse[]>({\n queryOptions,\n ...params\n}: ListSchedulesParams & { queryOptions?: MastraQueryOptions<ScheduleResponse[], TData> } = {}): UseQueryResult<\n TData,\n Error\n> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse[], Error, TData>({\n queryKey: ['schedules', params],\n queryFn: async () => {\n const result = await client.listSchedules(params);\n return result.schedules;\n },\n ...queryOptions,\n });\n};\n","import type { ScheduleTriggerResponse, MastraClient } from '@mastra/client-js';\nimport { useInfiniteQuery
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/hooks/schedules/use-schedule.ts","../../../src/hooks/schedules/use-schedules.ts","../../../src/hooks/schedules/use-schedule-triggers.ts","../../../src/hooks/schedules/use-toggle-schedule.ts"],"sourcesContent":["import type { ScheduleResponse } 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/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useSchedule = <TData = ScheduleResponse>({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraQueryOptions<ScheduleResponse, TData>;\n}): UseQueryResult<TData, Error> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse, Error, TData>({\n queryKey: ['schedule', scheduleId],\n queryFn: async () => {\n if (!scheduleId) throw new Error('scheduleId is required');\n return client.getSchedule(scheduleId);\n },\n ...queryOptions,\n });\n};\n","import type { ListSchedulesParams, ScheduleResponse } 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\nexport const useSchedules = <TData = ScheduleResponse[]>({\n queryOptions,\n ...params\n}: ListSchedulesParams & { queryOptions?: MastraQueryOptions<ScheduleResponse[], TData> } = {}): UseQueryResult<\n TData,\n Error\n> => {\n const client = useMastraClient();\n\n return useQuery<ScheduleResponse[], Error, TData>({\n queryKey: ['schedules', params],\n queryFn: async () => {\n const result = await client.listSchedules(params);\n return result.schedules;\n },\n ...queryOptions,\n });\n};\n","import type { ScheduleTriggerResponse, 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';\n\nexport type UseScheduleTriggersResult = Omit<UseInfiniteQueryResult<unknown, Error>, 'data'> & {\n data: ScheduleTriggerResponse[];\n setEndOfListElement: (element: HTMLDivElement | null) => void;\n};\n\nconst PER_PAGE = 25;\n\ntype ListScheduleTriggersResponse = Awaited<ReturnType<MastraClient['listScheduleTriggers']>>;\n\n/** `queryOptions` spread last; the hook flattens pages itself, so `select` is not applied to `data`. */\n/**\n * Does not guard on empty ids; pass `queryOptions: { enabled }` to skip the fetch.\n */\nexport const useScheduleTriggers = ({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraInfiniteQueryOptions<\n ListScheduleTriggersResponse,\n InfiniteData<ListScheduleTriggersResponse, number | undefined>,\n QueryKey,\n number | undefined\n >;\n}): UseScheduleTriggersResult => {\n const client = useMastraClient();\n const { inView: isEndOfListInView, setRef: setEndOfListElement } = useInView();\n\n const query = useInfiniteQuery({\n queryKey: ['schedule-triggers', scheduleId],\n initialPageParam: undefined as number | undefined,\n queryFn: async ({ pageParam }) => {\n if (!scheduleId) return { triggers: [] } as unknown as ListScheduleTriggersResponse;\n return client.listScheduleTriggers(scheduleId, {\n limit: PER_PAGE,\n toActualFireAt: pageParam,\n });\n },\n getNextPageParam: lastPage => {\n if (!lastPage?.triggers?.length || lastPage.triggers.length < PER_PAGE) {\n return undefined;\n }\n // triggers come back ordered by actualFireAt desc; cursor for next page\n // is the oldest timestamp on the current page (exclusive upper bound).\n return lastPage.triggers[lastPage.triggers.length - 1]!.actualFireAt;\n },\n refetchInterval: query => {\n const triggers = query.state.data?.pages.flatMap(p => p.triggers) ?? [];\n const hasActive = triggers.some(t => {\n if (!t.run) return t.outcome === 'published';\n return t.run.status === 'pending' || t.run.status === 'running' || t.run.status === 'waiting';\n });\n return hasActive ? 5_000 : false;\n },\n ...queryOptions,\n });\n\n const triggers = query.data?.pages.flatMap(page => page?.triggers ?? []) ?? [];\n\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;\n useEffect(() => {\n if (isEndOfListInView && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage();\n }\n }, [isEndOfListInView, hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n return { ...query, data: triggers, setEndOfListElement };\n};\n","import type { ScheduleResponse } from '@mastra/client-js';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { useMastraClient } from '../../mastra-client-context';\nimport type { MastraMutationOptions } from '../shared/query-options';\n\n/**\n * Pause/resume a schedule. Used by the schedule detail page button.\n *\n * On success invalidates `['schedule', scheduleId]` and `['schedules']` so the\n * detail meta strip and the list view both refresh.\n */\nexport const useToggleSchedule = ({\n scheduleId,\n queryOptions,\n}: {\n scheduleId: string | undefined;\n queryOptions?: MastraMutationOptions<ScheduleResponse, 'pause' | 'resume'>;\n}) => {\n const client = useMastraClient();\n const queryClient = useQueryClient();\n\n return useMutation<ScheduleResponse, Error, 'pause' | 'resume'>({\n mutationFn: async action => {\n if (!scheduleId) throw new Error('scheduleId is required');\n return action === 'pause' ? client.pauseSchedule(scheduleId) : client.resumeSchedule(scheduleId);\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['schedule', scheduleId] });\n void queryClient.invalidateQueries({ queryKey: ['schedules'] });\n },\n ...queryOptions,\n });\n};\n"],"mappings":";;;;;;;;AASA,MAAa,eAAyC,EACpD,YACA,mBAIkC;CAClC,MAAM,SAAS,gBAAgB;CAE/B,OAAO,SAAyC;EAC9C,UAAU,CAAC,YAAY,UAAU;EACjC,SAAS,YAAY;GACnB,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,wBAAwB;GACzD,OAAO,OAAO,YAAY,UAAU;EACtC;EACA,GAAG;CACL,CAAC;AACH;;;ACpBA,MAAa,gBAA4C,EACvD,cACA,GAAG,WACuF,CAAC,MAGxF;CACH,MAAM,SAAS,gBAAgB;CAE/B,OAAO,SAA2C;EAChD,UAAU,CAAC,aAAa,MAAM;EAC9B,SAAS,YAAY;GAEnB,QAAO,MADc,OAAO,cAAc,MAAM,EAAA,CAClC;EAChB;EACA,GAAG;CACL,CAAC;AACH;;;ACVA,MAAM,WAAW;;;;;AAQjB,MAAa,uBAAuB,EAClC,YACA,mBAS+B;CAC/B,MAAM,SAAS,gBAAgB;CAC/B,MAAM,EAAE,QAAQ,mBAAmB,QAAQ,wBAAwB,UAAU;CAE7E,MAAM,QAAQ,iBAAiB;EAC7B,UAAU,CAAC,qBAAqB,UAAU;EAC1C,kBAAkB,KAAA;EAClB,SAAS,OAAO,EAAE,gBAAgB;GAChC,IAAI,CAAC,YAAY,OAAO,EAAE,UAAU,CAAC,EAAE;GACvC,OAAO,OAAO,qBAAqB,YAAY;IAC7C,OAAO;IACP,gBAAgB;GAClB,CAAC;EACH;EACA,mBAAkB,aAAY;GAC5B,IAAI,CAAC,UAAU,UAAU,UAAU,SAAS,SAAS,SAAS,UAC5D;GAIF,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS,EAAE,CAAE;EAC1D;EACA,kBAAiB,UAAS;GAMxB,QALiB,MAAM,MAAM,MAAM,MAAM,SAAQ,MAAK,EAAE,QAAQ,KAAK,CAAC,EAAA,CAC3C,MAAK,MAAK;IACnC,IAAI,CAAC,EAAE,KAAK,OAAO,EAAE,YAAY;IACjC,OAAO,EAAE,IAAI,WAAW,aAAa,EAAE,IAAI,WAAW,aAAa,EAAE,IAAI,WAAW;GACtF,CACe,IAAI,MAAQ;EAC7B;EACA,GAAG;CACL,CAAC;CAED,MAAM,WAAW,MAAM,MAAM,MAAM,SAAQ,SAAQ,MAAM,YAAY,CAAC,CAAC,KAAK,CAAC;CAE7E,MAAM,EAAE,aAAa,oBAAoB,kBAAkB;CAC3D,gBAAgB;EACd,IAAI,qBAAqB,eAAe,CAAC,oBACvC,cAAmB;CAEvB,GAAG;EAAC;EAAmB;EAAa;EAAoB;CAAa,CAAC;CAEtE,OAAO;EAAE,GAAG;EAAO,MAAM;EAAU;CAAoB;AACzD;;;;;;;;;AChEA,MAAa,qBAAqB,EAChC,YACA,mBAII;CACJ,MAAM,SAAS,gBAAgB;CAC/B,MAAM,cAAc,eAAe;CAEnC,OAAO,YAAyD;EAC9D,YAAY,OAAM,WAAU;GAC1B,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,wBAAwB;GACzD,OAAO,WAAW,UAAU,OAAO,cAAc,UAAU,IAAI,OAAO,eAAe,UAAU;EACjG;EACA,iBAAiB;GACf,YAAiB,kBAAkB,EAAE,UAAU,CAAC,YAAY,UAAU,EAAE,CAAC;GACzE,YAAiB,kBAAkB,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC;EAChE;EACA,GAAG;CACL,CAAC;AACH"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ScheduleTriggerResponse, MastraClient } from '@mastra/client-js';
|
|
2
|
-
import {
|
|
2
|
+
import type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';
|
|
3
3
|
import type { MastraInfiniteQueryOptions } from '../shared/query-options.js';
|
|
4
4
|
export type UseScheduleTriggersResult = Omit<UseInfiniteQueryResult<unknown, Error>, 'data'> & {
|
|
5
5
|
data: ScheduleTriggerResponse[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-schedule-triggers.d.ts","sourceRoot":"","sources":["../../../src/hooks/schedules/use-schedule-triggers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"use-schedule-triggers.d.ts","sourceRoot":"","sources":["../../../src/hooks/schedules/use-schedule-triggers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAG5F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAG1E,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,GAAG;IAC7F,IAAI,EAAE,uBAAuB,EAAE,CAAC;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,KAAK,IAAI,CAAC;CAC/D,CAAC;AAIF,KAAK,4BAA4B,GAAG,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;AAE9F,wGAAwG;AACxG;;GAEG;AACH,eAAO,MAAM,mBAAmB,kCAG7B;IACD,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,CAAC,EAAE,0BAA0B,CACvC,4BAA4B,EAC5B,YAAY,CAAC,4BAA4B,EAAE,MAAM,GAAG,SAAS,CAAC,EAC9D,QAAQ,EACR,MAAM,GAAG,SAAS,CACnB,CAAC;CACH,KAAG,yBA2CH,CAAC"}
|