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.
- package/LICENSE +21 -0
- package/README.md +184 -2
- package/bin/webapi.mjs +45 -0
- package/dist/browser/auth-capture.d.ts +110 -0
- package/dist/browser/auth-capture.d.ts.map +1 -0
- package/dist/browser/auth-capture.js +487 -0
- package/dist/browser/auth-capture.js.map +1 -0
- package/dist/browser/credentials.d.ts +102 -0
- package/dist/browser/credentials.d.ts.map +1 -0
- package/dist/browser/credentials.js +209 -0
- package/dist/browser/credentials.js.map +1 -0
- package/dist/browser/js-eval.d.ts +44 -0
- package/dist/browser/js-eval.d.ts.map +1 -0
- package/dist/browser/js-eval.js +20 -0
- package/dist/browser/js-eval.js.map +1 -0
- package/dist/browser/login-flow.d.ts +171 -0
- package/dist/browser/login-flow.d.ts.map +1 -0
- package/dist/browser/login-flow.js +606 -0
- package/dist/browser/login-flow.js.map +1 -0
- package/dist/browser/manager.d.ts +144 -0
- package/dist/browser/manager.d.ts.map +1 -0
- package/dist/browser/manager.js +586 -0
- package/dist/browser/manager.js.map +1 -0
- package/dist/browser/platforms.d.ts +96 -0
- package/dist/browser/platforms.d.ts.map +1 -0
- package/dist/browser/platforms.js +288 -0
- package/dist/browser/platforms.js.map +1 -0
- package/dist/browser/sse-capture.d.ts +23 -0
- package/dist/browser/sse-capture.d.ts.map +1 -0
- package/dist/browser/sse-capture.js +199 -0
- package/dist/browser/sse-capture.js.map +1 -0
- package/dist/browser/stealth.d.ts +12 -0
- package/dist/browser/stealth.d.ts.map +1 -0
- package/dist/browser/stealth.js +12 -0
- package/dist/browser/stealth.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +441 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +90 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +246 -0
- package/dist/config.js.map +1 -0
- package/dist/controllers/history-db.d.ts +56 -0
- package/dist/controllers/history-db.d.ts.map +1 -0
- package/dist/controllers/history-db.js +260 -0
- package/dist/controllers/history-db.js.map +1 -0
- package/dist/controllers/request-pool.d.ts +78 -0
- package/dist/controllers/request-pool.d.ts.map +1 -0
- package/dist/controllers/request-pool.js +222 -0
- package/dist/controllers/request-pool.js.map +1 -0
- package/dist/controllers/router.d.ts +172 -0
- package/dist/controllers/router.d.ts.map +1 -0
- package/dist/controllers/router.js +569 -0
- package/dist/controllers/router.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/log/api-logger.d.ts +55 -0
- package/dist/log/api-logger.d.ts.map +1 -0
- package/dist/log/api-logger.js +264 -0
- package/dist/log/api-logger.js.map +1 -0
- package/dist/log/debug-logger.d.ts +49 -0
- package/dist/log/debug-logger.d.ts.map +1 -0
- package/dist/log/debug-logger.js +285 -0
- package/dist/log/debug-logger.js.map +1 -0
- package/dist/log/logger.d.ts +16 -0
- package/dist/log/logger.d.ts.map +1 -0
- package/dist/log/logger.js +35 -0
- package/dist/log/logger.js.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +90 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/providers/base.d.ts +83 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +51 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/chatglm-dom-extract.d.ts +24 -0
- package/dist/providers/chatglm-dom-extract.d.ts.map +1 -0
- package/dist/providers/chatglm-dom-extract.js +175 -0
- package/dist/providers/chatglm-dom-extract.js.map +1 -0
- package/dist/providers/chatglm-dom.d.ts +110 -0
- package/dist/providers/chatglm-dom.d.ts.map +1 -0
- package/dist/providers/chatglm-dom.js +384 -0
- package/dist/providers/chatglm-dom.js.map +1 -0
- package/dist/providers/chatglm-sse.d.ts +119 -0
- package/dist/providers/chatglm-sse.d.ts.map +1 -0
- package/dist/providers/chatglm-sse.js +241 -0
- package/dist/providers/chatglm-sse.js.map +1 -0
- package/dist/providers/chatglm.d.ts +288 -0
- package/dist/providers/chatglm.d.ts.map +1 -0
- package/dist/providers/chatglm.js +1631 -0
- package/dist/providers/chatglm.js.map +1 -0
- package/dist/routes/chat.d.ts +117 -0
- package/dist/routes/chat.d.ts.map +1 -0
- package/dist/routes/chat.js +1346 -0
- package/dist/routes/chat.js.map +1 -0
- package/dist/routes/compress.d.ts +142 -0
- package/dist/routes/compress.d.ts.map +1 -0
- package/dist/routes/compress.js +551 -0
- package/dist/routes/compress.js.map +1 -0
- package/dist/routes/failover.d.ts +34 -0
- package/dist/routes/failover.d.ts.map +1 -0
- package/dist/routes/failover.js +221 -0
- package/dist/routes/failover.js.map +1 -0
- package/dist/routes/helpers.d.ts +174 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +525 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/meta.d.ts +31 -0
- package/dist/routes/meta.d.ts.map +1 -0
- package/dist/routes/meta.js +184 -0
- package/dist/routes/meta.js.map +1 -0
- package/dist/routes/sse.d.ts +82 -0
- package/dist/routes/sse.d.ts.map +1 -0
- package/dist/routes/sse.js +77 -0
- package/dist/routes/sse.js.map +1 -0
- package/dist/server/app.d.ts +24 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +292 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/middleware/api-key.d.ts +61 -0
- package/dist/server/middleware/api-key.d.ts.map +1 -0
- package/dist/server/middleware/api-key.js +95 -0
- package/dist/server/middleware/api-key.js.map +1 -0
- package/dist/server/middleware/rate-limit.d.ts +67 -0
- package/dist/server/middleware/rate-limit.d.ts.map +1 -0
- package/dist/server/middleware/rate-limit.js +134 -0
- package/dist/server/middleware/rate-limit.js.map +1 -0
- package/dist/server/middleware/timeout.d.ts +34 -0
- package/dist/server/middleware/timeout.d.ts.map +1 -0
- package/dist/server/middleware/timeout.js +64 -0
- package/dist/server/middleware/timeout.js.map +1 -0
- package/dist/server/types.d.ts +20 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/tools/build.d.ts +14 -0
- package/dist/tools/build.d.ts.map +1 -0
- package/dist/tools/build.js +105 -0
- package/dist/tools/build.js.map +1 -0
- package/dist/tools/clean.d.ts +32 -0
- package/dist/tools/clean.d.ts.map +1 -0
- package/dist/tools/clean.js +65 -0
- package/dist/tools/clean.js.map +1 -0
- package/dist/tools/convert.d.ts +50 -0
- package/dist/tools/convert.d.ts.map +1 -0
- package/dist/tools/convert.js +289 -0
- package/dist/tools/convert.js.map +1 -0
- package/dist/tools/names.d.ts +138 -0
- package/dist/tools/names.d.ts.map +1 -0
- package/dist/tools/names.js +578 -0
- package/dist/tools/names.js.map +1 -0
- package/dist/tools/parse.d.ts +31 -0
- package/dist/tools/parse.d.ts.map +1 -0
- package/dist/tools/parse.js +273 -0
- package/dist/tools/parse.js.map +1 -0
- package/dist/tools/placeholder.d.ts +21 -0
- package/dist/tools/placeholder.d.ts.map +1 -0
- package/dist/tools/placeholder.js +135 -0
- package/dist/tools/placeholder.js.map +1 -0
- package/dist/tools/prompts.d.ts +42 -0
- package/dist/tools/prompts.d.ts.map +1 -0
- package/dist/tools/prompts.js +69 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/protocol.d.ts +58 -0
- package/dist/tools/protocol.d.ts.map +1 -0
- package/dist/tools/protocol.js +287 -0
- package/dist/tools/protocol.js.map +1 -0
- package/dist/tools/repair.d.ts +83 -0
- package/dist/tools/repair.d.ts.map +1 -0
- package/dist/tools/repair.js +510 -0
- package/dist/tools/repair.js.map +1 -0
- package/dist/tools/workspace.d.ts +40 -0
- package/dist/tools/workspace.d.ts.map +1 -0
- package/dist/tools/workspace.js +224 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/types/message.d.ts +87 -0
- package/dist/types/message.d.ts.map +1 -0
- package/dist/types/message.js +85 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/openai.d.ts +100 -0
- package/dist/types/openai.d.ts.map +1 -0
- package/dist/types/openai.js +66 -0
- package/dist/types/openai.js.map +1 -0
- package/dist/utils/errors.d.ts +82 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +115 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/mutex.d.ts +31 -0
- package/dist/utils/mutex.d.ts.map +1 -0
- package/dist/utils/mutex.js +61 -0
- package/dist/utils/mutex.js.map +1 -0
- package/dist/utils/prompt.d.ts +150 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +514 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/text.d.ts +30 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +70 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +67 -4
- package/postinstall.mjs +34 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGLM 的 SSE 原始流解析 —— `legacy-python/src/providers/chatglm_browser.py`
|
|
3
|
+
* 顶部的纯函数部分(第 38~207 行)。
|
|
4
|
+
*
|
|
5
|
+
* 拆出来的原因:这是整个项目**信息保真度的命门**(正文只能来自这里,不能读 DOM),
|
|
6
|
+
* 而它又是纯字符串处理 —— 独立成文件后可以完全不启动浏览器就写单测。
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ 改动本文件前请先读 `docs/hard-constraints.md` 的「`text` / `think` 载荷的语义」。
|
|
9
|
+
*/
|
|
10
|
+
import { logger } from '../log/logger.js';
|
|
11
|
+
import { rawDecodeJson } from '../tools/parse.js';
|
|
12
|
+
const lastRawSse = { text: '', think: '', toolCalls: [] };
|
|
13
|
+
/** 登记主轮 SSE 结果(仅主通道调用)。 */
|
|
14
|
+
export function setLastRawSse(patch) {
|
|
15
|
+
if (patch.text !== undefined)
|
|
16
|
+
lastRawSse.text = patch.text;
|
|
17
|
+
if (patch.think !== undefined)
|
|
18
|
+
lastRawSse.think = patch.think;
|
|
19
|
+
if (patch.toolCalls !== undefined)
|
|
20
|
+
lastRawSse.toolCalls = patch.toolCalls;
|
|
21
|
+
}
|
|
22
|
+
/** 读取主轮 SSE 结果。 */
|
|
23
|
+
export function getLastRawSse() {
|
|
24
|
+
return lastRawSse;
|
|
25
|
+
}
|
|
26
|
+
/** 清空主轮 SSE 结果(测试/诊断用)。 */
|
|
27
|
+
export function resetLastRawSse() {
|
|
28
|
+
lastRawSse.text = '';
|
|
29
|
+
lastRawSse.think = '';
|
|
30
|
+
lastRawSse.toolCalls = [];
|
|
31
|
+
}
|
|
32
|
+
/* ------------------------------------------------------------------ */
|
|
33
|
+
/* 报文解析 */
|
|
34
|
+
/* ------------------------------------------------------------------ */
|
|
35
|
+
/**
|
|
36
|
+
* 从 SSE 原始报文中迭代出所有 JSON 对象。
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ ChatGLM 会把多个 JSON 对象**直接拼接**在同一行(中间没有换行分隔),
|
|
39
|
+
* 所以**不能**「按行 `JSON.parse`」,必须从任意位置连续往后扫。
|
|
40
|
+
* 解析失败时只前进一个字符(跳过噪声),与 Python 的 `raw_decode` 循环等价。
|
|
41
|
+
*/
|
|
42
|
+
export function* iterSseJson(raw) {
|
|
43
|
+
if (!raw)
|
|
44
|
+
return;
|
|
45
|
+
let i = 0;
|
|
46
|
+
const n = raw.length;
|
|
47
|
+
while (i < n) {
|
|
48
|
+
const j = raw.indexOf('{', i);
|
|
49
|
+
if (j < 0)
|
|
50
|
+
break;
|
|
51
|
+
const decoded = rawDecodeJson(raw, j);
|
|
52
|
+
if (!decoded) {
|
|
53
|
+
i = j + 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const value = decoded.value;
|
|
57
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
58
|
+
yield value;
|
|
59
|
+
}
|
|
60
|
+
i = decoded.end;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 从 SSE 原始报文里一次性提取(正文, 思考, 是否完成, 平台内部工具计数)。
|
|
65
|
+
*
|
|
66
|
+
* ## 报文形态(实测)
|
|
67
|
+
*
|
|
68
|
+
* ```
|
|
69
|
+
* data: {"id":"...","conversation_id":"...","status":"init","parts":[
|
|
70
|
+
* {"role":"assistant","content":[
|
|
71
|
+
* {"type":"think","think":"..."},
|
|
72
|
+
* {"type":"text","text":"<tool_call>\n{\"name\": ...}"}
|
|
73
|
+
* ]}]}
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* ## 四条关键事实(2026-09-27 首测、2026-09-29 修正)
|
|
77
|
+
*
|
|
78
|
+
* 1. `type=text` / `type=think` 的载荷**并不总是**「每事件都带全文」,
|
|
79
|
+
* 实测是两种形态混在一起、按 `logic_id`(一轮回答)组织:
|
|
80
|
+
* - **增量片段**:一轮内逐字推送的小片段(实测 1~60 字符,共数百条);
|
|
81
|
+
* - **累积快照**:该轮结束时再推一条**携带本轮全文**的片段。
|
|
82
|
+
* 判据:一轮结束时 `拼接总长 == 2 × 最长片段`(快照恰好等于所有增量之和)。
|
|
83
|
+
* 因此:
|
|
84
|
+
* - 轮次**正常结束**(快照已到)时「取最长」与「拼接」结果一致;
|
|
85
|
+
* - 轮次**被掐断**(空闲/硬超时,快照没来)时「取最长」只拿到一个中途片段
|
|
86
|
+
* (实测 最长 1787 / 拼接 11097,思考内容丢 84%,且长度再也不增长)。
|
|
87
|
+
* 本函数对 `think` 按 `logic_id` 累积(是快照就替换、是增量就追加),
|
|
88
|
+
* 两种形态都能拿到本轮全文。
|
|
89
|
+
* 2. **`status == "finish"` 是权威完成信号**:整轮结束时推送一条
|
|
90
|
+
* `status="finish"`(最后一条)。页面 DOM 的「重新生成」按钮检测在 ChatGLM
|
|
91
|
+
* 改版后已不可靠,完成判定应以本信号为主。
|
|
92
|
+
* 3. 该文本未经 Markdown 渲染,引号/下划线/空格都不会丢,
|
|
93
|
+
* 是「写文件 / 改代码」**唯一可用的内容来源**。
|
|
94
|
+
* 4. ⚠️ `text` **仍按「取最长」处理,不做累积拼接**:触发联网搜索时模型会
|
|
95
|
+
* **整段改写**回答(先给草稿、再换成最终答案),累积拼接会得到
|
|
96
|
+
* 「草稿 + 最终答案」的乱码(2026-09-27 22:17 事故)。
|
|
97
|
+
* `think` 不做改写,累积是安全的。
|
|
98
|
+
*/
|
|
99
|
+
export function extractSseAll(raw) {
|
|
100
|
+
let answer = '';
|
|
101
|
+
let finished = false;
|
|
102
|
+
/** `logic_id` → 该轮已累积的思考文本。 */
|
|
103
|
+
const thinkGroups = new Map();
|
|
104
|
+
/** 保持出现顺序(Python 用 list 记录 key 顺序)。 */
|
|
105
|
+
const thinkOrder = [];
|
|
106
|
+
const platformTools = {};
|
|
107
|
+
for (const obj of iterSseJson(raw)) {
|
|
108
|
+
if (obj['status'] === 'finish')
|
|
109
|
+
finished = true;
|
|
110
|
+
const parts = obj['parts'];
|
|
111
|
+
if (!Array.isArray(parts))
|
|
112
|
+
continue;
|
|
113
|
+
for (const part of parts) {
|
|
114
|
+
if (!part || typeof part !== 'object')
|
|
115
|
+
continue;
|
|
116
|
+
const partRec = part;
|
|
117
|
+
const lid = (typeof partRec['logic_id'] === 'string' && partRec['logic_id']) ||
|
|
118
|
+
(typeof obj['id'] === 'string' ? obj['id'] : '') ||
|
|
119
|
+
'';
|
|
120
|
+
const contents = partRec['content'];
|
|
121
|
+
if (!Array.isArray(contents))
|
|
122
|
+
continue;
|
|
123
|
+
for (const item of contents) {
|
|
124
|
+
if (!item || typeof item !== 'object')
|
|
125
|
+
continue;
|
|
126
|
+
const c = item;
|
|
127
|
+
const ctype = c['type'];
|
|
128
|
+
if (ctype === 'text') {
|
|
129
|
+
const s = typeof c['text'] === 'string' ? c['text'] : '';
|
|
130
|
+
// ⚠️ 取最长,绝不累积(见上方事实 4)
|
|
131
|
+
if (s.length > answer.length)
|
|
132
|
+
answer = s;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (ctype === 'think') {
|
|
136
|
+
const s = typeof c['think'] === 'string' ? c['think'] : '';
|
|
137
|
+
if (!s)
|
|
138
|
+
continue;
|
|
139
|
+
const prev = thinkGroups.get(lid);
|
|
140
|
+
if (prev === undefined) {
|
|
141
|
+
thinkGroups.set(lid, s);
|
|
142
|
+
thinkOrder.push(lid);
|
|
143
|
+
}
|
|
144
|
+
else if (s.startsWith(prev)) {
|
|
145
|
+
thinkGroups.set(lid, s); // 累积快照 → 替换
|
|
146
|
+
}
|
|
147
|
+
else if (!prev.startsWith(s)) {
|
|
148
|
+
thinkGroups.set(lid, prev + s); // 增量片段 → 追加
|
|
149
|
+
}
|
|
150
|
+
// else:新片段是已累积内容的前缀(重复下发)→ 忽略
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (ctype === 'tool_calls') {
|
|
154
|
+
const tc = c['tool_calls'];
|
|
155
|
+
const name = tc && typeof tc === 'object' && !Array.isArray(tc)
|
|
156
|
+
? tc['name']
|
|
157
|
+
: undefined;
|
|
158
|
+
if (typeof name === 'string' && name) {
|
|
159
|
+
platformTools[name] = (platformTools[name] ?? 0) + 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const think = thinkOrder.map((lid) => thinkGroups.get(lid) ?? '').join('\n\n');
|
|
166
|
+
return { answer, think, finished, platformTools };
|
|
167
|
+
}
|
|
168
|
+
/** `extractSseAll` 的兼容封装:只要正文与思考。 */
|
|
169
|
+
export function extractSseAnswer(raw) {
|
|
170
|
+
const { answer, think } = extractSseAll(raw);
|
|
171
|
+
return { answer, think };
|
|
172
|
+
}
|
|
173
|
+
/* ------------------------------------------------------------------ */
|
|
174
|
+
/* 异常判定 / 增量计算 */
|
|
175
|
+
/* ------------------------------------------------------------------ */
|
|
176
|
+
/** 「浏览器/驱动连接已断开」类错误的特征串。 */
|
|
177
|
+
const CONNECTION_ERROR_MARKERS = [
|
|
178
|
+
'connection closed',
|
|
179
|
+
'connection is closed',
|
|
180
|
+
'target closed',
|
|
181
|
+
'target page, context or browser has been closed',
|
|
182
|
+
'browser has been closed',
|
|
183
|
+
'browser closed',
|
|
184
|
+
'juggler',
|
|
185
|
+
'protocol error',
|
|
186
|
+
'pipe closed',
|
|
187
|
+
'broken pipe',
|
|
188
|
+
];
|
|
189
|
+
/**
|
|
190
|
+
* 异常是否属于「浏览器/驱动连接已断开」类(而不是业务/解析错误)。
|
|
191
|
+
*
|
|
192
|
+
* 命中时应当让共享浏览器实例失效并重建,否则会一直复用坏实例
|
|
193
|
+
* (`isConnected()` 还没更新,不主动失效就只能重启服务)。
|
|
194
|
+
*/
|
|
195
|
+
export function isBrowserConnectionError(e) {
|
|
196
|
+
const name = e instanceof Error ? e.name : '';
|
|
197
|
+
const message = e instanceof Error ? e.message : typeof e === 'string' ? e : JSON.stringify(e ?? '');
|
|
198
|
+
const text = `${name}: ${message}`.toLowerCase();
|
|
199
|
+
return CONNECTION_ERROR_MARKERS.some((m) => text.includes(m));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 计算「安全的」增量文本。
|
|
203
|
+
*
|
|
204
|
+
* `computeIncremental` 只是按长度切后缀,在两个真实场景下会产出**乱码**:
|
|
205
|
+
*
|
|
206
|
+
* 1. **模型中途改写回答**(实测):ChatGLM 的 `text` 字段并不是单调追加 ——
|
|
207
|
+
* 触发联网搜索时,先输出一句 78 字符的「我先…」,搜索回来后把整段**换掉**
|
|
208
|
+
* 成 2846 字符的最终答案。此时按长度切出来的是「从半句话中间开始」的碎片
|
|
209
|
+
* (实测客户端看到:``我先`.`requirements.txt`` 和 ``、`requirements.txt`` …)。
|
|
210
|
+
* 2. DOM 兜底路径下,思考正文被最终答案**替换**(长度变小)。
|
|
211
|
+
*
|
|
212
|
+
* 规则:
|
|
213
|
+
* - 前缀真的相同 → 切后缀(正常流式);
|
|
214
|
+
* - 新文本是旧文本的前缀(变短)→ 视为被替换,不出内容;
|
|
215
|
+
* - 前缀不同(被改写)→ 交出**整段新文本**
|
|
216
|
+
* (宁可让客户端看到重复,也绝不能给半句话的碎片)。
|
|
217
|
+
*/
|
|
218
|
+
export function safeIncremental(lastText, currentText) {
|
|
219
|
+
if (!currentText)
|
|
220
|
+
return undefined;
|
|
221
|
+
if (!lastText)
|
|
222
|
+
return currentText;
|
|
223
|
+
if (currentText.startsWith(lastText))
|
|
224
|
+
return currentText.slice(lastText.length) || undefined;
|
|
225
|
+
if (lastText.startsWith(currentText))
|
|
226
|
+
return undefined;
|
|
227
|
+
return currentText;
|
|
228
|
+
}
|
|
229
|
+
/** 空白页/非平台域名的判定(供 `checkLoginStatus` 决定是否导航)。 */
|
|
230
|
+
export function isBlankOrForeignUrl(url, host) {
|
|
231
|
+
if (!url || url === 'about:blank')
|
|
232
|
+
return true;
|
|
233
|
+
return !url.includes(host);
|
|
234
|
+
}
|
|
235
|
+
/** 记一条 SSE 解析诊断日志(保持与原实现同样的可观测性)。 */
|
|
236
|
+
export function logSseSummary(raw, result) {
|
|
237
|
+
logger.info(`[chatglm] SSE 原始流捕获成功: 原始长度=${raw.length}, ` +
|
|
238
|
+
`回答长度=${result.answer.length}, 思考长度=${result.think.length}, ` +
|
|
239
|
+
`完成信号=${result.finished}, 平台内部工具=${JSON.stringify(result.platformTools)}`);
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=chatglm-sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatglm-sse.js","sourceRoot":"","sources":["../../src/providers/chatglm-sse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { type ChatRequest, type ChatResponse, BaseProvider } from './base.js';
|
|
2
|
+
export interface ChatGLMProviderOptions {
|
|
3
|
+
/**
|
|
4
|
+
* 使用哪个账号的凭证(`~/.ai-api/auth/chatglm/{accountId}.json`)。
|
|
5
|
+
* 主通道的账号由 `ModelRouter` **启动时随机分配**。
|
|
6
|
+
*/
|
|
7
|
+
accountId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* **辅助通道**标识。非空时本实例使用「独立 context + 该账号」的专用页面,
|
|
10
|
+
* 从而能与主通道**同时**工作且互不抢锁 —— 这是「后台预压缩与主对话并行」
|
|
11
|
+
* 以及「换账号绕开同账号并发限制」的基础。为空时是主通道。
|
|
12
|
+
*/
|
|
13
|
+
channelKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class ChatGLMBrowserProvider extends BaseProvider {
|
|
16
|
+
/** 本实例是不是「主通道」(服务用户真实对话的那个)。 */
|
|
17
|
+
private readonly isMainChannel;
|
|
18
|
+
/**
|
|
19
|
+
* 主轮全局(`_last_raw_sse_*` / `logs/chatglm_sse_*.txt`)只有主通道能写。
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ 判据是**角色**而不是「是否在专用页面上」:主通道被限流后切到另一个账号的
|
|
22
|
+
* 独立页面时,它依然是主通道,主轮全局仍由它写。
|
|
23
|
+
*/
|
|
24
|
+
private readonly ownsGlobals;
|
|
25
|
+
/** 允许「被限流时换账号重试」的只有主通道。 */
|
|
26
|
+
private readonly allowRotate;
|
|
27
|
+
private readonly MAX_ROTATE_PER_REQUEST;
|
|
28
|
+
/** 被平台限流后的账号冷却时长(实测约 1 分钟自动恢复,留余量取 120s)。 */
|
|
29
|
+
private readonly ROTATE_COOLDOWN;
|
|
30
|
+
/**
|
|
31
|
+
* 本轮是否撞到「平台限制提示」。由 `streamResponse` 设置,
|
|
32
|
+
* 由 `chatStream` 决定:能换账号就换号重试(客户端什么都还没收到,体验最好),
|
|
33
|
+
* 不能换才回传给客户端(`[ChatGLM 平台限制] …`)。
|
|
34
|
+
*/
|
|
35
|
+
private lastLimitNotice;
|
|
36
|
+
private page;
|
|
37
|
+
private readonly lock;
|
|
38
|
+
private responseJsInjected;
|
|
39
|
+
private cookiesLoaded;
|
|
40
|
+
private lastSendTime;
|
|
41
|
+
private readonly SEND_COOLDOWN;
|
|
42
|
+
/** 页面最后使用时间(空闲超时用)。 */
|
|
43
|
+
private lastUsedTime;
|
|
44
|
+
/** 30 分钟空闲 → 刷新页面。 */
|
|
45
|
+
private readonly PAGE_IDLE_TIMEOUT;
|
|
46
|
+
/**
|
|
47
|
+
* 页面当前那个对话属于哪个逻辑会话(`conversation_id`)。
|
|
48
|
+
* 与其一起记住页面对象:浏览器被重建 / 页面刷新后句柄会变,
|
|
49
|
+
* 此时必须当成「没有可复用的会话」。
|
|
50
|
+
*/
|
|
51
|
+
private sessionKey;
|
|
52
|
+
private sessionPage;
|
|
53
|
+
/** 辅助页面:专供两轮策略的 reparse 使用(独立对话,懒创建)。 */
|
|
54
|
+
private auxPage;
|
|
55
|
+
/**
|
|
56
|
+
* 网页输入框可接受的文本长度上限。
|
|
57
|
+
*
|
|
58
|
+
* 实测 ChatGLM:7 万字符仍可发送,8.2 万字符时 `textarea.value` 能写入
|
|
59
|
+
* 但按 Enter 不会提交(无报错、无新对话轮次)→ 表现为「空回复」。
|
|
60
|
+
* 取 64K:留出安全余量,同时尽量保留上下文。
|
|
61
|
+
*/
|
|
62
|
+
private readonly MAX_INPUT_LEN;
|
|
63
|
+
/**
|
|
64
|
+
* 「平台自带搜索刷屏」的提前收尾阈值:本轮平台 search `≥ 次数` 且已等待 `> 秒数`。
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ 2026-10-14 实测:模型把「bash 怎么删带空格的目录」当成需要检索的知识问题,
|
|
67
|
+
* 单轮在 ChatGLM 自带的 search 上刷了 **57 次**(45 个不同 query),耗掉 210s,
|
|
68
|
+
* 最后只吐出一段复述提示词的纯文本(零客户端工具调用)。
|
|
69
|
+
* 平台搜索对本地代码任务**永远没有价值**,而它一旦开始刷,等得越久亏得越多 ——
|
|
70
|
+
* 更致命的是把总预算吃光,让后面的 reparse / 续跑兜底没预算可用。
|
|
71
|
+
*/
|
|
72
|
+
private static readonly PLATFORM_SEARCH_GIVEUP_COUNT;
|
|
73
|
+
private static readonly PLATFORM_SEARCH_GIVEUP_SECONDS;
|
|
74
|
+
/** 页面开关探测是否已做过(每进程一次,见 {@link probeChatToggles})。 */
|
|
75
|
+
private togglesProbed;
|
|
76
|
+
/**
|
|
77
|
+
* `checkLoginStatus()` 被 router 在**每次请求**都调用,而它内部会做一次整页导航
|
|
78
|
+
* (约 6s)。缓存后同一会话内不再重复导航。
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ **只缓存「已登录」**:若把「未登录」也缓存,一次瞬时误判(页面正在重建 /
|
|
81
|
+
* DOM 未就绪)会让整个平台池失效 120s,表现为「聊着聊着突然不可用」。
|
|
82
|
+
*/
|
|
83
|
+
private loggedIn;
|
|
84
|
+
private lastLoginCheck;
|
|
85
|
+
private readonly LOGIN_CACHE_TTL;
|
|
86
|
+
/** 当前账号 ID(主通道可能被换号改写)。 */
|
|
87
|
+
private accountId;
|
|
88
|
+
/** 当前通道 key(主通道被换号后会变成 `chatglm:<newAccount>`)。 */
|
|
89
|
+
private channelKey;
|
|
90
|
+
constructor(options?: ChatGLMProviderOptions);
|
|
91
|
+
/** 页面空闲超过 30 分钟就刷新(避免长期挂着导致状态异常)。 */
|
|
92
|
+
private checkPageIdleTimeout;
|
|
93
|
+
/** 刷新到新的聊天会话。 */
|
|
94
|
+
private refreshPage;
|
|
95
|
+
/** 标记页面已使用(更新最后使用时间)。 */
|
|
96
|
+
markUsed(): Promise<void>;
|
|
97
|
+
/** 当前账号 ID(⚠️ 被限流换号后会变,不要缓存)。 */
|
|
98
|
+
getAccountId(): string;
|
|
99
|
+
/** 辅助通道标识(主通道返回 undefined)。 */
|
|
100
|
+
getChannelKey(): string | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* 确保页面就绪:页面 → 凭证 → 注入 JS。
|
|
103
|
+
*
|
|
104
|
+
* ⚠️⚠️ 三个「只做一次」的步骤**绝不能**嵌在 `if (!this.page)` 里(2026-09-27 事故):
|
|
105
|
+
* `checkLoginStatus()` 会先一步执行(router 在每次请求前都会调它),
|
|
106
|
+
* 它在内部 `getBrowser()` 后就把 `this.page` 赋好了 —— 于是本方法进来时
|
|
107
|
+
* `this.page` 已经有值,整个初始化块被跳过 → **`loadCookies()` 从来没被调用过**
|
|
108
|
+
* (日志里搜不到「已加载 N 个 Cookie」),浏览器一直以**访客**身份跑,
|
|
109
|
+
* 访客免费额度用完后平台就一直回「请求过于频繁,请稍后再试」。
|
|
110
|
+
*/
|
|
111
|
+
private ensureInitialized;
|
|
112
|
+
/**
|
|
113
|
+
* 辅助通道的页面准备:独立 context + 指定账号,且**每次辅助请求都重开干净对话**。
|
|
114
|
+
*
|
|
115
|
+
* 取舍理由(与旧 `_enter_aux_page()` 一致):
|
|
116
|
+
* - 避免上一轮的对话历史被带进这次压缩/转换(压缩提示词是自包含的);
|
|
117
|
+
* - 避免复用旧页面时输入框不可编辑 → `fill()` 无限等待 → 504(2026-09-28 事故)。
|
|
118
|
+
* 代价只有 1~2s,而且辅助通道与主对话并行,这点开销不吃主请求的预算。
|
|
119
|
+
*/
|
|
120
|
+
private ensureChannelPage;
|
|
121
|
+
/**
|
|
122
|
+
* 检查登录状态(带缓存 + 复用已有页面)。
|
|
123
|
+
*
|
|
124
|
+
* 性能说明:该方法被 `ModelRouter._getMixCandidates()` 在**每次请求**中调用。
|
|
125
|
+
* 原实现每次都做一次整页导航(实测约 6s),是请求耗时的大头。现在:
|
|
126
|
+
* - 已登录且近期检查过 → 直接返回缓存(0 开销)
|
|
127
|
+
* - 只有在尚未初始化页面时才真正导航一次,并复用该页面
|
|
128
|
+
*/
|
|
129
|
+
checkLoginStatus(): Promise<boolean>;
|
|
130
|
+
/**
|
|
131
|
+
* 探测页面是否处于**已登录**状态(访客不算登录)。
|
|
132
|
+
*
|
|
133
|
+
* ⚠️ 必须用 `querySelectorAll` 逐个判断可见性(脚本里已如此)——
|
|
134
|
+
* 只取第一个匹配元素时,若第一个 `[class*="user"]` 恰好是隐藏元素会误判为未登录。
|
|
135
|
+
*
|
|
136
|
+
* ⚠️ 更隐蔽的坑(2026-09-27 实测):**ChatGLM 未登录时会以「访客_xxxx」身份继续可用**
|
|
137
|
+
* (有少量免费额度),用完后接口就返回「请求过于频繁,请稍后再试」。而访客页面上
|
|
138
|
+
* `.user-info / [class*="user"]` 这些元素**同样可见** → 旧探测把访客误判成已登录 →
|
|
139
|
+
* 平台池一直"可用",实际每个请求都在烧访客额度,表现为莫名其妙的限流。
|
|
140
|
+
* 因此这里必须显式识别访客身份(看用户名标识,而不是"有没有用户元素")。
|
|
141
|
+
*/
|
|
142
|
+
private probeLoggedIn;
|
|
143
|
+
/**
|
|
144
|
+
* 发消息并等待回复,边等边 yield 增量。
|
|
145
|
+
*
|
|
146
|
+
* 本函数只**记录** `lastLimitNotice`,由 `chatStream` 决定是换号重试还是回传提示。
|
|
147
|
+
*/
|
|
148
|
+
private streamResponse;
|
|
149
|
+
/**
|
|
150
|
+
* 把 SSE 原文落盘。
|
|
151
|
+
*
|
|
152
|
+
* `..._last.txt` 永远写**最新**一轮;`..._longest.txt` 只在更长时覆盖 ——
|
|
153
|
+
* 旧实现只保留「更长的那份」,结果一个大样本会一直挡住后续样本,
|
|
154
|
+
* 失败报文根本没落盘,排查时看到的是早已过期的报文。
|
|
155
|
+
*/
|
|
156
|
+
private dumpSseSample;
|
|
157
|
+
/**
|
|
158
|
+
* 侦察:导出**平台自己的请求体**。
|
|
159
|
+
*
|
|
160
|
+
* ## 要回答的问题(决定架构走向)
|
|
161
|
+
* 网页端 Agent 与原生模型最大的差别在于:它只有**自己固定的工具集**
|
|
162
|
+
* (`search` / `open` / `click` / `execute_sandbox_code` / `finish`,且由平台服务端执行),
|
|
163
|
+
* 而原生模型接受调用方传入的 `tools` 并回传 `tool_calls`。
|
|
164
|
+
* 如果平台的 ask 接口**接受 tools / 函数定义**,我们就能把客户端工具直接交给它、
|
|
165
|
+
* 用它的原生函数调用通道承载,彻底扔掉「注入 `<tool_call>` 文本协议 + 正则解析 + 四层兜底」;
|
|
166
|
+
* 反之文本桥就删不掉。
|
|
167
|
+
*
|
|
168
|
+
* 因此这里把请求体原样落盘(`logs/chatglm_platform_request.txt`),
|
|
169
|
+
* 并在日志里打**顶层字段名**(最关键的结论:有没有 tools/functions 之类的入口)。
|
|
170
|
+
*/
|
|
171
|
+
private dumpPlatformRequests;
|
|
172
|
+
/**
|
|
173
|
+
* 发送前应用页面偏好:**关掉「联网搜索」**、**思考强度降为「快速」**。
|
|
174
|
+
*
|
|
175
|
+
* ⚠️ 这两项直接决定「模型会不会去刷平台自带的搜索」与「每轮要想多久」
|
|
176
|
+
* (实测默认开着时单轮 90~200s、`search` 刷 57 次)。失败只记日志,绝不阻断本轮。
|
|
177
|
+
*
|
|
178
|
+
* 逃生的口子:设 `WEBAIAPI_SKIP_PAGE_PREFS=1` 可跳过(排查用)。
|
|
179
|
+
*/
|
|
180
|
+
private applyChatPreferences;
|
|
181
|
+
/**
|
|
182
|
+
* 侦察:导出「联网搜索 / 深度思考」相关的页面开关与本地存储键。
|
|
183
|
+
*
|
|
184
|
+
* ## 要回答的问题(省时间的最大杠杆)
|
|
185
|
+
* 平台 ask 请求体里有 `meta_data.is_networking`(默认 true)与
|
|
186
|
+
* `chat_mode=deep_thinking` + `reasoning_effort=max`:
|
|
187
|
+
* 前者让模型不停去调平台自带的联网搜索(`search` 单轮 57 次、45 个 query),
|
|
188
|
+
* 后者让每回合要「想」90~200s。它们都是**页面 UI 状态**,
|
|
189
|
+
* 只有点到对应开关才能在发请求前改掉。
|
|
190
|
+
*
|
|
191
|
+
* 探测结果落盘 `logs/chatglm_page_toggles.txt`(每进程一次)。
|
|
192
|
+
*/
|
|
193
|
+
private probeChatToggles;
|
|
194
|
+
/** ChatGLM 专用思考检测。 */
|
|
195
|
+
private detectThinking;
|
|
196
|
+
/** ChatGLM 专用完成检测(DOM 兜底;权威信号是 SSE 的 `status == "finish"`)。 */
|
|
197
|
+
private detectCompletion;
|
|
198
|
+
/** 页面**最后一条** `.answer` 是否就是平台并发/限流提示。 */
|
|
199
|
+
private pageShowsBusyNotice;
|
|
200
|
+
/** 读取平台限制提示原文(没有则空串)。 */
|
|
201
|
+
private readBusyNotice;
|
|
202
|
+
/** 清空页面上的 SSE 捕获缓冲(每轮请求前调用)。 */
|
|
203
|
+
private resetSseCapture;
|
|
204
|
+
/** 读取页面捕获到的 SSE 原始文本(未经 Markdown 渲染,内容保真)。 */
|
|
205
|
+
private readSseCapture;
|
|
206
|
+
/** 页面是否存在聊天输入框(用于判断页面是否处于可聊天的状态)。 */
|
|
207
|
+
private hasInputBox;
|
|
208
|
+
/** 统计当前对话轮数(用于校验消息是否真正发出)。 */
|
|
209
|
+
private countConversationItems;
|
|
210
|
+
/** 读取输入框当前的文本长度(-1 = 未找到输入框)。 */
|
|
211
|
+
private inputValueLen;
|
|
212
|
+
/** 输入框是否已被清空(ChatGLM 提交成功后会清空输入框)。 */
|
|
213
|
+
private isInputCleared;
|
|
214
|
+
/**
|
|
215
|
+
* 等待发送成功的**任一**信号:
|
|
216
|
+
* 1. 出现新的对话轮次(`.conversation-item` 数量增加)
|
|
217
|
+
* 2. 输入框被清空(ChatGLM 提交后会清空输入框)
|
|
218
|
+
*
|
|
219
|
+
* 两个信号取「或」:不同页面版本下有时只有其中一个可用。
|
|
220
|
+
*/
|
|
221
|
+
private waitSendSuccess;
|
|
222
|
+
/** 查找发送按钮(部分情况下 Enter 不生效,需要点击按钮)。 */
|
|
223
|
+
private findSendButton;
|
|
224
|
+
/**
|
|
225
|
+
* 关闭可能遮挡输入框的浮层/对话框。
|
|
226
|
+
*
|
|
227
|
+
* ChatGLM 基于 Element UI,页面会偶发出现遮罩层(loading/公告弹窗),
|
|
228
|
+
* 导致 Playwright 点击输入框或发送按钮时被拦截
|
|
229
|
+
* (报错 "subtree intercepts pointer events"),消息因此发不出去。
|
|
230
|
+
*/
|
|
231
|
+
private dismissBlockingOverlay;
|
|
232
|
+
/**
|
|
233
|
+
* 发送消息。
|
|
234
|
+
*
|
|
235
|
+
* 与旧版差异:
|
|
236
|
+
* - 发送后**校验**是否真的产生了新对话轮次(Enter 可能未提交)
|
|
237
|
+
* - 校验失败时最多重试 1 次,第二次改试「点击发送按钮」
|
|
238
|
+
* - 多次失败直接抛异常,避免上游拿到空回复却以为是模型没说话
|
|
239
|
+
*
|
|
240
|
+
* @param sessionKey 逻辑会话 ID;发送成功后记到 `this.sessionKey`,
|
|
241
|
+
* 供下一次请求判断能否沿用同一个网页会话
|
|
242
|
+
* @param reuseSession true 表示本次发的就是增量、要**续写**当前会话(不导航)
|
|
243
|
+
*/
|
|
244
|
+
private sendMessage;
|
|
245
|
+
/** 创建新会话(ChatGLM 不需要,返回固定 ID)。 */
|
|
246
|
+
createConversation(): Promise<string>;
|
|
247
|
+
/** 删除会话(ChatGLM 不需要实现)。 */
|
|
248
|
+
deleteConversation(_conversationId: string): Promise<void>;
|
|
249
|
+
/** 非流式聊天(调用 `streamResponse` 并聚合)。 */
|
|
250
|
+
chat(request: ChatRequest): Promise<ChatResponse>;
|
|
251
|
+
/**
|
|
252
|
+
* 流式聊天。
|
|
253
|
+
*
|
|
254
|
+
* **必须串行化**:整个 provider 只有 1 个浏览器页面,若两个请求同时在页面上
|
|
255
|
+
* 发消息,ChatGLM 会返回「请等待其他对话生成完毕 / 本次回答已被终止 / 重新回答」,
|
|
256
|
+
* 并且该提示会被当成正常回复返回给客户端。
|
|
257
|
+
*/
|
|
258
|
+
chatStream(request: ChatRequest): AsyncGenerator<ChatResponse>;
|
|
259
|
+
/** 同平台所有可用账号 ID(排除没有 Cookie 的)。 */
|
|
260
|
+
private accountCandidates;
|
|
261
|
+
/**
|
|
262
|
+
* 挑一个可以接手的账号。
|
|
263
|
+
*
|
|
264
|
+
* 跳过:本轮已经试过的、正在冷却的、此刻正被别的角色(辅助通道)使用的。
|
|
265
|
+
* 被**闲置登记**(`role` 有记录但此刻不忙)占着的账号允许抢占 ——
|
|
266
|
+
* 辅助通道会在下一次挑账号时自动避开它。
|
|
267
|
+
*/
|
|
268
|
+
private pickRotateAccount;
|
|
269
|
+
/**
|
|
270
|
+
* 把主通道切到指定账号(走「独立 context 的专用页面」)。
|
|
271
|
+
*
|
|
272
|
+
* ⚠️ 必须换 context:不同账号 = 不同 Cookie jar。沿用全局页面会把
|
|
273
|
+
* 新账号的 Cookie 加到旧账号的 context 上,等于没换。
|
|
274
|
+
*/
|
|
275
|
+
private rotateToAccount;
|
|
276
|
+
/** 请求进门时,若当前账号还在冷却中就直接换号(省一轮注定失败的往返)。 */
|
|
277
|
+
private rotateIfCoolingOnEntry;
|
|
278
|
+
/**
|
|
279
|
+
* 切到辅助页面,并把主页面状态原样收好(返回 saved,失败返回 null)。
|
|
280
|
+
*
|
|
281
|
+
* 辅助页面是**懒创建**且长期保留的:它专供 reparse 使用,
|
|
282
|
+
* 每次 reparse 都会在它上面开一个新对话(不影响主页面上的会话)。
|
|
283
|
+
*/
|
|
284
|
+
private enterAuxPage;
|
|
285
|
+
/** 从辅助页面切回主页面(主会话上下文完好,可继续复用)。 */
|
|
286
|
+
private leaveAuxPage;
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=chatglm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatglm.d.ts","sourceRoot":"","sources":["../../src/providers/chatglm.ts"],"names":[],"mappings":"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"}
|