@carlonicora/nextjs-jsonapi 1.142.0 → 1.143.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/AbstractService-B0T7h8fX.d.mts +124 -0
  2. package/dist/AbstractService-Cew0PD0L.d.ts +124 -0
  3. package/dist/{AuthComponent-DdxCFgUZ.d.mts → AuthComponent-Cik96ElA.d.mts} +1 -1
  4. package/dist/{AuthComponent-Cd7lcYif.d.ts → AuthComponent-DTTKu3VK.d.ts} +1 -1
  5. package/dist/{BlockNoteEditor-35DDJH3M.mjs → BlockNoteEditor-34QM4UR5.mjs} +4 -4
  6. package/dist/{BlockNoteEditor-XXTBZ7DD.js → BlockNoteEditor-LMKWUOJG.js} +19 -19
  7. package/dist/{BlockNoteEditor-XXTBZ7DD.js.map → BlockNoteEditor-LMKWUOJG.js.map} +1 -1
  8. package/dist/billing/index.js +357 -357
  9. package/dist/billing/index.mjs +3 -3
  10. package/dist/{chunk-KZHHKPQZ.mjs → chunk-BIL2WQEV.mjs} +247 -1
  11. package/dist/chunk-BIL2WQEV.mjs.map +1 -0
  12. package/dist/{chunk-5SFFHIEE.js → chunk-DS4IBFWA.js} +252 -6
  13. package/dist/chunk-DS4IBFWA.js.map +1 -0
  14. package/dist/{chunk-7CR4YB7Y.js → chunk-F6PZZ4KI.js} +730 -730
  15. package/dist/{chunk-7CR4YB7Y.js.map → chunk-F6PZZ4KI.js.map} +1 -1
  16. package/dist/{chunk-ZV2GZKFR.mjs → chunk-HN5ED7OF.mjs} +2 -2
  17. package/dist/{chunk-FZ5SPJEU.mjs → chunk-UGSOBBZH.mjs} +4 -4
  18. package/dist/{chunk-C6UXEPNY.js → chunk-UXJAS6C5.js} +7 -7
  19. package/dist/{chunk-C6UXEPNY.js.map → chunk-UXJAS6C5.js.map} +1 -1
  20. package/dist/client/index.d.mts +3 -3
  21. package/dist/client/index.d.ts +3 -3
  22. package/dist/client/index.js +4 -4
  23. package/dist/client/index.mjs +3 -3
  24. package/dist/components/index.d.mts +3 -3
  25. package/dist/components/index.d.ts +3 -3
  26. package/dist/components/index.js +4 -4
  27. package/dist/components/index.mjs +3 -3
  28. package/dist/{config-BrkU_Ynz.d.ts → config-Cd5xTEfZ.d.ts} +1 -1
  29. package/dist/{config-snd2ToEB.d.mts → config-YyhFHxkU.d.mts} +1 -1
  30. package/dist/contexts/index.d.mts +1 -1
  31. package/dist/contexts/index.d.ts +1 -1
  32. package/dist/contexts/index.js +4 -4
  33. package/dist/contexts/index.mjs +3 -3
  34. package/dist/core/index.d.mts +13 -49
  35. package/dist/core/index.d.ts +13 -49
  36. package/dist/core/index.js +8 -2
  37. package/dist/core/index.js.map +1 -1
  38. package/dist/core/index.mjs +7 -1
  39. package/dist/features/help/index.d.mts +2 -2
  40. package/dist/features/help/index.d.ts +2 -2
  41. package/dist/features/help/index.js +37 -37
  42. package/dist/features/help/index.mjs +3 -3
  43. package/dist/features/tokenusage/index.css +29 -0
  44. package/dist/features/tokenusage/index.css.map +1 -0
  45. package/dist/features/tokenusage/index.d.mts +388 -0
  46. package/dist/features/tokenusage/index.d.ts +388 -0
  47. package/dist/features/tokenusage/index.js +1012 -0
  48. package/dist/features/tokenusage/index.js.map +1 -0
  49. package/dist/features/tokenusage/index.mjs +1012 -0
  50. package/dist/features/tokenusage/index.mjs.map +1 -0
  51. package/dist/index.d.mts +8 -6
  52. package/dist/index.d.ts +8 -6
  53. package/dist/index.js +9 -3
  54. package/dist/index.js.map +1 -1
  55. package/dist/index.mjs +8 -2
  56. package/dist/{s3.service-Dmfxpn-k.d.ts → s3.service-CpwA3sl_.d.ts} +2 -123
  57. package/dist/{s3.service-DYflLXCt.d.mts → s3.service-Dt6MfuHr.d.mts} +2 -123
  58. package/dist/server/index.d.mts +3 -2
  59. package/dist/server/index.d.ts +3 -2
  60. package/dist/server/index.js +3 -3
  61. package/dist/server/index.mjs +1 -1
  62. package/dist/tokenusage-admin.module-5wJ_tZTj.d.ts +61 -0
  63. package/dist/tokenusage-admin.module-DQVT4O4j.d.mts +61 -0
  64. package/dist/{types-CQSjy7et.d.mts → types-Bq_UA8Lg.d.mts} +1 -1
  65. package/dist/{types-DHOxe8rc.d.ts → types-bsm8Fp55.d.ts} +1 -1
  66. package/dist/{usePageUrlGenerator-tjq2mlDV.d.ts → usePageUrlGenerator-J_ECv-oQ.d.ts} +1 -1
  67. package/dist/{usePageUrlGenerator-uOnyJ6j2.d.mts → usePageUrlGenerator-uKBjAKqC.d.mts} +1 -1
  68. package/package.json +6 -1
  69. package/src/core/index.ts +5 -0
  70. package/src/core/registry/ModuleRegistry.ts +4 -0
  71. package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +189 -0
  72. package/src/features/tokenusage/components/TokenUsageAdminFilterBar.tsx +138 -0
  73. package/src/features/tokenusage/components/TokenUsageAdminTiles.tsx +177 -0
  74. package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +160 -0
  75. package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +99 -0
  76. package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +225 -0
  77. package/src/features/tokenusage/components/__tests__/TokenUsageAdminTiles.spec.tsx +42 -0
  78. package/src/features/tokenusage/components/__tests__/TokenUsageRankedBar.spec.tsx +41 -0
  79. package/src/features/tokenusage/components/__tests__/TokenUsageTimelineChart.spec.tsx +47 -0
  80. package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +252 -0
  81. package/src/features/tokenusage/contexts/__tests__/TokenUsageAdminContext.spec.tsx +80 -0
  82. package/src/features/tokenusage/data/TokenUsageAdminService.ts +54 -0
  83. package/src/features/tokenusage/data/__tests__/tokenusage-admin-models.spec.ts +147 -0
  84. package/src/features/tokenusage/data/index.ts +8 -0
  85. package/src/features/tokenusage/data/tokenusage-admin-breakdown.interface.ts +18 -0
  86. package/src/features/tokenusage/data/tokenusage-admin-breakdown.ts +115 -0
  87. package/src/features/tokenusage/data/tokenusage-admin-summary.interface.ts +14 -0
  88. package/src/features/tokenusage/data/tokenusage-admin-summary.ts +90 -0
  89. package/src/features/tokenusage/data/tokenusage-admin-timeline.interface.ts +13 -0
  90. package/src/features/tokenusage/data/tokenusage-admin-timeline.ts +94 -0
  91. package/src/features/tokenusage/data/tokenusage-admin.types.ts +56 -0
  92. package/src/features/tokenusage/i18n-keys.ts +65 -0
  93. package/src/features/tokenusage/index.ts +30 -0
  94. package/src/features/tokenusage/lib/metrics.ts +69 -0
  95. package/src/features/tokenusage/lib/operation-label.ts +41 -0
  96. package/src/features/tokenusage/lib/palette.ts +197 -0
  97. package/src/features/tokenusage/tokenusage-admin.module.ts +22 -0
  98. package/dist/chunk-5SFFHIEE.js.map +0 -1
  99. package/dist/chunk-KZHHKPQZ.mjs.map +0 -1
  100. /package/dist/{BlockNoteEditor-35DDJH3M.mjs.map → BlockNoteEditor-34QM4UR5.mjs.map} +0 -0
  101. /package/dist/{chunk-ZV2GZKFR.mjs.map → chunk-HN5ED7OF.mjs.map} +0 -0
  102. /package/dist/{chunk-FZ5SPJEU.mjs.map → chunk-UGSOBBZH.mjs.map} +0 -0
