@tencentcloud/sdk-mcp 1.6.8 → 1.7.1

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +59 -35
  3. package/knowledge/build-index.js +28 -4
  4. package/knowledge/chunker.js +82 -14
  5. package/knowledge/chunking-policy.js +100 -0
  6. package/knowledge/doc-url-resolver.js +70 -19
  7. package/knowledge/frontmatter-meta.js +167 -0
  8. package/knowledge/path-meta.js +28 -6
  9. package/knowledge/search/exact-lookup.js +126 -125
  10. package/knowledge/search/filter-pool.js +12 -29
  11. package/knowledge/search/framework-fallback.js +22 -0
  12. package/knowledge/search/intent-router.js +29 -0
  13. package/knowledge/search/l1-enrich.js +159 -0
  14. package/knowledge/search/query-normalizer.js +63 -3
  15. package/knowledge/search/search-core.js +410 -0
  16. package/knowledge/search/search-knowledge.js +419 -377
  17. package/knowledge/search/search-message.js +88 -0
  18. package/knowledge/search/search-pass.js +428 -0
  19. package/knowledge/search/search-types.js +1 -0
  20. package/knowledge/search/url-row-idf.js +120 -0
  21. package/knowledge/search/url-row-scorer.js +337 -0
  22. package/package.json +3 -1
  23. package/resource/index/manifest.json +1 -1
  24. package/resource/integration/callkit/preset/flutter/integration.md +7 -3
  25. package/resource/integration/callkit/preset/ios/integration.md +1 -0
  26. package/resource/integration/chat/preset/android/integration.md +1 -0
  27. package/resource/integration/chat/preset/flutter/integration.md +111 -10
  28. package/resource/integration/chat/preset/ios/integration.md +1 -0
  29. package/resource/integration/chat/preset/react/integration.md +1 -0
  30. package/resource/integration/chat/preset/uni-app/integration.md +1 -0
  31. package/resource/integration/chat/preset/vue/integration.md +1 -0
  32. package/resource/integration/livekit/preset/live/android/prepare-integration.md +1 -0
  33. package/resource/integration/livekit/preset/live/vue/live-player-desk-integration.md +12 -10
  34. package/resource/integration/livekit/preset/live/vue/live-pusher-integration.md +14 -14
  35. package/resource/integration/roomkit/preset/vue/integration.md +1 -0
  36. package/resource/knowledge/callkit/best-practice/callkit-miniprogram/uniapp-shrink.md +64 -0
  37. package/resource/knowledge/callkit/best-practice/common/call-autoplay-blocked.md +81 -0
  38. package/resource/knowledge/callkit/best-practice/common/call-message-in-chat.md +84 -0
  39. package/resource/knowledge/callkit/best-practice/common/call-vs-calls.md +114 -0
  40. package/resource/knowledge/callkit/best-practice/common/callengine-call-flow.md +128 -0
  41. package/resource/knowledge/callkit/best-practice/common/callengine-log-level.md +72 -0
  42. package/resource/knowledge/callkit/best-practice/common/callkit-get-engine-instance.md +87 -0
  43. package/resource/knowledge/callkit/best-practice/common/callkit-get-roomid.md +83 -0
  44. package/resource/knowledge/callkit/best-practice/common/callkit-limitations.md +37 -0
  45. package/resource/knowledge/callkit/best-practice/common/callkit-user-register-and-kicked.md +103 -0
  46. package/resource/knowledge/callkit/core-sdk/android/answer-call.md +1 -0
  47. package/resource/knowledge/callkit/core-sdk/android/make-call.md +1 -0
  48. package/resource/knowledge/callkit/core-sdk/flutter/answer-call.md +1 -0
  49. package/resource/knowledge/callkit/core-sdk/flutter/make-call.md +1 -0
  50. package/resource/knowledge/callkit/core-sdk/ios/answer-call.md +1 -0
  51. package/resource/knowledge/callkit/core-sdk/ios/make-call.md +1 -0
  52. package/resource/knowledge/callkit/core-sdk/ios/offline-push.md +1 -1
  53. package/resource/knowledge/callkit/core-sdk/uni-app/answer-call.md +1 -0
  54. package/resource/knowledge/callkit/core-sdk/uni-app/make-call.md +1 -0
  55. package/resource/knowledge/callkit/faq/flutter.md +1 -0
  56. package/resource/knowledge/callkit/faq/uniapp-app.md +1 -0
  57. package/resource/knowledge/callkit/preset/react/api.md +1 -150
  58. package/resource/knowledge/callkit/preset/vue/api.md +1 -38
  59. package/resource/knowledge/chat/best-practice/common/avchatroom-message-loss-diagnosis.md +71 -0
  60. package/resource/knowledge/chat/best-practice/common/group-conversation-cleanup-on-system-notice.md +67 -0
  61. package/resource/knowledge/chat/best-practice/common/image-message-url-lifecycle.md +63 -0
  62. package/resource/knowledge/chat/best-practice/common/login-repeat-2025-diagnosis.md +65 -0
  63. package/resource/knowledge/chat/best-practice/common/message-read-report-and-receipt.md +72 -0
  64. package/resource/knowledge/chat/best-practice/common/message-receive-event.md +70 -0
  65. package/resource/knowledge/chat/best-practice/common/message-risk-error-and-render.md +69 -0
  66. package/resource/knowledge/chat/best-practice/common/message-unread-count.md +71 -0
  67. package/resource/knowledge/chat/best-practice/miniprogram/audio-message-send.md +118 -0
  68. package/resource/knowledge/chat/best-practice/miniprogram/login-timeout-2801-diagnosis.md +67 -0
  69. package/resource/knowledge/chat/best-practice/miniprogram/media-upload-fail-diagnosis.md +68 -0
  70. package/resource/knowledge/chat/best-practice/web/audio-message-send.md +123 -0
  71. package/resource/knowledge/chat/preset/android/features/conversation-list.md +1 -0
  72. package/resource/knowledge/chat/preset/android/features/language.md +1 -0
  73. package/resource/knowledge/chat/preset/android/features/message-input.md +1 -0
  74. package/resource/knowledge/chat/preset/android/features/message-list.md +1 -0
  75. package/resource/knowledge/chat/preset/android-view/integration.md +1 -0
  76. package/resource/knowledge/chat/preset/flutter/features/conversation-list.md +1 -0
  77. package/resource/knowledge/chat/preset/flutter/features/message-input.md +1 -0
  78. package/resource/knowledge/chat/preset/flutter/features/message-list.md +1 -0
  79. package/resource/knowledge/chat/preset/ios/features/conversation-list.md +1 -0
  80. package/resource/knowledge/chat/preset/ios/features/message-input.md +1 -0
  81. package/resource/knowledge/chat/preset/ios/features/message-list.md +1 -0
  82. package/resource/knowledge/chat/preset/ios-uikit/integration.md +1 -0
  83. package/resource/knowledge/chat/preset/react/features/conversation-preview.md +1 -0
  84. package/resource/knowledge/chat/preset/react/features/message-input.md +61 -3
  85. package/resource/knowledge/chat/preset/react/features/message-list.md +7 -0
  86. package/resource/knowledge/chat/preset/vue/features/message-input.md +58 -5
  87. package/resource/knowledge/chat/preset/vue/features/message-list.md +7 -0
  88. package/resource/knowledge/chat/urls/product.md +8 -8
  89. package/resource/knowledge/chat/urls/restapi.md +3 -3
  90. package/resource/knowledge/chat/urls/sdk/android.md +5 -5
  91. package/resource/knowledge/chat/urls/sdk/flutter.md +5 -5
  92. package/resource/knowledge/chat/urls/sdk/ios.md +5 -5
  93. package/resource/knowledge/chat/urls/sdk/web.md +8 -8
  94. package/resource/knowledge/chat/urls/webhook.md +11 -17
  95. package/resource/knowledge/livekit/best-practice/android/voice-room-anchor-start.md +124 -0
  96. package/resource/knowledge/livekit/best-practice/common/admin-take-seat.md +73 -0
  97. package/resource/knowledge/livekit/best-practice/common/audience-leave-live.md +81 -0
  98. package/resource/knowledge/livekit/best-practice/common/voice-room-no-audio-troubleshoot.md +100 -0
  99. package/resource/knowledge/livekit/best-practice/common/voice-room-seat-list-change.md +65 -0
  100. package/resource/knowledge/livekit/best-practice/flutter/voice-room-anchor-start.md +126 -0
  101. package/resource/knowledge/livekit/best-practice/ios/voice-room-anchor-start.md +130 -0
  102. package/resource/knowledge/push/config/harmonyos.md +1 -1
  103. package/resource/knowledge/push/config/ios.md +2 -2
  104. package/resource/knowledge/push/config/react-native.md +1 -0
  105. package/resource/knowledge/push/config/unity.md +1 -0
  106. package/resource/knowledge/push/config/unreal-engine.md +1 -0
  107. package/resource/knowledge/push/integration/android.md +17 -16
  108. package/resource/knowledge/push/integration/donut.md +2 -2
  109. package/resource/knowledge/push/integration/flutter.md +4 -3
  110. package/resource/knowledge/push/integration/harmonyos.md +1 -1
  111. package/resource/knowledge/push/integration/ios.md +4 -3
  112. package/resource/knowledge/push/integration/react-native.md +1 -1
  113. package/resource/knowledge/push/integration/uni-app.md +1 -0
  114. package/resource/knowledge/push/integration/unity.md +4 -4
  115. package/resource/knowledge/push/integration/unreal-engine.md +37 -35
  116. package/resource/knowledge/roomkit/best-practice/common/room-ghost-member.md +75 -0
  117. package/resource/knowledge/roomkit/best-practice/common/room-limit-and-multi.md +88 -0
  118. package/resource/knowledge/roomkit/best-practice/common/room-screen-share-preview.md +87 -0
  119. package/resource/knowledge/roomkit/best-practice/common/room-status-after-all-leave.md +73 -0
  120. package/resource/knowledge/roomkit/best-practice/vue/room-uikit-vs-atomicx.md +115 -0
  121. package/resource/knowledge/roomkit/core-sdk/android/call-user-to-room.md +1 -0
  122. package/resource/knowledge/roomkit/core-sdk/android/room-participant-state.md +1 -0
  123. package/resource/knowledge/roomkit/core-sdk/android/room-state.md +1 -0
  124. package/resource/knowledge/roomkit/core-sdk/android/schedule-room.md +1 -0
  125. package/resource/knowledge/roomkit/core-sdk/flutter/call-user-to-room.md +1 -0
  126. package/resource/knowledge/roomkit/core-sdk/flutter/room-participant-state.md +1 -0
  127. package/resource/knowledge/roomkit/core-sdk/flutter/room-state.md +1 -0
  128. package/resource/knowledge/roomkit/core-sdk/flutter/schedule-room.md +1 -0
  129. package/resource/knowledge/roomkit/core-sdk/ios/call-user-to-room.md +1 -0
  130. package/resource/knowledge/roomkit/core-sdk/ios/room-participant-state.md +1 -0
  131. package/resource/knowledge/roomkit/core-sdk/ios/room-state.md +1 -0
  132. package/resource/knowledge/roomkit/core-sdk/ios/schedule-room.md +1 -0
  133. package/resource/knowledge/roomkit/core-sdk/vue/room-view.md +1 -0
  134. package/resource/knowledge/roomkit/preset/react/integration.md +1 -0
  135. package/resource/knowledge/rtcengine/web/plugin/30-advanced-cross-room-link.md +2 -0
  136. package/resource/knowledge/rtcengine/web/plugin/39-advanced-video-decoder.md +2 -0
  137. package/resource/knowledge/trtc/android/TRTCCloud.md +1 -0
  138. package/resource/knowledge/trtc/c++/ITRTCCloud.md +1 -0
  139. package/resource/knowledge/trtc/ios/TRTCCloud.md +1 -0
  140. package/server.js +9 -4
  141. package/streamable-http.js +19 -54
  142. package/tools/common/search_trtc_knowledge.js +24 -12
  143. package/tools/definitions/finalize_answer_def.js +2 -2
  144. package/tools/definitions/integration-boundary.js +2 -1
  145. package/tools/definitions/search_trtc_knowledge_def.js +5 -3
  146. package/utils/fetch-doc-content.js +198 -0
  147. package/utils/preflight-route.js +197 -0
  148. package/utils/report-cls-client.js +1 -1
  149. package/utils/report-es-client.js +1 -1
  150. package/cursor-setting.png +0 -0
  151. package/mcp-server-prompt.png +0 -0
  152. package/resource/knowledge/callkit/best-practice/web/callengine-invitation.md +0 -80
  153. package/resource/knowledge/callkit/best-practice/web/callengine-log-control.md +0 -20
  154. package/resource/knowledge/callkit/best-practice/web/callkit-call-vs-calls.md +0 -116
  155. package/resource/knowledge/callkit/preset/react/uikit-api-reference.md +0 -469
  156. package/resource/knowledge/callkit/preset/vue/uikit-api-reference.md +0 -581
  157. package/resource/knowledge/callkit/preset/web/api.md +0 -79
  158. package/resource/knowledge/chat/best-practice/web/image-message-url-handling.md +0 -16
