const PACKAGE_ID_PREFIX = 'cp_'; const ARTIFACT_ID_PREFIX = 'ca_'; export const CONVERSATION_PACKAGE_SCHEMA_VERSION = 1; export const CONVERSATION_ARTIFACT_KINDS = Object.freeze([ 'input_image', 'input_file', 'generated_image', 'generated_file', 'page', 'public_html', 'long_image', 'thumbnail', 'docx', 'pdf', ]); function requiredString(value, field) { const normalized = String(value ?? '').trim(); if (!normalized) { throw Object.assign(new Error(`${field} is required`), { code: 'invalid_conversation_package', field, }); } return normalized; } function optionalString(value) { const normalized = String(value ?? '').trim(); return normalized || null; } function safeStorageSegment(value, field) { const normalized = requiredString(value, field); const encoded = encodeURIComponent(normalized); if (encoded.includes('%2F') || encoded.includes('%5C')) { throw Object.assign(new Error(`${field} must not contain path separators`), { code: 'invalid_storage_segment', field, }); } return encoded; } export function buildConversationStoragePrefix({ userId, sessionId }) { return `users/${safeStorageSegment(userId, 'userId')}/conversations/${safeStorageSegment( sessionId, 'sessionId', )}`; } export function buildConversationPackageUri({ userId, sessionId }) { return `mindspace://users/${safeStorageSegment(userId, 'userId')}/conversations/${safeStorageSegment( sessionId, 'sessionId', )}`; } export function createConversationPackageRecord(input) { const userId = requiredString(input?.userId, 'userId'); const sessionId = requiredString(input?.sessionId, 'sessionId'); const now = Number.isFinite(Number(input?.now)) ? Number(input.now) : Date.now(); const id = optionalString(input?.id) ?? `${PACKAGE_ID_PREFIX}${sessionId}`; const storagePrefix = optionalString(input?.storagePrefix) ?? buildConversationStoragePrefix({ userId, sessionId }); return { id, userId, sessionId, title: optionalString(input?.title), status: optionalString(input?.status) ?? 'active', storagePrefix, manifestAssetId: optionalString(input?.manifestAssetId), createdAt: Number.isFinite(Number(input?.createdAt)) ? Number(input.createdAt) : now, updatedAt: Number.isFinite(Number(input?.updatedAt)) ? Number(input.updatedAt) : now, }; } export function assertConversationArtifactKind(kind) { const normalized = requiredString(kind, 'artifactKind'); if (!CONVERSATION_ARTIFACT_KINDS.includes(normalized)) { throw Object.assign(new Error(`unsupported conversation artifact kind: ${normalized}`), { code: 'unsupported_conversation_artifact_kind', field: 'artifactKind', }); } return normalized; } export function createConversationArtifact(input) { const packageId = requiredString(input?.packageId, 'packageId'); const artifactKind = assertConversationArtifactKind(input?.artifactKind ?? input?.kind); const now = Number.isFinite(Number(input?.now)) ? Number(input.now) : Date.now(); const id = optionalString(input?.id) ?? `${ARTIFACT_ID_PREFIX}${packageId}_${artifactKind}_${now}`; return { id, packageId, artifactKind, role: optionalString(input?.role), assetId: optionalString(input?.assetId), pageId: optionalString(input?.pageId), publicationId: optionalString(input?.publicationId), agentRunId: optionalString(input?.agentRunId), messageId: optionalString(input?.messageId), displayName: optionalString(input?.displayName), mimeType: optionalString(input?.mimeType), sizeBytes: Number.isFinite(Number(input?.sizeBytes)) ? Number(input.sizeBytes) : null, storageKey: optionalString(input?.storageKey), canonicalUrl: optionalString(input?.canonicalUrl), sortOrder: Number.isFinite(Number(input?.sortOrder)) ? Number(input.sortOrder) : 0, createdAt: Number.isFinite(Number(input?.createdAt)) ? Number(input.createdAt) : now, }; } function artifactManifestEntry(artifact) { return { artifactId: artifact.id, kind: artifact.artifactKind, ...(artifact.role ? { role: artifact.role } : {}), ...(artifact.assetId ? { assetId: artifact.assetId } : {}), ...(artifact.pageId ? { pageId: artifact.pageId } : {}), ...(artifact.publicationId ? { publicationId: artifact.publicationId } : {}), ...(artifact.agentRunId ? { agentRunId: artifact.agentRunId } : {}), ...(artifact.messageId ? { messageId: artifact.messageId } : {}), ...(artifact.displayName ? { displayName: artifact.displayName } : {}), ...(artifact.mimeType ? { mimeType: artifact.mimeType } : {}), ...(artifact.sizeBytes !== null ? { sizeBytes: artifact.sizeBytes } : {}), ...(artifact.storageKey ? { storageKey: artifact.storageKey } : {}), ...(artifact.canonicalUrl ? { canonicalUrl: artifact.canonicalUrl } : {}), sortOrder: artifact.sortOrder, createdAt: artifact.createdAt, }; } export function buildConversationPackageManifest({ packageRecord, artifacts = [] }) { const record = createConversationPackageRecord(packageRecord); const normalizedArtifacts = artifacts .map((artifact) => createConversationArtifact({ ...artifact, packageId: record.id })) .sort((a, b) => a.sortOrder - b.sortOrder || a.createdAt - b.createdAt || a.id.localeCompare(b.id)); return { schemaVersion: CONVERSATION_PACKAGE_SCHEMA_VERSION, packageId: record.id, userId: record.userId, sessionId: record.sessionId, uri: buildConversationPackageUri({ userId: record.userId, sessionId: record.sessionId }), title: record.title, status: record.status, storagePrefix: record.storagePrefix, manifestAssetId: record.manifestAssetId, createdAt: record.createdAt, updatedAt: record.updatedAt, artifacts: normalizedArtifacts.map(artifactManifestEntry), }; } export const mindspaceConversationPackageInternals = { requiredString, optionalString, safeStorageSegment, };