@hywkp/sider 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/src/channel.d.ts +12 -0
- package/dist/src/channel.d.ts.map +1 -1
- package/dist/src/channel.js +369 -151
- package/dist/src/channel.js.map +1 -1
- package/dist/src/inbound-media.d.ts +24 -0
- package/dist/src/inbound-media.d.ts.map +1 -0
- package/dist/src/inbound-media.js +56 -0
- package/dist/src/inbound-media.js.map +1 -0
- package/dist/src/media-upload.d.ts +31 -0
- package/dist/src/media-upload.d.ts.map +1 -0
- package/dist/src/media-upload.js +319 -0
- package/dist/src/media-upload.js.map +1 -0
- package/package.json +1 -1
package/dist/src/channel.js
CHANGED
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
import { DEFAULT_ACCOUNT_ID, createReplyPrefixOptions, createTypingCallbacks, formatTextWithAttachmentLinks, normalizeAccountId, resolveOutboundMediaUrls, } from "openclaw/plugin-sdk";
|
|
2
|
+
import { resolveInboundSiderMedia } from "./inbound-media.js";
|
|
3
|
+
import { buildSiderPartsFromReplyPayload } from "./media-upload.js";
|
|
2
4
|
const CHANNEL_ID = "sider";
|
|
3
5
|
const DEFAULT_GATEWAY_URL = "http://47.82.167.142:3001";
|
|
4
6
|
const DEFAULT_CONNECT_TIMEOUT_MS = 8_000;
|
|
5
7
|
const DEFAULT_SEND_TIMEOUT_MS = 12_000;
|
|
6
8
|
const DEFAULT_RECONNECT_DELAY_MS = 2_000;
|
|
7
|
-
const IMAGE_EXTENSIONS = new Set([
|
|
8
|
-
".jpg",
|
|
9
|
-
".jpeg",
|
|
10
|
-
".png",
|
|
11
|
-
".gif",
|
|
12
|
-
".webp",
|
|
13
|
-
".bmp",
|
|
14
|
-
".svg",
|
|
15
|
-
".heic",
|
|
16
|
-
".heif",
|
|
17
|
-
".avif",
|
|
18
|
-
]);
|
|
19
|
-
const MIME_BY_EXTENSION = {
|
|
20
|
-
".jpg": "image/jpeg",
|
|
21
|
-
".jpeg": "image/jpeg",
|
|
22
|
-
".png": "image/png",
|
|
23
|
-
".gif": "image/gif",
|
|
24
|
-
".webp": "image/webp",
|
|
25
|
-
".bmp": "image/bmp",
|
|
26
|
-
".svg": "image/svg+xml",
|
|
27
|
-
".pdf": "application/pdf",
|
|
28
|
-
".txt": "text/plain",
|
|
29
|
-
".json": "application/json",
|
|
30
|
-
".zip": "application/zip",
|
|
31
|
-
};
|
|
32
9
|
const meta = {
|
|
33
10
|
id: CHANNEL_ID,
|
|
34
11
|
label: "Sider",
|
|
@@ -40,6 +17,9 @@ const meta = {
|
|
|
40
17
|
order: 97,
|
|
41
18
|
};
|
|
42
19
|
let runtimeRef = null;
|
|
20
|
+
const SIDER_SESSION_BINDING_TTL_MS = 6 * 60 * 60 * 1000;
|
|
21
|
+
const SIDER_SESSION_BINDING_MAX = 512;
|
|
22
|
+
const siderSessionBindings = new Map();
|
|
43
23
|
export function setSiderRuntime(runtime) {
|
|
44
24
|
runtimeRef = runtime;
|
|
45
25
|
}
|
|
@@ -65,6 +45,96 @@ function logWarn(message, data) {
|
|
|
65
45
|
function sleep(ms) {
|
|
66
46
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
67
47
|
}
|
|
48
|
+
function normalizeSessionBindingKey(raw) {
|
|
49
|
+
const key = raw?.trim().toLowerCase();
|
|
50
|
+
return key || undefined;
|
|
51
|
+
}
|
|
52
|
+
function pruneSiderSessionBindings(now = Date.now()) {
|
|
53
|
+
for (const [key, binding] of siderSessionBindings) {
|
|
54
|
+
if (now - binding.lastSeenAt > SIDER_SESSION_BINDING_TTL_MS) {
|
|
55
|
+
siderSessionBindings.delete(key);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (siderSessionBindings.size <= SIDER_SESSION_BINDING_MAX) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const sorted = [...siderSessionBindings.entries()].sort((a, b) => a[1].lastSeenAt - b[1].lastSeenAt);
|
|
62
|
+
const overflow = siderSessionBindings.size - SIDER_SESSION_BINDING_MAX;
|
|
63
|
+
for (let index = 0; index < overflow; index += 1) {
|
|
64
|
+
const key = sorted[index]?.[0];
|
|
65
|
+
if (key) {
|
|
66
|
+
siderSessionBindings.delete(key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function rememberSiderSessionBinding(params) {
|
|
71
|
+
const key = normalizeSessionBindingKey(params.sessionKey);
|
|
72
|
+
if (!key) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const existing = siderSessionBindings.get(key);
|
|
77
|
+
if (existing) {
|
|
78
|
+
existing.account = params.account;
|
|
79
|
+
existing.sessionId = params.sessionId;
|
|
80
|
+
existing.lastSeenAt = now;
|
|
81
|
+
pruneSiderSessionBindings(now);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
siderSessionBindings.set(key, {
|
|
85
|
+
account: params.account,
|
|
86
|
+
sessionId: params.sessionId,
|
|
87
|
+
lastSeenAt: now,
|
|
88
|
+
toolSeq: 0,
|
|
89
|
+
currentToolCallId: undefined,
|
|
90
|
+
callIdByToolCallId: new Map(),
|
|
91
|
+
});
|
|
92
|
+
pruneSiderSessionBindings(now);
|
|
93
|
+
}
|
|
94
|
+
function resolveSiderSessionBinding(sessionKey) {
|
|
95
|
+
const key = normalizeSessionBindingKey(sessionKey);
|
|
96
|
+
if (!key) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const binding = siderSessionBindings.get(key);
|
|
100
|
+
if (!binding) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
binding.lastSeenAt = Date.now();
|
|
104
|
+
return binding;
|
|
105
|
+
}
|
|
106
|
+
function toJsonSafeValue(value) {
|
|
107
|
+
if (value === undefined) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(JSON.stringify(value));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return String(value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function extractToolResultText(result) {
|
|
118
|
+
const maxChars = 4000;
|
|
119
|
+
const clip = (text) => {
|
|
120
|
+
if (text.length <= maxChars) {
|
|
121
|
+
return text;
|
|
122
|
+
}
|
|
123
|
+
return `${text.slice(0, maxChars)}...`;
|
|
124
|
+
};
|
|
125
|
+
const record = toRecord(result);
|
|
126
|
+
if (!record) {
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
const candidateKeys = ["text", "content", "message", "output", "stdout"];
|
|
130
|
+
for (const key of candidateKeys) {
|
|
131
|
+
const value = record[key];
|
|
132
|
+
if (typeof value === "string" && value.trim()) {
|
|
133
|
+
return clip(value.trim());
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
68
138
|
function toRecord(value) {
|
|
69
139
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
70
140
|
return null;
|
|
@@ -138,6 +208,26 @@ function parseSessionTarget(raw) {
|
|
|
138
208
|
}
|
|
139
209
|
return trimmed;
|
|
140
210
|
}
|
|
211
|
+
function normalizeSiderMessagingTarget(raw) {
|
|
212
|
+
const trimmed = raw.trim();
|
|
213
|
+
if (!trimmed) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
const withoutProviderPrefix = trimmed.replace(/^sider:/i, "").trim();
|
|
217
|
+
return withoutProviderPrefix || undefined;
|
|
218
|
+
}
|
|
219
|
+
function looksLikeSiderTargetId(raw, normalized) {
|
|
220
|
+
const candidate = (normalized ?? raw ?? "").trim();
|
|
221
|
+
if (!candidate) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const match = candidate.match(/^session:(.+)$/i);
|
|
225
|
+
if (match) {
|
|
226
|
+
return match[1].trim().length > 0;
|
|
227
|
+
}
|
|
228
|
+
// Sider has no directory lookup yet; treat non-whitespace tokens as explicit ids.
|
|
229
|
+
return !/\s/.test(candidate);
|
|
230
|
+
}
|
|
141
231
|
function resolveOutboundSessionId(params) {
|
|
142
232
|
const target = params.to?.trim() || params.account.defaultTo?.trim() || "";
|
|
143
233
|
if (!target) {
|
|
@@ -415,94 +505,6 @@ async function sendSiderEventBestEffort(params) {
|
|
|
415
505
|
});
|
|
416
506
|
}
|
|
417
507
|
}
|
|
418
|
-
function extFromMediaUrl(raw) {
|
|
419
|
-
try {
|
|
420
|
-
if (/^https?:\/\//i.test(raw)) {
|
|
421
|
-
const pathname = new URL(raw).pathname;
|
|
422
|
-
return pathname.includes(".") ? pathname.slice(pathname.lastIndexOf(".")).toLowerCase() : "";
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
catch {
|
|
426
|
-
// no-op
|
|
427
|
-
}
|
|
428
|
-
const qPos = raw.indexOf("?");
|
|
429
|
-
const sanitized = qPos >= 0 ? raw.slice(0, qPos) : raw;
|
|
430
|
-
const slashPos = sanitized.lastIndexOf("/");
|
|
431
|
-
const last = slashPos >= 0 ? sanitized.slice(slashPos + 1) : sanitized;
|
|
432
|
-
const dotPos = last.lastIndexOf(".");
|
|
433
|
-
return dotPos >= 0 ? last.slice(dotPos).toLowerCase() : "";
|
|
434
|
-
}
|
|
435
|
-
function fileNameFromMediaUrl(raw) {
|
|
436
|
-
try {
|
|
437
|
-
if (/^https?:\/\//i.test(raw)) {
|
|
438
|
-
const pathname = new URL(raw).pathname;
|
|
439
|
-
const seg = pathname.split("/").filter(Boolean).pop();
|
|
440
|
-
return seg || undefined;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
catch {
|
|
444
|
-
// no-op
|
|
445
|
-
}
|
|
446
|
-
const qPos = raw.indexOf("?");
|
|
447
|
-
const sanitized = qPos >= 0 ? raw.slice(0, qPos) : raw;
|
|
448
|
-
const seg = sanitized.split("/").filter(Boolean).pop();
|
|
449
|
-
return seg || undefined;
|
|
450
|
-
}
|
|
451
|
-
function inferMediaKind(mediaUrl) {
|
|
452
|
-
const ext = extFromMediaUrl(mediaUrl);
|
|
453
|
-
return IMAGE_EXTENSIONS.has(ext) ? "image" : "file";
|
|
454
|
-
}
|
|
455
|
-
function inferMediaMimeType(mediaUrl) {
|
|
456
|
-
const ext = extFromMediaUrl(mediaUrl);
|
|
457
|
-
return MIME_BY_EXTENSION[ext];
|
|
458
|
-
}
|
|
459
|
-
async function uploadMediaToSider(params) {
|
|
460
|
-
const mediaKind = inferMediaKind(params.mediaUrl);
|
|
461
|
-
const fileName = fileNameFromMediaUrl(params.mediaUrl);
|
|
462
|
-
const mimeType = inferMediaMimeType(params.mediaUrl);
|
|
463
|
-
// TODO(siderclaw-upload): Upload local/remote media to sider server and return hosted resource URL.
|
|
464
|
-
// Current placeholder returns the original mediaUrl directly.
|
|
465
|
-
const resourceUrl = params.mediaUrl;
|
|
466
|
-
void params.account;
|
|
467
|
-
return {
|
|
468
|
-
resourceUrl,
|
|
469
|
-
mediaKind,
|
|
470
|
-
mimeType,
|
|
471
|
-
fileName,
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
async function buildSiderPartsFromReplyPayload(params) {
|
|
475
|
-
const parts = [];
|
|
476
|
-
const text = params.payload.text?.trim();
|
|
477
|
-
if (text) {
|
|
478
|
-
parts.push({
|
|
479
|
-
type: "core.text",
|
|
480
|
-
spec_version: 1,
|
|
481
|
-
payload: { text },
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
const mediaUrls = resolveOutboundMediaUrls(params.payload);
|
|
485
|
-
for (const mediaUrl of mediaUrls) {
|
|
486
|
-
const uploaded = await uploadMediaToSider({
|
|
487
|
-
account: params.account,
|
|
488
|
-
mediaUrl,
|
|
489
|
-
});
|
|
490
|
-
parts.push({
|
|
491
|
-
type: "core.media",
|
|
492
|
-
spec_version: 1,
|
|
493
|
-
payload: {
|
|
494
|
-
media_type: uploaded.mediaKind,
|
|
495
|
-
url: uploaded.resourceUrl,
|
|
496
|
-
mime_type: uploaded.mimeType,
|
|
497
|
-
file_name: uploaded.fileName,
|
|
498
|
-
},
|
|
499
|
-
meta: {
|
|
500
|
-
source_media_url: mediaUrl,
|
|
501
|
-
},
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
return parts;
|
|
505
|
-
}
|
|
506
508
|
function parseTextFromPart(part) {
|
|
507
509
|
if (part.type !== "core.text") {
|
|
508
510
|
return null;
|
|
@@ -519,21 +521,27 @@ function parseTextFromPart(part) {
|
|
|
519
521
|
return null;
|
|
520
522
|
}
|
|
521
523
|
function parseMediaFromPart(part) {
|
|
522
|
-
if (part.type !== "core.media") {
|
|
524
|
+
if (part.type !== "core.media" && part.type !== "core.file") {
|
|
523
525
|
return null;
|
|
524
526
|
}
|
|
525
527
|
const payload = toRecord(part.payload);
|
|
526
528
|
if (!payload) {
|
|
527
529
|
return null;
|
|
528
530
|
}
|
|
529
|
-
const urlCandidates = [payload.url, payload.resource_url, payload.media_url];
|
|
531
|
+
const urlCandidates = [payload.download_url, payload.url, payload.resource_url, payload.media_url];
|
|
530
532
|
const url = urlCandidates.find((entry) => typeof entry === "string" && entry.trim());
|
|
531
533
|
if (!url) {
|
|
532
534
|
return null;
|
|
533
535
|
}
|
|
534
|
-
const mimeCandidates = [payload.mime_type, payload.content_type];
|
|
536
|
+
const mimeCandidates = [payload.mime, payload.mime_type, payload.content_type];
|
|
535
537
|
const mimeType = mimeCandidates.find((entry) => typeof entry === "string" && entry.trim());
|
|
536
|
-
|
|
538
|
+
const fileNameCandidates = [payload.name, payload.file_name];
|
|
539
|
+
const fileName = fileNameCandidates.find((entry) => typeof entry === "string" && entry.trim());
|
|
540
|
+
return {
|
|
541
|
+
url: url.trim(),
|
|
542
|
+
mimeType: mimeType?.trim() || undefined,
|
|
543
|
+
fileName: fileName?.trim() || undefined,
|
|
544
|
+
};
|
|
537
545
|
}
|
|
538
546
|
function buildEventMeta(params) {
|
|
539
547
|
return {
|
|
@@ -594,6 +602,147 @@ function buildStreamingDoneEvent(params) {
|
|
|
594
602
|
meta: buildEventMeta({ accountId: params.accountId }),
|
|
595
603
|
};
|
|
596
604
|
}
|
|
605
|
+
function buildToolCallEvent(params) {
|
|
606
|
+
return {
|
|
607
|
+
eventType: "tool.call",
|
|
608
|
+
payload: {
|
|
609
|
+
session_id: params.sessionId,
|
|
610
|
+
seq: params.seq,
|
|
611
|
+
call_id: params.callId,
|
|
612
|
+
phase: params.phase,
|
|
613
|
+
tool_name: params.toolName,
|
|
614
|
+
tool_call_id: params.toolCallId,
|
|
615
|
+
run_id: params.runId,
|
|
616
|
+
session_key: params.sessionKey,
|
|
617
|
+
tool_args: params.toolArgs,
|
|
618
|
+
error: params.error,
|
|
619
|
+
duration_ms: params.durationMs,
|
|
620
|
+
ts: Date.now(),
|
|
621
|
+
},
|
|
622
|
+
meta: buildEventMeta({ accountId: params.accountId }),
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function buildToolResultEvent(params) {
|
|
626
|
+
const text = extractToolResultText(params.result);
|
|
627
|
+
const safeResult = toJsonSafeValue(params.result);
|
|
628
|
+
const safeToolArgs = toJsonSafeValue(params.toolArgs);
|
|
629
|
+
return {
|
|
630
|
+
eventType: "tool.result",
|
|
631
|
+
payload: {
|
|
632
|
+
session_id: params.sessionId,
|
|
633
|
+
seq: params.seq,
|
|
634
|
+
call_id: params.callId,
|
|
635
|
+
tool_name: params.toolName,
|
|
636
|
+
tool_call_id: params.toolCallId,
|
|
637
|
+
run_id: params.runId,
|
|
638
|
+
session_key: params.sessionKey,
|
|
639
|
+
tool_args: safeToolArgs,
|
|
640
|
+
result: safeResult,
|
|
641
|
+
error: params.error,
|
|
642
|
+
duration_ms: params.durationMs,
|
|
643
|
+
text,
|
|
644
|
+
has_text: text.trim().length > 0,
|
|
645
|
+
media_urls: [],
|
|
646
|
+
media_count: 0,
|
|
647
|
+
is_error: Boolean(params.error),
|
|
648
|
+
ts: Date.now(),
|
|
649
|
+
},
|
|
650
|
+
meta: buildEventMeta({ accountId: params.accountId }),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function resolveRelayCallIdForToolEvent(params) {
|
|
654
|
+
if (params.toolCallId) {
|
|
655
|
+
const existing = params.binding.callIdByToolCallId.get(params.toolCallId);
|
|
656
|
+
if (existing) {
|
|
657
|
+
params.binding.currentToolCallId = existing;
|
|
658
|
+
return existing;
|
|
659
|
+
}
|
|
660
|
+
const created = crypto.randomUUID();
|
|
661
|
+
params.binding.callIdByToolCallId.set(params.toolCallId, created);
|
|
662
|
+
params.binding.currentToolCallId = created;
|
|
663
|
+
return created;
|
|
664
|
+
}
|
|
665
|
+
if (params.phase === "start" || !params.binding.currentToolCallId) {
|
|
666
|
+
params.binding.currentToolCallId = crypto.randomUUID();
|
|
667
|
+
}
|
|
668
|
+
return params.binding.currentToolCallId;
|
|
669
|
+
}
|
|
670
|
+
function clearRelayCallIdForToolEvent(params) {
|
|
671
|
+
if (params.toolCallId) {
|
|
672
|
+
params.binding.callIdByToolCallId.delete(params.toolCallId);
|
|
673
|
+
}
|
|
674
|
+
if (params.binding.currentToolCallId === params.callId) {
|
|
675
|
+
params.binding.currentToolCallId = undefined;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
export async function emitSiderToolHookEvent(params) {
|
|
679
|
+
const binding = resolveSiderSessionBinding(params.sessionKey);
|
|
680
|
+
if (!binding) {
|
|
681
|
+
logDebug("skip sider tool hook event: session binding not found", {
|
|
682
|
+
sessionKey: params.sessionKey,
|
|
683
|
+
toolName: params.toolName,
|
|
684
|
+
toolCallId: params.toolCallId,
|
|
685
|
+
phase: params.phase,
|
|
686
|
+
});
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const callId = resolveRelayCallIdForToolEvent({
|
|
690
|
+
binding,
|
|
691
|
+
phase: params.phase,
|
|
692
|
+
toolCallId: params.toolCallId,
|
|
693
|
+
});
|
|
694
|
+
if (params.phase === "start") {
|
|
695
|
+
binding.toolSeq += 1;
|
|
696
|
+
const toolCallEvent = buildToolCallEvent({
|
|
697
|
+
sessionId: binding.sessionId,
|
|
698
|
+
accountId: binding.account.accountId,
|
|
699
|
+
seq: binding.toolSeq,
|
|
700
|
+
callId,
|
|
701
|
+
phase: "start",
|
|
702
|
+
toolName: params.toolName,
|
|
703
|
+
toolCallId: params.toolCallId,
|
|
704
|
+
runId: params.runId,
|
|
705
|
+
sessionKey: params.sessionKey,
|
|
706
|
+
toolArgs: params.params,
|
|
707
|
+
error: params.error,
|
|
708
|
+
durationMs: params.durationMs,
|
|
709
|
+
});
|
|
710
|
+
await sendSiderEventBestEffort({
|
|
711
|
+
account: binding.account,
|
|
712
|
+
sessionId: binding.sessionId,
|
|
713
|
+
event: toolCallEvent,
|
|
714
|
+
context: "tool.call.hook.start",
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
if (params.phase !== "start") {
|
|
718
|
+
binding.toolSeq += 1;
|
|
719
|
+
const toolResultEvent = buildToolResultEvent({
|
|
720
|
+
sessionId: binding.sessionId,
|
|
721
|
+
accountId: binding.account.accountId,
|
|
722
|
+
seq: binding.toolSeq,
|
|
723
|
+
callId,
|
|
724
|
+
toolName: params.toolName,
|
|
725
|
+
toolCallId: params.toolCallId,
|
|
726
|
+
runId: params.runId,
|
|
727
|
+
sessionKey: params.sessionKey,
|
|
728
|
+
toolArgs: params.params,
|
|
729
|
+
result: params.result,
|
|
730
|
+
error: params.error,
|
|
731
|
+
durationMs: params.durationMs,
|
|
732
|
+
});
|
|
733
|
+
await sendSiderEventBestEffort({
|
|
734
|
+
account: binding.account,
|
|
735
|
+
sessionId: binding.sessionId,
|
|
736
|
+
event: toolResultEvent,
|
|
737
|
+
context: `tool.result.hook.${params.phase}`,
|
|
738
|
+
});
|
|
739
|
+
clearRelayCallIdForToolEvent({
|
|
740
|
+
binding,
|
|
741
|
+
callId,
|
|
742
|
+
toolCallId: params.toolCallId,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
597
746
|
async function openStreamingSessionIfNeeded(params) {
|
|
598
747
|
if (params.streamState.active && params.streamState.streamId) {
|
|
599
748
|
return;
|
|
@@ -752,34 +901,42 @@ async function handleStreamingPartialSnapshot(params) {
|
|
|
752
901
|
params.streamState.accumulatedBlockText = params.snapshot;
|
|
753
902
|
}
|
|
754
903
|
async function deliverReplyPayloadToSider(params) {
|
|
904
|
+
const payloadMediaUrls = resolveOutboundMediaUrls(params.payload);
|
|
755
905
|
if (params.kind === "block") {
|
|
756
906
|
const delta = typeof params.payload.text === "string" ? params.payload.text : "";
|
|
757
|
-
if (delta.length
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
params.streamState.blockDeltaCount += 1;
|
|
761
|
-
mergeBlockTextIntoStreamState({
|
|
762
|
-
streamState: params.streamState,
|
|
763
|
-
text: delta,
|
|
764
|
-
});
|
|
765
|
-
if (params.streamState.partialDeltaCount === 0) {
|
|
766
|
-
await sendStreamingDeltaEvent({
|
|
767
|
-
account: params.account,
|
|
768
|
-
sessionId: params.sessionId,
|
|
907
|
+
if (delta.length > 0) {
|
|
908
|
+
params.streamState.blockDeltaCount += 1;
|
|
909
|
+
mergeBlockTextIntoStreamState({
|
|
769
910
|
streamState: params.streamState,
|
|
770
|
-
delta,
|
|
771
|
-
context: "stream.delta.block",
|
|
911
|
+
text: delta,
|
|
772
912
|
});
|
|
913
|
+
if (params.streamState.partialDeltaCount === 0) {
|
|
914
|
+
await sendStreamingDeltaEvent({
|
|
915
|
+
account: params.account,
|
|
916
|
+
sessionId: params.sessionId,
|
|
917
|
+
streamState: params.streamState,
|
|
918
|
+
delta,
|
|
919
|
+
context: "stream.delta.block",
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
logDebug("skip block stream delta because partial streaming is active", {
|
|
924
|
+
accountId: params.account.accountId,
|
|
925
|
+
sessionId: params.sessionId,
|
|
926
|
+
deltaLength: delta.length,
|
|
927
|
+
partialDeltaCount: params.streamState.partialDeltaCount,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
773
930
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
accountId: params.account.accountId,
|
|
777
|
-
sessionId: params.sessionId,
|
|
778
|
-
deltaLength: delta.length,
|
|
779
|
-
partialDeltaCount: params.streamState.partialDeltaCount,
|
|
780
|
-
});
|
|
931
|
+
if (payloadMediaUrls.length === 0) {
|
|
932
|
+
return;
|
|
781
933
|
}
|
|
782
|
-
|
|
934
|
+
logDebug("block payload contains media; sending persisted sider message", {
|
|
935
|
+
accountId: params.account.accountId,
|
|
936
|
+
sessionId: params.sessionId,
|
|
937
|
+
mediaCount: payloadMediaUrls.length,
|
|
938
|
+
hasText: delta.length > 0,
|
|
939
|
+
});
|
|
783
940
|
}
|
|
784
941
|
if (params.kind === "final") {
|
|
785
942
|
await flushStreamEventQueue(params.streamState);
|
|
@@ -810,20 +967,39 @@ async function deliverReplyPayloadToSider(params) {
|
|
|
810
967
|
}
|
|
811
968
|
const parts = await buildSiderPartsFromReplyPayload({
|
|
812
969
|
account: params.account,
|
|
970
|
+
sessionId: params.sessionId,
|
|
813
971
|
payload: params.payload,
|
|
972
|
+
logger: {
|
|
973
|
+
debug: logDebug,
|
|
974
|
+
warn: logWarn,
|
|
975
|
+
},
|
|
814
976
|
});
|
|
815
977
|
if (parts.length === 0) {
|
|
978
|
+
logDebug("skip sider outbound message: empty parts", {
|
|
979
|
+
accountId: params.account.accountId,
|
|
980
|
+
sessionId: params.sessionId,
|
|
981
|
+
kind: params.kind,
|
|
982
|
+
hasText: typeof params.payload.text === "string" && params.payload.text.length > 0,
|
|
983
|
+
mediaCount: payloadMediaUrls.length,
|
|
984
|
+
});
|
|
816
985
|
return;
|
|
817
986
|
}
|
|
987
|
+
logDebug("sending sider outbound message from payload", {
|
|
988
|
+
accountId: params.account.accountId,
|
|
989
|
+
sessionId: params.sessionId,
|
|
990
|
+
kind: params.kind,
|
|
991
|
+
partTypes: parts.map((part) => part.type),
|
|
992
|
+
mediaCount: payloadMediaUrls.length,
|
|
993
|
+
});
|
|
818
994
|
await sendMessageToSider({
|
|
819
995
|
account: params.account,
|
|
820
996
|
sessionId: params.sessionId,
|
|
821
997
|
parts,
|
|
822
998
|
});
|
|
823
|
-
if (params.kind === "final"
|
|
824
|
-
typeof params.payload.text === "string" &&
|
|
825
|
-
params.payload.text.trim()) {
|
|
999
|
+
if (params.kind === "final" || payloadMediaUrls.length > 0) {
|
|
826
1000
|
params.streamState.persistedFinalText = true;
|
|
1001
|
+
}
|
|
1002
|
+
if (params.kind === "final") {
|
|
827
1003
|
params.streamState.accumulatedBlockText = "";
|
|
828
1004
|
params.streamState.blockDeltaCount = 0;
|
|
829
1005
|
params.streamState.partialDeltaCount = 0;
|
|
@@ -856,7 +1032,7 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
856
1032
|
}
|
|
857
1033
|
const rawText = textChunks.join("\n").trim();
|
|
858
1034
|
const mediaUrls = mediaItems.map((item) => item.url);
|
|
859
|
-
const
|
|
1035
|
+
const parsedMediaTypes = mediaItems
|
|
860
1036
|
.map((item) => item.mimeType)
|
|
861
1037
|
.filter((item) => Boolean(item));
|
|
862
1038
|
const hasControlCommand = rawText ? core.channel.text.hasControlCommand(rawText, cfg) : false;
|
|
@@ -895,7 +1071,27 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
895
1071
|
commandAuthorized,
|
|
896
1072
|
});
|
|
897
1073
|
}
|
|
898
|
-
const
|
|
1074
|
+
const resolvedInboundMedia = await resolveInboundSiderMedia({
|
|
1075
|
+
runtime: core,
|
|
1076
|
+
cfg,
|
|
1077
|
+
accountId: account.accountId,
|
|
1078
|
+
mediaItems,
|
|
1079
|
+
logger: {
|
|
1080
|
+
debug: logDebug,
|
|
1081
|
+
warn: logWarn,
|
|
1082
|
+
},
|
|
1083
|
+
});
|
|
1084
|
+
const unresolvedMediaUrls = resolvedInboundMedia.unresolvedMedia.map((item) => item.url);
|
|
1085
|
+
if (unresolvedMediaUrls.length > 0) {
|
|
1086
|
+
logWarn("sider inbound media fallback to attachment url", {
|
|
1087
|
+
accountId: account.accountId,
|
|
1088
|
+
sessionId,
|
|
1089
|
+
unresolvedCount: unresolvedMediaUrls.length,
|
|
1090
|
+
unresolvedMediaUrls,
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
const bodyForAgent = formatTextWithAttachmentLinks(rawText, unresolvedMediaUrls);
|
|
1094
|
+
const mediaPayload = resolvedInboundMedia.mediaPayload;
|
|
899
1095
|
const route = core.channel.routing.resolveAgentRoute({
|
|
900
1096
|
cfg,
|
|
901
1097
|
channel: CHANNEL_ID,
|
|
@@ -937,12 +1133,21 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
937
1133
|
WasMentioned: true,
|
|
938
1134
|
OriginatingChannel: CHANNEL_ID,
|
|
939
1135
|
OriginatingTo: to,
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1136
|
+
...mediaPayload,
|
|
1137
|
+
MediaUrl: mediaPayload.MediaUrl ||
|
|
1138
|
+
(unresolvedMediaUrls.length > 0 ? unresolvedMediaUrls[0] : undefined),
|
|
1139
|
+
MediaUrls: mediaPayload.MediaUrls ||
|
|
1140
|
+
(unresolvedMediaUrls.length > 0 ? unresolvedMediaUrls : undefined),
|
|
1141
|
+
MediaType: mediaPayload.MediaType || parsedMediaTypes[0],
|
|
1142
|
+
MediaTypes: mediaPayload.MediaTypes ||
|
|
1143
|
+
(parsedMediaTypes.length > 0 ? parsedMediaTypes : undefined),
|
|
944
1144
|
CommandAuthorized: commandAuthorized,
|
|
945
1145
|
});
|
|
1146
|
+
rememberSiderSessionBinding({
|
|
1147
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
1148
|
+
account,
|
|
1149
|
+
sessionId,
|
|
1150
|
+
});
|
|
946
1151
|
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
|
|
947
1152
|
agentId: route.agentId,
|
|
948
1153
|
});
|
|
@@ -971,6 +1176,8 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
971
1176
|
commandAuthorized,
|
|
972
1177
|
textLength: rawText.length,
|
|
973
1178
|
mediaCount: mediaUrls.length,
|
|
1179
|
+
mediaDownloadedCount: mediaPayload.MediaPaths?.length ?? 0,
|
|
1180
|
+
mediaDownloadFailedCount: unresolvedMediaUrls.length,
|
|
974
1181
|
});
|
|
975
1182
|
const streamState = {
|
|
976
1183
|
active: false,
|
|
@@ -1128,8 +1335,6 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
1128
1335
|
});
|
|
1129
1336
|
streamState.persistedFinalText = true;
|
|
1130
1337
|
streamState.accumulatedBlockText = "";
|
|
1131
|
-
streamState.partialDeltaCount = 0;
|
|
1132
|
-
streamState.partialSnapshot = "";
|
|
1133
1338
|
}
|
|
1134
1339
|
}
|
|
1135
1340
|
async function handleInboundRealtimeEvent(params) {
|
|
@@ -1360,7 +1565,7 @@ export const siderPlugin = {
|
|
|
1360
1565
|
conversationId: result.conversationId,
|
|
1361
1566
|
};
|
|
1362
1567
|
},
|
|
1363
|
-
sendMedia: async ({ cfg, accountId, to, text, mediaUrl }) => {
|
|
1568
|
+
sendMedia: async ({ cfg, accountId, to, text, mediaUrl, mediaLocalRoots }) => {
|
|
1364
1569
|
const account = resolveSiderAccount(cfg, accountId);
|
|
1365
1570
|
if (!account.configured) {
|
|
1366
1571
|
throw new Error(`sider account "${account.accountId}" is not configured: missing gatewayUrl/sessionId(or sessionKey)/relayId`);
|
|
@@ -1368,10 +1573,16 @@ export const siderPlugin = {
|
|
|
1368
1573
|
const sessionId = resolveOutboundSessionId({ account, to });
|
|
1369
1574
|
const parts = await buildSiderPartsFromReplyPayload({
|
|
1370
1575
|
account,
|
|
1576
|
+
sessionId,
|
|
1371
1577
|
payload: {
|
|
1372
1578
|
text,
|
|
1373
1579
|
mediaUrl,
|
|
1374
1580
|
},
|
|
1581
|
+
mediaLocalRoots,
|
|
1582
|
+
logger: {
|
|
1583
|
+
debug: logDebug,
|
|
1584
|
+
warn: logWarn,
|
|
1585
|
+
},
|
|
1375
1586
|
});
|
|
1376
1587
|
if (parts.length === 0) {
|
|
1377
1588
|
throw new Error("sider sendMedia requires text and/or mediaUrl");
|
|
@@ -1404,5 +1615,12 @@ export const siderPlugin = {
|
|
|
1404
1615
|
ctx.log?.info(`[${account.accountId}] sider relay monitor stopped`);
|
|
1405
1616
|
},
|
|
1406
1617
|
},
|
|
1618
|
+
messaging: {
|
|
1619
|
+
normalizeTarget: normalizeSiderMessagingTarget,
|
|
1620
|
+
targetResolver: {
|
|
1621
|
+
looksLikeId: looksLikeSiderTargetId,
|
|
1622
|
+
hint: "session:<sessionId> (or raw <sessionId>)",
|
|
1623
|
+
},
|
|
1624
|
+
},
|
|
1407
1625
|
};
|
|
1408
1626
|
//# sourceMappingURL=channel.js.map
|