@@ -1,6 +1,8 @@
1
1
  const TOOL_DESCRIPTION = `
2
2
  凡 Tencent RTC 文档咨询(Chat/Call/Live/Room/Push/Web TRTC/Native TRTC):FAQ、错误码、API、组件、feature、插件、集成查阅、排障 → MUST 先本工具。禁止 web_search。写接入代码 → get_*_integration。
3
3
 
4
+ **prompt 必须原样保留用户问题**:禁止扩写、发散、改写、补充 API 用法/场景描述。用户问什么就传什么原文。若需补充语义(如 API/事件/具体实现),用 types/frameworks/product 结构化参数表达,或另行搜索。
5
+
4
6
  **product**(能推断 MUST 传;不能确定先问用户):
5
7
  push:TIMPush/推送/厂商/FCM/华为/小米/OPPO(≠chat)| chat:会话/TUIKit/IM/主题 | call:CallKit | live:LiveKit | room:RoomKit/会议
6
8
  rtcengine:Web TRTC | native_trtc_sdk:TRTCCloud | 跨域:["push","chat"]
@@ -19,12 +21,12 @@ types含api/component/feature/plugin/sdk/integration→MUST | API/组件/enterRo
19
21
 
20
22
  **L0/L1**:本地fragments仅基于片段答;chat/urls/须fetch官网URL,禁止臆造。
21
23
 
22
- **返回**:status、confidence、fragments(doc_urls)、meta.references/exact_hits、top_score/second_score、queryID、message、meta.answer_contract。答案须严格遵守 answer_contract(结论先行、用户可执行、未确认显式化);正文不展开证据链,且禁止输出“命中片段/检索结果/置信度”等系统话术,但末尾需保留“参考文档”并列出 references/doc_urls。最终回复前 MUST 调用 finalize_answer;明确 follow-up 反馈后调用 submit_feedback。
24
+ **返回**:status、confidence、fragments(doc_urls)、meta.references、queryID、message、meta.answer_contract。答案遵守 answer_contract(完整输出、用户可执行、未确认显式化),禁止精简,禁止输出"命中片段/检索结果/置信度/分数"等系统话术,但末尾需保留"参考文档"并列出 references/doc_urls。最终回复前 MUST 调用 finalize_answer;明确 follow-up 反馈后调用 submit_feedback。
23
25
  `;
