@copilotz/admin 0.9.35 → 0.9.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -63,6 +63,7 @@ __export(index_exports, {
63
63
  formatUsageBucket: () => formatUsageBucket,
64
64
  getUsageDimensionLabel: () => getUsageDimensionLabel,
65
65
  getUsageGroupLabel: () => getUsageGroupLabel,
66
+ getUsageMetricLabel: () => getUsageMetricLabel,
66
67
  getUsageRange: () => getUsageRange,
67
68
  getUsageTotalValue: () => getUsageTotalValue,
68
69
  mergeAdminConfig: () => mergeAdminConfig,
@@ -120,11 +121,44 @@ async function mergeHeaders(headers, getRequestHeaders) {
120
121
  async function parseJsonResponse(response) {
121
122
  const payload = await response.json().catch(() => null);
122
123
  if (!response.ok) {
123
- const message = payload?.message ?? payload?.data?.message ?? `Admin request failed (${response.status})`;
124
+ const message = payload?.message ?? payload?.data?.message ?? payload?.error?.message ?? `Admin request failed (${response.status})`;
124
125
  throw new Error(message);
125
126
  }
126
127
  return payload?.data ?? payload;
127
128
  }
129
+ async function parseJsonEnvelopeResponse(response) {
130
+ const payload = await response.json().catch(() => null);
131
+ if (!response.ok) {
132
+ const message = payload?.message ?? payload?.data?.message ?? payload?.error?.message ?? `Admin request failed (${response.status})`;
133
+ throw new Error(message);
134
+ }
135
+ return payload;
136
+ }
137
+ function isRecord(value) {
138
+ return typeof value === "object" && value !== null && !Array.isArray(value);
139
+ }
140
+ function normalizeMessagePageInfo(value, messages) {
141
+ const oldestFromData = messages[0]?.id ?? null;
142
+ const newestFromData = messages[messages.length - 1]?.id ?? null;
143
+ if (!isRecord(value)) {
144
+ return {
145
+ hasMoreBefore: false,
146
+ oldestMessageId: oldestFromData,
147
+ newestMessageId: newestFromData
148
+ };
149
+ }
150
+ return {
151
+ hasMoreBefore: value.hasMoreBefore === true,
152
+ oldestMessageId: typeof value.oldestMessageId === "string" ? value.oldestMessageId : oldestFromData,
153
+ newestMessageId: typeof value.newestMessageId === "string" ? value.newestMessageId : newestFromData
154
+ };
155
+ }
156
+ function normalizeMessagePage(payload) {
157
+ const candidate = isRecord(payload) && isRecord(payload.data) && Array.isArray(payload.data.data) ? payload.data : payload;
158
+ const data = isRecord(candidate) && Array.isArray(candidate.data) ? candidate.data : Array.isArray(candidate) ? candidate : [];
159
+ const pageInfo = isRecord(candidate) ? normalizeMessagePageInfo(candidate.pageInfo, data) : normalizeMessagePageInfo(void 0, data);
160
+ return { data, pageInfo };
161
+ }
128
162
  function createAdminClient(options = {}) {
129
163
  const baseUrl = resolveBaseUrl(options.baseUrl);
130
164
  const paths = { ...DEFAULT_PATHS, ...options.paths };
@@ -135,6 +169,13 @@ function createAdminClient(options = {}) {
135
169
  });
136
170
  return await parseJsonResponse(response);
137
171
  };
172
+ const requestEnvelopeJson = async (path, params) => {
173
+ const url = buildUrl(baseUrl, path, params);
174
+ const response = await fetch(url.toString(), {
175
+ headers: await mergeHeaders({}, options.getRequestHeaders)
176
+ });
177
+ return await parseJsonEnvelopeResponse(response);
178
+ };
138
179
  const writeJson = async (method, path, data, params) => {
139
180
  const url = buildUrl(baseUrl, path, params);
140
181
  const response = await fetch(url.toString(), {
@@ -177,12 +218,16 @@ function createAdminClient(options = {}) {
177
218
  metric: filters.metric,
178
219
  groupBy: filters.groupBy,
179
220
  attribution: filters.attribution,
221
+ kind: filters.kind,
180
222
  threadId: filters.threadId,
181
223
  participantId: filters.participantId,
182
224
  participantType: filters.participantType,
183
225
  namespace: filters.namespace,
184
226
  provider: filters.provider,
185
- model: filters.model
227
+ model: filters.model,
228
+ resource: filters.resource,
229
+ operation: filters.operation,
230
+ status: filters.status
186
231
  }),
187
232
  listThreads: async (listOptions = {}) => await requestJson(`${paths.adminBase}/threads`, {
188
233
  search: listOptions.search,
@@ -210,13 +255,16 @@ function createAdminClient(options = {}) {
210
255
  getThread: async (threadId) => await requestJson(
211
256
  `${paths.threadsBase}/${encodeURIComponent(threadId)}`
212
257
  ),
213
- getThreadMessages: async (threadId, messageOptions = {}) => await requestJson(
214
- `${paths.threadsBase}/${encodeURIComponent(threadId)}/messages`,
215
- {
216
- limit: messageOptions.limit ? String(messageOptions.limit) : void 0,
217
- before: messageOptions.before
218
- }
219
- ),
258
+ getThreadMessages: async (threadId, messageOptions = {}) => {
259
+ const payload = await requestEnvelopeJson(
260
+ `${paths.threadsBase}/${encodeURIComponent(threadId)}/messages`,
261
+ {
262
+ limit: messageOptions.limit ? String(messageOptions.limit) : void 0,
263
+ before: messageOptions.before
264
+ }
265
+ );
266
+ return normalizeMessagePage(payload);
267
+ },
220
268
  getThreadEvent: async (threadId) => await requestJson(
221
269
  `${paths.threadsBase}/${encodeURIComponent(threadId)}/events`
222
270
  ),
@@ -1426,7 +1474,11 @@ var EMPTY_USAGE_TOTALS = {
1426
1474
  reasoningTokens: 0,
1427
1475
  totalCalls: 0,
1428
1476
  totalCostUsd: 0,
1429
- totalTokens: 0
1477
+ failedCalls: 0,
1478
+ totalCredits: 0,
1479
+ totalDurationMs: 0,
1480
+ totalTokens: 0,
1481
+ unpricedCalls: 0
1430
1482
  };
1431
1483
  var USAGE_CHART_COLORS = [
1432
1484
  "hsl(var(--primary))",
@@ -1437,53 +1489,66 @@ var USAGE_CHART_COLORS = [
1437
1489
  "hsl(var(--muted-foreground))"
1438
1490
  ];
1439
1491
  function addUsageTotals(target, source) {
1440
- target.cacheCreationInputCostUsd += source.cacheCreationInputCostUsd;
1441
- target.cacheCreationInputTokens += source.cacheCreationInputTokens;
1442
- target.cacheReadInputCostUsd += source.cacheReadInputCostUsd;
1443
- target.cacheReadInputTokens += source.cacheReadInputTokens;
1444
- target.inputCostUsd += source.inputCostUsd;
1445
- target.inputTokens += source.inputTokens;
1446
- target.outputCostUsd += source.outputCostUsd;
1447
- target.outputTokens += source.outputTokens;
1448
- target.reasoningCostUsd += source.reasoningCostUsd;
1449
- target.reasoningTokens += source.reasoningTokens;
1450
- target.totalCalls += source.totalCalls;
1451
- target.totalCostUsd += source.totalCostUsd;
1452
- target.totalTokens += source.totalTokens;
1492
+ target.cacheCreationInputCostUsd += safeUsageNumber(source.cacheCreationInputCostUsd);
1493
+ target.cacheCreationInputTokens += safeUsageNumber(source.cacheCreationInputTokens);
1494
+ target.cacheReadInputCostUsd += safeUsageNumber(source.cacheReadInputCostUsd);
1495
+ target.cacheReadInputTokens += safeUsageNumber(source.cacheReadInputTokens);
1496
+ target.inputCostUsd += safeUsageNumber(source.inputCostUsd);
1497
+ target.inputTokens += safeUsageNumber(source.inputTokens);
1498
+ target.outputCostUsd += safeUsageNumber(source.outputCostUsd);
1499
+ target.outputTokens += safeUsageNumber(source.outputTokens);
1500
+ target.reasoningCostUsd += safeUsageNumber(source.reasoningCostUsd);
1501
+ target.reasoningTokens += safeUsageNumber(source.reasoningTokens);
1502
+ target.totalCalls += safeUsageNumber(source.totalCalls);
1503
+ target.totalCostUsd += safeUsageNumber(source.totalCostUsd);
1504
+ target.failedCalls += safeUsageNumber(source.failedCalls);
1505
+ target.totalCredits += safeUsageNumber(source.totalCredits);
1506
+ target.totalDurationMs += safeUsageNumber(source.totalDurationMs);
1507
+ target.totalTokens += safeUsageNumber(source.totalTokens);
1508
+ target.unpricedCalls += safeUsageNumber(source.unpricedCalls);
1509
+ }
1510
+ function safeUsageNumber(value) {
1511
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1453
1512
  }
1454
1513
  function getUsageTotalValue(totals, metricKind, dimension) {
1455
- if (metricKind === "calls") return totals.totalCalls;
1514
+ if (metricKind === "calls") return safeUsageNumber(totals.totalCalls);
1515
+ if (metricKind === "duration") {
1516
+ return safeUsageNumber(totals.totalDurationMs);
1517
+ }
1518
+ if (metricKind === "credits") return safeUsageNumber(totals.totalCredits);
1519
+ if (metricKind === "failures") return safeUsageNumber(totals.failedCalls);
1520
+ if (metricKind === "unpriced") return safeUsageNumber(totals.unpricedCalls);
1456
1521
  if (metricKind === "cost") {
1457
1522
  switch (dimension) {
1458
1523
  case "input":
1459
- return totals.inputCostUsd;
1524
+ return safeUsageNumber(totals.inputCostUsd);
1460
1525
  case "output":
1461
- return totals.outputCostUsd;
1526
+ return safeUsageNumber(totals.outputCostUsd);
1462
1527
  case "reasoning":
1463
- return totals.reasoningCostUsd;
1528
+ return safeUsageNumber(totals.reasoningCostUsd);
1464
1529
  case "cacheRead":
1465
- return totals.cacheReadInputCostUsd;
1530
+ return safeUsageNumber(totals.cacheReadInputCostUsd);
1466
1531
  case "cacheWrite":
1467
- return totals.cacheCreationInputCostUsd;
1532
+ return safeUsageNumber(totals.cacheCreationInputCostUsd);
1468
1533
  case "total":
1469
1534
  default:
1470
- return totals.totalCostUsd;
1535
+ return safeUsageNumber(totals.totalCostUsd);
1471
1536
  }
1472
1537
  }
1473
1538
  switch (dimension) {
1474
1539
  case "input":
1475
- return totals.inputTokens;
1540
+ return safeUsageNumber(totals.inputTokens);
1476
1541
  case "output":
1477
- return totals.outputTokens;
1542
+ return safeUsageNumber(totals.outputTokens);
1478
1543
  case "reasoning":
1479
- return totals.reasoningTokens;
1544
+ return safeUsageNumber(totals.reasoningTokens);
1480
1545
  case "cacheRead":
1481
- return totals.cacheReadInputTokens;
1546
+ return safeUsageNumber(totals.cacheReadInputTokens);
1482
1547
  case "cacheWrite":
1483
- return totals.cacheCreationInputTokens;
1548
+ return safeUsageNumber(totals.cacheCreationInputTokens);
1484
1549
  case "total":
1485
1550
  default:
1486
- return totals.totalTokens;
1551
+ return safeUsageNumber(totals.totalTokens);
1487
1552
  }
1488
1553
  }
1489
1554
  function aggregateUsageRows(points, metricKind, dimension) {
@@ -1595,6 +1660,14 @@ function getUsageDimensionLabel(dimension) {
1595
1660
  }
1596
1661
  function getUsageGroupLabel(groupBy) {
1597
1662
  switch (groupBy) {
1663
+ case "kind":
1664
+ return "Kind";
1665
+ case "resource":
1666
+ return "Resource";
1667
+ case "operation":
1668
+ return "Operation";
1669
+ case "status":
1670
+ return "Status";
1598
1671
  case "thread":
1599
1672
  return "Thread";
1600
1673
  case "namespace":
@@ -1608,6 +1681,25 @@ function getUsageGroupLabel(groupBy) {
1608
1681
  return "Participant";
1609
1682
  }
1610
1683
  }
1684
+ function getUsageMetricLabel(metricKind) {
1685
+ switch (metricKind) {
1686
+ case "cost":
1687
+ return "Spend";
1688
+ case "tokens":
1689
+ return "Tokens";
1690
+ case "duration":
1691
+ return "Duration";
1692
+ case "credits":
1693
+ return "Credits";
1694
+ case "failures":
1695
+ return "Failures";
1696
+ case "unpriced":
1697
+ return "Unpriced";
1698
+ case "calls":
1699
+ default:
1700
+ return "Calls";
1701
+ }
1702
+ }
1611
1703
  function formatUsageBucket(bucket, interval) {
1612
1704
  const date = new Date(bucket);
1613
1705
  if (Number.isNaN(date.getTime())) return bucket;
@@ -1650,6 +1742,9 @@ function formatMetricValue(value, metricKind) {
1650
1742
  style: "currency"
1651
1743
  }).format(value);
1652
1744
  }
1745
+ if (metricKind === "duration") {
1746
+ return formatDuration(value);
1747
+ }
1653
1748
  return formatNumber(value);
1654
1749
  }
1655
1750
  function formatCompactMetric(value, metricKind) {
@@ -1662,12 +1757,30 @@ function formatCompactMetric(value, metricKind) {
1662
1757
  style: "currency"
1663
1758
  }).format(value);
1664
1759
  }
1760
+ if (metricKind === "duration") {
1761
+ if (value >= 36e5) return `${(value / 36e5).toFixed(1)}h`;
1762
+ if (value >= 6e4) return `${(value / 6e4).toFixed(1)}m`;
1763
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}s`;
1764
+ return `${Math.round(value)}ms`;
1765
+ }
1665
1766
  return new Intl.NumberFormat(void 0, {
1666
1767
  compactDisplay: "short",
1667
1768
  maximumFractionDigits: 1,
1668
1769
  notation: "compact"
1669
1770
  }).format(value);
1670
1771
  }
1772
+ function formatDuration(value) {
1773
+ if (value >= 36e5) {
1774
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 36e5)} h`;
1775
+ }
1776
+ if (value >= 6e4) {
1777
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 6e4)} min`;
1778
+ }
1779
+ if (value >= 1e3) {
1780
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 1e3)} sec`;
1781
+ }
1782
+ return `${formatNumber(Math.round(value))} ms`;
1783
+ }
1671
1784
 
1672
1785
  // src/modules/agents/index.tsx
1673
1786
  var import_jsx_runtime20 = require("react/jsx-runtime");
@@ -2759,7 +2872,9 @@ function ThreadsPage({ context }) {
2759
2872
  align: "right",
2760
2873
  id: "participants",
2761
2874
  header: "Participants",
2762
- render: (thread) => formatNumber(thread.participantIds.length)
2875
+ render: (thread) => formatNumber(
2876
+ Array.isArray(thread.participantIds) ? thread.participantIds.length : 0
2877
+ )
2763
2878
  },
2764
2879
  {
2765
2880
  align: "right",
@@ -2810,8 +2925,8 @@ function ThreadInspector({
2810
2925
  ]).then(([nextThread, page]) => {
2811
2926
  if (!active) return;
2812
2927
  setThread(nextThread);
2813
- setMessages(page.data);
2814
- setPageInfo(page.pageInfo);
2928
+ setMessages(Array.isArray(page?.data) ? page.data : []);
2929
+ setPageInfo(page?.pageInfo ?? null);
2815
2930
  }).catch((cause) => {
2816
2931
  if (active) setError(cause instanceof Error ? cause.message : "Failed to load thread");
2817
2932
  }).finally(() => {
@@ -2831,8 +2946,11 @@ function ThreadInspector({
2831
2946
  before: pageInfo.oldestMessageId,
2832
2947
  limit: MESSAGE_PAGE_SIZE
2833
2948
  });
2834
- setMessages((current) => [...page.data, ...current]);
2835
- setPageInfo(page.pageInfo);
2949
+ setMessages((current) => [
2950
+ ...Array.isArray(page?.data) ? page.data : [],
2951
+ ...current
2952
+ ]);
2953
+ setPageInfo(page?.pageInfo ?? null);
2836
2954
  } finally {
2837
2955
  setIsLoadingMore(false);
2838
2956
  }
@@ -3090,6 +3208,22 @@ var USAGE_DIMENSIONS = [
3090
3208
  "cacheRead",
3091
3209
  "cacheWrite"
3092
3210
  ];
3211
+ var USAGE_WORKLOADS = [
3212
+ "all",
3213
+ "llm",
3214
+ "tool",
3215
+ "asset",
3216
+ "rag",
3217
+ "embedding"
3218
+ ];
3219
+ var STATUS_OPTIONS = [
3220
+ "all",
3221
+ "completed",
3222
+ "failed",
3223
+ "cancelled",
3224
+ "aborted",
3225
+ "error"
3226
+ ];
3093
3227
  function usageModule() {
3094
3228
  return {
3095
3229
  group: "operate",
@@ -3116,11 +3250,12 @@ function UsagePage({ context }) {
3116
3250
  "7d"
3117
3251
  );
3118
3252
  const [interval, setInterval] = import_react8.default.useState("day");
3253
+ const [workload, setWorkload] = import_react8.default.useState("all");
3119
3254
  const [metricKind, setMetricKind] = import_react8.default.useState(
3120
- "cost"
3255
+ "calls"
3121
3256
  );
3122
3257
  const [dimension, setDimension] = import_react8.default.useState("total");
3123
- const [groupBy, setGroupBy] = import_react8.default.useState("participant");
3258
+ const [groupBy, setGroupBy] = import_react8.default.useState("kind");
3124
3259
  const [attribution, setAttribution] = import_react8.default.useState(
3125
3260
  "initiatedBy"
3126
3261
  );
@@ -3129,6 +3264,11 @@ function UsagePage({ context }) {
3129
3264
  const [participantId, setParticipantId] = import_react8.default.useState("");
3130
3265
  const [provider, setProvider] = import_react8.default.useState("");
3131
3266
  const [model, setModel] = import_react8.default.useState("");
3267
+ const [resource, setResource] = import_react8.default.useState("");
3268
+ const [operation, setOperation] = import_react8.default.useState("");
3269
+ const [status, setStatus] = import_react8.default.useState(
3270
+ "all"
3271
+ );
3132
3272
  const [customFrom, setCustomFrom] = import_react8.default.useState("");
3133
3273
  const [customTo, setCustomTo] = import_react8.default.useState("");
3134
3274
  const [usage, setUsage] = import_react8.default.useState(null);
@@ -3138,6 +3278,32 @@ function UsagePage({ context }) {
3138
3278
  () => getUsageRange(period, customFrom, customTo),
3139
3279
  [customFrom, customTo, period]
3140
3280
  );
3281
+ const metricOptions = import_react8.default.useMemo(
3282
+ () => getUsageMetricOptions(workload),
3283
+ [workload]
3284
+ );
3285
+ const groupOptions = import_react8.default.useMemo(
3286
+ () => getUsageGroupOptions(workload),
3287
+ [workload]
3288
+ );
3289
+ const showLlmFields = workload === "llm";
3290
+ const showResourceFields = workload !== "llm";
3291
+ const showDimension = metricKind === "tokens" || metricKind === "cost" && workload === "llm";
3292
+ import_react8.default.useEffect(() => {
3293
+ if (!metricOptions.includes(metricKind)) {
3294
+ setMetricKind(metricOptions[0]);
3295
+ }
3296
+ }, [metricKind, metricOptions]);
3297
+ import_react8.default.useEffect(() => {
3298
+ if (!groupOptions.includes(groupBy)) {
3299
+ setGroupBy(groupOptions[0]);
3300
+ }
3301
+ }, [groupBy, groupOptions]);
3302
+ import_react8.default.useEffect(() => {
3303
+ if (!showDimension && dimension !== "total") {
3304
+ setDimension("total");
3305
+ }
3306
+ }, [dimension, showDimension]);
3141
3307
  import_react8.default.useEffect(() => {
3142
3308
  let active = true;
3143
3309
  setIsLoading(true);
@@ -3146,13 +3312,17 @@ function UsagePage({ context }) {
3146
3312
  attribution,
3147
3313
  from: range.from,
3148
3314
  groupBy,
3315
+ kind: workload,
3149
3316
  interval,
3150
3317
  metric: metricKind,
3151
- model: emptyToUndefined(model),
3318
+ model: showLlmFields ? emptyToUndefined(model) : void 0,
3152
3319
  namespace: context.scope.namespace || void 0,
3320
+ operation: showResourceFields ? emptyToUndefined(operation) : void 0,
3153
3321
  participantId: emptyToUndefined(participantId),
3154
3322
  participantType,
3155
- provider: emptyToUndefined(provider),
3323
+ provider: showLlmFields ? emptyToUndefined(provider) : void 0,
3324
+ resource: showResourceFields ? emptyToUndefined(resource) : void 0,
3325
+ status: status === "all" ? void 0 : status,
3156
3326
  threadId: emptyToUndefined(threadId),
3157
3327
  to: range.to
3158
3328
  }).then((next) => {
@@ -3177,12 +3347,18 @@ function UsagePage({ context }) {
3177
3347
  interval,
3178
3348
  metricKind,
3179
3349
  model,
3350
+ operation,
3180
3351
  participantId,
3181
3352
  participantType,
3182
3353
  provider,
3183
3354
  range.from,
3184
3355
  range.to,
3185
- threadId
3356
+ resource,
3357
+ showLlmFields,
3358
+ showResourceFields,
3359
+ status,
3360
+ threadId,
3361
+ workload
3186
3362
  ]);
3187
3363
  const points = usage?.points ?? [];
3188
3364
  const totals = usage?.totals ?? EMPTY_USAGE_TOTALS;
@@ -3199,9 +3375,10 @@ function UsagePage({ context }) {
3199
3375
  PageHeader,
3200
3376
  {
3201
3377
  title: "Usage",
3202
- description: "Inspect cost, token, and call attribution across namespaces, models, participants, threads, and providers.",
3378
+ description: "Inspect metered LLM, tool, and resource activity by workload, actor, status, cost, duration, tokens, and credits.",
3203
3379
  badges: [
3204
- { label: period },
3380
+ { label: workload },
3381
+ { label: getUsageMetricLabel(metricKind), variant: "secondary" },
3205
3382
  { label: getUsageGroupLabel(groupBy), variant: "secondary" },
3206
3383
  ...isLoading ? [{ label: "Loading" }] : []
3207
3384
  ]
@@ -3229,10 +3406,21 @@ function UsagePage({ context }) {
3229
3406
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3230
3407
  UsageSelect,
3231
3408
  {
3232
- label: "Metric",
3409
+ label: "Workload",
3410
+ onValueChange: (value) => setWorkload(value),
3411
+ options: USAGE_WORKLOADS,
3412
+ value: workload,
3413
+ formatOption: getUsageKindLabel
3414
+ }
3415
+ ),
3416
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3417
+ UsageSelect,
3418
+ {
3419
+ label: "Measure",
3233
3420
  onValueChange: (value) => setMetricKind(value),
3234
- options: ["cost", "tokens", "calls"],
3235
- value: metricKind
3421
+ options: metricOptions,
3422
+ value: metricKind,
3423
+ formatOption: getUsageMetricLabel
3236
3424
  }
3237
3425
  ),
3238
3426
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
@@ -3240,20 +3428,22 @@ function UsagePage({ context }) {
3240
3428
  {
3241
3429
  label: "Group",
3242
3430
  onValueChange: (value) => setGroupBy(value),
3243
- options: ["participant", "thread", "namespace", "provider", "model"],
3244
- value: groupBy
3431
+ options: groupOptions,
3432
+ value: groupBy,
3433
+ formatOption: getUsageGroupLabel
3245
3434
  }
3246
3435
  ),
3247
3436
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3248
3437
  UsageSelect,
3249
3438
  {
3250
- label: "Attribution",
3439
+ label: "Actor",
3251
3440
  onValueChange: (value) => setAttribution(value),
3252
3441
  options: ["initiatedBy", "generatedBy"],
3253
- value: attribution
3442
+ value: attribution,
3443
+ formatOption: getUsageAttributionLabel
3254
3444
  }
3255
3445
  ),
3256
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3446
+ showDimension && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3257
3447
  UsageSelect,
3258
3448
  {
3259
3449
  label: "Dimension",
@@ -3288,12 +3478,21 @@ function UsagePage({ context }) {
3288
3478
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3289
3479
  UsageSelect,
3290
3480
  {
3291
- label: "Type",
3481
+ label: "Actor type",
3292
3482
  onValueChange: (value) => setParticipantType(value),
3293
3483
  options: ["all", "human", "agent", "job"],
3294
3484
  value: participantType
3295
3485
  }
3296
3486
  ),
3487
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3488
+ UsageSelect,
3489
+ {
3490
+ label: "Status",
3491
+ onValueChange: (value) => setStatus(value),
3492
+ options: STATUS_OPTIONS,
3493
+ value: status
3494
+ }
3495
+ ),
3297
3496
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3298
3497
  Input,
3299
3498
  {
@@ -3312,46 +3511,86 @@ function UsagePage({ context }) {
3312
3511
  value: participantId
3313
3512
  }
3314
3513
  ),
3315
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3316
- Input,
3317
- {
3318
- className: "h-8 w-[140px]",
3319
- onChange: (event) => setProvider(event.target.value),
3320
- placeholder: "Provider",
3321
- value: provider
3322
- }
3323
- ),
3324
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3325
- Input,
3326
- {
3327
- className: "h-8 w-[140px]",
3328
- onChange: (event) => setModel(event.target.value),
3329
- placeholder: "Model",
3330
- value: model
3331
- }
3332
- )
3514
+ showResourceFields && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3515
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3516
+ Input,
3517
+ {
3518
+ className: "h-8 w-[170px]",
3519
+ onChange: (event) => setResource(event.target.value),
3520
+ placeholder: "Resource / tool",
3521
+ value: resource
3522
+ }
3523
+ ),
3524
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3525
+ Input,
3526
+ {
3527
+ className: "h-8 w-[150px]",
3528
+ onChange: (event) => setOperation(event.target.value),
3529
+ placeholder: "Operation",
3530
+ value: operation
3531
+ }
3532
+ )
3533
+ ] }),
3534
+ showLlmFields && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3535
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3536
+ Input,
3537
+ {
3538
+ className: "h-8 w-[140px]",
3539
+ onChange: (event) => setProvider(event.target.value),
3540
+ placeholder: "Provider",
3541
+ value: provider
3542
+ }
3543
+ ),
3544
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3545
+ Input,
3546
+ {
3547
+ className: "h-8 w-[140px]",
3548
+ onChange: (event) => setModel(event.target.value),
3549
+ placeholder: "Model",
3550
+ value: model
3551
+ }
3552
+ )
3553
+ ] })
3333
3554
  ] }),
