Skip to content

Commit 17d293d

Browse files
authored
Support OpenClaw callback attachments
Adds inline base64 attachment support to openclaw-channel callbacks, stores callback artifacts in Calciforge-owned temp storage, forwards plugin result attachments, and documents the callback payload contract.
1 parent e0de10a commit 17d293d

3 files changed

Lines changed: 445 additions & 18 deletions

File tree

crates/calciforge-openclaw-channel-plugin/index.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ async function handleInboundRequest({
163163
api,
164164
sessionKey,
165165
);
166-
if (isSilentReply(replyText)) {
166+
const attachments = normalizeAttachments(result.attachments);
167+
if (isSilentReply(replyText) && attachments.length === 0) {
167168
log?.info?.("[calciforge-channel] silent reply - not forwarding");
168169
return true;
169170
}
@@ -173,6 +174,7 @@ async function handleInboundRequest({
173174
replyAuthToken,
174175
sessionKey,
175176
message: replyText,
177+
attachments,
176178
channel,
177179
replyTo,
178180
log,
@@ -243,6 +245,7 @@ async function deliverReply({
243245
replyAuthToken,
244246
sessionKey,
245247
message,
248+
attachments,
246249
channel,
247250
replyTo,
248251
log,
@@ -253,13 +256,18 @@ async function deliverReply({
253256
}
254257

255258
try {
259+
const payload = { sessionKey, message, channel, to: replyTo };
260+
if (attachments?.length) {
261+
payload.attachments = attachments;
262+
}
263+
256264
const resp = await fetch(replyWebhook, {
257265
method: "POST",
258266
headers: {
259267
"Content-Type": "application/json",
260268
Authorization: `Bearer ${replyAuthToken}`,
261269
},
262-
body: JSON.stringify({ sessionKey, message, channel, to: replyTo }),
270+
body: JSON.stringify(payload),
263271
signal: AbortSignal.timeout(30000),
264272
});
265273

@@ -273,6 +281,25 @@ async function deliverReply({
273281
}
274282
}
275283

284+
function normalizeAttachments(value) {
285+
if (!Array.isArray(value)) return [];
286+
return value
287+
.map((attachment) => {
288+
if (!attachment || typeof attachment !== "object") return null;
289+
const dataBase64 = attachment.dataBase64 ?? attachment.data_base64;
290+
const mimeType = attachment.mimeType ?? attachment.mime_type;
291+
if (typeof dataBase64 !== "string" || !dataBase64) return null;
292+
if (mimeType !== undefined && typeof mimeType !== "string") return null;
293+
return {
294+
...(typeof attachment.name === "string" ? { name: attachment.name } : {}),
295+
...(typeof mimeType === "string" ? { mimeType } : {}),
296+
...(typeof attachment.caption === "string" ? { caption: attachment.caption } : {}),
297+
dataBase64,
298+
};
299+
})
300+
.filter(Boolean);
301+
}
302+
276303
function json(res, status, body) {
277304
res.writeHead(status, { "Content-Type": "application/json" });
278305
res.end(JSON.stringify(body));

0 commit comments

Comments
 (0)