import { defineSource } from '@jaspers-ai/sdk' import { z } from 'zod' import { HOSTS, MassiveError, baseField, daysAgo, get, type Params } from './client' // Calls a few endpoints at once and says which the user's plan allows: OK, not authorized, rate-limited. interface Check { endpoint: string source: string path: string params?: Params } const BASIC: Check[] = [ { endpoint: 'AAPL day bars', source: 'aggregates', path: `/v2/aggs/ticker/AAPL/range/1/day/${daysAgo(10)}/${daysAgo(0)}`, params: { limit: 5 } }, { endpoint: 'AAPL previous close', source: 'previous-close', path: '/v2/aggs/ticker/AAPL/prev' }, { endpoint: 'AAPL stock snapshot', source: 'snapshot', path: '/v2/snapshot/locale/us/markets/stocks/tickers/AAPL' }, { endpoint: 'SPY option chain', source: 'option-chain', path: '/v3/snapshot/options/SPY', params: { limit: 1 } }, { endpoint: 'AAPL news', source: 'news', path: '/v2/reference/news', params: { ticker: 'AAPL', limit: 1 } }, { endpoint: 'Market status', source: 'market-status', path: '/v1/marketstatus/now' }, ] const EXTENDED: Check[] = [ { endpoint: 'AAPL trades', source: 'trades', path: '/v3/trades/AAPL', params: { limit: 1 } }, { endpoint: 'AAPL quotes', source: 'quotes', path: '/v3/quotes/AAPL', params: { limit: 1 } }, { endpoint: 'AAPL last trade', source: 'last-trade', path: '/v2/last/trade/AAPL' }, { endpoint: 'AAPL financials', source: 'financials', path: '/vX/reference/financials', params: { ticker: 'AAPL', limit: 1 } }, { endpoint: 'AAPL ticker details', source: 'ticker-details', path: '/v3/reference/tickers/AAPL' }, { endpoint: 'AAPL SMA', source: 'indicator', path: '/v1/indicators/sma/AAPL', params: { timespan: 'day', window: 50, limit: 1 } }, { endpoint: 'SPY option contracts', source: 'option-contracts', path: '/v3/reference/options/contracts', params: { underlying_ticker: 'SPY', limit: 1 } }, { endpoint: 'Gainers', source: 'gainers-losers', path: '/v2/snapshot/locale/us/markets/stocks/gainers' }, { endpoint: 'SPX index snapshot', source: 'index-snapshot', path: '/v3/snapshot/indices', params: { 'ticker.any_of': 'I:SPX' } }, { endpoint: 'EUR/USD forex snapshot', source: 'forex-snapshot', path: '/v2/snapshot/locale/global/markets/forex/tickers/C:EURUSD' }, { endpoint: 'BTC/USD crypto snapshot', source: 'crypto-snapshot', path: '/v2/snapshot/locale/global/markets/crypto/tickers/X:BTCUSD' }, ] /** What came back, in a few words: a row count, delayed data, or a per-ticker refusal inside a 200. */ function readBody(body: any): { status: string; detail: string } { const results = body?.results const first = Array.isArray(results) ? results[0] : undefined if (first?.error) return { status: first.error === 'NOT_AUTHORIZED' ? 'not authorized' : 'error', detail: String(first.message ?? first.error) } const count = Array.isArray(results) ? results.length : Array.isArray(body?.tickers) ? body.tickers.length : Array.isArray(body) ? body.length : 1 const delayed = body?.status === 'DELAYED' ? ', delayed data' : '' return { status: 'OK', detail: `${count} result${count === 1 ? '' : 's'}${delayed}` } } function classify(error: unknown): { status: string; http: number | null; detail: string } { const message = error instanceof Error ? error.message : String(error) if (error instanceof MassiveError) { const s = error.status const status = s === 403 ? 'not authorized' : s === 429 ? 'rate-limited' : s === 408 ? 'timeout' : s === 401 ? 'bad key' : 'error' return { status, http: s, detail: message } } return { status: 'error', http: null, detail: message } } export const selfCheck = defineSource({ description: "Checks which Massive endpoints the user's plan allows, by calling each once with a small request: one row per endpoint with status (OK, not authorized, rate-limited, timeout, error), the HTTP status, and Massive's message. extended adds more endpoints; the free plan allows 5 calls a minute, so later ones may show rate-limited.", hosts: HOSTS, input: z.object({ extended: z.boolean().default(false).describe('Also check trades, quotes, financials, indices, forex, crypto, and gainers.'), base: baseField, }), async run(args, ctx) { const checks = args.extended ? [...BASIC, ...EXTENDED] : BASIC const settled = await Promise.allSettled(checks.map((c) => get(ctx, args.base, c.path, c.params ?? {}))) return settled.map((s, i) => { const c = checks[i]! const outcome = s.status === 'fulfilled' ? { ...readBody(s.value), http: 200 } : classify(s.reason) return { endpoint: c.endpoint, source: `massive/${c.source}`, status: outcome.status, http: outcome.http, detail: outcome.detail, path: c.path } }) }, })