UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

240 lines (239 loc) 8.96 kB
/** * ToolForge — 工具锻造主编排器 * * 三级锻造策略: * Reuse (0ms) → 直接重用注册表中已有工具 * Compose (10ms) → 通过 DynamicComposer 组合已有原子工具 * Generate (5s) → LLM 生成工具代码 → SandboxRunner 验证 → 注册 * * 瀑布逻辑:reuse → compose → generate,首个成功即返回。 */ import Logger from '#infra/logging/Logger.js'; import { DynamicComposer } from './DynamicComposer.js'; import { SandboxRunner } from './SandboxRunner.js'; import { TemporaryToolRegistry } from './TemporaryToolRegistry.js'; import { ToolRequirementAnalyzer } from './ToolRequirementAnalyzer.js'; /* ────────────────────── Class ────────────────────── */ export class ToolForge { #registry; #analyzer; #composer; #sandbox; #tempRegistry; #signalBus; #logger = Logger.getInstance(); #defaultTtlMs; #compositionSpecBuilder; constructor(registry, options = {}) { this.#registry = registry; this.#signalBus = options.signalBus ?? null; this.#defaultTtlMs = options.defaultTtlMs ?? 30 * 60 * 1000; this.#compositionSpecBuilder = options.compositionSpecBuilder; this.#analyzer = new ToolRequirementAnalyzer(registry); this.#composer = new DynamicComposer(registry); this.#sandbox = new SandboxRunner(); this.#tempRegistry = new TemporaryToolRegistry(registry, { signalBus: this.#signalBus ?? undefined, }); } /* ────────── Public API ────────── */ /** * 锻造工具 — 瀑布流:reuse → compose → generate */ async forge(request) { const requirement = { intent: request.intent, action: request.action, target: request.target, constraints: request.constraints, }; // Step 1: 需求分析 const analysis = this.#analyzer.analyze(requirement); this.#logger.info(`ToolForge: analysis for "${request.intent}" → mode=${analysis.mode}, confidence=${analysis.confidence}`); // Step 2: 按推荐模式尝试,失败则降级 const result = (await this.#tryReuse(analysis, requirement)) ?? (await this.#tryCompose(analysis, requirement)) ?? (await this.#tryGenerate(analysis, requirement, request.codeGenerator)); if (result) { this.#emitSignal('forge_complete', { mode: result.mode, tool: result.toolName, analysis, }); return result; } // 全部失败 return { success: false, mode: 'generate', analysis, error: 'All forge modes exhausted. Cannot satisfy tool requirement.', }; } /** * 获取临时工具注册表(暴露给 Pipeline 集成用) */ get temporaryRegistry() { return this.#tempRegistry; } /** * 获取分析器 */ get analyzer() { return this.#analyzer; } /** * 销毁 Forge(清理临时工具和定时器) */ dispose() { this.#tempRegistry.dispose(); } /* ────────── Forge Modes ────────── */ async #tryReuse(analysis, _requirement) { if (analysis.mode !== 'reuse' || !analysis.matchedTool) { // 即便分析推荐 compose/generate,也尝试检查直接匹配 if (analysis.matchedTool && this.#registry.has(analysis.matchedTool)) { return { success: true, mode: 'reuse', toolName: analysis.matchedTool, analysis, }; } return null; } if (!this.#registry.has(analysis.matchedTool)) { return null; } this.#logger.debug(`ToolForge: reuse existing tool "${analysis.matchedTool}"`); return { success: true, mode: 'reuse', toolName: analysis.matchedTool, analysis, }; } async #tryCompose(analysis, requirement) { if (!analysis.composableTools || analysis.composableTools.length < 2) { return null; } // 如果有外部 spec builder,使用它 const spec = this.#compositionSpecBuilder?.(analysis, requirement); if (!spec) { // 默认构建 sequential 组合 const defaultSpec = this.#buildDefaultCompositionSpec(analysis, requirement); if (!defaultSpec) { return null; } return this.#executeComposition(defaultSpec, analysis); } return this.#executeComposition(spec, analysis); } #buildDefaultCompositionSpec(analysis, requirement) { const tools = analysis.composableTools; if (!tools || tools.length < 2) { return null; } const composedName = `composed_${requirement.action}_${requirement.target}`; return { name: composedName, description: `Auto-composed tool for: ${requirement.intent}`, steps: tools.map((tool) => ({ tool, args: (prevResult) => { if (typeof prevResult === 'object' && prevResult !== null) { return prevResult; } return {}; }, })), mergeStrategy: 'sequential', }; } async #executeComposition(spec, analysis) { const result = this.#composer.compose(spec); if (!result.success || !result.handler) { this.#logger.debug(`ToolForge: composition failed — ${result.error}`); return null; } // 注册为临时工具 this.#tempRegistry.registerTemporary({ name: spec.name, description: spec.description, parameters: spec.parameters ?? {}, handler: result.handler, forgeMode: 'compose', }, this.#defaultTtlMs); this.#logger.info(`ToolForge: composed tool "${spec.name}" from ${spec.steps.length} steps`); return { success: true, mode: 'compose', toolName: spec.name, analysis, }; } async #tryGenerate(analysis, requirement, codeGenerator) { if (!codeGenerator) { this.#logger.debug('ToolForge: generate mode skipped (no codeGenerator provided)'); return null; } // 调用 LLM 生成工具 const generated = await codeGenerator(requirement); if (!generated) { return null; } // 安全检查 const safety = this.#sandbox.checkSafety(generated.code); if (!safety.passed) { this.#logger.warn(`ToolForge: generated code failed safety check — ${safety.violations.join(', ')}`); return { success: false, mode: 'generate', analysis, error: `Safety violations: ${safety.violations.join(', ')}`, }; } // 沙箱测试 if (generated.testCases.length > 0) { const testResult = await this.#sandbox.run(generated.code, generated.testCases); if (!testResult.success) { const failures = testResult.testResults .filter((t) => !t.passed) .map((t) => t.description) .join(', '); this.#logger.warn(`ToolForge: generated code failed tests — ${failures}`); return { success: false, mode: 'generate', analysis, error: `Test failures: ${failures}`, }; } } // 构建 handler 包装 const handler = this.#sandbox.createHandler(generated.code); // 注册为临时工具 this.#tempRegistry.registerTemporary({ name: generated.name, description: generated.description, parameters: generated.parameters, handler, forgeMode: 'generate', }, this.#defaultTtlMs); this.#logger.info(`ToolForge: generated and registered tool "${generated.name}"`); return { success: true, mode: 'generate', toolName: generated.name, analysis, }; } /* ────────── Signal ────────── */ #emitSignal(action, data) { if (this.#signalBus) { this.#signalBus.send('forge', 'ToolForge', 1, { metadata: { action, ...data }, }); } } }