@dcrays/dcgchat 0.1.3 → 0.1.4
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/package.json +3 -3
- package/src/bot.ts +156 -18
- package/src/channel.ts +36 -6
- package/src/connection.ts +11 -0
- package/src/log.ts +46 -0
- package/src/monitor.ts +10 -9
- package/src/types.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcrays/dcgchat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw channel plugin for DCG Chat (WebSocket)",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^22.0.0",
|
|
18
18
|
"@types/ws": "^8.5.0",
|
|
19
|
-
"openclaw": "2026.
|
|
19
|
+
"openclaw": "2026.2.13",
|
|
20
20
|
"typescript": "^5.7.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"openclaw": ">=2026.
|
|
23
|
+
"openclaw": ">=2026.2.13"
|
|
24
24
|
},
|
|
25
25
|
"openclaw": {
|
|
26
26
|
"extensions": [
|
package/src/bot.ts
CHANGED
|
@@ -1,9 +1,97 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
3
|
import { createReplyPrefixContext } from "openclaw/plugin-sdk";
|
|
3
4
|
import type { InboundMessage, OutboundReply } from "./types.js";
|
|
4
5
|
import { getDcgchatRuntime } from "./runtime.js";
|
|
5
6
|
import { resolveAccount } from "./channel.js";
|
|
6
7
|
|
|
8
|
+
type MediaInfo = {
|
|
9
|
+
path: string;
|
|
10
|
+
contentType: string;
|
|
11
|
+
placeholder: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
async function resolveMediaFromUrls(
|
|
15
|
+
fileUrls: string[],
|
|
16
|
+
maxBytes: number,
|
|
17
|
+
log?: (msg: string) => void,
|
|
18
|
+
): Promise<MediaInfo[]> {
|
|
19
|
+
const core = getDcgchatRuntime();
|
|
20
|
+
const out: MediaInfo[] = [];
|
|
21
|
+
|
|
22
|
+
log?.(`dcgchat media: starting resolve for ${fileUrls.length} file(s): ${JSON.stringify(fileUrls)}`);
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < fileUrls.length; i++) {
|
|
25
|
+
const url = fileUrls[i];
|
|
26
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetching ${url}`);
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(url);
|
|
29
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetch response status=${response.status}, content-type=${response.headers.get("content-type")}, content-length=${response.headers.get("content-length")}`);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetch failed with HTTP ${response.status}, skipping`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
35
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] downloaded buffer size=${buffer.length} bytes`);
|
|
36
|
+
|
|
37
|
+
let contentType = response.headers.get("content-type") || "";
|
|
38
|
+
if (!contentType) {
|
|
39
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] no content-type header, detecting mime...`);
|
|
40
|
+
contentType = await core.media.detectMime({ buffer });
|
|
41
|
+
}
|
|
42
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] resolved contentType=${contentType}`);
|
|
43
|
+
|
|
44
|
+
const fileName = path.basename(new URL(url).pathname) || "file";
|
|
45
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fileName=${fileName}, saving to disk (maxBytes=${maxBytes})...`);
|
|
46
|
+
|
|
47
|
+
const saved = await core.channel.media.saveMediaBuffer(
|
|
48
|
+
buffer,
|
|
49
|
+
contentType,
|
|
50
|
+
"inbound",
|
|
51
|
+
maxBytes,
|
|
52
|
+
fileName,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const isImage = contentType.startsWith("image/");
|
|
56
|
+
out.push({
|
|
57
|
+
path: saved.path,
|
|
58
|
+
contentType: saved.contentType,
|
|
59
|
+
placeholder: isImage ? "<media:image>" : "<media:file>",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] saved to ${saved.path} (contentType=${saved.contentType}, isImage=${isImage})`);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] FAILED to process ${url}: ${String(err)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
log?.(`dcgchat media: resolve complete, ${out.length}/${fileUrls.length} file(s) succeeded`);
|
|
69
|
+
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildMediaPayload(mediaList: MediaInfo[]): {
|
|
74
|
+
MediaPath?: string;
|
|
75
|
+
MediaType?: string;
|
|
76
|
+
MediaUrl?: string;
|
|
77
|
+
MediaPaths?: string[];
|
|
78
|
+
MediaUrls?: string[];
|
|
79
|
+
MediaTypes?: string[];
|
|
80
|
+
} {
|
|
81
|
+
if (mediaList.length === 0) return {};
|
|
82
|
+
const first = mediaList[0];
|
|
83
|
+
const mediaPaths = mediaList.map((m) => m.path);
|
|
84
|
+
const mediaTypes = mediaList.map((m) => m.contentType).filter(Boolean);
|
|
85
|
+
return {
|
|
86
|
+
MediaPath: first?.path,
|
|
87
|
+
MediaType: first?.contentType,
|
|
88
|
+
MediaUrl: first?.path,
|
|
89
|
+
MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
90
|
+
MediaUrls: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
91
|
+
MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
7
95
|
/**
|
|
8
96
|
* 处理一条用户消息,调用 Agent 并返回回复
|
|
9
97
|
*/
|
|
@@ -12,7 +100,8 @@ export async function handleDcgchatMessage(params: {
|
|
|
12
100
|
msg: InboundMessage;
|
|
13
101
|
accountId: string;
|
|
14
102
|
runtime?: RuntimeEnv;
|
|
15
|
-
|
|
103
|
+
onChunk: (reply: OutboundReply) => void;
|
|
104
|
+
}): Promise<void> {
|
|
16
105
|
const { cfg, msg, accountId, runtime } = params;
|
|
17
106
|
const log = runtime?.log ?? console.log;
|
|
18
107
|
const error = runtime?.error ?? console.error;
|
|
@@ -23,7 +112,18 @@ export async function handleDcgchatMessage(params: {
|
|
|
23
112
|
const text = msg.content.text?.trim();
|
|
24
113
|
|
|
25
114
|
if (!userId || !text) {
|
|
26
|
-
|
|
115
|
+
params.onChunk({
|
|
116
|
+
messageType: "openclaw_bot_chat",
|
|
117
|
+
_userId: msg._userId,
|
|
118
|
+
source: "client",
|
|
119
|
+
content: {
|
|
120
|
+
bot_token: msg.content.bot_token,
|
|
121
|
+
session_id: msg.content.session_id,
|
|
122
|
+
message_id: msg.content.message_id,
|
|
123
|
+
response: "[错误] 消息格式不正确",
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
27
127
|
}
|
|
28
128
|
|
|
29
129
|
try {
|
|
@@ -36,6 +136,18 @@ export async function handleDcgchatMessage(params: {
|
|
|
36
136
|
peer: { kind: "direct", id: userId },
|
|
37
137
|
});
|
|
38
138
|
|
|
139
|
+
// 处理用户上传的文件
|
|
140
|
+
const fileUrls = msg.content.file_urls ?? [];
|
|
141
|
+
log(`dcgchat[${accountId}]: incoming message from user=${userId}, text="${text?.slice(0, 80)}", file_urls count=${fileUrls.length}`);
|
|
142
|
+
let mediaPayload: Record<string, unknown> = {};
|
|
143
|
+
if (fileUrls.length > 0) {
|
|
144
|
+
log(`dcgchat[${accountId}]: processing ${fileUrls.length} file(s): ${JSON.stringify(fileUrls)}`);
|
|
145
|
+
const mediaMaxBytes = 30 * 1024 * 1024;
|
|
146
|
+
const mediaList = await resolveMediaFromUrls(fileUrls, mediaMaxBytes, log);
|
|
147
|
+
mediaPayload = buildMediaPayload(mediaList);
|
|
148
|
+
log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${fileUrls.length} file(s), payload=${JSON.stringify(mediaPayload)}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
39
151
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
40
152
|
// const messageBody = `${userId}: ${text}`;
|
|
41
153
|
const messageBody = text;
|
|
@@ -52,24 +164,26 @@ export async function handleDcgchatMessage(params: {
|
|
|
52
164
|
RawBody: text,
|
|
53
165
|
CommandBody: text,
|
|
54
166
|
From: userId,
|
|
55
|
-
To:
|
|
167
|
+
To: userId,
|
|
56
168
|
SessionKey: route.sessionKey,
|
|
57
|
-
AccountId:
|
|
169
|
+
AccountId: msg.content.session_id,
|
|
58
170
|
ChatType: "direct",
|
|
59
171
|
SenderName: userId,
|
|
60
172
|
SenderId: userId,
|
|
61
173
|
Provider: "dcgchat" as const,
|
|
62
174
|
Surface: "dcgchat" as const,
|
|
63
|
-
MessageSid:
|
|
175
|
+
MessageSid: msg.content.message_id,
|
|
64
176
|
Timestamp: Date.now(),
|
|
65
177
|
WasMentioned: true,
|
|
66
178
|
CommandAuthorized: true,
|
|
67
179
|
OriginatingChannel: "dcgchat" as const,
|
|
68
180
|
OriginatingTo: `user:${userId}`,
|
|
181
|
+
...mediaPayload,
|
|
69
182
|
});
|
|
70
183
|
|
|
184
|
+
log(`dcgchat[${accountId}]: ctxPayload=${JSON.stringify(ctxPayload)}`);
|
|
185
|
+
|
|
71
186
|
const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
|
|
72
|
-
const replyChunks: string[] = [];
|
|
73
187
|
|
|
74
188
|
const { dispatcher, replyOptions, markDispatchIdle } =
|
|
75
189
|
core.channel.reply.createReplyDispatcherWithTyping({
|
|
@@ -78,8 +192,22 @@ export async function handleDcgchatMessage(params: {
|
|
|
78
192
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
|
|
79
193
|
onReplyStart: async () => {},
|
|
80
194
|
deliver: async (payload) => {
|
|
195
|
+
log(`dcgchat[chunk]: ${payload.text}`);
|
|
81
196
|
const t = payload.text?.trim();
|
|
82
|
-
if (t)
|
|
197
|
+
if (t) {
|
|
198
|
+
params.onChunk({
|
|
199
|
+
messageType: "openclaw_bot_chat",
|
|
200
|
+
_userId: msg._userId,
|
|
201
|
+
source: "client",
|
|
202
|
+
content: {
|
|
203
|
+
bot_token: msg.content.bot_token,
|
|
204
|
+
session_id: msg.content.session_id,
|
|
205
|
+
message_id: msg.content.message_id,
|
|
206
|
+
response: t,
|
|
207
|
+
state: 'chunk',
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
83
211
|
},
|
|
84
212
|
onError: (err, info) => {
|
|
85
213
|
error(`dcgchat[${accountId}] ${info.kind} reply failed: ${String(err)}`);
|
|
@@ -99,13 +227,8 @@ export async function handleDcgchatMessage(params: {
|
|
|
99
227
|
},
|
|
100
228
|
});
|
|
101
229
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const reply = replyChunks.join("\n\n").trim();
|
|
105
|
-
log(`dcgchat[${accountId}]: dispatch complete, reply length=${reply.length}`);
|
|
106
|
-
|
|
107
|
-
// return { type: "reply", userId, text: reply || "[无回复]" };
|
|
108
|
-
return {
|
|
230
|
+
log(`dcgchat[chunk]: all_finished`);
|
|
231
|
+
params.onChunk({
|
|
109
232
|
messageType: "openclaw_bot_chat",
|
|
110
233
|
_userId: msg._userId,
|
|
111
234
|
source: "client",
|
|
@@ -113,12 +236,27 @@ export async function handleDcgchatMessage(params: {
|
|
|
113
236
|
bot_token: msg.content.bot_token,
|
|
114
237
|
session_id: msg.content.session_id,
|
|
115
238
|
message_id: msg.content.message_id,
|
|
116
|
-
response:
|
|
117
|
-
|
|
118
|
-
|
|
239
|
+
response: '',
|
|
240
|
+
state: 'final',
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
markDispatchIdle();
|
|
245
|
+
log(`dcgchat[${accountId}]: dispatch complete`);
|
|
119
246
|
|
|
120
247
|
} catch (err) {
|
|
121
248
|
error(`dcgchat[${accountId}]: handle message failed: ${String(err)}`);
|
|
122
|
-
|
|
249
|
+
params.onChunk({
|
|
250
|
+
messageType: "openclaw_bot_chat",
|
|
251
|
+
_userId: msg._userId,
|
|
252
|
+
source: "client",
|
|
253
|
+
content: {
|
|
254
|
+
bot_token: msg.content.bot_token,
|
|
255
|
+
session_id: msg.content.session_id,
|
|
256
|
+
message_id: msg.content.message_id,
|
|
257
|
+
response: `[错误] ${err instanceof Error ? err.message : String(err)}`,
|
|
258
|
+
state: 'final',
|
|
259
|
+
},
|
|
260
|
+
});
|
|
123
261
|
}
|
|
124
262
|
}
|
package/src/channel.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
3
3
|
import type { ResolvedDcgchatAccount, DcgchatConfig } from "./types.js";
|
|
4
|
+
import { logDcgchat } from "./log.js";
|
|
5
|
+
import { getWsConnection } from "./connection.js";
|
|
4
6
|
|
|
5
7
|
export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDcgchatAccount {
|
|
6
8
|
const id = accountId ?? DEFAULT_ACCOUNT_ID;
|
|
@@ -29,11 +31,13 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
29
31
|
capabilities: {
|
|
30
32
|
chatTypes: ["direct"],
|
|
31
33
|
polls: false,
|
|
32
|
-
threads:
|
|
33
|
-
media:
|
|
34
|
+
threads: true,
|
|
35
|
+
media: true,
|
|
36
|
+
nativeCommands: true,
|
|
34
37
|
reactions: false,
|
|
35
38
|
edit: false,
|
|
36
39
|
reply: true,
|
|
40
|
+
blockStreaming: true,
|
|
37
41
|
},
|
|
38
42
|
reload: { configPrefixes: ["channels.dcgchat"] },
|
|
39
43
|
configSchema: {
|
|
@@ -81,16 +85,42 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
81
85
|
},
|
|
82
86
|
outbound: {
|
|
83
87
|
deliveryMode: "direct",
|
|
84
|
-
textChunkLimit:
|
|
85
|
-
sendText: async (
|
|
86
|
-
const target = to || "(implicit)";
|
|
87
|
-
|
|
88
|
+
textChunkLimit: 25,
|
|
89
|
+
sendText: async (ctx) => {
|
|
90
|
+
const target = ctx.to || "(implicit)";
|
|
91
|
+
logDcgchat.info(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> : ${ctx.text}`);
|
|
92
|
+
const ws = getWsConnection()
|
|
93
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
94
|
+
const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
|
|
95
|
+
const content = {
|
|
96
|
+
messageType: "openclaw_bot_chat",
|
|
97
|
+
_userId: target,
|
|
98
|
+
source: "client",
|
|
99
|
+
content: {
|
|
100
|
+
bot_token: botToken,
|
|
101
|
+
response: ctx.text,
|
|
102
|
+
session_id:ctx.accountId || Date.now().toString(),
|
|
103
|
+
message_id: Date.now().toString(),
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
ws.send(JSON.stringify(content));
|
|
107
|
+
logDcgchat.info(`dcgchat[${ctx.accountId}]: sendText to ${target}, ${JSON.stringify(content)}`);
|
|
108
|
+
}
|
|
88
109
|
return {
|
|
89
110
|
channel: "dcgchat",
|
|
90
111
|
messageId: `dcg-${Date.now()}`,
|
|
91
112
|
chatId: target,
|
|
92
113
|
};
|
|
93
114
|
},
|
|
115
|
+
sendMedia: async (ctx) => {
|
|
116
|
+
const target = ctx.to || "(implicit)";
|
|
117
|
+
logDcgchat.info(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${target}: ${ctx.text}`);
|
|
118
|
+
return {
|
|
119
|
+
channel: "dcgchat",
|
|
120
|
+
messageId: `dcg-${Date.now()}`,
|
|
121
|
+
chatId: target,
|
|
122
|
+
};
|
|
123
|
+
},
|
|
94
124
|
},
|
|
95
125
|
gateway: {
|
|
96
126
|
startAccount: async (ctx) => {
|
package/src/log.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const logsDir = path.resolve(__dirname, "../logs");
|
|
7
|
+
|
|
8
|
+
function getLogFilePath(): string {
|
|
9
|
+
const date = new Date();
|
|
10
|
+
const yyyy = date.getFullYear();
|
|
11
|
+
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
12
|
+
const dd = String(date.getDate()).padStart(2, "0");
|
|
13
|
+
return path.join(logsDir, `${yyyy}-${mm}-${dd}.log`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function formatLine(level: string, message: string, extra?: unknown): string {
|
|
17
|
+
const now = new Date().toISOString();
|
|
18
|
+
const suffix = extra !== undefined ? " " + JSON.stringify(extra) : "";
|
|
19
|
+
return `[${now}] [${level}] ${message}${suffix}\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeLog(level: string, message: string, extra?: unknown): void {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(logsDir)) {
|
|
25
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
fs.appendFileSync(getLogFilePath(), formatLine(level, message, extra), "utf-8");
|
|
28
|
+
} catch {
|
|
29
|
+
// 写日志失败时静默处理,避免影响主流程
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const logDcgchat = {
|
|
34
|
+
info(message: string, extra?: unknown): void {
|
|
35
|
+
writeLog("INFO", message, extra);
|
|
36
|
+
},
|
|
37
|
+
warn(message: string, extra?: unknown): void {
|
|
38
|
+
writeLog("WARN", message, extra);
|
|
39
|
+
},
|
|
40
|
+
error(message: string, extra?: unknown): void {
|
|
41
|
+
writeLog("ERROR", message, extra);
|
|
42
|
+
},
|
|
43
|
+
debug(message: string, extra?: unknown): void {
|
|
44
|
+
writeLog("DEBUG", message, extra);
|
|
45
|
+
},
|
|
46
|
+
};
|
package/src/monitor.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
|
3
3
|
import { resolveAccount } from "./channel.js";
|
|
4
4
|
import { handleDcgchatMessage } from "./bot.js";
|
|
5
5
|
import type { InboundMessage, OutboundReply } from "./types.js";
|
|
6
|
+
import { setWsConnection } from "./connection.js";
|
|
6
7
|
|
|
7
8
|
export type MonitorDcgchatOpts = {
|
|
8
9
|
config?: ClawdbotConfig;
|
|
@@ -79,6 +80,7 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
|
|
|
79
80
|
|
|
80
81
|
ws.on("open", () => {
|
|
81
82
|
log(`dcgchat[${account.accountId}]: connected`);
|
|
83
|
+
setWsConnection(ws);
|
|
82
84
|
startHeartbeat();
|
|
83
85
|
});
|
|
84
86
|
|
|
@@ -105,24 +107,23 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
|
|
|
105
107
|
parsed.content = JSON.parse(parsed.content)
|
|
106
108
|
|
|
107
109
|
const msg = parsed as unknown as InboundMessage;
|
|
108
|
-
|
|
110
|
+
await handleDcgchatMessage({
|
|
109
111
|
cfg,
|
|
110
112
|
msg,
|
|
111
113
|
accountId: account.accountId,
|
|
112
114
|
runtime,
|
|
115
|
+
onChunk: (reply) => {
|
|
116
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
117
|
+
const res = { ...reply, content: JSON.stringify(reply.content) };
|
|
118
|
+
ws.send(JSON.stringify(res));
|
|
119
|
+
}
|
|
120
|
+
},
|
|
113
121
|
});
|
|
114
|
-
|
|
115
|
-
if (ws?.readyState === WebSocket.OPEN) {
|
|
116
|
-
const res = {
|
|
117
|
-
...reply,
|
|
118
|
-
content: JSON.stringify(reply.content)
|
|
119
|
-
}
|
|
120
|
-
ws.send(JSON.stringify(res));
|
|
121
|
-
}
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
ws.on("close", () => {
|
|
125
125
|
stopHeartbeat();
|
|
126
|
+
setWsConnection(null);
|
|
126
127
|
log(`dcgchat[${account.accountId}]: disconnected`);
|
|
127
128
|
if (shouldReconnect) {
|
|
128
129
|
log(`dcgchat[${account.accountId}]: reconnecting in ${RECONNECT_DELAY_MS}ms...`);
|
package/src/types.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type InboundMessage = {
|
|
|
38
38
|
session_id: string;
|
|
39
39
|
message_id: string;
|
|
40
40
|
text: string;
|
|
41
|
+
file_urls?: string[];
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -62,5 +63,6 @@ export type OutboundReply = {
|
|
|
62
63
|
session_id: string; // ""
|
|
63
64
|
message_id: string; // ""
|
|
64
65
|
response: string; // ""
|
|
66
|
+
state: string; // final, chunk
|
|
65
67
|
}
|
|
66
68
|
}
|