@tokcalc/mcp-server 0.1.3 → 0.2.0-alpha.1

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 (193) hide show
  1. package/README.md +86 -406
  2. package/dist/http.js +23504 -0
  3. package/dist/index.js +21290 -0
  4. package/package.json +36 -92
  5. package/.zscripts/build.sh +0 -175
  6. package/.zscripts/database-runtime-build.sh +0 -33
  7. package/.zscripts/dev.pid +0 -1
  8. package/.zscripts/dev.sh +0 -154
  9. package/.zscripts/mini-services-build.sh +0 -78
  10. package/.zscripts/mini-services-install.sh +0 -65
  11. package/.zscripts/mini-services-start.sh +0 -123
  12. package/.zscripts/python-runtime-build.sh +0 -120
  13. package/.zscripts/start.sh +0 -145
  14. package/CAPACITY_STUDY.md +0 -283
  15. package/CODE_OF_CONDUCT.md +0 -55
  16. package/CONTRIBUTING.md +0 -177
  17. package/Caddyfile +0 -23
  18. package/LICENSE +0 -204
  19. package/bun.lock +0 -1965
  20. package/components.json +0 -21
  21. package/db/custom.db +0 -0
  22. package/download/README.md +0 -1
  23. package/download/tokcalc-dark-calculator.png +0 -0
  24. package/download/tokcalc-dark-default.png +0 -0
  25. package/download/tokcalc-demo.webm +0 -0
  26. package/download/tokcalc-github-link.png +0 -0
  27. package/download/tokcalc-hydration-fixed.png +0 -0
  28. package/download/tokcalc-issue-resolved.png +0 -0
  29. package/download/tokcalc-light-mode.png +0 -0
  30. package/download/tokcalc-light-reference.png +0 -0
  31. package/download/tokcalc-long-context-qwen.png +0 -0
  32. package/download/tokcalc-long-context.png +0 -0
  33. package/download/tokcalc-og-image-preview.png +0 -0
  34. package/download/tokcalc-phase2-3.png +0 -0
  35. package/download/tokcalc-plain-english.png +0 -0
  36. package/download/tokcalc-preview.png +0 -0
  37. package/download/tokcalc-share-bvb.png +0 -0
  38. package/download/tokcalc-share-feature.png +0 -0
  39. package/download/tokcalc-tab-build-vs-buy.png +0 -0
  40. package/download/tokcalc-tab-calculator.png +0 -0
  41. package/download/tokcalc-tab-reference.png +0 -0
  42. package/eslint.config.mjs +0 -50
  43. package/examples/websocket/frontend.tsx +0 -196
  44. package/examples/websocket/server.ts +0 -138
  45. package/mini-services/.gitkeep +0 -0
  46. package/mini-services/mcp-server/README.md +0 -86
  47. package/mini-services/mcp-server/bun.lock +0 -202
  48. package/mini-services/mcp-server/index.ts +0 -504
  49. package/mini-services/mcp-server/package.json +0 -40
  50. package/next.config.ts +0 -12
  51. package/postcss.config.mjs +0 -5
  52. package/prisma/schema.prisma +0 -32
  53. package/public/google6f58ca6be85fa903.html +0 -1
  54. package/public/logo.svg +0 -29
  55. package/public/manifest.json +0 -51
  56. package/public/og-icon-256.png +0 -0
  57. package/public/og.png +0 -0
  58. package/public/robots.txt +0 -25
  59. package/public/sitemap.xml +0 -23
  60. package/public/tokcalc-demo.gif +0 -0
  61. package/scripts/og-template.html +0 -120
  62. package/scripts/render-og.mjs +0 -43
  63. package/server.json +0 -21
  64. package/src/app/api/pricing/aws/route.ts +0 -186
  65. package/src/app/api/pricing/azure/route.ts +0 -168
  66. package/src/app/api/pricing/gcp/route.ts +0 -230
  67. package/src/app/api/pricing/vast-ai/route.ts +0 -164
  68. package/src/app/api/route.ts +0 -5
  69. package/src/app/compare/h100-vs-h200/layout.tsx +0 -30
  70. package/src/app/compare/h100-vs-h200/page.tsx +0 -328
  71. package/src/app/globals.css +0 -122
  72. package/src/app/layout.tsx +0 -276
  73. package/src/app/page.tsx +0 -2670
  74. package/src/components/azure-live-pricing.tsx +0 -185
  75. package/src/components/benchmark-import.tsx +0 -340
  76. package/src/components/confidence-badge.tsx +0 -116
  77. package/src/components/live-pricing-comparison.tsx +0 -241
  78. package/src/components/theme-provider.tsx +0 -11
  79. package/src/components/theme-toggle.tsx +0 -55
  80. package/src/components/ui/accordion.tsx +0 -66
  81. package/src/components/ui/alert-dialog.tsx +0 -157
  82. package/src/components/ui/alert.tsx +0 -66
  83. package/src/components/ui/aspect-ratio.tsx +0 -11
  84. package/src/components/ui/avatar.tsx +0 -53
  85. package/src/components/ui/badge.tsx +0 -46
  86. package/src/components/ui/breadcrumb.tsx +0 -109
  87. package/src/components/ui/button.tsx +0 -59
  88. package/src/components/ui/calendar.tsx +0 -213
  89. package/src/components/ui/card.tsx +0 -92
  90. package/src/components/ui/carousel.tsx +0 -241
  91. package/src/components/ui/chart.tsx +0 -353
  92. package/src/components/ui/checkbox.tsx +0 -32
  93. package/src/components/ui/collapsible.tsx +0 -33
  94. package/src/components/ui/command.tsx +0 -184
  95. package/src/components/ui/context-menu.tsx +0 -252
  96. package/src/components/ui/dialog.tsx +0 -143
  97. package/src/components/ui/drawer.tsx +0 -135
  98. package/src/components/ui/dropdown-menu.tsx +0 -257
  99. package/src/components/ui/form.tsx +0 -167
  100. package/src/components/ui/hover-card.tsx +0 -44
  101. package/src/components/ui/input-otp.tsx +0 -77
  102. package/src/components/ui/input.tsx +0 -21
  103. package/src/components/ui/label.tsx +0 -24
  104. package/src/components/ui/menubar.tsx +0 -276
  105. package/src/components/ui/navigation-menu.tsx +0 -168
  106. package/src/components/ui/pagination.tsx +0 -127
  107. package/src/components/ui/popover.tsx +0 -48
  108. package/src/components/ui/progress.tsx +0 -31
  109. package/src/components/ui/radio-group.tsx +0 -45
  110. package/src/components/ui/resizable.tsx +0 -56
  111. package/src/components/ui/scroll-area.tsx +0 -58
  112. package/src/components/ui/select.tsx +0 -185
  113. package/src/components/ui/separator.tsx +0 -28
  114. package/src/components/ui/sheet.tsx +0 -139
  115. package/src/components/ui/sidebar.tsx +0 -726
  116. package/src/components/ui/skeleton.tsx +0 -13
  117. package/src/components/ui/slider.tsx +0 -63
  118. package/src/components/ui/sonner.tsx +0 -25
  119. package/src/components/ui/switch.tsx +0 -31
  120. package/src/components/ui/table.tsx +0 -116
  121. package/src/components/ui/tabs.tsx +0 -66
  122. package/src/components/ui/textarea.tsx +0 -18
  123. package/src/components/ui/toast.tsx +0 -129
  124. package/src/components/ui/toaster.tsx +0 -35
  125. package/src/components/ui/toggle-group.tsx +0 -73
  126. package/src/components/ui/toggle.tsx +0 -47
  127. package/src/components/ui/tooltip.tsx +0 -61
  128. package/src/components/vast-ai-live-pricing.tsx +0 -176
  129. package/src/hooks/use-mobile.ts +0 -19
  130. package/src/hooks/use-toast.ts +0 -194
  131. package/src/lib/benchmark-parser-sglang.ts +0 -150
  132. package/src/lib/benchmark-parser-tokcalc.ts +0 -247
  133. package/src/lib/benchmark-parser-trtllm.ts +0 -152
  134. package/src/lib/benchmark-parser-vllm.ts +0 -198
  135. package/src/lib/benchmark-schema.ts +0 -263
  136. package/src/lib/db.ts +0 -13
  137. package/src/lib/engine-presets.ts +0 -183
  138. package/src/lib/price-schema.ts +0 -141
  139. package/src/lib/token-calc.ts +0 -808
  140. package/src/lib/track.ts +0 -31
  141. package/src/lib/url-state.ts +0 -256
  142. package/src/lib/utils.ts +0 -6
  143. package/tailwind.config.ts +0 -64
  144. package/tests/database-runtime-build.sh +0 -75
  145. package/tests/python-runtime-build.sh +0 -64
  146. package/tests/python-runtime-container.sh +0 -31
  147. package/tool-results/bash_1789888171144_2c5381860539.txt +0 -161
  148. package/tool-results/bash_1789888175925_49c53ba3c61b.txt +0 -191
  149. package/tool-results/bash_1789888181202_49c53ba3c61b.txt +0 -191
  150. package/tool-results/bash_1789888195219_4a86a5c91411.txt +0 -200
  151. package/tool-results/bash_1789888203128_6cca13c71b47.txt +0 -199
  152. package/tool-results/bash_1789929256963_2a52aff0d0a8.txt +0 -160
  153. package/tool-results/read_1789888151021_69f58eec6a5b.txt +0 -653
  154. package/tool-results/read_1789888153837_1d3a8bfc2a94.txt +0 -653
  155. package/tool-results/read_1789888163087_ccc406d47505.txt +0 -122
  156. package/tool-results/read_1789888167347_67d1d7c9830a.txt +0 -122
  157. package/tool-results/read_1789929252529_d90e8f383a25.txt +0 -285
  158. package/tsconfig.json +0 -42
  159. package/upload/Pasted Content_1789887800864.txt +0 -652
  160. package/upload/Pasted Content_1789887909561.txt +0 -652
  161. package/upload/Pasted Content_1789887918428.txt +0 -652
  162. package/upload/Pasted Content_1789887959420.txt +0 -652
  163. package/upload/Pasted Content_1789888020485.txt +0 -652
  164. package/upload/Pasted Content_1789888058079.txt +0 -652
  165. package/upload/Pasted Content_1789888885033.txt +0 -686
  166. package/upload/Pasted Content_1789928912741.txt +0 -285
  167. package/upload/Pasted Content_1789928938402.txt +0 -285
  168. package/upload/Pasted Content_1789929160389.txt +0 -285
  169. package/upload/Pasted Content_1789929176660.txt +0 -285
  170. package/upload/issue_vision.json +0 -28
  171. package/upload/pasted_image_1789883175209.png +0 -0
  172. package/upload/pasted_image_1789899056690.png +0 -0
  173. package/upload/pasted_image_1789900371483.png +0 -0
  174. package/upload/pasted_image_1789900472823.png +0 -0
  175. package/upload/pasted_image_1789900490374.png +0 -0
  176. package/upload/pasted_image_1789900585552.png +0 -0
  177. package/upload/pasted_image_1789900606519.png +0 -0
  178. package/upload/pasted_image_1789901598705.png +0 -0
  179. package/upload/pasted_image_1789901613545.png +0 -0
  180. package/upload/pasted_image_1789978382674.png +0 -0
  181. package/upload/pasted_image_1789978392749.png +0 -0
  182. package/upload/pasted_image_1789978474879.png +0 -0
  183. package/upload/pasted_image_1789978523652.png +0 -0
  184. package/upload/pasted_image_1789984219089.png +0 -0
  185. package/upload/pasted_image_1789984491896.png +0 -0
  186. package/upload/pasted_image_1789985017950.png +0 -0
  187. package/upload/pasted_image_1789985036765.png +0 -0
  188. package/upload/pasted_image_1789985049848.png +0 -0
  189. package/upload/pasted_image_1790002427833.png +0 -0
  190. package/upload/pasted_image_1790002659944.png +0 -0
  191. package/upload/pasted_image_1790037038476.png +0 -0
  192. package/upload/screenshot_analysis.json +0 -28
  193. package/upload/vision_output.json +0 -28
