import fs from 'node:fs/promises'; import path from 'node:path'; import { buildSelectedAssetDeleteInstructions, buildSelectedAssetReadInstructions, } from './mindspace-asset-agent-instructions.mjs'; import { resolveAssetWorkspaceRelativePath } from './mindspace-workspace-relative-path.mjs'; import { ensureUserZoneDirs } from './user-space.mjs'; function sanitizeBasename(value) { const normalized = String(value ?? '').replace(/\\/g, '/').trim(); const basename = normalized.split('/').pop() ?? ''; return basename.replace(/[^\w.\-()\u4e00-\u9fff]+/g, '_') || 'file'; } async function materializeAssetToWorkspace({ assetService, resolveWorkspaceRoot, userId, assetId, }) { const { asset, path: sourcePath } = await assetService.readAsset(userId, assetId); const workspaceRoot = await resolveWorkspaceRoot(userId); ensureUserZoneDirs(workspaceRoot); const filename = sanitizeBasename(asset.filename ?? asset.displayName ?? asset.id); const relativePath = resolveAssetWorkspaceRelativePath({ categoryCode: asset.categoryCode ?? 'oa', originalFilename: filename, }) ?? `oa/${filename}`; const destPath = path.join(workspaceRoot, ...relativePath.split('/')); await fs.mkdir(path.dirname(destPath), { recursive: true }); await fs.copyFile(sourcePath, destPath); const stat = await fs.stat(destPath); return { assetId, relativePath, sizeBytes: stat.size, displayName: asset.displayName ?? filename, mimeType: asset.mimeType ?? null, }; } export { buildSelectedAssetDeleteInstructions, buildSelectedAssetReadInstructions, } from './mindspace-asset-agent-instructions.mjs'; export function createAssetAgentService({ assetService, resolveUserIdForAgentSession, resolveWorkspaceRoot, }) { if (typeof resolveWorkspaceRoot !== 'function') { throw new Error('resolveWorkspaceRoot is required for asset agent service'); } async function resolveOwnedUserId(sessionId, explicitUserId = null) { if (explicitUserId) return explicitUserId; const userId = await resolveUserIdForAgentSession(sessionId); if (!userId) { throw Object.assign(new Error('无效的 Agent 会话'), { code: 'forbidden' }); } return userId; } return { async applyAgentDelete(input = {}) { const sessionId = String(input?.sessionId ?? input?.session_id ?? '').trim(); const assetId = String(input?.assetId ?? input?.asset_id ?? '').trim(); const confirmed = input?.confirmed === true || String(input?.confirmed ?? '').toLowerCase() === 'true'; if (!sessionId || !assetId) { throw Object.assign(new Error('缺少 session_id 或 asset_id'), { code: 'invalid_request' }); } if (!confirmed) { throw Object.assign(new Error('删除前须已在对话中获得用户确认'), { code: 'confirmation_required' }); } const userId = await resolveOwnedUserId(sessionId); const result = await assetService.deleteAsset(userId, assetId); return { assetId, userId, ...result, }; }, async applyAgentDownload(input = {}) { const sessionId = String(input?.sessionId ?? input?.session_id ?? '').trim(); const assetId = String(input?.assetId ?? input?.asset_id ?? '').trim(); if (!sessionId || !assetId) { throw Object.assign(new Error('缺少 session_id 或 asset_id'), { code: 'invalid_request' }); } const userId = await resolveOwnedUserId(sessionId); const materialized = await materializeAssetToWorkspace({ assetService, resolveWorkspaceRoot, userId, assetId, }); return { userId, ...materialized, }; }, async materializeAssetsForSession({ sessionId = null, userId = null, assetIds = [], } = {}) { const normalizedIds = [...new Set( (Array.isArray(assetIds) ? assetIds : []) .map((item) => String(item ?? '').trim()) .filter(Boolean), )]; if (normalizedIds.length === 0) { return { materialized: [] }; } const resolvedUserId = await resolveOwnedUserId(sessionId, userId); const materialized = []; for (const assetId of normalizedIds) { materialized.push( await materializeAssetToWorkspace({ assetService, resolveWorkspaceRoot, userId: resolvedUserId, assetId, }), ); } return { materialized }; }, }; }