@ynhcj/xiaoyi 2.5.2 → 2.5.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 +207 -207
- package/dist/channel.d.ts +2 -1
- package/dist/channel.js +510 -353
- package/dist/config-schema.d.ts +18 -18
- package/dist/onboarding.d.ts +6 -0
- package/dist/onboarding.js +167 -0
- package/dist/runtime.d.ts +17 -5
- package/dist/runtime.js +45 -22
- package/dist/websocket.d.ts +16 -0
- package/dist/websocket.js +21 -0
- package/dist/xiaoyi-media.d.ts +81 -0
- package/dist/xiaoyi-media.js +216 -0
- package/openclaw.plugin.json +9 -9
- package/package.json +62 -62
- package/xiaoyi.js +1 -1
package/dist/config-schema.d.ts
CHANGED
|
@@ -23,24 +23,24 @@ export declare const XiaoYiConfigSchema: z.ZodObject<{
|
|
|
23
23
|
/** Multi-account configuration */
|
|
24
24
|
accounts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
25
25
|
}, "strip", z.ZodTypeAny, {
|
|
26
|
-
enabled
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
accounts?: Record<string, unknown
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
wsUrl?: string;
|
|
28
|
+
ak?: string;
|
|
29
|
+
sk?: string;
|
|
30
|
+
agentId?: string;
|
|
31
|
+
enableStreaming?: boolean;
|
|
32
|
+
name?: string;
|
|
33
|
+
debug?: boolean;
|
|
34
|
+
accounts?: Record<string, unknown>;
|
|
35
35
|
}, {
|
|
36
|
-
enabled?: boolean
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
accounts?: Record<string, unknown
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
wsUrl?: string;
|
|
38
|
+
ak?: string;
|
|
39
|
+
sk?: string;
|
|
40
|
+
agentId?: string;
|
|
41
|
+
enableStreaming?: boolean;
|
|
42
|
+
name?: string;
|
|
43
|
+
debug?: boolean;
|
|
44
|
+
accounts?: Record<string, unknown>;
|
|
45
45
|
}>;
|
|
46
46
|
export type XiaoYiConfig = z.infer<typeof XiaoYiConfigSchema>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* XiaoYi onboarding adapter for CLI setup wizard.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.xiaoyiOnboardingAdapter = void 0;
|
|
7
|
+
const channel = "xiaoyi";
|
|
8
|
+
/**
|
|
9
|
+
* Get XiaoYi channel config from OpenClaw config
|
|
10
|
+
*/
|
|
11
|
+
function getXiaoYiConfig(cfg) {
|
|
12
|
+
return cfg?.channels?.xiaoyi;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if XiaoYi is properly configured
|
|
16
|
+
*/
|
|
17
|
+
function isXiaoYiConfigured(config) {
|
|
18
|
+
if (!config) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
// Check required fields: ak, sk, agentId
|
|
22
|
+
// wsUrl1/wsUrl2 are optional (defaults will be used if not provided)
|
|
23
|
+
const hasAk = typeof config.ak === "string" && config.ak.trim().length > 0;
|
|
24
|
+
const hasSk = typeof config.sk === "string" && config.sk.trim().length > 0;
|
|
25
|
+
const hasAgentId = typeof config.agentId === "string" && config.agentId.trim().length > 0;
|
|
26
|
+
return hasAk && hasSk && hasAgentId;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Set XiaoYi channel configuration
|
|
30
|
+
*/
|
|
31
|
+
function setXiaoYiConfig(cfg, config) {
|
|
32
|
+
const existing = getXiaoYiConfig(cfg);
|
|
33
|
+
const merged = {
|
|
34
|
+
enabled: config.enabled ?? existing?.enabled ?? true,
|
|
35
|
+
wsUrl: config.wsUrl ?? existing?.wsUrl ?? "",
|
|
36
|
+
wsUrl1: config.wsUrl1 ?? existing?.wsUrl1 ?? "",
|
|
37
|
+
wsUrl2: config.wsUrl2 ?? existing?.wsUrl2 ?? "",
|
|
38
|
+
ak: config.ak ?? existing?.ak ?? "",
|
|
39
|
+
sk: config.sk ?? existing?.sk ?? "",
|
|
40
|
+
agentId: config.agentId ?? existing?.agentId ?? "",
|
|
41
|
+
enableStreaming: config.enableStreaming ?? existing?.enableStreaming ?? true,
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
...cfg,
|
|
45
|
+
channels: {
|
|
46
|
+
...cfg.channels,
|
|
47
|
+
xiaoyi: merged,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Note about XiaoYi setup
|
|
53
|
+
*/
|
|
54
|
+
async function noteXiaoYiSetupHelp(prompter) {
|
|
55
|
+
await prompter.note([
|
|
56
|
+
"XiaoYi (小艺) uses A2A protocol via WebSocket connection.",
|
|
57
|
+
"",
|
|
58
|
+
"Required credentials:",
|
|
59
|
+
" - ak: Access Key for authentication",
|
|
60
|
+
" - sk: Secret Key for authentication",
|
|
61
|
+
" - agentId: Your agent identifier",
|
|
62
|
+
"",
|
|
63
|
+
"WebSocket URLs will use default values.",
|
|
64
|
+
"",
|
|
65
|
+
"Docs: https://docs.openclaw.ai/channels/xiaoyi",
|
|
66
|
+
].join("\n"), "XiaoYi setup");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Prompt for Access Key
|
|
70
|
+
*/
|
|
71
|
+
async function promptAk(prompter, config) {
|
|
72
|
+
const existing = config?.ak ?? "";
|
|
73
|
+
return String(await prompter.text({
|
|
74
|
+
message: "XiaoYi Access Key (ak)",
|
|
75
|
+
initialValue: existing,
|
|
76
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
77
|
+
})).trim();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Prompt for Secret Key
|
|
81
|
+
*/
|
|
82
|
+
async function promptSk(prompter, config) {
|
|
83
|
+
const existing = config?.sk ?? "";
|
|
84
|
+
return String(await prompter.text({
|
|
85
|
+
message: "XiaoYi Secret Key (sk)",
|
|
86
|
+
initialValue: existing,
|
|
87
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
88
|
+
})).trim();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Prompt for Agent ID
|
|
92
|
+
*/
|
|
93
|
+
async function promptAgentId(prompter, config) {
|
|
94
|
+
const existing = config?.agentId ?? "";
|
|
95
|
+
return String(await prompter.text({
|
|
96
|
+
message: "XiaoYi Agent ID",
|
|
97
|
+
initialValue: existing,
|
|
98
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
99
|
+
})).trim();
|
|
100
|
+
}
|
|
101
|
+
exports.xiaoyiOnboardingAdapter = {
|
|
102
|
+
channel,
|
|
103
|
+
getStatus: async ({ cfg }) => {
|
|
104
|
+
const config = getXiaoYiConfig(cfg);
|
|
105
|
+
const configured = isXiaoYiConfigured(config);
|
|
106
|
+
const enabled = config?.enabled !== false;
|
|
107
|
+
const statusLines = [];
|
|
108
|
+
if (configured) {
|
|
109
|
+
statusLines.push(`XiaoYi: ${enabled ? "enabled" : "disabled"}`);
|
|
110
|
+
if (config?.wsUrl1 || config?.wsUrl) {
|
|
111
|
+
statusLines.push(` WebSocket: ${config.wsUrl1 || config.wsUrl}`);
|
|
112
|
+
}
|
|
113
|
+
if (config?.wsUrl2) {
|
|
114
|
+
statusLines.push(` Secondary: ${config.wsUrl2}`);
|
|
115
|
+
}
|
|
116
|
+
if (config?.agentId) {
|
|
117
|
+
statusLines.push(` Agent ID: ${config.agentId}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
statusLines.push("XiaoYi: needs ak, sk, and agentId");
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
channel,
|
|
125
|
+
configured,
|
|
126
|
+
statusLines,
|
|
127
|
+
selectionHint: configured ? "configured" : "needs setup",
|
|
128
|
+
quickstartScore: 50,
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
configure: async ({ cfg, prompter }) => {
|
|
132
|
+
const config = getXiaoYiConfig(cfg);
|
|
133
|
+
if (!isXiaoYiConfigured(config)) {
|
|
134
|
+
await noteXiaoYiSetupHelp(prompter);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const reconfigure = await prompter.confirm({
|
|
138
|
+
message: "XiaoYi already configured. Reconfigure?",
|
|
139
|
+
initialValue: false,
|
|
140
|
+
});
|
|
141
|
+
if (!reconfigure) {
|
|
142
|
+
return { cfg, accountId: "default" };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Prompt for required credentials
|
|
146
|
+
const ak = await promptAk(prompter, config);
|
|
147
|
+
const sk = await promptSk(prompter, config);
|
|
148
|
+
const agentId = await promptAgentId(prompter, config);
|
|
149
|
+
const cfgWithConfig = setXiaoYiConfig(cfg, {
|
|
150
|
+
ak,
|
|
151
|
+
sk,
|
|
152
|
+
agentId,
|
|
153
|
+
enabled: true,
|
|
154
|
+
});
|
|
155
|
+
return { cfg: cfgWithConfig, accountId: "default" };
|
|
156
|
+
},
|
|
157
|
+
disable: (cfg) => {
|
|
158
|
+
const xiaoyi = getXiaoYiConfig(cfg);
|
|
159
|
+
return {
|
|
160
|
+
...cfg,
|
|
161
|
+
channels: {
|
|
162
|
+
...cfg.channels,
|
|
163
|
+
xiaoyi: { ...xiaoyi, enabled: false },
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class XiaoYiRuntime {
|
|
|
22
22
|
private sessionTimeoutSent;
|
|
23
23
|
private timeoutConfig;
|
|
24
24
|
private sessionAbortControllerMap;
|
|
25
|
+
private sessionActiveRunMap;
|
|
25
26
|
constructor();
|
|
26
27
|
getInstanceId(): string;
|
|
27
28
|
/**
|
|
@@ -52,11 +53,16 @@ export declare class XiaoYiRuntime {
|
|
|
52
53
|
* Set timeout for a session
|
|
53
54
|
* @param sessionId - Session ID
|
|
54
55
|
* @param callback - Function to call when timeout occurs
|
|
55
|
-
* @returns The
|
|
56
|
+
* @returns The interval ID (for cancellation)
|
|
57
|
+
*
|
|
58
|
+
* IMPORTANT: This now uses setInterval instead of setTimeout
|
|
59
|
+
* - First trigger: after 60 seconds
|
|
60
|
+
* - Subsequent triggers: every 60 seconds after that
|
|
61
|
+
* - Cleared when: response received, session completed, or explicitly cleared
|
|
56
62
|
*/
|
|
57
63
|
setTimeoutForSession(sessionId: string, callback: () => void): NodeJS.Timeout | undefined;
|
|
58
64
|
/**
|
|
59
|
-
* Clear timeout for a session
|
|
65
|
+
* Clear timeout interval for a session
|
|
60
66
|
* @param sessionId - Session ID
|
|
61
67
|
*/
|
|
62
68
|
clearSessionTimeout(sessionId: string): void;
|
|
@@ -71,7 +77,7 @@ export declare class XiaoYiRuntime {
|
|
|
71
77
|
*/
|
|
72
78
|
markSessionCompleted(sessionId: string): void;
|
|
73
79
|
/**
|
|
74
|
-
* Clear all
|
|
80
|
+
* Clear all timeout intervals
|
|
75
81
|
*/
|
|
76
82
|
clearAllTimeouts(): void;
|
|
77
83
|
/**
|
|
@@ -101,12 +107,18 @@ export declare class XiaoYiRuntime {
|
|
|
101
107
|
/**
|
|
102
108
|
* Create and register an AbortController for a session
|
|
103
109
|
* @param sessionId - Session ID
|
|
104
|
-
* @returns The AbortController and its signal
|
|
110
|
+
* @returns The AbortController and its signal, or null if session is busy
|
|
105
111
|
*/
|
|
106
112
|
createAbortControllerForSession(sessionId: string): {
|
|
107
113
|
controller: AbortController;
|
|
108
114
|
signal: AbortSignal;
|
|
109
|
-
};
|
|
115
|
+
} | null;
|
|
116
|
+
/**
|
|
117
|
+
* Check if a session has an active agent run
|
|
118
|
+
* @param sessionId - Session ID
|
|
119
|
+
* @returns true if session is busy
|
|
120
|
+
*/
|
|
121
|
+
isSessionActive(sessionId: string): boolean;
|
|
110
122
|
/**
|
|
111
123
|
* Abort a session's agent run
|
|
112
124
|
* @param sessionId - Session ID
|
package/dist/runtime.js
CHANGED
|
@@ -10,7 +10,7 @@ const websocket_1 = require("./websocket");
|
|
|
10
10
|
const DEFAULT_TIMEOUT_CONFIG = {
|
|
11
11
|
enabled: true,
|
|
12
12
|
duration: 60000, // 60 seconds
|
|
13
|
-
message: "
|
|
13
|
+
message: "任务正在处理中,请稍后",
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* Runtime state for XiaoYi channel
|
|
@@ -28,6 +28,8 @@ class XiaoYiRuntime {
|
|
|
28
28
|
this.timeoutConfig = DEFAULT_TIMEOUT_CONFIG;
|
|
29
29
|
// AbortController management for canceling agent runs
|
|
30
30
|
this.sessionAbortControllerMap = new Map();
|
|
31
|
+
// Track if a session has an active agent run (for concurrent request detection)
|
|
32
|
+
this.sessionActiveRunMap = new Map();
|
|
31
33
|
this.instanceId = `runtime_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
32
34
|
console.log(`XiaoYi: Created new runtime instance: ${this.instanceId}`);
|
|
33
35
|
}
|
|
@@ -122,7 +124,12 @@ class XiaoYiRuntime {
|
|
|
122
124
|
* Set timeout for a session
|
|
123
125
|
* @param sessionId - Session ID
|
|
124
126
|
* @param callback - Function to call when timeout occurs
|
|
125
|
-
* @returns The
|
|
127
|
+
* @returns The interval ID (for cancellation)
|
|
128
|
+
*
|
|
129
|
+
* IMPORTANT: This now uses setInterval instead of setTimeout
|
|
130
|
+
* - First trigger: after 60 seconds
|
|
131
|
+
* - Subsequent triggers: every 60 seconds after that
|
|
132
|
+
* - Cleared when: response received, session completed, or explicitly cleared
|
|
126
133
|
*/
|
|
127
134
|
setTimeoutForSession(sessionId, callback) {
|
|
128
135
|
if (!this.timeoutConfig.enabled) {
|
|
@@ -133,32 +140,33 @@ class XiaoYiRuntime {
|
|
|
133
140
|
const hadExistingTimeout = this.sessionTimeoutMap.has(sessionId);
|
|
134
141
|
const hadSentTimeout = this.sessionTimeoutSent.has(sessionId);
|
|
135
142
|
this.clearSessionTimeout(sessionId);
|
|
136
|
-
//
|
|
143
|
+
// Clear the timeout sent flag to allow this session to timeout again
|
|
137
144
|
if (hadSentTimeout) {
|
|
138
145
|
this.sessionTimeoutSent.delete(sessionId);
|
|
139
146
|
console.log(`[TIMEOUT] Previous timeout flag cleared for session ${sessionId} (session reuse)`);
|
|
140
147
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
// Use setInterval for periodic timeout triggers
|
|
149
|
+
// First trigger after duration, then every duration after that
|
|
150
|
+
const intervalId = setInterval(() => {
|
|
151
|
+
console.log(`[TIMEOUT] Timeout triggered for session ${sessionId} (will trigger again in ${this.timeoutConfig.duration}ms if still active)`);
|
|
144
152
|
this.sessionTimeoutSent.add(sessionId);
|
|
145
153
|
callback();
|
|
146
154
|
}, this.timeoutConfig.duration);
|
|
147
|
-
this.sessionTimeoutMap.set(sessionId,
|
|
148
|
-
const logSuffix = hadExistingTimeout ? " (replacing existing
|
|
149
|
-
console.log(`[TIMEOUT] ${this.timeoutConfig.duration}ms timeout started for session ${sessionId}${logSuffix}`);
|
|
150
|
-
return
|
|
155
|
+
this.sessionTimeoutMap.set(sessionId, intervalId);
|
|
156
|
+
const logSuffix = hadExistingTimeout ? " (replacing existing interval)" : "";
|
|
157
|
+
console.log(`[TIMEOUT] ${this.timeoutConfig.duration}ms periodic timeout started for session ${sessionId}${logSuffix}`);
|
|
158
|
+
return intervalId;
|
|
151
159
|
}
|
|
152
160
|
/**
|
|
153
|
-
* Clear timeout for a session
|
|
161
|
+
* Clear timeout interval for a session
|
|
154
162
|
* @param sessionId - Session ID
|
|
155
163
|
*/
|
|
156
164
|
clearSessionTimeout(sessionId) {
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
|
|
165
|
+
const intervalId = this.sessionTimeoutMap.get(sessionId);
|
|
166
|
+
if (intervalId) {
|
|
167
|
+
clearInterval(intervalId);
|
|
160
168
|
this.sessionTimeoutMap.delete(sessionId);
|
|
161
|
-
console.log(`[TIMEOUT] Timeout cleared for session ${sessionId}`);
|
|
169
|
+
console.log(`[TIMEOUT] Timeout interval cleared for session ${sessionId}`);
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
172
|
/**
|
|
@@ -178,15 +186,15 @@ class XiaoYiRuntime {
|
|
|
178
186
|
console.log(`[TIMEOUT] Session ${sessionId} marked as completed`);
|
|
179
187
|
}
|
|
180
188
|
/**
|
|
181
|
-
* Clear all
|
|
189
|
+
* Clear all timeout intervals
|
|
182
190
|
*/
|
|
183
191
|
clearAllTimeouts() {
|
|
184
|
-
for (const [sessionId,
|
|
185
|
-
|
|
192
|
+
for (const [sessionId, intervalId] of this.sessionTimeoutMap.entries()) {
|
|
193
|
+
clearInterval(intervalId);
|
|
186
194
|
}
|
|
187
195
|
this.sessionTimeoutMap.clear();
|
|
188
196
|
this.sessionTimeoutSent.clear();
|
|
189
|
-
console.log("[TIMEOUT] All
|
|
197
|
+
console.log("[TIMEOUT] All timeout intervals cleared");
|
|
190
198
|
}
|
|
191
199
|
/**
|
|
192
200
|
* Get WebSocket manager
|
|
@@ -227,16 +235,28 @@ class XiaoYiRuntime {
|
|
|
227
235
|
/**
|
|
228
236
|
* Create and register an AbortController for a session
|
|
229
237
|
* @param sessionId - Session ID
|
|
230
|
-
* @returns The AbortController and its signal
|
|
238
|
+
* @returns The AbortController and its signal, or null if session is busy
|
|
231
239
|
*/
|
|
232
240
|
createAbortControllerForSession(sessionId) {
|
|
233
|
-
//
|
|
234
|
-
this.
|
|
241
|
+
// Check if there's an active agent run for this session
|
|
242
|
+
if (this.sessionActiveRunMap.get(sessionId)) {
|
|
243
|
+
console.log(`[CONCURRENT] Session ${sessionId} has an active agent run, cannot create new AbortController`);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
235
246
|
const controller = new AbortController();
|
|
236
247
|
this.sessionAbortControllerMap.set(sessionId, controller);
|
|
248
|
+
this.sessionActiveRunMap.set(sessionId, true);
|
|
237
249
|
console.log(`[ABORT] Created AbortController for session ${sessionId}`);
|
|
238
250
|
return { controller, signal: controller.signal };
|
|
239
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Check if a session has an active agent run
|
|
254
|
+
* @param sessionId - Session ID
|
|
255
|
+
* @returns true if session is busy
|
|
256
|
+
*/
|
|
257
|
+
isSessionActive(sessionId) {
|
|
258
|
+
return this.sessionActiveRunMap.get(sessionId) || false;
|
|
259
|
+
}
|
|
240
260
|
/**
|
|
241
261
|
* Abort a session's agent run
|
|
242
262
|
* @param sessionId - Session ID
|
|
@@ -272,6 +292,9 @@ class XiaoYiRuntime {
|
|
|
272
292
|
this.sessionAbortControllerMap.delete(sessionId);
|
|
273
293
|
console.log(`[ABORT] Cleared AbortController for session ${sessionId}`);
|
|
274
294
|
}
|
|
295
|
+
// Also clear the active run flag
|
|
296
|
+
this.sessionActiveRunMap.delete(sessionId);
|
|
297
|
+
console.log(`[CONCURRENT] Session ${sessionId} marked as inactive`);
|
|
275
298
|
}
|
|
276
299
|
/**
|
|
277
300
|
* Clear all AbortControllers
|
package/dist/websocket.d.ts
CHANGED
|
@@ -75,6 +75,22 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
|
|
|
75
75
|
* This uses "status-update" event type which keeps the conversation active
|
|
76
76
|
*/
|
|
77
77
|
sendStatusUpdate(taskId: string, sessionId: string, message: string, targetServer?: ServerId): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Send PUSH message (主动推送) via HTTP API
|
|
80
|
+
*
|
|
81
|
+
* This is used when SubAgent completes execution and needs to push results to user
|
|
82
|
+
* independently of the original A2A request-response flow.
|
|
83
|
+
*
|
|
84
|
+
* Unlike sendResponse (which responds to a specific request via WebSocket), push messages are
|
|
85
|
+
* sent through HTTP API asynchronously.
|
|
86
|
+
*
|
|
87
|
+
* @param sessionId - User's session ID
|
|
88
|
+
* @param message - Message content to push
|
|
89
|
+
*
|
|
90
|
+
* Reference: 华为小艺推送消息 API
|
|
91
|
+
* TODO: 实现实际的推送消息发送逻辑
|
|
92
|
+
*/
|
|
93
|
+
sendPushMessage(sessionId: string, message: string): Promise<void>;
|
|
78
94
|
/**
|
|
79
95
|
* Send tasks cancel response to specific server
|
|
80
96
|
*/
|
package/dist/websocket.js
CHANGED
|
@@ -514,6 +514,27 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
514
514
|
throw error;
|
|
515
515
|
}
|
|
516
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Send PUSH message (主动推送) via HTTP API
|
|
519
|
+
*
|
|
520
|
+
* This is used when SubAgent completes execution and needs to push results to user
|
|
521
|
+
* independently of the original A2A request-response flow.
|
|
522
|
+
*
|
|
523
|
+
* Unlike sendResponse (which responds to a specific request via WebSocket), push messages are
|
|
524
|
+
* sent through HTTP API asynchronously.
|
|
525
|
+
*
|
|
526
|
+
* @param sessionId - User's session ID
|
|
527
|
+
* @param message - Message content to push
|
|
528
|
+
*
|
|
529
|
+
* Reference: 华为小艺推送消息 API
|
|
530
|
+
* TODO: 实现实际的推送消息发送逻辑
|
|
531
|
+
*/
|
|
532
|
+
async sendPushMessage(sessionId, message) {
|
|
533
|
+
console.log(`[PUSH] Would send push message to session ${sessionId}, length: ${message.length} chars`);
|
|
534
|
+
console.log(`[PUSH] Content: ${message.substring(0, 50)}${message.length > 50 ? "..." : ""}`);
|
|
535
|
+
// TODO: Implement actual push message sending via HTTP API
|
|
536
|
+
// Need to confirm correct push message format with XiaoYi API documentation
|
|
537
|
+
}
|
|
517
538
|
/**
|
|
518
539
|
* Send tasks cancel response to specific server
|
|
519
540
|
*/
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XiaoYi Media Handler - Downloads and saves media files locally
|
|
3
|
+
* Similar to clawdbot-feishu's media.ts approach
|
|
4
|
+
*/
|
|
5
|
+
type PluginRuntime = any;
|
|
6
|
+
export interface DownloadedMedia {
|
|
7
|
+
path: string;
|
|
8
|
+
contentType: string;
|
|
9
|
+
placeholder: string;
|
|
10
|
+
fileName?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MediaDownloadOptions {
|
|
13
|
+
maxBytes?: number;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if a MIME type is an image
|
|
18
|
+
*/
|
|
19
|
+
export declare function isImageMimeType(mimeType: string | undefined): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Check if a MIME type is a PDF
|
|
22
|
+
*/
|
|
23
|
+
export declare function isPdfMimeType(mimeType: string | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Check if a MIME type is text-based
|
|
26
|
+
*/
|
|
27
|
+
export declare function isTextMimeType(mimeType: string | undefined): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Download and save media file to local disk
|
|
30
|
+
* This is the key function that follows clawdbot-feishu's approach
|
|
31
|
+
*/
|
|
32
|
+
export declare function downloadAndSaveMedia(runtime: PluginRuntime, uri: string, mimeType: string, fileName: string, options?: MediaDownloadOptions): Promise<DownloadedMedia>;
|
|
33
|
+
/**
|
|
34
|
+
* Download and save multiple media files
|
|
35
|
+
*/
|
|
36
|
+
export declare function downloadAndSaveMediaList(runtime: PluginRuntime, files: Array<{
|
|
37
|
+
uri: string;
|
|
38
|
+
mimeType: string;
|
|
39
|
+
name: string;
|
|
40
|
+
}>, options?: MediaDownloadOptions): Promise<DownloadedMedia[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Build media payload for inbound context
|
|
43
|
+
* Similar to clawdbot-feishu's buildFeishuMediaPayload()
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildXiaoYiMediaPayload(mediaList: DownloadedMedia[]): {
|
|
46
|
+
MediaPath?: string;
|
|
47
|
+
MediaType?: string;
|
|
48
|
+
MediaUrl?: string;
|
|
49
|
+
MediaPaths?: string[];
|
|
50
|
+
MediaUrls?: string[];
|
|
51
|
+
MediaTypes?: string[];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Extract text from downloaded file for including in message body
|
|
55
|
+
*/
|
|
56
|
+
export declare function extractTextFromFile(path: string, mimeType: string): Promise<string | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Input image content type for AI processing
|
|
59
|
+
*/
|
|
60
|
+
export interface InputImageContent {
|
|
61
|
+
type: "image";
|
|
62
|
+
data: string;
|
|
63
|
+
mimeType: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Image download limits
|
|
67
|
+
*/
|
|
68
|
+
export interface ImageLimits {
|
|
69
|
+
maxBytes?: number;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract image from URL and return base64 encoded data
|
|
74
|
+
*/
|
|
75
|
+
export declare function extractImageFromUrl(url: string, limits?: Partial<ImageLimits>): Promise<InputImageContent>;
|
|
76
|
+
/**
|
|
77
|
+
* Extract text content from URL
|
|
78
|
+
* Supports text-based files (txt, md, json, xml, csv, etc.)
|
|
79
|
+
*/
|
|
80
|
+
export declare function extractTextFromUrl(url: string, maxBytes?: number, timeoutMs?: number): Promise<string>;
|
|
81
|
+
export {};
|