registry.js
2.05 KB
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
/**
* Provider 自动注册表 —— 扫描 `providers/` 目录,按文件名 `llm.<apiType>.js` 建表。
*
* 「加厂商 = 加一个文件」:放进 `providers/llm.<x>.js` 并默认导出一个继承 LLMBase 的类,
* 即被发现,无需改本文件或任何现有文件。注册键 = 文件名中 `llm.` 与 `.js` 之间的段。
*
* 不在模块顶层执行扫描(边界纪律:无顶层副作用);`loadRegistry()` 被 `createLLM` 调用时才读目录。
* 目录内只有真实厂商适配器:假 provider 已删除,网关不提供任何「不发真实请求」的实现。
*/
import { readdirSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
const here = path.dirname(fileURLToPath(import.meta.url))
const PROVIDERS_DIR = path.join(here, 'providers')
/** 文件名形如 `llm.openai.js` / `llm.azure_foundry.js` → 捕获 provider key。 */
const FILE_RE = /^llm\.([a-z0-9_-]+)\.js$/
/**
* 扫描 providers/ 目录,返回 `{ apiType: () => Promise<ProviderClass> }` 的惰性加载表。
* 惰性:只在真正 `createLLM({ provider })` 命中某 apiType 时才 dynamic import 该文件,
* 避免加载未用到的子类(也避免任一子类的加载错误污染整张表)。
* @param {string} [dir] 覆盖扫描目录(仅供测试自定义 providers 目录)。
* @returns {Record<string, () => Promise<{ default: new (cfg: any) => any }>>}
*/
export function loadRegistry(dir = PROVIDERS_DIR) {
/** @type {Record<string, () => Promise<{ default: new (cfg: any) => any }>>} */
const table = {}
for (const name of readdirSync(dir)) {
const m = FILE_RE.exec(name)
if (!m) continue
const apiType = m[1]
const fileUrl = pathToFileURL(path.join(dir, name)).href
table[apiType] = () => import(fileUrl)
}
return table
}
/**
* 列出已注册的 apiType(用于未知 provider 的报错信息)。
* @param {string} [dir]
* @returns {string[]}
*/
export function registeredProviders(dir = PROVIDERS_DIR) {
return Object.keys(loadRegistry(dir)).sort()
}