palz-connector 1.0.2 → 1.0.3
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/plugin.ts +52 -48
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/plugin.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import WebSocket from 'ws';
|
|
9
9
|
import { readFileSync } from 'fs';
|
|
10
|
-
import { resolve, dirname
|
|
10
|
+
import { resolve, dirname } from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
|
|
14
14
|
// ============ 类型定义 ============
|
|
15
15
|
|
|
@@ -49,23 +49,12 @@ function getPluginDir(): string {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function readGatewayToken(log?: any): string {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const token = openclawConfig?.gateway?.auth?.token;
|
|
56
|
-
if (token) {
|
|
57
|
-
log?.info?.('[Palz] gatewayToken 从 ~/.openclaw/openclaw.json 读取');
|
|
58
|
-
return token;
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
// 文件不存在或解析失败,继续尝试环境变量
|
|
52
|
+
if (process.env.OPENCLAW_GATEWAY_TOKEN) {
|
|
53
|
+
log?.info?.('[Palz] gatewayToken 从环境变量 OPENCLAW_GATEWAY_TOKEN 读取');
|
|
54
|
+
return process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
62
55
|
}
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
return process.env.GATEWAY_TOKEN;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
log?.warn?.('[Palz] gatewayToken 未配置(~/.openclaw/openclaw.json 和环境变量 GATEWAY_TOKEN 均未设置)');
|
|
57
|
+
log?.warn?.('[Palz] gatewayToken 未配置(环境变量 OPENCLAW_GATEWAY_TOKEN 未设置)');
|
|
69
58
|
return '';
|
|
70
59
|
}
|
|
71
60
|
|
|
@@ -153,36 +142,28 @@ function isNewSessionCommand(text: string): boolean {
|
|
|
153
142
|
|
|
154
143
|
function getSessionKey(
|
|
155
144
|
senderId: string,
|
|
145
|
+
conversationId: string,
|
|
156
146
|
forceNew: boolean,
|
|
157
|
-
sessionTimeout: number,
|
|
158
147
|
log?: any,
|
|
159
148
|
): { sessionKey: string; isNew: boolean } {
|
|
149
|
+
const sessionId = `palz:${senderId}:${conversationId}`;
|
|
160
150
|
const now = Date.now();
|
|
161
151
|
|
|
162
152
|
if (forceNew) {
|
|
163
|
-
const sessionId = `palz:${senderId}:${now}`;
|
|
164
153
|
userSessions.set(senderId, { lastActivity: now, sessionId });
|
|
165
|
-
log?.info?.(`[Palz][Session] 用户主动开启新会话: ${senderId}`);
|
|
154
|
+
log?.info?.(`[Palz][Session] 用户主动开启新会话: ${senderId}, conversation=${conversationId}`);
|
|
166
155
|
return { sessionKey: sessionId, isNew: true };
|
|
167
156
|
}
|
|
168
157
|
|
|
169
158
|
const existing = userSessions.get(senderId);
|
|
170
|
-
if (existing) {
|
|
171
|
-
const elapsed = now - existing.lastActivity;
|
|
172
|
-
if (elapsed > sessionTimeout) {
|
|
173
|
-
const sessionId = `palz:${senderId}:${now}`;
|
|
174
|
-
userSessions.set(senderId, { lastActivity: now, sessionId });
|
|
175
|
-
log?.info?.(`[Palz][Session] 会话超时(${Math.round(elapsed / 60000)}分钟),自动开启新会话: ${senderId}`);
|
|
176
|
-
return { sessionKey: sessionId, isNew: true };
|
|
177
|
-
}
|
|
159
|
+
if (existing && existing.sessionId === sessionId) {
|
|
178
160
|
existing.lastActivity = now;
|
|
179
|
-
return { sessionKey:
|
|
161
|
+
return { sessionKey: sessionId, isNew: false };
|
|
180
162
|
}
|
|
181
163
|
|
|
182
|
-
const sessionId = `palz:${senderId}:${now}`;
|
|
183
164
|
userSessions.set(senderId, { lastActivity: now, sessionId });
|
|
184
|
-
log?.info?.(`[Palz][Session]
|
|
185
|
-
return { sessionKey: sessionId, isNew:
|
|
165
|
+
log?.info?.(`[Palz][Session] 新会话: ${senderId}, conversation=${conversationId}`);
|
|
166
|
+
return { sessionKey: sessionId, isNew: !existing };
|
|
186
167
|
}
|
|
187
168
|
|
|
188
169
|
// ============ Gateway SSE 客户端 ============
|
|
@@ -198,7 +179,18 @@ async function* streamFromGateway(
|
|
|
198
179
|
const rt = getRuntime();
|
|
199
180
|
const gatewayUrl = `http://127.0.0.1:${rt.gateway?.port || 18789}/v1/chat/completions`;
|
|
200
181
|
|
|
201
|
-
|
|
182
|
+
const requestBody = {
|
|
183
|
+
model: 'main',
|
|
184
|
+
messages: [{ role: 'user', content: userContent }],
|
|
185
|
+
stream: true,
|
|
186
|
+
user: sessionKey,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const contentPreview = typeof userContent === 'string'
|
|
190
|
+
? userContent.slice(0, 200)
|
|
191
|
+
: JSON.stringify(userContent).slice(0, 200);
|
|
192
|
+
log?.info?.(`[Palz][Gateway] POST ${gatewayUrl}, session=${sessionKey}, content=${contentPreview}`);
|
|
193
|
+
log?.info?.(`[Palz][Gateway] Request body: ${JSON.stringify(requestBody, null, 2)}`);
|
|
202
194
|
|
|
203
195
|
const controller = new AbortController();
|
|
204
196
|
const timer = setTimeout(() => controller.abort(), GATEWAY_TIMEOUT);
|
|
@@ -211,27 +203,28 @@ async function* streamFromGateway(
|
|
|
211
203
|
'Content-Type': 'application/json',
|
|
212
204
|
...(gatewayAuth ? { Authorization: `Bearer ${gatewayAuth}` } : {}),
|
|
213
205
|
},
|
|
214
|
-
body: JSON.stringify(
|
|
215
|
-
model: 'main',
|
|
216
|
-
messages: [{ role: 'user', content: userContent }],
|
|
217
|
-
stream: true,
|
|
218
|
-
user: sessionKey,
|
|
219
|
-
}),
|
|
206
|
+
body: JSON.stringify(requestBody),
|
|
220
207
|
signal: controller.signal,
|
|
221
208
|
});
|
|
222
209
|
} catch (err: any) {
|
|
223
210
|
clearTimeout(timer);
|
|
211
|
+
log?.error?.(`[Palz][Gateway] 请求异常: ${err.message}`);
|
|
224
212
|
throw err;
|
|
225
213
|
}
|
|
226
214
|
|
|
215
|
+
log?.info?.(`[Palz][Gateway] Response status=${response.status}, headers=${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
216
|
+
|
|
227
217
|
if (!response.ok || !response.body) {
|
|
228
218
|
clearTimeout(timer);
|
|
229
|
-
|
|
219
|
+
const errorBody = await response.text().catch(() => '');
|
|
220
|
+
log?.error?.(`[Palz][Gateway] 请求失败: status=${response.status}, body=${errorBody}`);
|
|
221
|
+
throw new Error(`Gateway error: ${response.status} ${errorBody}`);
|
|
230
222
|
}
|
|
231
223
|
|
|
232
224
|
const reader = (response.body as any).getReader();
|
|
233
225
|
const decoder = new TextDecoder();
|
|
234
226
|
let buffer = '';
|
|
227
|
+
let fullResponse = '';
|
|
235
228
|
|
|
236
229
|
try {
|
|
237
230
|
while (true) {
|
|
@@ -243,16 +236,23 @@ async function* streamFromGateway(
|
|
|
243
236
|
for (const line of lines) {
|
|
244
237
|
if (!line.startsWith('data: ')) continue;
|
|
245
238
|
const data = line.slice(6).trim();
|
|
246
|
-
if (data === '[DONE]')
|
|
239
|
+
if (data === '[DONE]') {
|
|
240
|
+
log?.info?.(`[Palz][Gateway] 流式响应完成, 总长度=${fullResponse.length}, 内容=${fullResponse.slice(0, 500)}`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
247
243
|
try {
|
|
248
244
|
const chunk = JSON.parse(data);
|
|
249
245
|
const content = chunk.choices?.[0]?.delta?.content;
|
|
250
|
-
if (content)
|
|
246
|
+
if (content) {
|
|
247
|
+
fullResponse += content;
|
|
248
|
+
yield content;
|
|
249
|
+
}
|
|
251
250
|
} catch {
|
|
252
|
-
|
|
251
|
+
log?.warn?.(`[Palz][Gateway] 解析SSE数据失败: ${data.slice(0, 200)}`);
|
|
253
252
|
}
|
|
254
253
|
}
|
|
255
254
|
}
|
|
255
|
+
log?.info?.(`[Palz][Gateway] 流结束, 总长度=${fullResponse.length}, 内容=${fullResponse.slice(0, 500)}`);
|
|
256
256
|
} finally {
|
|
257
257
|
clearTimeout(timer);
|
|
258
258
|
}
|
|
@@ -306,18 +306,22 @@ function handleIMMessage(params: {
|
|
|
306
306
|
|
|
307
307
|
const senderId = msg.sender_id;
|
|
308
308
|
const conversationId = msg.conversation_id;
|
|
309
|
-
|
|
309
|
+
const msgSnapshot = Object.fromEntries(
|
|
310
|
+
Object.entries(msg).map(([k, v]) => {
|
|
311
|
+
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
312
|
+
return [k, s.length > 300 ? s.slice(0, 300) + '...' : v];
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
log?.info?.(`[Palz] 收到消息: ${JSON.stringify(msgSnapshot)}`);
|
|
310
316
|
|
|
311
317
|
withUserLock(senderId, async () => {
|
|
312
|
-
const sessionTimeout = config.sessionTimeout ?? 1800000;
|
|
313
|
-
|
|
314
318
|
if (isNewSessionCommand(plainText)) {
|
|
315
|
-
getSessionKey(senderId,
|
|
319
|
+
getSessionKey(senderId, conversationId, true, log);
|
|
316
320
|
await sendToIM(config, conversationId, '✨ 已开启新会话', log);
|
|
317
321
|
return;
|
|
318
322
|
}
|
|
319
323
|
|
|
320
|
-
const { sessionKey } = getSessionKey(senderId,
|
|
324
|
+
const { sessionKey } = getSessionKey(senderId, conversationId, false, log);
|
|
321
325
|
const gatewayAuth = config.gatewayToken || '';
|
|
322
326
|
|
|
323
327
|
let fullResponse = '';
|