webaiapi 0.0.0-stage → 0.1.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.
Files changed (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -2
  3. package/bin/webapi.mjs +45 -0
  4. package/dist/browser/auth-capture.d.ts +110 -0
  5. package/dist/browser/auth-capture.d.ts.map +1 -0
  6. package/dist/browser/auth-capture.js +487 -0
  7. package/dist/browser/auth-capture.js.map +1 -0
  8. package/dist/browser/credentials.d.ts +102 -0
  9. package/dist/browser/credentials.d.ts.map +1 -0
  10. package/dist/browser/credentials.js +209 -0
  11. package/dist/browser/credentials.js.map +1 -0
  12. package/dist/browser/js-eval.d.ts +44 -0
  13. package/dist/browser/js-eval.d.ts.map +1 -0
  14. package/dist/browser/js-eval.js +20 -0
  15. package/dist/browser/js-eval.js.map +1 -0
  16. package/dist/browser/login-flow.d.ts +171 -0
  17. package/dist/browser/login-flow.d.ts.map +1 -0
  18. package/dist/browser/login-flow.js +606 -0
  19. package/dist/browser/login-flow.js.map +1 -0
  20. package/dist/browser/manager.d.ts +144 -0
  21. package/dist/browser/manager.d.ts.map +1 -0
  22. package/dist/browser/manager.js +586 -0
  23. package/dist/browser/manager.js.map +1 -0
  24. package/dist/browser/platforms.d.ts +96 -0
  25. package/dist/browser/platforms.d.ts.map +1 -0
  26. package/dist/browser/platforms.js +288 -0
  27. package/dist/browser/platforms.js.map +1 -0
  28. package/dist/browser/sse-capture.d.ts +23 -0
  29. package/dist/browser/sse-capture.d.ts.map +1 -0
  30. package/dist/browser/sse-capture.js +199 -0
  31. package/dist/browser/sse-capture.js.map +1 -0
  32. package/dist/browser/stealth.d.ts +12 -0
  33. package/dist/browser/stealth.d.ts.map +1 -0
  34. package/dist/browser/stealth.js +12 -0
  35. package/dist/browser/stealth.js.map +1 -0
  36. package/dist/cli.d.ts +12 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +441 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/config.d.ts +90 -0
  41. package/dist/config.d.ts.map +1 -0
  42. package/dist/config.js +246 -0
  43. package/dist/config.js.map +1 -0
  44. package/dist/controllers/history-db.d.ts +56 -0
  45. package/dist/controllers/history-db.d.ts.map +1 -0
  46. package/dist/controllers/history-db.js +260 -0
  47. package/dist/controllers/history-db.js.map +1 -0
  48. package/dist/controllers/request-pool.d.ts +78 -0
  49. package/dist/controllers/request-pool.d.ts.map +1 -0
  50. package/dist/controllers/request-pool.js +222 -0
  51. package/dist/controllers/request-pool.js.map +1 -0
  52. package/dist/controllers/router.d.ts +172 -0
  53. package/dist/controllers/router.d.ts.map +1 -0
  54. package/dist/controllers/router.js +569 -0
  55. package/dist/controllers/router.js.map +1 -0
  56. package/dist/index.d.ts +72 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +61 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/log/api-logger.d.ts +55 -0
  61. package/dist/log/api-logger.d.ts.map +1 -0
  62. package/dist/log/api-logger.js +264 -0
  63. package/dist/log/api-logger.js.map +1 -0
  64. package/dist/log/debug-logger.d.ts +49 -0
  65. package/dist/log/debug-logger.d.ts.map +1 -0
  66. package/dist/log/debug-logger.js +285 -0
  67. package/dist/log/debug-logger.js.map +1 -0
  68. package/dist/log/logger.d.ts +16 -0
  69. package/dist/log/logger.d.ts.map +1 -0
  70. package/dist/log/logger.js +35 -0
  71. package/dist/log/logger.js.map +1 -0
  72. package/dist/postinstall.d.ts +3 -0
  73. package/dist/postinstall.d.ts.map +1 -0
  74. package/dist/postinstall.js +90 -0
  75. package/dist/postinstall.js.map +1 -0
  76. package/dist/providers/base.d.ts +83 -0
  77. package/dist/providers/base.d.ts.map +1 -0
  78. package/dist/providers/base.js +51 -0
  79. package/dist/providers/base.js.map +1 -0
  80. package/dist/providers/chatglm-dom-extract.d.ts +24 -0
  81. package/dist/providers/chatglm-dom-extract.d.ts.map +1 -0
  82. package/dist/providers/chatglm-dom-extract.js +175 -0
  83. package/dist/providers/chatglm-dom-extract.js.map +1 -0
  84. package/dist/providers/chatglm-dom.d.ts +110 -0
  85. package/dist/providers/chatglm-dom.d.ts.map +1 -0
  86. package/dist/providers/chatglm-dom.js +384 -0
  87. package/dist/providers/chatglm-dom.js.map +1 -0
  88. package/dist/providers/chatglm-sse.d.ts +119 -0
  89. package/dist/providers/chatglm-sse.d.ts.map +1 -0
  90. package/dist/providers/chatglm-sse.js +241 -0
  91. package/dist/providers/chatglm-sse.js.map +1 -0
  92. package/dist/providers/chatglm.d.ts +288 -0
  93. package/dist/providers/chatglm.d.ts.map +1 -0
  94. package/dist/providers/chatglm.js +1631 -0
  95. package/dist/providers/chatglm.js.map +1 -0
  96. package/dist/routes/chat.d.ts +117 -0
  97. package/dist/routes/chat.d.ts.map +1 -0
  98. package/dist/routes/chat.js +1346 -0
  99. package/dist/routes/chat.js.map +1 -0
  100. package/dist/routes/compress.d.ts +142 -0
  101. package/dist/routes/compress.d.ts.map +1 -0
  102. package/dist/routes/compress.js +551 -0
  103. package/dist/routes/compress.js.map +1 -0
  104. package/dist/routes/failover.d.ts +34 -0
  105. package/dist/routes/failover.d.ts.map +1 -0
  106. package/dist/routes/failover.js +221 -0
  107. package/dist/routes/failover.js.map +1 -0
  108. package/dist/routes/helpers.d.ts +174 -0
  109. package/dist/routes/helpers.d.ts.map +1 -0
  110. package/dist/routes/helpers.js +525 -0
  111. package/dist/routes/helpers.js.map +1 -0
  112. package/dist/routes/meta.d.ts +31 -0
  113. package/dist/routes/meta.d.ts.map +1 -0
  114. package/dist/routes/meta.js +184 -0
  115. package/dist/routes/meta.js.map +1 -0
  116. package/dist/routes/sse.d.ts +82 -0
  117. package/dist/routes/sse.d.ts.map +1 -0
  118. package/dist/routes/sse.js +77 -0
  119. package/dist/routes/sse.js.map +1 -0
  120. package/dist/server/app.d.ts +24 -0
  121. package/dist/server/app.d.ts.map +1 -0
  122. package/dist/server/app.js +292 -0
  123. package/dist/server/app.js.map +1 -0
  124. package/dist/server/middleware/api-key.d.ts +61 -0
  125. package/dist/server/middleware/api-key.d.ts.map +1 -0
  126. package/dist/server/middleware/api-key.js +95 -0
  127. package/dist/server/middleware/api-key.js.map +1 -0
  128. package/dist/server/middleware/rate-limit.d.ts +67 -0
  129. package/dist/server/middleware/rate-limit.d.ts.map +1 -0
  130. package/dist/server/middleware/rate-limit.js +134 -0
  131. package/dist/server/middleware/rate-limit.js.map +1 -0
  132. package/dist/server/middleware/timeout.d.ts +34 -0
  133. package/dist/server/middleware/timeout.d.ts.map +1 -0
  134. package/dist/server/middleware/timeout.js +64 -0
  135. package/dist/server/middleware/timeout.js.map +1 -0
  136. package/dist/server/types.d.ts +20 -0
  137. package/dist/server/types.d.ts.map +1 -0
  138. package/dist/server/types.js +2 -0
  139. package/dist/server/types.js.map +1 -0
  140. package/dist/tools/build.d.ts +14 -0
  141. package/dist/tools/build.d.ts.map +1 -0
  142. package/dist/tools/build.js +105 -0
  143. package/dist/tools/build.js.map +1 -0
  144. package/dist/tools/clean.d.ts +32 -0
  145. package/dist/tools/clean.d.ts.map +1 -0
  146. package/dist/tools/clean.js +65 -0
  147. package/dist/tools/clean.js.map +1 -0
  148. package/dist/tools/convert.d.ts +50 -0
  149. package/dist/tools/convert.d.ts.map +1 -0
  150. package/dist/tools/convert.js +289 -0
  151. package/dist/tools/convert.js.map +1 -0
  152. package/dist/tools/names.d.ts +138 -0
  153. package/dist/tools/names.d.ts.map +1 -0
  154. package/dist/tools/names.js +578 -0
  155. package/dist/tools/names.js.map +1 -0
  156. package/dist/tools/parse.d.ts +31 -0
  157. package/dist/tools/parse.d.ts.map +1 -0
  158. package/dist/tools/parse.js +273 -0
  159. package/dist/tools/parse.js.map +1 -0
  160. package/dist/tools/placeholder.d.ts +21 -0
  161. package/dist/tools/placeholder.d.ts.map +1 -0
  162. package/dist/tools/placeholder.js +135 -0
  163. package/dist/tools/placeholder.js.map +1 -0
  164. package/dist/tools/prompts.d.ts +42 -0
  165. package/dist/tools/prompts.d.ts.map +1 -0
  166. package/dist/tools/prompts.js +69 -0
  167. package/dist/tools/prompts.js.map +1 -0
  168. package/dist/tools/protocol.d.ts +58 -0
  169. package/dist/tools/protocol.d.ts.map +1 -0
  170. package/dist/tools/protocol.js +287 -0
  171. package/dist/tools/protocol.js.map +1 -0
  172. package/dist/tools/repair.d.ts +83 -0
  173. package/dist/tools/repair.d.ts.map +1 -0
  174. package/dist/tools/repair.js +510 -0
  175. package/dist/tools/repair.js.map +1 -0
  176. package/dist/tools/workspace.d.ts +40 -0
  177. package/dist/tools/workspace.d.ts.map +1 -0
  178. package/dist/tools/workspace.js +224 -0
  179. package/dist/tools/workspace.js.map +1 -0
  180. package/dist/types/message.d.ts +87 -0
  181. package/dist/types/message.d.ts.map +1 -0
  182. package/dist/types/message.js +85 -0
  183. package/dist/types/message.js.map +1 -0
  184. package/dist/types/openai.d.ts +100 -0
  185. package/dist/types/openai.d.ts.map +1 -0
  186. package/dist/types/openai.js +66 -0
  187. package/dist/types/openai.js.map +1 -0
  188. package/dist/utils/errors.d.ts +82 -0
  189. package/dist/utils/errors.d.ts.map +1 -0
  190. package/dist/utils/errors.js +115 -0
  191. package/dist/utils/errors.js.map +1 -0
  192. package/dist/utils/mutex.d.ts +31 -0
  193. package/dist/utils/mutex.d.ts.map +1 -0
  194. package/dist/utils/mutex.js +61 -0
  195. package/dist/utils/mutex.js.map +1 -0
  196. package/dist/utils/prompt.d.ts +150 -0
  197. package/dist/utils/prompt.d.ts.map +1 -0
  198. package/dist/utils/prompt.js +514 -0
  199. package/dist/utils/prompt.js.map +1 -0
  200. package/dist/utils/text.d.ts +30 -0
  201. package/dist/utils/text.d.ts.map +1 -0
  202. package/dist/utils/text.js +70 -0
  203. package/dist/utils/text.js.map +1 -0
  204. package/package.json +67 -4
  205. package/postinstall.mjs +34 -0
@@ -0,0 +1,384 @@
1
+ /**
2
+ * ChatGLM 页面侧脚本(`page.evaluate()` 执行的 JS)—— 小的那些。
3
+ *
4
+ * 从 `legacy-python/src/providers/chatglm_browser.py` 的 `page.evaluate("""...""")`
5
+ * 字符串**原样抽出**并集中放这里,好处:Provider 类只剩控制流;这些脚本可单独审阅
6
+ * (它们是「页面当前处于什么状态」的唯一真相)。
7
+ *
8
+ * ⚠️ 字符串里的 `\\n` / `\\.` 是**给浏览器看的转义**(等价于 Python 的 `\\n`),
9
+ * 改动时请保持「反斜杠+n」两字符写法。
10
+ * ⚠️ 读 DOM 得到的正文**绝不作为最终答案**(Markdown 渲染会破坏内容)。
11
+ * 正文一律来自 SSE;DOM 只用于「诊断 / 兜底 / 等待循环的活性判断」。
12
+ *
13
+ * 大脚本在 `chatglm-dom-extract.ts`。
14
+ */
15
+ /** 探测是否**已登录**(访客不算登录)。 */
16
+ export const PROBE_LOGGED_IN_JS = `() => {
17
+ const nameEl = document.querySelector(
18
+ '.sidebar-user-name, .userInfoBar, [class*="sidebar-user-name"], ' +
19
+ '[class*="userInfoBar"], [class*="user-name"], [class*="userName"]'
20
+ );
21
+ const raw = nameEl ? (nameEl.innerText || '').replace(/\\n/g, ' ').trim() : '';
22
+ const name = (raw.split(/\\s+/)[0] || '');
23
+ let has_user_el = false;
24
+ for (const sel of ['.user-info', '.avatar', '[class*="user-info"]',
25
+ '[class*="avatar"]', '[class*="user"]']) {
26
+ for (const el of document.querySelectorAll(sel)) {
27
+ if (el.offsetParent !== null &&
28
+ el.getBoundingClientRect().height > 0) {
29
+ has_user_el = true;
30
+ break;
31
+ }
32
+ }
33
+ if (has_user_el) break;
34
+ }
35
+ return { name: name, raw: raw.slice(0, 40), has_user_el: has_user_el };
36
+ }`;
37
+ /** 思考阶段检测(5 种策略,任一命中即「仍在思考」)。 */
38
+ export const DETECT_THINKING_JS = `() => {
39
+ const thinkingEl = document.querySelector('.advance-thinking');
40
+ if (thinkingEl && !thinkingEl.classList.contains('collapse')) return true;
41
+
42
+ const statusEls = document.querySelectorAll(
43
+ '.advance-thinking-status, .advance-thinking-status_inner, ' +
44
+ '.advance-thinking-done, .advance-thinking-done_text, ' +
45
+ '[class*="thinking-status"], [class*="thinking-done"]'
46
+ );
47
+ for (const el of statusEls) {
48
+ if (el.offsetHeight <= 0) continue;
49
+ const t = (el.innerText || '').trim();
50
+ if (/构思回复|分析请求|思考中|正在思考|thinking/i.test(t)) {
51
+ if (!/思考结束|已深度思考|思考完成|ended|complete/i.test(t)) return true;
52
+ }
53
+ }
54
+
55
+ const ansEls = document.querySelectorAll(
56
+ '.answer-content-wrap:not(.text-advance-thinking-content), .answer-content'
57
+ );
58
+ for (const el of ansEls) {
59
+ if (el.offsetHeight <= 0) continue;
60
+ const t = (el.innerText || '').trim();
61
+ if (/^构思回复|^分析请求|^跳过思考/.test(t)) return true;
62
+ if (/\\n构思回复[::]|\\n分析请求[::]/.test(t)) return true;
63
+ }
64
+
65
+ const stopSelectors = [
66
+ 'button[aria-label*="stop" i]', 'button[aria-label*="停止"]',
67
+ 'button[class*="stop-generat"]', '[class*="stop-generat"]',
68
+ ];
69
+ for (const sel of stopSelectors) {
70
+ try {
71
+ const el = document.querySelector(sel);
72
+ if (el && el.offsetHeight > 0 && !el.disabled) return true;
73
+ } catch(e) {}
74
+ }
75
+
76
+ const loadingEls = document.querySelectorAll(
77
+ '.search-loading-container, .loading-text, [class*="search-loading"]'
78
+ );
79
+ for (const el of loadingEls) {
80
+ if (el.offsetHeight > 0) return true;
81
+ }
82
+ return false;
83
+ }`;
84
+ /** 完成检测(DOM 兜底;权威信号是 SSE 的 `status == "finish"`)。 */
85
+ export const DETECT_COMPLETION_JS = `() => {
86
+ const regenEls = document.querySelectorAll(
87
+ '.regenerate.canuse, i.regenerate.canuse, .shim.regenerate.canuse'
88
+ );
89
+ for (const el of regenEls) {
90
+ if (el.offsetHeight > 0 && el.offsetWidth > 0) return true;
91
+ }
92
+
93
+ const containerEls = document.querySelectorAll('.regenerate-part-container');
94
+ for (const el of containerEls) {
95
+ if (el.offsetHeight > 0 && el.offsetWidth > 0) return true;
96
+ }
97
+
98
+ return false;
99
+ }`;
100
+ /**
101
+ * 页面**最后一条** `.answer` 是否就是平台并发/限流提示。
102
+ *
103
+ * ⚠️ 只看 `.answer`,**不看 body**:`document.body.innerText` 里还包含我们自己发出去的
104
+ * 提示词(历史被拍平后一起发过去)。一旦某轮出现过限流提示,这段文字就会随历史进入
105
+ * 后续每一轮的 prompt → 用 body 判断会**永久误判**为限流,把正常请求一律掐掉。
106
+ */
107
+ export const PAGE_SHOWS_BUSY_JS = `() => {
108
+ const els = document.querySelectorAll('.answer');
109
+ if (!els.length) return false;
110
+ const t = (els[els.length - 1].innerText || '').trim();
111
+ if (!t || t.length > 120) return false;
112
+ return /请等待其他对话生成完毕|本次回答已被终止|还有对话正在生成|正在生成其他回答|请求过于频繁|过于频繁|请稍后再试/.test(t);
113
+ }`;
114
+ /**
115
+ * 读取平台限制提示**原文**(回传给客户端用)。
116
+ *
117
+ * 只截取「关键词 + 紧跟的短尾」并在第一个句号/换行处截断 ——
118
+ * 不要用 `[^\\n]*` 贪婪匹配整行:页面上一行里可能跟随着我们自己的提示词
119
+ * (曾抓到「请求过于频繁,请稍后再试。…请稍等 1~2 分钟后重试。现在输出工具调用…」)。
120
+ */
121
+ export const READ_BUSY_NOTICE_JS = `() => {
122
+ const els = document.querySelectorAll('.answer');
123
+ const src = els.length
124
+ ? (els[els.length - 1].innerText || '')
125
+ : (document.body.innerText || '');
126
+ const m = src.match(/(请求过于频繁|请等待其他对话生成完毕|还有对话正在生成|正在生成其他回答)([^\\n]{0,20})/);
127
+ if (!m) return '';
128
+ return (m[1] + (m[2] || '')).split(/[。\\n]/)[0].trim();
129
+ }`;
130
+ /** 页面是否存在聊天输入框(判断页面是否处于可聊天状态)。 */
131
+ export const HAS_INPUT_BOX_JS = `() => {
132
+ if (document.querySelector('textarea')) return true;
133
+ if (document.querySelector('[contenteditable="true"]')) return true;
134
+ return false;
135
+ }`;
136
+ /** 统计当前对话轮数(校验消息是否真正发出)。 */
137
+ export const COUNT_CONVERSATION_ITEMS_JS = `() => document.querySelectorAll('.conversation-list-outer .item.conversation-item').length`;
138
+ /** 读取输入框当前文本长度(-1 表示没找到输入框)。 */
139
+ export const INPUT_VALUE_LEN_JS = `() => {
140
+ const t = document.querySelector('textarea');
141
+ if (t) return (t.value || '').length;
142
+ const ce = document.querySelector('[contenteditable="true"]');
143
+ if (ce) return (ce.innerText || '').length;
144
+ return -1;
145
+ }`;
146
+ /** 输入框是否已被清空(ChatGLM 提交成功后会清空输入框)。 */
147
+ export const IS_INPUT_CLEARED_JS = `() => {
148
+ const t = document.querySelector('textarea');
149
+ if (t) return (t.value || '').trim() === '';
150
+ const ce = document.querySelector('[contenteditable="true"]');
151
+ if (ce) return (ce.innerText || '').trim() === '';
152
+ return false;
153
+ }`;
154
+ /** 清空输入框(发送前)。 */
155
+ export const CLEAR_INPUT_JS = `() => {
156
+ const t = document.querySelector('textarea');
157
+ if (t) { t.value = ''; }
158
+ const ce = document.querySelector('[contenteditable="true"]');
159
+ if (ce) { ce.innerHTML = ''; }
160
+ }`;
161
+ /**
162
+ * 关闭遮挡浮层的第 1 步:点掉浮层内可见的关闭按钮。
163
+ *
164
+ * ChatGLM 基于 Element UI,页面会偶发出现遮罩层(loading/公告弹窗),
165
+ * 导致 Playwright 点击输入框或发送按钮时被拦截
166
+ * (报错 "subtree intercepts pointer events"),消息因此发不出去。
167
+ */
168
+ export const DISMISS_OVERLAY_CLOSE_JS = `() => {
169
+ const sels = [
170
+ '.el-dialog__headerbtn', '.el-dialog__close',
171
+ '.el-message-box__headerbtn', '.el-message-box__close',
172
+ ];
173
+ let n = 0;
174
+ for (const s of sels) {
175
+ document.querySelectorAll(s).forEach(el => {
176
+ try { el.click(); n++; } catch (e) {}
177
+ });
178
+ }
179
+ return n;
180
+ }`;
181
+ /** 第 2 步:统计仍然可见的遮挡浮层数量(>0 时按 Esc)。 */
182
+ export const DISMISS_OVERLAY_BLOCKING_JS = `() => {
183
+ let n = 0;
184
+ document.querySelectorAll('.el-overlay, .el-overlay-dialog').forEach(el => {
185
+ const st = getComputedStyle(el);
186
+ if (st.display !== 'none' && st.visibility !== 'hidden' &&
187
+ el.getBoundingClientRect().height > 0) n++;
188
+ });
189
+ return n;
190
+ }`;
191
+ /** 第 3 步兜底:让残留遮罩不再拦截指针事件。 */
192
+ export const DISMISS_OVERLAY_FALLBACK_JS = `() => {
193
+ document.querySelectorAll('.el-overlay, .el-overlay-dialog').forEach(el => {
194
+ const st = getComputedStyle(el);
195
+ if (st.display !== 'none') {
196
+ el.style.pointerEvents = 'none';
197
+ }
198
+ });
199
+ }`;
200
+ /** 清空页面上的 SSE 捕获缓冲(每轮请求前调用)。 */
201
+ export const RESET_SSE_JS = `() => { if (window.__glmSSEReset) window.__glmSSEReset(); }`;
202
+ /** 读取页面捕获到的 SSE 原始文本。 */
203
+ export const SSE_GET_JS = `() => (window.__glmSSEGet ? window.__glmSSEGet() : null)`;
204
+ /**
205
+ * 侦察:读取页面捕获到的**平台请求体**。
206
+ *
207
+ * 目的:判断「能否把客户端工具定义交给平台、用它的原生函数调用通道承载客户端工具」。
208
+ * 见 `SSE_CAPTURE_JS` 的说明与 `docs/migration-status.md`。
209
+ */
210
+ export const REQUEST_BODIES_GET_JS = `() => (window.__glmSSEBodiesGet ? window.__glmSSEBodiesGet() : null)`;
211
+ /**
212
+ * 侦察:找出页面上与「联网搜索 / 深度思考 / 工具」有关的开关控件。
213
+ *
214
+ * ## 为什么要找它们(2026-10-14 实测)
215
+ * 平台自己发出去的 ask 请求体里有 `meta_data.is_networking`(默认 **true**)、
216
+ * `chat_mode: "deep_thinking"`、`reasoning_effort: "max"`:
217
+ * 前者让模型不停去调平台自带的联网搜索(`search` 单轮刷 57 次),
218
+ * 后者让每回合要「想」90~200s。这两个都是**页面 UI 状态**,
219
+ * 只有点到对应开关才能在发请求前改掉它们。
220
+ *
221
+ * 因此先把候选控件与本地存储键 dump 出来(`logs/chatglm_page_toggles.txt`),
222
+ * 拿到稳定选择器后再实现「发送前关掉它们」。
223
+ */
224
+ /**
225
+ * 发送前设置页面开关:**关掉「联网搜索」**、**把「思考强度」降到「快速」**。
226
+ *
227
+ * ## 为什么(2026-10-14 实测)
228
+ * 平台 ask 请求体里默认是 `is_networking: true` + `chat_mode: deep_thinking` +
229
+ * `reasoning_effort: max`:
230
+ * - 联网开着 → 模型把「bash 怎么删带空格的目录」当检索问题,单轮刷 57 次 `search`,耗 210s;
231
+ * - 极致思考 → 每回合「想」90~200s。
232
+ * 两个都是**页面 UI 状态**,所以在发送前点掉它们(探针给出选择器:
233
+ * `.search-btn`、`.item-think` → `.think-mode-item`)。
234
+ *
235
+ * 幂等:已经是目标状态就不点(返回 `changed:false`)。
236
+ * 注意:这里用页面内 `el.click()`(不受 Playwright 可见性检查影响),
237
+ * 因为「思考强度」子菜单是 popper —— 渲染在 DOM 里但默认隐藏。
238
+ */
239
+ export const APPLY_CHAT_PREFS_JS = `() => {
240
+ const clip = (s, n) => String(s == null ? '' : s).replace(/\\s+/g, ' ').trim().slice(0, n || 80);
241
+ const popper = (el) => {
242
+ try {
243
+ const p = el.closest('[class*="popper"], [class*="dropdown"], [class*="submenu"], [class*="menu"]');
244
+ return p ? clip(p.className, 60) : '';
245
+ } catch (e) { return ''; }
246
+ };
247
+ const snapItem = (el, i) => ({
248
+ i: i,
249
+ text: clip(el.innerText, 30),
250
+ cls: clip(el.className, 60),
251
+ visible: !!(el.offsetWidth || el.offsetHeight),
252
+ popper: popper(el),
253
+ });
254
+ const snap = () => ({
255
+ searchBtn: (() => {
256
+ const b = document.querySelector('.search-btn') ||
257
+ document.querySelector('.operation-btn[class*="search"]');
258
+ return b ? { cls: clip(b.className, 120), html: clip(b.innerHTML, 80),
259
+ parentCls: clip((b.parentElement || {}).className, 80) } : null;
260
+ })(),
261
+ thinkEntry: (() => {
262
+ const e = document.querySelector('.item-think');
263
+ return e ? { cls: clip(e.className, 60), text: clip(e.innerText, 30),
264
+ visible: !!(e.offsetWidth || e.offsetHeight) } : null;
265
+ })(),
266
+ items: Array.prototype.slice.call(document.querySelectorAll('.think-mode-item')).map(snapItem),
267
+ });
268
+
269
+ const out = { notes: [], before: snap(), clicked: [], after: null };
270
+ const click = (el) => { try { el.click(); return true; } catch (e) { return false; } };
271
+ const on = (el) => /active|selected|is-checked|checked|open/i.test(String(el.className || ''));
272
+
273
+ // ---- 1) 联网搜索:⚠️ 2026-10-14 实测**不改** ----
274
+ // .search-btn 点击只会弹出模型下拉(el-dropdown/model-select-item),
275
+ // 而平台请求体里的 is_networking 始终为 true —— 它不是页面上可切的开关
276
+ // (更像「允许联网」的能力位)。文档见 docs/native-parity.md 阶段 3。
277
+
278
+ // ---- 2) 思考强度:逐个「快速」项都点一次(可能有多个 popper,其中一个是真菜单)----
279
+ try {
280
+ const items = Array.prototype.slice.call(document.querySelectorAll('.think-mode-item'));
281
+ items.forEach((el, i) => {
282
+ if (!/快速/.test(el.innerText || '')) return;
283
+ const entry = document.querySelector('.item-think');
284
+ if (entry) {
285
+ try {
286
+ entry.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
287
+ entry.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
288
+ } catch (e) {}
289
+ }
290
+ out.clicked.push('fast#' + i + ':onBefore=' + on(el) + ':clicked=' + click(el));
291
+ });
292
+ } catch (e) { out.notes.push('think:' + e); }
293
+
294
+ out.after = snap();
295
+ return out;
296
+ }`;
297
+ export const PROBE_CHAT_TOGGLES_JS = `() => {
298
+ const clip = (s, n) => String(s == null ? '' : s).slice(0, n || 60);
299
+ const attr = (el, name) => { try { return String(el.getAttribute(name) || ''); } catch (e) { return ''; } };
300
+ const rec = (el) => ({
301
+ tag: (el.tagName || '').toLowerCase(),
302
+ cls: clip(el.className, 80),
303
+ text: clip((el.innerText || el.textContent || '').replace(/\\s+/g, ' ').trim(), 60),
304
+ aria: clip(attr(el, 'aria-label'), 40),
305
+ role: clip(attr(el, 'role'), 20),
306
+ checked: clip(attr(el, 'aria-checked') || attr(el, 'data-checked'), 10),
307
+ title: clip(attr(el, 'title'), 40),
308
+ visible: !!(el.offsetWidth || el.offsetHeight),
309
+ });
310
+
311
+ const out = { url: location.href, controls: [], storage: {}, matched: [] };
312
+ const sel = [
313
+ '.el-switch', '[role="switch"]', '[aria-checked]',
314
+ 'button', '[role="button"]',
315
+ '[class*="switch"]', '[class*="net"]', '[class*="search"]',
316
+ '[class*="tool"]', '[class*="mode"]', '[class*="deep"]', '[class*="think"]'
317
+ ].join(',');
318
+
319
+ document.querySelectorAll(sel).forEach((el) => {
320
+ if (out.controls.length >= 120) return;
321
+ const r = rec(el);
322
+ if (!r.visible && !r.text) return;
323
+ out.controls.push(r);
324
+ if (/联网|搜索|工具|深度|思考|模式|模型|net|search|tool|deep|think|mode/i.test(r.text + ' ' + r.aria + ' ' + r.cls)) {
325
+ out.matched.push(r);
326
+ }
327
+ });
328
+
329
+ const dump = (store, prefix) => {
330
+ try {
331
+ for (const k of Object.keys(store)) {
332
+ if (Object.keys(out.storage).length >= 60) return;
333
+ out.storage[prefix + k] = clip(store.getItem(k), 120);
334
+ }
335
+ } catch (e) {}
336
+ };
337
+ dump(window.localStorage, 'local:');
338
+ dump(window.sessionStorage, 'sess:');
339
+
340
+ return out;
341
+ }`;
342
+ /** 诊断:判定平台限制时的依据(`.answer` 条数 + 最后一条内容 + URL)。 */
343
+ export const BUSY_NOTICE_DIAG_JS = `() => {
344
+ const els = document.querySelectorAll('.answer');
345
+ return {
346
+ n: els.length,
347
+ tail: els.length ? (els[els.length - 1].innerText || '').slice(0, 200) : '',
348
+ url: location.href,
349
+ };
350
+ }`;
351
+ /**
352
+ * 零产出诊断:区分「模型没答」与「答了但选择器/锚点没匹配上」。
353
+ */
354
+ export const ZERO_OUTPUT_DIAG_JS = `() => {
355
+ const ansEls = document.querySelectorAll('.answer');
356
+ const lastAns = ansEls.length ? (ansEls[ansEls.length-1].innerText || '') : null;
357
+ const conv = document.querySelectorAll('.session-block .conversation-container');
358
+ const lastConv = conv.length ? (conv[conv.length-1].innerText || '') : null;
359
+ return {
360
+ url: location.href,
361
+ answerCount: ansEls.length,
362
+ answerLen: lastAns === null ? -1 : lastAns.length,
363
+ answerHead: lastAns === null ? '' : lastAns.slice(0, 300),
364
+ convLen: lastConv === null ? -1 : lastConv.length,
365
+ convHead: lastConv === null ? '' : lastConv.slice(0, 300),
366
+ regenerate: document.querySelectorAll('.regenerate.canuse').length,
367
+ };
368
+ }`;
369
+ /** 输入框与发送按钮的选择器(按优先级)。 */
370
+ export const INPUT_SELECTORS = [
371
+ 'textarea.scroll-display-none',
372
+ 'textarea[class*="scroll-display-none"]',
373
+ 'textarea',
374
+ '[contenteditable="true"]',
375
+ ];
376
+ /** 发送按钮选择器(Enter 不生效时点击)。 */
377
+ export const SEND_BUTTON_SELECTORS = [
378
+ 'button[class*="send"]',
379
+ '[class*="send-btn"]',
380
+ '[class*="sendBtn"]',
381
+ 'div[class*="enter"]:not([class*="input"])',
382
+ 'button[type="submit"]',
383
+ ];
384
+ //# sourceMappingURL=chatglm-dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatglm-dom.js","sourceRoot":"","sources":["../../src/providers/chatglm-dom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,2BAA2B;AAC3B,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;EAoBhC,CAAC;AAEH,iCAAiC;AACjC,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6ChC,CAAC;AAEH,qDAAqD;AACrD,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;EAclC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;EAMhC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;EAQjC,CAAC;AAEH,kCAAkC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;EAI9B,CAAC;AAEH,4BAA4B;AAC5B,MAAM,CAAC,MAAM,2BAA2B,GACtC,4FAA4F,CAAC;AAE/F,gCAAgC;AAChC,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;EAMhC,CAAC;AAEH,sCAAsC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;EAMjC,CAAC;AAEH,kBAAkB;AAClB,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;EAK5B,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;EAYtC,CAAC;AAEH,sCAAsC;AACtC,MAAM,CAAC,MAAM,2BAA2B,GAAG;;;;;;;;EAQzC,CAAC;AAEH,6BAA6B;AAC7B,MAAM,CAAC,MAAM,2BAA2B,GAAG;;;;;;;EAOzC,CAAC;AAEH,gCAAgC;AAChC,MAAM,CAAC,MAAM,YAAY,GAAG,6DAA6D,CAAC;AAE1F,yBAAyB;AACzB,MAAM,CAAC,MAAM,UAAU,GAAG,0DAA0D,CAAC;AAErF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,sEAAsE,CAAC;AAE5G;;;;;;;;;;;;GAYG;AACH;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyDjC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CnC,CAAC;AAEH,kDAAkD;AAClD,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;EAOjC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;EAcjC,CAAC;AAEH,0BAA0B;AAC1B,MAAM,CAAC,MAAM,eAAe,GAA0B;IACpD,8BAA8B;IAC9B,wCAAwC;IACxC,UAAU;IACV,0BAA0B;CAC3B,CAAC;AAEF,6BAA6B;AAC7B,MAAM,CAAC,MAAM,qBAAqB,GAA0B;IAC1D,uBAAuB;IACvB,qBAAqB;IACrB,oBAAoB;IACpB,2CAA2C;IAC3C,uBAAuB;CACxB,CAAC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * 最近一轮的 SSE 原始数据。
3
+ *
4
+ * 路由层要从「主轮回答」里恢复工具调用(`parse_source` / reparse 输入),
5
+ * 所以主通道每轮结束后会把结果登记在这里。
6
+ *
7
+ * ⚠️ **只有主通道能写**(见 `ChatGLMBrowserProvider.ownsGlobals`):
8
+ * 辅助通道(后台预压缩)可能与主对话**同时**跑完,若它也写,
9
+ * 主轮的 reparse 就会拿到压缩请求的报文 → 转换出完全错误的工具调用。
10
+ * 辅助通道自己的产出通过 stream 的 chunk 直接返回给调用方,不需要全局。
11
+ */
12
+ export interface LastRawSse {
13
+ /** 本轮 SSE 原始报文里提取出的正文(未经 Markdown 渲染,内容保真)。 */
14
+ text: string;
15
+ /** 本轮 SSE 里的「思考」文本。 */
16
+ think: string;
17
+ /** 从 SSE 原文里直接解析出的 tool_calls(目前留空,由路由层填充)。 */
18
+ toolCalls: unknown[];
19
+ }
20
+ /** 登记主轮 SSE 结果(仅主通道调用)。 */
21
+ export declare function setLastRawSse(patch: Partial<LastRawSse>): void;
22
+ /** 读取主轮 SSE 结果。 */
23
+ export declare function getLastRawSse(): Readonly<LastRawSse>;
24
+ /** 清空主轮 SSE 结果(测试/诊断用)。 */
25
+ export declare function resetLastRawSse(): void;
26
+ /**
27
+ * 从 SSE 原始报文中迭代出所有 JSON 对象。
28
+ *
29
+ * ⚠️ ChatGLM 会把多个 JSON 对象**直接拼接**在同一行(中间没有换行分隔),
30
+ * 所以**不能**「按行 `JSON.parse`」,必须从任意位置连续往后扫。
31
+ * 解析失败时只前进一个字符(跳过噪声),与 Python 的 `raw_decode` 循环等价。
32
+ */
33
+ export declare function iterSseJson(raw: string): Generator<Record<string, unknown>>;
34
+ /** `extractSseAll` 的结果。 */
35
+ export interface SseExtractResult {
36
+ /** 正文(`type=text`,**取最长、绝不累积**)。 */
37
+ answer: string;
38
+ /** 思考(`type=think`,按 `logic_id` 累积)。 */
39
+ think: string;
40
+ /** 是否收到权威完成信号(`status === "finish"`)。 */
41
+ finished: boolean;
42
+ /**
43
+ * **ChatGLM 平台内部**工具调用的计数(如 `{search: 387, finish: 2}`)。
44
+ * 用于发现「模型跑偏去用网页端自带的联网搜索」。
45
+ */
46
+ platformTools: Record<string, number>;
47
+ }
48
+ /**
49
+ * 从 SSE 原始报文里一次性提取(正文, 思考, 是否完成, 平台内部工具计数)。
50
+ *
51
+ * ## 报文形态(实测)
52
+ *
53
+ * ```
54
+ * data: {"id":"...","conversation_id":"...","status":"init","parts":[
55
+ * {"role":"assistant","content":[
56
+ * {"type":"think","think":"..."},
57
+ * {"type":"text","text":"<tool_call>\n{\"name\": ...}"}
58
+ * ]}]}
59
+ * ```
60
+ *
61
+ * ## 四条关键事实(2026-09-27 首测、2026-09-29 修正)
62
+ *
63
+ * 1. `type=text` / `type=think` 的载荷**并不总是**「每事件都带全文」,
64
+ * 实测是两种形态混在一起、按 `logic_id`(一轮回答)组织:
65
+ * - **增量片段**:一轮内逐字推送的小片段(实测 1~60 字符,共数百条);
66
+ * - **累积快照**:该轮结束时再推一条**携带本轮全文**的片段。
67
+ * 判据:一轮结束时 `拼接总长 == 2 × 最长片段`(快照恰好等于所有增量之和)。
68
+ * 因此:
69
+ * - 轮次**正常结束**(快照已到)时「取最长」与「拼接」结果一致;
70
+ * - 轮次**被掐断**(空闲/硬超时,快照没来)时「取最长」只拿到一个中途片段
71
+ * (实测 最长 1787 / 拼接 11097,思考内容丢 84%,且长度再也不增长)。
72
+ * 本函数对 `think` 按 `logic_id` 累积(是快照就替换、是增量就追加),
73
+ * 两种形态都能拿到本轮全文。
74
+ * 2. **`status == "finish"` 是权威完成信号**:整轮结束时推送一条
75
+ * `status="finish"`(最后一条)。页面 DOM 的「重新生成」按钮检测在 ChatGLM
76
+ * 改版后已不可靠,完成判定应以本信号为主。
77
+ * 3. 该文本未经 Markdown 渲染,引号/下划线/空格都不会丢,
78
+ * 是「写文件 / 改代码」**唯一可用的内容来源**。
79
+ * 4. ⚠️ `text` **仍按「取最长」处理,不做累积拼接**:触发联网搜索时模型会
80
+ * **整段改写**回答(先给草稿、再换成最终答案),累积拼接会得到
81
+ * 「草稿 + 最终答案」的乱码(2026-09-27 22:17 事故)。
82
+ * `think` 不做改写,累积是安全的。
83
+ */
84
+ export declare function extractSseAll(raw: string): SseExtractResult;
85
+ /** `extractSseAll` 的兼容封装:只要正文与思考。 */
86
+ export declare function extractSseAnswer(raw: string): {
87
+ answer: string;
88
+ think: string;
89
+ };
90
+ /**
91
+ * 异常是否属于「浏览器/驱动连接已断开」类(而不是业务/解析错误)。
92
+ *
93
+ * 命中时应当让共享浏览器实例失效并重建,否则会一直复用坏实例
94
+ * (`isConnected()` 还没更新,不主动失效就只能重启服务)。
95
+ */
96
+ export declare function isBrowserConnectionError(e: unknown): boolean;
97
+ /**
98
+ * 计算「安全的」增量文本。
99
+ *
100
+ * `computeIncremental` 只是按长度切后缀,在两个真实场景下会产出**乱码**:
101
+ *
102
+ * 1. **模型中途改写回答**(实测):ChatGLM 的 `text` 字段并不是单调追加 ——
103
+ * 触发联网搜索时,先输出一句 78 字符的「我先…」,搜索回来后把整段**换掉**
104
+ * 成 2846 字符的最终答案。此时按长度切出来的是「从半句话中间开始」的碎片
105
+ * (实测客户端看到:``我先`.`requirements.txt`` 和 ``、`requirements.txt`` …)。
106
+ * 2. DOM 兜底路径下,思考正文被最终答案**替换**(长度变小)。
107
+ *
108
+ * 规则:
109
+ * - 前缀真的相同 → 切后缀(正常流式);
110
+ * - 新文本是旧文本的前缀(变短)→ 视为被替换,不出内容;
111
+ * - 前缀不同(被改写)→ 交出**整段新文本**
112
+ * (宁可让客户端看到重复,也绝不能给半句话的碎片)。
113
+ */
114
+ export declare function safeIncremental(lastText: string, currentText: string): string | undefined;
115
+ /** 空白页/非平台域名的判定(供 `checkLoginStatus` 决定是否导航)。 */
116
+ export declare function isBlankOrForeignUrl(url: string, host: string): boolean;
117
+ /** 记一条 SSE 解析诊断日志(保持与原实现同样的可观测性)。 */
118
+ export declare function logSseSummary(raw: string, result: SseExtractResult): void;
119
+ //# sourceMappingURL=chatglm-sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatglm-sse.d.ts","sourceRoot":"","sources":["../../src/providers/chatglm-sse.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,SAAS,EAAE,OAAO,EAAE,CAAC;CACtB;AAID,2BAA2B;AAC3B,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAI9D;AAED,mBAAmB;AACnB,wBAAgB,aAAa,IAAI,QAAQ,CAAC,UAAU,CAAC,CAEpD;AAED,2BAA2B;AAC3B,wBAAgB,eAAe,IAAI,IAAI,CAItC;AAMD;;;;;;GAMG;AACH,wBAAiB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsB5E;AAED,2BAA2B;AAC3B,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CA0E3D;AAED,qCAAqC;AACrC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAG/E;AAoBD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAM5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAMpB;AAED,iDAAiD;AACjD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtE;AAED,qCAAqC;AACrC,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAMzE"}