Files
memind/mindspace-conversation-package.mjs
T
2026-07-02 21:03:14 +08:00

166 lines
5.8 KiB
JavaScript

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,
};