File size: 2,964 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import {
  type IConfigService,
  type IProviderDiscoveryService,
  type ModelCatalogConfig,
  MODEL_CATALOG_SECTION,
} from '@moonshot-ai/agent-core-v2';

import type { ServerLogger } from '../pinoLoggerService';

const DEFAULT_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
const INTERVAL_ENV = 'KIMI_CODE_MODEL_CATALOG_REFRESH_INTERVAL_MS';
const REFRESH_ON_START_ENV = 'KIMI_CODE_MODEL_CATALOG_REFRESH_ON_START';

export class ModelCatalogRefreshScheduler {
  private timer: ReturnType<typeof setInterval> | undefined;
  private started = false;
  private disposed = false;

  constructor(
    private readonly discovery: IProviderDiscoveryService,
    private readonly config: IConfigService,
    private readonly logger: Pick<ServerLogger, 'info' | 'warn'>,
    private readonly env: NodeJS.ProcessEnv = process.env,
  ) {}

  async start(): Promise<void> {
    if (this.started) return;
    this.started = true;

    await this.config.ready;
    if (this.disposed) return;
    const catalogConfig = this.config.get<ModelCatalogConfig | undefined>(MODEL_CATALOG_SECTION);
    const intervalMs = resolveIntervalMs(this.env, catalogConfig?.refreshIntervalMs);
    const refreshOnStart = resolveRefreshOnStart(this.env, catalogConfig?.refreshOnStart);

    if (refreshOnStart) {
      void this.refresh('startup');
    }

    if (intervalMs > 0) {
      this.timer = setInterval(() => void this.refresh('interval'), intervalMs);
      this.timer.unref?.();
      this.logger.info({ intervalMs }, 'provider-model catalog auto-refresh enabled');
    }
  }

  dispose(): void {
    this.disposed = true;
    if (this.timer !== undefined) {
      clearInterval(this.timer);
      this.timer = undefined;
    }
  }

  private async refresh(trigger: 'startup' | 'interval'): Promise<void> {
    try {
      const result = await this.discovery.refreshProviderModels({ scope: 'all' });
      if (result.failed.length > 0) {
        this.logger.warn(
          { trigger, failed: result.failed },
          'provider-model catalog refresh completed with failures',
        );
      }
    } catch (error) {
      this.logger.warn(
        { trigger, err: error instanceof Error ? error.message : String(error) },
        'provider-model catalog refresh failed',
      );
    }
  }
}

function resolveIntervalMs(env: NodeJS.ProcessEnv, configValue: number | undefined): number {
  const raw = env[INTERVAL_ENV];
  if (raw !== undefined && raw.trim().length > 0) {
    const parsed = Number(raw);
    if (Number.isFinite(parsed) && parsed >= 0) return parsed;
  }
  return configValue ?? DEFAULT_REFRESH_INTERVAL_MS;
}

function resolveRefreshOnStart(env: NodeJS.ProcessEnv, configValue: boolean | undefined): boolean {
  const raw = env[REFRESH_ON_START_ENV];
  if (raw !== undefined && raw.trim().length > 0) {
    const normalized = raw.trim().toLowerCase();
    return normalized === '1' || normalized === 'true' || normalized === 'yes';
  }
  return configValue ?? true;
}