3334
3555
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3335
3556
  MetricStrip,
3336
3557
  {
3337
3558
  items: [
3338
3559
  {
3339
- detail: `${totals.totalCalls} calls`,
3560
+ detail: `${formatMetricValue(totals.unpricedCalls, "unpriced")} unpriced`,
3340
3561
  icon: import_lucide_react12.DollarSign,
3341
- label: "Cost",
3562
+ label: "Spend",
3342
3563
  value: formatMetricValue(totals.totalCostUsd, "cost")
3343
3564
  },
3344
3565
  {
3345
- detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3566
+ detail: `${formatMetricValue(totals.failedCalls, "failures")} failed`,
3346
3567
  icon: import_lucide_react12.Activity,
3568
+ label: "Calls",
3569
+ value: formatMetricValue(totals.totalCalls, "calls")
3570
+ },
3571
+ {
3572
+ detail: "Tool and resource runtime",
3573
+ icon: import_lucide_react12.Clock,
3574
+ label: "Duration",
3575
+ value: formatMetricValue(totals.totalDurationMs, "duration")
3576
+ },
3577
+ {
3578
+ detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3579
+ icon: import_lucide_react12.Sparkles,
3347
3580
  label: "Tokens",
3348
3581
  value: formatMetricValue(totals.totalTokens, "tokens")
3349
3582
  },
3350
3583
  {
3351
- detail: `${formatMetricValue(totals.reasoningTokens, "tokens")} reasoning`,
3352
- icon: import_lucide_react12.Sparkles,
3353
- label: "Calls",
3354
- value: formatMetricValue(totals.totalCalls, "calls")
3584
+ detail: "Custom metering",
3585
+ icon: import_lucide_react12.Coins,
3586
+ label: "Credits",
3587
+ value: formatMetricValue(totals.totalCredits, "credits")
3588
+ },
3589
+ {
3590
+ detail: "Failed or aborted work",
3591
+ icon: import_lucide_react12.AlertTriangle,
3592
+ label: "Failures",
3593
+ value: formatMetricValue(totals.failedCalls, "failures")
3355
3594
  }
3356
3595
  ]
3357
3596
  }