@@ -0,0 +1,54 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
2
+ import { TokenUsageAdminBreakdownInterface } from "./tokenusage-admin-breakdown.interface";
3
+ import { TokenUsageAdminSummaryInterface } from "./tokenusage-admin-summary.interface";
4
+ import { TokenUsageAdminTimelineInterface } from "./tokenusage-admin-timeline.interface";
5
+ import { Dimension, Granularity, Scope, StackBy, TokenUsageAdminFilters } from "./tokenusage-admin.types";
6
+
7
+ function withFilters(endpoint: EndpointCreator, filters: TokenUsageAdminFilters): EndpointCreator {
8
+ endpoint.addAdditionalParam("from", filters.from);
9
+ endpoint.addAdditionalParam("to", filters.to);
10
+ if (filters.companyId) endpoint.addAdditionalParam("companyId", filters.companyId);
11
+ return endpoint;
12
+ }
13
+
14
+ export class TokenUsageAdminService extends AbstractService {
15
+ /** Six rows: {customer, platform, total} × {current, previous}. */
16
+ static async getSummary(filters: TokenUsageAdminFilters): Promise<TokenUsageAdminSummaryInterface[]> {
17
+ const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageAdminSummary }), filters);
18
+
19
+ return this.callApi<TokenUsageAdminSummaryInterface[]>({
20
+ type: Modules.TokenUsageAdminSummary,
21
+ method: HttpMethod.GET,
22
+ endpoint: endpoint.generate(),
23
+ });
24
+ }
25
+
26
+ static async getTimeline(
27
+ params: TokenUsageAdminFilters & { granularity: Granularity; stackBy: StackBy },
28
+ ): Promise<TokenUsageAdminTimelineInterface[]> {
29
+ const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageAdminTimeline }), params);
30
+ endpoint.addAdditionalParam("granularity", params.granularity);
31
+ endpoint.addAdditionalParam("stackBy", params.stackBy);
32
+
33
+ return this.callApi<TokenUsageAdminTimelineInterface[]>({
34
+ type: Modules.TokenUsageAdminTimeline,
35
+ method: HttpMethod.GET,
36
+ endpoint: endpoint.generate(),
37
+ });
38
+ }
39
+
40
+ static async getBreakdown(
41
+ params: TokenUsageAdminFilters & { dimension: Dimension; scope: Scope; limit?: number },
42
+ ): Promise<TokenUsageAdminBreakdownInterface[]> {
43
+ const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageAdminBreakdown }), params);
44
+ endpoint.addAdditionalParam("dimension", params.dimension);
45
+ endpoint.addAdditionalParam("scope", params.scope);
46
+ if (params.limit !== undefined) endpoint.addAdditionalParam("limit", String(params.limit));
47
+
48
+ return this.callApi<TokenUsageAdminBreakdownInterface[]>({
49
+ type: Modules.TokenUsageAdminBreakdown,
50
+ method: HttpMethod.GET,
51
+ endpoint: endpoint.generate(),
52
+ });
53
+ }
54
+ }
@@ -0,0 +1,147 @@
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { ModuleFactory, ModuleWithPermissions } from "../../../../permissions/types";
3
+ import { ModuleRegistry } from "../../../../core/registry/ModuleRegistry";
4
+ import {
5
+ TokenUsageAdminBreakdownModule,
6
+ TokenUsageAdminSummaryModule,
7
+ TokenUsageAdminTimelineModule,
8
+ } from "../../tokenusage-admin.module";
9
+ import { TokenUsageAdminBreakdown } from "../tokenusage-admin-breakdown";
10
+ import { TokenUsageAdminSummary } from "../tokenusage-admin-summary";
11
+ import { TokenUsageAdminTimeline } from "../tokenusage-admin-timeline";
12
+
13
+ // createJsonApi reads Modules.X.name (resolved lazily via ModuleRegistry), so
14
+ // the modules must be registered before a model serialises — mirrors the
15
+ // HowTo.spec / AssistantMessage.spec setup convention in this package.
16
+ const moduleFactory: ModuleFactory = (params) =>
17
+ ({
18
+ pageUrl: params.pageUrl,
19
+ name: params.name,
20
+ model: params.model,
21
+ }) as ModuleWithPermissions;
22
+
23
+ beforeAll(() => {
24
+ ModuleRegistry.register("TokenUsageAdminSummary", TokenUsageAdminSummaryModule(moduleFactory));
25
+ ModuleRegistry.register("TokenUsageAdminTimeline", TokenUsageAdminTimelineModule(moduleFactory));
26
+ ModuleRegistry.register("TokenUsageAdminBreakdown", TokenUsageAdminBreakdownModule(moduleFactory));
27
+ });
28
+
29
+ const hydrate = (attributes: Record<string, unknown>) =>
30
+ ({ jsonApi: { id: "x", type: "t", attributes }, included: [] }) as any;
31
+
32
+ describe("token usage admin models", () => {
33
+ it("rehydrates every summary attribute", () => {
34
+ const m = new TokenUsageAdminSummary().rehydrate(
35
+ hydrate({
36
+ scope: "customer",
37
+ window: "current",
38
+ cost: 9.46,
39
+ credits: 3394,
40
+ tokensIn: 49244143,
41
+ tokensOut: 12,
42
+ cached: 75752,
43
+ calls: 2956,
44
+ }),
45
+ );
46
+ expect(m.scope).toBe("customer");
47
+ expect(m.window).toBe("current");
48
+ expect(m.cost).toBe(9.46);
49
+ expect(m.credits).toBe(3394);
50
+ expect(m.tokensIn).toBe(49244143);
51
+ expect(m.tokensOut).toBe(12);
52
+ expect(m.cached).toBe(75752);
53
+ expect(m.calls).toBe(2956);
54
+ });
55
+
56
+ it("parses the timeline bucket into a Date, not the wire string", () => {
57
+ const m = new TokenUsageAdminTimeline().rehydrate(
58
+ hydrate({
59
+ bucket: "2026-08-01",
60
+ series: "customer",
61
+ cost: 1,
62
+ credits: 1,
63
+ tokensIn: 1,
64
+ tokensOut: 1,
65
+ cached: 0,
66
+ calls: 1,
67
+ }),
68
+ );
69
+ expect(m.bucket).toBeInstanceOf(Date);
70
+ expect(m.bucket.toISOString().slice(0, 10)).toBe("2026-08-01");
71
+ expect(m.series).toBe("customer");
72
+ });
73
+
74
+ it("leaves company-only breakdown fields undefined on other dimensions", () => {
75
+ const m = new TokenUsageAdminBreakdown().rehydrate(
76
+ hydrate({
77
+ label: "m.rossi",
78
+ sublabel: "Studio Rossi",
79
+ cost: 3.74,
80
+ credits: 900,
81
+ tokensIn: 1,
82
+ tokensOut: 1,
83
+ cached: 0,
84
+ calls: 4,
85
+ }),
86
+ );
87
+ expect(m.label).toBe("m.rossi");
88
+ expect(m.sublabel).toBe("Studio Rossi");
89
+ expect(m.activeUsers).toBeUndefined();
90
+ expect(m.monthlyCredits).toBeUndefined();
91
+ expect(m.availableMonthlyCredits).toBeUndefined();
92
+ });
93
+
94
+ it("serialises a summary back to a JSON:API resource object", () => {
95
+ const doc = new TokenUsageAdminSummary().createJsonApi({
96
+ id: "customer|current",
97
+ scope: "customer",
98
+ window: "current",
99
+ cost: 9.46,
100
+ credits: 3394,
101
+ tokensIn: 1,
102
+ tokensOut: 2,
103
+ cached: 3,
104
+ calls: 4,
105
+ });
106
+
107
+ expect(doc.data.type).toBe("tokenusages/administration/summary");
108
+ expect(doc.data.id).toBe("customer|current");
109
+ expect(doc.data.attributes).toMatchObject({ scope: "customer", window: "current", cost: 9.46, calls: 4 });
110
+ });
111
+
112
+ it("emits the timeline bucket as YYYY-MM-DD via formatLocalDate, never a raw Date", () => {
113
+ const doc = new TokenUsageAdminTimeline().createJsonApi({
114
+ id: "2026-08-01|customer",
115
+ bucket: new Date(2026, 7, 1),
116
+ series: "customer",
117
+ cost: 1,
118
+ credits: 1,
119
+ tokensIn: 1,
120
+ tokensOut: 1,
121
+ cached: 0,
122
+ calls: 1,
123
+ });
124
+
125
+ expect(doc.data.attributes.bucket).toBe("2026-08-01");
126
+ expect(typeof doc.data.attributes.bucket).toBe("string");
127
+ // A raw Date would survive JSON.stringify as an ISO instant and UTC-shift.
128
+ expect(JSON.parse(JSON.stringify(doc)).data.attributes.bucket).toBe("2026-08-01");
129
+ });
130
+
131
+ it("omits absent optional breakdown attributes rather than emitting undefined", () => {
132
+ const doc = new TokenUsageAdminBreakdown().createJsonApi({
133
+ id: "u1",
134
+ label: "m.rossi",
135
+ cost: 1,
136
+ credits: 1,
137
+ tokensIn: 1,
138
+ tokensOut: 1,
139
+ cached: 0,
140
+ calls: 1,
141
+ });
142
+
143
+ expect(doc.data.attributes.label).toBe("m.rossi");
144
+ expect("sublabel" in doc.data.attributes).toBe(false);
145
+ expect("activeUsers" in doc.data.attributes).toBe(false);
146
+ });
147
+ });
@@ -0,0 +1,8 @@
1
+ export * from "./tokenusage-admin.types";
2
+ export { TokenUsageAdminSummary } from "./tokenusage-admin-summary";
3
+ export type { TokenUsageAdminSummaryInterface } from "./tokenusage-admin-summary.interface";
4
+ export { TokenUsageAdminTimeline } from "./tokenusage-admin-timeline";
5
+ export type { TokenUsageAdminTimelineInterface } from "./tokenusage-admin-timeline.interface";
6
+ export { TokenUsageAdminBreakdown } from "./tokenusage-admin-breakdown";
7
+ export type { TokenUsageAdminBreakdownInterface } from "./tokenusage-admin-breakdown.interface";
8
+ export { TokenUsageAdminService } from "./TokenUsageAdminService";
@@ -0,0 +1,18 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface TokenUsageAdminBreakdownInterface extends ApiDataInterface {
4
+ get label(): string;
5
+ get sublabel(): string | undefined;
6
+ get cost(): number;
7
+ get credits(): number;
8
+ get tokensIn(): number;
9
+ get tokensOut(): number;
10
+ get cached(): number;
11
+ get calls(): number;
12
+ /** Company dimension only. */
13
+ get activeUsers(): number | undefined;
14
+ /** Company dimension only. */
15
+ get monthlyCredits(): number | undefined;
16
+ /** Company dimension only. */
17
+ get availableMonthlyCredits(): number | undefined;
18
+ }
@@ -0,0 +1,115 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { TokenUsageAdminBreakdownInterface } from "./tokenusage-admin-breakdown.interface";
3
+ import { TokenUsageAdminBreakdownInput } from "./tokenusage-admin.types";
4
+
5
+ /**
6
+ * One ranked row of a breakdown. The same shape serves all three dimensions —
7
+ * company, user and operation — which is why the three "by-X" views collapse
8
+ * into a single `breakdown?dimension=` call.
9
+ *
10
+ * `activeUsers` / `monthlyCredits` / `availableMonthlyCredits` are populated only
11
+ * for `dimension=company`; they are absent on user and operation rows.
12
+ */
13
+ export class TokenUsageAdminBreakdown extends AbstractApiData implements TokenUsageAdminBreakdownInterface {
14
+ private _label: string = "";
15
+ private _sublabel?: string;
16
+ private _cost: number = 0;
17
+ private _credits: number = 0;
18
+ private _tokensIn: number = 0;
19
+ private _tokensOut: number = 0;
20
+ private _cached: number = 0;
21
+ private _calls: number = 0;
22
+ private _activeUsers?: number;
23
+ private _monthlyCredits?: number;
24
+ private _availableMonthlyCredits?: number;
25
+
26
+ get label(): string {
27
+ return this._label;
28
+ }
29
+
30
+ get sublabel(): string | undefined {
31
+ return this._sublabel;
32
+ }
33
+
34
+ get cost(): number {
35
+ return this._cost;
36
+ }
37
+
38
+ get credits(): number {
39
+ return this._credits;
40
+ }
41
+
42
+ get tokensIn(): number {
43
+ return this._tokensIn;
44
+ }
45
+
46
+ get tokensOut(): number {
47
+ return this._tokensOut;
48
+ }
49
+
50
+ get cached(): number {
51
+ return this._cached;
52
+ }
53
+
54
+ get calls(): number {
55
+ return this._calls;
56
+ }
57
+
58
+ get activeUsers(): number | undefined {
59
+ return this._activeUsers;
60
+ }
61
+
62
+ get monthlyCredits(): number | undefined {
63
+ return this._monthlyCredits;
64
+ }
65
+
66
+ get availableMonthlyCredits(): number | undefined {
67
+ return this._availableMonthlyCredits;
68
+ }
69
+
70
+ rehydrate(data: JsonApiHydratedDataInterface): this {
71
+ super.rehydrate(data);
72
+
73
+ const attrs = data.jsonApi.attributes;
74
+ this._label = attrs.label ?? "";
75
+ this._sublabel = attrs.sublabel ?? undefined;
76
+ this._cost = attrs.cost ?? 0;
77
+ this._credits = attrs.credits ?? 0;
78
+ this._tokensIn = attrs.tokensIn ?? 0;
79
+ this._tokensOut = attrs.tokensOut ?? 0;
80
+ this._cached = attrs.cached ?? 0;
81
+ this._calls = attrs.calls ?? 0;
82
+ this._activeUsers = attrs.activeUsers ?? undefined;
83
+ this._monthlyCredits = attrs.monthlyCredits ?? undefined;
84
+ this._availableMonthlyCredits = attrs.availableMonthlyCredits ?? undefined;
85
+
86
+ return this;
87
+ }
88
+
89
+ createJsonApi(data: TokenUsageAdminBreakdownInput) {
90
+ const response: any = {
91
+ data: {
92
+ type: Modules.TokenUsageAdminBreakdown.name,
93
+ id: data.id,
94
+ attributes: {},
95
+ relationships: {},
96
+ },
97
+ included: [],
98
+ };
99
+
100
+ if (data.label !== undefined) response.data.attributes.label = data.label;
101
+ if (data.sublabel !== undefined) response.data.attributes.sublabel = data.sublabel;
102
+ if (data.cost !== undefined) response.data.attributes.cost = data.cost;
103
+ if (data.credits !== undefined) response.data.attributes.credits = data.credits;
104
+ if (data.tokensIn !== undefined) response.data.attributes.tokensIn = data.tokensIn;
105
+ if (data.tokensOut !== undefined) response.data.attributes.tokensOut = data.tokensOut;
106
+ if (data.cached !== undefined) response.data.attributes.cached = data.cached;
107
+ if (data.calls !== undefined) response.data.attributes.calls = data.calls;
108
+ if (data.activeUsers !== undefined) response.data.attributes.activeUsers = data.activeUsers;
109
+ if (data.monthlyCredits !== undefined) response.data.attributes.monthlyCredits = data.monthlyCredits;
110
+ if (data.availableMonthlyCredits !== undefined)
111
+ response.data.attributes.availableMonthlyCredits = data.availableMonthlyCredits;
112
+
113
+ return response;
114
+ }
115
+ }
@@ -0,0 +1,14 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface TokenUsageAdminSummaryInterface extends ApiDataInterface {
4
+ /** "customer" | "platform" | "total" */
5
+ get scope(): string;
6
+ /** "current" | "previous" */
7
+ get window(): string;
8
+ get cost(): number;
9
+ get credits(): number;
10
+ get tokensIn(): number;
11
+ get tokensOut(): number;
12
+ get cached(): number;
13
+ get calls(): number;
14
+ }
@@ -0,0 +1,90 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { TokenUsageAdminSummaryInterface } from "./tokenusage-admin-summary.interface";
3
+ import { TokenUsageAdminSummaryInput } from "./tokenusage-admin.types";
4
+
5
+ /**
6
+ * One row of the administrative summary: a cost centre observed over one time
7
+ * window. Six rows are returned per request — scope crossed with window — so the
8
+ * KPI tiles can render a value and its delta without a second call.
9
+ */
10
+ export class TokenUsageAdminSummary extends AbstractApiData implements TokenUsageAdminSummaryInterface {
11
+ private _scope: string = "";
12
+ private _window: string = "";
13
+ private _cost: number = 0;
14
+ private _credits: number = 0;
15
+ private _tokensIn: number = 0;
16
+ private _tokensOut: number = 0;
17
+ private _cached: number = 0;
18
+ private _calls: number = 0;
19
+
20
+ get scope(): string {
21
+ return this._scope;
22
+ }
23
+
24
+ get window(): string {
25
+ return this._window;
26
+ }
27
+
28
+ get cost(): number {
29
+ return this._cost;
30
+ }
31
+
32
+ get credits(): number {
33
+ return this._credits;
34
+ }
35
+
36
+ get tokensIn(): number {
37
+ return this._tokensIn;
38
+ }
39
+
40
+ get tokensOut(): number {
41
+ return this._tokensOut;
42
+ }
43
+
44
+ get cached(): number {
45
+ return this._cached;
46
+ }
47
+
48
+ get calls(): number {
49
+ return this._calls;
50
+ }
51
+
52
+ rehydrate(data: JsonApiHydratedDataInterface): this {
53
+ super.rehydrate(data);
54
+
55
+ const attrs = data.jsonApi.attributes;
56
+ this._scope = attrs.scope ?? "";
57
+ this._window = attrs.window ?? "";
58
+ this._cost = attrs.cost ?? 0;
59
+ this._credits = attrs.credits ?? 0;
60
+ this._tokensIn = attrs.tokensIn ?? 0;
61
+ this._tokensOut = attrs.tokensOut ?? 0;
62
+ this._cached = attrs.cached ?? 0;
63
+ this._calls = attrs.calls ?? 0;
64
+
65
+ return this;
66
+ }
67
+
68
+ createJsonApi(data: TokenUsageAdminSummaryInput) {
69
+ const response: any = {
70
+ data: {
71
+ type: Modules.TokenUsageAdminSummary.name,
72
+ id: data.id,
73
+ attributes: {},
74
+ relationships: {},
75
+ },
76
+ included: [],
77
+ };
78
+
79
+ if (data.scope !== undefined) response.data.attributes.scope = data.scope;
80
+ if (data.window !== undefined) response.data.attributes.window = data.window;
81
+ if (data.cost !== undefined) response.data.attributes.cost = data.cost;
82
+ if (data.credits !== undefined) response.data.attributes.credits = data.credits;
83
+ if (data.tokensIn !== undefined) response.data.attributes.tokensIn = data.tokensIn;
84
+ if (data.tokensOut !== undefined) response.data.attributes.tokensOut = data.tokensOut;
85
+ if (data.cached !== undefined) response.data.attributes.cached = data.cached;
86
+ if (data.calls !== undefined) response.data.attributes.calls = data.calls;
87
+
88
+ return response;
89
+ }
90
+ }
@@ -0,0 +1,13 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface TokenUsageAdminTimelineInterface extends ApiDataInterface {
4
+ /** Backend declares this `type: "date"`, so it is a Date in memory. */
5
+ get bucket(): Date;
6
+ get series(): string;
7
+ get cost(): number;
8
+ get credits(): number;
9
+ get tokensIn(): number;
10
+ get tokensOut(): number;
11
+ get cached(): number;
12
+ get calls(): number;
13
+ }
@@ -0,0 +1,94 @@
1
+ import { AbstractApiData, formatLocalDate, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { TokenUsageAdminTimelineInterface } from "./tokenusage-admin-timeline.interface";
3
+ import { TokenUsageAdminTimelineInput } from "./tokenusage-admin.types";
4
+
5
+ /**
6
+ * One (bucket, series) cell of the usage-over-time chart. Rows arrive flat; the
7
+ * chart pivots them into stacked columns.
8
+ */
9
+ export class TokenUsageAdminTimeline extends AbstractApiData implements TokenUsageAdminTimelineInterface {
10
+ private _bucket: Date = new Date(0);
11
+ private _series: string = "";
12
+ private _cost: number = 0;
13
+ private _credits: number = 0;
14
+ private _tokensIn: number = 0;
15
+ private _tokensOut: number = 0;
16
+ private _cached: number = 0;
17
+ private _calls: number = 0;
18
+
19
+ get bucket(): Date {
20
+ return this._bucket;
21
+ }
22
+
23
+ get series(): string {
24
+ return this._series;
25
+ }
26
+
27
+ get cost(): number {
28
+ return this._cost;
29
+ }
30
+
31
+ get credits(): number {
32
+ return this._credits;
33
+ }
34
+
35
+ get tokensIn(): number {
36
+ return this._tokensIn;
37
+ }
38
+
39
+ get tokensOut(): number {
40
+ return this._tokensOut;
41
+ }
42
+
43
+ get cached(): number {
44
+ return this._cached;
45
+ }
46
+
47
+ get calls(): number {
48
+ return this._calls;
49
+ }
50
+
51
+ rehydrate(data: JsonApiHydratedDataInterface): this {
52
+ super.rehydrate(data);
53
+
54
+ const attrs = data.jsonApi.attributes;
55
+ // Wire format is "YYYY-MM-DD"; the interface promises a Date.
56
+ this._bucket = attrs.bucket ? new Date(attrs.bucket) : new Date(0);
57
+ this._series = attrs.series ?? "";
58
+ this._cost = attrs.cost ?? 0;
59
+ this._credits = attrs.credits ?? 0;
60
+ this._tokensIn = attrs.tokensIn ?? 0;
61
+ this._tokensOut = attrs.tokensOut ?? 0;
62
+ this._cached = attrs.cached ?? 0;
63
+ this._calls = attrs.calls ?? 0;
64
+
65
+ return this;
66
+ }
67
+
68
+ createJsonApi(data: TokenUsageAdminTimelineInput) {
69
+ const response: any = {
70
+ data: {
71
+ type: Modules.TokenUsageAdminTimeline.name,
72
+ id: data.id,
73
+ attributes: {},
74
+ relationships: {},
75
+ },
76
+ included: [],
77
+ };
78
+
79
+ // `bucket` is type: "date" on the backend descriptor. formatLocalDate is
80
+ // mandatory — passing the raw Date lets JSON.stringify call .toISOString(),
81
+ // which UTC-shifts and can move the bucket a day. Imported from the package
82
+ // core, never reimplemented inline.
83
+ if (data.bucket !== undefined) response.data.attributes.bucket = formatLocalDate(data.bucket);
84
+ if (data.series !== undefined) response.data.attributes.series = data.series;
85
+ if (data.cost !== undefined) response.data.attributes.cost = data.cost;
86
+ if (data.credits !== undefined) response.data.attributes.credits = data.credits;
87
+ if (data.tokensIn !== undefined) response.data.attributes.tokensIn = data.tokensIn;
88
+ if (data.tokensOut !== undefined) response.data.attributes.tokensOut = data.tokensOut;
89
+ if (data.cached !== undefined) response.data.attributes.cached = data.cached;
90
+ if (data.calls !== undefined) response.data.attributes.calls = data.calls;
91
+
92
+ return response;
93
+ }
94
+ }
@@ -0,0 +1,56 @@
1
+ export type Granularity = "day" | "week" | "month";
2
+ export type StackBy = "scope" | "type" | "company";
3
+ export type Dimension = "company" | "user" | "operation";
4
+ export type Scope = "customer" | "platform";
5
+ export type Metric = "cost" | "credits" | "tokens";
6
+
7
+ export type TokenUsageAdminFilters = {
8
+ /** ISO 8601 instant. */
9
+ from: string;
10
+ /** ISO 8601 instant. */
11
+ to: string;
12
+ companyId?: string;
13
+ };
14
+
15
+ // Input types for createJsonApi(). `id` is always required — JSON:API resource
16
+ // objects are identified.
17
+
18
+ export type TokenUsageAdminSummaryInput = {
19
+ id: string;
20
+ scope: string;
21
+ window: string;
22
+ cost: number;
23
+ credits: number;
24
+ tokensIn: number;
25
+ tokensOut: number;
26
+ cached: number;
27
+ calls: number;
28
+ };
29
+
30
+ export type TokenUsageAdminTimelineInput = {
31
+ id: string;
32
+ /** Backend field type is "date" — emitted via formatLocalDate, never raw. */
33
+ bucket: Date;
34
+ series: string;
35
+ cost: number;
36
+ credits: number;
37
+ tokensIn: number;
38
+ tokensOut: number;
39
+ cached: number;
40
+ calls: number;
41
+ };
42
+
43
+ export type TokenUsageAdminBreakdownInput = {
44
+ id: string;
45
+ label: string;
46
+ sublabel?: string;
47
+ cost: number;
48
+ credits: number;
49
+ tokensIn: number;
50
+ tokensOut: number;
51
+ cached: number;
52
+ calls: number;
53
+ activeUsers?: number;
54
+ monthlyCredits?: number;
55
+ availableMonthlyCredits?: number;
56
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The fixed namespace of i18n keys the administrative token-usage feature reads
3
+ * via useTranslations/getTranslations. Consuming apps must define each entry in
4
+ * their messages/<locale>.json — this list is the contract between the package
5
+ * and the app.
6
+ *
7
+ * One key is NOT listed because it is resolved from data rather than from a
8
+ * fixed string: the timeline chart labels each series through
9
+ * `token_usage.types.<camelCase>` and falls back to the raw series key when the
10
+ * app has no entry, so that namespace stays the app's own vocabulary.
11
+ */
12
+ export const TOKEN_USAGE_ADMIN_I18N_KEYS = [
13
+ // Page + KPI tiles
14
+ "token_usage.admin.title",
15
+ "token_usage.admin.customer_spend",
16
+ "token_usage.admin.platform_spend",
17
+ "token_usage.admin.total_cost",
18
+ "token_usage.admin.avg_per_call",
19
+ "token_usage.admin.cache_hit",
20
+ "token_usage.admin.vs_previous",
21
+
22
+ // Panel titles
23
+ "token_usage.admin.usage_over_time",
24
+ "token_usage.admin.by_company",
25
+ "token_usage.admin.by_user",
26
+ "token_usage.admin.customer_by_operation",
27
+ "token_usage.admin.platform_by_operation",
28
+ "token_usage.admin.detail",
29
+
30
+ // Shared states
31
+ "token_usage.admin.other",
32
+ "token_usage.admin.no_data",
33
+
34
+ // Filter bar
35
+ "token_usage.admin.all_companies",
36
+ "token_usage.admin.granularity.label",
37
+ "token_usage.admin.granularity.day",
38
+ "token_usage.admin.granularity.week",
39
+ "token_usage.admin.granularity.month",
40
+ "token_usage.admin.metric.label",
41
+ "token_usage.admin.metric.cost",
42
+ "token_usage.admin.metric.credits",
43
+ "token_usage.admin.metric.tokens",
44
+
45
+ // Timeline stacking control
46
+ "token_usage.admin.stack_by",
47
+ "token_usage.admin.stack.scope",
48
+ "token_usage.admin.stack.type",
49
+ "token_usage.admin.stack.company",
50
+
51
+ // Breakdown table columns
52
+ "token_usage.admin.columns.label",
53
+ "token_usage.admin.columns.sublabel",
54
+ "token_usage.admin.columns.calls",
55
+ "token_usage.admin.columns.tokens_in",
56
+ "token_usage.admin.columns.tokens_out",
57
+ "token_usage.admin.columns.cost",
58
+ "token_usage.admin.columns.credits",
59
+ "token_usage.admin.columns.share",
60
+ "token_usage.admin.columns.active_users",
61
+
62
+ // Timeline chart
63
+ "token_usage.series.other",
64
+ "token_usage.timeline.empty",
65
+ ] as const;