autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
155 lines (154 loc) • 5.06 kB
JavaScript
/**
* TemporaryToolRegistry — TTL 临时工具注册
*
* 在 ToolRegistry 之上增加 TTL 自动回收机制。
* 锻造的工具默认 30 分钟有效,到期自动从 ToolRegistry 中移除。
*
* 设计:
* - 装饰器模式,不修改 ToolRegistry 核心逻辑
* - 定期检查(60s 间隔)清理过期工具
* - 支持手动续期和提前回收
*/
import Logger from '#infra/logging/Logger.js';
/* ────────────────────── Constants ────────────────────── */
const DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes
const CLEANUP_INTERVAL_MS = 60 * 1000; // 60 seconds
/* ────────────────────── Class ────────────────────── */
export class TemporaryToolRegistry {
#registry;
#tempTools = new Map();
#cleanupTimer = null;
#signalBus;
#logger = Logger.getInstance();
constructor(registry, options = {}) {
this.#registry = registry;
this.#signalBus = options.signalBus ?? null;
this.#startCleanup();
}
/**
* 注册一个临时工具
*/
registerTemporary(tool, ttlMs = DEFAULT_TTL_MS) {
const now = Date.now();
const entry = {
...tool,
registeredAt: now,
expiresAt: ttlMs > 0 ? now + ttlMs : 0,
};
// 如果已存在同名临时工具,先移除
if (this.#tempTools.has(tool.name)) {
this.revoke(tool.name);
}
// 注册到主 ToolRegistry
this.#registry.register({
name: tool.name,
description: `[Forged:${tool.forgeMode}] ${tool.description}`,
parameters: tool.parameters,
handler: tool.handler,
});
this.#tempTools.set(tool.name, entry);
if (this.#signalBus) {
this.#signalBus.send('forge', 'TemporaryToolRegistry', 1, {
target: tool.name,
metadata: { action: 'registered', forgeMode: tool.forgeMode, ttlMs },
});
}
this.#logger.debug(`TemporaryToolRegistry: registered "${tool.name}" (mode=${tool.forgeMode}, ttl=${ttlMs}ms)`);
}
/**
* 手动回收临时工具
*/
revoke(name) {
const tool = this.#tempTools.get(name);
if (!tool) {
return false;
}
this.#registry.unregister(name);
this.#tempTools.delete(name);
if (this.#signalBus) {
this.#signalBus.send('forge', 'TemporaryToolRegistry', 0, {
target: name,
metadata: { action: 'revoked' },
});
}
this.#logger.debug(`TemporaryToolRegistry: revoked "${name}"`);
return true;
}
/**
* 续期临时工具
*/
renew(name, additionalMs = DEFAULT_TTL_MS) {
const tool = this.#tempTools.get(name);
if (!tool) {
return false;
}
tool.expiresAt = Date.now() + additionalMs;
return true;
}
/**
* 清理过期工具
*/
cleanup() {
const now = Date.now();
let cleaned = 0;
for (const [name, tool] of this.#tempTools) {
if (tool.expiresAt > 0 && tool.expiresAt <= now) {
this.#registry.unregister(name);
this.#tempTools.delete(name);
cleaned++;
this.#logger.debug(`TemporaryToolRegistry: expired "${name}"`);
}
}
return cleaned;
}
/**
* 获取所有临时工具信息
*/
list() {
const now = Date.now();
const result = [];
for (const [name, tool] of this.#tempTools) {
result.push({
name,
forgeMode: tool.forgeMode,
registeredAt: tool.registeredAt,
expiresAt: tool.expiresAt,
remainingMs: tool.expiresAt > 0 ? Math.max(0, tool.expiresAt - now) : -1,
});
}
return result;
}
/**
* 检查是否是临时工具
*/
isTemporary(name) {
return this.#tempTools.has(name);
}
/** 临时工具数量 */
get size() {
return this.#tempTools.size;
}
/** 停止定期清理(用于 shutdown) */
dispose() {
if (this.#cleanupTimer) {
clearInterval(this.#cleanupTimer);
this.#cleanupTimer = null;
}
// 回收所有临时工具
for (const name of [...this.#tempTools.keys()]) {
this.revoke(name);
}
}
/* ── Internal ── */
#startCleanup() {
this.#cleanupTimer = setInterval(() => {
this.cleanup();
}, CLEANUP_INTERVAL_MS);
// 允许 Node.js 进程在只剩此 timer 时退出
if (this.#cleanupTimer &&
typeof this.#cleanupTimer === 'object' &&
'unref' in this.#cleanupTimer) {
this.#cleanupTimer.unref();
}
}
}