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,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 跨平台故障切换 —— `legacy-python/src/api/routes.py` 第 1038~1265 行的移植。
|
|
3
|
+
*
|
|
4
|
+
* 包含:
|
|
5
|
+
* - {@link buildReparseInput}:构造「两轮策略」的输入(合并 SSE 回答 + 思考 + DOM);
|
|
6
|
+
* - {@link streamWithFailover}:带故障切换的流式生成器。
|
|
7
|
+
*
|
|
8
|
+
* ## ⚠️ 本文件最重要的一条约束:生成器**必须被关闭**
|
|
9
|
+
* Python 用 `async with aclosing(provider.chat_stream(...))` 包住 `async for`,
|
|
10
|
+
* 理由写在原注释里:「确保提前退出时也关闭 provider 生成器,否则 provider 内部的
|
|
11
|
+
* 串行锁(`self._lock`)不会释放,**后续请求会永久阻塞**」。
|
|
12
|
+
*
|
|
13
|
+
* TypeScript 的 `for await...of` **在语法层面**就保证这件事:无论循环是正常结束、
|
|
14
|
+
* `break`、还是循环体抛异常,迭代器的 `return()` 都会被调用 ——
|
|
15
|
+
* 而 `async *chatStream()` 里的 `finally`(释放锁)一定执行。
|
|
16
|
+
* 所以这里不需要 `aclosing` 的等价物,**但绝不能把 `for await` 换成手动
|
|
17
|
+
* `while (true) { iterator.next() }`**,那样就丢掉了这层语言级保障。
|
|
18
|
+
*/
|
|
19
|
+
import { getLastRawSse } from '../providers/chatglm-sse.js';
|
|
20
|
+
import { logger as baseLogger } from '../log/logger.js';
|
|
21
|
+
import { logAiPlatformRequest } from '../log/api-logger.js';
|
|
22
|
+
import { errorMessage, PlatformUnavailableError } from '../utils/errors.js';
|
|
23
|
+
import { extractPlatformName, formatHistoryContext, platformOf, truncatePromptForBrowser, } from './helpers.js';
|
|
24
|
+
const logger = baseLogger;
|
|
25
|
+
/** 支持网页会话复用(增量发送)的平台。 */
|
|
26
|
+
const SESSION_REUSE_PLATFORMS = new Set(['chatglm']);
|
|
27
|
+
/**
|
|
28
|
+
* 构造「两轮对话」策略的输入文本。
|
|
29
|
+
*
|
|
30
|
+
* 与展示给客户端的文本**目的不同**:
|
|
31
|
+
* - 展示:只能用 SSE 的最终回答(`type=text`),不能把思考过程泄漏出去;
|
|
32
|
+
* - 恢复工具调用:**信号越多越好**。
|
|
33
|
+
*
|
|
34
|
+
* 实测失败案例:模型把完整工具意图写在**思考**里(`type=think` 有 3339 字符,
|
|
35
|
+
* 例如「我需要:1. 用 list_dir 查看项目结构 并读取关键文件:core/backtest_engine.py…」),
|
|
36
|
+
* 而最终回答只有 77 个字符(没有 `<tool_call>`)。
|
|
37
|
+
* 此时若只把 77 字符的回答交给转换器,必然恢复不出任何工具调用。
|
|
38
|
+
*
|
|
39
|
+
* 因此这里把 SSE 回答、SSE 思考、DOM 拼接文本合并去重后一起交给转换器。
|
|
40
|
+
*/
|
|
41
|
+
export function buildReparseInput(parseSource, fullResponse) {
|
|
42
|
+
// 思考内容来自 provider 写下的「主轮全局」。
|
|
43
|
+
// ⚠️ 辅助通道不写这些全局(见 chatglm.ts 的说明),所以这里拿到的永远
|
|
44
|
+
// 是**主轮**的思考,不会被并行的压缩请求污染。
|
|
45
|
+
let think = '';
|
|
46
|
+
try {
|
|
47
|
+
think = getLastRawSse().think ?? '';
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// 忽略:拿不到思考就用剩下的两个信号
|
|
51
|
+
}
|
|
52
|
+
let ordered = [];
|
|
53
|
+
for (const piece of [parseSource, think, fullResponse]) {
|
|
54
|
+
const p = (piece ?? '').trim();
|
|
55
|
+
if (!p)
|
|
56
|
+
continue;
|
|
57
|
+
// 已包含该片段,跳过
|
|
58
|
+
if (ordered.some((q) => p === q || q.includes(p)))
|
|
59
|
+
continue;
|
|
60
|
+
// 新片段包含旧片段 → 用更完整的替换
|
|
61
|
+
ordered = ordered.filter((q) => !p.includes(q));
|
|
62
|
+
ordered.push(p);
|
|
63
|
+
}
|
|
64
|
+
return ordered.join('\n\n');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 带跨平台故障切换的流式响应生成器。
|
|
68
|
+
*
|
|
69
|
+
* 策略:
|
|
70
|
+
* - `mix` 模型:按优先级依次尝试各平台,当前平台失败后切换到下一个候选平台。
|
|
71
|
+
* 跨平台切换时从 `history_db` 注入最近 N 条历史作为上下文。
|
|
72
|
+
* - 平台名模型(如 `chatglm`):直接调用对应平台的 Browser Provider。
|
|
73
|
+
*
|
|
74
|
+
* @throws {PlatformUnavailableError} 所有候选平台都失败
|
|
75
|
+
*/
|
|
76
|
+
export async function* streamWithFailover(modelRouter, model, chatRequest, historyDb, requestId = 'unknown') {
|
|
77
|
+
const originalMessage = chatRequest.message;
|
|
78
|
+
const convId = chatRequest.conversation_id ?? null;
|
|
79
|
+
/* ===== 辅助任务(压缩 / 两轮转换 / 自驱动续跑)走「独立辅助通道」=====
|
|
80
|
+
* 独立通道 = 同平台的**另一个账号** + 独立 context 页面 + 独立锁,
|
|
81
|
+
* 因此可以与主对话真并行,也不会占用主账号的并发额度。
|
|
82
|
+
* 拿不到独立通道时列表为空 → 完全保持原行为(回落主候选,串行)。 */
|
|
83
|
+
const reqCtx = chatRequest.context ?? {};
|
|
84
|
+
const wantAux = Boolean(reqCtx['use_aux_page']);
|
|
85
|
+
const auxCandidates = wantAux ? modelRouter.getAuxCandidates() : [];
|
|
86
|
+
logger.info(`[_stream_with_failover] 入口, model=${model}` +
|
|
87
|
+
(auxCandidates.length > 0
|
|
88
|
+
? `(辅助任务 → 独立通道 ${auxCandidates.map((c) => c[0]).join(',')})`
|
|
89
|
+
: ''));
|
|
90
|
+
if (model === 'mix') {
|
|
91
|
+
logger.info('[_stream_with_failover] 进入 mix 分支');
|
|
92
|
+
const candidates = await modelRouter.getMixCandidates();
|
|
93
|
+
// 独立通道优先;它万一失败再回落到主候选(此时与主对话串行,
|
|
94
|
+
// 功能不退化,只是会慢一些)
|
|
95
|
+
const all = auxCandidates.length > 0 ? [...auxCandidates, ...candidates] : candidates;
|
|
96
|
+
if (all.length === 0) {
|
|
97
|
+
throw new PlatformUnavailableError('mix', '所有平台均不可用,请运行 `npx webaiapi login chatglm` 捕获凭证');
|
|
98
|
+
}
|
|
99
|
+
let lastError = null;
|
|
100
|
+
for (let idx = 0; idx < all.length; idx += 1) {
|
|
101
|
+
const candidate = all[idx];
|
|
102
|
+
if (!candidate)
|
|
103
|
+
continue;
|
|
104
|
+
const [platform, provider] = candidate;
|
|
105
|
+
/* ===== 会话复用:只补发增量 =====
|
|
106
|
+
* 仅对**第一个候选平台**且该平台支持网页会话复用(chatglm)时启用;
|
|
107
|
+
* 跨平台切换时绝不能用增量 —— 另一个平台的会话里没有历史。 */
|
|
108
|
+
const ctx = chatRequest.context ?? {};
|
|
109
|
+
const delta = typeof ctx['delta_message'] === 'string' ? ctx['delta_message'] : '';
|
|
110
|
+
if (delta && idx === 0 && SESSION_REUSE_PLATFORMS.has(platformOf(platform))) {
|
|
111
|
+
chatRequest.message = delta;
|
|
112
|
+
logger.info(`[session] 会话 ${convId} 复用 ${platform} 网页会话,` +
|
|
113
|
+
`只发增量 ${delta.length} 字符(完整提示词 ${originalMessage.length} 字符)`);
|
|
114
|
+
}
|
|
115
|
+
else if (idx === 0) {
|
|
116
|
+
chatRequest.message = originalMessage;
|
|
117
|
+
}
|
|
118
|
+
/* 跨平台切换时注入历史上下文(首次尝试不注入,依赖平台自身 session)。
|
|
119
|
+
* ⚠️ 辅助任务(压缩/转换)的提示词是**自包含**的:往里塞历史上下文
|
|
120
|
+
* 只会污染它(例如把对话历史塞进「压缩这段历史」的提示里)。 */
|
|
121
|
+
if (idx > 0 && convId && !wantAux) {
|
|
122
|
+
const history = await historyDb.getRecentMessages(convId, 20);
|
|
123
|
+
if (history.length > 0) {
|
|
124
|
+
const context = formatHistoryContext(history);
|
|
125
|
+
chatRequest.message = truncatePromptForBrowser(`${context}\n\n[当前消息]\n${originalMessage}`);
|
|
126
|
+
logger.info(`mix 跨平台切换到 ${platform},注入 ${history.length} 条历史上下文`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
chatRequest.message = originalMessage;
|
|
130
|
+
logger.info(`mix 跨平台切换到 ${platform}(无历史可注入)`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ⚠️ `started` 必须声明在 try **之外**:TS 的 `catch` 是独立作用域,
|
|
134
|
+
// 在 `try` 里用 `let` 声明的变量在 `catch` 中不可见
|
|
135
|
+
// (Python 的 try/except 不引入新作用域,所以原实现写在 try 里也没问题)。
|
|
136
|
+
let started = false;
|
|
137
|
+
try {
|
|
138
|
+
// API 日志:记录发送给 AI 平台的完整内容(发送前记录)
|
|
139
|
+
logAiPlatformRequest({
|
|
140
|
+
requestId,
|
|
141
|
+
platform,
|
|
142
|
+
messages: [{ role: 'user', content: chatRequest.message }],
|
|
143
|
+
});
|
|
144
|
+
let emptyError = null;
|
|
145
|
+
// ⚠️ `for await` 在 break/throw/return 时都会调用生成器的 return()
|
|
146
|
+
// → provider 的 finally 释放串行锁。不要改成手动 next()。
|
|
147
|
+
for await (const chunk of provider.chatStream(chatRequest)) {
|
|
148
|
+
// 纯错误(无任何内容)视为本次尝试失败,允许切换到下一个平台
|
|
149
|
+
if (chunk.error && !(chunk.content ?? '').trim()) {
|
|
150
|
+
emptyError = chunk.error;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
started = true;
|
|
154
|
+
yield [chunk, platform];
|
|
155
|
+
}
|
|
156
|
+
if (emptyError !== null) {
|
|
157
|
+
throw new PlatformUnavailableError(platform, `返回空内容: ${emptyError}`);
|
|
158
|
+
}
|
|
159
|
+
return; // 成功完成
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (started) {
|
|
163
|
+
// 已开始 yield,不能切换(客户端已收到部分响应)
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
lastError = error;
|
|
167
|
+
logger.warn(`mix 跨平台切换:平台 ${platform} 失败 - ` +
|
|
168
|
+
`${error instanceof Error ? error.name : 'Error'}: ${errorMessage(error)}`);
|
|
169
|
+
chatRequest.message = originalMessage; // 重置消息
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// 所有平台都失败
|
|
173
|
+
throw new PlatformUnavailableError('mix', lastError ? errorMessage(lastError) : 'all platforms failed');
|
|
174
|
+
}
|
|
175
|
+
/* ===== 平台名模型:直接调用对应平台的 Browser Provider ===== */
|
|
176
|
+
logger.info(`[_stream_with_failover] 进入 else 分支 (platform model), model=${model}`);
|
|
177
|
+
let provider = await modelRouter.getProvider(model);
|
|
178
|
+
const platform = extractPlatformName(provider.platform);
|
|
179
|
+
logger.info(`[chatglm] _stream_with_failover: 获取到 provider=${provider.platform}, platform=${platform}`);
|
|
180
|
+
// 辅助任务优先走独立通道(另一个账号 + 独立页面 → 与主对话并行)
|
|
181
|
+
if (auxCandidates.length > 0) {
|
|
182
|
+
const auxCandidate = auxCandidates[0];
|
|
183
|
+
if (auxCandidate && platformOf(auxCandidate[0]) === platformOf(platform)) {
|
|
184
|
+
logger.info(`[_stream_with_failover] 辅助任务改用独立通道 ${auxCandidate[0]}` +
|
|
185
|
+
`(账号 ${auxCandidate[1].getAccountId()})`);
|
|
186
|
+
provider = auxCandidate[1];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
logger.info('[chatglm] _stream_with_failover: 开始调用 provider.chat_stream()');
|
|
190
|
+
// 会话复用:同一会话只补发增量(provider 若无法复用会自动改发完整提示词)
|
|
191
|
+
const ctx = chatRequest.context ?? {};
|
|
192
|
+
const delta = typeof ctx['delta_message'] === 'string' ? ctx['delta_message'] : '';
|
|
193
|
+
if (delta && SESSION_REUSE_PLATFORMS.has(platformOf(platform))) {
|
|
194
|
+
chatRequest.message = delta;
|
|
195
|
+
logger.info(`[session] 会话 ${convId} 复用 ${platform} 网页会话,` +
|
|
196
|
+
`只发增量 ${delta.length} 字符(完整提示词 ${originalMessage.length} 字符)`);
|
|
197
|
+
}
|
|
198
|
+
// API 日志:记录 AI 平台请求(平台名模型,发送前记录)
|
|
199
|
+
logAiPlatformRequest({
|
|
200
|
+
requestId,
|
|
201
|
+
platform,
|
|
202
|
+
messages: [{ role: 'user', content: chatRequest.message }],
|
|
203
|
+
});
|
|
204
|
+
let emptyError = null;
|
|
205
|
+
for await (const chunk of provider.chatStream(chatRequest)) {
|
|
206
|
+
// 纯错误(无内容)记录下来,循环结束后再抛出,
|
|
207
|
+
// 保证生成器被正常关闭(否则 provider 的串行锁不会释放)
|
|
208
|
+
if (chunk.error && !(chunk.content ?? '').trim()) {
|
|
209
|
+
emptyError = chunk.error;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
logger.debug(`[chatglm] _stream_with_failover: yield chunk, content_len=${chunk.content?.length ?? 0}`);
|
|
213
|
+
yield [chunk, platform];
|
|
214
|
+
}
|
|
215
|
+
if (emptyError !== null) {
|
|
216
|
+
// 让上层把真实原因返回给客户端,避免「空回复 + 无任何说明」
|
|
217
|
+
throw new PlatformUnavailableError(platform, `返回空内容: ${emptyError}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export { extractPlatformName };
|
|
221
|
+
//# sourceMappingURL=failover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover.js","sourceRoot":"","sources":["../../src/routes/failover.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { type ChatMessage } from '../types/message.js';
|
|
2
|
+
/**
|
|
3
|
+
* 估算文本的 token 数。
|
|
4
|
+
*
|
|
5
|
+
* 启发式(与原实现一致):中文约 0.5 token/字,英文约 0.25 token/词。
|
|
6
|
+
* 仅用于 OpenAI 兼容响应的 `usage` 字段,不参与任何决策。
|
|
7
|
+
*/
|
|
8
|
+
export declare function estimateTokens(text: string): number;
|
|
9
|
+
/** 构建 OpenAI 兼容的 usage 对象。 */
|
|
10
|
+
export declare function buildUsage(promptText: string, completionText: string): {
|
|
11
|
+
prompt_tokens: number;
|
|
12
|
+
completion_tokens: number;
|
|
13
|
+
total_tokens: number;
|
|
14
|
+
};
|
|
15
|
+
/** 构建 OpenAI 兼容的错误响应体。 */
|
|
16
|
+
export declare function openaiErrorResponse(message: string, errorType?: string, code?: string | null): {
|
|
17
|
+
error: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* 把历史消息格式化为上下文字符串(跨平台切换时注入,保持对话连续性)。
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatHistoryContext(history: Array<Record<string, unknown>>): string;
|
|
23
|
+
/**
|
|
24
|
+
* 从消息列表里提取用户**真正的问题**。
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ 多轮工具调用场景下,`messages` 的最后一条往往是 tool 结果或
|
|
27
|
+
* `<system-reminder>`,**不能直接取 `messages[-1]`**。
|
|
28
|
+
*
|
|
29
|
+
* 优先级:
|
|
30
|
+
* 1. 最后一条含 `<user_query>...</user_query>` 的用户消息里的 query 内容;
|
|
31
|
+
* 2. 最后一条 role=user 且不以 `<system-reminder>` 开头的消息(完整内容);
|
|
32
|
+
* 3. 兜底:最后一条 role=user 的消息。
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractUserQuestion(messages: ReadonlyArray<ChatMessage | MessageDictLike>): string;
|
|
35
|
+
/** 宽松的消息形状(调用方可能传 pydantic 对象或裸 dict)。 */
|
|
36
|
+
export type MessageDictLike = Record<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* 剔掉「平台自带工具」的议论句,保留模型真正想做的事。
|
|
39
|
+
*
|
|
40
|
+
* 粒度是**句子**而不是整行:同一行里既有议论又有实质(例如「浏览器工具不可用;用 bash 删除
|
|
41
|
+
* /a/b」)时,只丢议论那一句,绝不整行丢 —— 否则会连着真命令一起丢掉。
|
|
42
|
+
*/
|
|
43
|
+
export declare function stripPlatformToolNoise(text: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* 从消息列表**尾部**抽取「最近进展」,给转换轮当上下文。
|
|
46
|
+
*
|
|
47
|
+
* ⚠️ 2026-10-14:转换轮原来只有「模型自己的自言自语 + 一个取错的用户请求」,
|
|
48
|
+
* 完全不知道进行到哪了(上一轮写了哪个文件、跑过什么命令),自然转不出正确的下一步。
|
|
49
|
+
* `buildReparsePrompt` 的 `conversationHistory` 只被用来**抽文件路径**,从没展示给模型。
|
|
50
|
+
*
|
|
51
|
+
* @param messages 完整消息列表(越靠后越新)
|
|
52
|
+
* @returns 形如 `用户: …` / `AI 调用工具: write_file` / `工具结果: 已写入 …` 的多行文本
|
|
53
|
+
*/
|
|
54
|
+
export declare function formatRecentProgress(messages: ReadonlyArray<ChatMessage | MessageDictLike>, options?: {
|
|
55
|
+
maxEntries?: number;
|
|
56
|
+
maxCharsPerEntry?: number;
|
|
57
|
+
maxTotalChars?: number;
|
|
58
|
+
}): string;
|
|
59
|
+
/**
|
|
60
|
+
* 网页端输入框长度上限(实测)。
|
|
61
|
+
*
|
|
62
|
+
* ChatGLM 输入框填 8.2 万字符时:`textarea.value` 正常写入,但按 Enter
|
|
63
|
+
* **不会提交**(不报错、不产生新对话轮次),表现为「模型未返回任何内容」。
|
|
64
|
+
* 实测 7 万字符仍可发送;这里取 64K(留安全余量,同时尽量保留上下文)。
|
|
65
|
+
*/
|
|
66
|
+
export declare const BROWSER_PROMPT_MAX_LEN = 64000;
|
|
67
|
+
/**
|
|
68
|
+
* 整个 HTTP 请求的时间预算(秒)。
|
|
69
|
+
*
|
|
70
|
+
* 必须 < 超时中间件的 300s,并留出把 `[DONE]` 写回客户端的时间。
|
|
71
|
+
*
|
|
72
|
+
* 为什么要设总预算:一次请求内部可能串起**多个完整对话往返** ——
|
|
73
|
+
* 首轮对话 + 两轮策略的 reparse。各轮在 provider 内部各自有 240s 硬超时,
|
|
74
|
+
* 不设总预算则合计必然超 300s:实测首轮 176s + reparse 114s,
|
|
75
|
+
* 被中间件在 300s 处掐断 → 客户端拿到 504,连首轮已经拿到的文本都没有。
|
|
76
|
+
*/
|
|
77
|
+
export declare const TOTAL_TIME_BUDGET = 270;
|
|
78
|
+
/**
|
|
79
|
+
* 历史压缩的预算门槛(秒)。
|
|
80
|
+
*
|
|
81
|
+
* 「压缩历史」不是免费的:一份 8 万字符的提示词要压缩 2 次,每次都是一轮完整对话,
|
|
82
|
+
* 实测烧掉 117s,剩下的 150s 只够主对话跑一次 → 「两轮策略」因剩余时间不足被跳过,
|
|
83
|
+
* 模型只交一份计划书就结束回合 → 用户看到「读了半天、一个文件都没改、任务终止」。
|
|
84
|
+
*
|
|
85
|
+
* 对策:剩余预算低于本值就不压缩,改用快速中间截断,把时间留给真正干活的环节。
|
|
86
|
+
*/
|
|
87
|
+
export declare const COMPRESS_MIN_BUDGET = 170;
|
|
88
|
+
/** 单块压缩的时间上限(秒),避免无限等待。 */
|
|
89
|
+
export declare const COMPRESS_CHUNK_MAX_SECONDS = 60;
|
|
90
|
+
/**
|
|
91
|
+
* 动作恢复轮(**唯一一层兜底**)的预算:至少要留多少剩余时间才值得启动,以及它自己的上限。
|
|
92
|
+
*
|
|
93
|
+
* 实测一轮完整对话要 20~120s:门槛设得太低会启动一个**注定跑不完**的恢复轮
|
|
94
|
+
* (2026-10-14 实测:首轮烧掉 210s 后只剩 60s,恢复轮一直转到 270s 总预算耗尽,
|
|
95
|
+
* 客户端既没拿到工具调用还多等了 60s)。
|
|
96
|
+
*/
|
|
97
|
+
export declare const CONTINUE_MIN_BUDGET = 80;
|
|
98
|
+
export declare const CONTINUE_MAX_BUDGET = 100;
|
|
99
|
+
/**
|
|
100
|
+
* 同一用户请求下允许的最大工具调用轮数。
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ 旧值 8 太小:模型一次想读 4 个文件时我们**只下发第一个**,
|
|
103
|
+
* 8 轮就被截停,然后回一句「操作已完成。」—— 实测用户「帮我修复这几处」,
|
|
104
|
+
* 读了 8 个文件后就被截停,**一个文件都没改**。
|
|
105
|
+
*/
|
|
106
|
+
export declare const TOOL_ROUND_LIMIT = 24;
|
|
107
|
+
/**
|
|
108
|
+
* 「只读」工具名(归一化后比对:去掉非字母并小写)。
|
|
109
|
+
*
|
|
110
|
+
* 这些调用不改任何状态,同一条回复里的多个调用可以**一起下发**,
|
|
111
|
+
* 由客户端并行执行 —— 省下大量轮次(读取 6 个文件从 6 轮降到 1 轮)。
|
|
112
|
+
*/
|
|
113
|
+
export declare const READ_ONLY_TOOLS: ReadonlySet<string>;
|
|
114
|
+
/** 工具名是否属于「只读」类(可安全地在同一轮里批量下发)。 */
|
|
115
|
+
export declare function isReadOnlyTool(name: string): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* 识别「只给计划、没有动手」的未完成回答。
|
|
118
|
+
*
|
|
119
|
+
* 实测:模型读了 7 轮文件后,最后的回答是一段**计划书** ——
|
|
120
|
+
* 「仍有三处关键文件内容未知…需先补读」—— 里面没有任何工具调用。
|
|
121
|
+
* 客户端收到纯文本即结束回合,用户看到「读了半天、一个文件都没改、任务终止」。
|
|
122
|
+
* 模型还自认为「工具轮数已达上限(8/8)」(这句是它自己脑补的),
|
|
123
|
+
* 于是主动放弃动手改成写计划。
|
|
124
|
+
*
|
|
125
|
+
* 现在它是「动作恢复轮」的触发信号之一(提示词见
|
|
126
|
+
* `tools/prompts.ts::buildActionRecoveryPrompt`,原 `CONTINUE_PROMPT` 已并入)。
|
|
127
|
+
*/
|
|
128
|
+
export declare const UNFINISHED_MARKERS: ReadonlyArray<string>;
|
|
129
|
+
/** 回答是否是「只说计划、没有动手」的未完成态。 */
|
|
130
|
+
export declare function looksUnfinishedAnswer(text: string): boolean;
|
|
131
|
+
/** 项目根路径识别:`/home/<user>/<a>/<b>`(macOS 同理 `/Users/...`)。 */
|
|
132
|
+
export declare const PROJECT_ROOT_RE: RegExp;
|
|
133
|
+
/**
|
|
134
|
+
* 模型「自认读不到本地文件」的话术:出现这些短语说明它整轮没真正用我们的工具,
|
|
135
|
+
* 而是在跟网页端自带能力(联网搜索等)较劲,回答通常与本项目无关。
|
|
136
|
+
*/
|
|
137
|
+
export declare const SELF_ADMISSION_PHRASES: ReadonlyArray<string>;
|
|
138
|
+
/**
|
|
139
|
+
* 检测「回答与本项目无关」的迹象,返回要追加到正文前的提示(无异常返回空串)。
|
|
140
|
+
*
|
|
141
|
+
* 两类信号(实测的跑偏方式):
|
|
142
|
+
* 1. 回答里出现**其他项目根目录**(如 `/home/amble/project/ai-api`),
|
|
143
|
+
* 而当前工作目录是 `/home/amble/object/biga` —— 模型读/聊的是别的项目;
|
|
144
|
+
* 2. 回答自认「工具不可用 / 读不到本地文件」—— 说明它整轮在跟网页端自带的
|
|
145
|
+
* 联网搜索较劲,没真正读本项目代码。
|
|
146
|
+
*
|
|
147
|
+
* 这类回答看起来很自信但与本项目无关,直接把原文给用户是误导,
|
|
148
|
+
* 因此在正文前加一行明确提示(同时打 WARN 便于统计频率)。
|
|
149
|
+
*/
|
|
150
|
+
export declare function detectMismatchedAnswer(text: string, workspacePath: string): string;
|
|
151
|
+
/**
|
|
152
|
+
* 截断注入给网页端的 prompt,防止超过输入框上限导致消息发不出去。
|
|
153
|
+
*
|
|
154
|
+
* 截断策略:保留头部(工具调用协议 + 工具列表,决定能否产出工具调用)
|
|
155
|
+
* 与尾部(最近的对话内容,含 `<user_query>`),仅丢弃中间部分。
|
|
156
|
+
*/
|
|
157
|
+
export declare function truncatePromptForBrowser(text: string, maxLen?: number): string;
|
|
158
|
+
/** 把 `provider.platform` 归一成站点名(`chatglm-browser` → `chatglm`)。 */
|
|
159
|
+
export declare function platformOf(platformName: string): string;
|
|
160
|
+
/** 从 provider 的 platform 名里取平台名(目前是直通,保留钩子)。 */
|
|
161
|
+
export declare function extractPlatformName(providerPlatform: string): string;
|
|
162
|
+
/** 单条消息的指纹(用于逐条比对历史是否被改写)。 */
|
|
163
|
+
export declare function msgFingerprint(msg: ChatMessage | MessageDictLike): string;
|
|
164
|
+
/**
|
|
165
|
+
* 若这个会话的历史是上次的延续,返回「本次新增消息」的列表。
|
|
166
|
+
*
|
|
167
|
+
* @returns 新增消息;不可复用(无记录 / 历史被改写 / 无新增)时返回 `[]`。
|
|
168
|
+
*/
|
|
169
|
+
export declare function getSessionDelta(messages: ReadonlyArray<ChatMessage | MessageDictLike>, conversationId: string | null | undefined): Array<ChatMessage | MessageDictLike>;
|
|
170
|
+
/** 记录「这批消息已经发进 platform 的网页会话」,供下次算增量。 */
|
|
171
|
+
export declare function rememberSessionSend(conversationId: string | null | undefined, messages: ReadonlyArray<ChatMessage | MessageDictLike>, platform: string): void;
|
|
172
|
+
/** 清空会话发送记录(测试用)。 */
|
|
173
|
+
export declare function __clearSessionSendState(): void;
|
|
174
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/routes/helpers.ts"],"names":[],"mappings":"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"}
|