koishi-plugin-elysia-api-aggregator 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +97 -59
- package/lib/index.mjs +97 -59
- package/package.json +1 -1
package/lib/index.cjs
CHANGED
|
@@ -39,7 +39,6 @@ var ModelFetcher = class {
|
|
|
39
39
|
__name(this, "ModelFetcher");
|
|
40
40
|
}
|
|
41
41
|
async fetchModels(source) {
|
|
42
|
-
this.ctx.logger.info(`Fetching models from ${source.name} (${source.platform})`);
|
|
43
42
|
try {
|
|
44
43
|
switch (source.platform) {
|
|
45
44
|
case "openai":
|
|
@@ -279,74 +278,113 @@ function apply(ctx, config) {
|
|
|
279
278
|
getByType: /* @__PURE__ */ __name((type) => service.getByType(type), "getByType")
|
|
280
279
|
}
|
|
281
280
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
281
|
+
let isLoading = false;
|
|
282
|
+
let pendingReload = false;
|
|
283
|
+
let lastModelsHash = "";
|
|
284
|
+
let lastConfigHash = "";
|
|
285
|
+
const buildConfigHash = /* @__PURE__ */ __name(() => {
|
|
286
|
+
return JSON.stringify({
|
|
287
|
+
autoFetchSources: config.autoFetchSources,
|
|
288
|
+
manualModels: config.manualModels
|
|
289
|
+
});
|
|
290
|
+
}, "buildConfigHash");
|
|
291
|
+
const buildModelsHash = /* @__PURE__ */ __name((models) => {
|
|
292
|
+
const normalized = [...models].map((m) => ({
|
|
293
|
+
id: m.id,
|
|
294
|
+
name: m.name,
|
|
295
|
+
source: m.source,
|
|
296
|
+
sourceName: m.sourceName,
|
|
297
|
+
baseUrl: m.baseUrl,
|
|
298
|
+
platform: m.platform,
|
|
299
|
+
type: m.type,
|
|
300
|
+
maxTokens: m.maxTokens,
|
|
301
|
+
visionCapable: m.visionCapable,
|
|
302
|
+
toolsCapable: m.toolsCapable,
|
|
303
|
+
structuredOutput: m.structuredOutput,
|
|
304
|
+
thinkingMode: m.thinkingMode,
|
|
305
|
+
available: m.available
|
|
306
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
307
|
+
return JSON.stringify(normalized);
|
|
308
|
+
}, "buildModelsHash");
|
|
309
|
+
async function loadModels(trigger = "config") {
|
|
310
|
+
if (isLoading) {
|
|
311
|
+
pendingReload = true;
|
|
296
312
|
if (config.debugMode) {
|
|
297
|
-
ctx.logger.info(`loadModels
|
|
298
|
-
} else {
|
|
299
|
-
ctx.logger.info(`Fetched ${sourceModels.length} models from ${source.name}`);
|
|
313
|
+
ctx.logger.info(`loadModels skipped (already running), queued pending reload (trigger=${trigger})`);
|
|
300
314
|
}
|
|
315
|
+
return;
|
|
301
316
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
317
|
+
isLoading = true;
|
|
318
|
+
const loadStartedAt = Date.now();
|
|
319
|
+
try {
|
|
320
|
+
ctx.logger.info("Loading models...");
|
|
321
|
+
const fetchedModels = [];
|
|
322
|
+
for (const source of config.autoFetchSources) {
|
|
323
|
+
if (!source.enabled) continue;
|
|
324
|
+
const sourceStartedAt = Date.now();
|
|
325
|
+
const sourceModels = await fetcher.fetchModels(source);
|
|
326
|
+
fetchedModels.push(...sourceModels);
|
|
327
|
+
const sourceCostMs = Date.now() - sourceStartedAt;
|
|
328
|
+
ctx.logger.info(`[source] ${source.name}: ${sourceModels.length} models (${sourceCostMs}ms)`);
|
|
329
|
+
}
|
|
330
|
+
const manualModels = config.manualModels.map((m) => {
|
|
331
|
+
return {
|
|
332
|
+
id: m.id,
|
|
333
|
+
name: m.name,
|
|
334
|
+
source: "manual",
|
|
335
|
+
sourceName: m.sourceName,
|
|
336
|
+
baseUrl: m.baseUrl,
|
|
337
|
+
apiKey: m.apiKey,
|
|
338
|
+
platform: m.platform,
|
|
339
|
+
// 使用默认值
|
|
340
|
+
type: "llm",
|
|
341
|
+
maxTokens: 128e3,
|
|
342
|
+
visionCapable: false,
|
|
343
|
+
toolsCapable: false,
|
|
344
|
+
structuredOutput: false,
|
|
345
|
+
thinkingMode: "both",
|
|
346
|
+
available: true,
|
|
347
|
+
lastChecked: /* @__PURE__ */ new Date()
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
const allModels = [...fetchedModels, ...manualModels];
|
|
351
|
+
service.updateModels(allModels);
|
|
352
|
+
const totalCostMs = Date.now() - loadStartedAt;
|
|
353
|
+
ctx.logger.info(`[source] manual: ${manualModels.length} models`);
|
|
354
|
+
const modelsHash = buildModelsHash(allModels);
|
|
355
|
+
if (modelsHash === lastModelsHash) {
|
|
356
|
+
ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms, unchanged)`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
lastModelsHash = modelsHash;
|
|
360
|
+
ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms)`);
|
|
361
|
+
ctx.emit("elysia-api/models-updated", [...allModels]);
|
|
362
|
+
} finally {
|
|
363
|
+
isLoading = false;
|
|
364
|
+
if (pendingReload) {
|
|
365
|
+
pendingReload = false;
|
|
366
|
+
void loadModels("pending");
|
|
308
367
|
}
|
|
309
|
-
return {
|
|
310
|
-
id: m.id,
|
|
311
|
-
name: m.name,
|
|
312
|
-
source: "manual",
|
|
313
|
-
sourceName: m.sourceName,
|
|
314
|
-
baseUrl: m.baseUrl,
|
|
315
|
-
apiKey: m.apiKey,
|
|
316
|
-
platform: m.platform,
|
|
317
|
-
// 使用默认值
|
|
318
|
-
type: "llm",
|
|
319
|
-
maxTokens: 128e3,
|
|
320
|
-
visionCapable: false,
|
|
321
|
-
toolsCapable: false,
|
|
322
|
-
structuredOutput: false,
|
|
323
|
-
thinkingMode: "both",
|
|
324
|
-
available: true,
|
|
325
|
-
lastChecked: /* @__PURE__ */ new Date()
|
|
326
|
-
};
|
|
327
|
-
});
|
|
328
|
-
const allModels = [...fetchedModels, ...manualModels];
|
|
329
|
-
service.updateModels(allModels);
|
|
330
|
-
if (config.debugMode) {
|
|
331
|
-
ctx.logger.info(`loadModels: Total models loaded: ${allModels.length}`);
|
|
332
|
-
ctx.logger.info(`loadModels: Model IDs: ${allModels.map((m) => m.id).join(", ")}`);
|
|
333
|
-
ctx.logger.info(`loadModels: ctx.elysiaApi exists: ${ctx.elysiaApi != null}`);
|
|
334
|
-
ctx.logger.info(`loadModels: ctx.elysiaApi.models exists: ${ctx.elysiaApi?.models != null}`);
|
|
335
|
-
} else {
|
|
336
|
-
ctx.logger.info(`Total models loaded: ${allModels.length}`);
|
|
337
|
-
}
|
|
338
|
-
if (config.debugMode) {
|
|
339
|
-
ctx.logger.info(`loadModels: Emitting elysia-api/models-updated event with ${allModels.length} models`);
|
|
340
368
|
}
|
|
341
|
-
ctx.emit("elysia-api/models-updated", [...allModels]);
|
|
342
369
|
}
|
|
343
370
|
__name(loadModels, "loadModels");
|
|
344
|
-
|
|
371
|
+
lastConfigHash = buildConfigHash();
|
|
372
|
+
ctx.on("ready", () => {
|
|
373
|
+
void loadModels("ready");
|
|
374
|
+
});
|
|
345
375
|
ctx.on("config", () => {
|
|
346
|
-
|
|
376
|
+
const configHash = buildConfigHash();
|
|
377
|
+
if (configHash === lastConfigHash) {
|
|
378
|
+
if (config.debugMode) {
|
|
379
|
+
ctx.logger.info("aggregator: config event ignored (aggregator config unchanged)");
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
lastConfigHash = configHash;
|
|
384
|
+
void loadModels("config");
|
|
347
385
|
});
|
|
348
386
|
ctx.command("elysia-api.models.reload", "重新加载模型列表").action(async () => {
|
|
349
|
-
await loadModels();
|
|
387
|
+
await loadModels("command");
|
|
350
388
|
const count = service.getAll().length;
|
|
351
389
|
return `已加载 ${count} 个模型`;
|
|
352
390
|
});
|
package/lib/index.mjs
CHANGED
|
@@ -13,7 +13,6 @@ var ModelFetcher = class {
|
|
|
13
13
|
__name(this, "ModelFetcher");
|
|
14
14
|
}
|
|
15
15
|
async fetchModels(source) {
|
|
16
|
-
this.ctx.logger.info(`Fetching models from ${source.name} (${source.platform})`);
|
|
17
16
|
try {
|
|
18
17
|
switch (source.platform) {
|
|
19
18
|
case "openai":
|
|
@@ -253,74 +252,113 @@ function apply(ctx, config) {
|
|
|
253
252
|
getByType: /* @__PURE__ */ __name((type) => service.getByType(type), "getByType")
|
|
254
253
|
}
|
|
255
254
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
255
|
+
let isLoading = false;
|
|
256
|
+
let pendingReload = false;
|
|
257
|
+
let lastModelsHash = "";
|
|
258
|
+
let lastConfigHash = "";
|
|
259
|
+
const buildConfigHash = /* @__PURE__ */ __name(() => {
|
|
260
|
+
return JSON.stringify({
|
|
261
|
+
autoFetchSources: config.autoFetchSources,
|
|
262
|
+
manualModels: config.manualModels
|
|
263
|
+
});
|
|
264
|
+
}, "buildConfigHash");
|
|
265
|
+
const buildModelsHash = /* @__PURE__ */ __name((models) => {
|
|
266
|
+
const normalized = [...models].map((m) => ({
|
|
267
|
+
id: m.id,
|
|
268
|
+
name: m.name,
|
|
269
|
+
source: m.source,
|
|
270
|
+
sourceName: m.sourceName,
|
|
271
|
+
baseUrl: m.baseUrl,
|
|
272
|
+
platform: m.platform,
|
|
273
|
+
type: m.type,
|
|
274
|
+
maxTokens: m.maxTokens,
|
|
275
|
+
visionCapable: m.visionCapable,
|
|
276
|
+
toolsCapable: m.toolsCapable,
|
|
277
|
+
structuredOutput: m.structuredOutput,
|
|
278
|
+
thinkingMode: m.thinkingMode,
|
|
279
|
+
available: m.available
|
|
280
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
281
|
+
return JSON.stringify(normalized);
|
|
282
|
+
}, "buildModelsHash");
|
|
283
|
+
async function loadModels(trigger = "config") {
|
|
284
|
+
if (isLoading) {
|
|
285
|
+
pendingReload = true;
|
|
270
286
|
if (config.debugMode) {
|
|
271
|
-
ctx.logger.info(`loadModels
|
|
272
|
-
} else {
|
|
273
|
-
ctx.logger.info(`Fetched ${sourceModels.length} models from ${source.name}`);
|
|
287
|
+
ctx.logger.info(`loadModels skipped (already running), queued pending reload (trigger=${trigger})`);
|
|
274
288
|
}
|
|
289
|
+
return;
|
|
275
290
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
291
|
+
isLoading = true;
|
|
292
|
+
const loadStartedAt = Date.now();
|
|
293
|
+
try {
|
|
294
|
+
ctx.logger.info("Loading models...");
|
|
295
|
+
const fetchedModels = [];
|
|
296
|
+
for (const source of config.autoFetchSources) {
|
|
297
|
+
if (!source.enabled) continue;
|
|
298
|
+
const sourceStartedAt = Date.now();
|
|
299
|
+
const sourceModels = await fetcher.fetchModels(source);
|
|
300
|
+
fetchedModels.push(...sourceModels);
|
|
301
|
+
const sourceCostMs = Date.now() - sourceStartedAt;
|
|
302
|
+
ctx.logger.info(`[source] ${source.name}: ${sourceModels.length} models (${sourceCostMs}ms)`);
|
|
303
|
+
}
|
|
304
|
+
const manualModels = config.manualModels.map((m) => {
|
|
305
|
+
return {
|
|
306
|
+
id: m.id,
|
|
307
|
+
name: m.name,
|
|
308
|
+
source: "manual",
|
|
309
|
+
sourceName: m.sourceName,
|
|
310
|
+
baseUrl: m.baseUrl,
|
|
311
|
+
apiKey: m.apiKey,
|
|
312
|
+
platform: m.platform,
|
|
313
|
+
// 使用默认值
|
|
314
|
+
type: "llm",
|
|
315
|
+
maxTokens: 128e3,
|
|
316
|
+
visionCapable: false,
|
|
317
|
+
toolsCapable: false,
|
|
318
|
+
structuredOutput: false,
|
|
319
|
+
thinkingMode: "both",
|
|
320
|
+
available: true,
|
|
321
|
+
lastChecked: /* @__PURE__ */ new Date()
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
const allModels = [...fetchedModels, ...manualModels];
|
|
325
|
+
service.updateModels(allModels);
|
|
326
|
+
const totalCostMs = Date.now() - loadStartedAt;
|
|
327
|
+
ctx.logger.info(`[source] manual: ${manualModels.length} models`);
|
|
328
|
+
const modelsHash = buildModelsHash(allModels);
|
|
329
|
+
if (modelsHash === lastModelsHash) {
|
|
330
|
+
ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms, unchanged)`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
lastModelsHash = modelsHash;
|
|
334
|
+
ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms)`);
|
|
335
|
+
ctx.emit("elysia-api/models-updated", [...allModels]);
|
|
336
|
+
} finally {
|
|
337
|
+
isLoading = false;
|
|
338
|
+
if (pendingReload) {
|
|
339
|
+
pendingReload = false;
|
|
340
|
+
void loadModels("pending");
|
|
282
341
|
}
|
|
283
|
-
return {
|
|
284
|
-
id: m.id,
|
|
285
|
-
name: m.name,
|
|
286
|
-
source: "manual",
|
|
287
|
-
sourceName: m.sourceName,
|
|
288
|
-
baseUrl: m.baseUrl,
|
|
289
|
-
apiKey: m.apiKey,
|
|
290
|
-
platform: m.platform,
|
|
291
|
-
// 使用默认值
|
|
292
|
-
type: "llm",
|
|
293
|
-
maxTokens: 128e3,
|
|
294
|
-
visionCapable: false,
|
|
295
|
-
toolsCapable: false,
|
|
296
|
-
structuredOutput: false,
|
|
297
|
-
thinkingMode: "both",
|
|
298
|
-
available: true,
|
|
299
|
-
lastChecked: /* @__PURE__ */ new Date()
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
const allModels = [...fetchedModels, ...manualModels];
|
|
303
|
-
service.updateModels(allModels);
|
|
304
|
-
if (config.debugMode) {
|
|
305
|
-
ctx.logger.info(`loadModels: Total models loaded: ${allModels.length}`);
|
|
306
|
-
ctx.logger.info(`loadModels: Model IDs: ${allModels.map((m) => m.id).join(", ")}`);
|
|
307
|
-
ctx.logger.info(`loadModels: ctx.elysiaApi exists: ${ctx.elysiaApi != null}`);
|
|
308
|
-
ctx.logger.info(`loadModels: ctx.elysiaApi.models exists: ${ctx.elysiaApi?.models != null}`);
|
|
309
|
-
} else {
|
|
310
|
-
ctx.logger.info(`Total models loaded: ${allModels.length}`);
|
|
311
|
-
}
|
|
312
|
-
if (config.debugMode) {
|
|
313
|
-
ctx.logger.info(`loadModels: Emitting elysia-api/models-updated event with ${allModels.length} models`);
|
|
314
342
|
}
|
|
315
|
-
ctx.emit("elysia-api/models-updated", [...allModels]);
|
|
316
343
|
}
|
|
317
344
|
__name(loadModels, "loadModels");
|
|
318
|
-
|
|
345
|
+
lastConfigHash = buildConfigHash();
|
|
346
|
+
ctx.on("ready", () => {
|
|
347
|
+
void loadModels("ready");
|
|
348
|
+
});
|
|
319
349
|
ctx.on("config", () => {
|
|
320
|
-
|
|
350
|
+
const configHash = buildConfigHash();
|
|
351
|
+
if (configHash === lastConfigHash) {
|
|
352
|
+
if (config.debugMode) {
|
|
353
|
+
ctx.logger.info("aggregator: config event ignored (aggregator config unchanged)");
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
lastConfigHash = configHash;
|
|
358
|
+
void loadModels("config");
|
|
321
359
|
});
|
|
322
360
|
ctx.command("elysia-api.models.reload", "重新加载模型列表").action(async () => {
|
|
323
|
-
await loadModels();
|
|
361
|
+
await loadModels("command");
|
|
324
362
|
const count = service.getAll().length;
|
|
325
363
|
return `已加载 ${count} 个模型`;
|
|
326
364
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-elysia-api-aggregator",
|
|
3
3
|
"description": "Inspired by New-API, the Elysia-API model aggregator plugin allows automatic fetching and manual configuration of available AI models, designed to work with the orchestrator plugin.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"main": "lib/index.cjs",
|
|
6
6
|
"module": "lib/index.mjs",
|
|
7
7
|
"typings": "lib/index.d.ts",
|