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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台注册表 —— **「一个平台」的全部声明集中在这里**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要有这个文件
|
|
5
|
+
* 移植前,关于「平台」的信息散在 `credentials.ts` 的**三张并行 Record**
|
|
6
|
+
* (`PLATFORM_API_PATTERNS` / `PLATFORM_LOGIN_URLS` / `PLATFORM_NAMES`)里,
|
|
7
|
+
* 加上 `extractAccountInfoSync` 里一个**按平台名分支的 switch**。要加一个平台,
|
|
8
|
+
* 得记住改 4 个地方 —— 漏掉任何一处都**不报错**,只是该平台在某个菜单/某条
|
|
9
|
+
* 日志里显示成原始 id、或者身份提取静默退化(最难查的一类问题)。
|
|
10
|
+
*
|
|
11
|
+
* 现在:**加平台 = 在 {@link PLATFORM_LIST} 里加一条**。其余代码全部按这张表迭代。
|
|
12
|
+
*
|
|
13
|
+
* ## 依赖方向
|
|
14
|
+
* `platforms.ts` → `credentials.ts`(单向)。凭证的**类型与读写**留在
|
|
15
|
+
* `credentials.ts`,平台元数据与**身份提取**在这里。
|
|
16
|
+
* 反过来不让 `credentials.ts` 依赖本文件,就避免了两者循环 import
|
|
17
|
+
* —— 这也是 `extractAccountInfoSync` 从 `credentials.ts` **搬过来**的原因。
|
|
18
|
+
*
|
|
19
|
+
* ## `providerImplemented` 与 `PLATFORM_MODEL_NAMES` 的关系
|
|
20
|
+
* 「能不能对话」取决于是否真的实现了 Provider(`controllers/router.ts` 的
|
|
21
|
+
* `PROVIDER_FACTORIES`)。本文件用 {@link PlatformSpec.providerImplemented} 声明,
|
|
22
|
+
* `router.ts` 的 `PLATFORM_MODEL_NAMES` 由它派生 —— **单一真相**。
|
|
23
|
+
* 为防止两边写岔,有单测逐条核对(`test/browser/platforms.test.ts`):
|
|
24
|
+
* 声明为 true 的平台必须真有工厂,反之亦然。
|
|
25
|
+
*/
|
|
26
|
+
import { type AccountInfo, type AuthRecord, type PlatformApiPattern } from './credentials.js';
|
|
27
|
+
export type { PlatformApiPattern };
|
|
28
|
+
/** 一个平台的完整声明。 */
|
|
29
|
+
export interface PlatformSpec {
|
|
30
|
+
/** 平台 id(= 模型名 = 凭证目录名,如 `chatglm`)。 */
|
|
31
|
+
id: string;
|
|
32
|
+
/** 中文显示名(菜单 / 日志 / `/status` 用)。 */
|
|
33
|
+
displayName: string;
|
|
34
|
+
/** 登录入口(`webapi login` 会打开它)。 */
|
|
35
|
+
loginUrl: string;
|
|
36
|
+
/**
|
|
37
|
+
* Web UI 内部 API 的匹配特征(用于从被抓请求里认出哪个含认证信息)。
|
|
38
|
+
* 缺失时只捕获 Cookie(见 `AuthCapture` 的告警分支)。
|
|
39
|
+
*/
|
|
40
|
+
apiPattern?: PlatformApiPattern;
|
|
41
|
+
/**
|
|
42
|
+
* 是否已实现对话 Provider。
|
|
43
|
+
*
|
|
44
|
+
* `false` 表示**只能捕获凭证、暂不能对话** —— 这是给未来平台留的位置:
|
|
45
|
+
* 先把登录/身份提取接通(这样 `webapi login` 菜单里就能看到它),
|
|
46
|
+
* 等 Provider 写好再把本字段翻成 `true` 即可。
|
|
47
|
+
*/
|
|
48
|
+
providerImplemented: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 从已捕获的凭证里提取账号身份。
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ 必须是**纯函数**(只依赖入参),因为 `router` 会在判断辅助通道
|
|
53
|
+
* 能否启用时反复调用它比较身份。
|
|
54
|
+
*
|
|
55
|
+
* 约定:至少产出 `identifier`(用于唯一标识「这是同一个人」),
|
|
56
|
+
* 尽量产出 `label`(人类可读,如用户名/邮箱)。两者都可能缺,
|
|
57
|
+
* 由 {@link extractAccountInfoSync} 统一兜底。
|
|
58
|
+
*/
|
|
59
|
+
extractIdentity: (auth: AuthRecord) => AccountInfo;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 全部已知平台,**数组顺序 = 菜单顺序 = `mix` 的优先级顺序**。
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ 加新平台时:写完 Provider 再把 `providerImplemented` 翻成 `true`,
|
|
65
|
+
* 并在 `controllers/router.ts` 的 `PROVIDER_FACTORIES` 里加一行。
|
|
66
|
+
* 两处不一致会被 `test/browser/platforms.test.ts` 挡住。
|
|
67
|
+
*/
|
|
68
|
+
export declare const PLATFORM_LIST: readonly PlatformSpec[];
|
|
69
|
+
/** 按 id 索引的平台表(查表用)。 */
|
|
70
|
+
export declare const PLATFORMS: Record<string, PlatformSpec>;
|
|
71
|
+
/** 全部平台 id(按 {@link PLATFORM_LIST} 顺序)。 */
|
|
72
|
+
export declare const PLATFORM_IDS: string[];
|
|
73
|
+
/** 已实现对话 Provider 的平台 id。 */
|
|
74
|
+
export declare const PLATFORMS_WITH_PROVIDER: string[];
|
|
75
|
+
/** 取平台显示名(未登记的平台原样返回 id)。 */
|
|
76
|
+
export declare function platformDisplayName(platform: string): string;
|
|
77
|
+
/** 平台是否已登记(有登录入口)。 */
|
|
78
|
+
export declare function isKnownPlatform(platform: string): boolean;
|
|
79
|
+
/** 平台 → 内部 API 特征(只有配置了的平台才有键)。 */
|
|
80
|
+
export declare const PLATFORM_API_PATTERNS: Record<string, PlatformApiPattern>;
|
|
81
|
+
/** 平台 → 登录入口。 */
|
|
82
|
+
export declare const PLATFORM_LOGIN_URLS: Record<string, string>;
|
|
83
|
+
/** 平台 → 中文显示名。 */
|
|
84
|
+
export declare const PLATFORM_NAMES: Record<string, string>;
|
|
85
|
+
/**
|
|
86
|
+
* 从凭证里提取账号身份(**按平台分派 + 统一兜底**)。
|
|
87
|
+
*
|
|
88
|
+
* 兜底规则(对所有平台生效,顺序不能改):
|
|
89
|
+
* 1. 平台提取器没给出 `identifier` → 用 bearer 前 16 位;连 bearer 都没有 → `'unknown'`;
|
|
90
|
+
* 2. 仍没有 `label` → 用 `identifier` 前 12 位。
|
|
91
|
+
*
|
|
92
|
+
* 这两条保证任何凭证都能得到一个**稳定可比对**的 identifier ——
|
|
93
|
+
* `router` 判断「两个账号文件是不是同一个人」全靠它。
|
|
94
|
+
*/
|
|
95
|
+
export declare function extractAccountInfoSync(platform: string, auth: AuthRecord): AccountInfo;
|
|
96
|
+
//# sourceMappingURL=platforms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platforms.d.ts","sourceRoot":"","sources":["../../src/browser/platforms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAMnC,iBAAiB;AACjB,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC;;;;;;OAMG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;OASG;IACH,eAAe,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,WAAW,CAAC;CACpD;AAsJD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,YAAY,EA6DhD,CAAC;AAEF,wBAAwB;AACxB,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAElD,CAAC;AAEF,2CAA2C;AAC3C,eAAO,MAAM,YAAY,EAAE,MAAM,EAAyC,CAAC;AAE3E,6BAA6B;AAC7B,eAAO,MAAM,uBAAuB,EAAE,MAAM,EAEpB,CAAC;AAEzB,6BAA6B;AAC7B,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,sBAAsB;AACtB,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAUD,mCAAmC;AACnC,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAMpE,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAEtD,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAEjD,CAAC;AAMF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,CAkBtF"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台注册表 —— **「一个平台」的全部声明集中在这里**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要有这个文件
|
|
5
|
+
* 移植前,关于「平台」的信息散在 `credentials.ts` 的**三张并行 Record**
|
|
6
|
+
* (`PLATFORM_API_PATTERNS` / `PLATFORM_LOGIN_URLS` / `PLATFORM_NAMES`)里,
|
|
7
|
+
* 加上 `extractAccountInfoSync` 里一个**按平台名分支的 switch**。要加一个平台,
|
|
8
|
+
* 得记住改 4 个地方 —— 漏掉任何一处都**不报错**,只是该平台在某个菜单/某条
|
|
9
|
+
* 日志里显示成原始 id、或者身份提取静默退化(最难查的一类问题)。
|
|
10
|
+
*
|
|
11
|
+
* 现在:**加平台 = 在 {@link PLATFORM_LIST} 里加一条**。其余代码全部按这张表迭代。
|
|
12
|
+
*
|
|
13
|
+
* ## 依赖方向
|
|
14
|
+
* `platforms.ts` → `credentials.ts`(单向)。凭证的**类型与读写**留在
|
|
15
|
+
* `credentials.ts`,平台元数据与**身份提取**在这里。
|
|
16
|
+
* 反过来不让 `credentials.ts` 依赖本文件,就避免了两者循环 import
|
|
17
|
+
* —— 这也是 `extractAccountInfoSync` 从 `credentials.ts` **搬过来**的原因。
|
|
18
|
+
*
|
|
19
|
+
* ## `providerImplemented` 与 `PLATFORM_MODEL_NAMES` 的关系
|
|
20
|
+
* 「能不能对话」取决于是否真的实现了 Provider(`controllers/router.ts` 的
|
|
21
|
+
* `PROVIDER_FACTORIES`)。本文件用 {@link PlatformSpec.providerImplemented} 声明,
|
|
22
|
+
* `router.ts` 的 `PLATFORM_MODEL_NAMES` 由它派生 —— **单一真相**。
|
|
23
|
+
* 为防止两边写岔,有单测逐条核对(`test/browser/platforms.test.ts`):
|
|
24
|
+
* 声明为 true 的平台必须真有工厂,反之亦然。
|
|
25
|
+
*/
|
|
26
|
+
import { decodeJwt, getCookieValue, } from './credentials.js';
|
|
27
|
+
/* ------------------------------------------------------------------ */
|
|
28
|
+
/* 身份提取(各平台) */
|
|
29
|
+
/* ------------------------------------------------------------------ */
|
|
30
|
+
/** 宽松取字符串(`unknown` → `string`,保持与原实现一致)。 */
|
|
31
|
+
function str(value) {
|
|
32
|
+
return typeof value === 'string' ? value : value == null ? '' : String(value);
|
|
33
|
+
}
|
|
34
|
+
/** ChatGLM:bearer 是 JWT,含 `sub`(用户名)与 `uid`。 */
|
|
35
|
+
function extractChatglm(auth) {
|
|
36
|
+
const info = {};
|
|
37
|
+
const cookies = auth.cookies;
|
|
38
|
+
const payload = decodeJwt(str(auth.bearer));
|
|
39
|
+
if (Object.keys(payload).length > 0) {
|
|
40
|
+
const username = str(payload['sub']);
|
|
41
|
+
const uid = str(payload['uid']);
|
|
42
|
+
if (username)
|
|
43
|
+
info['label'] = username;
|
|
44
|
+
if (uid)
|
|
45
|
+
info['identifier'] = uid;
|
|
46
|
+
info['token_type'] = 'jwt';
|
|
47
|
+
}
|
|
48
|
+
if (!info['identifier']) {
|
|
49
|
+
const uid = getCookieValue(cookies, 'chatglm_user_id');
|
|
50
|
+
if (uid)
|
|
51
|
+
info['identifier'] = uid;
|
|
52
|
+
}
|
|
53
|
+
return info;
|
|
54
|
+
}
|
|
55
|
+
/** Kimi:JWT 可能在 bearer,也可能在 `kimi-auth` cookie 里。 */
|
|
56
|
+
function extractKimi(auth) {
|
|
57
|
+
const info = {};
|
|
58
|
+
const cookies = auth.cookies;
|
|
59
|
+
const bearerPayload = decodeJwt(str(auth.bearer));
|
|
60
|
+
const payload = Object.keys(bearerPayload).length > 0
|
|
61
|
+
? bearerPayload
|
|
62
|
+
: decodeJwt(getCookieValue(cookies, 'kimi-auth'));
|
|
63
|
+
if (Object.keys(payload).length > 0) {
|
|
64
|
+
const ssid = str(payload['ssid']);
|
|
65
|
+
const deviceId = str(payload['device_id']);
|
|
66
|
+
const jti = str(payload['jti']);
|
|
67
|
+
if (ssid) {
|
|
68
|
+
info['identifier'] = ssid;
|
|
69
|
+
info['label'] = `ssid:${ssid.slice(0, 12)}`;
|
|
70
|
+
}
|
|
71
|
+
if (deviceId)
|
|
72
|
+
info['device_id'] = deviceId;
|
|
73
|
+
if (jti)
|
|
74
|
+
info['jti'] = jti;
|
|
75
|
+
info['token_type'] = 'jwt';
|
|
76
|
+
}
|
|
77
|
+
return info;
|
|
78
|
+
}
|
|
79
|
+
/** DeepSeek:优先 `ds_session_id` cookie,退回 bearer 前缀。 */
|
|
80
|
+
function extractDeepseek(auth) {
|
|
81
|
+
const info = {};
|
|
82
|
+
const bearer = str(auth.bearer);
|
|
83
|
+
const sessionId = getCookieValue(auth.cookies, 'ds_session_id');
|
|
84
|
+
if (sessionId) {
|
|
85
|
+
info['identifier'] = sessionId;
|
|
86
|
+
info['label'] = `session:${sessionId.slice(0, 12)}`;
|
|
87
|
+
}
|
|
88
|
+
else if (bearer) {
|
|
89
|
+
info['identifier'] = bearer.slice(0, 16);
|
|
90
|
+
info['label'] = `token:${bearer.slice(0, 8)}...`;
|
|
91
|
+
}
|
|
92
|
+
return info;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 通义千问 / 豆包:**待验证** —— 从 cookies 里找含 `user` / `uid`(豆包还含
|
|
96
|
+
* `session`)的字段。长度 ≥ 64 的值跳过,避免把 JWT 整串当身份显示。
|
|
97
|
+
*/
|
|
98
|
+
function extractCookieScan(auth, kind) {
|
|
99
|
+
const info = {};
|
|
100
|
+
for (const c of auth.cookies ?? []) {
|
|
101
|
+
const name = c?.name ?? '';
|
|
102
|
+
const lower = name.toLowerCase();
|
|
103
|
+
const hit = kind === 'qianwen'
|
|
104
|
+
? lower.includes('user') || lower.includes('uid')
|
|
105
|
+
: lower.includes('user') || lower.includes('uid') || lower.includes('session');
|
|
106
|
+
if (!hit)
|
|
107
|
+
continue;
|
|
108
|
+
const value = c?.value ?? '';
|
|
109
|
+
if (value && value.length < 64) {
|
|
110
|
+
info['identifier'] = value;
|
|
111
|
+
info['label'] = `${name}:${value.slice(0, 12)}`;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return info;
|
|
116
|
+
}
|
|
117
|
+
/** 智谱清言(z.ai):JWT 用 `id` / `email`,不是标准的 `sub` / `uid`。 */
|
|
118
|
+
function extractZhipu(auth) {
|
|
119
|
+
const info = {};
|
|
120
|
+
const payload = decodeJwt(str(auth.bearer));
|
|
121
|
+
if (Object.keys(payload).length > 0) {
|
|
122
|
+
const uid = str(payload['id']) ||
|
|
123
|
+
str(payload['uid']) ||
|
|
124
|
+
str(payload['user_id']) ||
|
|
125
|
+
str(payload['sub']);
|
|
126
|
+
const email = str(payload['email']);
|
|
127
|
+
const name = str(payload['name']);
|
|
128
|
+
if (name) {
|
|
129
|
+
info['name'] = name;
|
|
130
|
+
info['label'] = name;
|
|
131
|
+
}
|
|
132
|
+
else if (email) {
|
|
133
|
+
info['email'] = email;
|
|
134
|
+
info['label'] = email;
|
|
135
|
+
}
|
|
136
|
+
if (uid)
|
|
137
|
+
info['identifier'] = uid;
|
|
138
|
+
info['token_type'] = 'jwt';
|
|
139
|
+
}
|
|
140
|
+
return info;
|
|
141
|
+
}
|
|
142
|
+
/** 未登记平台的兜底:只能拿 bearer 前缀当身份。 */
|
|
143
|
+
function extractGeneric(auth) {
|
|
144
|
+
const info = {};
|
|
145
|
+
const bearer = str(auth.bearer);
|
|
146
|
+
if (bearer) {
|
|
147
|
+
info['identifier'] = bearer.slice(0, 16);
|
|
148
|
+
info['label'] = `token:${bearer.slice(0, 8)}...`;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
info['identifier'] = 'unknown';
|
|
152
|
+
}
|
|
153
|
+
return info;
|
|
154
|
+
}
|
|
155
|
+
/* ------------------------------------------------------------------ */
|
|
156
|
+
/* 注册表 */
|
|
157
|
+
/* ------------------------------------------------------------------ */
|
|
158
|
+
/**
|
|
159
|
+
* 全部已知平台,**数组顺序 = 菜单顺序 = `mix` 的优先级顺序**。
|
|
160
|
+
*
|
|
161
|
+
* ⚠️ 加新平台时:写完 Provider 再把 `providerImplemented` 翻成 `true`,
|
|
162
|
+
* 并在 `controllers/router.ts` 的 `PROVIDER_FACTORIES` 里加一行。
|
|
163
|
+
* 两处不一致会被 `test/browser/platforms.test.ts` 挡住。
|
|
164
|
+
*/
|
|
165
|
+
export const PLATFORM_LIST = [
|
|
166
|
+
{
|
|
167
|
+
id: 'chatglm',
|
|
168
|
+
displayName: 'ChatGLM (智谱)',
|
|
169
|
+
loginUrl: 'https://chatglm.cn/main/chat?lang=zh',
|
|
170
|
+
apiPattern: {
|
|
171
|
+
urlPattern: 'chatglm.cn/chatglm/backend-api/',
|
|
172
|
+
authHeader: 'authorization',
|
|
173
|
+
authPrefix: '',
|
|
174
|
+
},
|
|
175
|
+
providerImplemented: true,
|
|
176
|
+
extractIdentity: extractChatglm,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'deepseek',
|
|
180
|
+
displayName: 'DeepSeek',
|
|
181
|
+
loginUrl: 'https://chat.deepseek.com/',
|
|
182
|
+
apiPattern: {
|
|
183
|
+
urlPattern: 'chat.deepseek.com/api/v0/chat/',
|
|
184
|
+
authHeader: 'authorization',
|
|
185
|
+
authPrefix: 'Bearer ',
|
|
186
|
+
},
|
|
187
|
+
providerImplemented: false,
|
|
188
|
+
extractIdentity: extractDeepseek,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'qianwen',
|
|
192
|
+
displayName: '通义千问',
|
|
193
|
+
loginUrl: 'https://www.qianwen.com/',
|
|
194
|
+
apiPattern: {
|
|
195
|
+
urlPattern: 'chat2-api.qianwen.com/api/v2/chat',
|
|
196
|
+
authHeader: 'x-xsrf-token',
|
|
197
|
+
authPrefix: '',
|
|
198
|
+
},
|
|
199
|
+
providerImplemented: false,
|
|
200
|
+
extractIdentity: (auth) => extractCookieScan(auth, 'qianwen'),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'kimi',
|
|
204
|
+
displayName: 'Kimi (月之暗面)',
|
|
205
|
+
loginUrl: 'https://www.kimi.com/',
|
|
206
|
+
apiPattern: { urlPattern: 'kimi.com', authHeader: 'authorization', authPrefix: 'Bearer ' },
|
|
207
|
+
providerImplemented: false,
|
|
208
|
+
extractIdentity: extractKimi,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'doubao',
|
|
212
|
+
displayName: '豆包 (Doubao)',
|
|
213
|
+
loginUrl: 'https://www.doubao.com/',
|
|
214
|
+
apiPattern: { urlPattern: 'doubao.com', authHeader: '', authPrefix: '', cookieOnly: true },
|
|
215
|
+
providerImplemented: false,
|
|
216
|
+
extractIdentity: (auth) => extractCookieScan(auth, 'doubao'),
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'zhipu',
|
|
220
|
+
displayName: '智谱清言 (GLM)',
|
|
221
|
+
loginUrl: 'https://chat.z.ai/',
|
|
222
|
+
apiPattern: { urlPattern: 'z.ai', authHeader: 'authorization', authPrefix: '' },
|
|
223
|
+
providerImplemented: false,
|
|
224
|
+
extractIdentity: extractZhipu,
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
/** 按 id 索引的平台表(查表用)。 */
|
|
228
|
+
export const PLATFORMS = Object.fromEntries(PLATFORM_LIST.map((spec) => [spec.id, spec]));
|
|
229
|
+
/** 全部平台 id(按 {@link PLATFORM_LIST} 顺序)。 */
|
|
230
|
+
export const PLATFORM_IDS = PLATFORM_LIST.map((spec) => spec.id);
|
|
231
|
+
/** 已实现对话 Provider 的平台 id。 */
|
|
232
|
+
export const PLATFORMS_WITH_PROVIDER = PLATFORM_LIST.filter((spec) => spec.providerImplemented).map((spec) => spec.id);
|
|
233
|
+
/** 取平台显示名(未登记的平台原样返回 id)。 */
|
|
234
|
+
export function platformDisplayName(platform) {
|
|
235
|
+
return PLATFORMS[platform]?.displayName ?? platform;
|
|
236
|
+
}
|
|
237
|
+
/** 平台是否已登记(有登录入口)。 */
|
|
238
|
+
export function isKnownPlatform(platform) {
|
|
239
|
+
return Boolean(PLATFORMS[platform]);
|
|
240
|
+
}
|
|
241
|
+
/* ------------------------------------------------------------------ */
|
|
242
|
+
/* 向后兼容的视图 */
|
|
243
|
+
/* ------------------------------------------------------------------ */
|
|
244
|
+
/* */
|
|
245
|
+
/* 这三张表从 `credentials.ts` 搬来。保留同名的“视图”导出,是因为 */
|
|
246
|
+
/* `index.ts`(库入口)已经对外导出了它们,改名会破坏使用者。 */
|
|
247
|
+
/* 内部代码请直接用 {@link PLATFORMS} —— 视图只是给外部用的。 */
|
|
248
|
+
/** 平台 → 内部 API 特征(只有配置了的平台才有键)。 */
|
|
249
|
+
export const PLATFORM_API_PATTERNS = Object.fromEntries(
|
|
250
|
+
// 类型谓词是必需的:否则 `Object.fromEntries` 会推断出
|
|
251
|
+
// `Record<string, PlatformApiPattern | undefined>`,与声明不符
|
|
252
|
+
PLATFORM_LIST.filter((spec) => Boolean(spec.apiPattern)).map((spec) => [spec.id, spec.apiPattern]));
|
|
253
|
+
/** 平台 → 登录入口。 */
|
|
254
|
+
export const PLATFORM_LOGIN_URLS = Object.fromEntries(PLATFORM_LIST.map((spec) => [spec.id, spec.loginUrl]));
|
|
255
|
+
/** 平台 → 中文显示名。 */
|
|
256
|
+
export const PLATFORM_NAMES = Object.fromEntries(PLATFORM_LIST.map((spec) => [spec.id, spec.displayName]));
|
|
257
|
+
/* ------------------------------------------------------------------ */
|
|
258
|
+
/* 统一入口 */
|
|
259
|
+
/* ------------------------------------------------------------------ */
|
|
260
|
+
/**
|
|
261
|
+
* 从凭证里提取账号身份(**按平台分派 + 统一兜底**)。
|
|
262
|
+
*
|
|
263
|
+
* 兜底规则(对所有平台生效,顺序不能改):
|
|
264
|
+
* 1. 平台提取器没给出 `identifier` → 用 bearer 前 16 位;连 bearer 都没有 → `'unknown'`;
|
|
265
|
+
* 2. 仍没有 `label` → 用 `identifier` 前 12 位。
|
|
266
|
+
*
|
|
267
|
+
* 这两条保证任何凭证都能得到一个**稳定可比对**的 identifier ——
|
|
268
|
+
* `router` 判断「两个账号文件是不是同一个人」全靠它。
|
|
269
|
+
*/
|
|
270
|
+
export function extractAccountInfoSync(platform, auth) {
|
|
271
|
+
const spec = PLATFORMS[platform];
|
|
272
|
+
const info = spec ? spec.extractIdentity(auth) : extractGeneric(auth);
|
|
273
|
+
if (!info['identifier']) {
|
|
274
|
+
const bearer = str(auth.bearer);
|
|
275
|
+
if (bearer) {
|
|
276
|
+
info['identifier'] = bearer.slice(0, 16);
|
|
277
|
+
info['label'] = `token:${bearer.slice(0, 8)}...`;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
info['identifier'] = 'unknown';
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!info['label']) {
|
|
284
|
+
info['label'] = (info['identifier'] ?? '').slice(0, 12);
|
|
285
|
+
}
|
|
286
|
+
return info;
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=platforms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platforms.js","sourceRoot":"","sources":["../../src/browser/platforms.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE 原始流捕获脚本(浏览器注入用)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须抓 SSE 而不是只用 DOM `innerText`:平台把回答按 Markdown 渲染后输出,
|
|
5
|
+
* `innerText` 的信息已被破坏(下划线被当加粗吞掉、半角引号变全角、NBSP 混入、
|
|
6
|
+
* 还夹着用户自己的提问)。写文件场景下这些破坏是致命的 —— 写进去的代码是坏的。
|
|
7
|
+
*
|
|
8
|
+
* 因此在页面层 hook `fetch` / `XMLHttpRequest`,把**未经渲染的 SSE 原文**
|
|
9
|
+
* 缓存到 `window.__glmSSE`,由 Node 侧读取解析。
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ 与原实现(已删除的 Python 版)的差异:**本文件不再逐字节一致** —— 2026-10-14
|
|
12
|
+
* 为了侦察「网页平台自己的请求体是否接受客户端工具定义」(决定能否用平台原生函数调用
|
|
13
|
+
* 通道承载客户端工具)追加了**请求体捕获**。页面侧接口因此多了一个:
|
|
14
|
+
*
|
|
15
|
+
* 页面侧接口:
|
|
16
|
+
* - `window.__glmSSEReset()` —— 开新对话前清空(同时清请求体记录)
|
|
17
|
+
* - `window.__glmSSEGet()` —— 取 `{ text, xhrText, done, urls, rawCount }`
|
|
18
|
+
* - `window.__glmSSEBodiesGet()` —— 取捕获到的平台**请求体**列表(侦察用,最多 20 条)
|
|
19
|
+
*
|
|
20
|
+
* 修改本脚本后请同步 `test/browser/injected-scripts.test.ts` 的体检断言。
|
|
21
|
+
*/
|
|
22
|
+
export declare const SSE_CAPTURE_JS = "\n(() => {\n if (window.__glmSSEHooked) return;\n window.__glmSSEHooked = true;\n\n // \u5355\u6761\u8BF7\u6C42\u4F53\u7684\u622A\u65AD\u957F\u5EA6\uFF1A\u591F\u88C5\u4E0B\u6700\u957F\u63D0\u793A\u8BCD + \u5176\u4F59\u5B57\u6BB5\n const _BODY_MAX = 70000;\n const _REQ_MAX = 20;\n\n const _blank = () => ({ raws: [], xhrText: '', done: false, urls: [], reqs: [] });\n window.__glmSSE = _blank();\n window.__glmSSEReset = () => { window.__glmSSE = _blank(); };\n\n window.__glmSSEGet = () => {\n const s = window.__glmSSE || _blank();\n return {\n text: (s.raws || []).join(''),\n xhrText: s.xhrText || '',\n done: !!s.done,\n urls: s.urls || [],\n rawCount: (s.raws || []).length\n };\n };\n\n /** \u4FA6\u5BDF\u7528\uFF1A\u628A\u8BF7\u6C42\u4F53\u8F6C\u6210\u53EF\u8BFB\u5B57\u7B26\u4E32\uFF08\u541E\u6389\u4E00\u5207\u5F02\u5E38\uFF09\u3002 */\n const _snip = (v) => {\n try {\n if (v === null || v === undefined) return '';\n if (typeof v === 'string') {\n return v.length > _BODY_MAX ? v.slice(0, _BODY_MAX) + '\\u2026[truncated]' : v;\n }\n if (typeof URLSearchParams !== 'undefined' && v instanceof URLSearchParams) {\n return _snip(v.toString());\n }\n if (typeof FormData !== 'undefined' && v instanceof FormData) {\n const o = {};\n v.forEach((val, k) => { o[k] = (typeof val === 'string') ? val : '[blob]'; });\n return _snip(JSON.stringify(o));\n }\n if (typeof Blob !== 'undefined' && v instanceof Blob) {\n return '[Blob ' + v.size + ' bytes]';\n }\n if (typeof ArrayBuffer !== 'undefined' && v instanceof ArrayBuffer) {\n return '[ArrayBuffer ' + v.byteLength + ' bytes]';\n }\n if (typeof v === 'object') {\n // ReadableStream / \u5176\u5B83\u4E0D\u53EF\u5E8F\u5217\u5316\u7684\u4F53\n return _snip(JSON.stringify(v));\n }\n return String(v);\n } catch (e) {\n return '[unserializable]';\n }\n };\n\n /** \u8BB0\u4E00\u6761\u8BF7\u6C42\uFF08\u53EA\u7559\u6700\u8FD1 _REQ_MAX \u6761\uFF09\u3002 */\n const _record = (method, url, contentType, body) => {\n try {\n const reqs = window.__glmSSE.reqs;\n reqs.push({\n method: method || '',\n url: String(url || '').slice(0, 300),\n contentType: String(contentType || ''),\n body: _snip(body)\n });\n if (reqs.length > _REQ_MAX) reqs.splice(0, reqs.length - _REQ_MAX);\n } catch (e) {}\n };\n\n window.__glmSSEBodiesGet = () => {\n const s = window.__glmSSE || _blank();\n return s.reqs || [];\n };\n\n /** \u4ECE fetch \u7684 init / Request \u5BF9\u8C61\u91CC\u53D6 content-type\u3002 */\n const _headerOf = (headers, name) => {\n try {\n if (!headers) return '';\n if (typeof headers.get === 'function') return headers.get(name) || '';\n const key = Object.keys(headers).find((k) => k.toLowerCase() === name);\n return key ? String(headers[key]) : '';\n } catch (e) {\n return '';\n }\n };\n\n // \u53EA\u5173\u5FC3\u300C\u53EF\u80FD\u627F\u8F7D\u5BF9\u8BDD\u5185\u5BB9\u300D\u7684\u8BF7\u6C42\uFF0C\u907F\u514D\u628A\u9759\u6001\u8D44\u6E90\u4E5F\u6293\u4E0B\u6765\n const _looksLikeStream = (ct, url) => {\n ct = (ct || '').toLowerCase();\n url = url || '';\n if (ct.indexOf('event-stream') >= 0) return true;\n if (ct.indexOf('stream') >= 0) return true;\n return /(chat|conversation|assistant|generate|completion|message|sse|stream)/i.test(url);\n };\n\n // ---- 1) fetch \u62E6\u622A ----\n const _origFetch = window.fetch;\n if (_origFetch) {\n window.fetch = async function (...args) {\n // \u4FA6\u5BDF\uFF1A**\u8BF7\u6C42\u4F53**\u5728 await \u4E4B\u524D\u5C31\u5F97\u53D6\uFF08body \u53EF\u80FD\u662F\u6D41\uFF0C\u54CD\u5E94\u56DE\u6765\u5C31\u6D88\u8D39\u6389\u4E86\uFF09\n let reqUrl = '';\n try {\n reqUrl = (typeof args[0] === 'string') ? args[0]\n : (args[0] && args[0].url) ? args[0].url : '';\n if (_looksLikeStream('', reqUrl)) {\n const init = args[1] || {};\n const body = (init.body !== undefined && init.body !== null)\n ? init.body\n : (args[0] && typeof args[0] === 'object' ? args[0].body : null);\n _record(\n init.method || (args[0] && args[0].method) || 'GET',\n reqUrl,\n _headerOf(init.headers, 'content-type') || _headerOf(args[0] && args[0].headers, 'content-type'),\n body\n );\n }\n } catch (e) {}\n\n const resp = await _origFetch.apply(this, args);\n try {\n const url = reqUrl;\n const ct = (resp && resp.headers && resp.headers.get)\n ? (resp.headers.get('content-type') || '') : '';\n if (resp && resp.body && _looksLikeStream(ct, url)) {\n try { window.__glmSSE.urls.push(url.slice(0, 200)); } catch (e) {}\n const clone = resp.clone();\n const reader = clone.body.getReader();\n const dec = new TextDecoder('utf-8');\n (async () => {\n try {\n for (;;) {\n const r = await reader.read();\n if (r.done) break;\n window.__glmSSE.raws.push(dec.decode(r.value, { stream: true }));\n }\n } catch (e) {\n } finally {\n window.__glmSSE.done = true;\n }\n })();\n }\n } catch (e) {}\n return resp;\n };\n }\n\n // ---- 2) XMLHttpRequest \u62E6\u622A\uFF08\u90E8\u5206\u63A5\u53E3\u8D70 XHR \u6D41\u5F0F\uFF09----\n try {\n const _origOpen = XMLHttpRequest.prototype.open;\n const _origSend = XMLHttpRequest.prototype.send;\n XMLHttpRequest.prototype.open = function (method, url, ...rest) {\n try { this.__glmURL = url || ''; this.__glmMethod = method || ''; } catch (e) {}\n return _origOpen.call(this, method, url, ...rest);\n };\n XMLHttpRequest.prototype.send = function (...args) {\n const self = this;\n try {\n // \u4FA6\u5BDF\uFF1A\u8BF7\u6C42\u4F53\n if (_looksLikeStream('', self.__glmURL)) {\n let ctype = '';\n try { ctype = self.getRequestHeader ? (self.getRequestHeader('content-type') || '') : ''; } catch (e) {}\n _record(self.__glmMethod || 'POST', self.__glmURL, ctype, args[0]);\n }\n } catch (e) {}\n try {\n self.addEventListener('progress', () => {\n try {\n const t = self.responseText || '';\n if (t) window.__glmSSE.xhrText = t;\n } catch (e) {}\n });\n } catch (e) {}\n return _origSend.apply(self, args);\n };\n } catch (e) {}\n})();\n";
|
|
23
|
+
//# sourceMappingURL=sse-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-capture.d.ts","sourceRoot":"","sources":["../../src/browser/sse-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,cAAc,ylNAgL1B,CAAC"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE 原始流捕获脚本(浏览器注入用)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须抓 SSE 而不是只用 DOM `innerText`:平台把回答按 Markdown 渲染后输出,
|
|
5
|
+
* `innerText` 的信息已被破坏(下划线被当加粗吞掉、半角引号变全角、NBSP 混入、
|
|
6
|
+
* 还夹着用户自己的提问)。写文件场景下这些破坏是致命的 —— 写进去的代码是坏的。
|
|
7
|
+
*
|
|
8
|
+
* 因此在页面层 hook `fetch` / `XMLHttpRequest`,把**未经渲染的 SSE 原文**
|
|
9
|
+
* 缓存到 `window.__glmSSE`,由 Node 侧读取解析。
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ 与原实现(已删除的 Python 版)的差异:**本文件不再逐字节一致** —— 2026-10-14
|
|
12
|
+
* 为了侦察「网页平台自己的请求体是否接受客户端工具定义」(决定能否用平台原生函数调用
|
|
13
|
+
* 通道承载客户端工具)追加了**请求体捕获**。页面侧接口因此多了一个:
|
|
14
|
+
*
|
|
15
|
+
* 页面侧接口:
|
|
16
|
+
* - `window.__glmSSEReset()` —— 开新对话前清空(同时清请求体记录)
|
|
17
|
+
* - `window.__glmSSEGet()` —— 取 `{ text, xhrText, done, urls, rawCount }`
|
|
18
|
+
* - `window.__glmSSEBodiesGet()` —— 取捕获到的平台**请求体**列表(侦察用,最多 20 条)
|
|
19
|
+
*
|
|
20
|
+
* 修改本脚本后请同步 `test/browser/injected-scripts.test.ts` 的体检断言。
|
|
21
|
+
*/
|
|
22
|
+
export const SSE_CAPTURE_JS = `
|
|
23
|
+
(() => {
|
|
24
|
+
if (window.__glmSSEHooked) return;
|
|
25
|
+
window.__glmSSEHooked = true;
|
|
26
|
+
|
|
27
|
+
// 单条请求体的截断长度:够装下最长提示词 + 其余字段
|
|
28
|
+
const _BODY_MAX = 70000;
|
|
29
|
+
const _REQ_MAX = 20;
|
|
30
|
+
|
|
31
|
+
const _blank = () => ({ raws: [], xhrText: '', done: false, urls: [], reqs: [] });
|
|
32
|
+
window.__glmSSE = _blank();
|
|
33
|
+
window.__glmSSEReset = () => { window.__glmSSE = _blank(); };
|
|
34
|
+
|
|
35
|
+
window.__glmSSEGet = () => {
|
|
36
|
+
const s = window.__glmSSE || _blank();
|
|
37
|
+
return {
|
|
38
|
+
text: (s.raws || []).join(''),
|
|
39
|
+
xhrText: s.xhrText || '',
|
|
40
|
+
done: !!s.done,
|
|
41
|
+
urls: s.urls || [],
|
|
42
|
+
rawCount: (s.raws || []).length
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** 侦察用:把请求体转成可读字符串(吞掉一切异常)。 */
|
|
47
|
+
const _snip = (v) => {
|
|
48
|
+
try {
|
|
49
|
+
if (v === null || v === undefined) return '';
|
|
50
|
+
if (typeof v === 'string') {
|
|
51
|
+
return v.length > _BODY_MAX ? v.slice(0, _BODY_MAX) + '\\u2026[truncated]' : v;
|
|
52
|
+
}
|
|
53
|
+
if (typeof URLSearchParams !== 'undefined' && v instanceof URLSearchParams) {
|
|
54
|
+
return _snip(v.toString());
|
|
55
|
+
}
|
|
56
|
+
if (typeof FormData !== 'undefined' && v instanceof FormData) {
|
|
57
|
+
const o = {};
|
|
58
|
+
v.forEach((val, k) => { o[k] = (typeof val === 'string') ? val : '[blob]'; });
|
|
59
|
+
return _snip(JSON.stringify(o));
|
|
60
|
+
}
|
|
61
|
+
if (typeof Blob !== 'undefined' && v instanceof Blob) {
|
|
62
|
+
return '[Blob ' + v.size + ' bytes]';
|
|
63
|
+
}
|
|
64
|
+
if (typeof ArrayBuffer !== 'undefined' && v instanceof ArrayBuffer) {
|
|
65
|
+
return '[ArrayBuffer ' + v.byteLength + ' bytes]';
|
|
66
|
+
}
|
|
67
|
+
if (typeof v === 'object') {
|
|
68
|
+
// ReadableStream / 其它不可序列化的体
|
|
69
|
+
return _snip(JSON.stringify(v));
|
|
70
|
+
}
|
|
71
|
+
return String(v);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return '[unserializable]';
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** 记一条请求(只留最近 _REQ_MAX 条)。 */
|
|
78
|
+
const _record = (method, url, contentType, body) => {
|
|
79
|
+
try {
|
|
80
|
+
const reqs = window.__glmSSE.reqs;
|
|
81
|
+
reqs.push({
|
|
82
|
+
method: method || '',
|
|
83
|
+
url: String(url || '').slice(0, 300),
|
|
84
|
+
contentType: String(contentType || ''),
|
|
85
|
+
body: _snip(body)
|
|
86
|
+
});
|
|
87
|
+
if (reqs.length > _REQ_MAX) reqs.splice(0, reqs.length - _REQ_MAX);
|
|
88
|
+
} catch (e) {}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
window.__glmSSEBodiesGet = () => {
|
|
92
|
+
const s = window.__glmSSE || _blank();
|
|
93
|
+
return s.reqs || [];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** 从 fetch 的 init / Request 对象里取 content-type。 */
|
|
97
|
+
const _headerOf = (headers, name) => {
|
|
98
|
+
try {
|
|
99
|
+
if (!headers) return '';
|
|
100
|
+
if (typeof headers.get === 'function') return headers.get(name) || '';
|
|
101
|
+
const key = Object.keys(headers).find((k) => k.toLowerCase() === name);
|
|
102
|
+
return key ? String(headers[key]) : '';
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// 只关心「可能承载对话内容」的请求,避免把静态资源也抓下来
|
|
109
|
+
const _looksLikeStream = (ct, url) => {
|
|
110
|
+
ct = (ct || '').toLowerCase();
|
|
111
|
+
url = url || '';
|
|
112
|
+
if (ct.indexOf('event-stream') >= 0) return true;
|
|
113
|
+
if (ct.indexOf('stream') >= 0) return true;
|
|
114
|
+
return /(chat|conversation|assistant|generate|completion|message|sse|stream)/i.test(url);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// ---- 1) fetch 拦截 ----
|
|
118
|
+
const _origFetch = window.fetch;
|
|
119
|
+
if (_origFetch) {
|
|
120
|
+
window.fetch = async function (...args) {
|
|
121
|
+
// 侦察:**请求体**在 await 之前就得取(body 可能是流,响应回来就消费掉了)
|
|
122
|
+
let reqUrl = '';
|
|
123
|
+
try {
|
|
124
|
+
reqUrl = (typeof args[0] === 'string') ? args[0]
|
|
125
|
+
: (args[0] && args[0].url) ? args[0].url : '';
|
|
126
|
+
if (_looksLikeStream('', reqUrl)) {
|
|
127
|
+
const init = args[1] || {};
|
|
128
|
+
const body = (init.body !== undefined && init.body !== null)
|
|
129
|
+
? init.body
|
|
130
|
+
: (args[0] && typeof args[0] === 'object' ? args[0].body : null);
|
|
131
|
+
_record(
|
|
132
|
+
init.method || (args[0] && args[0].method) || 'GET',
|
|
133
|
+
reqUrl,
|
|
134
|
+
_headerOf(init.headers, 'content-type') || _headerOf(args[0] && args[0].headers, 'content-type'),
|
|
135
|
+
body
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} catch (e) {}
|
|
139
|
+
|
|
140
|
+
const resp = await _origFetch.apply(this, args);
|
|
141
|
+
try {
|
|
142
|
+
const url = reqUrl;
|
|
143
|
+
const ct = (resp && resp.headers && resp.headers.get)
|
|
144
|
+
? (resp.headers.get('content-type') || '') : '';
|
|
145
|
+
if (resp && resp.body && _looksLikeStream(ct, url)) {
|
|
146
|
+
try { window.__glmSSE.urls.push(url.slice(0, 200)); } catch (e) {}
|
|
147
|
+
const clone = resp.clone();
|
|
148
|
+
const reader = clone.body.getReader();
|
|
149
|
+
const dec = new TextDecoder('utf-8');
|
|
150
|
+
(async () => {
|
|
151
|
+
try {
|
|
152
|
+
for (;;) {
|
|
153
|
+
const r = await reader.read();
|
|
154
|
+
if (r.done) break;
|
|
155
|
+
window.__glmSSE.raws.push(dec.decode(r.value, { stream: true }));
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
} finally {
|
|
159
|
+
window.__glmSSE.done = true;
|
|
160
|
+
}
|
|
161
|
+
})();
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {}
|
|
164
|
+
return resp;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---- 2) XMLHttpRequest 拦截(部分接口走 XHR 流式)----
|
|
169
|
+
try {
|
|
170
|
+
const _origOpen = XMLHttpRequest.prototype.open;
|
|
171
|
+
const _origSend = XMLHttpRequest.prototype.send;
|
|
172
|
+
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
|
|
173
|
+
try { this.__glmURL = url || ''; this.__glmMethod = method || ''; } catch (e) {}
|
|
174
|
+
return _origOpen.call(this, method, url, ...rest);
|
|
175
|
+
};
|
|
176
|
+
XMLHttpRequest.prototype.send = function (...args) {
|
|
177
|
+
const self = this;
|
|
178
|
+
try {
|
|
179
|
+
// 侦察:请求体
|
|
180
|
+
if (_looksLikeStream('', self.__glmURL)) {
|
|
181
|
+
let ctype = '';
|
|
182
|
+
try { ctype = self.getRequestHeader ? (self.getRequestHeader('content-type') || '') : ''; } catch (e) {}
|
|
183
|
+
_record(self.__glmMethod || 'POST', self.__glmURL, ctype, args[0]);
|
|
184
|
+
}
|
|
185
|
+
} catch (e) {}
|
|
186
|
+
try {
|
|
187
|
+
self.addEventListener('progress', () => {
|
|
188
|
+
try {
|
|
189
|
+
const t = self.responseText || '';
|
|
190
|
+
if (t) window.__glmSSE.xhrText = t;
|
|
191
|
+
} catch (e) {}
|
|
192
|
+
});
|
|
193
|
+
} catch (e) {}
|
|
194
|
+
return _origSend.apply(self, args);
|
|
195
|
+
};
|
|
196
|
+
} catch (e) {}
|
|
197
|
+
})();
|
|
198
|
+
`;
|
|
199
|
+
//# sourceMappingURL=sse-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-capture.js","sourceRoot":"","sources":["../../src/browser/sse-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgL7B,CAAC"}
|