@@ -3360,7 +3599,7 @@ function UsagePage({ context }) {
3360
3599
  EmptyState,
3361
3600
  {
3362
3601
  title: "No usage",
3363
- description: "Usage records will appear here when provider-native usage is captured."
3602
+ description: "Metered LLM, tool, and resource records will appear here after work is captured in the usage ledger."
3364
3603
  }
3365
3604
  ) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3366
3605
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Card, { className: "h-[320px] py-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(CardContent, { className: "h-full px-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UsageChart, { chartState, metricKind }) }) }),
@@ -3439,6 +3678,7 @@ function UsageRowsTable({
3439
3678
  totals
3440
3679
  }) {
3441
3680
  const totalValue = getUsageTotalValue(totals, metricKind, dimension);
3681
+ const valueHeader = (metricKind === "tokens" || metricKind === "cost") && dimension !== "total" ? `${getUsageDimensionLabel(dimension)} ${getUsageMetricLabel(metricKind)}` : getUsageMetricLabel(metricKind);
3442
3682
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3443
3683
  ResourceTable,
3444
3684
  {
@@ -3456,7 +3696,7 @@ function UsageRowsTable({
3456
3696
  {
3457
3697
  align: "right",
3458
3698
  id: "value",
3459
- header: `${getUsageDimensionLabel(dimension)} ${metricKind}`,
3699
+ header: valueHeader,
3460
3700
  render: (row) => formatMetricValue(row.value, metricKind)
3461
3701
  },
3462
3702
  {
@@ -3473,20 +3713,100 @@ function UsageRowsTable({
3473
3713
  },
3474
3714
  {
3475
3715
  align: "right",
3476
- id: "input",
3477
- header: "Input",
3478
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "input"), metricKind)
3716
+ id: "spend",
3717
+ header: "Spend",
3718
+ render: (row) => formatMetricValue(row.totalCostUsd, "cost")
3719
+ },
3720
+ {
3721
+ align: "right",
3722
+ id: "duration",
3723
+ header: "Duration",
3724
+ render: (row) => formatMetricValue(row.totalDurationMs, "duration")
3725
+ },
3726
+ {
3727
+ align: "right",
3728
+ id: "tokens",
3729
+ header: "Tokens",
3730
+ render: (row) => formatMetricValue(row.totalTokens, "tokens")
3731
+ },
3732
+ {
3733
+ align: "right",
3734
+ id: "failures",
3735
+ header: "Failures",
3736
+ render: (row) => formatMetricValue(row.failedCalls, "failures")
3479
3737
  },
3480
3738
  {
3481
3739
  align: "right",
3482
- id: "output",
3483
- header: "Output",
3484
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "output"), metricKind)
3740
+ id: "unpriced",
3741
+ header: "Unpriced",
3742
+ render: (row) => formatMetricValue(row.unpricedCalls, "unpriced")
3485
3743
  }
3486
3744
  ]
3487
3745
  }
