koishi-plugin-elysia-api-aggregator 0.2.4 → 0.2.5

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/config.d.ts CHANGED
@@ -23,6 +23,7 @@ export type AggregatorSourceConfig = AutoFetchAggregatorSource | ManualAggregato
23
23
  export interface Config {
24
24
  sources: AggregatorSourceConfig[];
25
25
  debugMode?: boolean;
26
+ verboseLog?: boolean;
26
27
  }
27
28
  export declare const Config: Schema<Config>;
28
29
  export declare const name = "elysia-api-aggregator";
package/lib/index.cjs CHANGED
@@ -262,32 +262,27 @@ var sourceBaseSchema = import_koishi.Schema.object({
262
262
  import_koishi.Schema.const("gemini").description("Gemini"),
263
263
  import_koishi.Schema.const("openai-compatible").description("OpenAI 兼容")
264
264
  ]).description("平台类型"),
265
- enabled: import_koishi.Schema.boolean().default(true).description("启用")
265
+ enabled: import_koishi.Schema.boolean().default(true).description("启用"),
266
+ autoFetchModels: import_koishi.Schema.boolean().default(true).description("是否自动拉取模型")
266
267
  });
267
268
  var sourceSchema = import_koishi.Schema.intersect([
268
269
  sourceBaseSchema,
269
- import_koishi.Schema.intersect([
270
+ import_koishi.Schema.union([
271
+ import_koishi.Schema.object({}),
270
272
  import_koishi.Schema.object({
271
- autoFetchModels: import_koishi.Schema.boolean().default(true).description("是否自动拉取模型")
272
- }),
273
- import_koishi.Schema.union([
274
- import_koishi.Schema.object({
275
- autoFetchModels: import_koishi.Schema.const(true).required()
276
- }),
277
- import_koishi.Schema.object({
278
- autoFetchModels: import_koishi.Schema.const(false).required(),
279
- manualModels: import_koishi.Schema.array(manualSourceModelSchema).role("table").description("手动添加模型")
280
- })
281
- ])
273
+ autoFetchModels: import_koishi.Schema.const(false).required(),
274
+ manualModels: import_koishi.Schema.array(manualSourceModelSchema).default([]).role("table").description("手动添加模型")
275
+ })
282
276
  ])
283
277
  ]);
284
278
  var Config = import_koishi.Schema.intersect([
285
279
  import_koishi.Schema.object({
286
- sources: import_koishi.Schema.array(sourceSchema).description("添加源")
280
+ sources: import_koishi.Schema.array(sourceSchema).role("list").default([]).description("添加源")
287
281
  }).description("模型源配置"),
288
282
  // Debug options
289
283
  import_koishi.Schema.object({
290
- debugMode: import_koishi.Schema.boolean().default(false).description("启用调试日志")
284
+ debugMode: import_koishi.Schema.boolean().default(false).description("启用调试日志"),
285
+ verboseLog: import_koishi.Schema.boolean().default(false).description("启用详细日志(输出配置摘要、hash 与重载判断过程)")
291
286
  }).description("调试选项")
292
287
  ]);
293
288
  var name = "elysia-api-aggregator";
