@littlebearapps/platform-admin-sdk 2.1.0 → 2.3.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 (122) hide show
  1. package/README.md +2 -5
  2. package/dist/check-upgrade.d.ts +29 -0
  3. package/dist/check-upgrade.js +97 -0
  4. package/dist/index.js +59 -4
  5. package/dist/manifest.d.ts +2 -0
  6. package/dist/scaffold.js +5 -1
  7. package/dist/templates.d.ts +6 -1
  8. package/dist/templates.js +141 -3
  9. package/dist/upgrade.d.ts +1 -0
  10. package/dist/upgrade.js +21 -2
  11. package/package.json +1 -1
  12. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  13. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  14. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  15. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  16. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  17. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  18. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  19. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  20. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  21. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  22. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  23. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  24. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  25. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  26. package/templates/full/dashboard/src/pages/map.astro +561 -0
  27. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  28. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  29. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  30. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  31. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  32. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  33. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  34. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  35. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  36. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  37. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  38. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  39. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  40. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  41. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  42. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  43. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  44. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  45. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  46. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  47. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  48. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  49. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  50. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  51. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  52. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  53. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  54. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  55. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  56. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  57. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  58. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  59. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  60. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  61. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  62. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  63. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  64. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  65. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  66. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  67. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  68. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  69. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  70. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  71. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  72. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  73. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  74. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  75. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  76. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  77. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  78. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  79. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  80. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  81. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  82. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  83. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  84. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  85. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  86. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  87. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  88. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  89. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  90. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  91. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  92. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  93. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  94. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  95. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  96. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  97. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  98. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  99. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  100. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  101. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  102. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  103. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  104. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  105. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  106. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  107. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  108. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  109. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  110. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  111. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  112. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  113. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  114. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  115. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  116. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  117. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  118. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  119. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  120. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  121. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  122. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Usage Dashboard Color System
