webaiapi 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -2
  3. package/bin/webapi.mjs +45 -0
  4. package/dist/browser/auth-capture.d.ts +110 -0
  5. package/dist/browser/auth-capture.d.ts.map +1 -0
  6. package/dist/browser/auth-capture.js +487 -0
  7. package/dist/browser/auth-capture.js.map +1 -0
  8. package/dist/browser/credentials.d.ts +102 -0
  9. package/dist/browser/credentials.d.ts.map +1 -0
  10. package/dist/browser/credentials.js +209 -0
  11. package/dist/browser/credentials.js.map +1 -0
  12. package/dist/browser/js-eval.d.ts +44 -0
  13. package/dist/browser/js-eval.d.ts.map +1 -0
  14. package/dist/browser/js-eval.js +20 -0
  15. package/dist/browser/js-eval.js.map +1 -0
  16. package/dist/browser/login-flow.d.ts +171 -0
  17. package/dist/browser/login-flow.d.ts.map +1 -0
  18. package/dist/browser/login-flow.js +606 -0
  19. package/dist/browser/login-flow.js.map +1 -0
  20. package/dist/browser/manager.d.ts +144 -0
  21. package/dist/browser/manager.d.ts.map +1 -0
  22. package/dist/browser/manager.js +586 -0
  23. package/dist/browser/manager.js.map +1 -0
  24. package/dist/browser/platforms.d.ts +96 -0
  25. package/dist/browser/platforms.d.ts.map +1 -0
  26. package/dist/browser/platforms.js +288 -0
  27. package/dist/browser/platforms.js.map +1 -0
  28. package/dist/browser/sse-capture.d.ts +23 -0
  29. package/dist/browser/sse-capture.d.ts.map +1 -0
  30. package/dist/browser/sse-capture.js +199 -0
  31. package/dist/browser/sse-capture.js.map +1 -0
  32. package/dist/browser/stealth.d.ts +12 -0
  33. package/dist/browser/stealth.d.ts.map +1 -0
  34. package/dist/browser/stealth.js +12 -0
  35. package/dist/browser/stealth.js.map +1 -0
  36. package/dist/cli.d.ts +12 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +441 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/config.d.ts +90 -0
  41. package/dist/config.d.ts.map +1 -0
  42. package/dist/config.js +246 -0
  43. package/dist/config.js.map +1 -0
  44. package/dist/controllers/history-db.d.ts +56 -0
  45. package/dist/controllers/history-db.d.ts.map +1 -0
  46. package/dist/controllers/history-db.js +260 -0
  47. package/dist/controllers/history-db.js.map +1 -0
  48. package/dist/controllers/request-pool.d.ts +78 -0
  49. package/dist/controllers/request-pool.d.ts.map +1 -0
  50. package/dist/controllers/request-pool.js +222 -0
  51. package/dist/controllers/request-pool.js.map +1 -0
  52. package/dist/controllers/router.d.ts +172 -0
  53. package/dist/controllers/router.d.ts.map +1 -0
  54. package/dist/controllers/router.js +569 -0
  55. package/dist/controllers/router.js.map +1 -0
  56. package/dist/index.d.ts +72 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +61 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/log/api-logger.d.ts +55 -0
  61. package/dist/log/api-logger.d.ts.map +1 -0
  62. package/dist/log/api-logger.js +264 -0
  63. package/dist/log/api-logger.js.map +1 -0
  64. package/dist/log/debug-logger.d.ts +49 -0
  65. package/dist/log/debug-logger.d.ts.map +1 -0
  66. package/dist/log/debug-logger.js +285 -0
  67. package/dist/log/debug-logger.js.map +1 -0
  68. package/dist/log/logger.d.ts +16 -0
  69. package/dist/log/logger.d.ts.map +1 -0
  70. package/dist/log/logger.js +35 -0
  71. package/dist/log/logger.js.map +1 -0
  72. package/dist/postinstall.d.ts +3 -0
  73. package/dist/postinstall.d.ts.map +1 -0
  74. package/dist/postinstall.js +90 -0
  75. package/dist/postinstall.js.map +1 -0
  76. package/dist/providers/base.d.ts +83 -0
  77. package/dist/providers/base.d.ts.map +1 -0
  78. package/dist/providers/base.js +51 -0
  79. package/dist/providers/base.js.map +1 -0
  80. package/dist/providers/chatglm-dom-extract.d.ts +24 -0
  81. package/dist/providers/chatglm-dom-extract.d.ts.map +1 -0
  82. package/dist/providers/chatglm-dom-extract.js +175 -0
  83. package/dist/providers/chatglm-dom-extract.js.map +1 -0
  84. package/dist/providers/chatglm-dom.d.ts +110 -0
  85. package/dist/providers/chatglm-dom.d.ts.map +1 -0
  86. package/dist/providers/chatglm-dom.js +384 -0
  87. package/dist/providers/chatglm-dom.js.map +1 -0
  88. package/dist/providers/chatglm-sse.d.ts +119 -0
  89. package/dist/providers/chatglm-sse.d.ts.map +1 -0
  90. package/dist/providers/chatglm-sse.js +241 -0
  91. package/dist/providers/chatglm-sse.js.map +1 -0
  92. package/dist/providers/chatglm.d.ts +288 -0
  93. package/dist/providers/chatglm.d.ts.map +1 -0
  94. package/dist/providers/chatglm.js +1631 -0
  95. package/dist/providers/chatglm.js.map +1 -0
  96. package/dist/routes/chat.d.ts +117 -0
  97. package/dist/routes/chat.d.ts.map +1 -0
  98. package/dist/routes/chat.js +1346 -0
  99. package/dist/routes/chat.js.map +1 -0
  100. package/dist/routes/compress.d.ts +142 -0
  101. package/dist/routes/compress.d.ts.map +1 -0
  102. package/dist/routes/compress.js +551 -0
  103. package/dist/routes/compress.js.map +1 -0
  104. package/dist/routes/failover.d.ts +34 -0
  105. package/dist/routes/failover.d.ts.map +1 -0
  106. package/dist/routes/failover.js +221 -0
  107. package/dist/routes/failover.js.map +1 -0
  108. package/dist/routes/helpers.d.ts +174 -0
  109. package/dist/routes/helpers.d.ts.map +1 -0
  110. package/dist/routes/helpers.js +525 -0
  111. package/dist/routes/helpers.js.map +1 -0
  112. package/dist/routes/meta.d.ts +31 -0
  113. package/dist/routes/meta.d.ts.map +1 -0
  114. package/dist/routes/meta.js +184 -0
  115. package/dist/routes/meta.js.map +1 -0
  116. package/dist/routes/sse.d.ts +82 -0
  117. package/dist/routes/sse.d.ts.map +1 -0
  118. package/dist/routes/sse.js +77 -0
  119. package/dist/routes/sse.js.map +1 -0
  120. package/dist/server/app.d.ts +24 -0
  121. package/dist/server/app.d.ts.map +1 -0
  122. package/dist/server/app.js +292 -0
  123. package/dist/server/app.js.map +1 -0
  124. package/dist/server/middleware/api-key.d.ts +61 -0
  125. package/dist/server/middleware/api-key.d.ts.map +1 -0
  126. package/dist/server/middleware/api-key.js +95 -0
  127. package/dist/server/middleware/api-key.js.map +1 -0
  128. package/dist/server/middleware/rate-limit.d.ts +67 -0
  129. package/dist/server/middleware/rate-limit.d.ts.map +1 -0
  130. package/dist/server/middleware/rate-limit.js +134 -0
  131. package/dist/server/middleware/rate-limit.js.map +1 -0
  132. package/dist/server/middleware/timeout.d.ts +34 -0
  133. package/dist/server/middleware/timeout.d.ts.map +1 -0
  134. package/dist/server/middleware/timeout.js +64 -0
  135. package/dist/server/middleware/timeout.js.map +1 -0
  136. package/dist/server/types.d.ts +20 -0
  137. package/dist/server/types.d.ts.map +1 -0
  138. package/dist/server/types.js +2 -0
  139. package/dist/server/types.js.map +1 -0
  140. package/dist/tools/build.d.ts +14 -0
  141. package/dist/tools/build.d.ts.map +1 -0
  142. package/dist/tools/build.js +105 -0
  143. package/dist/tools/build.js.map +1 -0
  144. package/dist/tools/clean.d.ts +32 -0
  145. package/dist/tools/clean.d.ts.map +1 -0
  146. package/dist/tools/clean.js +65 -0
  147. package/dist/tools/clean.js.map +1 -0
  148. package/dist/tools/convert.d.ts +50 -0
  149. package/dist/tools/convert.d.ts.map +1 -0
  150. package/dist/tools/convert.js +289 -0
  151. package/dist/tools/convert.js.map +1 -0
  152. package/dist/tools/names.d.ts +138 -0
  153. package/dist/tools/names.d.ts.map +1 -0
  154. package/dist/tools/names.js +578 -0
  155. package/dist/tools/names.js.map +1 -0
  156. package/dist/tools/parse.d.ts +31 -0
  157. package/dist/tools/parse.d.ts.map +1 -0
  158. package/dist/tools/parse.js +273 -0
  159. package/dist/tools/parse.js.map +1 -0
  160. package/dist/tools/placeholder.d.ts +21 -0
  161. package/dist/tools/placeholder.d.ts.map +1 -0
  162. package/dist/tools/placeholder.js +135 -0
  163. package/dist/tools/placeholder.js.map +1 -0
  164. package/dist/tools/prompts.d.ts +42 -0
  165. package/dist/tools/prompts.d.ts.map +1 -0
  166. package/dist/tools/prompts.js +69 -0
  167. package/dist/tools/prompts.js.map +1 -0
  168. package/dist/tools/protocol.d.ts +58 -0
  169. package/dist/tools/protocol.d.ts.map +1 -0
  170. package/dist/tools/protocol.js +287 -0
  171. package/dist/tools/protocol.js.map +1 -0
  172. package/dist/tools/repair.d.ts +83 -0
  173. package/dist/tools/repair.d.ts.map +1 -0
  174. package/dist/tools/repair.js +510 -0
  175. package/dist/tools/repair.js.map +1 -0
  176. package/dist/tools/workspace.d.ts +40 -0
  177. package/dist/tools/workspace.d.ts.map +1 -0
  178. package/dist/tools/workspace.js +224 -0
  179. package/dist/tools/workspace.js.map +1 -0
  180. package/dist/types/message.d.ts +87 -0
  181. package/dist/types/message.d.ts.map +1 -0
  182. package/dist/types/message.js +85 -0
  183. package/dist/types/message.js.map +1 -0
  184. package/dist/types/openai.d.ts +100 -0
  185. package/dist/types/openai.d.ts.map +1 -0
  186. package/dist/types/openai.js +66 -0
  187. package/dist/types/openai.js.map +1 -0
  188. package/dist/utils/errors.d.ts +82 -0
  189. package/dist/utils/errors.d.ts.map +1 -0
  190. package/dist/utils/errors.js +115 -0
  191. package/dist/utils/errors.js.map +1 -0
  192. package/dist/utils/mutex.d.ts +31 -0
  193. package/dist/utils/mutex.d.ts.map +1 -0
  194. package/dist/utils/mutex.js +61 -0
  195. package/dist/utils/mutex.js.map +1 -0
  196. package/dist/utils/prompt.d.ts +150 -0
  197. package/dist/utils/prompt.d.ts.map +1 -0
  198. package/dist/utils/prompt.js +514 -0
  199. package/dist/utils/prompt.js.map +1 -0
  200. package/dist/utils/text.d.ts +30 -0
  201. package/dist/utils/text.d.ts.map +1 -0
  202. package/dist/utils/text.js +70 -0
  203. package/dist/utils/text.js.map +1 -0
  204. package/package.json +67 -4
  205. package/postinstall.mjs +34 -0