@@ -354,10 +349,33 @@ function apply(ctx, config) {
354
349
  let pendingReload = false;
355
350
  let lastModelsHash = "";
356
351
  let lastConfigHash = "";
352
+ const summarizeSources = /* @__PURE__ */ __name((sources) => {
353
+ return sources.map((source) => ({
354
+ name: source.name,
355
+ baseUrl: source.baseUrl,
356
+ platform: source.platform,
357
+ enabled: source.enabled,
358
+ autoFetchModels: source.autoFetchModels,
359
+ manualModelsCount: source.autoFetchModels ? 0 : source.manualModels.length,
360
+ hasApiKey: Boolean(source.apiKey)
361
+ }));
362
+ }, "summarizeSources");
363
+ const verboseLog = /* @__PURE__ */ __name((message, payload) => {
364
+ if (!config.verboseLog) return;
365
+ if (payload === void 0) {
366
+ ctx.logger.info(`[aggregator verbose] ${message}`);
367
+ return;
368
+ }
369
+ ctx.logger.info(`[aggregator verbose] ${message}: ${JSON.stringify(payload)}`);
370
+ }, "verboseLog");
357
371
  const buildConfigHash = /* @__PURE__ */ __name(() => {
358
- return JSON.stringify({
359
- sources: config.sources
360
- });
372
+ const normalized = {
373
+ sources: summarizeSources(config.sources)
374
+ };
375
+ const hash = JSON.stringify(normalized);
376
+ verboseLog("buildConfigHash summary", normalized);
377
+ verboseLog("buildConfigHash value", hash);
378
+ return hash;
361
379
  }, "buildConfigHash");
362
380
  const buildModelsHash = /* @__PURE__ */ __name((models) => {
363
381
  const normalized = [...models].map((m) => ({
@@ -398,22 +416,53 @@ function apply(ctx, config) {
398
416
  }));
399
417
  }, "mapManualSourceModels");
400
418
  async function loadModels(trigger = "config") {
419
+ verboseLog("loadModels entered", {
420
+ trigger,
421
+ isLoading,
422
+ pendingReload,
423
+ sourceCount: config.sources.length,
424
+ sources: summarizeSources(config.sources)
425
+ });
401
426
  if (isLoading) {
402
427
  pendingReload = true;
403
428
  if (config.debugMode) {
404
429
  ctx.logger.info(`loadModels skipped (already running), queued pending reload (trigger=${trigger})`);
405
430
  }
431
+ verboseLog("loadModels skipped because already loading", {
432
+ trigger,
433
+ pendingReload
434
+ });
406
435
  return;
407
436
  }
408
437
  isLoading = true;
409
438
  const loadStartedAt = Date.now();
410
439
  try {
411
440
  ctx.logger.info("Loading models...");
441
+ verboseLog("loadModels start state", {
442
+ trigger,
443
+ sourceCount: config.sources.length,
444
+ sources: summarizeSources(config.sources)
445
+ });
412
446
  const allModels = [];
413
447
  for (const source of config.sources) {
414
- if (!source.enabled) continue;
448
+ if (!source.enabled) {
449
+ verboseLog("skip disabled source", {
450
+ name: source.name,
451
+ autoFetchModels: source.autoFetchModels
452
+ });
453
+ continue;
454
+ }
415
455
  const sourceStartedAt = Date.now();
416
456
  let sourceModels = [];
457
+ verboseLog("processing source", {
458
+ name: source.name,
459
+ baseUrl: source.baseUrl,
460
+ platform: source.platform,
461
+ enabled: source.enabled,
462
+ autoFetchModels: source.autoFetchModels,
463
+ manualModelsCount: source.autoFetchModels ? 0 : source.manualModels.length,
464
+ hasApiKey: Boolean(source.apiKey)
465
+ });
417
466
  if (source.autoFetchModels) {
418
467
  sourceModels = await fetcher.fetchModels(source);
419
468
  } else {
@@ -424,39 +473,90 @@ function apply(ctx, config) {
424
473
  ctx.logger.info(
425
474
  `[source] ${source.name}: ${sourceModels.length} models (${sourceCostMs}ms${source.autoFetchModels ? ", auto" : ", manual"})`
426
475
  );
476
+ verboseLog("source processed", {
477
+ name: source.name,
478
+ trigger,
479
+ sourceModelsCount: sourceModels.length,
480
+ sourceCostMs
481
+ });
427
482
  }
428
483
  service.updateModels(allModels);
429
484
  const totalCostMs = Date.now() - loadStartedAt;
430
485
  const modelsHash = buildModelsHash(allModels);
486
+ verboseLog("buildModelsHash value", {
487
+ modelsHash,
488
+ lastModelsHash,
489
+ allModelsCount: allModels.length
490
+ });
431
491
  if (modelsHash === lastModelsHash) {
432
492
  ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms, unchanged)`);
493
+ verboseLog("models unchanged, skip emit", {
494
+ trigger,
495
+ allModelsCount: allModels.length,
496
+ totalCostMs
497
+ });
433
498
  return;
434
499
  }
435
500
  lastModelsHash = modelsHash;
436
501
  ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms)`);
502
+ verboseLog("models updated", {
503
+ trigger,
504
+ allModelsCount: allModels.length,
505
+ totalCostMs,
506
+ lastModelsHash
507
+ });
437
508
  ctx.emit("elysia-api/models-updated", [...allModels]);
438
509
  } finally {
439
510
  isLoading = false;
511
+ verboseLog("loadModels finally", {
512
+ trigger,
513
+ pendingReload,
514
+ isLoading
515
+ });
440
516
  if (pendingReload) {
441
517
  pendingReload = false;
518
+ verboseLog("pending reload consumed", {
519
+ nextTrigger: "pending"
520
+ });
442
521
  void loadModels("pending");
443
522
  }
444
523
  }
445
524
  }
446
525
  __name(loadModels, "loadModels");
447
526
  lastConfigHash = buildConfigHash();
527
+ verboseLog("initial lastConfigHash", lastConfigHash);
448
528
  ctx.on("ready", () => {
529
+ verboseLog("ready event fired", {
530
+ sourceCount: config.sources.length,
531
+ sources: summarizeSources(config.sources)
532
+ });
449
533
  void loadModels("ready");
450
534
  });
451
535
  ctx.on("config", () => {
536
+ verboseLog("config event fired", {
537
+ sourceCount: config.sources.length,
538
+ sources: summarizeSources(config.sources),
539
+ lastConfigHash
540
+ });
452
541
  const configHash = buildConfigHash();
542
+ verboseLog("config event hash compare", {
543
+ configHash,
544
+ lastConfigHash,
545
+ equal: configHash === lastConfigHash
546
+ });
453
547
  if (configHash === lastConfigHash) {
454
548
  if (config.debugMode) {
455
549
  ctx.logger.info("aggregator: config event ignored (aggregator config unchanged)");
456
550
  }
551
+ verboseLog("config event ignored", {
552
+ reason: "unchanged"
553
+ });
457
554
  return;
458
555
  }
459
556
  lastConfigHash = configHash;
557
+ verboseLog("config event accepted", {
558
+ newLastConfigHash: lastConfigHash
559
+ });
460
560
  void loadModels("config");
461
561
  });
462
562
  ctx.command("elysia-api.models.reload", "重新加载模型列表").action(async () => {
package/lib/index.mjs CHANGED
@@ -236,32 +236,27 @@ var sourceBaseSchema = Schema.object({
236
236
  Schema.const("gemini").description("Gemini"),
237
237
  Schema.const("openai-compatible").description("OpenAI 兼容")
238
238
  ]).description("平台类型"),
239
- enabled: Schema.boolean().default(true).description("启用")
239
+ enabled: Schema.boolean().default(true).description("启用"),
240
+ autoFetchModels: Schema.boolean().default(true).description("是否自动拉取模型")
240
241
  });
241
242
  var sourceSchema = Schema.intersect([
242
243
  sourceBaseSchema,
243
- Schema.intersect([
244
+ Schema.union([
245
+ Schema.object({}),
244
246
  Schema.object({
245
- autoFetchModels: Schema.boolean().default(true).description("是否自动拉取模型")
246
- }),
247
- Schema.union([
248
- Schema.object({
249
- autoFetchModels: Schema.const(true).required()
250
- }),
251
- Schema.object({
252
- autoFetchModels: Schema.const(false).required(),
253
- manualModels: Schema.array(manualSourceModelSchema).role("table").description("手动添加模型")
254
- })
255
- ])
247
+ autoFetchModels: Schema.const(false).required(),
248
+ manualModels: Schema.array(manualSourceModelSchema).default([]).role("table").description("手动添加模型")
249
+ })
256
250
  ])
257
251
  ]);
258
252
  var Config = Schema.intersect([
259
253
  Schema.object({
260
- sources: Schema.array(sourceSchema).description("添加源")
254
+ sources: Schema.array(sourceSchema).role("list").default([]).description("添加源")
261
255
  }).description("模型源配置"),
262
256
  // Debug options
263
257
  Schema.object({
264
- debugMode: Schema.boolean().default(false).description("启用调试日志")
258
+ debugMode: Schema.boolean().default(false).description("启用调试日志"),
259
+ verboseLog: Schema.boolean().default(false).description("启用详细日志(输出配置摘要、hash 与重载判断过程)")
265
260
  }).description("调试选项")
266
261
  ]);
267
262
  var name = "elysia-api-aggregator";
@@ -328,10 +323,33 @@ function apply(ctx, config) {
328
323
  let pendingReload = false;
329
324
  let lastModelsHash = "";
330
325
  let lastConfigHash = "";
326
+ const summarizeSources = /* @__PURE__ */ __name((sources) => {
327
+ return sources.map((source) => ({
328
+ name: source.name,
329
+ baseUrl: source.baseUrl,
330
+ platform: source.platform,
331
+ enabled: source.enabled,
332
+ autoFetchModels: source.autoFetchModels,
333
+ manualModelsCount: source.autoFetchModels ? 0 : source.manualModels.length,
334
+ hasApiKey: Boolean(source.apiKey)
335
+ }));
336
+ }, "summarizeSources");
337
+ const verboseLog = /* @__PURE__ */ __name((message, payload) => {
338
+ if (!config.verboseLog) return;
339
+ if (payload === void 0) {
340
+ ctx.logger.info(`[aggregator verbose] ${message}`);
341
+ return;
342
+ }
343
+ ctx.logger.info(`[aggregator verbose] ${message}: ${JSON.stringify(payload)}`);
344
+ }, "verboseLog");
331
345
  const buildConfigHash = /* @__PURE__ */ __name(() => {
332
- return JSON.stringify({
333
- sources: config.sources
334
- });
346
+ const normalized = {
347
+ sources: summarizeSources(config.sources)
348
+ };
349
+ const hash = JSON.stringify(normalized);
350
+ verboseLog("buildConfigHash summary", normalized);
351
+ verboseLog("buildConfigHash value", hash);
352
+ return hash;
335
353
  }, "buildConfigHash");
336
354
  const buildModelsHash = /* @__PURE__ */ __name((models) => {
337
355
  const normalized = [...models].map((m) => ({
@@ -372,22 +390,53 @@ function apply(ctx, config) {
372
390
  }));
373
391
  }, "mapManualSourceModels");
374
392
  async function loadModels(trigger = "config") {
393
+ verboseLog("loadModels entered", {
394
+ trigger,
395
+ isLoading,
396
+ pendingReload,
397
+ sourceCount: config.sources.length,
398
+ sources: summarizeSources(config.sources)
399
+ });
375
400
  if (isLoading) {
376
401
  pendingReload = true;
377
402
  if (config.debugMode) {
378
403
  ctx.logger.info(`loadModels skipped (already running), queued pending reload (trigger=${trigger})`);
379
404
  }
405
+ verboseLog("loadModels skipped because already loading", {
406
+ trigger,
407
+ pendingReload
408
+ });
380
409
  return;
381
410
  }
382
411
  isLoading = true;
383
412
  const loadStartedAt = Date.now();
384
413
  try {
385
414
  ctx.logger.info("Loading models...");
415
+ verboseLog("loadModels start state", {
416
+ trigger,
417
+ sourceCount: config.sources.length,
418
+ sources: summarizeSources(config.sources)
419
+ });
386
420
  const allModels = [];
387
421
  for (const source of config.sources) {
388
- if (!source.enabled) continue;
422
+ if (!source.enabled) {
423
+ verboseLog("skip disabled source", {
424
+ name: source.name,
425
+ autoFetchModels: source.autoFetchModels
426
+ });
427
+ continue;
428
+ }
389
429
  const sourceStartedAt = Date.now();
390
430
  let sourceModels = [];
431
+ verboseLog("processing source", {
432
+ name: source.name,
433
+ baseUrl: source.baseUrl,
434
+ platform: source.platform,
435
+ enabled: source.enabled,
436
+ autoFetchModels: source.autoFetchModels,
437
+ manualModelsCount: source.autoFetchModels ? 0 : source.manualModels.length,
438
+ hasApiKey: Boolean(source.apiKey)
439
+ });
391
440
  if (source.autoFetchModels) {
392
441
  sourceModels = await fetcher.fetchModels(source);
393
442
  } else {
@@ -398,39 +447,90 @@ function apply(ctx, config) {
398
447
  ctx.logger.info(
399
448
  `[source] ${source.name}: ${sourceModels.length} models (${sourceCostMs}ms${source.autoFetchModels ? ", auto" : ", manual"})`
400
449
  );
450
+ verboseLog("source processed", {
451
+ name: source.name,
452
+ trigger,
453
+ sourceModelsCount: sourceModels.length,
454
+ sourceCostMs
455
+ });
401
456
  }
402
457
  service.updateModels(allModels);
403
458
  const totalCostMs = Date.now() - loadStartedAt;
404
459
  const modelsHash = buildModelsHash(allModels);
460
+ verboseLog("buildModelsHash value", {
461
+ modelsHash,
462
+ lastModelsHash,
463
+ allModelsCount: allModels.length
464
+ });
405
465
  if (modelsHash === lastModelsHash) {
406
466
  ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms, unchanged)`);
467
+ verboseLog("models unchanged, skip emit", {
468
+ trigger,
469
+ allModelsCount: allModels.length,
470
+ totalCostMs
471
+ });
407
472
  return;
408
473
  }
409
474
  lastModelsHash = modelsHash;
410
475
  ctx.logger.info(`Total models loaded: ${allModels.length} (${totalCostMs}ms)`);
476
+ verboseLog("models updated", {
477
+ trigger,
478
+ allModelsCount: allModels.length,
479
+ totalCostMs,
480
+ lastModelsHash
481
+ });
411
482
  ctx.emit("elysia-api/models-updated", [...allModels]);
412
483
  } finally {
413
484
  isLoading = false;
485
+ verboseLog("loadModels finally", {
486
+ trigger,
487
+ pendingReload,
488
+ isLoading
489
+ });
414
490
  if (pendingReload) {
415
491
  pendingReload = false;
492
+ verboseLog("pending reload consumed", {
493
+ nextTrigger: "pending"
494
+ });
416
495
  void loadModels("pending");
417
496
  }
418
497
  }
419
498
  }
420
499
  __name(loadModels, "loadModels");
421
500
  lastConfigHash = buildConfigHash();
501
+ verboseLog("initial lastConfigHash", lastConfigHash);
422
502
  ctx.on("ready", () => {
503
+ verboseLog("ready event fired", {
504
+ sourceCount: config.sources.length,
505
+ sources: summarizeSources(config.sources)
506
+ });
423
507
  void loadModels("ready");
424
508
  });
425
509
  ctx.on("config", () => {
510
+ verboseLog("config event fired", {
511
+ sourceCount: config.sources.length,
512
+ sources: summarizeSources(config.sources),
513
+ lastConfigHash
514
+ });
426
515
  const configHash = buildConfigHash();
516
+ verboseLog("config event hash compare", {
517
+ configHash,
518
+ lastConfigHash,
519
+ equal: configHash === lastConfigHash
520
+ });
427
521
  if (configHash === lastConfigHash) {
428
522
  if (config.debugMode) {
429
523
  ctx.logger.info("aggregator: config event ignored (aggregator config unchanged)");
430
524
  }
525
+ verboseLog("config event ignored", {
526
+ reason: "unchanged"
527
+ });
431
528
  return;
432
529
  }
433
530
  lastConfigHash = configHash;
531
+ verboseLog("config event accepted", {
532
+ newLastConfigHash: lastConfigHash
533
+ });
434
534
  void loadModels("config");
435
535
  });
436
536
  ctx.command("elysia-api.models.reload", "重新加载模型列表").action(async () => {
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",
4
+ "version": "0.2.5",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",