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 | undefined; private started = false; private disposed = false; constructor( private readonly discovery: IProviderDiscoveryService, private readonly config: IConfigService, private readonly logger: Pick, private readonly env: NodeJS.ProcessEnv = process.env, ) {} async start(): Promise { if (this.started) return; this.started = true; await this.config.ready; if (this.disposed) return; const catalogConfig = this.config.get(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 { 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; }