@@ -1,230 +0,0 @@
1
- /**
2
- * Google Cloud GPU pricing proxy.
3
- *
4
- * Uses the Google Cloud Billing Catalog API:
5
- * GET https://cloudbilling.googleapis.com/v1/services/{SERVICE_ID}/skus?key=API_KEY
6
- *
7
- * Requires GCP_API_KEY environment variable.
8
- * Set on Vercel: Settings → Environment Variables → GCP_API_KEY
9
- *
10
- * This serverless function:
11
- * 1. Fetches Compute Engine SKUs from the Cloud Billing Catalog API
12
- * 2. Filters for GPU instance types (a3, a4 — H100, H200, B200)
13
- * 3. Extracts on-demand pricing
14
- * 4. Caches in memory for 24 hours
15
- *
16
- * The API key is stored as an env var — NOT hardcoded in source code.
17
- * This is important because tokcalc is open source on GitHub.
18
- */
19
-
20
- import { NextResponse } from "next/server";
21
-
22
- // GCP GPU instance types to look for in SKU descriptions
23
- const GPU_PATTERNS = [
24
- { pattern: "H100", gpu: "H100" },
25
- { pattern: "H200", gpu: "H200" },
26
- { pattern: "B200", gpu: "B200" },
27
- { pattern: "A100", gpu: "A100" },
28
- { pattern: "L4", gpu: "L4" },
29
- { pattern: "T4", gpu: "T4" },
30
- { pattern: "L40", gpu: "L40S" },
31
- { pattern: "V100", gpu: "V100" },
32
- ];
33
-
34
- // GCP instance type → GPU count mapping
35
- const GCP_GPU_COUNTS: Record<string, number> = {
36
- "a3-highgpu-8g": 8, "a3-highgpu-2g": 2,
37
- "a3-edgegpu-8g": 8, "a3-edgegpu-2g": 2,
38
- "a4-highgpu-8g": 8,
39
- "a2-highgpu-1g": 1, "a2-highgpu-2g": 2, "a2-highgpu-4g": 4, "a2-highgpu-8g": 8,
40
- "n1-standard-4": 1, // T4 usually 1 per instance
41
- "g2-standard-4": 1, "g2-standard-8": 1, "g2-standard-12": 1, "g2-standard-16": 1, "g2-standard-24": 4, "g2-standard-32": 1, "g2-standard-48": 8,
42
- };
43
-
44
- let cache: { data: CleanGcpPrice[]; timestamp: number } | null = null;
45
- const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
46
-
47
- interface CleanGcpPrice {
48
- gpu: string;
49
- instanceType: string;
50
- pricePerGpuHour: number;
51
- instancePriceHour: number;
52
- gpuCount: number;
53
- region: string;
54
- retrievedAt: string;
55
- }
56
-
57
- export async function GET() {
58
- const apiKey = process.env.GCP_API_KEY;
59
-
60
- if (!apiKey) {
61
- return NextResponse.json(
62
- {
63
- source: "gcp",
64
- error: "GCP_API_KEY environment variable is not set. Add it on Vercel: Settings → Environment Variables → GCP_API_KEY",
65
- fallback: "Use Azure or Vast.ai live pricing instead.",
66
- },
67
- { status: 503 },
68
- );
69
- }
70
-
71
- if (cache && Date.now() - cache.timestamp < CACHE_TTL_MS) {
72
- return NextResponse.json({
73
- source: "gcp",
74
- cached: true,
75
- retrievedAt: new Date(cache.timestamp).toISOString(),
76
- count: cache.data.length,
77
- prices: cache.data,
78
- });
79
- }
80
-
81
- try {
82
- // Step 1: Get the list of services to find Compute Engine's service ID
83
- const servicesUrl = `https://cloudbilling.googleapis.com/v1/services?key=${apiKey}&pageSize=100`;
84
- const servicesResponse = await fetch(servicesUrl, {
85
- signal: AbortSignal.timeout(10000),
86
- });
87
-
88
- if (!servicesResponse.ok) {
89
- throw new Error(`GCP Services API returned ${servicesResponse.status}`);
90
- }
91
-
92
- const servicesData = await servicesResponse.json() as {
93
- services?: Array<{ serviceId: string; displayName: string }>;
94
- };
95
-
96
- // Find Compute Engine service
97
- const computeService = servicesData.services?.find(
98
- (s) => s.displayName?.toLowerCase().includes("compute engine"),
99
- );
100
-
101
- if (!computeService) {
102
- throw new Error("Compute Engine service not found in GCP billing catalog");
103
- }
104
-
105
- // Step 2: Fetch SKUs for Compute Engine
106
- const skusUrl = `https://cloudbilling.googleapis.com/v1/services/${computeService.serviceId}/skus?key=${apiKey}&pageSize=5000`;
107
- const skusResponse = await fetch(skusUrl, {
108
- signal: AbortSignal.timeout(15000),
109
- });
110
-
111
- if (!skusResponse.ok) {
112
- throw new Error(`GCP SKUs API returned ${skusResponse.status}`);
113
- }
114
-
115
- const skusData = await skusResponse.json() as {
116
- skus?: Array<{
117
- skuId: string;
118
- description: string;
119
- pricingInfo?: Array<{
120
- pricingExpression?: {
121
- tieredRates?: Array<{
122
- unitPrice?: { units?: string; nanos?: number };
123
- }>;
124
- };
125
- }>;
126
- serviceRegions?: string[];
127
- geoTaxonomy?: { regions?: string[] };
128
- }>;
129
- nextPagePageToken?: string;
130
- };
131
-
132
- const skus = skusData.skus || [];
133
-
134
- const prices: CleanGcpPrice[] = [];
135
-
136
- for (const sku of skus) {
137
- const description = sku.description || "";
138
-
139
- // Check if this SKU matches any GPU pattern
140
- const gpuMatch = GPU_PATTERNS.find((p) => description.includes(p.pattern));
141
- if (!gpuMatch) continue;
142
-
143
- // Skip preemptible/spot/reserved
144
- if (description.toLowerCase().includes("preemptible")) continue;
145
- if (description.toLowerCase().includes("spot")) continue;
146
- if (description.toLowerCase().includes("reserved")) continue;
147
- if (description.toLowerCase().includes("commitment")) continue;
148
-
149
- // Skip non-GPU SKUs that happen to mention GPU in text
150
- if (!description.toLowerCase().includes("gpu") && !description.toLowerCase().includes("premium")) {
151
- // Only include if the instance type is in our known GPU instances
152
- const instanceMatch = Object.keys(GCP_GPU_COUNTS).find(
153
- (it) => description.toLowerCase().includes(it.toLowerCase()),
154
- );
155
- if (!instanceMatch) continue;
156
- }
157
-
158
- // Extract price
159
- const pricingInfo = sku.pricingInfo?.[0];
160
- const tieredRates = pricingInfo?.pricingExpression?.tieredRates;
161
- const firstRate = tieredRates?.[0];
162
- const unitPrice = firstRate?.unitPrice;
163
-
164
- if (!unitPrice) continue;
165
-
166
- // GCP prices are in USD with nanos precision
167
- const dollars = parseFloat(unitPrice.units || "0");
168
- const nanos = (unitPrice.nanos || 0) / 1e9;
169
- const pricePerHour = dollars + nanos;
170
-
171
- if (isNaN(pricePerHour) || pricePerHour <= 0) continue;
172
-
173
- // Try to determine instance type and GPU count
174
- let instanceType = "unknown";
175
- let gpuCount = 1;
176
-
177
- for (const [it, count] of Object.entries(GCP_GPU_COUNTS)) {
178
- if (description.toLowerCase().includes(it.toLowerCase())) {
179
- instanceType = it;
180
- gpuCount = count;
181
- break;
182
- }
183
- }
184
-
185
- const pricePerGpuHour = Math.round((pricePerHour / gpuCount) * 100) / 100;
186
-
187
- prices.push({
188
- gpu: gpuMatch.gpu,
189
- instanceType,
190
- pricePerGpuHour,
191
- instancePriceHour: Math.round(pricePerHour * 100) / 100,
192
- gpuCount,
193
- region: sku.serviceRegions?.[0] || sku.geoTaxonomy?.regions?.[0] || "global",
194
- retrievedAt: new Date().toISOString(),
195
- });
196
- }
197
-
198
- // Deduplicate: keep cheapest per-GPU price per GPU model
199
- const seen = new Map<string, CleanGcpPrice>();
200
- for (const p of prices) {
201
- const key = p.gpu;
202
- if (!seen.has(key) || seen.get(key)!.pricePerGpuHour > p.pricePerGpuHour) {
203
- seen.set(key, p);
204
- }
205
- }
206
-
207
- const deduped = Array.from(seen.values()).sort((a, b) => a.pricePerGpuHour - b.pricePerGpuHour);
208
-
209
- cache = { data: deduped, timestamp: Date.now() };
210
-
211
- return NextResponse.json({
212
- source: "gcp",
213
- cached: false,
214
- retrievedAt: new Date().toISOString(),
215
- count: deduped.length,
216
- prices: deduped,
217
- note: "On-demand pricing from Google Cloud Billing Catalog API. Region varies by SKU. Requires GCP_API_KEY env var.",
218
- });
219
- } catch (error) {
220
- console.error("GCP pricing fetch error:", error);
221
- return NextResponse.json(
222
- {
223
- source: "gcp",
224
- error: "Failed to fetch GCP pricing. Check that GCP_API_KEY is set and valid.",
225
- fallback: "Use Azure or Vast.ai live pricing instead.",
226
- },
227
- { status: 502 },
228
- );
229
- }
230
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * Vast.ai marketplace pricing proxy.
3
- *
4
- * Fetches live spot-market GPU offers from Vast.ai's public API:
5
- * GET https://cloud.vast.ai/api/v0/bundles/
6
- *
7
- * This serverless function:
8
- * 1. Fetches server-side (avoids CORS issues)
9
- * 2. Filters for GPU-only offers
10
- * 3. Deduplicates by GPU model (keeps cheapest available)
11
- * 4. Caches in memory for 5 minutes (spot prices change rapidly)
12
- *
13
- * Per Perplexity research (Prompt #3):
14
- * "Vast.ai is the best fit for dynamic spot/marketplace pricing"
15
- * "Official offer-search REST API / OpenAPI"
16
- * "Treat every returned offer as temporary market data"
17
- */
18
-
19
- import { NextRequest, NextResponse } from "next/server";
20
-
21
- interface VastAiBundle {
22
- id: number;
23
- gpu_name?: string;
24
- num_gpus?: number;
25
- gpu_ram?: number;
26
- dph_total?: number; // total $/hour
27
- reliability?: number;
28
- geolocation?: string;
29
- inetname?: string;
30
- dlperf?: number; // download performance
31
- cuda_max_good?: number; // CUDA version
32
- total_flops?: number;
33
- cpu_ram?: number;
34
- cpu_cores?: number;
35
- }
36
-
37
- interface CleanOffer {
38
- gpu: string;
39
- gpuCount: number;
40
- vramGb: number;
41
- pricePerGpuHour: number;
42
- totalPriceHour: number;
43
- reliability: number;
44
- region: string;
45
- bundleId: number;
46
- }
47
-
48
- // In-memory cache (5 minute TTL — spot prices change rapidly)
49
- let cache: { data: CleanOffer[]; timestamp: number } | null = null;
50
- const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
51
-
52
- // GPUs we care about — use short names for flexible matching
53
- // (e.g., "H100" matches both "H100 SXM" and "H100 NVL")
54
- const TARGET_GPUS = [
55
- "H100", "H200", "B200",
56
- "A100", "A6000",
57
- "RTX 4090", "RTX 3090", "RTX 4080", "RTX 3080",
58
- "RTX 5090", "RTX 5080", "RTX 5070",
59
- "L40", "L4",
60
- "V100",
61
- "T4",
62
- ];
63
-
64
- export async function GET(_request: NextRequest) {
65
- // Return cached data if fresh
66
- if (cache && Date.now() - cache.timestamp < CACHE_TTL_MS) {
67
- return NextResponse.json({
68
- source: "vast_ai",
69
- cached: true,
70
- retrievedAt: new Date(cache.timestamp).toISOString(),
71
- count: cache.data.length,
72
- offers: cache.data,
73
- note: "Spot-market prices. Updated every 5 minutes. Individual offers may be gone by the time you try to rent.",
74
- });
75
- }
76
-
77
- try {
78
- // Vast.ai API: search for GPU offers, sorted by cheapest first
79
- // The q parameter filters by GPU name, order sorts by price ascending
80
- const url = "https://cloud.vast.ai/api/v0/bundles/";
81
- const response = await fetch(url, {
82
- headers: { "Accept": "application/json" },
83
- // Timeout after 10 seconds
84
- signal: AbortSignal.timeout(10000),
85
- });
86
-
87
- if (!response.ok) {
88
- throw new Error(`Vast.ai API returned ${response.status}`);
89
- }
90
-
91
- const data = await response.json() as { offers?: VastAiBundle[] };
92
- const bundles = data.offers || [];
93
-
94
- // Filter and clean offers
95
- const offers: CleanOffer[] = [];
96
-
97
- for (const bundle of bundles) {
98
- if (!bundle.gpu_name || !bundle.dph_total || bundle.dph_total <= 0) continue;
99
- if (!bundle.num_gpus || bundle.num_gpus <= 0) continue;
100
-
101
- const gpuName = bundle.gpu_name.trim();
102
- const gpuCount = bundle.num_gpus;
103
- const totalPriceHour = bundle.dph_total;
104
- const pricePerGpuHour = Math.round((totalPriceHour / gpuCount) * 100) / 100;
105
-
106
- // Check if this GPU is in our target list (bidirectional matching)
107
- const isTarget = TARGET_GPUS.some(
108
- (target) =>
109
- gpuName.toLowerCase().includes(target.toLowerCase()) ||
110
- target.toLowerCase().includes(gpuName.toLowerCase()),
111
- );
112
- if (!isTarget) continue;
113
-
114
- offers.push({
115
- gpu: gpuName,
116
- gpuCount,
117
- vramGb: bundle.gpu_ram || 0,
118
- pricePerGpuHour,
119
- totalPriceHour: Math.round(totalPriceHour * 100) / 100,
120
- reliability: bundle.reliability || 0,
121
- region: bundle.geolocation || "unknown",
122
- bundleId: bundle.id,
123
- });
124
- }
125
-
126
- // Deduplicate: keep cheapest per-GPU price for each GPU model
127
- const cheapestPerGpu = new Map<string, CleanOffer>();
128
- for (const offer of offers) {
129
- const key = offer.gpu;
130
- if (!cheapestPerGpu.has(key) || cheapestPerGpu.get(key)!.pricePerGpuHour > offer.pricePerGpuHour) {
131
- cheapestPerGpu.set(key, offer);
132
- }
133
- }
134
-
135
- const deduped = Array.from(cheapestPerGpu.values()).sort(
136
- (a, b) => a.pricePerGpuHour - b.pricePerGpuHour,
137
- );
138
-
139
- // Update cache
140
- cache = {
141
- data: deduped,
142
- timestamp: Date.now(),
143
- };
144
-
145
- return NextResponse.json({
146
- source: "vast_ai",
147
- cached: false,
148
- retrievedAt: new Date().toISOString(),
149
- count: deduped.length,
150
- offers: deduped,
151
- note: "Spot-market prices from Vast.ai marketplace. Individual offers may be gone by the time you try to rent. Reliability score indicates host trustworthiness (0-1).",
152
- });
153
- } catch (error) {
154
- console.error("Vast.ai pricing fetch error:", error);
155
- return NextResponse.json(
156
- {
157
- source: "vast_ai",
158
- error: "Failed to fetch Vast.ai marketplace pricing. The API may be temporarily unavailable or rate-limited.",
159
- fallback: "Use static pricing from tokcalc's GPU catalog.",
160
- },
161
- { status: 502 },
162
- );
163
- }
164
- }
@@ -1,5 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- export async function GET() {
4
- return NextResponse.json({ message: "Hello, world!" });
5
- }
@@ -1,30 +0,0 @@
1
- import type { Metadata } from "next";
2
-
3
- export const metadata: Metadata = {
4
- title: "H100 vs H200 for LLM Inference: Throughput, Latency, VRAM & Cost",
5
- description:
6
- "Side-by-side comparison of NVIDIA H100 SXM5 80GB vs H200 SXM5 141GB for Llama 3.3 70B FP8 serving. Interactive throughput, latency, VRAM, and cost calculator with transparent formulas.",
7
- keywords: [
8
- "H100 vs H200",
9
- "H100 H200 comparison",
10
- "LLM inference GPU comparison",
11
- "H100 LLM throughput",
12
- "H200 LLM throughput",
13
- "H100 vs H200 VRAM",
14
- "Llama 70B GPU comparison",
15
- "NVIDIA H100 H200",
16
- ],
17
- openGraph: {
18
- title: "H100 vs H200 for LLM Inference — Throughput, VRAM & Cost",
19
- description:
20
- "Interactive comparison of H100 80GB vs H200 141GB for Llama 3.3 70B serving. See how context length affects capacity.",
21
- type: "article",
22
- },
23
- alternates: {
24
- canonical: "https://tokcalc.vercel.app/compare/h100-vs-h200",
25
- },
26
- };
27
-
28
- export default function Page({ children }: { children: React.ReactNode }) {
29
- return children;
30
- }