Setting the file. One moment. Provider Registry · Mastra · mastra-ai/skills · Skills DocsReference
scripts/provider-registry.mjs
JavaScript·180 lines·6 KB
() {
10 const rel = join('node_modules', '@mastra', 'core', 'dist', 'provider-registry.json');
11 // Walk up from script location to find project root with node_modules
12 let dir = __dirname;
13 for (let i = 0; i < 10; i++) {
14 try {
15 const p = join(dir, rel);
16 readFileSync(p, "utf-8");
17 return p;
18 } catch {
19 dir = dirname(dir);
20 }
21 }
22 // Fall back to cwd
23 return join(process.cwd(), rel);
24}
25
26function loadRegistry() {
27 const path = findRegistryPath();
28 try {
29 return JSON.parse(readFileSync(path, "utf-8"));
30 } catch (e) {
31 console.error(`Error: Could not load provider registry at ${path}`);
32 console.error(e.message);
33 process.exit(1);
34 }
35}
36
37/**
38 * Extract version numbers from a model name for sorting.
39 * Returns an array of numeric segments, e.g. "gpt-5.4" → [5, 4].
40 * Handles dot-separated (3.5), hyphen-separated (3-7), and mixed formats.
41 * Models without detectable version numbers return null.
42 */
43function extractVersion(name) {
44 // Use named capture to grab version-like sequences along with their context.
45 // We capture digits separated by dots/hyphens, plus any trailing letter for filtering.
46 const regex = /(\d+(?:[.\-]\d+)*)([a-zA-Z])?/g;
47 const candidates = [];
48 let match;
49 while ((match = regex.exec(name)) !== null) {
50 const numStr = match[1];
51 const suffix = match[2] || "";
52 candidates.push({ numStr, suffix, index: match.index });
53 }
54 if (candidates.length === 0) return null;
55
56 // Process candidates: filter and clean up non-version parts
57 const processed = [];
58 for (const c of candidates) {
59 let parts = c.numStr.split(/[.\-]/).map(Number);
60 // If followed by a size suffix (b/B/k/K/m/M/t/T) — e.g. "8b", "70B", "1t" —
61 // strip the last numeric part (param count) but keep earlier parts as the version
62 if (/^[bBkKmMtT]$/.test(c.suffix)) {
63 parts = parts.slice(0, -1);
64 if (parts.length === 0) continue;
65 }
66 // Strip date-like segments (>= 2020 or YYYYMMDD-style 8-digit numbers)
67 parts = parts.filter((p) => p < 2020);
68 if (parts.length === 0) continue;
69 // Skip very large standalone numbers (parameter counts, IDs)
70 if (parts.length === 1 && parts[0] >= 100 && candidates.length > 1) continue;
71 // Skip trailing date-like patterns (MM-DD) in the latter half of the name
72 if (
73 parts.length === 2 &&
74 parts[0] >= 1 && parts[0] <= 12 &&
75 parts[1] >= 1 && parts[1] <= 31 &&
76 c.index > name.length / 2 &&
77 candidates.length > 1
78 ) continue;
79 processed.push(parts);
80 }
81
82 if (processed.length === 0) return null;
83
84 // Return the first valid version candidate (versions appear early in model names)
85 return processed[0];
86}
87
88function compareVersionsDesc(a, b) {
89 const va = extractVersion(a);
90 const vb = extractVersion(b);
91
92 // Models without versions go to the end
93 if (!va && !vb) return a.localeCompare(b);
94 if (!va) return 1;
95 if (!vb) return -1;
96
97 // Compare version tuples numerically, descending
98 const len = Math.max(va.length, vb.length);
99 for (let i = 0; i < len; i++) {
100 const ai = va[i] ?? 0;
101 const bi = vb[i] ?? 0;
102 if (bi !== ai) return bi - ai;
103 }
104 // Same version — secondary sort by full name descending
105 return b.localeCompare(a);
106}
107
108function printUsage() {
109 console.log(`Usage: provider-registry.mjs [options]
110
111Options:
112 --list List all available model providers
113 --provider <name> List all models for a provider (sorted newest first)
114 --help Show this help message
115
116Examples:
117 node provider-registry.mjs --list
118 node provider-registry.mjs --provider openai
119 node provider-registry.mjs --provider anthropic`);
120}
121
122function listProviders(registry) {
123 const entries = Object.entries(registry.providers)
124 .map(([key, val]) => ({ key, name: val.name || key }))
125 .sort((a, b) => a.key.localeCompare(b.key));
126
127 const maxKey = Math.max(...entries.map((e) => e.key.length));
128 const maxName = Math.max(...entries.map((e) => e.name.length));
129
130 console.log(`${"PROVIDER".padEnd(maxKey)} ${"NAME".padEnd(maxName)} MODELS`);
131 console.log(`${"─".repeat(maxKey)} ${"─".repeat(maxName)} ${"─".repeat(6)}`);
132 for (const entry of entries) {
133 const modelCount = registry.providers[entry.key].models.length;
134 console.log(`${entry.key.padEnd(maxKey)} ${entry.name.padEnd(maxName)} ${modelCount}`);
135 }
136 console.log(`\n${entries.length} providers`);
137}
138
139function listModels(registry, providerName) {
140 const provider = registry.providers[providerName];
141 if (!provider) {
142 console.error(`Error: Provider "${providerName}" not found.`);
143 console.error(`Run with --list to see available providers.`);
144 process.exit(1);
145 }
146
147 const models = [...provider.models].sort(compareVersionsDesc);
148
149 console.log(`${provider.name || providerName} — ${models.length} models\n`);
150 for (const model of models) {
151 console.log(` ${model}`);
152 }
153}
154
155const args = process.argv.slice(2);
156
157if (args.includes("--help") || args.length === 0) {
158 printUsage();
159 process.exit(0);
160}
161
162if (args.includes("--list")) {
163 listProviders(loadRegistry());
164 process.exit(0);
165}
166
167const providerIdx = args.indexOf("--provider");
168if (providerIdx !== -1) {
169 const name = args[providerIdx + 1];
170 if (!name) {
171 console.error("Error: --provider requires a provider name.");
172 process.exit(1);
173 }
174 listModels(loadRegistry(), name);
175 process.exit(0);
176}
177
178console.error("Error: Unknown arguments:", args.join(" "));
179printUsage();
180process.exit(1);