import { callQvac } from '../daemon/qvac-master.js'; import { p2pStatus } from './optional-sync.js'; const READ = 'read'; const WRITE = 'write'; const DANGEROUS = 'dangerous'; const specs = [ ['embed', 'embed', 'Create local text embeddings.', READ], ['rag_ingest', 'ragIngest', 'Ingest local documents into a QVAC RAG workspace.', WRITE], ['rag_chunk', 'ragChunk', 'Chunk local documents for RAG.', READ], ['rag_save_embeddings', 'ragSaveEmbeddings', 'Save local RAG embeddings.', WRITE], ['rag_search', 'ragSearch', 'Search a local QVAC RAG workspace.', READ], ['rag_reindex', 'ragReindex', 'Reindex a local QVAC RAG workspace.', WRITE], ['rag_delete_embeddings', 'ragDeleteEmbeddings', 'Delete embeddings from a local workspace.', DANGEROUS], ['rag_list_workspaces', 'ragListWorkspaces', 'List local QVAC RAG workspaces.', READ], ['rag_close_workspace', 'ragCloseWorkspace', 'Close a local RAG workspace.', WRITE], ['rag_delete_workspace', 'ragDeleteWorkspace', 'Delete a local RAG workspace.', DANGEROUS], ['batch_prompt', 'batchCompletion', 'Run a local batch completion.', WRITE], ['translate', 'translate', 'Translate text locally.', READ], ['ocr', 'ocr', 'Run local QVAC OCR on an image.', READ], ['classify_image', 'classify', 'Classify a local image.', READ], ['imagine', 'diffusion', 'Generate or edit a local image.', WRITE], ['make_video', 'video', 'Generate a local video job.', WRITE], ['compose_music', 'audioGen', 'Generate a local music job.', WRITE], ['transcribe_file', 'transcribe', 'Transcribe a local audio file.', READ], ['tts_speak', 'textToSpeech', 'Synthesize local speech.', READ], ['finetune_lora', 'finetune', 'Run a confirmed local LoRA training job.', DANGEROUS], ['bci_transcribe', 'bciTranscribe', 'Use the local BCI transcription slot.', READ], ['vla_act', 'vla', 'Run the local VLA Lab slot.', DANGEROUS], ['world_create', 'worldCreateScene', 'Create a local ABot-World scene.', WRITE], ['world_step', 'worldStep', 'Advance a local ABot-World scene.', WRITE], ['model_registry_list', 'modelRegistryList', 'List models in the local QVAC registry.', READ], ['model_registry_search', 'modelRegistrySearch', 'Search the local QVAC registry.', READ], ['model_registry_get', 'modelRegistryGetModel', 'Read one local registry model entry.', READ], ['qvac_profile', 'getSystemResources', 'Read local QVAC resource/profile data.', READ], ]; function resultShape(value) { if (!value || typeof value !== 'object') return value; if (value.requestId) return { requestId: value.requestId, job: true }; if (value.bufferStream || value.blockStream || value.tokenStream) return { job: true, stream: true, requestId: value.requestId || null }; return value; } export function createPhase9Tools() { const tools = specs.map(([name, method, description, permission]) => ({ name, permission, description: `${description} All execution remains local and is routed through the single QVAC master.`, parameters: { type: 'object', properties: { input: { type: 'object' } } }, execute: async ({ input = {} } = {}) => { if (method === 'modelRegistryGetModel') return resultShape(await callQvac(method, [input.registryPath || input.path, input.registrySource || input.source])); if (method === 'ocr') { const result = await callQvac(method, input); return { blocks: await result.blocks }; } return resultShape(await callQvac(method, input)); }})); tools.push({ name: 'job_cancel', permission: 'dangerous', description: 'Cancel a local QVAC request or model job through the master.', parameters: { type: 'object', properties: { requestId: { type: 'string' }, modelId: { type: 'string' }, kind: { type: 'string' } } }, execute: async (input) => callQvac('cancel', input) }); tools.push({ name: 'p2p_status', permission: 'read', description: 'Report optional P2P model/memory transport status; it is disabled by default.', parameters: { type: 'object', properties: {} }, execute: () => p2pStatus() }); return tools; } // The harness reserves 32 custom-tool slots. Keep the complete per-capability // catalog available to callers, while exposing one gateway to the agent so // every QVAC capability remains reachable without exceeding that limit. export function createPhase9GatewayTool() { const operations = [...specs.map(([name]) => name), 'job_cancel', 'p2p_status']; return [{ name: 'qvac_capability', permission: 'write', description: `Run one local QVAC capability through the single master adapter. Operations: ${operations.join(', ')}.`, parameters: { type: 'object', properties: { operation: { type: 'string', enum: operations }, input: { type: 'object' }, }, required: ['operation'], }, execute: async ({ operation, input = {} } = {}) => { if (operation === 'p2p_status') return p2pStatus(); if (operation === 'job_cancel') return callQvac('cancel', input); const spec = specs.find(([name]) => name === operation); if (!spec) throw new Error(`unknown QVAC capability: ${operation}`); const [, method] = spec; if (method === 'modelRegistryGetModel') return resultShape(await callQvac(method, [input.registryPath || input.path, input.registrySource || input.source])); if (method === 'ocr') { const result = await callQvac(method, input); return { blocks: await result.blocks }; } return resultShape(await callQvac(method, input)); }, }]; } export { specs as PHASE9_SPECS };