mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-20 06:19:57 +08:00
* ai sdk * chat * move more to lib * fixes * refactor * fixes * streamdown * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * data-spec * fixes * sortable * github * tools * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * tables * fixes * fixes * rename to chat * fixes docs * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fix lint * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes
166 lines
5.3 KiB
TypeScript
166 lines
5.3 KiB
TypeScript
import { tool } from "ai";
|
|
import { z } from "zod";
|
|
|
|
// =============================================================================
|
|
// Helpers
|
|
// =============================================================================
|
|
|
|
function handleFetchError(res: Response, coinId: string) {
|
|
if (res.status === 404) {
|
|
return { error: `Cryptocurrency not found: ${coinId}` };
|
|
}
|
|
if (res.status === 429) {
|
|
return { error: "CoinGecko rate limit exceeded. Try again in a minute." };
|
|
}
|
|
return { error: `Failed to fetch crypto data: ${res.statusText}` };
|
|
}
|
|
|
|
function sampleTimeSeries(
|
|
prices: [number, number][],
|
|
maxPoints: number,
|
|
): Array<{ date: string; price: number }> {
|
|
const step = Math.max(1, Math.floor(prices.length / maxPoints));
|
|
return prices
|
|
.filter((_, i) => i % step === 0)
|
|
.map(([timestamp, price]) => ({
|
|
date: new Date(timestamp).toLocaleDateString("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
}),
|
|
price: Math.round(price * 100) / 100,
|
|
}));
|
|
}
|
|
|
|
// =============================================================================
|
|
// getCryptoPrice — current market data + 7-day sparkline
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get cryptocurrency market data from CoinGecko.
|
|
* Free public API, no API key required.
|
|
* https://docs.coingecko.com/reference/introduction
|
|
*/
|
|
export const getCryptoPrice = tool({
|
|
description:
|
|
"Get current price, market cap, 24h change, and 7-day sparkline for a cryptocurrency. For longer price history (30d, 90d, 365d), use getCryptoPriceHistory instead.",
|
|
inputSchema: z.object({
|
|
coinId: z
|
|
.string()
|
|
.describe(
|
|
"CoinGecko coin ID (e.g., 'bitcoin', 'ethereum', 'solana', 'dogecoin', 'cardano')",
|
|
),
|
|
}),
|
|
execute: async ({ coinId }) => {
|
|
const url = `https://api.coingecko.com/api/v3/coins/${encodeURIComponent(coinId)}?localization=false&tickers=false&community_data=false&developer_data=false&sparkline=true`;
|
|
|
|
const res = await fetch(url, {
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
|
|
if (!res.ok) return handleFetchError(res, coinId);
|
|
|
|
const data = (await res.json()) as {
|
|
id: string;
|
|
symbol: string;
|
|
name: string;
|
|
market_data: {
|
|
current_price: { usd: number };
|
|
market_cap: { usd: number };
|
|
total_volume: { usd: number };
|
|
price_change_percentage_24h: number;
|
|
price_change_percentage_7d: number;
|
|
price_change_percentage_30d: number;
|
|
high_24h: { usd: number };
|
|
low_24h: { usd: number };
|
|
ath: { usd: number };
|
|
ath_date: { usd: string };
|
|
circulating_supply: number;
|
|
total_supply: number | null;
|
|
sparkline_7d: { price: number[] };
|
|
};
|
|
market_cap_rank: number;
|
|
};
|
|
|
|
const md = data.market_data;
|
|
|
|
// Convert sparkline (hourly array) to dated points
|
|
const now = Date.now();
|
|
const sparkline = md.sparkline_7d.price;
|
|
const step = Math.max(1, Math.floor(sparkline.length / 14));
|
|
const sparklineData = sparkline
|
|
.filter((_, i) => i % step === 0)
|
|
.map((price, i) => {
|
|
const hourIndex = i * step;
|
|
const ts = now - (sparkline.length - hourIndex) * 3600_000;
|
|
return {
|
|
date: new Date(ts).toLocaleDateString("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
}),
|
|
price: Math.round(price * 100) / 100,
|
|
};
|
|
});
|
|
|
|
return {
|
|
id: data.id,
|
|
symbol: data.symbol.toUpperCase(),
|
|
name: data.name,
|
|
rank: data.market_cap_rank,
|
|
price: md.current_price.usd,
|
|
marketCap: md.market_cap.usd,
|
|
volume24h: md.total_volume.usd,
|
|
change24h: Math.round(md.price_change_percentage_24h * 100) / 100,
|
|
change7d: Math.round(md.price_change_percentage_7d * 100) / 100,
|
|
change30d: Math.round(md.price_change_percentage_30d * 100) / 100,
|
|
high24h: md.high_24h.usd,
|
|
low24h: md.low_24h.usd,
|
|
allTimeHigh: md.ath.usd,
|
|
allTimeHighDate: md.ath_date.usd,
|
|
circulatingSupply: md.circulating_supply,
|
|
totalSupply: md.total_supply,
|
|
sparkline7d: sparklineData,
|
|
};
|
|
},
|
|
});
|
|
|
|
// =============================================================================
|
|
// getCryptoPriceHistory — flexible date range price history
|
|
// =============================================================================
|
|
|
|
export const getCryptoPriceHistory = tool({
|
|
description:
|
|
"Get historical price data for a cryptocurrency over a specified number of days (e.g., 30, 90, 365). Returns date-labeled data points suitable for charting.",
|
|
inputSchema: z.object({
|
|
coinId: z
|
|
.string()
|
|
.describe("CoinGecko coin ID (e.g., 'bitcoin', 'ethereum', 'solana')"),
|
|
days: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(365)
|
|
.describe("Number of days of history to fetch (e.g., 30, 90, 365)"),
|
|
}),
|
|
execute: async ({ coinId, days }) => {
|
|
const url = `https://api.coingecko.com/api/v3/coins/${encodeURIComponent(coinId)}/market_chart?vs_currency=usd&days=${days}`;
|
|
|
|
const res = await fetch(url, {
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
|
|
if (!res.ok) return handleFetchError(res, coinId);
|
|
|
|
const data = (await res.json()) as {
|
|
prices: [number, number][];
|
|
};
|
|
|
|
const priceHistory = sampleTimeSeries(data.prices, 20);
|
|
|
|
return {
|
|
coinId,
|
|
days,
|
|
priceHistory,
|
|
};
|
|
},
|
|
});
|