dsh-im-ops/src/channels/shared/semantic/artifact-delivery.mjs

177 lines
5.1 KiB
JavaScript

import {
materializeOutboundArtifact,
releaseOutboundArtifact,
} from './artifact.mjs';
import {
createArtifactFailureReceipt,
createDeliveryReceipt,
mergeDeliveryReceipts,
providerMessageIdsFor,
} from './delivery.mjs';
function unavailableError() {
const error = new Error('Native file delivery is unavailable');
error.code = 'artifact-provider-unavailable';
return error;
}
function isAbort(error, signal) {
return signal?.aborted
|| error?.name === 'AbortError'
|| error?.code === 'ABORT_ERR';
}
function providerIds(value) {
if (Array.isArray(value)) {
return [...new Set(value
.filter((candidate) => (
(typeof candidate === 'string' && candidate.trim())
|| Number.isSafeInteger(candidate)
))
.map(String))];
}
return providerMessageIdsFor(value);
}
async function sendMaterializedArtifact(file, {
sendFile,
sendImage,
signal,
}) {
if (file.mediaType?.startsWith('image/') && typeof sendImage === 'function') {
try {
return {
presentation: 'image',
result: await sendImage(file),
};
} catch (error) {
if (isAbort(error, signal) || error?.code === 'artifact-delivery-uncertain') {
throw error;
}
}
}
signal?.throwIfAborted();
if (typeof sendFile !== 'function') throw unavailableError();
return {
presentation: 'file',
result: await sendFile(file),
};
}
/**
* Deliver registered artifacts with one shared image-first policy while keeping
* provider protocol details inside the channel-supplied send closures.
*/
export async function deliverOutboundArtifacts({
artifacts = [],
baseReceipt = null,
deliveryId,
aggregatePresentation,
alwaysMerge = false,
channelKey,
signal,
sendFile,
sendImage,
sendFailureNotice,
onFailure,
logger,
}) {
const receipts = baseReceipt ? [baseReceipt] : [];
let userVisible = Boolean(baseReceipt) && baseReceipt.deliveryOutcome !== 'failed';
let failureNoticeVisible = false;
let artifactsSent = 0;
let artifactSendErrors = 0;
let artifactIndex = 0;
try {
while (artifactIndex < artifacts.length) {
const artifact = artifacts[artifactIndex];
artifactIndex += 1;
try {
signal?.throwIfAborted();
const file = await materializeOutboundArtifact(artifact, { signal });
signal?.throwIfAborted();
const sent = await sendMaterializedArtifact(file, {
sendFile,
sendImage,
signal,
});
signal?.throwIfAborted();
receipts.push(createDeliveryReceipt({
deliveryId: file.deliveryKey,
presentation: `${channelKey}-${sent.presentation}`,
providerMessageIds: providerIds(sent.result),
artifacts: [{ artifactId: file.artifactId, outcome: 'sent' }],
}));
artifactsSent += 1;
userVisible = true;
} catch (error) {
if (isAbort(error, signal)) throw error;
artifactSendErrors += 1;
const failure = typeof onFailure === 'function'
? await onFailure(artifact, error)
: null;
const reference = typeof failure?.referenceId === 'string'
? ` [${failure.referenceId}]`
: '';
logger?.warn?.(
`[dsh-im:${channelKey}] result artifact delivery failed${reference} (${error?.code ?? 'unknown'})`,
);
let messageIds = [];
if (typeof sendFailureNotice === 'function') {
try {
signal?.throwIfAborted();
const notice = await sendFailureNotice(artifact, error, failure);
signal?.throwIfAborted();
messageIds = providerIds(notice);
failureNoticeVisible = true;
} catch (noticeError) {
if (isAbort(noticeError, signal)) throw noticeError;
logger?.warn?.(
`[dsh-im:${channelKey}] unable to send the safe artifact failure notice`,
);
}
}
const failureReceipt = createArtifactFailureReceipt({
artifactId: artifact?.artifactId ?? 'unknown',
deliveryId: artifact?.deliveryKey ?? artifact?.artifactId ?? 'unknown',
error,
providerMessageIds: messageIds,
});
receipts.push(failureReceipt);
if (failureNoticeVisible || failureReceipt.artifacts[0]?.outcome === 'unknown') {
userVisible = true;
}
} finally {
releaseOutboundArtifact(artifact);
}
}
} finally {
while (artifactIndex < artifacts.length) {
releaseOutboundArtifact(artifacts[artifactIndex]);
artifactIndex += 1;
}
}
let receipt = null;
if (receipts.length === 1 && !alwaysMerge) {
[receipt] = receipts;
} else if (receipts.length > 0) {
receipt = mergeDeliveryReceipts({
deliveryId: deliveryId
?? baseReceipt?.deliveryId
?? artifacts[0]?.deliveryKey,
presentation: aggregatePresentation
?? `${channelKey}-${baseReceipt ? 'text-and-files' : 'files'}`,
receipts,
});
}
return {
receipt,
userVisible,
failureNoticeVisible,
artifactsSent,
artifactSendErrors,
};
}