3488
3746
  );
3489
3747
  }
3748
+ function getUsageMetricOptions(workload) {
3749
+ if (workload === "llm") {
3750
+ return ["cost", "tokens", "calls", "failures", "unpriced"];
3751
+ }
3752
+ if (workload === "tool") {
3753
+ return ["calls", "duration", "cost", "credits", "failures", "unpriced"];
3754
+ }
3755
+ return [
3756
+ "calls",
3757
+ "cost",
3758
+ "duration",
3759
+ "tokens",
3760
+ "credits",
3761
+ "failures",
3762
+ "unpriced"
3763
+ ];
3764
+ }
3765
+ function getUsageGroupOptions(workload) {
3766
+ if (workload === "llm") {
3767
+ return ["participant", "model", "provider", "thread", "namespace", "status"];
3768
+ }
3769
+ if (workload === "tool") {
3770
+ return [
3771
+ "resource",
3772
+ "operation",
3773
+ "status",
3774
+ "participant",
3775
+ "thread",
3776
+ "namespace"
3777
+ ];
3778
+ }
3779
+ return [
3780
+ "kind",
3781
+ "resource",
3782
+ "operation",
3783
+ "status",
3784
+ "participant",
3785
+ "thread",
3786
+ "namespace"
3787
+ ];
3788
+ }
3789
+ function getUsageKindLabel(kind) {
3790
+ switch (kind) {
3791
+ case "all":
3792
+ return "All";
3793
+ case "llm":
3794
+ return "LLM";
3795
+ case "tool":
3796
+ return "Tools";
3797
+ case "asset":
3798
+ return "Assets";
3799
+ case "rag":
3800
+ return "RAG";
3801
+ case "embedding":
3802
+ return "Embeddings";
3803
+ default:
3804
+ return kind;
3805
+ }
3806
+ }
3807
+ function getUsageAttributionLabel(attribution) {
3808
+ return attribution === "initiatedBy" ? "Initiator" : "Performer";
3809
+ }
3490
3810
  function emptyToUndefined(value) {
3491
3811
  const trimmed = value.trim();
3492
3812
  return trimmed.length > 0 ? trimmed : void 0;
@@ -3975,6 +4295,7 @@ function useCopilotzAdmin(options = {}) {
3975
4295
  formatUsageBucket,
3976
4296
  getUsageDimensionLabel,
3977
4297
  getUsageGroupLabel,
4298
+ getUsageMetricLabel,
3978
4299
  getUsageRange,
3979
4300
  getUsageTotalValue,
3980
4301
  mergeAdminConfig,