3
+ *
4
+ * Consistent, accessible color palette for resource types.
5
+ * Part of task-18: Usage Dashboard Interactive Chart & Table Enhancement
6
+ *
7
+ * Each resource type has light and dark mode variants optimised for:
8
+ * - WCAG 2.1 AA contrast on backgrounds
9
+ * - Visual distinction between resource types
10
+ * - Consistency with the existing design token system
11
+ */
12
+
13
+ /**
14
+ * Resource type identifiers matching DailyCostBreakdown keys
15
+ */
16
+ export type ResourceType =
17
+ | 'workers'
18
+ | 'd1'
19
+ | 'kv'
20
+ | 'r2'
21
+ | 'vectorize'
22
+ | 'aiGateway'
23
+ | 'durableObjects'
24
+ | 'workersAI'
25
+ | 'queues';
26
+
27
+ /**
28
+ * Theme-aware color definition
29
+ */
30
+ export interface ResourceColor {
31
+ light: {
32
+ fill: string;
33
+ fillHover: string;
34
+ text: string;
35
+ };
36
+ dark: {
37
+ fill: string;
38
+ fillHover: string;
39
+ text: string;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Resource display metadata
45
+ */
46
+ export interface ResourceInfo {
47
+ key: ResourceType;
48
+ label: string;
49
+ shortLabel: string;
50
+ color: ResourceColor;
51
+ }
52
+
53
+ /**
54
+ * Color palette for each resource type
55
+ *
56
+ * Colours chosen for:
57
+ * - High contrast in both light and dark modes
58
+ * - Distinct hues for easy differentiation
59
+ * - Consistent saturation and brightness levels
60
+ */
61
+ export const RESOURCE_COLORS: Record<ResourceType, ResourceColor> = {
62
+ workers: {
63
+ light: {
64
+ fill: '#f59e0b', // amber-500
65
+ fillHover: '#d97706', // amber-600
66
+ text: '#92400e', // amber-800
67
+ },
68
+ dark: {
69
+ fill: '#fbbf24', // amber-400
70
+ fillHover: '#f59e0b', // amber-500
71
+ text: '#fcd34d', // amber-300
72
+ },
73
+ },
74
+ d1: {
75
+ light: {
76
+ fill: '#3b82f6', // blue-500
77
+ fillHover: '#2563eb', // blue-600
78
+ text: '#1e40af', // blue-800
79
+ },
80
+ dark: {
81
+ fill: '#60a5fa', // blue-400
82
+ fillHover: '#3b82f6', // blue-500
83
+ text: '#93c5fd', // blue-300
84
+ },
85
+ },
86
+ kv: {
87
+ light: {
88
+ fill: '#10b981', // emerald-500
89
+ fillHover: '#059669', // emerald-600
90
+ text: '#065f46', // emerald-800
91
+ },
92
+ dark: {
93
+ fill: '#34d399', // emerald-400
94
+ fillHover: '#10b981', // emerald-500
95
+ text: '#6ee7b7', // emerald-300
96
+ },
97
+ },
98
+ r2: {
99
+ light: {
100
+ fill: '#8b5cf6', // violet-500
101
+ fillHover: '#7c3aed', // violet-600
102
+ text: '#5b21b6', // violet-800
103
+ },
104
+ dark: {
105
+ fill: '#a78bfa', // violet-400
106
+ fillHover: '#8b5cf6', // violet-500
107
+ text: '#c4b5fd', // violet-300
108
+ },
109
+ },
110
+ vectorize: {
111
+ light: {
112
+ fill: '#ec4899', // pink-500
113
+ fillHover: '#db2777', // pink-600
114
+ text: '#9d174d', // pink-800
115
+ },
116
+ dark: {
117
+ fill: '#f472b6', // pink-400
118
+ fillHover: '#ec4899', // pink-500
119
+ text: '#f9a8d4', // pink-300
120
+ },
121
+ },
122
+ aiGateway: {
123
+ light: {
124
+ fill: '#06b6d4', // cyan-500
125
+ fillHover: '#0891b2', // cyan-600
126
+ text: '#155e75', // cyan-800
127
+ },
128
+ dark: {
129
+ fill: '#22d3ee', // cyan-400
130
+ fillHover: '#06b6d4', // cyan-500
131
+ text: '#67e8f9', // cyan-300
132
+ },
133
+ },
134
+ durableObjects: {
135
+ light: {
136
+ fill: '#f97316', // orange-500
137
+ fillHover: '#ea580c', // orange-600
138
+ text: '#9a3412', // orange-800
139
+ },
140
+ dark: {
141
+ fill: '#fb923c', // orange-400
142
+ fillHover: '#f97316', // orange-500
143
+ text: '#fdba74', // orange-300
144
+ },
145
+ },
146
+ workersAI: {
147
+ light: {
148
+ fill: '#6366f1', // indigo-500
149
+ fillHover: '#4f46e5', // indigo-600
150
+ text: '#3730a3', // indigo-800
151
+ },
152
+ dark: {
153
+ fill: '#818cf8', // indigo-400
154
+ fillHover: '#6366f1', // indigo-500
155
+ text: '#a5b4fc', // indigo-300
156
+ },
157
+ },
158
+ queues: {
159
+ light: {
160
+ fill: '#14b8a6', // teal-500
161
+ fillHover: '#0d9488', // teal-600
162
+ text: '#115e59', // teal-800
163
+ },
164
+ dark: {
165
+ fill: '#2dd4bf', // teal-400
166
+ fillHover: '#14b8a6', // teal-500
167
+ text: '#5eead4', // teal-300
168
+ },
169
+ },
170
+ };
171
+
172
+ /**
173
+ * Resource display information
174
+ */
175
+ export const RESOURCE_INFO: ResourceInfo[] = [
176
+ {
177
+ key: 'workers',
178
+ label: 'Workers',
179
+ shortLabel: 'WK',
180
+ color: RESOURCE_COLORS.workers,
181
+ },
182
+ {
183
+ key: 'd1',
184
+ label: 'D1 Database',
185
+ shortLabel: 'D1',
186
+ color: RESOURCE_COLORS.d1,
187
+ },
188
+ {
189
+ key: 'kv',
190
+ label: 'KV Storage',
191
+ shortLabel: 'KV',
192
+ color: RESOURCE_COLORS.kv,
193
+ },
194
+ {
195
+ key: 'r2',
196
+ label: 'R2 Storage',
197
+ shortLabel: 'R2',
198
+ color: RESOURCE_COLORS.r2,
199
+ },
200
+ {
201
+ key: 'vectorize',
202
+ label: 'Vectorize',
203
+ shortLabel: 'VZ',
204
+ color: RESOURCE_COLORS.vectorize,
205
+ },
206
+ {
207
+ key: 'aiGateway',
208
+ label: 'AI Gateway',
209
+ shortLabel: 'AI',
210
+ color: RESOURCE_COLORS.aiGateway,
211
+ },
212
+ {
213
+ key: 'durableObjects',
214
+ label: 'Durable Objects',
215
+ shortLabel: 'DO',
216
+ color: RESOURCE_COLORS.durableObjects,
217
+ },
218
+ {
219
+ key: 'workersAI',
220
+ label: 'Workers AI',
221
+ shortLabel: 'AI',
222
+ color: RESOURCE_COLORS.workersAI,
223
+ },
224
+ {
225
+ key: 'queues',
226
+ label: 'Queues',
227
+ shortLabel: 'Q',
228
+ color: RESOURCE_COLORS.queues,
229
+ },
230
+ ];
231
+
232
+ /**
233
+ * Get Chart.js compatible color arrays for a given theme
234
+ */
235
+ export function getChartColors(theme: 'light' | 'dark'): {
236
+ backgroundColor: string[];
237
+ borderColor: string[];
238
+ hoverBackgroundColor: string[];
239
+ } {
240
+ const fills = RESOURCE_INFO.map((r) => r.color[theme].fill);
241
+ const hovers = RESOURCE_INFO.map((r) => r.color[theme].fillHover);
242
+
243
+ return {
244
+ backgroundColor: fills,
245
+ borderColor: fills.map((c) => c + 'cc'), // Add some transparency for border
246
+ hoverBackgroundColor: hovers,
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Get color for a specific resource type
252
+ */
253
+ export function getResourceColor(
254
+ resourceType: ResourceType,
255
+ theme: 'light' | 'dark'
256
+ ): ResourceColor['light'] | ResourceColor['dark'] {
257
+ return RESOURCE_COLORS[resourceType][theme];
258
+ }
259
+
260
+ /**
261
+ * Get resource info by key
262
+ */
263
+ export function getResourceInfo(resourceType: ResourceType): ResourceInfo | undefined {
264
+ return RESOURCE_INFO.find((r) => r.key === resourceType);
265
+ }
266
+
267
+ /**
268
+ * CSS custom properties for resource colours (for injection into stylesheets)
269
+ */
270
+ export function generateResourceColorCSS(): string {
271
+ const lines: string[] = [':root {'];
272
+
273
+ for (const [key, color] of Object.entries(RESOURCE_COLORS)) {
274
+ lines.push(` --usage-resource-${key}: ${color.light.fill};`);
275
+ lines.push(` --usage-resource-${key}-hover: ${color.light.fillHover};`);
276
+ lines.push(` --usage-resource-${key}-text: ${color.light.text};`);
277
+ }
278
+
279
+ lines.push('}');
280
+ lines.push('');
281
+ lines.push('[data-theme="dark"] {');
282
+
283
+ for (const [key, color] of Object.entries(RESOURCE_COLORS)) {
284
+ lines.push(` --usage-resource-${key}: ${color.dark.fill};`);
285
+ lines.push(` --usage-resource-${key}-hover: ${color.dark.fillHover};`);
286
+ lines.push(` --usage-resource-${key}-text: ${color.dark.text};`);
287
+ }
288
+
289
+ lines.push('}');
290
+
291
+ return lines.join('\n');
292
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * AI Model Usage Breakdown API Endpoint
3
+ *
4
+ * Returns per-model usage and cost data from:
5
+ * - Workers AI (workersai_model_daily)
6
+ * - AI Gateway (aigateway_model_daily)
7
+ *
8
+ * @module pages/api/usage/ai-models
9
+ */
10
+
11
+ import type { APIRoute } from 'astro';
12
+ import type { D1Database } from '@cloudflare/workers-types';
13
+
14
+ // =============================================================================
15
+ // TYPES
16
+ // =============================================================================
17
+
18
+ interface WorkersAIModel {
19
+ model: string;
20
+ project: string;
21
+ requests: number;
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ costUsd: number;
25
+ }
26
+
27
+ interface AIGatewayModel {
28
+ provider: string;
29
+ model: string;
30
+ gatewayId: string;
31
+ requests: number;
32
+ cachedRequests: number;
33
+ tokensIn: number;
34
+ tokensOut: number;
35
+ costUsd: number;
36
+ }
37
+
38
+ interface SuccessResponse {
39
+ success: true;
40
+ workersAI: {
41
+ models: WorkersAIModel[];
42
+ totalCostUsd: number;
43
+ totalRequests: number;
44
+ };
45
+ aiGateway: {
46
+ models: AIGatewayModel[];
47
+ totalCostUsd: number;
48
+ totalRequests: number;
49
+ byProvider: Record<string, { requests: number; costUsd: number }>;
50
+ };
51
+ period: { startDate: string; endDate: string };
52
+ responseTimeMs: number;
53
+ }
54
+
55
+ interface ErrorResponse {
56
+ success: false;
57
+ error: string;
58
+ code: string;
59
+ }
60
+
61
+ // =============================================================================
62
+ // HANDLER
63
+ // =============================================================================
64
+
65
+ export const GET: APIRoute = async ({ locals, url }) => {
66
+ const startTime = Date.now();
67
+
68
+ const env = locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined;
69
+
70
+ if (!env?.PLATFORM_DB) {
71
+ const response: ErrorResponse = {
72
+ success: false,
73
+ error: 'Database binding not available',
74
+ code: 'BINDING_ERROR',
75
+ };
76
+ return new Response(JSON.stringify(response), {
77
+ status: 503,
78
+ headers: { 'Content-Type': 'application/json' },
79
+ });
80
+ }
81
+
82
+ // Parse query params
83
+ const periodParam = url.searchParams.get('period') ?? '30d';
84
+ const now = new Date();
85
+ let startDate: string;
86
+
87
+ if (periodParam === '7d') {
88
+ const d = new Date(now);
89
+ d.setDate(d.getDate() - 7);
90
+ startDate = d.toISOString().slice(0, 10);
91
+ } else if (periodParam === '24h') {
92
+ startDate = now.toISOString().slice(0, 10);
93
+ } else {
94
+ const d = new Date(now);
95
+ d.setDate(d.getDate() - 30);
96
+ startDate = d.toISOString().slice(0, 10);
97
+ }
98
+
99
+ const endDate = now.toISOString().slice(0, 10);
100
+
101
+ try {
102
+ // Query Workers AI model usage
103
+ const workersAIResult = await env.PLATFORM_DB.prepare(
104
+ `
105
+ SELECT
106
+ model,
107
+ project,
108
+ SUM(requests) as requests,
109
+ SUM(input_tokens) as input_tokens,
110
+ SUM(output_tokens) as output_tokens,
111
+ SUM(cost_usd) as cost_usd
112
+ FROM workersai_model_daily
113
+ WHERE snapshot_date >= ? AND snapshot_date <= ?
114
+ GROUP BY model, project
115
+ ORDER BY cost_usd DESC
116
+ `
117
+ )
118
+ .bind(startDate, endDate)
119
+ .all<{
120
+ model: string;
121
+ project: string;
122
+ requests: number;
123
+ input_tokens: number;
124
+ output_tokens: number;
125
+ cost_usd: number;
126
+ }>();
127
+
128
+ // Query AI Gateway model usage
129
+ const aiGatewayResult = await env.PLATFORM_DB.prepare(
130
+ `
131
+ SELECT
132
+ provider,
133
+ model,
134
+ gateway_id,
135
+ SUM(requests) as requests,
136
+ SUM(cached_requests) as cached_requests,
137
+ SUM(tokens_in) as tokens_in,
138
+ SUM(tokens_out) as tokens_out,
139
+ SUM(cost_usd) as cost_usd
140
+ FROM aigateway_model_daily
141
+ WHERE snapshot_date >= ? AND snapshot_date <= ?
142
+ GROUP BY provider, model, gateway_id
143
+ ORDER BY cost_usd DESC
144
+ `
145
+ )
146
+ .bind(startDate, endDate)
147
+ .all<{
148
+ provider: string;
149
+ model: string;
150
+ gateway_id: string;
151
+ requests: number;
152
+ cached_requests: number;
153
+ tokens_in: number;
154
+ tokens_out: number;
155
+ cost_usd: number;
156
+ }>();
157
+
158
+ // Transform Workers AI results
159
+ const workersAIModels: WorkersAIModel[] = (workersAIResult.results ?? []).map((row) => ({
160
+ model: row.model,
161
+ project: row.project,
162
+ requests: row.requests ?? 0,
163
+ inputTokens: row.input_tokens ?? 0,
164
+ outputTokens: row.output_tokens ?? 0,
165
+ costUsd: row.cost_usd ?? 0,
166
+ }));
167
+
168
+ const workersAITotalCost = workersAIModels.reduce((sum, m) => sum + m.costUsd, 0);
169
+ const workersAITotalRequests = workersAIModels.reduce((sum, m) => sum + m.requests, 0);
170
+
171
+ // Transform AI Gateway results
172
+ const aiGatewayModels: AIGatewayModel[] = (aiGatewayResult.results ?? []).map((row) => ({
173
+ provider: row.provider,
174
+ model: row.model,
175
+ gatewayId: row.gateway_id,
176
+ requests: row.requests ?? 0,
177
+ cachedRequests: row.cached_requests ?? 0,
178
+ tokensIn: row.tokens_in ?? 0,
179
+ tokensOut: row.tokens_out ?? 0,
180
+ costUsd: row.cost_usd ?? 0,
181
+ }));
182
+
183
+ const aiGatewayTotalCost = aiGatewayModels.reduce((sum, m) => sum + m.costUsd, 0);
184
+ const aiGatewayTotalRequests = aiGatewayModels.reduce((sum, m) => sum + m.requests, 0);
185
+
186
+ // Group AI Gateway by provider
187
+ const byProvider: Record<string, { requests: number; costUsd: number }> = {};
188
+ for (const m of aiGatewayModels) {
189
+ if (!byProvider[m.provider]) {
190
+ byProvider[m.provider] = { requests: 0, costUsd: 0 };
191
+ }
192
+ byProvider[m.provider].requests += m.requests;
193
+ byProvider[m.provider].costUsd += m.costUsd;
194
+ }
195
+
196
+ const response: SuccessResponse = {
197
+ success: true,
198
+ workersAI: {
199
+ models: workersAIModels,
200
+ totalCostUsd: Math.round(workersAITotalCost * 100) / 100,
201
+ totalRequests: workersAITotalRequests,
202
+ },
203
+ aiGateway: {
204
+ models: aiGatewayModels,
205
+ totalCostUsd: Math.round(aiGatewayTotalCost * 100) / 100,
206
+ totalRequests: aiGatewayTotalRequests,
207
+ byProvider,
208
+ },
209
+ period: { startDate, endDate },
210
+ responseTimeMs: Date.now() - startTime,
211
+ };
212
+
213
+ return new Response(JSON.stringify(response), {
214
+ status: 200,
215
+ headers: {
216
+ 'Content-Type': 'application/json',
217
+ 'Cache-Control': 'public, max-age=300',
218
+ },
219
+ });
220
+ } catch (error) {
221
+ const errorMessage = error instanceof Error ? error.message : String(error);
222
+ console.error('[AI-MODELS] Error:', errorMessage);
223
+
224
+ const response: ErrorResponse = {
225
+ success: false,
226
+ error: 'Failed to fetch AI model usage',
227
+ code: 'INTERNAL_ERROR',
228
+ };
229
+
230
+ return new Response(JSON.stringify(response), {
231
+ status: 500,
232
+ headers: { 'Content-Type': 'application/json' },
233
+ });
234
+ }
235
+ };