@tokcalc/mcp-server 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +86 -406
  2. package/dist/index.js +21285 -0
  3. package/package.json +32 -90
  4. package/.zscripts/build.sh +0 -175
  5. package/.zscripts/database-runtime-build.sh +0 -33
  6. package/.zscripts/dev.pid +0 -1
  7. package/.zscripts/dev.sh +0 -154
  8. package/.zscripts/mini-services-build.sh +0 -78
  9. package/.zscripts/mini-services-install.sh +0 -65
  10. package/.zscripts/mini-services-start.sh +0 -123
  11. package/.zscripts/python-runtime-build.sh +0 -120
  12. package/.zscripts/start.sh +0 -145
  13. package/CAPACITY_STUDY.md +0 -283
  14. package/CODE_OF_CONDUCT.md +0 -55
  15. package/CONTRIBUTING.md +0 -177
  16. package/Caddyfile +0 -23
  17. package/LICENSE +0 -204
  18. package/bun.lock +0 -1965
  19. package/components.json +0 -21
  20. package/db/custom.db +0 -0
  21. package/download/README.md +0 -1
  22. package/download/tokcalc-dark-calculator.png +0 -0
  23. package/download/tokcalc-dark-default.png +0 -0
  24. package/download/tokcalc-demo.webm +0 -0
  25. package/download/tokcalc-github-link.png +0 -0
  26. package/download/tokcalc-hydration-fixed.png +0 -0
  27. package/download/tokcalc-issue-resolved.png +0 -0
  28. package/download/tokcalc-light-mode.png +0 -0
  29. package/download/tokcalc-light-reference.png +0 -0
  30. package/download/tokcalc-long-context-qwen.png +0 -0
  31. package/download/tokcalc-long-context.png +0 -0
  32. package/download/tokcalc-og-image-preview.png +0 -0
  33. package/download/tokcalc-phase2-3.png +0 -0
  34. package/download/tokcalc-plain-english.png +0 -0
  35. package/download/tokcalc-preview.png +0 -0
  36. package/download/tokcalc-share-bvb.png +0 -0
  37. package/download/tokcalc-share-feature.png +0 -0
  38. package/download/tokcalc-tab-build-vs-buy.png +0 -0
  39. package/download/tokcalc-tab-calculator.png +0 -0
  40. package/download/tokcalc-tab-reference.png +0 -0
  41. package/eslint.config.mjs +0 -50
  42. package/examples/websocket/frontend.tsx +0 -196
  43. package/examples/websocket/server.ts +0 -138
  44. package/mini-services/.gitkeep +0 -0
  45. package/mini-services/mcp-server/README.md +0 -86
  46. package/mini-services/mcp-server/bun.lock +0 -202
  47. package/mini-services/mcp-server/index.ts +0 -504
  48. package/mini-services/mcp-server/package.json +0 -40
  49. package/next.config.ts +0 -12
  50. package/postcss.config.mjs +0 -5
  51. package/prisma/schema.prisma +0 -32
  52. package/public/google6f58ca6be85fa903.html +0 -1
  53. package/public/logo.svg +0 -29
  54. package/public/manifest.json +0 -51
  55. package/public/og-icon-256.png +0 -0
  56. package/public/og.png +0 -0
  57. package/public/robots.txt +0 -25
  58. package/public/sitemap.xml +0 -23
  59. package/public/tokcalc-demo.gif +0 -0
  60. package/scripts/og-template.html +0 -120
  61. package/scripts/render-og.mjs +0 -43
  62. package/server.json +0 -21
  63. package/src/app/api/pricing/aws/route.ts +0 -186
  64. package/src/app/api/pricing/azure/route.ts +0 -168
  65. package/src/app/api/pricing/gcp/route.ts +0 -230
  66. package/src/app/api/pricing/vast-ai/route.ts +0 -164
  67. package/src/app/api/route.ts +0 -5
  68. package/src/app/compare/h100-vs-h200/layout.tsx +0 -30
  69. package/src/app/compare/h100-vs-h200/page.tsx +0 -328
  70. package/src/app/globals.css +0 -122
  71. package/src/app/layout.tsx +0 -276
  72. package/src/app/page.tsx +0 -2670
  73. package/src/components/azure-live-pricing.tsx +0 -185
  74. package/src/components/benchmark-import.tsx +0 -340
  75. package/src/components/confidence-badge.tsx +0 -116
  76. package/src/components/live-pricing-comparison.tsx +0 -241
  77. package/src/components/theme-provider.tsx +0 -11
  78. package/src/components/theme-toggle.tsx +0 -55
  79. package/src/components/ui/accordion.tsx +0 -66
  80. package/src/components/ui/alert-dialog.tsx +0 -157
  81. package/src/components/ui/alert.tsx +0 -66
  82. package/src/components/ui/aspect-ratio.tsx +0 -11
  83. package/src/components/ui/avatar.tsx +0 -53
  84. package/src/components/ui/badge.tsx +0 -46
  85. package/src/components/ui/breadcrumb.tsx +0 -109
  86. package/src/components/ui/button.tsx +0 -59
  87. package/src/components/ui/calendar.tsx +0 -213
  88. package/src/components/ui/card.tsx +0 -92
  89. package/src/components/ui/carousel.tsx +0 -241
  90. package/src/components/ui/chart.tsx +0 -353
  91. package/src/components/ui/checkbox.tsx +0 -32
  92. package/src/components/ui/collapsible.tsx +0 -33
  93. package/src/components/ui/command.tsx +0 -184
  94. package/src/components/ui/context-menu.tsx +0 -252
  95. package/src/components/ui/dialog.tsx +0 -143
  96. package/src/components/ui/drawer.tsx +0 -135
  97. package/src/components/ui/dropdown-menu.tsx +0 -257
  98. package/src/components/ui/form.tsx +0 -167
  99. package/src/components/ui/hover-card.tsx +0 -44
  100. package/src/components/ui/input-otp.tsx +0 -77
  101. package/src/components/ui/input.tsx +0 -21
  102. package/src/components/ui/label.tsx +0 -24
  103. package/src/components/ui/menubar.tsx +0 -276
  104. package/src/components/ui/navigation-menu.tsx +0 -168
  105. package/src/components/ui/pagination.tsx +0 -127
  106. package/src/components/ui/popover.tsx +0 -48
  107. package/src/components/ui/progress.tsx +0 -31
  108. package/src/components/ui/radio-group.tsx +0 -45
  109. package/src/components/ui/resizable.tsx +0 -56
  110. package/src/components/ui/scroll-area.tsx +0 -58
  111. package/src/components/ui/select.tsx +0 -185
  112. package/src/components/ui/separator.tsx +0 -28
  113. package/src/components/ui/sheet.tsx +0 -139
  114. package/src/components/ui/sidebar.tsx +0 -726
  115. package/src/components/ui/skeleton.tsx +0 -13
  116. package/src/components/ui/slider.tsx +0 -63
  117. package/src/components/ui/sonner.tsx +0 -25
  118. package/src/components/ui/switch.tsx +0 -31
  119. package/src/components/ui/table.tsx +0 -116
  120. package/src/components/ui/tabs.tsx +0 -66
  121. package/src/components/ui/textarea.tsx +0 -18
  122. package/src/components/ui/toast.tsx +0 -129
  123. package/src/components/ui/toaster.tsx +0 -35
  124. package/src/components/ui/toggle-group.tsx +0 -73
  125. package/src/components/ui/toggle.tsx +0 -47
  126. package/src/components/ui/tooltip.tsx +0 -61
  127. package/src/components/vast-ai-live-pricing.tsx +0 -176
  128. package/src/hooks/use-mobile.ts +0 -19
  129. package/src/hooks/use-toast.ts +0 -194
  130. package/src/lib/benchmark-parser-sglang.ts +0 -150
  131. package/src/lib/benchmark-parser-tokcalc.ts +0 -247
  132. package/src/lib/benchmark-parser-trtllm.ts +0 -152
  133. package/src/lib/benchmark-parser-vllm.ts +0 -198
  134. package/src/lib/benchmark-schema.ts +0 -263
  135. package/src/lib/db.ts +0 -13
  136. package/src/lib/engine-presets.ts +0 -183
  137. package/src/lib/price-schema.ts +0 -141
  138. package/src/lib/token-calc.ts +0 -808
  139. package/src/lib/track.ts +0 -31
  140. package/src/lib/url-state.ts +0 -256
  141. package/src/lib/utils.ts +0 -6
  142. package/tailwind.config.ts +0 -64
  143. package/tests/database-runtime-build.sh +0 -75
  144. package/tests/python-runtime-build.sh +0 -64
  145. package/tests/python-runtime-container.sh +0 -31
  146. package/tool-results/bash_1789888171144_2c5381860539.txt +0 -161
  147. package/tool-results/bash_1789888175925_49c53ba3c61b.txt +0 -191
  148. package/tool-results/bash_1789888181202_49c53ba3c61b.txt +0 -191
  149. package/tool-results/bash_1789888195219_4a86a5c91411.txt +0 -200
  150. package/tool-results/bash_1789888203128_6cca13c71b47.txt +0 -199
  151. package/tool-results/bash_1789929256963_2a52aff0d0a8.txt +0 -160
  152. package/tool-results/read_1789888151021_69f58eec6a5b.txt +0 -653
  153. package/tool-results/read_1789888153837_1d3a8bfc2a94.txt +0 -653
  154. package/tool-results/read_1789888163087_ccc406d47505.txt +0 -122
  155. package/tool-results/read_1789888167347_67d1d7c9830a.txt +0 -122
  156. package/tool-results/read_1789929252529_d90e8f383a25.txt +0 -285
  157. package/tsconfig.json +0 -42
  158. package/upload/Pasted Content_1789887800864.txt +0 -652
  159. package/upload/Pasted Content_1789887909561.txt +0 -652
  160. package/upload/Pasted Content_1789887918428.txt +0 -652
  161. package/upload/Pasted Content_1789887959420.txt +0 -652
  162. package/upload/Pasted Content_1789888020485.txt +0 -652
  163. package/upload/Pasted Content_1789888058079.txt +0 -652
  164. package/upload/Pasted Content_1789888885033.txt +0 -686
  165. package/upload/Pasted Content_1789928912741.txt +0 -285
  166. package/upload/Pasted Content_1789928938402.txt +0 -285
  167. package/upload/Pasted Content_1789929160389.txt +0 -285
  168. package/upload/Pasted Content_1789929176660.txt +0 -285
  169. package/upload/issue_vision.json +0 -28
  170. package/upload/pasted_image_1789883175209.png +0 -0
  171. package/upload/pasted_image_1789899056690.png +0 -0
  172. package/upload/pasted_image_1789900371483.png +0 -0
  173. package/upload/pasted_image_1789900472823.png +0 -0
  174. package/upload/pasted_image_1789900490374.png +0 -0
  175. package/upload/pasted_image_1789900585552.png +0 -0
  176. package/upload/pasted_image_1789900606519.png +0 -0
  177. package/upload/pasted_image_1789901598705.png +0 -0
  178. package/upload/pasted_image_1789901613545.png +0 -0
  179. package/upload/pasted_image_1789978382674.png +0 -0
  180. package/upload/pasted_image_1789978392749.png +0 -0
  181. package/upload/pasted_image_1789978474879.png +0 -0
  182. package/upload/pasted_image_1789978523652.png +0 -0
  183. package/upload/pasted_image_1789984219089.png +0 -0
  184. package/upload/pasted_image_1789984491896.png +0 -0
  185. package/upload/pasted_image_1789985017950.png +0 -0
  186. package/upload/pasted_image_1789985036765.png +0 -0
  187. package/upload/pasted_image_1789985049848.png +0 -0
  188. package/upload/pasted_image_1790002427833.png +0 -0
  189. package/upload/pasted_image_1790002659944.png +0 -0
  190. package/upload/pasted_image_1790037038476.png +0 -0
  191. package/upload/screenshot_analysis.json +0 -28
  192. 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
- }