@@ -0,0 +1,184 @@
1
+ import { getAvailableModelsList, settings } from '../config.js';
2
+ import { getHistoryDb } from '../controllers/history-db.js';
3
+ import { getRequestPool, getScheduler } from '../controllers/request-pool.js';
4
+ import { getModelRouter } from '../controllers/router.js';
5
+ import { logger } from '../log/logger.js';
6
+ import { makeModelInfo } from '../types/openai.js';
7
+ /**
8
+ * 注册元信息路由。
9
+ *
10
+ * ⚠️ 路由前缀由调用方在 Fastify 注册时给出(`settings.apiPrefix`,
11
+ * 默认 `/v1`),所以这里的路径都不带 `/v1`。
12
+ */
13
+ export function registerMetaRoutes(app) {
14
+ /**
15
+ * 可用模型列表(OpenAI 兼容)。
16
+ *
17
+ * 由两部分组成:
18
+ * 1. 配置里的对外展示模型(`AVAILABLE_MODELS`,默认 `mix`);
19
+ * 2. **已捕获凭证的平台名**作为独立模型(动态:用户捕获了哪个平台,
20
+ * 就能直接调用该平台名)。
21
+ *
22
+ * 调用平台名模型时只调度对应平台,不跨平台(与 `mix` 不同)。
23
+ */
24
+ app.get('/models', async () => {
25
+ const modelRouter = getModelRouter();
26
+ const modelList = [];
27
+ const seenIds = new Set();
28
+ for (const modelId of getAvailableModelsList()) {
29
+ // 支持 `model:platform` 格式
30
+ let model;
31
+ let platform;
32
+ if (modelId.includes(':')) {
33
+ const idx = modelId.indexOf(':');
34
+ model = modelId.slice(0, idx);
35
+ platform = modelId.slice(idx + 1);
36
+ }
37
+ else {
38
+ model = modelId;
39
+ platform = 'unknown';
40
+ }
41
+ if (seenIds.has(model))
42
+ continue;
43
+ seenIds.add(model);
44
+ modelList.push(makeModelInfo(model, platform));
45
+ }
46
+ // 追加已登录平台名作为独立模型
47
+ for (const platform of modelRouter.getAvailablePlatformModels()) {
48
+ if (seenIds.has(platform))
49
+ continue;
50
+ seenIds.add(platform);
51
+ modelList.push(makeModelInfo(platform, platform));
52
+ }
53
+ // 追加 Provider 登记过的模型别名(glm-4 / glm-3 → chatglm)。
54
+ // ⚠️ 不加这段的话,`/v1/models` 与实际可用性**对不上**:
55
+ // `model=glm-4` 能用(isModelAvailable 以登记表为准),列表里却没有它 ——
56
+ // OpenAI 兼容客户端会先拉列表再让用户挑,挑不到就等于没有这个功能。
57
+ for (const model of modelRouter.getAllModels()) {
58
+ if (seenIds.has(model))
59
+ continue;
60
+ seenIds.add(model);
61
+ modelList.push(makeModelInfo(model, modelRouter.platformOfModel(model) ?? 'unknown'));
62
+ }
63
+ return { object: 'list', data: modelList };
64
+ });
65
+ /**
66
+ * 健康检查。
67
+ *
68
+ * ⚠️ 这里会**逐个**调用 `checkLoginStatus()`(chatglm 的是整页 DOM 探测)。
69
+ * 它内部有 120s 的「已登录」缓存,所以正常轮询不会每次都做整页导航。
70
+ */
71
+ app.get('/health', async () => {
72
+ const modelRouter = getModelRouter();
73
+ const providerStatus = {};
74
+ for (const [name, provider] of modelRouter.getAllProviders()) {
75
+ try {
76
+ const isLoggedIn = await provider.checkLoginStatus();
77
+ providerStatus[name] = { status: isLoggedIn ? 'connected' : 'disconnected' };
78
+ }
79
+ catch (error) {
80
+ providerStatus[name] = { status: 'error', message: String(error) };
81
+ }
82
+ }
83
+ return {
84
+ status: 'ok',
85
+ version: settings.appVersion,
86
+ providers: providerStatus,
87
+ };
88
+ });
89
+ /**
90
+ * 系统状态。
91
+ *
92
+ * 「账号与辅助通道」这一段是把**多账号到底有没有生效**变成可验证的事实。
93
+ * 实测踩坑:登录了两个账号但其实是同一个(浏览器里还是旧账号登录态),
94
+ * 只看「账号数」看不出来,得看身份与角色。
95
+ */
96
+ app.get('/status', async () => {
97
+ const requestPool = getRequestPool();
98
+ const scheduler = getScheduler();
99
+ const modelRouter = getModelRouter();
100
+ let accountsSummary = {};
101
+ let auxChannel = { enabled: false };
102
+ try {
103
+ accountsSummary = modelRouter.getAccountsSummary();
104
+ const aux = modelRouter.getAuxCandidates();
105
+ const first = aux[0];
106
+ if (first) {
107
+ const [auxName, auxProvider] = first;
108
+ const auxAccountId = auxProvider.getAccountId();
109
+ auxChannel = {
110
+ enabled: true,
111
+ platform: auxName,
112
+ account_id: auxAccountId,
113
+ identity: accountsSummary[auxName]?.find((a) => a.account_id === auxAccountId)?.identity ?? '',
114
+ };
115
+ }
116
+ }
117
+ catch (error) {
118
+ auxChannel = { enabled: false, error: String(error) };
119
+ }
120
+ return {
121
+ request_pool: requestPool.getStatus(),
122
+ scheduler: scheduler.getLoadStatus(),
123
+ accounts: accountsSummary,
124
+ aux_channel: auxChannel,
125
+ };
126
+ });
127
+ }
128
+ /**
129
+ * 启动时的日志重置与历史库自检。
130
+ *
131
+ * ⚠️ 与原实现的差异:Python 是**覆盖写** `logs/api_requests.log` 并写入一行
132
+ * `# API 请求日志 - <时间> 启动`;TS 侧由 `resetApiLog()` 清空(保持「原样记录、
133
+ * 不加工」的契约 —— 头部注释会干扰对日志的 `grep`)。
134
+ *
135
+ * 历史库清理是**异步**的,日志里会带上统计数字。
136
+ */
137
+ export async function runStartupTasks() {
138
+ logger.info(`启动 ${settings.appName} v${settings.appVersion}`);
139
+ logger.info(`调试模式: ${String(settings.debug)}`);
140
+ logger.info(`API 地址: http://${settings.apiHost}:${settings.apiPort}${settings.apiPrefix}`);
141
+ try {
142
+ const historyDb = getHistoryDb();
143
+ const deleted = await historyDb.cleanupOld();
144
+ const stats = await historyDb.stats();
145
+ logger.info(`历史数据库: ${stats.total_messages} 条消息, ` +
146
+ `${stats.conversations} 个会话, TTL=${stats.ttl_days}天` +
147
+ (deleted > 0 ? `(启动清理 ${deleted} 条过期)` : '') +
148
+ (historyDb.available ? '' : '(⚠️ 已降级为无历史模式)'));
149
+ }
150
+ catch (error) {
151
+ logger.error(`启动清理历史消息失败: ${String(error)}`);
152
+ }
153
+ }
154
+ /**
155
+ * 后台定期清理历史消息(每 1 小时一次)。
156
+ *
157
+ * 返回一个可取消的句柄(关服时调用 `stop()`)。
158
+ */
159
+ export function startPeriodicHistoryCleanup(intervalMs = 3600_000) {
160
+ let stopped = false;
161
+ const timer = setInterval(() => {
162
+ if (stopped)
163
+ return;
164
+ void (async () => {
165
+ try {
166
+ const deleted = await getHistoryDb().cleanupOld();
167
+ if (deleted > 0)
168
+ logger.info(`定期清理历史消息: 清理 ${deleted} 条过期消息`);
169
+ }
170
+ catch (error) {
171
+ logger.error(`定期清理历史消息失败: ${String(error)}`);
172
+ }
173
+ })();
174
+ }, intervalMs);
175
+ // 不要因为这个定时器而阻塞进程退出
176
+ timer.unref?.();
177
+ return {
178
+ stop: () => {
179
+ stopped = true;
180
+ clearInterval(timer);
181
+ },
182
+ };
183
+ }
184
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../src/routes/meta.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * OpenAI 兼容的 SSE 分片构造 —— 从 `legacy-python/src/api/routes.py` 的
3
+ * `chat_completions` 里抽出的公共部分。
4
+ *
5
+ * ⚠️ 两条必须遵守的格式约束:
6
+ * 1. **`tool_calls` 分片必须在 `data: [DONE]` 之前发出**。客户端(CodeBuddy/Trae)
7
+ * 是收到 `[DONE]` 就结束本轮解析的,之后发的一律丢弃。
8
+ * 2. **`finish_reason` 只能出现一次**,且在最后一个内容分片里;
9
+ * 带 `tool_calls` 时必须是 `"tool_calls"` 而不是 `"stop"`
10
+ * (否则客户端会把这一轮当成"已答完",不去执行工具)。
11
+ */
12
+ import type { ToolCallPayload } from '../types/message.js';
13
+ /** SSE 结束标记。 */
14
+ export declare const DONE = "data: [DONE]\n\n";
15
+ /** 分片里的 delta。 */
16
+ export interface ChunkDelta {
17
+ role?: string;
18
+ content?: string;
19
+ tool_calls?: Array<Record<string, unknown>>;
20
+ }
21
+ /** 一个 `chat.completion.chunk`。 */
22
+ export interface ChatCompletionChunk {
23
+ id: string;
24
+ object: 'chat.completion.chunk';
25
+ created: number;
26
+ model: string;
27
+ choices: Array<{
28
+ index: number;
29
+ delta: ChunkDelta;
30
+ finish_reason: string | null;
31
+ }>;
32
+ }
33
+ /**
34
+ * 把对象序列化成一条 SSE 消息。
35
+ *
36
+ * ⚠️ 用 `JSON.stringify` 而非任何「美化」输出:客户端按行解析。
37
+ * 中文**不需要**转义(JS 的 `JSON.stringify` 默认输出原字符,
38
+ * 等价于 Python 的 `ensure_ascii=False`)。
39
+ */
40
+ export declare function sseData(payload: unknown): string;
41
+ /** 当前 Unix 时间戳(秒)。 */
42
+ export declare function nowSeconds(now?: number): number;
43
+ /**
44
+ * 构造一个内容分片。
45
+ *
46
+ * ⚠️ 与 Python 的差异:Python 用 `int(asyncio.get_event_loop().time())` 填 `created`
47
+ * (那是**单调时钟**,值很小,属于历史遗留的笔误)。这里改用真正的 Unix 时间戳;
48
+ * `created` 字段客户端不解析,改成正确值只会有益无害。
49
+ */
50
+ export declare function contentChunk(params: {
51
+ id: string;
52
+ model: string;
53
+ content: string;
54
+ role?: string;
55
+ finishReason?: string | null;
56
+ created?: number;
57
+ }): string;
58
+ /**
59
+ * 构造工具调用分片。
60
+ *
61
+ * OpenAI 的流式工具调用是分片的(`index` + `function.arguments` 增量),
62
+ * 但**客户端普遍接受一次性发完整 arguments**,而本项目一次性解析完再发
63
+ * (见 `chat_completions` 的「先缓冲完整回复,解析 tool_calls 后再发送」)。
64
+ * 因此这里一次性发出全部,`finish_reason` 为 `"tool_calls"`。
65
+ */
66
+ export declare function toolCallsChunk(params: {
67
+ id: string;
68
+ model: string;
69
+ toolCalls: ReadonlyArray<ToolCallPayload>;
70
+ created?: number;
71
+ }): string;
72
+ /**
73
+ * 构造「首片」分片(只有 role,没有内容)。
74
+ *
75
+ * OpenAI 的流式响应第一片固定是 `{"role":"assistant"}`,客户端据此初始化消息。
76
+ */
77
+ export declare function roleChunk(params: {
78
+ id: string;
79
+ model: string;
80
+ created?: number;
81
+ }): string;
82
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/routes/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,gBAAgB;AAChB,eAAO,MAAM,IAAI,qBAAqB,CAAC;AAEvC,kBAAkB;AAClB,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC7C;AAED,kCAAkC;AAClC,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,uBAAuB,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,UAAU,CAAC;QAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC,CAAC;CACJ;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED,sBAAsB;AACtB,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAmB,GAAG,MAAM,CAE3D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAaT;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAuBT;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEzF"}
@@ -0,0 +1,77 @@
1
+ /** SSE 结束标记。 */
2
+ export const DONE = 'data: [DONE]\n\n';
3
+ /**
4
+ * 把对象序列化成一条 SSE 消息。
5
+ *
6
+ * ⚠️ 用 `JSON.stringify` 而非任何「美化」输出:客户端按行解析。
7
+ * 中文**不需要**转义(JS 的 `JSON.stringify` 默认输出原字符,
8
+ * 等价于 Python 的 `ensure_ascii=False`)。
9
+ */
10
+ export function sseData(payload) {
11
+ return `data: ${JSON.stringify(payload)}\n\n`;
12
+ }
13
+ /** 当前 Unix 时间戳(秒)。 */
14
+ export function nowSeconds(now = Date.now()) {
15
+ return Math.floor(now / 1000);
16
+ }
17
+ /**
18
+ * 构造一个内容分片。
19
+ *
20
+ * ⚠️ 与 Python 的差异:Python 用 `int(asyncio.get_event_loop().time())` 填 `created`
21
+ * (那是**单调时钟**,值很小,属于历史遗留的笔误)。这里改用真正的 Unix 时间戳;
22
+ * `created` 字段客户端不解析,改成正确值只会有益无害。
23
+ */
24
+ export function contentChunk(params) {
25
+ const { id, model, content, role, finishReason = null, created } = params;
26
+ const delta = {};
27
+ if (role !== undefined)
28
+ delta.role = role;
29
+ if (content)
30
+ delta.content = content;
31
+ return sseData({
32
+ id,
33
+ object: 'chat.completion.chunk',
34
+ created: created ?? nowSeconds(),
35
+ model,
36
+ choices: [{ index: 0, delta, finish_reason: finishReason }],
37
+ });
38
+ }
39
+ /**
40
+ * 构造工具调用分片。
41
+ *
42
+ * OpenAI 的流式工具调用是分片的(`index` + `function.arguments` 增量),
43
+ * 但**客户端普遍接受一次性发完整 arguments**,而本项目一次性解析完再发
44
+ * (见 `chat_completions` 的「先缓冲完整回复,解析 tool_calls 后再发送」)。
45
+ * 因此这里一次性发出全部,`finish_reason` 为 `"tool_calls"`。
46
+ */
47
+ export function toolCallsChunk(params) {
48
+ const { id, model, toolCalls, created } = params;
49
+ const payload = toolCalls.map((tc, index) => ({
50
+ index,
51
+ id: tc.id,
52
+ type: 'function',
53
+ function: { name: tc.function.name, arguments: tc.function.arguments },
54
+ }));
55
+ return sseData({
56
+ id,
57
+ object: 'chat.completion.chunk',
58
+ created: created ?? nowSeconds(),
59
+ model,
60
+ choices: [
61
+ {
62
+ index: 0,
63
+ delta: { tool_calls: payload },
64
+ finish_reason: 'tool_calls',
65
+ },
66
+ ],
67
+ });
68
+ }
69
+ /**
70
+ * 构造「首片」分片(只有 role,没有内容)。
71
+ *
72
+ * OpenAI 的流式响应第一片固定是 `{"role":"assistant"}`,客户端据此初始化消息。
73
+ */
74
+ export function roleChunk(params) {
75
+ return contentChunk({ ...params, content: '', role: 'assistant' });
76
+ }
77
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/routes/sse.ts"],"names":[],"mappings":"AAaA,gBAAgB;AAChB,MAAM,CAAC,MAAM,IAAI,GAAG,kBAAkB,CAAC;AAsBvC;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,OAAgB;IACtC,OAAO,SAAS,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC;AAChD,CAAC;AAED,sBAAsB;AACtB,MAAM,UAAU,UAAU,CAAC,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,MAO5B;IACC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAC1E,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAC1C,IAAI,OAAO;QAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;IAErC,OAAO,OAAO,CAAC;QACb,EAAE;QACF,MAAM,EAAE,uBAAuB;QAC/B,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE;QAChC,KAAK;QACL,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;KAC5D,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,MAK9B;IACC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAEjD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5C,KAAK;QACL,EAAE,EAAE,EAAE,CAAC,EAAE;QACT,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE;KACvE,CAAC,CAAC,CAAC;IAEJ,OAAO,OAAO,CAAC;QACb,EAAE;QACF,MAAM,EAAE,uBAAuB;QAC/B,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE;QAChC,KAAK;QACL,OAAO,EAAE;YACP;gBACE,KAAK,EAAE,CAAC;gBACR,KAAK,EAAE,EAAE,UAAU,EAAE,OAAoD,EAAE;gBAC3E,aAAa,EAAE,YAAY;aAC5B;SACF;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAuD;IAC/E,OAAO,YAAY,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type FastifyInstance } from 'fastify';
2
+ /**
3
+ * 初始化 API Key 管理器。
4
+ *
5
+ * 与 Python `main()` 的语义一致:**没有配置任何密钥时视为禁用认证**
6
+ * (即使 `API_KEY_REQUIRED=true` 也不拦 —— 否则等于把自己锁在门外)。
7
+ */
8
+ export declare function initApiKeys(): void;
9
+ /**
10
+ * 创建 Fastify 应用(不监听端口)。
11
+ *
12
+ * 中间件顺序与原实现一致:CORS → 超时 → 限流 → 认证 → 路由。
13
+ */
14
+ export declare function createApp(): Promise<FastifyInstance>;
15
+ /**
16
+ * 启动服务并阻塞直到进程收到退出信号。
17
+ *
18
+ * @returns 进程退出码:`0` 正常 / `1` 启动失败(如端口被占用)。
19
+ * ⚠️ 返回退出码而不是在函数里设 `process.exitCode` —— CLI 侧的
20
+ * `process.exitCode = await runCli()` 会覆盖它(实测踩过:报错信息是对的,
21
+ * 退出码却是 0,脚本会误判成功)。
22
+ */
23
+ export declare function startServer(): Promise<number>;
24
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAqBA,OAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,SAAS,CAAC;AAmBxD;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAgBlC;AAED;;;;GAIG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAiH1D;AA6CD;;;;;;;GAOG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CA8FnD"}