24
26
  const PARAMETER_DESCRIPTIONS = {
25
- prompt: '用户原始问题全文,保留完整上下文,不要压缩成关键词摘要。',
27
+ prompt: '用户原始问题原文,MUST 原样透传,禁止扩写/发散/补充(如补 API 用法、场景、追问)。用户没说的不写进去;需补充语义用 types/frameworks 等结构化参数表达。',
26
28
  product: '产品数组:chat|call|live|room|push|rtcengine|native_trtc_sdk。能推断 MUST 传;push优先(TIMPush/推送→push,禁止误归chat)。live 常见关键词:LiveKit/直播/语聊房/TUILiveKit/AtomicXCore/开播/观看。不能确定先问用户。',
27
- frameworks: '平台数组:react|vue|web|miniprogram|android|ios|flutter|c++|uni-app|harmonyos|react-native|unity|unreal-engine|donut。支持传多个平台用于跨端检索;CallKit 中 TUICallKit 建议按 react/vue 区分检索,TUICallEngine/TUICallEvent 仍归 web;小程序用 miniprogram;harmonyos/react-native/unity/unreal/donut仅push适用。',
29
+ frameworks: '平台数组:react|vue|web|miniprogram|android|ios|flutter|c++|uni-app|harmonyos|react-native|unity|unreal-engine|donut。支持传多个平台用于跨端检索;CallKit 中 TUICallKit 建议按 react/vue 区分检索,TUICallEngine/TUICallEvent 仍归 web;小程序用 miniprogram;harmonyos/react-native/unity/unreal/donut仅push适用。若未传且问题依赖具体客户端平台,必须先调用 present_framework_choice 询问后重搜。',
28
30
  types: '默认不传;开放["faq"]。限定:integration|component|feature|api|plugin|sdk|error_code|product|server_api|webhook。component 与 feature 等价(同时匹配);发现型/罗列型问题("有哪些组件")MUST 省略 types。控制台/计费/产品能力配置→product(非feature)。勿主动窄传。',
29
31
  scenario: 'live|voice,仅live产品。',
30
32
  queryID: '首次\'\',后续同一咨询MUST透传(见server)。',
@@ -0,0 +1,198 @@
1
+ /**
2
+ * fetch-doc-content.ts
3
+ *
4
+ * 在命中 chat/urls L1 URL 映射行时,由 server 内部 fetch 官网目标文档并清洗成
5
+ * markdown 返回给 AI,避免 AI 自行联网抓取(不可靠、拿到的还是整页 HTML)。
6
+ *
7
+ * 设计要点:
8
+ * - 白名单域名:仅允许 fetch 我们自己的 urls 映射里出现的官方域名,防 SSRF。
9
+ * - Node 22 原生 fetch 会自动解 gzip(解决 cloud.tencent.com 返回 gzip 导致乱码的问题)。
10
+ * - 内部分流函数 fetch 按 hostname 选策略:
11
+ * - trtc.io(en 端 urls 主域名):`url + '.md'` 直拿 text/markdown,跳过 cheerio 清洗
12
+ * - 其他:cheerio + turndown 清洗 HTML(cloud.tencent.com / web.sdk.qcloud.com)
13
+ * - 内存缓存 + 超时 + 失败兜底(返回 null,不阻断原有检索)。
14
+ * - 对外函数 fetchDocContent 签名不变(l1-enrich 调用方无感知)。
15
+ * - 缓存 key 始终用原 URL(不带 .md 后缀),避免缓存 key 污染。
16
+ */
17
+ import * as cheerio from 'cheerio';
18
+ import TurndownService from 'turndown';
19
+ /**
20
+ * 白名单域名:只允许这些域名的文档页被 fetch。
21
+ * 与 zh/resource/knowledge/chat/urls/ 和 en/resource/knowledge/chat/urls/ 下的映射保持一致。
22
+ */
23
+ const ALLOWED_HOSTS = new Set([
24
+ 'cloud.tencent.com',
25
+ 'web.sdk.qcloud.com',
26
+ 'trtc.io',
27
+ ]);
28
+ /**
29
+ * 走"url + '.md' 直拿 markdown"策略的域名。
30
+ * 这类域名提供原生 markdown 视图(content-type=text/markdown),
31
+ * 比 cheerio+turndown 清洗 HTML 更干净、token 更省。
32
+ */
33
+ const DIRECT_MD_HOSTS = new Set([
34
+ 'trtc.io',
35
+ ]);
36
+ const REQUEST_TIMEOUT_MS = 15000;
37
+ /** 默认截断长度(字符),超过则按段落截断 */
38
+ const DEFAULT_MAX_CHARS = 50000;
39
+ /** 内存缓存:url -> markdown,避免同一咨询/相近请求重复打官网 */
40
+ const cache = new Map();
41
+ /** 判断 URL 是否命中白名单域名 */
42
+ function isAllowedUrl(url) {
43
+ try {
44
+ const { hostname } = new URL(url);
45
+ return ALLOWED_HOSTS.has(hostname);
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ /**
52
+ * 从 HTML 中提取正文容器(cheerio 按域名选择稳定容器)。
53
+ * 找不到时回退到更宽泛的选择器;仍找不到返回 null,交由调用方退化为整页清洗。
54
+ */
55
+ function extractArticleSelector(hostname) {
56
+ if (hostname === 'cloud.tencent.com') {
57
+ // 腾讯云文档:正文在 #docArticleContent 内(SSR 直出),依次回退
58
+ return ['#docArticleContent', '#doc-slate-root', '.J-markdown-box'];
59
+ }
60
+ if (hostname === 'web.sdk.qcloud.com') {
61
+ // JSDoc 静态文档:正文在 #main 的 article 内
62
+ return ['#main article', '#main', 'article'];
63
+ }
64
+ return [];
65
+ }
66
+ /** 使用 turndown 将选中的 HTML 节点转成 markdown */
67
+ function convertToMarkdown(html) {
68
+ const turndown = new TurndownService({
69
+ headingStyle: 'atx', // ## 风格标题
70
+ codeBlockStyle: 'fenced', // ``` 围栏代码块
71
+ bulletListMarker: '-',
72
+ });
73
+ turndown.remove(['script', 'style', 'nav', 'noscript', 'iframe']);
74
+ let md = turndown.turndown(html);
75
+ md = md
76
+ .replace(/[ \t]+\n/g, '\n')
77
+ .replace(/\n{3,}/g, '\n\n')
78
+ .trim();
79
+ return md;
80
+ }
81
+ /** 按段落截断到 maxChars */
82
+ function truncateMarkdown(md, maxChars) {
83
+ if (md.length <= maxChars)
84
+ return md;
85
+ const head = md.slice(0, maxChars);
86
+ const lastBreak = head.lastIndexOf('\n\n');
87
+ return `${head.slice(0, lastBreak > 0 ? lastBreak : maxChars)}\n…[已截断]`;
88
+ }
89
+ /**
90
+ * 公共 fetch helper:带 timeout + 错误吞咽,把 Response 交给 handle 解析。
91
+ * 用 globalThis.fetch 避免与下方内部分流函数 fetch 同名冲突。
92
+ */
93
+ async function fetchWithTimeout(url, handle) {
94
+ const controller = new AbortController();
95
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
96
+ try {
97
+ const res = await globalThis.fetch(url, {
98
+ signal: controller.signal,
99
+ headers: {
100
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
101
+ // 加 text/markdown 偏好,让 trtc.io 等支持 .md 后缀的站点优先返回 markdown
102
+ 'Accept': 'text/markdown,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
103
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
104
+ },
105
+ // Node fetch 自动处理 gzip/deflate/br
106
+ redirect: 'follow',
107
+ });
108
+ if (!res.ok)
109
+ return null;
110
+ return await handle(res);
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ finally {
116
+ clearTimeout(timer);
117
+ }
118
+ }
119
+ /** cheerio+turndown 路径:处理 cloud.tencent.com / web.sdk.qcloud.com 等 HTML 文档 */
120
+ async function fetchAndConvert(url, maxChars) {
121
+ return fetchWithTimeout(url, async (res) => {
122
+ const html = await res.text();
123
+ const { hostname } = new URL(url);
124
+ // cheerio 定位正文容器
125
+ const $ = cheerio.load(html);
126
+ const selectors = extractArticleSelector(hostname);
127
+ let article = selectors.map((sel) => $(sel)).find((node) => node.length > 0);
128
+ if (!article || article.length === 0) {
129
+ // 找不到容器时退化为 body 清洗(会偏大,但不丢正文)
130
+ article = $('body');
131
+ }
132
+ const markdown = convertToMarkdown(article.html() ?? '');
133
+ if (!markdown)
134
+ return null;
135
+ const truncated = markdown.length > maxChars;
136
+ const final = truncated ? truncateMarkdown(markdown, maxChars) : markdown;
137
+ return {
138
+ markdown: final,
139
+ truncated,
140
+ htmlBytes: Buffer.byteLength(html, 'utf-8'),
141
+ };
142
+ });
143
+ }
144
+ /**
145
+ * direct-md 路径:trtc.io 提供 `url + '.md'` 视图,
146
+ * content-type=text/markdown,可直接消费,跳过 cheerio 清洗。
147
+ * 失败时(content-type 不对 / 404)返回 null,让调用方走 fallback 路径。
148
+ */
149
+ async function fetchDirectMarkdown(url, maxChars) {
150
+ const mdUrl = `${url}.md`;
151
+ return fetchWithTimeout(mdUrl, async (res) => {
152
+ const ct = res.headers.get('content-type') ?? '';
153
+ if (!/text\/markdown/i.test(ct))
154
+ return null;
155
+ const md = await res.text();
156
+ const truncated = md.length > maxChars;
157
+ return {
158
+ markdown: truncated ? truncateMarkdown(md, maxChars) : md,
159
+ truncated,
160
+ htmlBytes: 0,
161
+ };
162
+ });
163
+ }
164
+ /**
165
+ * 内部分流 fetch:按 hostname 选择策略。
166
+ * - DIRECT_MD_HOSTS 内的域名走 fetchDirectMarkdown;失败 fallback 到 fetchAndConvert。
167
+ * - 其他域名走 fetchAndConvert(cheerio+turndown)。
168
+ */
169
+ async function fetch(url, hostname, maxChars) {
170
+ if (DIRECT_MD_HOSTS.has(hostname)) {
171
+ const direct = await fetchDirectMarkdown(url, maxChars);
172
+ // 防御:.md 视图失败时(如 content-type 不对、404)fallback 到 cheerio 路径
173
+ return direct ?? fetchAndConvert(url, maxChars);
174
+ }
175
+ return fetchAndConvert(url, maxChars);
176
+ }
177
+ /**
178
+ * fetch 官网文档并清洗为 markdown。
179
+ * 命中白名单才请求;失败/超时/非白名单一律返回 null,不抛错。
180
+ */
181
+ export async function fetchDocContent(url, opts) {
182
+ if (!isAllowedUrl(url))
183
+ return null;
184
+ // 缓存 key 用原 URL(不带 .md),让 fetchDocContent 调用方透明
185
+ const cached = cache.get(url);
186
+ if (cached)
187
+ return { markdown: cached, truncated: false, htmlBytes: 0 };
188
+ const { hostname } = new URL(url);
189
+ const maxChars = opts?.maxChars ?? DEFAULT_MAX_CHARS;
190
+ const result = await fetch(url, hostname, maxChars);
191
+ if (result)
192
+ cache.set(url, result.markdown);
193
+ return result;
194
+ }
195
+ /** 清空内存缓存(测试/热更时用) */
196
+ export function clearFetchDocCache() {
197
+ cache.clear();
198
+ }
@@ -0,0 +1,197 @@
1
+ // ========== 信号分层(方案 §3,中英双语) ==========
2
+ // 产品词:产品线路标识
3
+ const PRODUCT_SIGNALS = {
4
+ call: /CallKit|TUICallKit|TUICallEngine|语音通话|视频通话|来电|去电|拨打|\b(?:voice|video)\s+calls?\b|incoming\s+calls?|outgoing\s+calls?|make\s+a\s+call|\bdial(?:ing)?\b/i,
5
+ live: /LiveKit|TUILiveKit|直播|语聊房|连麦|开播|观看直播|\blive\s+stream(?:ing)?\b|\bvoice\s+room\b|co-?host(?:ing)?|go\s+live|start\s+stream(?:ing)?|watch(?:ing)?\s+live|\bbroadcast(?:ing)?\b/i,
6
+ room: /RoomKit|会议|会中|会议室|屏幕分享|白板|\bconference\b|\bmeetings?\b|in-?meeting|meeting\s+room|screen\s+shar(?:e|ing)|white\s*board/i,
7
+ chat: /\bchat\b|\bim\b|会话|消息|TUIKit|ChatKit|\bconversation\b|\bmessaging\b|\binstant\s+messaging\b/i,
8
+ push: /TIMPush|推送|离线推送|FCM|APNs|华为|小米|OPPO|vivo|\bpush(?:\s+notification)?s?\b|offline\s+push|vendor\s+channel|\bHuawei\b|\bXiaomi\b|\bHonor\b/i,
9
+ };
10
+ // 平台词(纯运行形态,不含任何产品词)
11
+ const PLATFORM_NATIVE_SIGNAL = /\b(android|ios|flutter|c\+\+|native)\b|安卓\s*(sdk|端|原生)|iOS\s*(SDK|端|原生)|原生\s*(sdk|端)?|native\s+(?:sdk|client|app)|mobile\s+(?:sdk|native)/i;
12
+ const PLATFORM_WEB_SIGNAL = /\b(web|h5|html5|browser|webview)\b|浏览器|网页|Web端|H5端|网页端|web\s+(?:app|client|page)|in-?browser|mobile\s+browser|安卓\s*(浏览器|网页|H5|Web|webview)|iOS\s*(浏览器|网页|H5|Web|webview)|Android\s*(?:浏览器|网页|H5|Web|webview|browser|web)|iPad\s*(?:浏览器|网页|H5|Web|browser|web)|\b(Safari|Chrome|Edge|UC|QQ浏览器|QQ\s+Browser)\b/i;
13
+ // 平台+产品复合词(运行形态 + 产品线路标识)
14
+ const WEB_PRODUCT_PAIR_SIGNAL = /\bweb\s+(trtc|sdk|im|chat|roomkit)\b|\b(trtc|sdk|im|chat|roomkit)\s+web\b|trtc[-\s]?sdk|trtc\.js/i;
15
+ // API 词(客户端调用形态)
16
+ const NATIVE_TRTC_CLASS_SIGNAL = /\b(TRTCCloud|TRTCCloudListener|TRTCCloudDelegate|ITRTCCloud|TRTCTypeDef|TRTCCloudCallback|TXAudioEffectManager|TXBeautyManager|TXDeviceManager|TXLiteAVCode|TXLiveSDKTypeDef)\b/i;
17
+ const NATIVE_TRTC_METHOD_SIGNAL = /\b(?:TRTCCloud|ITRTCCloud|TX[A-Za-z0-9_]+)\s*(?:\.|::|->)\s*(?:set|get|start|stop|enable|disable)[A-Z][A-Za-z0-9_]+\b/i;
18
+ const WEB_TRTC_API_RE = /TRTC\.(setCurrentSpeaker|getMicrophoneList|getSpeakerList|getCameraList|isSupported|create)|trtc\.(startLocalAudio|startLocalVideo|updateLocalAudio|updateLocalVideo)/i;
19
+ const WEB_TRTC_TOKEN_RE = /^TRTC\.(setCurrentSpeaker|getMicrophoneList|getSpeakerList|getCameraList|isSupported|create)$/i;
20
+ // 协议级 TRTC 信号
21
+ const TRTC_SIGNAL = /\bTRTC\b|\bWebRTC\b|rtcengine|TRTCCloud|腾讯实时音视频/i;
22
+ // ========== 工具函数 ==========
23
+ function uniq(list) {
24
+ return [...new Set(list)];
25
+ }
26
+ function extractQueryApiTokens(prompt) {
27
+ const matches = prompt.match(/\b(?:TRTC|trtc)\.[A-Za-z_][A-Za-z0-9_]*\b/g) ?? [];
28
+ return uniq(matches.map((m) => m.replace(/^trtc\./i, 'TRTC.')));
29
+ }
30
+ function collectPromptSignals(prompt) {
31
+ const products = new Set();
32
+ if (PRODUCT_SIGNALS.call.test(prompt))
33
+ products.add('call');
34
+ if (PRODUCT_SIGNALS.live.test(prompt))
35
+ products.add('live');
36
+ if (PRODUCT_SIGNALS.room.test(prompt))
37
+ products.add('room');
38
+ if (PRODUCT_SIGNALS.chat.test(prompt))
39
+ products.add('chat');
40
+ if (PRODUCT_SIGNALS.push.test(prompt))
41
+ products.add('push');
42
+ if (TRTC_SIGNAL.test(prompt))
43
+ products.add('trtc');
44
+ // 平台 tokens:web signal 锁定 → 压制 native 单字(安卓/iOS 仅归 hostEnv);
45
+ // 真复合词(unreal/unity/harmonyos/react-native/flutter/c++)不受影响。
46
+ const platforms = [];
47
+ const webLocked = PLATFORM_WEB_SIGNAL.test(prompt) || WEB_PRODUCT_PAIR_SIGNAL.test(prompt);
48
+ if (webLocked) {
49
+ platforms.push('web');
50
+ }
51
+ const nativeSpecificHits = [];
52
+ if (/\b(?:unreal|UE[45]?|unreal-engine)\b/i.test(prompt))
53
+ nativeSpecificHits.push('unreal-engine');
54
+ if (/\b(?:unity)\b/i.test(prompt))
55
+ nativeSpecificHits.push('unity');
56
+ if (/\b(?:harmonyos|鸿蒙|HarmonyOS)\b/i.test(prompt))
57
+ nativeSpecificHits.push('harmonyos');
58
+ if (/\b(?:react-native)\b/i.test(prompt))
59
+ nativeSpecificHits.push('react-native');
60
+ if (/\b(?:flutter)\b/i.test(prompt))
61
+ nativeSpecificHits.push('flutter');
62
+ if (/\b(?:c\+\+)\b/i.test(prompt))
63
+ nativeSpecificHits.push('c++');
64
+ // 安卓/iOS 单字只在非 web-locked 时进 platforms(web-locked 时仅归 hostEnv)
65
+ if (!webLocked && /\b(?:ios|iphone|ipad|苹果)\b/i.test(prompt))
66
+ nativeSpecificHits.push('ios');
67
+ if (!webLocked && /\b(?:android|安卓)\b/i.test(prompt))
68
+ nativeSpecificHits.push('android');
69
+ // 真复合词(如 unity/unreal/flutter/harmonyos/react-native/c++)无条件 push;单字在 webLocked 时被压制
70
+ if (nativeSpecificHits.length > 0) {
71
+ platforms.push(...nativeSpecificHits);
72
+ }
73
+ else if (!webLocked && PLATFORM_NATIVE_SIGNAL.test(prompt)) {
74
+ // 仅 "native" 词,未指定细分 → 默认 android(webLocked 时不触发 fallback,避免被 \b(android|ios|...) 反向命中把 web 路径覆盖成 native)
75
+ platforms.push('android');
76
+ }
77
+ // Host env:仅用于标记,不进 platforms 归一化
78
+ let hostEnv = null;
79
+ if (/\bios\b|iphone|ipad|苹果/i.test(prompt))
80
+ hostEnv = 'ios';
81
+ else if (/\bandroid\b|安卓/i.test(prompt))
82
+ hostEnv = 'android';
83
+ const queryApiTokens = extractQueryApiTokens(prompt);
84
+ const hasWebTrtcApi = PLATFORM_WEB_SIGNAL.test(prompt)
85
+ || WEB_TRTC_API_RE.test(prompt)
86
+ || queryApiTokens.some((t) => WEB_TRTC_TOKEN_RE.test(t));
87
+ const hasNativeTrtcApi = NATIVE_TRTC_CLASS_SIGNAL.test(prompt) || NATIVE_TRTC_METHOD_SIGNAL.test(prompt);
88
+ return { products, platforms, hostEnv, hasWebTrtcApi, hasNativeTrtcApi, queryApiTokens };
89
+ }
90
+ function reconcileWithPrompt(promptValues, explicit) {
91
+ // 核心规则:prompt 是真值源,explicit 只在 prompt 为空时启用。
92
+ // 这条规则必须严格遵守——
93
+ // 「prompt 是用户问题表达出来的真实意图」,explicit 只在外部上下文(IDE/文件/历史)有补充价值,
94
+ // 但 prompt 既然明示了「Web」,就要按 Web 来,不要因为 explicit 多了 'android' 就把它"补"进来当成双路。
95
+ // 情形 1:prompt 真值存在 → 信任 prompt 真值
96
+ // - 即便 explicit 扩张(prompt 外多列了平台)→ 也只输出 prompt 真值(prompt_override=true 表示 agent 推理被忽略)
97
+ // - 即便 explicit 收缩(prompt 真值的子集)→ 也只输出 prompt 真值(避免丢失用户意图)
98
+ if (promptValues.length > 0) {
99
+ const promptSet = new Set(promptValues);
100
+ const sameAsExplicit = !!explicit?.length
101
+ && explicit.length === promptValues.length
102
+ && explicit.every((v) => promptSet.has(v));
103
+ return { values: uniq(promptValues), corrected: !sameAsExplicit };
104
+ }
105
+ // 情形 2:prompt 真值无平台信息 → 信任上游 agent 基于上下文(IDE/文件/历史)的推理
106
+ if (!promptValues.length) {
107
+ return { values: uniq(explicit ?? []), corrected: false };
108
+ }
109
+ return { values: [], corrected: false };
110
+ }
111
+ // ========== Phase 3: 单 SearchParams 构造 ==========
112
+ //
113
+ // 关键约束:**永远只返回单 primary**,consumer 只跑一次 searchKnowledge。
114
+ // multi-platform / multi-product 通过把 frameworks / product 设成数组交给 search core 处理。
115
+ // search core 的 filterPool.applyRankMultipliers 已原生支持多值归并。
116
+ function passthroughPlan(params, reason, env) {
117
+ return { primary: params, meta: { route_reason: reason, environment_downgraded: env } };
118
+ }
119
+ /** 单产品路径:product 是单值;frameworks 是数组(可能多平台共享一个 product) */
120
+ function singleProductPlan(params, product, frameworks, hostEnv, reason) {
121
+ const envDiff = hostEnv != null && !frameworks.includes(hostEnv);
122
+ return {
123
+ primary: { ...params, product: [product], frameworks },
124
+ meta: { route_reason: reason, environment_downgraded: envDiff },
125
+ };
126
+ }
127
+ /** TRC 路径:product 是数组(rtcengine/native_trtc_sdk 两个产品码,按 platforms 各命中其一);frameworks 也是数组 */
128
+ function trcMultiProductPlan(params, products, frameworks, reason, env) {
129
+ return {
130
+ primary: { ...params, product: products, frameworks },
131
+ meta: { route_reason: reason, environment_downgraded: env },
132
+ };
133
+ }
134
+ // ========== 主入口(基于 prompt 真值归一化) ==========
135
+ export function preflightRoute(params) {
136
+ const prompt = params.prompt ?? '';
137
+ const explicitFrameworks = params.frameworks?.length ? uniq(params.frameworks) : undefined;
138
+ const explicitProducts = params.product?.length ? uniq(params.product) : undefined;
139
+ // Phase 1
140
+ const signals = collectPromptSignals(prompt);
141
+ // —— 真跨主题(多产品信号)直接 passthrough
142
+ const nonTrtcProducts = [...signals.products].filter((p) => p !== 'trtc');
143
+ if (nonTrtcProducts.length > 1) {
144
+ return passthroughPlan(params, 'cross_product_passthrough', false);
145
+ }
146
+ // Phase 2: frameworks 调和
147
+ const fwRecon = reconcileWithPrompt(signals.platforms, explicitFrameworks);
148
+ const frameworks = fwRecon.values;
149
+ const envDowngraded = signals.hostEnv != null && !frameworks.includes(signals.hostEnv);
150
+ // Phase 2.5: products 调和(仅当 prompt 有非 TRTC 单产品时取此产品;TRTC 由 platform 推断 rtcengine/native_trtc_sdk)
151
+ const explicitTrtcBoth = explicitProducts?.includes('rtcengine') && explicitProducts?.includes('native_trtc_sdk');
152
+ const explicitTrtcAny = explicitProducts?.some((p) => p === 'rtcengine' || p === 'native_trtc_sdk') ?? false;
153
+ const isTrtcFamily = signals.products.has('trtc') || explicitTrtcBoth || (explicitTrtcAny && !nonTrtcProducts[0]);
154
+ // —— TRTC 家族:产物随平台分流
155
+ if (isTrtcFamily) {
156
+ const hasWeb = frameworks.includes('web');
157
+ const natives = frameworks.filter((f) => f !== 'web');
158
+ // TRTC + 多平台(web + native)→ product 数组,search core filterPool 原生接受多 product 交集
159
+ if (hasWeb && natives.length > 0) {
160
+ const reason = fwRecon.corrected ? 'trtc_multi_platform_prompt_override' : 'trtc_multi_platform';
161
+ return trcMultiProductPlan(params, ['rtcengine', 'native_trtc_sdk'], frameworks, reason, envDowngraded);
162
+ }
163
+ // TRTC + 仅 web
164
+ if (hasWeb) {
165
+ const reason = fwRecon.corrected ? 'trtc_web_prompt_override' : 'trtc_web';
166
+ return singleProductPlan(params, 'rtcengine', ['web'], signals.hostEnv, reason);
167
+ }
168
+ // TRTC + 仅 native
169
+ if (natives.length > 0) {
170
+ const reason = fwRecon.corrected ? 'trtc_native_prompt_override' : 'trtc_native';
171
+ return singleProductPlan(params, 'native_trtc_sdk', natives, signals.hostEnv, reason);
172
+ }
173
+ // TRTC 但 prompt / explicit 都没有可用平台 → 透传
174
+ return passthroughPlan(params, 'trtc_no_platform_resolved', false);
175
+ }
176
+ // —— 单非 TRTC 产品
177
+ if (nonTrtcProducts.length === 1) {
178
+ const product = nonTrtcProducts[0];
179
+ // 多平台 → product 单值,frameworks 多值(search core filterPool 原生按 framework 交集过滤)
180
+ if (frameworks.length >= 2) {
181
+ const reason = fwRecon.corrected ? 'multi_platform_prompt_override' : 'multi_platform';
182
+ return singleProductPlan(params, product, frameworks, signals.hostEnv, reason);
183
+ }
184
+ // 单平台
185
+ if (frameworks.length === 1) {
186
+ const reason = fwRecon.corrected ? 'single_platform_prompt_override' : 'single_platform';
187
+ return singleProductPlan(params, product, frameworks, signals.hostEnv, reason);
188
+ }
189
+ // 无平台信号:透传(保留 explicit frameworks)
190
+ if (explicitFrameworks?.length) {
191
+ return singleProductPlan(params, product, uniq(explicitFrameworks), signals.hostEnv, 'product_no_platform_explicit');
192
+ }
193
+ return passthroughPlan(params, 'product_no_platform_signal', false);
194
+ }
195
+ // —— 无产品信号:纯透传
196
+ return passthroughPlan(params, 'no_product_signal', false);
197
+ }
@@ -32,7 +32,7 @@ function sendCLSReport(reportData) {
32
32
  const time = Date.now();
33
33
  const data = {
34
34
  platform: 'mcp',
35
- verison: '1.6.8',
35
+ verison: '1.7.1',
36
36
  sdkappid: `${SDKAppID}`,
37
37
  method: method,
38
38
  userid: queryID || userID,
@@ -7,7 +7,7 @@ function reportESClient(reportData) {
7
7
  report: [
8
8
  {
9
9
  platform: 'mcp',
10
- version: '1.6.8',
10
+ version: '1.7.1',
11
11
  sdkappid: `${SDKAppID}`,
12
12
  method: method,
13
13
  userID: `${userID}`,
Binary file
Binary file
@@ -1,80 +0,0 @@
1
- ---
2
- product: 'Call'
3
- tech_stack: 'callengine'
4
- framework: 'web'
5
- doc_kind: 'best_practice'
6
- title: 'TUICallengine 通话处理'
7
- keywords: ['ON_CALL_RECEIVED', '通话邀请', '@trtc/call-engine-lite-js', 'calls', 'businessID', 'userData', 'callMediaType', 'calls', 'accept', 'onReceiveNewInvitation', 'onRecvNewMessage 自定义消息']
8
- ---
9
-
10
- # TUICallEngine Web 通话邀请接收与信令说明
11
-
12
- 适用包:**@trtc/call-engine-lite-js**(TUICallEngine Web SDK)。
13
-
14
- ## 一、如何接收通话邀请?
15
-
16
- **应用层应监听 `TUICallEvent.ON_CALL_RECEIVED`,而不是 TRTC Signaling 或 IM 自定义消息。**
17
-
18
- ```javascript
19
- import { TUICallEngine, TUICallEvent } from '@trtc/call-engine-lite-js';
20
-
21
- const tuiCallEngine = TUICallEngine.createInstance({ SDKAppID: xxx, tim: timInstance });
22
- await tuiCallEngine.login({ userID, userSig });
23
-
24
- tuiCallEngine.on(TUICallEvent.ON_CALL_RECEIVED, (event) => {
25
- console.log('来电', event);
26
- // 展示接听 UI,随后 accept() 或 reject()
27
- });
28
- ```
29
-
30
- ### ON_CALL_RECEIVED 事件字段
31
-
32
- |参数|类型|含义|
33
- |---------|---------|---------|
34
- |callerId|String|邀请者。|
35
- |calleeIdList|Array<String>|同时还被邀请的人。|
36
- |isFromGroup|Boolean|是否是群组通话。|
37
- |inviteData|Object|呼叫数据。|
38
- |inviteID|String|邀请 ID,标识一次邀请。|
39
- |userData|String|扩展字段:用于在邀请信令中增加扩展信息。|
40
- |callId|String|本次通话的唯一 ID。|
41
- |roomID|Number|此次通话的音视频房间 ID。|
42
- |callMediaType|Number|通话的媒体类型,视频通话、语音通话。|
43
- |callRole|String|角色,枚举类型:主叫、被叫。|
44
-
45
- ## 二、calls API 发送邀请时的参数有哪些?
46
-
47
- 主叫通过 **`TUICallEngine.calls()`** 发起邀请:
48
-
49
- ```javascript
50
- await tuiCallEngine.calls({
51
- userIDList: ['user_bob'],
52
- type: 2, // 1=语音 2=视频(CallMediaType)
53
- userData: '{"ext":"info"}', // 可选,字符串
54
- timeout: 30, // 秒,默认 30
55
- roomID: 123456, // 数字房间号, 范围 [1, 2147483647]
56
- strRoomID: '', // 字符串房间号
57
- chatGroupID: '', // 在 Chat 群聊中多人通话时填写
58
- offlinePushInfo: { /* … */ },
59
- });
60
- ```
61
-
62
- ## 三、在 Chat 中集成音视频通话时,如果需要处理通话消息上屏,自定义消息中的字段含义?
63
-
64
- |**问题**|**说明**|
65
- |---------|---------|
66
- |如何判断 “发起通话”|actionType = 1 且 cmd = 'videoCall' 或 'audioCall'|
67
- |如何判断 “已接听”|actionType = 3 |
68
- |如何判断 “通话已结束”,“通话时长是多少”|actionType = 1 且 cmd = 'hangup',通话时长取 call_end 字段,单位:秒|
69
- |如何判断 “取消通话”|actionType = 2|
70
- |如何判断 “拒绝通话”|actionType = 4|
71
- |如何判断对端忙线产生的 “拒绝通话”|actionType = 4 且 cmd = "line_busy"|
72
- |如何被叫判断 “超时未接听”|actionType = 5|
73
-
74
- ## 四、最佳实践摘要
75
-
76
- 1. **只监听** `TUICallEvent.ON_CALL_RECEIVED`,用 `accept()` / `reject()` 响应。
77
- 2. **不要**监听 `TIM.EVENT.MESSAGE_RECEIVED` 自行解析通话邀请。
78
- 3. 扩展信息用 **`userData` 字符串**,主叫传入、被叫从事件读取。
79
- 4. **`businessID` / 底层 payload 格式**属 SDK 内部实现;无官方公开文档前不要编造。
80
- 5. **`@trtc/call-engine-lite-js` 4.x 起发起通话时需要传递自定义内容可以通过 `userData` 传递给被叫,不存在 `customData` 字段。
@@ -1,20 +0,0 @@
1
- ---
2
- product: 'Call'
3
- tech_stack: 'callengine'
4
- framework: 'web'
5
- doc_kind: 'best_practice'
6
- title: 'TUICallengine 设置日志输出级别'
7
- keywords: ['控制台日志', '关闭 log', 'log level']
8
- ---
9
-
10
- # TUICallEngine 设置日志输出级别
11
-
12
- Web TUICallEngine 提供浏览器控制台日志输出控制接口 setLogLevel。
13
- 日志级别分为以下几种:
14
- 0 普通级别,日志量较多,接入时建议使用
15
- 1 主要级别,SDK 输出关键信息,默认日志等级
16
- 2 告警级别,SDK 只输出告警和错误级别的日志
17
- 3 错误级别,SDK 只输出错误级别的日志
18
- 4 无日志级别,SDK 将不打印任何日志
19
-
20
- 文档:[setLogLevel](https://web.sdk.qcloud.com/component/trtccalling/doc/TUICallEngine/web/zh-cn/TUICallEngine.html#setLogLevel)