@hywkp/sider 0.0.3 → 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 +619 -194
- 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,83 +602,409 @@ function buildStreamingDoneEvent(params) {
|
|
|
594
602
|
meta: buildEventMeta({ accountId: params.accountId }),
|
|
595
603
|
};
|
|
596
604
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
params.
|
|
605
|
-
params.
|
|
606
|
-
params.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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;
|
|
618
659
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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,
|
|
626
686
|
});
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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,
|
|
634
709
|
});
|
|
635
710
|
await sendSiderEventBestEffort({
|
|
636
|
-
account:
|
|
637
|
-
sessionId:
|
|
638
|
-
event:
|
|
639
|
-
context: "
|
|
711
|
+
account: binding.account,
|
|
712
|
+
sessionId: binding.sessionId,
|
|
713
|
+
event: toolCallEvent,
|
|
714
|
+
context: "tool.call.hook.start",
|
|
640
715
|
});
|
|
641
|
-
return;
|
|
642
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
|
+
}
|
|
746
|
+
async function openStreamingSessionIfNeeded(params) {
|
|
643
747
|
if (params.streamState.active && params.streamState.streamId) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
params.streamState.active = true;
|
|
751
|
+
params.streamState.streamId = crypto.randomUUID();
|
|
752
|
+
params.streamState.seq = 0;
|
|
753
|
+
const streamStartEvent = buildStreamingStartEvent({
|
|
754
|
+
sessionId: params.sessionId,
|
|
755
|
+
streamId: params.streamState.streamId,
|
|
756
|
+
accountId: params.account.accountId,
|
|
757
|
+
});
|
|
758
|
+
await sendSiderEventBestEffort({
|
|
759
|
+
account: params.account,
|
|
760
|
+
sessionId: params.sessionId,
|
|
761
|
+
event: streamStartEvent,
|
|
762
|
+
context: "stream.start",
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
async function sendStreamingDeltaEvent(params) {
|
|
766
|
+
if (!params.delta) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
await openStreamingSessionIfNeeded({
|
|
770
|
+
account: params.account,
|
|
771
|
+
sessionId: params.sessionId,
|
|
772
|
+
streamState: params.streamState,
|
|
773
|
+
});
|
|
774
|
+
params.streamState.seq += 1;
|
|
775
|
+
const streamDeltaEvent = buildStreamingDeltaEvent({
|
|
776
|
+
sessionId: params.sessionId,
|
|
777
|
+
streamId: params.streamState.streamId,
|
|
778
|
+
seq: params.streamState.seq,
|
|
779
|
+
delta: params.delta,
|
|
780
|
+
accountId: params.account.accountId,
|
|
781
|
+
});
|
|
782
|
+
logDebug("sending stream delta event", {
|
|
783
|
+
accountId: params.account.accountId,
|
|
784
|
+
sessionId: params.sessionId,
|
|
785
|
+
eventType: streamDeltaEvent.eventType,
|
|
786
|
+
streamId: params.streamState.streamId,
|
|
787
|
+
seq: params.streamState.seq,
|
|
788
|
+
deltaLength: params.delta.length,
|
|
789
|
+
context: params.context,
|
|
790
|
+
});
|
|
791
|
+
await sendSiderEventBestEffort({
|
|
792
|
+
account: params.account,
|
|
793
|
+
sessionId: params.sessionId,
|
|
794
|
+
event: streamDeltaEvent,
|
|
795
|
+
context: params.context,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
async function closeStreamingSessionIfActive(params) {
|
|
799
|
+
if (!params.streamState.active || !params.streamState.streamId) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
params.streamState.seq += 1;
|
|
803
|
+
const streamDoneEvent = buildStreamingDoneEvent({
|
|
804
|
+
sessionId: params.sessionId,
|
|
805
|
+
streamId: params.streamState.streamId,
|
|
806
|
+
seq: params.streamState.seq,
|
|
807
|
+
accountId: params.account.accountId,
|
|
808
|
+
reason: params.reason,
|
|
809
|
+
});
|
|
810
|
+
await sendSiderEventBestEffort({
|
|
811
|
+
account: params.account,
|
|
812
|
+
sessionId: params.sessionId,
|
|
813
|
+
event: streamDoneEvent,
|
|
814
|
+
context: params.context,
|
|
815
|
+
});
|
|
816
|
+
params.streamState.active = false;
|
|
817
|
+
params.streamState.streamId = undefined;
|
|
818
|
+
params.streamState.seq = 0;
|
|
819
|
+
}
|
|
820
|
+
function enqueueStreamEventTask(params) {
|
|
821
|
+
const next = params.streamState.streamEventQueue.then(params.task).catch((err) => {
|
|
822
|
+
logWarn("sider stream event queue task failed", {
|
|
823
|
+
error: String(err),
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
params.streamState.streamEventQueue = next;
|
|
827
|
+
return next;
|
|
828
|
+
}
|
|
829
|
+
async function flushStreamEventQueue(streamState) {
|
|
830
|
+
await streamState.streamEventQueue;
|
|
831
|
+
}
|
|
832
|
+
function derivePartialDelta(params) {
|
|
833
|
+
const prev = params.previous;
|
|
834
|
+
const next = params.next;
|
|
835
|
+
if (!next) {
|
|
836
|
+
return "";
|
|
837
|
+
}
|
|
838
|
+
if (!prev) {
|
|
839
|
+
return next;
|
|
840
|
+
}
|
|
841
|
+
if (next === prev) {
|
|
842
|
+
return "";
|
|
843
|
+
}
|
|
844
|
+
if (next.startsWith(prev)) {
|
|
845
|
+
return next.slice(prev.length);
|
|
846
|
+
}
|
|
847
|
+
if (prev.startsWith(next)) {
|
|
848
|
+
// Snapshot regressed (restart/truncation); wait for next stable snapshot.
|
|
849
|
+
return "";
|
|
850
|
+
}
|
|
851
|
+
const maxOverlap = Math.min(prev.length, next.length);
|
|
852
|
+
for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
|
|
853
|
+
if (prev.endsWith(next.slice(0, overlap))) {
|
|
854
|
+
return next.slice(overlap);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return next;
|
|
858
|
+
}
|
|
859
|
+
function mergeBlockTextIntoStreamState(params) {
|
|
860
|
+
const next = params.text;
|
|
861
|
+
if (!next) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const prev = params.streamState.accumulatedBlockText;
|
|
865
|
+
if (!prev) {
|
|
866
|
+
params.streamState.accumulatedBlockText = next;
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
if (next.startsWith(prev)) {
|
|
870
|
+
// Some runtimes emit cumulative text snapshots.
|
|
871
|
+
params.streamState.accumulatedBlockText = next;
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (prev.endsWith(next)) {
|
|
875
|
+
// Avoid duplicating identical trailing chunks.
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
params.streamState.accumulatedBlockText += next;
|
|
879
|
+
}
|
|
880
|
+
async function handleStreamingPartialSnapshot(params) {
|
|
881
|
+
if (!params.snapshot) {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const delta = derivePartialDelta({
|
|
885
|
+
previous: params.streamState.partialSnapshot,
|
|
886
|
+
next: params.snapshot,
|
|
887
|
+
});
|
|
888
|
+
params.streamState.partialSnapshot = params.snapshot;
|
|
889
|
+
if (!delta) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
await sendStreamingDeltaEvent({
|
|
893
|
+
account: params.account,
|
|
894
|
+
sessionId: params.sessionId,
|
|
895
|
+
streamState: params.streamState,
|
|
896
|
+
delta,
|
|
897
|
+
context: "stream.delta.partial",
|
|
898
|
+
});
|
|
899
|
+
params.streamState.partialDeltaCount += 1;
|
|
900
|
+
// Partial callbacks carry the latest visible assistant snapshot.
|
|
901
|
+
params.streamState.accumulatedBlockText = params.snapshot;
|
|
902
|
+
}
|
|
903
|
+
async function deliverReplyPayloadToSider(params) {
|
|
904
|
+
const payloadMediaUrls = resolveOutboundMediaUrls(params.payload);
|
|
905
|
+
if (params.kind === "block") {
|
|
906
|
+
const delta = typeof params.payload.text === "string" ? params.payload.text : "";
|
|
907
|
+
if (delta.length > 0) {
|
|
908
|
+
params.streamState.blockDeltaCount += 1;
|
|
909
|
+
mergeBlockTextIntoStreamState({
|
|
910
|
+
streamState: params.streamState,
|
|
911
|
+
text: delta,
|
|
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
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (payloadMediaUrls.length === 0) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
logDebug("block payload contains media; sending persisted sider message", {
|
|
649
935
|
accountId: params.account.accountId,
|
|
650
|
-
|
|
936
|
+
sessionId: params.sessionId,
|
|
937
|
+
mediaCount: payloadMediaUrls.length,
|
|
938
|
+
hasText: delta.length > 0,
|
|
651
939
|
});
|
|
652
|
-
|
|
940
|
+
}
|
|
941
|
+
if (params.kind === "final") {
|
|
942
|
+
await flushStreamEventQueue(params.streamState);
|
|
943
|
+
if (!params.streamState.active && params.streamState.blockDeltaCount === 0) {
|
|
944
|
+
const finalText = typeof params.payload.text === "string" ? params.payload.text : "";
|
|
945
|
+
if (finalText.length > 0) {
|
|
946
|
+
logDebug("no block delta observed; sending synthetic stream events from final payload", {
|
|
947
|
+
accountId: params.account.accountId,
|
|
948
|
+
sessionId: params.sessionId,
|
|
949
|
+
textLength: finalText.length,
|
|
950
|
+
});
|
|
951
|
+
await sendStreamingDeltaEvent({
|
|
952
|
+
account: params.account,
|
|
953
|
+
sessionId: params.sessionId,
|
|
954
|
+
streamState: params.streamState,
|
|
955
|
+
delta: finalText,
|
|
956
|
+
context: "stream.delta.final-fallback",
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
await closeStreamingSessionIfActive({
|
|
653
961
|
account: params.account,
|
|
654
962
|
sessionId: params.sessionId,
|
|
655
|
-
|
|
963
|
+
streamState: params.streamState,
|
|
964
|
+
reason: "final",
|
|
656
965
|
context: "stream.done",
|
|
657
966
|
});
|
|
658
|
-
params.streamState.active = false;
|
|
659
|
-
params.streamState.streamId = undefined;
|
|
660
|
-
params.streamState.seq = 0;
|
|
661
967
|
}
|
|
662
968
|
const parts = await buildSiderPartsFromReplyPayload({
|
|
663
969
|
account: params.account,
|
|
970
|
+
sessionId: params.sessionId,
|
|
664
971
|
payload: params.payload,
|
|
972
|
+
logger: {
|
|
973
|
+
debug: logDebug,
|
|
974
|
+
warn: logWarn,
|
|
975
|
+
},
|
|
665
976
|
});
|
|
666
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
|
+
});
|
|
667
985
|
return;
|
|
668
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
|
+
});
|
|
669
994
|
await sendMessageToSider({
|
|
670
995
|
account: params.account,
|
|
671
996
|
sessionId: params.sessionId,
|
|
672
997
|
parts,
|
|
673
998
|
});
|
|
999
|
+
if (params.kind === "final" || payloadMediaUrls.length > 0) {
|
|
1000
|
+
params.streamState.persistedFinalText = true;
|
|
1001
|
+
}
|
|
1002
|
+
if (params.kind === "final") {
|
|
1003
|
+
params.streamState.accumulatedBlockText = "";
|
|
1004
|
+
params.streamState.blockDeltaCount = 0;
|
|
1005
|
+
params.streamState.partialDeltaCount = 0;
|
|
1006
|
+
params.streamState.partialSnapshot = "";
|
|
1007
|
+
}
|
|
674
1008
|
}
|
|
675
1009
|
async function handleInboundRealtimeMessage(params) {
|
|
676
1010
|
const { cfg, account, event } = params;
|
|
@@ -698,7 +1032,7 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
698
1032
|
}
|
|
699
1033
|
const rawText = textChunks.join("\n").trim();
|
|
700
1034
|
const mediaUrls = mediaItems.map((item) => item.url);
|
|
701
|
-
const
|
|
1035
|
+
const parsedMediaTypes = mediaItems
|
|
702
1036
|
.map((item) => item.mimeType)
|
|
703
1037
|
.filter((item) => Boolean(item));
|
|
704
1038
|
const hasControlCommand = rawText ? core.channel.text.hasControlCommand(rawText, cfg) : false;
|
|
@@ -737,7 +1071,27 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
737
1071
|
commandAuthorized,
|
|
738
1072
|
});
|
|
739
1073
|
}
|
|
740
|
-
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;
|
|
741
1095
|
const route = core.channel.routing.resolveAgentRoute({
|
|
742
1096
|
cfg,
|
|
743
1097
|
channel: CHANNEL_ID,
|
|
@@ -779,12 +1133,21 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
779
1133
|
WasMentioned: true,
|
|
780
1134
|
OriginatingChannel: CHANNEL_ID,
|
|
781
1135
|
OriginatingTo: to,
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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),
|
|
786
1144
|
CommandAuthorized: commandAuthorized,
|
|
787
1145
|
});
|
|
1146
|
+
rememberSiderSessionBinding({
|
|
1147
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
1148
|
+
account,
|
|
1149
|
+
sessionId,
|
|
1150
|
+
});
|
|
788
1151
|
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
|
|
789
1152
|
agentId: route.agentId,
|
|
790
1153
|
});
|
|
@@ -813,11 +1176,19 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
813
1176
|
commandAuthorized,
|
|
814
1177
|
textLength: rawText.length,
|
|
815
1178
|
mediaCount: mediaUrls.length,
|
|
1179
|
+
mediaDownloadedCount: mediaPayload.MediaPaths?.length ?? 0,
|
|
1180
|
+
mediaDownloadFailedCount: unresolvedMediaUrls.length,
|
|
816
1181
|
});
|
|
817
1182
|
const streamState = {
|
|
818
1183
|
active: false,
|
|
819
1184
|
streamId: undefined,
|
|
820
1185
|
seq: 0,
|
|
1186
|
+
blockDeltaCount: 0,
|
|
1187
|
+
partialDeltaCount: 0,
|
|
1188
|
+
partialSnapshot: "",
|
|
1189
|
+
accumulatedBlockText: "",
|
|
1190
|
+
persistedFinalText: false,
|
|
1191
|
+
streamEventQueue: Promise.resolve(),
|
|
821
1192
|
};
|
|
822
1193
|
const typingCallbacks = createTypingCallbacks({
|
|
823
1194
|
start: async () => {
|
|
@@ -871,6 +1242,7 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
871
1242
|
});
|
|
872
1243
|
},
|
|
873
1244
|
});
|
|
1245
|
+
let dispatchCompleted = false;
|
|
874
1246
|
try {
|
|
875
1247
|
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
876
1248
|
ctx: ctxPayload,
|
|
@@ -879,6 +1251,13 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
879
1251
|
...prefixOptions,
|
|
880
1252
|
typingCallbacks,
|
|
881
1253
|
deliver: async (payload, info) => {
|
|
1254
|
+
logDebug("sider dispatch deliver payload", {
|
|
1255
|
+
accountId: account.accountId,
|
|
1256
|
+
sessionId,
|
|
1257
|
+
kind: info.kind,
|
|
1258
|
+
hasText: typeof payload.text === "string" && payload.text.length > 0,
|
|
1259
|
+
mediaCount: resolveOutboundMediaUrls(payload).length,
|
|
1260
|
+
});
|
|
882
1261
|
await deliverReplyPayloadToSider({
|
|
883
1262
|
account,
|
|
884
1263
|
sessionId,
|
|
@@ -894,9 +1273,28 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
894
1273
|
},
|
|
895
1274
|
},
|
|
896
1275
|
replyOptions: {
|
|
1276
|
+
disableBlockStreaming: false,
|
|
1277
|
+
onPartialReply: async (payload) => {
|
|
1278
|
+
const snapshot = typeof payload.text === "string" ? payload.text : "";
|
|
1279
|
+
if (!snapshot) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
await enqueueStreamEventTask({
|
|
1283
|
+
streamState,
|
|
1284
|
+
task: async () => {
|
|
1285
|
+
await handleStreamingPartialSnapshot({
|
|
1286
|
+
account,
|
|
1287
|
+
sessionId,
|
|
1288
|
+
streamState,
|
|
1289
|
+
snapshot,
|
|
1290
|
+
});
|
|
1291
|
+
},
|
|
1292
|
+
});
|
|
1293
|
+
},
|
|
897
1294
|
onModelSelected,
|
|
898
1295
|
},
|
|
899
1296
|
});
|
|
1297
|
+
dispatchCompleted = true;
|
|
900
1298
|
logDebug("completed sider inbound dispatch", {
|
|
901
1299
|
accountId: account.accountId,
|
|
902
1300
|
sessionId,
|
|
@@ -904,25 +1302,39 @@ async function handleInboundRealtimeMessage(params) {
|
|
|
904
1302
|
});
|
|
905
1303
|
}
|
|
906
1304
|
finally {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1305
|
+
await flushStreamEventQueue(streamState);
|
|
1306
|
+
await closeStreamingSessionIfActive({
|
|
1307
|
+
account,
|
|
1308
|
+
sessionId,
|
|
1309
|
+
streamState,
|
|
1310
|
+
reason: dispatchCompleted ? "final" : "interrupted",
|
|
1311
|
+
context: dispatchCompleted ? "stream.done.dispatch-end" : "stream.done.interrupted",
|
|
1312
|
+
});
|
|
1313
|
+
streamState.blockDeltaCount = 0;
|
|
1314
|
+
}
|
|
1315
|
+
if (!dispatchCompleted) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (!streamState.persistedFinalText && streamState.accumulatedBlockText.trim()) {
|
|
1319
|
+
const text = streamState.accumulatedBlockText;
|
|
1320
|
+
logDebug("no final message payload observed; persisting accumulated block text as final message", {
|
|
1321
|
+
accountId: account.accountId,
|
|
1322
|
+
sessionId,
|
|
1323
|
+
textLength: text.length,
|
|
1324
|
+
});
|
|
1325
|
+
await sendMessageToSider({
|
|
1326
|
+
account,
|
|
1327
|
+
sessionId,
|
|
1328
|
+
parts: [
|
|
1329
|
+
{
|
|
1330
|
+
type: "core.text",
|
|
1331
|
+
spec_version: 1,
|
|
1332
|
+
payload: { text },
|
|
1333
|
+
},
|
|
1334
|
+
],
|
|
1335
|
+
});
|
|
1336
|
+
streamState.persistedFinalText = true;
|
|
1337
|
+
streamState.accumulatedBlockText = "";
|
|
926
1338
|
}
|
|
927
1339
|
}
|
|
928
1340
|
async function handleInboundRealtimeEvent(params) {
|
|
@@ -1153,7 +1565,7 @@ export const siderPlugin = {
|
|
|
1153
1565
|
conversationId: result.conversationId,
|
|
1154
1566
|
};
|
|
1155
1567
|
},
|
|
1156
|
-
sendMedia: async ({ cfg, accountId, to, text, mediaUrl }) => {
|
|
1568
|
+
sendMedia: async ({ cfg, accountId, to, text, mediaUrl, mediaLocalRoots }) => {
|
|
1157
1569
|
const account = resolveSiderAccount(cfg, accountId);
|
|
1158
1570
|
if (!account.configured) {
|
|
1159
1571
|
throw new Error(`sider account "${account.accountId}" is not configured: missing gatewayUrl/sessionId(or sessionKey)/relayId`);
|
|
@@ -1161,10 +1573,16 @@ export const siderPlugin = {
|
|
|
1161
1573
|
const sessionId = resolveOutboundSessionId({ account, to });
|
|
1162
1574
|
const parts = await buildSiderPartsFromReplyPayload({
|
|
1163
1575
|
account,
|
|
1576
|
+
sessionId,
|
|
1164
1577
|
payload: {
|
|
1165
1578
|
text,
|
|
1166
1579
|
mediaUrl,
|
|
1167
1580
|
},
|
|
1581
|
+
mediaLocalRoots,
|
|
1582
|
+
logger: {
|
|
1583
|
+
debug: logDebug,
|
|
1584
|
+
warn: logWarn,
|
|
1585
|
+
},
|
|
1168
1586
|
});
|
|
1169
1587
|
if (parts.length === 0) {
|
|
1170
1588
|
throw new Error("sider sendMedia requires text and/or mediaUrl");
|
|
@@ -1197,5 +1615,12 @@ export const siderPlugin = {
|
|
|
1197
1615
|
ctx.log?.info(`[${account.accountId}] sider relay monitor stopped`);
|
|
1198
1616
|
},
|
|
1199
1617
|
},
|
|
1618
|
+
messaging: {
|
|
1619
|
+
normalizeTarget: normalizeSiderMessagingTarget,
|
|
1620
|
+
targetResolver: {
|
|
1621
|
+
looksLikeId: looksLikeSiderTargetId,
|
|
1622
|
+
hint: "session:<sessionId> (or raw <sessionId>)",
|
|
1623
|
+
},
|
|
1624
|
+
},
|
|
1200
1625
|
};
|
|
1201
1626
|
//# sourceMappingURL=channel.js.map
|