File size: 3,380 Bytes
ffba252
 
ee20373
 
d135f12
ffba252
 
 
 
ee20373
ffba252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d135f12
ffba252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee20373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ffba252
ee20373
ffba252
ee20373
ffba252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
'use strict';

const { loadEnv } = require('../load-env');
loadEnv();
const { getJson } = require('../fetch-utils');

const API_URL = 'https://router.requesty.ai/v1/models';

function loadApiKey() {
  return process.env.REQUESTY_API_KEY || null;
}

const toPerMillion = (val) => (val ? Math.round(parseFloat(val) * 1_000_000 * 10000) / 10000 : 0);

const getSizeB = (id) => {
  const match = (id || '').match(/[^.\d](\d+)b/i) || (id || '').match(/^(\d+)b/i);
  return match ? parseInt(match[1]) : undefined;
};

async function fetchRequesty() {
  const apiKey = loadApiKey();
  if (!apiKey) {
    console.warn('  (no REQUESTY_API_KEY found – skipping Requesty)');
    return [];
  }

  const data = await getJson(API_URL, {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: 'application/json',
    },
  });

  const models = [];

  for (const model of data.data || []) {
    const inputPrice = toPerMillion(model.input_price);
    const outputPrice = toPerMillion(model.output_price);

    // Skip free/zero-priced entries
    if (inputPrice <= 0 && outputPrice <= 0) continue;

    const caps = [];
    if (model.supports_vision) caps.push('vision');
    if (model.supports_reasoning) caps.push('reasoning');
    if (model.supports_tool_calls) caps.push('tools');

    const baseType = model.api === 'chat' ? 'chat' : model.api;
    const type = (baseType === 'chat' && model.supports_vision) ? 'vision' : baseType;

    const modelEntry = {
      name: model.id,
      type,
      input_price_per_1m: inputPrice,
      output_price_per_1m: outputPrice,
      currency: 'USD',
    };

    if (caps.length) modelEntry.capabilities = caps;
    if (model.context_window) modelEntry.context_window = model.context_window;

    const size_b = getSizeB(model.id);
    if (size_b) modelEntry.size_b = size_b;

    models.push(modelEntry);
  }

  // Deduplicate @region and :effort variants — keep one entry per canonical base ID.
  // e.g. "anthropic/claude-3-7-sonnet@us-east-2" and "anthropic/claude-3-7-sonnet:high"
  // both collapse to "anthropic/claude-3-7-sonnet".
  const canonicalId = (id) => id.replace(/@[^/]+$/, '').replace(/:[^/]+$/, '');
  const seen = new Map();
  for (const model of models) {
    const base = canonicalId(model.name);
    if (!seen.has(base)) {
      // Store with canonical name
      seen.set(base, { ...model, name: base });
    } else {
      // Prefer lower input price if already present
      const existing = seen.get(base);
      if (model.input_price_per_1m < existing.input_price_per_1m) {
        seen.set(base, { ...model, name: base });
      }
    }
  }
  const deduped = [...seen.values()];

  // Sort by input price
  deduped.sort((a, b) => a.input_price_per_1m - b.input_price_per_1m);

  return deduped;
}

module.exports = { fetchRequesty, providerName: 'Requesty' };

// Run standalone: node scripts/providers/requesty.js
if (require.main === module) {
  fetchRequesty()
    .then((models) => {
      console.log(`Fetched ${models.length} models from Requesty API\n`);
      models.slice(0, 10).forEach((m) =>
        console.log(`  ${m.name.padEnd(55)} $${m.input_price_per_1m} / $${m.output_price_per_1m}`)
      );
      if (models.length > 10) console.log(`  ... and ${models.length - 10} more`);
    })
    .catch((err) => {
      console.error('Error:', err.message);
      process.exit(1);
    });
}