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,134 @@
|
|
|
1
|
+
import { logger } from '../../log/logger.js';
|
|
2
|
+
import { Mutex } from '../../utils/mutex.js';
|
|
3
|
+
/** 单调时钟(毫秒),等价于 Python `time.monotonic()`。 */
|
|
4
|
+
function monotonicMs() {
|
|
5
|
+
return performance.now();
|
|
6
|
+
}
|
|
7
|
+
/** 令牌桶。 */
|
|
8
|
+
export class TokenBucket {
|
|
9
|
+
rate;
|
|
10
|
+
capacity;
|
|
11
|
+
tokens;
|
|
12
|
+
lastTime;
|
|
13
|
+
constructor(
|
|
14
|
+
/** 令牌生成速率(每秒)。 */
|
|
15
|
+
rate,
|
|
16
|
+
/** 桶容量。 */
|
|
17
|
+
capacity, now = monotonicMs) {
|
|
18
|
+
this.rate = rate;
|
|
19
|
+
this.capacity = capacity;
|
|
20
|
+
this.tokens = capacity;
|
|
21
|
+
this.lastTime = now();
|
|
22
|
+
}
|
|
23
|
+
/** 尝试消费 1 个令牌。 */
|
|
24
|
+
consume(now = monotonicMs) {
|
|
25
|
+
const t = now();
|
|
26
|
+
const elapsed = (t - this.lastTime) / 1000;
|
|
27
|
+
this.lastTime = t;
|
|
28
|
+
this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.rate);
|
|
29
|
+
if (this.tokens >= 1) {
|
|
30
|
+
this.tokens -= 1;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
/** 最后使用时间(清理过期桶用)。 */
|
|
36
|
+
get lastUsed() {
|
|
37
|
+
return this.lastTime;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const DEFAULT_SKIP = new Set(['/health', '/status', '/v1/health', '/v1/status', '/']);
|
|
41
|
+
/** 限流器(可在测试中直接实例化,不依赖 Fastify)。 */
|
|
42
|
+
export class RateLimiter {
|
|
43
|
+
requestsPerMinute;
|
|
44
|
+
buckets = new Map();
|
|
45
|
+
lock = new Mutex();
|
|
46
|
+
lastCleanup = monotonicMs();
|
|
47
|
+
/** 清理间隔(秒)。 */
|
|
48
|
+
cleanupInterval = 600;
|
|
49
|
+
/** 判定是否放行(纯逻辑,便于单测)。 */
|
|
50
|
+
async allow(clientKey) {
|
|
51
|
+
const release = await this.lock.acquire();
|
|
52
|
+
try {
|
|
53
|
+
this.cleanup();
|
|
54
|
+
let bucket = this.buckets.get(clientKey);
|
|
55
|
+
if (!bucket) {
|
|
56
|
+
bucket = new TokenBucket(this.requestsPerMinute / 60, this.requestsPerMinute);
|
|
57
|
+
this.buckets.set(clientKey, bucket);
|
|
58
|
+
}
|
|
59
|
+
return bucket.consume();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
release();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** 清理超过 5 分钟未使用的桶。 */
|
|
66
|
+
cleanup() {
|
|
67
|
+
const now = monotonicMs();
|
|
68
|
+
if (now - this.lastCleanup < this.cleanupInterval * 1000)
|
|
69
|
+
return;
|
|
70
|
+
this.lastCleanup = now;
|
|
71
|
+
const cutoff = now - 300_000;
|
|
72
|
+
let removed = 0;
|
|
73
|
+
for (const [key, bucket] of this.buckets) {
|
|
74
|
+
if (bucket.lastUsed < cutoff) {
|
|
75
|
+
this.buckets.delete(key);
|
|
76
|
+
removed += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (removed > 0)
|
|
80
|
+
logger.debug(`清理 ${removed} 个过期速率限制桶`);
|
|
81
|
+
}
|
|
82
|
+
constructor(requestsPerMinute) {
|
|
83
|
+
this.requestsPerMinute = requestsPerMinute;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 取客户端标识:API Key 优先,否则回退到 IP。
|
|
88
|
+
*
|
|
89
|
+
* 与服务端反向代理的配合:Fastify 默认不信任 `X-Forwarded-For`,
|
|
90
|
+
* 需要时在 `app.ts` 里设置 `trustProxy`,届时 `request.ip` 会自动取真实 IP。
|
|
91
|
+
*/
|
|
92
|
+
export function clientKeyOf(request) {
|
|
93
|
+
const auth = request.headers['authorization'];
|
|
94
|
+
if (typeof auth === 'string' && auth.startsWith('Bearer ')) {
|
|
95
|
+
return `key:${auth.slice(7)}`;
|
|
96
|
+
}
|
|
97
|
+
const xApiKey = request.headers['x-api-key'];
|
|
98
|
+
if (typeof xApiKey === 'string' && xApiKey) {
|
|
99
|
+
return `key:${xApiKey}`;
|
|
100
|
+
}
|
|
101
|
+
return `ip:${request.ip || 'unknown'}`;
|
|
102
|
+
}
|
|
103
|
+
/** 构造 Fastify `onRequest` 钩子。 */
|
|
104
|
+
export function makeRateLimitHook(options) {
|
|
105
|
+
const skip = options.skipPaths ?? DEFAULT_SKIP;
|
|
106
|
+
const limiter = new RateLimiter(options.requestsPerMinute);
|
|
107
|
+
if (options.enabled) {
|
|
108
|
+
logger.info(`速率限制: ${options.requestsPerMinute} req/min per client`);
|
|
109
|
+
}
|
|
110
|
+
return async (request, reply) => {
|
|
111
|
+
if (!options.enabled)
|
|
112
|
+
return;
|
|
113
|
+
if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method))
|
|
114
|
+
return;
|
|
115
|
+
if (skip.has(request.url.split('?')[0] ?? ''))
|
|
116
|
+
return;
|
|
117
|
+
const clientKey = clientKeyOf(request);
|
|
118
|
+
if (!(await limiter.allow(clientKey))) {
|
|
119
|
+
logger.info(`速率限制触发: ${clientKey} (${request.method} ${request.url})`);
|
|
120
|
+
// ⚠️ 429 + Retry-After,格式与原实现一致(OpenAI 兼容)
|
|
121
|
+
void reply
|
|
122
|
+
.code(429)
|
|
123
|
+
.header('retry-after', '60')
|
|
124
|
+
.send({
|
|
125
|
+
error: {
|
|
126
|
+
message: '请求过于频繁,请稍后再试',
|
|
127
|
+
type: 'too_many_requests',
|
|
128
|
+
code: 'rate_limit_exceeded',
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/server/middleware/rate-limit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求超时中间件 —— `legacy-python/src/middleware/timeout.py` 的移植。
|
|
3
|
+
*
|
|
4
|
+
* 防止长请求无限挂起;超时后返回 OpenAI 格式的 504。
|
|
5
|
+
*
|
|
6
|
+
* ## 与原实现的对应关系
|
|
7
|
+
* Python 用 ASGI 层 `asyncio.wait_for(app(...), timeout)` 包住整个应用。
|
|
8
|
+
* Fastify 没有等价物,改用 `onRequest` 钩子 + `setTimeout` 兜底:
|
|
9
|
+
* 到点后若响应还没开始写,就直接往 `reply.raw`(Node 的 `ServerResponse`)写 504。
|
|
10
|
+
*
|
|
11
|
+
* ## ⚠️ 这里踩过的两个坑,一个都不能回退
|
|
12
|
+
* 1. **不能用框架的 JSON 序列化器塞 bytes**:旧实现调用
|
|
13
|
+
* `JSONResponse.render(b"")` → `TypeError: Object of type bytes is not JSON serializable`
|
|
14
|
+
* → 超时反而返回 500「内部服务器错误」。现在直接 `JSON.stringify` 出字符串再写。
|
|
15
|
+
* 2. **响应必须真的发出去**:旧实现漏了 `await send(...)`,504 根本没到客户端,
|
|
16
|
+
* 客户端只能一直等到自己超时。这里 `raw.writeHead(...)` + `raw.end(body)` 是同步的,
|
|
17
|
+
* 但**仍然包了 try/catch**:客户端提前断开时写入会抛 EPIPE,不能让它冒泡成未捕获异常。
|
|
18
|
+
*/
|
|
19
|
+
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
20
|
+
/** 超时中间件选项。 */
|
|
21
|
+
export interface TimeoutOptions {
|
|
22
|
+
/** 超时秒数(<=0 表示禁用)。 */
|
|
23
|
+
timeoutSeconds: number;
|
|
24
|
+
/** 豁免路径(健康检查等)。 */
|
|
25
|
+
skipPaths?: ReadonlySet<string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 构造 Fastify `onRequest` 钩子。
|
|
29
|
+
*
|
|
30
|
+
* 副作用:即使超时被禁用(或路径被豁免),**不设置 `deadlineTs`** 也不影响路由 ——
|
|
31
|
+
* 路由会退化成「按自己的默认预算跑」。
|
|
32
|
+
*/
|
|
33
|
+
export declare function makeTimeoutHook(options: TimeoutOptions): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
34
|
+
//# sourceMappingURL=timeout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAI5D,eAAe;AACf,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB;IACnB,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjC;AAYD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,GACtB,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAmDjE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { logger } from '../../log/logger.js';
|
|
2
|
+
const DEFAULT_SKIP = new Set(['/health', '/status', '/v1/health', '/v1/status', '/']);
|
|
3
|
+
const TIMEOUT_BODY = JSON.stringify({
|
|
4
|
+
error: {
|
|
5
|
+
message: '请求超时:服务器未能在规定时间内完成响应',
|
|
6
|
+
type: 'server_error',
|
|
7
|
+
code: 'request_timeout',
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* 构造 Fastify `onRequest` 钩子。
|
|
12
|
+
*
|
|
13
|
+
* 副作用:即使超时被禁用(或路径被豁免),**不设置 `deadlineTs`** 也不影响路由 ——
|
|
14
|
+
* 路由会退化成「按自己的默认预算跑」。
|
|
15
|
+
*/
|
|
16
|
+
export function makeTimeoutHook(options) {
|
|
17
|
+
const skip = options.skipPaths ?? DEFAULT_SKIP;
|
|
18
|
+
const { timeoutSeconds } = options;
|
|
19
|
+
if (timeoutSeconds > 0) {
|
|
20
|
+
logger.info(`请求超时中间件: timeout=${timeoutSeconds}s`);
|
|
21
|
+
}
|
|
22
|
+
return async (request, reply) => {
|
|
23
|
+
const path = request.url.split('?')[0] ?? '';
|
|
24
|
+
if (skip.has(path))
|
|
25
|
+
return;
|
|
26
|
+
if (timeoutSeconds <= 0)
|
|
27
|
+
return;
|
|
28
|
+
// 把绝对截止时间交给下游(路由 / Provider 用它收敛自己的等待预算)
|
|
29
|
+
request.deadlineTs = Date.now() + timeoutSeconds * 1000;
|
|
30
|
+
const raw = reply.raw;
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
if (raw.writableEnded)
|
|
33
|
+
return;
|
|
34
|
+
// 已经开始写响应(SSE 已经吐出过 chunk)→ 无法再改状态码,
|
|
35
|
+
// 直接收尾,避免「headers already sent」二次响应报错。
|
|
36
|
+
if (raw.headersSent) {
|
|
37
|
+
logger.warn(`请求超时: ${request.method} ${path} (超时 ${timeoutSeconds}s),响应已开始,直接收尾`);
|
|
38
|
+
try {
|
|
39
|
+
raw.end();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// 客户端可能已断开
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
logger.warn(`请求超时: ${request.method} ${path} (超时 ${timeoutSeconds}s)`);
|
|
47
|
+
try {
|
|
48
|
+
raw.writeHead(504, {
|
|
49
|
+
'content-type': 'application/json; charset=utf-8',
|
|
50
|
+
'content-length': Buffer.byteLength(TIMEOUT_BODY),
|
|
51
|
+
});
|
|
52
|
+
raw.end(TIMEOUT_BODY);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger.warn(`发送超时响应失败(客户端可能已断开): ${String(error)}`);
|
|
56
|
+
}
|
|
57
|
+
}, timeoutSeconds * 1000);
|
|
58
|
+
// 响应结束或连接关闭都要清掉定时器,否则会留下「跑到已结束的连接上」的悬空定时器
|
|
59
|
+
const cleanup = () => clearTimeout(timer);
|
|
60
|
+
raw.once('close', cleanup);
|
|
61
|
+
raw.once('finish', cleanup);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../../src/server/middleware/timeout.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAU7C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC;AAEtF,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC;IAClC,KAAK,EAAE;QACL,OAAO,EAAE,sBAAsB;QAC/B,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,iBAAiB;KACxB;CACF,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAuB;IAEvB,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,IAAI,YAAY,CAAC;IAC/C,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IAEnC,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,oBAAoB,cAAc,GAAG,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,KAAK,EAAE,OAAuB,EAAE,KAAmB,EAAiB,EAAE;QAC3E,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAC3B,IAAI,cAAc,IAAI,CAAC;YAAE,OAAO;QAEhC,yCAAyC;QACzC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,GAAG,IAAI,CAAC;QAExD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACtB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,GAAG,CAAC,aAAa;gBAAE,OAAO;YAE9B,qCAAqC;YACrC,uCAAuC;YACvC,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACpB,MAAM,CAAC,IAAI,CACT,SAAS,OAAO,CAAC,MAAM,IAAI,IAAI,QAAQ,cAAc,eAAe,CACrE,CAAC;gBACF,IAAI,CAAC;oBACH,GAAG,CAAC,GAAG,EAAE,CAAC;gBACZ,CAAC;gBAAC,MAAM,CAAC;oBACP,WAAW;gBACb,CAAC;gBACD,OAAO;YACT,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,MAAM,IAAI,IAAI,QAAQ,cAAc,IAAI,CAAC,CAAC;YACvE,IAAI,CAAC;gBACH,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;oBACjB,cAAc,EAAE,iCAAiC;oBACjD,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC;iBAClD,CAAC,CAAC;gBACH,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACxB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,uBAAuB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACtD,CAAC;QACH,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC,CAAC;QAE1B,0CAA0C;QAC1C,MAAM,OAAO,GAAG,GAAS,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAChD,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3B,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fastify 类型扩展。
|
|
3
|
+
*
|
|
4
|
+
* `deadlineTs` 是「整个 HTTP 请求的绝对截止时间(毫秒)」——
|
|
5
|
+
* 由超时中间件写入,路由层与 Provider 层用它把自己的等待预算**收敛**到剩余时间内。
|
|
6
|
+
*
|
|
7
|
+
* 为什么必须传下去:首轮 + 两轮策略的 reparse 是**两个完整往返**,
|
|
8
|
+
* 各自按 240s 跑必然超过中间件的 300s,客户端只会拿到 504
|
|
9
|
+
* (2026-09-27 19:16 的真实事故)。
|
|
10
|
+
*/
|
|
11
|
+
declare module 'fastify' {
|
|
12
|
+
interface FastifyRequest {
|
|
13
|
+
/** 整个请求的绝对截止时间(epoch 毫秒)。未设置时表示不限。 */
|
|
14
|
+
deadlineTs?: number;
|
|
15
|
+
/** 本次请求的追踪 ID(贯穿日志与 SSE 注释行)。 */
|
|
16
|
+
requestId?: string;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,sCAAsC;QACtC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iCAAiC;QACjC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;CACF;AAED,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ToolCallPayload, ToolDefinition } from '../types/message.js';
|
|
2
|
+
import { type ToolMaps } from './names.js';
|
|
3
|
+
/**
|
|
4
|
+
* 构造 OpenAI 格式的 tool_call。
|
|
5
|
+
*
|
|
6
|
+
* @param name AI 输出的工具名
|
|
7
|
+
* @param args AI 输出的参数(对象或 JSON 字符串)
|
|
8
|
+
* @param maps 本次请求的工具映射表(替代 Python 版的模块级全局变量)
|
|
9
|
+
* @returns 规范化后的 tool_call;`name` 为空时返回 `null`
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildToolCall(name: string | null | undefined, args: unknown, maps?: ToolMaps): ToolCallPayload | null;
|
|
12
|
+
/** 便捷:给一批调用做「工具名是否在客户端列表中」的预检(只读,不修改)。 */
|
|
13
|
+
export declare function isKnownToolName(name: string, tools: ReadonlyArray<ToolDefinition> | null | undefined): boolean;
|
|
14
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/tools/build.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE3E,OAAO,EACL,KAAK,QAAQ,EAOd,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,IAAI,EAAE,OAAO,EACb,IAAI,GAAE,QAA0B,GAC/B,eAAe,GAAG,IAAI,CA2ExB;AAED,0CAA0C;AAC1C,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,IAAI,GAAG,SAAS,GACtD,OAAO,CAGT"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具调用构造 —— `legacy-python/src/utils/tool_calls.py` 的 `_build_tool_call`。
|
|
3
|
+
*
|
|
4
|
+
* 负责把「AI 输出的工具名 + 参数」规范成 OpenAI `tool_calls` 结构:
|
|
5
|
+
* 1. 过滤 IDE 内部工具(返回一条带 error 的调用,避免 AI 无限等结果)
|
|
6
|
+
* 2. 工具名别名映射(`edit_file` → `SearchReplace`)
|
|
7
|
+
* 3. **动态映射到客户端真实工具名**(`Write` → `write_to_file`)
|
|
8
|
+
* 4. 参数名归一化 + 映射到客户端真实参数名(`file_path` → `filePath`)
|
|
9
|
+
* 5. 清理参数值里的 DOM HTML 残留
|
|
10
|
+
*/
|
|
11
|
+
import { logger } from '../log/logger.js';
|
|
12
|
+
import { cleanDomHtml } from './clean.js';
|
|
13
|
+
import { EMPTY_TOOL_MAPS, TOOL_NAME_ALIASES, TRAE_INTERNAL_TOOLS, makeToolCallId, normalizeParamKey, normalizeToolArguments, } from './names.js';
|
|
14
|
+
/**
|
|
15
|
+
* 构造 OpenAI 格式的 tool_call。
|
|
16
|
+
*
|
|
17
|
+
* @param name AI 输出的工具名
|
|
18
|
+
* @param args AI 输出的参数(对象或 JSON 字符串)
|
|
19
|
+
* @param maps 本次请求的工具映射表(替代 Python 版的模块级全局变量)
|
|
20
|
+
* @returns 规范化后的 tool_call;`name` 为空时返回 `null`
|
|
21
|
+
*/
|
|
22
|
+
export function buildToolCall(name, args, maps = EMPTY_TOOL_MAPS) {
|
|
23
|
+
if (!name)
|
|
24
|
+
return null;
|
|
25
|
+
// === 1. IDE 内部工具:在网页 API 环境里不可用 ===
|
|
26
|
+
// 返回一条带 error 的调用,告诉 AI「这个工具不存在」,避免它无限等待执行结果
|
|
27
|
+
if (TRAE_INTERNAL_TOOLS.has(name)) {
|
|
28
|
+
logger.warn({ name }, '[tool_calls] 忽略 Trae 内部工具');
|
|
29
|
+
return {
|
|
30
|
+
id: makeToolCallId(),
|
|
31
|
+
type: 'function',
|
|
32
|
+
function: {
|
|
33
|
+
name,
|
|
34
|
+
arguments: JSON.stringify({
|
|
35
|
+
error: `工具 '${name}' 仅在 Trae IDE 环境中可用,当前为网页 API 环境。`,
|
|
36
|
+
suggestion: '请使用文本方式回复用户,不要调用此工具。',
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// === 2. 工具名别名 → 标准名 ===
|
|
42
|
+
let toolName = TOOL_NAME_ALIASES[name] ?? name;
|
|
43
|
+
// === 3. 标准名 → 客户端真实工具名 ===
|
|
44
|
+
if (toolName === 'Write' && maps.name['write'])
|
|
45
|
+
toolName = maps.name['write'];
|
|
46
|
+
else if (toolName === 'SearchReplace' && maps.name['edit'])
|
|
47
|
+
toolName = maps.name['edit'];
|
|
48
|
+
else if (toolName === 'Read' && maps.name['read'])
|
|
49
|
+
toolName = maps.name['read'];
|
|
50
|
+
// === 4. 参数归一化 ===
|
|
51
|
+
let argsObj;
|
|
52
|
+
if (typeof args === 'string') {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(args);
|
|
55
|
+
argsObj =
|
|
56
|
+
parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
57
|
+
? parsed
|
|
58
|
+
: { _raw: args };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
argsObj = { _raw: args };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (args && typeof args === 'object' && !Array.isArray(args)) {
|
|
65
|
+
argsObj = { ...args };
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
argsObj = {};
|
|
69
|
+
}
|
|
70
|
+
argsObj = normalizeToolArguments(toolName, argsObj);
|
|
71
|
+
// === 5. 映射到客户端真实参数名(file_path → filePath) ===
|
|
72
|
+
const clientParams = maps.param[toolName];
|
|
73
|
+
if (clientParams && Object.keys(clientParams).length > 0) {
|
|
74
|
+
const mappedArgs = {};
|
|
75
|
+
for (const [key, value] of Object.entries(argsObj)) {
|
|
76
|
+
const clientKey = clientParams[normalizeParamKey(key)] ?? key;
|
|
77
|
+
if (!(clientKey in mappedArgs))
|
|
78
|
+
mappedArgs[clientKey] = value;
|
|
79
|
+
}
|
|
80
|
+
argsObj = mappedArgs;
|
|
81
|
+
}
|
|
82
|
+
// === 6. 清理参数值里的 DOM HTML 标签 ===
|
|
83
|
+
for (const [key, value] of Object.entries(argsObj)) {
|
|
84
|
+
if (typeof value === 'string') {
|
|
85
|
+
const cleaned = cleanDomHtml(value);
|
|
86
|
+
if (cleaned !== value)
|
|
87
|
+
argsObj[key] = cleaned;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
id: makeToolCallId(),
|
|
92
|
+
type: 'function',
|
|
93
|
+
function: {
|
|
94
|
+
name: toolName,
|
|
95
|
+
arguments: JSON.stringify(argsObj),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** 便捷:给一批调用做「工具名是否在客户端列表中」的预检(只读,不修改)。 */
|
|
100
|
+
export function isKnownToolName(name, tools) {
|
|
101
|
+
if (!tools || tools.length === 0)
|
|
102
|
+
return true;
|
|
103
|
+
return tools.some((t) => t?.function?.name === name);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/tools/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM 工件清理与标签修复 —— `legacy-python/src/utils/tool_calls.py` 的一部分。
|
|
3
|
+
*
|
|
4
|
+
* 这些函数专门对付「ChatGLM 把 `<tool_call>` 当 HTML 元素消费掉」造成的破坏:
|
|
5
|
+
* 标签残缺、插入 `<span data-allow-html>`、智能引号、NBSP 等。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 清理工具调用值里由 ChatGLM markdown 渲染产生的 HTML 标签。
|
|
9
|
+
*
|
|
10
|
+
* 只移除**明显是 DOM 渲染产物**的标签(`<span>` / `<div>` / `<br>`),
|
|
11
|
+
* 不动 `<script>` / `<style>` 这类可能属于用户代码内容的标签。
|
|
12
|
+
*/
|
|
13
|
+
export declare function cleanDomHtml(text: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* 修复 DOM 渲染导致的 tool_call 标签破碎(等价于 `_normalize_tool_call_tags`)。
|
|
16
|
+
*
|
|
17
|
+
* 实际日志中出现过的破碎形态:
|
|
18
|
+
* - `<ttool_call>` → `<tool_call>`(多余 t)
|
|
19
|
+
* - `</ttool_call>` → `</tool_call>`
|
|
20
|
+
* - `<t \n ool_call>` → `<tool_call>`(跨行截断)
|
|
21
|
+
* - `<tool \n _call>` → `<tool_call>`
|
|
22
|
+
* - ChatGLM 还会插入 `<arg_key>` / `<arg_value>` 包裹参数
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeToolCallTags(text: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* 清理解析后残留在正文中的(空)工具调用标签。
|
|
27
|
+
*
|
|
28
|
+
* JSON 已被移除,但 `<tool_call>` / `</tool_call>` 可能还留在剩余文本里,
|
|
29
|
+
* 会让 assistant 的 content 出现空的工具调用标签。
|
|
30
|
+
*/
|
|
31
|
+
export declare function stripLeftoverToolTags(text: string): string;
|
|
32
|
+
//# sourceMappingURL=clean.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.d.ts","sourceRoot":"","sources":["../../src/tools/clean.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAajD;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY1D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB1D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM 工件清理与标签修复 —— `legacy-python/src/utils/tool_calls.py` 的一部分。
|
|
3
|
+
*
|
|
4
|
+
* 这些函数专门对付「ChatGLM 把 `<tool_call>` 当 HTML 元素消费掉」造成的破坏:
|
|
5
|
+
* 标签残缺、插入 `<span data-allow-html>`、智能引号、NBSP 等。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 清理工具调用值里由 ChatGLM markdown 渲染产生的 HTML 标签。
|
|
9
|
+
*
|
|
10
|
+
* 只移除**明显是 DOM 渲染产物**的标签(`<span>` / `<div>` / `<br>`),
|
|
11
|
+
* 不动 `<script>` / `<style>` 这类可能属于用户代码内容的标签。
|
|
12
|
+
*/
|
|
13
|
+
export function cleanDomHtml(text) {
|
|
14
|
+
let out = text;
|
|
15
|
+
// ChatGLM 特有的 span(含 data-allow-html 属性)
|
|
16
|
+
out = out.replace(/<span\s+data-allow-html[^>]*>/g, '');
|
|
17
|
+
out = out.replace(/<\/?span[^>]*>/g, '');
|
|
18
|
+
out = out.replace(/<\/?div[^>]*>/g, '');
|
|
19
|
+
out = out.replace(/<br\s*\/?>/g, '\\n');
|
|
20
|
+
// 残留的非断空格
|
|
21
|
+
out = out.replace(/\u00a0/g, ' ');
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 修复 DOM 渲染导致的 tool_call 标签破碎(等价于 `_normalize_tool_call_tags`)。
|
|
26
|
+
*
|
|
27
|
+
* 实际日志中出现过的破碎形态:
|
|
28
|
+
* - `<ttool_call>` → `<tool_call>`(多余 t)
|
|
29
|
+
* - `</ttool_call>` → `</tool_call>`
|
|
30
|
+
* - `<t \n ool_call>` → `<tool_call>`(跨行截断)
|
|
31
|
+
* - `<tool \n _call>` → `<tool_call>`
|
|
32
|
+
* - ChatGLM 还会插入 `<arg_key>` / `<arg_value>` 包裹参数
|
|
33
|
+
*/
|
|
34
|
+
export function normalizeToolCallTags(text) {
|
|
35
|
+
let out = text;
|
|
36
|
+
// 移除 ChatGLM 插入的 <arg_key> / <arg_value> / <arg_*> 标签
|
|
37
|
+
out = out.replace(/<\/?arg_\w+\s*>/g, '');
|
|
38
|
+
out = out.replace(/<t+ool_call\s*>/g, '<tool_call>');
|
|
39
|
+
out = out.replace(/<\/t+ool_call\s*>/g, '</tool_call>');
|
|
40
|
+
out = out.replace(/<t\s*\n\s*ool_call\s*>/g, '<tool_call>');
|
|
41
|
+
out = out.replace(/<tool\s*\n\s*_call\s*>/g, '<tool_call>');
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 清理解析后残留在正文中的(空)工具调用标签。
|
|
46
|
+
*
|
|
47
|
+
* JSON 已被移除,但 `<tool_call>` / `</tool_call>` 可能还留在剩余文本里,
|
|
48
|
+
* 会让 assistant 的 content 出现空的工具调用标签。
|
|
49
|
+
*/
|
|
50
|
+
export function stripLeftoverToolTags(text) {
|
|
51
|
+
if (!text)
|
|
52
|
+
return '';
|
|
53
|
+
let out = text;
|
|
54
|
+
// 1) 成对的空标签块(中间只有空白)
|
|
55
|
+
out = out.replace(/<\s*\/?\s*(?:t+ool_call)\s*>\s*<\s*\/?\s*(?:t+ool_call)\s*>/gi, '');
|
|
56
|
+
// 2) 任意残留的单个标签
|
|
57
|
+
out = out.replace(/<\s*\/?\s*t+ool_call\s*>/gi, '');
|
|
58
|
+
// 3) 粘连写法(缺少开头 "<" / 结尾 ">")
|
|
59
|
+
out = out.replace(/(?<![<\w])t+ool_call\s*>/g, '');
|
|
60
|
+
out = out.replace(/<\s*\/?\s*t+ool_call(?![>\w])/gi, '');
|
|
61
|
+
// 折叠多余空行
|
|
62
|
+
out = out.replace(/\n{3,}/g, '\n\n');
|
|
63
|
+
return out.trim();
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=clean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.js","sourceRoot":"","sources":["../../src/tools/clean.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,GAAG,GAAG,IAAI,CAAC;IAEf,yCAAyC;IACzC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;IACxD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACzC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;IACxC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAExC,UAAU;IACV,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAElC,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,IAAI,GAAG,GAAG,IAAI,CAAC;IAEf,sDAAsD;IACtD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;IAE1C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAC;IACrD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,oBAAoB,EAAE,cAAc,CAAC,CAAC;IACxD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC;IAC5D,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC;IAE5D,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IAErB,IAAI,GAAG,GAAG,IAAI,CAAC;IAEf,qBAAqB;IACrB,GAAG,GAAG,GAAG,CAAC,OAAO,CACf,+DAA+D,EAC/D,EAAE,CACH,CAAC;IACF,eAAe;IACf,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC;IACpD,6BAA6B;IAC7B,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;IACnD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,iCAAiC,EAAE,EAAE,CAAC,CAAC;IAEzD,SAAS;IACT,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAErC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文本 → 工具调用转换 —— `legacy-python/src/utils/tool_calls.py` 的一部分。
|
|
3
|
+
*
|
|
4
|
+
* 两条互补的路径:
|
|
5
|
+
* - {@link convertTextToToolCalls}:从文本里抠「带路径的代码块」,转成写文件调用
|
|
6
|
+
* - {@link directConvertToToolCalls}:**零损失**地整段包成一次写文件调用
|
|
7
|
+
* (不依赖第二轮 AI,避免内容被改写或丢失)
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ `docs/hard-constraints.md` 的提醒:这两个函数**没提取到工具调用时会把输入原样返回**,
|
|
10
|
+
* 所以调用方只有「确实提取到了」才允许采纳其文本,否则不要把已解析的正文覆盖掉。
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolCallPayload, ToolDefinition } from '../types/message.js';
|
|
13
|
+
/**
|
|
14
|
+
* 判断字符串是否像文件路径。
|
|
15
|
+
*
|
|
16
|
+
* 注意:只用来**排除明显不是路径**的代码块首行,宁可放宽也不要漏判。
|
|
17
|
+
*/
|
|
18
|
+
export declare function looksLikeFilepath(s: string, _lang?: string): boolean;
|
|
19
|
+
/** 按工具声明的参数名构建写文件参数(兼容 `file_path`/`filePath`、`content`/`code`)。 */
|
|
20
|
+
export declare function buildWriteArgs(paramNames: ReadonlyArray<string>, filepath: string, content: string): Record<string, string>;
|
|
21
|
+
/** 从用户请求和内容推断文件名。 */
|
|
22
|
+
export declare function inferFilename(userRequest: string, content: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* 判断 AI 回复是否像**文件内容**(而不是描述性/计划性文字)。
|
|
25
|
+
*
|
|
26
|
+
* 满足任一即触发:markdown 标题开头且够长、含带路径的代码块、
|
|
27
|
+
* 3+ 个代码块、代码行占比 > 40%、看起来是完整 HTML。
|
|
28
|
+
*/
|
|
29
|
+
export declare function looksLikeFileContent(text: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 从纯文本回复里提取「带路径的代码块」并转成写文件调用。
|
|
32
|
+
*
|
|
33
|
+
* 没提取到时返回 `(原文本, [])` —— 调用方务必检查是否真的提取到了。
|
|
34
|
+
*/
|
|
35
|
+
export declare function convertTextToToolCalls(text: string, tools: ReadonlyArray<ToolDefinition>): {
|
|
36
|
+
text: string;
|
|
37
|
+
toolCalls: ToolCallPayload[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* 把第一轮回复**整段**包成一次写文件调用(零损失)。
|
|
41
|
+
*
|
|
42
|
+
* 仅在回复看起来像文件内容时才触发;描述性文字不触发。
|
|
43
|
+
*/
|
|
44
|
+
export declare function directConvertToToolCalls(firstResponse: string, tools: ReadonlyArray<ToolDefinition>, userRequest?: string): {
|
|
45
|
+
text: string;
|
|
46
|
+
toolCalls: ToolCallPayload[];
|
|
47
|
+
};
|
|
48
|
+
/** 从文本中抽取出现过的疑似文件路径(供 reparse 提示词使用)。 */
|
|
49
|
+
export declare function collectFilePathsInText(text: string, limit?: number): Set<string>;
|
|
50
|
+
//# sourceMappingURL=convert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/tools/convert.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAoB3E;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAgBhE;AAcD,oEAAoE;AACpE,wBAAgB,cAAc,CAC5B,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,EACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBxB;AAED,qBAAqB;AACrB,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAgC1E;AAUD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAoC1D;AA6BD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,eAAe,EAAE,CAAA;CAAE,CAsDhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,EACpC,WAAW,SAAK,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,eAAe,EAAE,CAAA;CAAE,CA4ChD;AAED,yCAAyC;AACzC,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,SAA2B,GAC/B,GAAG,CAAC,MAAM,CAAC,CAiBb"}
|