feat: add native image delivery across channels

This commit is contained in:
xmanrui 2026-08-24 03:25:38 +08:00
parent dbc0a4b730
commit 3ec5f1eca7
31 changed files with 2754 additions and 846 deletions

View file

@ -35,14 +35,9 @@ import {
prefetchInboundFiles,
} from '../shared/inbound-file.mjs';
import { rememberConnectionTestTarget } from '../shared/connection-test.mjs';
import { deliverOutboundArtifacts } from '../shared/semantic/artifact-delivery.mjs';
import {
materializeOutboundArtifact,
releaseOutboundArtifact,
} from '../shared/semantic/artifact.mjs';
import {
createArtifactFailureReceipt,
createDeliveryReceipt,
mergeDeliveryReceipts,
providerMessageIdsFor,
} from '../shared/semantic/delivery.mjs';
@ -1084,70 +1079,39 @@ export class DingtalkHarnessBridge {
}
async #deliverArtifacts(target, sessionWebhook, replyTo, artifacts, baseReceipt) {
const receipts = baseReceipt ? [baseReceipt] : [];
let userVisible = Boolean(baseReceipt);
for (const artifact of artifacts) {
this.#signal?.throwIfAborted();
try {
if (typeof this.#api.sendFile !== 'function') {
const unavailable = new Error('DingTalk file delivery is unavailable');
unavailable.code = 'artifact-provider-unavailable';
throw unavailable;
}
const file = await materializeOutboundArtifact(artifact, {
signal: this.#signal,
});
const result = await this.#api.sendFile({
clientId: this.#clientId,
clientSecret: this.#clientSecret,
target,
file,
signal: this.#signal,
});
receipts.push(createDeliveryReceipt({
deliveryId: file.deliveryKey,
presentation: 'dingtalk-file',
providerMessageIds: dingtalkFileProviderIds(result),
artifacts: [{ artifactId: file.artifactId, outcome: 'sent' }],
}));
userVisible = true;
this.#status.artifactsSent = (this.#status.artifactsSent ?? 0) + 1;
} catch (error) {
if (this.#signal?.aborted) throw error;
this.#status.artifactSendErrors = (this.#status.artifactSendErrors ?? 0) + 1;
this.#logger.warn?.(
`[dsh-dingtalk] result file delivery failed (${error?.code ?? 'unknown'})`,
);
let noticeSent = false;
const providerMessageIds = await this.#send(
sessionWebhook,
artifactFailureText(artifact?.fileName, error),
).then((ids) => {
noticeSent = true;
return ids;
}).catch(() => []);
const failureReceipt = createArtifactFailureReceipt({
artifactId: artifact?.artifactId ?? 'unknown',
deliveryId: artifact?.deliveryKey ?? artifact?.artifactId ?? 'unknown',
error,
providerMessageIds,
});
receipts.push(failureReceipt);
if (noticeSent || failureReceipt.artifacts[0]?.outcome === 'unknown') userVisible = true;
} finally {
releaseOutboundArtifact(artifact);
}
}
const receipt = receipts.length === 0
? null
: receipts.length === 1
? receipts[0]
: mergeDeliveryReceipts({
deliveryId: replyTo,
presentation: baseReceipt ? 'dingtalk-text-and-files' : 'dingtalk-files',
receipts,
});
return { receipt, userVisible };
const sendArtifact = async (method, file) => dingtalkFileProviderIds(
await this.#api[method]({
clientId: this.#clientId,
clientSecret: this.#clientSecret,
target,
file,
signal: this.#signal,
}),
);
const delivery = await deliverOutboundArtifacts({
artifacts,
baseReceipt,
deliveryId: replyTo,
aggregatePresentation: baseReceipt ? 'dingtalk-text-and-files' : 'dingtalk-files',
channelKey: 'dingtalk',
signal: this.#signal,
sendImage: typeof this.#api.sendImage === 'function'
? (file) => sendArtifact('sendImage', file)
: undefined,
sendFile: typeof this.#api.sendFile === 'function'
? (file) => sendArtifact('sendFile', file)
: undefined,
sendFailureNotice: (artifact, error) => this.#send(
sessionWebhook,
artifactFailureText(artifact?.fileName, error),
),
logger: this.#logger,
});
this.#status.artifactsSent = (this.#status.artifactsSent ?? 0)
+ delivery.artifactsSent;
this.#status.artifactSendErrors = (this.#status.artifactSendErrors ?? 0)
+ delivery.artifactSendErrors;
return { receipt: delivery.receipt, userVisible: delivery.userVisible };
}
}