@wecom/wecom-openclaw-plugin 2026.4.2 → 2026.4.3-beta.0
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 +395 -10
- package/dist/index.d.ts +2 -4
- package/dist/index.js +58 -0
- package/dist/src/accounts.d.ts +57 -0
- package/dist/src/accounts.js +222 -0
- package/dist/src/agent/api-client.d.ts +94 -0
- package/dist/src/agent/api-client.js +292 -0
- package/dist/src/agent/handler.d.ts +63 -0
- package/dist/src/agent/handler.js +551 -0
- package/dist/src/agent/index.d.ts +5 -0
- package/dist/src/agent/index.js +5 -0
- package/dist/src/agent/webhook.d.ts +24 -0
- package/dist/src/agent/webhook.js +228 -0
- package/dist/src/agent/xml.d.ts +21 -0
- package/dist/src/agent/xml.js +41 -0
- package/dist/src/channel.js +579 -0
- package/dist/src/chat-queue.d.ts +31 -0
- package/dist/src/chat-queue.js +45 -0
- package/dist/{esm/types/src → src}/const.d.ts +46 -0
- package/dist/{esm/src → src}/const.js +77 -30
- package/dist/{esm/src → src}/dm-policy.js +6 -9
- package/dist/src/dynamic-agent.d.ts +37 -0
- package/dist/src/dynamic-agent.js +60 -0
- package/dist/src/dynamic-routing.d.ts +65 -0
- package/dist/src/dynamic-routing.js +58 -0
- package/dist/{esm/src → src}/group-policy.js +8 -22
- package/dist/src/http.d.ts +26 -0
- package/dist/src/http.js +93 -0
- package/dist/{esm/types/src → src}/interface.d.ts +0 -2
- package/dist/src/interface.js +4 -0
- package/dist/src/mcp/index.js +6 -0
- package/dist/{esm/src → src}/mcp/interceptors/biz-error.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/index.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/msg-media.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-create.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-export.js +2 -6
- package/dist/src/mcp/interceptors/types.js +7 -0
- package/dist/{esm/src → src}/mcp/schema.js +1 -4
- package/dist/{esm/src → src}/mcp/tool.js +4 -8
- package/dist/{esm/src → src}/mcp/transport.js +15 -16
- package/dist/{esm/src → src}/media-handler.js +6 -10
- package/dist/{esm/src → src}/media-uploader.js +64 -10
- package/dist/{esm/src → src}/message-parser.js +1 -4
- package/dist/{esm/types/src → src}/message-sender.d.ts +18 -0
- package/dist/{esm/src → src}/message-sender.js +45 -11
- package/dist/{esm/src → src}/monitor.js +145 -292
- package/dist/{esm/src → src}/onboarding.js +22 -26
- package/dist/{esm/types/src → src}/openclaw-compat.d.ts +49 -0
- package/dist/{esm/src → src}/openclaw-compat.js +133 -27
- package/dist/{esm/src → src}/reqid-store.js +4 -7
- package/dist/{esm/src → src}/runtime.js +2 -5
- package/dist/src/shared/command-auth.d.ts +23 -0
- package/dist/src/shared/command-auth.js +73 -0
- package/dist/src/shared/xml-parser.d.ts +46 -0
- package/dist/src/shared/xml-parser.js +210 -0
- package/dist/{esm/src → src}/state-dir-resolve.js +3 -7
- package/dist/{esm/src → src}/state-manager.js +84 -16
- package/dist/src/target.d.ts +35 -0
- package/dist/src/target.js +67 -0
- package/dist/src/template-card-manager.d.ts +55 -0
- package/dist/src/template-card-manager.js +205 -0
- package/dist/{esm/src → src}/template-card-parser.js +17 -8
- package/dist/{esm/src → src}/timeout.js +2 -5
- package/dist/src/types/account.d.ts +29 -0
- package/dist/src/types/account.js +4 -0
- package/dist/src/types/config.d.ts +91 -0
- package/dist/src/types/config.js +7 -0
- package/dist/src/types/constants.d.ts +42 -0
- package/dist/src/types/constants.js +42 -0
- package/dist/src/types/index.d.ts +7 -0
- package/dist/src/types/index.js +5 -0
- package/dist/src/types/message.d.ts +238 -0
- package/dist/src/types/message.js +5 -0
- package/dist/{esm/types/src → src}/utils.d.ts +41 -0
- package/dist/{esm/src → src}/utils.js +24 -10
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +24 -0
- package/dist/src/webhook/command-auth.d.ts +47 -0
- package/dist/src/webhook/command-auth.js +96 -0
- package/dist/src/webhook/gateway.d.ts +36 -0
- package/dist/src/webhook/gateway.js +235 -0
- package/dist/src/webhook/handler.d.ts +18 -0
- package/dist/src/webhook/handler.js +420 -0
- package/dist/src/webhook/helpers.d.ts +156 -0
- package/dist/src/webhook/helpers.js +833 -0
- package/dist/src/webhook/http.d.ts +26 -0
- package/dist/src/webhook/http.js +94 -0
- package/dist/src/webhook/index.d.ts +11 -0
- package/dist/src/webhook/index.js +14 -0
- package/dist/src/webhook/media.d.ts +29 -0
- package/dist/src/webhook/media.js +88 -0
- package/dist/src/webhook/monitor.d.ts +59 -0
- package/dist/src/webhook/monitor.js +1161 -0
- package/dist/src/webhook/state.d.ts +220 -0
- package/dist/src/webhook/state.js +491 -0
- package/dist/src/webhook/target.d.ts +41 -0
- package/dist/src/webhook/target.js +132 -0
- package/dist/src/webhook/types.d.ts +347 -0
- package/dist/src/webhook/types.js +33 -0
- package/dist/src/webhook/video-frame.d.ts +13 -0
- package/dist/src/webhook/video-frame.js +30 -0
- package/package.json +15 -7
- package/dist/cjs/index.js +0 -45
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/src/channel.js +0 -339
- package/dist/cjs/src/channel.js.map +0 -1
- package/dist/cjs/src/const.js +0 -137
- package/dist/cjs/src/const.js.map +0 -1
- package/dist/cjs/src/dm-policy.js +0 -89
- package/dist/cjs/src/dm-policy.js.map +0 -1
- package/dist/cjs/src/group-policy.js +0 -133
- package/dist/cjs/src/group-policy.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/biz-error.js +0 -70
- package/dist/cjs/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/index.js +0 -73
- package/dist/cjs/src/mcp/interceptors/index.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/msg-media.js +0 -159
- package/dist/cjs/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js +0 -157
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js +0 -93
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/cjs/src/mcp/schema.js +0 -104
- package/dist/cjs/src/mcp/schema.js.map +0 -1
- package/dist/cjs/src/mcp/tool.js +0 -208
- package/dist/cjs/src/mcp/tool.js.map +0 -1
- package/dist/cjs/src/mcp/transport.js +0 -417
- package/dist/cjs/src/mcp/transport.js.map +0 -1
- package/dist/cjs/src/media-handler.js +0 -129
- package/dist/cjs/src/media-handler.js.map +0 -1
- package/dist/cjs/src/media-uploader.js +0 -284
- package/dist/cjs/src/media-uploader.js.map +0 -1
- package/dist/cjs/src/message-parser.js +0 -134
- package/dist/cjs/src/message-parser.js.map +0 -1
- package/dist/cjs/src/message-sender.js +0 -89
- package/dist/cjs/src/message-sender.js.map +0 -1
- package/dist/cjs/src/monitor.js +0 -991
- package/dist/cjs/src/monitor.js.map +0 -1
- package/dist/cjs/src/onboarding.js +0 -182
- package/dist/cjs/src/onboarding.js.map +0 -1
- package/dist/cjs/src/openclaw-compat.js +0 -400
- package/dist/cjs/src/openclaw-compat.js.map +0 -1
- package/dist/cjs/src/reqid-store.js +0 -88
- package/dist/cjs/src/reqid-store.js.map +0 -1
- package/dist/cjs/src/runtime.js +0 -16
- package/dist/cjs/src/runtime.js.map +0 -1
- package/dist/cjs/src/state-dir-resolve.js +0 -35
- package/dist/cjs/src/state-dir-resolve.js.map +0 -1
- package/dist/cjs/src/state-manager.js +0 -158
- package/dist/cjs/src/state-manager.js.map +0 -1
- package/dist/cjs/src/template-card-parser.js +0 -641
- package/dist/cjs/src/template-card-parser.js.map +0 -1
- package/dist/cjs/src/timeout.js +0 -42
- package/dist/cjs/src/timeout.js.map +0 -1
- package/dist/cjs/src/utils.js +0 -60
- package/dist/cjs/src/utils.js.map +0 -1
- package/dist/cjs/src/version.js +0 -26
- package/dist/cjs/src/version.js.map +0 -1
- package/dist/esm/index.js +0 -41
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/src/channel.js +0 -337
- package/dist/esm/src/channel.js.map +0 -1
- package/dist/esm/src/const.js.map +0 -1
- package/dist/esm/src/dm-policy.js.map +0 -1
- package/dist/esm/src/group-policy.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/index.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/esm/src/mcp/schema.js.map +0 -1
- package/dist/esm/src/mcp/tool.js.map +0 -1
- package/dist/esm/src/mcp/transport.js.map +0 -1
- package/dist/esm/src/media-handler.js.map +0 -1
- package/dist/esm/src/media-uploader.js.map +0 -1
- package/dist/esm/src/message-parser.js.map +0 -1
- package/dist/esm/src/message-sender.js.map +0 -1
- package/dist/esm/src/monitor.js.map +0 -1
- package/dist/esm/src/onboarding.js.map +0 -1
- package/dist/esm/src/openclaw-compat.js.map +0 -1
- package/dist/esm/src/reqid-store.js.map +0 -1
- package/dist/esm/src/runtime.js.map +0 -1
- package/dist/esm/src/state-dir-resolve.js.map +0 -1
- package/dist/esm/src/state-manager.js.map +0 -1
- package/dist/esm/src/template-card-parser.js.map +0 -1
- package/dist/esm/src/timeout.js.map +0 -1
- package/dist/esm/src/utils.js.map +0 -1
- package/dist/esm/src/version.js +0 -23
- package/dist/esm/src/version.js.map +0 -1
- package/dist/esm/types/index.d.ts +0 -9
- package/dist/esm/types/src/version.d.ts +0 -2
- /package/dist/{esm/types/src → src}/channel.d.ts +0 -0
- /package/dist/{esm/types/src → src}/dm-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/group-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/biz-error.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/msg-media.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-create.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-export.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/types.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/schema.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/tool.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/transport.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-handler.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-uploader.d.ts +0 -0
- /package/dist/{esm/types/src → src}/message-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/monitor.d.ts +0 -0
- /package/dist/{esm/types/src → src}/onboarding.d.ts +0 -0
- /package/dist/{esm/types/src → src}/reqid-store.d.ts +0 -0
- /package/dist/{esm/types/src → src}/runtime.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-dir-resolve.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-manager.d.ts +0 -0
- /package/dist/{esm/types/src → src}/template-card-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/timeout.d.ts +0 -0
|
@@ -29,8 +29,6 @@ export interface MessageState {
|
|
|
29
29
|
hasMediaFailed?: boolean;
|
|
30
30
|
/** 媒体发送失败时的纯文本错误摘要(用于替换 thinking 流展示给用户) */
|
|
31
31
|
mediaErrorSummary?: string;
|
|
32
|
-
/** deliver 回调是否被调用过(用于区分"核心无回复"和"核心回复了空内容") */
|
|
33
|
-
deliverCalled?: boolean;
|
|
34
32
|
/** 流式回复是否已过期(errcode 846608,>6分钟),需降级为主动发送 */
|
|
35
33
|
streamExpired?: boolean;
|
|
36
34
|
/** 是否已成功发送过模板卡片 */
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { clearCategoryCache } from '../transport.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* 业务错误码检查拦截器
|
|
5
3
|
*
|
|
@@ -9,6 +7,7 @@ import { clearCategoryCache } from '../transport.js';
|
|
|
9
7
|
*
|
|
10
8
|
* 此拦截器对所有 call 调用生效。
|
|
11
9
|
*/
|
|
10
|
+
import { clearCategoryCache } from "../transport.js";
|
|
12
11
|
// ============================================================================
|
|
13
12
|
// 常量
|
|
14
13
|
// ============================================================================
|
|
@@ -24,7 +23,7 @@ const BIZ_CACHE_CLEAR_ERROR_CODES = new Set([850002]);
|
|
|
24
23
|
// ============================================================================
|
|
25
24
|
// 拦截器实现
|
|
26
25
|
// ============================================================================
|
|
27
|
-
const bizErrorInterceptor = {
|
|
26
|
+
export const bizErrorInterceptor = {
|
|
28
27
|
name: "biz-error",
|
|
29
28
|
/** 对所有 call 调用生效 */
|
|
30
29
|
match: () => true,
|
|
@@ -63,6 +62,3 @@ function checkBizErrorAndClearCache(result, category) {
|
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
|
-
|
|
67
|
-
export { bizErrorInterceptor };
|
|
68
|
-
//# sourceMappingURL=biz-error.js.map
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import { bizErrorInterceptor } from './biz-error.js';
|
|
2
|
-
import { mediaInterceptor } from './msg-media.js';
|
|
3
|
-
import { smartpageCreateInterceptor } from './smartpage-create.js';
|
|
4
|
-
import { smartpageExportInterceptor } from './smartpage-export.js';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* MCP call 拦截器注册表与调度入口
|
|
8
3
|
*
|
|
@@ -13,6 +8,10 @@ import { smartpageExportInterceptor } from './smartpage-export.js';
|
|
|
13
8
|
*
|
|
14
9
|
* tool.ts 的 handleCall 无需任何改动。
|
|
15
10
|
*/
|
|
11
|
+
import { bizErrorInterceptor } from "./biz-error.js";
|
|
12
|
+
import { mediaInterceptor } from "./msg-media.js";
|
|
13
|
+
import { smartpageCreateInterceptor } from "./smartpage-create.js";
|
|
14
|
+
import { smartpageExportInterceptor } from "./smartpage-export.js";
|
|
16
15
|
// ============================================================================
|
|
17
16
|
// 拦截器注册表(按注册顺序执行)
|
|
18
17
|
// ============================================================================
|
|
@@ -29,7 +28,7 @@ const interceptors = [
|
|
|
29
28
|
* - timeoutMs: 取所有拦截器返回值中的最大值
|
|
30
29
|
* - args: 后注册的拦截器覆盖前者(一般同一调用只有一个拦截器会返回 args)
|
|
31
30
|
*/
|
|
32
|
-
async function resolveBeforeCall(ctx) {
|
|
31
|
+
export async function resolveBeforeCall(ctx) {
|
|
33
32
|
let mergedTimeoutMs;
|
|
34
33
|
let mergedArgs;
|
|
35
34
|
for (const interceptor of interceptors) {
|
|
@@ -56,7 +55,7 @@ async function resolveBeforeCall(ctx) {
|
|
|
56
55
|
* 前一个拦截器的返回值作为下一个拦截器的输入。
|
|
57
56
|
* 拦截器若不需要修改 result,应原样返回。
|
|
58
57
|
*/
|
|
59
|
-
async function runAfterCall(ctx, result) {
|
|
58
|
+
export async function runAfterCall(ctx, result) {
|
|
60
59
|
let current = result;
|
|
61
60
|
for (const interceptor of interceptors) {
|
|
62
61
|
if (!interceptor.match(ctx) || !interceptor.afterCall)
|
|
@@ -65,6 +64,3 @@ async function runAfterCall(ctx, result) {
|
|
|
65
64
|
}
|
|
66
65
|
return current;
|
|
67
66
|
}
|
|
68
|
-
|
|
69
|
-
export { resolveBeforeCall, runAfterCall };
|
|
70
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import { MEDIA_DOWNLOAD_TIMEOUT_MS } from '../transport.js';
|
|
4
|
-
import { getWeComRuntime } from '../../runtime.js';
|
|
5
|
-
import { detectMime } from '../../openclaw-compat.js';
|
|
6
|
-
|
|
7
1
|
/**
|
|
8
2
|
* get_msg_media 响应拦截器
|
|
9
3
|
*
|
|
@@ -13,10 +7,15 @@ import { detectMime } from '../../openclaw-compat.js';
|
|
|
13
7
|
* 解码为 Buffer 并通过 saveMediaBuffer 保存到本地媒体目录,
|
|
14
8
|
* 替换响应中的 base64_data 为 local_path,避免大模型被 base64 数据消耗 token
|
|
15
9
|
*/
|
|
10
|
+
import * as fs from "node:fs/promises";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { MEDIA_DOWNLOAD_TIMEOUT_MS } from "../transport.js";
|
|
13
|
+
import { getWeComRuntime } from "../../runtime.js";
|
|
14
|
+
import { detectMime } from "../../openclaw-compat.js";
|
|
16
15
|
// ============================================================================
|
|
17
16
|
// 拦截器实现
|
|
18
17
|
// ============================================================================
|
|
19
|
-
const mediaInterceptor = {
|
|
18
|
+
export const mediaInterceptor = {
|
|
20
19
|
name: "media",
|
|
21
20
|
/** 仅对 get_msg_media 方法生效 */
|
|
22
21
|
match: (ctx) => ctx.method === "get_msg_media",
|
|
@@ -132,6 +131,3 @@ async function interceptMediaResponse(result) {
|
|
|
132
131
|
}],
|
|
133
132
|
};
|
|
134
133
|
}
|
|
135
|
-
|
|
136
|
-
export { mediaInterceptor };
|
|
137
|
-
//# sourceMappingURL=msg-media.js.map
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* smartpage_create 请求拦截器
|
|
5
3
|
*
|
|
@@ -28,6 +26,7 @@ import * as fs from 'node:fs/promises';
|
|
|
28
26
|
* 传给 MCP Server 的格式:
|
|
29
27
|
* { "title": "...", "pages": [{"page_title": "...", "page_content": "...", "content_type": "..."}] }
|
|
30
28
|
*/
|
|
29
|
+
import * as fs from "node:fs/promises";
|
|
31
30
|
// ============================================================================
|
|
32
31
|
// 常量
|
|
33
32
|
// ============================================================================
|
|
@@ -111,7 +110,7 @@ async function resolvePages(ctx, pages) {
|
|
|
111
110
|
// ============================================================================
|
|
112
111
|
// 拦截器实现
|
|
113
112
|
// ============================================================================
|
|
114
|
-
const smartpageCreateInterceptor = {
|
|
113
|
+
export const smartpageCreateInterceptor = {
|
|
115
114
|
name: "smartpage-create",
|
|
116
115
|
/** 仅对 doc 品类的 smartpage_create 方法生效 */
|
|
117
116
|
match: (ctx) => ctx.category === "doc" && ctx.method === "smartpage_create",
|
|
@@ -131,6 +130,3 @@ const smartpageCreateInterceptor = {
|
|
|
131
130
|
return resolvePages(ctx, pages);
|
|
132
131
|
},
|
|
133
132
|
};
|
|
134
|
-
|
|
135
|
-
export { smartpageCreateInterceptor };
|
|
136
|
-
//# sourceMappingURL=smartpage-create.js.map
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { getWeComRuntime } from '../../runtime.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* smartpage_get_export_result 响应拦截器
|
|
5
3
|
*
|
|
@@ -15,10 +13,11 @@ import { getWeComRuntime } from '../../runtime.js';
|
|
|
15
13
|
*
|
|
16
14
|
* 这样 LLM 只看到轻量的文件路径信息,Skill 可通过文件路径读取完整内容。
|
|
17
15
|
*/
|
|
16
|
+
import { getWeComRuntime } from "../../runtime.js";
|
|
18
17
|
// ============================================================================
|
|
19
18
|
// 拦截器实现
|
|
20
19
|
// ============================================================================
|
|
21
|
-
const smartpageExportInterceptor = {
|
|
20
|
+
export const smartpageExportInterceptor = {
|
|
22
21
|
name: "smartpage-export",
|
|
23
22
|
/** 仅对 doc 品类的 smartpage_get_export_result 方法生效 */
|
|
24
23
|
match: (ctx) => ctx.category === "doc" && ctx.method === "smartpage_get_export_result",
|
|
@@ -86,6 +85,3 @@ async function interceptExportResponse(result) {
|
|
|
86
85
|
}],
|
|
87
86
|
};
|
|
88
87
|
}
|
|
89
|
-
|
|
90
|
-
export { smartpageExportInterceptor };
|
|
91
|
-
//# sourceMappingURL=smartpage-export.js.map
|
|
@@ -15,7 +15,7 @@ const GEMINI_UNSUPPORTED_KEYWORDS = new Set([
|
|
|
15
15
|
* 清洗 JSON Schema,内联 $ref 引用并移除 Gemini 不支持的关键词,
|
|
16
16
|
* 防止 Gemini 模型解析 function response 时报 400 错误。
|
|
17
17
|
*/
|
|
18
|
-
function cleanSchemaForGemini(schema) {
|
|
18
|
+
export function cleanSchemaForGemini(schema) {
|
|
19
19
|
if (!schema || typeof schema !== "object")
|
|
20
20
|
return schema;
|
|
21
21
|
if (Array.isArray(schema))
|
|
@@ -97,6 +97,3 @@ function cleanWithDefs(schema, defs, refStack) {
|
|
|
97
97
|
}
|
|
98
98
|
return cleaned;
|
|
99
99
|
}
|
|
100
|
-
|
|
101
|
-
export { cleanSchemaForGemini };
|
|
102
|
-
//# sourceMappingURL=schema.js.map
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { sendJsonRpc } from './transport.js';
|
|
2
|
-
import { cleanSchemaForGemini } from './schema.js';
|
|
3
|
-
import { resolveBeforeCall, runAfterCall } from './interceptors/index.js';
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* wecom_mcp — 模拟 MCP 调用的 Agent Tool
|
|
7
3
|
*
|
|
@@ -16,6 +12,9 @@ import { resolveBeforeCall, runAfterCall } from './interceptors/index.js';
|
|
|
16
12
|
* wecom_mcp list contact
|
|
17
13
|
* wecom_mcp call contact getContact '{}'
|
|
18
14
|
*/
|
|
15
|
+
import { sendJsonRpc } from "./transport.js";
|
|
16
|
+
import { cleanSchemaForGemini } from "./schema.js";
|
|
17
|
+
import { resolveBeforeCall, runAfterCall } from "./interceptors/index.js";
|
|
19
18
|
// ============================================================================
|
|
20
19
|
// 响应构造辅助
|
|
21
20
|
// ============================================================================
|
|
@@ -123,7 +122,7 @@ const parseArgs = (args) => {
|
|
|
123
122
|
/**
|
|
124
123
|
* 创建 wecom_mcp Agent Tool 定义
|
|
125
124
|
*/
|
|
126
|
-
function createWeComMcpTool() {
|
|
125
|
+
export function createWeComMcpTool() {
|
|
127
126
|
return {
|
|
128
127
|
name: "wecom_mcp",
|
|
129
128
|
label: "企业微信 MCP 工具",
|
|
@@ -201,6 +200,3 @@ function createWeComMcpTool() {
|
|
|
201
200
|
},
|
|
202
201
|
};
|
|
203
202
|
}
|
|
204
|
-
|
|
205
|
-
export { createWeComMcpTool };
|
|
206
|
-
//# sourceMappingURL=tool.js.map
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import { generateReqId } from '@wecom/aibot-node-sdk';
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID } from '../openclaw-compat.js';
|
|
3
|
-
import { getWeComWebSocket } from '../state-manager.js';
|
|
4
|
-
import { MCP_CONFIG_FETCH_TIMEOUT_MS, MCP_GET_CONFIG_CMD } from '../const.js';
|
|
5
|
-
import { withTimeout } from '../timeout.js';
|
|
6
|
-
import { PLUGIN_VERSION } from '../version.js';
|
|
7
|
-
|
|
8
1
|
/**
|
|
9
2
|
* MCP Streamable HTTP 传输层模块
|
|
10
3
|
*
|
|
@@ -16,13 +9,19 @@ import { PLUGIN_VERSION } from '../version.js';
|
|
|
16
9
|
* - SSE 流式响应解析
|
|
17
10
|
* - MCP 配置运行时缓存(通过 WSClient 拉取 URL 并缓存在内存中)
|
|
18
11
|
*/
|
|
12
|
+
import { generateReqId } from "@wecom/aibot-node-sdk";
|
|
13
|
+
import { DEFAULT_ACCOUNT_ID } from "../openclaw-compat.js";
|
|
14
|
+
import { getWeComWebSocket } from "../state-manager.js";
|
|
15
|
+
import { MCP_GET_CONFIG_CMD, MCP_CONFIG_FETCH_TIMEOUT_MS } from "../const.js";
|
|
16
|
+
import { withTimeout } from "../timeout.js";
|
|
17
|
+
import { PLUGIN_VERSION } from "../version.js";
|
|
19
18
|
// ============================================================================
|
|
20
19
|
// 内部状态
|
|
21
20
|
// ============================================================================
|
|
22
21
|
/** HTTP 请求超时时间(毫秒) */
|
|
23
|
-
const HTTP_REQUEST_TIMEOUT_MS =
|
|
22
|
+
const HTTP_REQUEST_TIMEOUT_MS = 30_000;
|
|
24
23
|
/** 媒体下载请求超时时间(毫秒),base64 编码的媒体文件最大可达 ~27MB */
|
|
25
|
-
const MEDIA_DOWNLOAD_TIMEOUT_MS =
|
|
24
|
+
export const MEDIA_DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
26
25
|
/** 日志前缀 */
|
|
27
26
|
const LOG_TAG = "[mcp]";
|
|
28
27
|
/**
|
|
@@ -31,7 +30,9 @@ const LOG_TAG = "[mcp]";
|
|
|
31
30
|
* 携带服务端返回的 JSON-RPC error.code,
|
|
32
31
|
* 用于上层按错误码进行差异化处理(如特定错误码触发缓存清理)。
|
|
33
32
|
*/
|
|
34
|
-
class McpRpcError extends Error {
|
|
33
|
+
export class McpRpcError extends Error {
|
|
34
|
+
code;
|
|
35
|
+
data;
|
|
35
36
|
constructor(code, message, data) {
|
|
36
37
|
super(message);
|
|
37
38
|
this.code = code;
|
|
@@ -45,7 +46,8 @@ class McpRpcError extends Error {
|
|
|
45
46
|
* 携带 HTTP 状态码,用于精确判断 session 失效(404)等场景,
|
|
46
47
|
* 避免通过字符串匹配 "404" 导致的误判。
|
|
47
48
|
*/
|
|
48
|
-
class McpHttpError extends Error {
|
|
49
|
+
export class McpHttpError extends Error {
|
|
50
|
+
statusCode;
|
|
49
51
|
constructor(statusCode, message) {
|
|
50
52
|
super(message);
|
|
51
53
|
this.statusCode = statusCode;
|
|
@@ -326,7 +328,7 @@ async function parseSseResponse(response) {
|
|
|
326
328
|
*
|
|
327
329
|
* @param category - MCP 品类名称
|
|
328
330
|
*/
|
|
329
|
-
function clearCategoryCache(category) {
|
|
331
|
+
export function clearCategoryCache(category) {
|
|
330
332
|
console.log(`${LOG_TAG} 清理缓存 (category="${category}")`);
|
|
331
333
|
mcpConfigCache.delete(category);
|
|
332
334
|
mcpSessionCache.delete(category);
|
|
@@ -346,7 +348,7 @@ function clearCategoryCache(category) {
|
|
|
346
348
|
* @param options - 可选配置(如自定义超时)
|
|
347
349
|
* @returns JSON-RPC result
|
|
348
350
|
*/
|
|
349
|
-
async function sendJsonRpc(category, method, params, options) {
|
|
351
|
+
export async function sendJsonRpc(category, method, params, options) {
|
|
350
352
|
const url = await getMcpUrl(category);
|
|
351
353
|
const timeoutMs = options?.timeoutMs;
|
|
352
354
|
const body = {
|
|
@@ -406,6 +408,3 @@ async function rebuildSession(url, category) {
|
|
|
406
408
|
inflightInitRequests.set(category, promise);
|
|
407
409
|
return promise;
|
|
408
410
|
}
|
|
409
|
-
|
|
410
|
-
export { MEDIA_DOWNLOAD_TIMEOUT_MS, McpHttpError, McpRpcError, clearCategoryCache, sendJsonRpc };
|
|
411
|
-
//# sourceMappingURL=transport.js.map
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { fileTypeFromBuffer } from 'file-type';
|
|
2
|
-
import { getWeComRuntime } from './runtime.js';
|
|
3
|
-
import { DEFAULT_MEDIA_MAX_MB, IMAGE_DOWNLOAD_TIMEOUT_MS, FILE_DOWNLOAD_TIMEOUT_MS } from './const.js';
|
|
4
|
-
import { withTimeout } from './timeout.js';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* 企业微信媒体(图片)下载和保存模块
|
|
8
3
|
*
|
|
9
4
|
* 负责下载、检测格式、保存图片到本地,包含超时保护
|
|
10
5
|
*/
|
|
6
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
7
|
+
import { getWeComRuntime } from "./runtime.js";
|
|
8
|
+
import { IMAGE_DOWNLOAD_TIMEOUT_MS, FILE_DOWNLOAD_TIMEOUT_MS, DEFAULT_MEDIA_MAX_MB } from "./const.js";
|
|
9
|
+
import { withTimeout } from "./timeout.js";
|
|
11
10
|
// ============================================================================
|
|
12
11
|
// 图片格式检测辅助函数(基于 file-type 包)
|
|
13
12
|
// ============================================================================
|
|
@@ -34,7 +33,7 @@ async function detectImageContentType(data) {
|
|
|
34
33
|
/**
|
|
35
34
|
* 下载并保存所有图片到本地,每张图片的下载带超时保护
|
|
36
35
|
*/
|
|
37
|
-
async function downloadAndSaveImages(params) {
|
|
36
|
+
export async function downloadAndSaveImages(params) {
|
|
38
37
|
const { imageUrls, config, runtime, wsClient } = params;
|
|
39
38
|
const core = getWeComRuntime();
|
|
40
39
|
const mediaList = [];
|
|
@@ -80,7 +79,7 @@ async function downloadAndSaveImages(params) {
|
|
|
80
79
|
/**
|
|
81
80
|
* 下载并保存所有文件到本地,每个文件的下载带超时保护
|
|
82
81
|
*/
|
|
83
|
-
async function downloadAndSaveFiles(params) {
|
|
82
|
+
export async function downloadAndSaveFiles(params) {
|
|
84
83
|
const { fileUrls, config, runtime, wsClient } = params;
|
|
85
84
|
const core = getWeComRuntime();
|
|
86
85
|
const mediaList = [];
|
|
@@ -121,6 +120,3 @@ async function downloadAndSaveFiles(params) {
|
|
|
121
120
|
}
|
|
122
121
|
return mediaList;
|
|
123
122
|
}
|
|
124
|
-
|
|
125
|
-
export { downloadAndSaveFiles, downloadAndSaveImages };
|
|
126
|
-
//# sourceMappingURL=media-handler.js.map
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { loadOutboundMediaFromUrl, detectMime } from './openclaw-compat.js';
|
|
2
|
-
import { ABSOLUTE_MAX_BYTES, VOICE_MAX_BYTES, VIDEO_MAX_BYTES, IMAGE_MAX_BYTES } from './const.js';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* 企业微信出站媒体上传工具模块
|
|
6
3
|
*
|
|
@@ -9,6 +6,8 @@ import { ABSOLUTE_MAX_BYTES, VOICE_MAX_BYTES, VIDEO_MAX_BYTES, IMAGE_MAX_BYTES }
|
|
|
9
6
|
* - 检测 MIME 类型并映射为企微媒体类型
|
|
10
7
|
* - 文件大小检查与降级策略
|
|
11
8
|
*/
|
|
9
|
+
import { loadOutboundMediaFromUrl, detectMime, } from "./openclaw-compat.js";
|
|
10
|
+
import { IMAGE_MAX_BYTES, VIDEO_MAX_BYTES, VOICE_MAX_BYTES, ABSOLUTE_MAX_BYTES, } from "./const.js";
|
|
12
11
|
// ============================================================================
|
|
13
12
|
// MIME → 企微媒体类型映射
|
|
14
13
|
// ============================================================================
|
|
@@ -18,7 +17,7 @@ import { ABSOLUTE_MAX_BYTES, VOICE_MAX_BYTES, VIDEO_MAX_BYTES, IMAGE_MAX_BYTES }
|
|
|
18
17
|
* @param mimeType - MIME 类型字符串
|
|
19
18
|
* @returns 企微媒体类型
|
|
20
19
|
*/
|
|
21
|
-
function detectWeComMediaType(mimeType) {
|
|
20
|
+
export function detectWeComMediaType(mimeType) {
|
|
22
21
|
const mime = mimeType.toLowerCase();
|
|
23
22
|
// 图片类型
|
|
24
23
|
if (mime.startsWith("image/")) {
|
|
@@ -50,7 +49,7 @@ function detectWeComMediaType(mimeType) {
|
|
|
50
49
|
* @param mediaLocalRoots - 允许读取本地文件的安全白名单目录
|
|
51
50
|
* @returns 解析后的媒体文件信息
|
|
52
51
|
*/
|
|
53
|
-
async function resolveMediaFile(mediaUrl, mediaLocalRoots) {
|
|
52
|
+
export async function resolveMediaFile(mediaUrl, mediaLocalRoots) {
|
|
54
53
|
// 使用兼容层加载媒体文件(优先 SDK,不可用时 fallback)
|
|
55
54
|
// 传入足够大的 maxBytes,由我们自己在后续步骤做大小检查
|
|
56
55
|
const result = await loadOutboundMediaFromUrl(mediaUrl, {
|
|
@@ -98,7 +97,7 @@ const VOICE_SUPPORTED_MIMES = new Set(["audio/amr"]);
|
|
|
98
97
|
* @param contentType - 文件的 MIME 类型(用于语音格式校验)
|
|
99
98
|
* @returns 大小检查结果
|
|
100
99
|
*/
|
|
101
|
-
function applyFileSizeLimits(fileSize, detectedType, contentType) {
|
|
100
|
+
export function applyFileSizeLimits(fileSize, detectedType, contentType) {
|
|
102
101
|
const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
|
|
103
102
|
// 先检查绝对上限(20MB)
|
|
104
103
|
if (fileSize > ABSOLUTE_MAX_BYTES) {
|
|
@@ -150,6 +149,9 @@ function applyFileSizeLimits(fileSize, detectedType, contentType) {
|
|
|
150
149
|
};
|
|
151
150
|
}
|
|
152
151
|
break;
|
|
152
|
+
case "file":
|
|
153
|
+
// file 类型在绝对上限内即可
|
|
154
|
+
break;
|
|
153
155
|
}
|
|
154
156
|
// 无需降级
|
|
155
157
|
return {
|
|
@@ -227,7 +229,7 @@ function mimeToExtension(mime) {
|
|
|
227
229
|
* 媒体消息统一走 aibot_send_msg 主动发送,避免多文件场景下 reqId 只能用一次的问题。
|
|
228
230
|
* channel.ts 的 sendMedia 和 monitor.ts 的 deliver 回调都使用此函数。
|
|
229
231
|
*/
|
|
230
|
-
async function uploadAndSendMedia(options) {
|
|
232
|
+
export async function uploadAndSendMedia(options) {
|
|
231
233
|
const { wsClient, mediaUrl, chatId, mediaLocalRoots, log, errorLog } = options;
|
|
232
234
|
try {
|
|
233
235
|
// 1. 加载媒体文件
|
|
@@ -274,6 +276,58 @@ async function uploadAndSendMedia(options) {
|
|
|
274
276
|
};
|
|
275
277
|
}
|
|
276
278
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
279
|
+
/**
|
|
280
|
+
* 被动回复媒体上传+发送流程
|
|
281
|
+
*
|
|
282
|
+
* 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → replyMedia
|
|
283
|
+
* 通过 aibot_respond_msg 被动回复通道发送媒体消息,可以覆盖之前的 THINKING_MESSAGE。
|
|
284
|
+
*
|
|
285
|
+
* 适用场景:回包只有媒体没有文本时,第一个媒体文件用此方法发送以清理 thinking 状态。
|
|
286
|
+
*/
|
|
287
|
+
export async function uploadAndReplyMedia(options) {
|
|
288
|
+
const { wsClient, mediaUrl, frame, mediaLocalRoots, log, errorLog } = options;
|
|
289
|
+
try {
|
|
290
|
+
// 1. 加载媒体文件
|
|
291
|
+
log?.(`[wecom] Uploading media (reply mode): url=${mediaUrl}`);
|
|
292
|
+
const media = await resolveMediaFile(mediaUrl, mediaLocalRoots);
|
|
293
|
+
// 2. 检测企微媒体类型
|
|
294
|
+
const detectedType = detectWeComMediaType(media.contentType);
|
|
295
|
+
// 3. 文件大小检查与降级策略
|
|
296
|
+
const sizeCheck = applyFileSizeLimits(media.buffer.length, detectedType, media.contentType);
|
|
297
|
+
if (sizeCheck.shouldReject) {
|
|
298
|
+
errorLog?.(`[wecom] Media rejected: ${sizeCheck.rejectReason}`);
|
|
299
|
+
return {
|
|
300
|
+
ok: false,
|
|
301
|
+
rejected: true,
|
|
302
|
+
rejectReason: sizeCheck.rejectReason,
|
|
303
|
+
finalType: sizeCheck.finalType,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const finalType = sizeCheck.finalType;
|
|
307
|
+
// 4. 分片上传获取 media_id
|
|
308
|
+
const uploadResult = await wsClient.uploadMedia(media.buffer, {
|
|
309
|
+
type: finalType,
|
|
310
|
+
filename: media.fileName,
|
|
311
|
+
});
|
|
312
|
+
log?.(`[wecom] Media uploaded: media_id=${uploadResult.media_id}, type=${finalType}`);
|
|
313
|
+
// 5. 通过 aibot_respond_msg 被动回复发送媒体消息(会覆盖 THINKING_MESSAGE)
|
|
314
|
+
const result = await wsClient.replyMedia(frame, finalType, uploadResult.media_id);
|
|
315
|
+
const messageId = result?.headers?.req_id ?? `wecom-reply-media-${Date.now()}`;
|
|
316
|
+
log?.(`[wecom] Media sent via replyMedia (passive reply): type=${finalType}`);
|
|
317
|
+
return {
|
|
318
|
+
ok: true,
|
|
319
|
+
messageId,
|
|
320
|
+
finalType,
|
|
321
|
+
downgraded: sizeCheck.downgraded,
|
|
322
|
+
downgradeNote: sizeCheck.downgradeNote,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
const errMsg = String(err);
|
|
327
|
+
errorLog?.(`[wecom] Failed to upload/reply media: url=${mediaUrl}, error=${errMsg}`);
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
error: errMsg,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -50,7 +50,7 @@ function buildTemplateCardEventText(body) {
|
|
|
50
50
|
* 解析消息内容(支持单条消息、图文混排、事件回调和引用消息)
|
|
51
51
|
* @returns 提取的文本数组、图片URL数组和引用消息内容
|
|
52
52
|
*/
|
|
53
|
-
function parseMessageContent(body) {
|
|
53
|
+
export function parseMessageContent(body) {
|
|
54
54
|
const textParts = [];
|
|
55
55
|
const imageUrls = [];
|
|
56
56
|
const imageAesKeys = new Map();
|
|
@@ -127,6 +127,3 @@ function parseMessageContent(body) {
|
|
|
127
127
|
}
|
|
128
128
|
return { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent };
|
|
129
129
|
}
|
|
130
|
-
|
|
131
|
-
export { parseMessageContent };
|
|
132
|
-
//# sourceMappingURL=message-parser.js.map
|
|
@@ -32,3 +32,21 @@ export declare function sendWeComReply(params: {
|
|
|
32
32
|
/** 指定 streamId,用于流式回复时保持相同的 streamId */
|
|
33
33
|
streamId?: string;
|
|
34
34
|
}): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* 非阻塞流式文本回复
|
|
37
|
+
*
|
|
38
|
+
* 基于 SDK 的 replyStreamNonBlocking 方法:
|
|
39
|
+
* - 如果上一条同 reqId 的消息尚未收到 ack,则跳过本次发送(返回 'skipped'),
|
|
40
|
+
* 避免流式中间帧排队积压导致延迟。
|
|
41
|
+
* - finish=true 的最终帧不受此限制,始终保证发送。
|
|
42
|
+
*
|
|
43
|
+
* @returns 'skipped' 表示被跳过,否则返回 streamId
|
|
44
|
+
*/
|
|
45
|
+
export declare function sendWeComReplyNonBlocking(params: {
|
|
46
|
+
wsClient: WSClient;
|
|
47
|
+
frame: WsFrame;
|
|
48
|
+
text: string;
|
|
49
|
+
runtime: RuntimeEnv;
|
|
50
|
+
streamId: string;
|
|
51
|
+
finish?: boolean;
|
|
52
|
+
}): Promise<string | 'skipped'>;
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
import { generateReqId } from '@wecom/aibot-node-sdk';
|
|
2
|
-
import { REPLY_SEND_TIMEOUT_MS } from './const.js';
|
|
3
|
-
import { withTimeout } from './timeout.js';
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* 企业微信消息发送模块
|
|
7
3
|
*
|
|
8
4
|
* 负责通过 WSClient 发送回复消息,包含超时保护
|
|
9
5
|
*/
|
|
6
|
+
import { generateReqId } from "@wecom/aibot-node-sdk";
|
|
7
|
+
import { REPLY_SEND_TIMEOUT_MS } from "./const.js";
|
|
8
|
+
import { withTimeout } from "./timeout.js";
|
|
10
9
|
// ============================================================================
|
|
11
10
|
// 流式过期错误(errcode 846608)
|
|
12
11
|
// ============================================================================
|
|
13
12
|
/** 流式回复超时错误码(>6分钟未更新,服务端拒绝继续流式更新) */
|
|
14
|
-
const STREAM_EXPIRED_ERRCODE = 846608;
|
|
13
|
+
export const STREAM_EXPIRED_ERRCODE = 846608;
|
|
15
14
|
/**
|
|
16
15
|
* 流式回复过期错误
|
|
17
16
|
* 当服务端返回 errcode=846608 时抛出,表示流式消息已超过6分钟无法更新,
|
|
18
17
|
* 调用方需降级为主动发送(sendMessage)方式回复。
|
|
19
18
|
*/
|
|
20
|
-
class StreamExpiredError extends Error {
|
|
19
|
+
export class StreamExpiredError extends Error {
|
|
20
|
+
errcode = STREAM_EXPIRED_ERRCODE;
|
|
21
21
|
constructor(message) {
|
|
22
22
|
super(message ?? `Stream message update expired (errcode=${STREAM_EXPIRED_ERRCODE})`);
|
|
23
|
-
this.errcode = STREAM_EXPIRED_ERRCODE;
|
|
24
23
|
this.name = "StreamExpiredError";
|
|
25
24
|
}
|
|
26
25
|
}
|
|
@@ -33,7 +32,7 @@ class StreamExpiredError extends Error {
|
|
|
33
32
|
*
|
|
34
33
|
* @returns messageId (streamId)
|
|
35
34
|
*/
|
|
36
|
-
async function sendWeComReply(params) {
|
|
35
|
+
export async function sendWeComReply(params) {
|
|
37
36
|
const { wsClient, frame, text, runtime, finish = true, streamId: existingStreamId } = params;
|
|
38
37
|
if (!text) {
|
|
39
38
|
return "";
|
|
@@ -80,6 +79,41 @@ async function sendWeComReply(params) {
|
|
|
80
79
|
runtime.log?.(`[plugin -> server] streamId=${streamId}, finish=${finish}`);
|
|
81
80
|
return streamId;
|
|
82
81
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// 非阻塞流式发送(用于 onPartialReply 场景)
|
|
84
|
+
// ============================================================================
|
|
85
|
+
/**
|
|
86
|
+
* 非阻塞流式文本回复
|
|
87
|
+
*
|
|
88
|
+
* 基于 SDK 的 replyStreamNonBlocking 方法:
|
|
89
|
+
* - 如果上一条同 reqId 的消息尚未收到 ack,则跳过本次发送(返回 'skipped'),
|
|
90
|
+
* 避免流式中间帧排队积压导致延迟。
|
|
91
|
+
* - finish=true 的最终帧不受此限制,始终保证发送。
|
|
92
|
+
*
|
|
93
|
+
* @returns 'skipped' 表示被跳过,否则返回 streamId
|
|
94
|
+
*/
|
|
95
|
+
export async function sendWeComReplyNonBlocking(params) {
|
|
96
|
+
const { wsClient, frame, text, runtime, streamId, finish = false } = params;
|
|
97
|
+
if (!text) {
|
|
98
|
+
return 'skipped';
|
|
99
|
+
}
|
|
100
|
+
if (!wsClient.isConnected) {
|
|
101
|
+
return 'skipped';
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const result = await wsClient.replyStreamNonBlocking(frame, streamId, text, finish);
|
|
105
|
+
if (result === 'skipped') {
|
|
106
|
+
return 'skipped';
|
|
107
|
+
}
|
|
108
|
+
return streamId;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
// 服务端返回 846608:流式消息超过6分钟无法更新,需降级为主动发送
|
|
112
|
+
const errMsg = err?.errmsg || err?.message || String(err);
|
|
113
|
+
if (err?.errcode === STREAM_EXPIRED_ERRCODE ||
|
|
114
|
+
errMsg.includes(String(STREAM_EXPIRED_ERRCODE))) {
|
|
115
|
+
throw new StreamExpiredError(errMsg);
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|