import * as plugins from '../plugins.js'; import type { AgentRegistry } from '../agents/classes.agentregistry.js'; import type { ApprovalQueue } from '../approvals/classes.approvalqueue.js'; import type { AuditLog } from '../audit/classes.auditlog.js'; import type { DeviceRegistry } from '../devices/classes.deviceregistry.js'; export class ToolRegistry { constructor( private deviceRegistry: DeviceRegistry, private agentRegistry: AgentRegistry, private approvalQueue: ApprovalQueue, private auditLog: AuditLog ) {} public listTools(filterArg: { ownerId?: string } = {}) { const deviceTools = this.deviceRegistry.listDevices().flatMap((deviceArg) => this.createDeviceTools(deviceArg)); const agentTools = this.agentRegistry.listAgents().map((agentArg) => this.createAgentTool(agentArg)); return [...deviceTools, ...agentTools].filter((toolArg) => !filterArg.ownerId || toolArg.ownerId === filterArg.ownerId); } public async executePlan(planArg: plugins.shxInterfaces.data.IToolPlan): Promise { const tools = this.listTools(); const results: plugins.shxInterfaces.data.IToolExecutionResult[] = []; for (const call of planArg.calls) { const tool = tools.find((toolArg) => toolArg.id === call.toolId); if (!tool) { results.push({ callId: call.id, status: 'failed', errorMessage: `Tool not found: ${call.toolId}`, }); continue; } if (tool.mode === 'ask') { const approval = this.approvalQueue.createApproval({ plan: planArg, call, agentId: tool.ownerId, title: planArg.title, reason: planArg.reason, requestedScopes: tool.requiredScopes, }); results.push({ callId: call.id, status: 'queuedForApproval', approvalId: approval.id, }); continue; } if (tool.mode === 'suggest') { results.push({ callId: call.id, status: 'suggested', output: { message: `${tool.name} returned a suggestion only.`, }, }); continue; } const output = this.executeAutoTool(tool, call); this.auditLog.appendReceipt({ kind: 'tool', callerId: call.callerId, toolId: tool.id, scope: tool.requiredScopes.join(','), inputSummary: JSON.stringify(call.input), outputSummary: JSON.stringify(output), reversible: tool.id.includes(':write'), }); results.push({ callId: call.id, status: 'executed', output, }); } return { planId: planArg.id, results, }; } private createDeviceTools(deviceArg: plugins.shxInterfaces.data.IDeviceDefinition): plugins.shxInterfaces.data.IToolDefinition[] { const tools: plugins.shxInterfaces.data.IToolDefinition[] = [ { id: `device:${deviceArg.id}:read`, ownerId: deviceArg.id, name: `Read ${deviceArg.name}`, description: `Read current state from ${deviceArg.name}.`, inputSchema: { type: 'object', properties: {} }, requiredScopes: ['device.read'], mode: 'auto', }, ]; for (const feature of deviceArg.features.filter((featureArg: plugins.shxInterfaces.data.IDeviceFeature) => featureArg.writable)) { const mode = feature.capability === 'lock' ? 'ask' : 'auto'; const scope = this.scopeForCapability(feature.capability, true); tools.push({ id: `device:${deviceArg.id}:${feature.id}:write`, ownerId: deviceArg.id, name: `Set ${deviceArg.name} ${feature.name}`, description: `Write ${feature.name} on ${deviceArg.name}.`, inputSchema: { type: 'object', properties: { value: { type: ['string', 'number', 'boolean', 'object', 'null'] }, }, required: ['value'], }, requiredScopes: [scope], mode, }); } return tools; } private createAgentTool(agentArg: plugins.shxInterfaces.data.IAgentDefinition): plugins.shxInterfaces.data.IToolDefinition { return { id: `agent:${agentArg.id}:decide`, ownerId: agentArg.id, name: `Ask ${agentArg.name}`, description: agentArg.role, inputSchema: { type: 'object', properties: { goal: { type: 'string' }, scopes: { type: 'array', items: { type: 'string' } }, }, required: ['goal'], }, requiredScopes: ['agent.invoke'], mode: agentArg.mode, }; } private executeAutoTool(toolArg: plugins.shxInterfaces.data.IToolDefinition, callArg: plugins.shxInterfaces.data.IToolCall) { if (toolArg.id.startsWith('device:') && toolArg.id.endsWith(':read')) { const deviceId = String(callArg.input.deviceId || toolArg.ownerId); return { device: this.deviceRegistry.getDeviceById(deviceId), }; } if (toolArg.id.startsWith('device:') && toolArg.id.endsWith(':write')) { const [, deviceId, featureId] = toolArg.id.split(':'); const state = this.deviceRegistry.updateDeviceState( deviceId, featureId, callArg.input.value as plugins.shxInterfaces.data.TDeviceStateValue ); return { state }; } if (toolArg.id.startsWith('agent:')) { this.agentRegistry.recordAction(toolArg.ownerId, String(callArg.input.goal || 'Agent invoked')); return { message: `${toolArg.name} accepted the task.`, }; } return { message: `${toolArg.name} executed.`, }; } private scopeForCapability(capabilityArg: plugins.shxInterfaces.data.TDeviceCapability, writeArg: boolean) { const suffix = writeArg ? 'write' : 'read'; if (capabilityArg === 'light') return `light.${suffix}` as plugins.shxInterfaces.data.TToolScope; if (capabilityArg === 'climate') return `climate.${suffix}` as plugins.shxInterfaces.data.TToolScope; if (capabilityArg === 'energy') return `energy.${suffix}` as plugins.shxInterfaces.data.TToolScope; if (capabilityArg === 'lock') return `lock.${suffix}` as plugins.shxInterfaces.data.TToolScope; if (capabilityArg === 'camera') return 'camera.read'; return writeArg ? 'device.write' : 'device.read'; } }