koishi-plugin-elysia-api-aggregator 0.2.1 → 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 +100 -39
- package/lib/index.mjs +100 -39
- package/package.json +1 -1
package/lib/index.cjs
CHANGED
|
@@ -278,52 +278,113 @@ function apply(ctx, config) {
|
|
|
278
278
|
getByType: /* @__PURE__ */ __name((type) => service.getByType(type), "getByType")
|
|
279
279
|
}
|
|
280
280
|
};
|
|
281
|
-
|
|
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;
|
|
312
|
+
if (config.debugMode) {
|
|
313
|
+
ctx.logger.info(`loadModels skipped (already running), queued pending reload (trigger=${trigger})`);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
isLoading = true;
|
|
282
318
|
const loadStartedAt = Date.now();
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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");
|
|
367
|
+
}
|
|
292
368
|
}
|
|
293
|
-
const manualModels = config.manualModels.map((m) => {
|
|
294
|
-
return {
|
|
295
|
-
id: m.id,
|
|
296
|
-
name: m.name,
|
|
297
|
-
source: "manual",
|
|
298
|
-
sourceName: m.sourceName,
|
|
299
|
-
baseUrl: m.baseUrl,
|
|
300
|
-
apiKey: m.apiKey,
|
|
301
|
-
platform: m.platform,
|
|
302
|
-
// 使用默认值
|
|
303
|
-
type: "llm",
|
|
304
|
-
maxTokens: 128e3,
|
|
305
|
-
visionCapable: false,
|
|
306
|
-
toolsCapable: false,
|
|
307
|
-
structuredOutput: false,
|
|
308
|
-
thinkingMode: "both",
|
|
309
|
-
available: true,
|
|
310
|
-
lastChecked: /* @__PURE__ */ new Date()
|
|
311
|
-
};
|
|
312
|
-
});
|
|
313
|
-
const allModels = [...fetchedModels, ...manualModels];
|
|
314
|
-
service.updateModels(allModels);
|
|
315
|
-
const totalCostMs = Date.now() - loadStartedAt;
|
|
316
|
-
ctx.logger.info(`[source] manual: ${manualModels.length} models`);
|
|
317
|
-
ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms)`);
|
|
318
|
-
ctx.emit("elysia-api/models-updated", [...allModels]);
|
|
319
369
|
}
|
|
320
370
|
__name(loadModels, "loadModels");
|
|
321
|
-
|
|
371
|
+
lastConfigHash = buildConfigHash();
|
|
372
|
+
ctx.on("ready", () => {
|
|
373
|
+
void loadModels("ready");
|
|
374
|
+
});
|
|
322
375
|
ctx.on("config", () => {
|
|
323
|
-
|
|
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");
|
|
324
385
|
});
|
|
325
386
|
ctx.command("elysia-api.models.reload", "重新加载模型列表").action(async () => {
|
|
326
|
-
await loadModels();
|
|
387
|
+
await loadModels("command");
|
|
327
388
|
const count = service.getAll().length;
|
|
328
389
|
return `已加载 ${count} 个模型`;
|
|
329
390
|
});
|
package/lib/index.mjs
CHANGED
|
@@ -252,52 +252,113 @@ function apply(ctx, config) {
|
|
|
252
252
|
getByType: /* @__PURE__ */ __name((type) => service.getByType(type), "getByType")
|
|
253
253
|
}
|
|
254
254
|
};
|
|
255
|
-
|
|
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;
|
|
286
|
+
if (config.debugMode) {
|
|
287
|
+
ctx.logger.info(`loadModels skipped (already running), queued pending reload (trigger=${trigger})`);
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
isLoading = true;
|
|
256
292
|
const loadStartedAt = Date.now();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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");
|
|
341
|
+
}
|
|
266
342
|
}
|
|
267
|
-
const manualModels = config.manualModels.map((m) => {
|
|
268
|
-
return {
|
|
269
|
-
id: m.id,
|
|
270
|
-
name: m.name,
|
|
271
|
-
source: "manual",
|
|
272
|
-
sourceName: m.sourceName,
|
|
273
|
-
baseUrl: m.baseUrl,
|
|
274
|
-
apiKey: m.apiKey,
|
|
275
|
-
platform: m.platform,
|
|
276
|
-
// 使用默认值
|
|
277
|
-
type: "llm",
|
|
278
|
-
maxTokens: 128e3,
|
|
279
|
-
visionCapable: false,
|
|
280
|
-
toolsCapable: false,
|
|
281
|
-
structuredOutput: false,
|
|
282
|
-
thinkingMode: "both",
|
|
283
|
-
available: true,
|
|
284
|
-
lastChecked: /* @__PURE__ */ new Date()
|
|
285
|
-
};
|
|
286
|
-
});
|
|
287
|
-
const allModels = [...fetchedModels, ...manualModels];
|
|
288
|
-
service.updateModels(allModels);
|
|
289
|
-
const totalCostMs = Date.now() - loadStartedAt;
|
|
290
|
-
ctx.logger.info(`[source] manual: ${manualModels.length} models`);
|
|
291
|
-
ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms)`);
|
|
292
|
-
ctx.emit("elysia-api/models-updated", [...allModels]);
|
|
293
343
|
}
|
|
294
344
|
__name(loadModels, "loadModels");
|
|
295
|
-
|
|
345
|
+
lastConfigHash = buildConfigHash();
|
|
346
|
+
ctx.on("ready", () => {
|
|
347
|
+
void loadModels("ready");
|
|
348
|
+
});
|
|
296
349
|
ctx.on("config", () => {
|
|
297
|
-
|
|
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");
|
|
298
359
|
});
|
|
299
360
|
ctx.command("elysia-api.models.reload", "重新加载模型列表").action(async () => {
|
|
300
|
-
await loadModels();
|
|
361
|
+
await loadModels("command");
|
|
301
362
|
const count = service.getAll().length;
|
|
302
363
|
return `已加载 ${count} 个模型`;
|
|
303
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",
|