llmapi-v2 2.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 (162) hide show
  1. package/.env.example +40 -0
  2. package/Dockerfile +17 -0
  3. package/dist/config.d.ts +48 -0
  4. package/dist/config.js +98 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/converter/request.d.ts +6 -0
  7. package/dist/converter/request.js +184 -0
  8. package/dist/converter/request.js.map +1 -0
  9. package/dist/converter/response.d.ts +6 -0
  10. package/dist/converter/response.js +76 -0
  11. package/dist/converter/response.js.map +1 -0
  12. package/dist/converter/stream.d.ts +54 -0
  13. package/dist/converter/stream.js +318 -0
  14. package/dist/converter/stream.js.map +1 -0
  15. package/dist/converter/types.d.ts +239 -0
  16. package/dist/converter/types.js +6 -0
  17. package/dist/converter/types.js.map +1 -0
  18. package/dist/data/posts.d.ts +19 -0
  19. package/dist/data/posts.js +462 -0
  20. package/dist/data/posts.js.map +1 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +233 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/middleware/api-key-auth.d.ts +6 -0
  25. package/dist/middleware/api-key-auth.js +76 -0
  26. package/dist/middleware/api-key-auth.js.map +1 -0
  27. package/dist/middleware/quota-guard.d.ts +10 -0
  28. package/dist/middleware/quota-guard.js +27 -0
  29. package/dist/middleware/quota-guard.js.map +1 -0
  30. package/dist/middleware/rate-limiter.d.ts +5 -0
  31. package/dist/middleware/rate-limiter.js +50 -0
  32. package/dist/middleware/rate-limiter.js.map +1 -0
  33. package/dist/middleware/request-logger.d.ts +6 -0
  34. package/dist/middleware/request-logger.js +37 -0
  35. package/dist/middleware/request-logger.js.map +1 -0
  36. package/dist/middleware/session-auth.d.ts +19 -0
  37. package/dist/middleware/session-auth.js +99 -0
  38. package/dist/middleware/session-auth.js.map +1 -0
  39. package/dist/providers/aliyun.d.ts +13 -0
  40. package/dist/providers/aliyun.js +20 -0
  41. package/dist/providers/aliyun.js.map +1 -0
  42. package/dist/providers/base-provider.d.ts +36 -0
  43. package/dist/providers/base-provider.js +133 -0
  44. package/dist/providers/base-provider.js.map +1 -0
  45. package/dist/providers/deepseek.d.ts +11 -0
  46. package/dist/providers/deepseek.js +18 -0
  47. package/dist/providers/deepseek.js.map +1 -0
  48. package/dist/providers/registry.d.ts +18 -0
  49. package/dist/providers/registry.js +98 -0
  50. package/dist/providers/registry.js.map +1 -0
  51. package/dist/providers/types.d.ts +17 -0
  52. package/dist/providers/types.js +3 -0
  53. package/dist/providers/types.js.map +1 -0
  54. package/dist/routes/admin.d.ts +1 -0
  55. package/dist/routes/admin.js +153 -0
  56. package/dist/routes/admin.js.map +1 -0
  57. package/dist/routes/auth.d.ts +2 -0
  58. package/dist/routes/auth.js +318 -0
  59. package/dist/routes/auth.js.map +1 -0
  60. package/dist/routes/blog.d.ts +1 -0
  61. package/dist/routes/blog.js +29 -0
  62. package/dist/routes/blog.js.map +1 -0
  63. package/dist/routes/dashboard.d.ts +1 -0
  64. package/dist/routes/dashboard.js +184 -0
  65. package/dist/routes/dashboard.js.map +1 -0
  66. package/dist/routes/messages.d.ts +1 -0
  67. package/dist/routes/messages.js +309 -0
  68. package/dist/routes/messages.js.map +1 -0
  69. package/dist/routes/models.d.ts +1 -0
  70. package/dist/routes/models.js +39 -0
  71. package/dist/routes/models.js.map +1 -0
  72. package/dist/routes/payment.d.ts +1 -0
  73. package/dist/routes/payment.js +150 -0
  74. package/dist/routes/payment.js.map +1 -0
  75. package/dist/routes/sitemap.d.ts +1 -0
  76. package/dist/routes/sitemap.js +38 -0
  77. package/dist/routes/sitemap.js.map +1 -0
  78. package/dist/services/alipay.d.ts +27 -0
  79. package/dist/services/alipay.js +106 -0
  80. package/dist/services/alipay.js.map +1 -0
  81. package/dist/services/database.d.ts +4 -0
  82. package/dist/services/database.js +170 -0
  83. package/dist/services/database.js.map +1 -0
  84. package/dist/services/health-checker.d.ts +13 -0
  85. package/dist/services/health-checker.js +95 -0
  86. package/dist/services/health-checker.js.map +1 -0
  87. package/dist/services/mailer.d.ts +3 -0
  88. package/dist/services/mailer.js +91 -0
  89. package/dist/services/mailer.js.map +1 -0
  90. package/dist/services/metrics.d.ts +56 -0
  91. package/dist/services/metrics.js +94 -0
  92. package/dist/services/metrics.js.map +1 -0
  93. package/dist/services/remote-control.d.ts +20 -0
  94. package/dist/services/remote-control.js +209 -0
  95. package/dist/services/remote-control.js.map +1 -0
  96. package/dist/services/remote-ws.d.ts +5 -0
  97. package/dist/services/remote-ws.js +143 -0
  98. package/dist/services/remote-ws.js.map +1 -0
  99. package/dist/services/usage.d.ts +13 -0
  100. package/dist/services/usage.js +39 -0
  101. package/dist/services/usage.js.map +1 -0
  102. package/dist/utils/errors.d.ts +27 -0
  103. package/dist/utils/errors.js +48 -0
  104. package/dist/utils/errors.js.map +1 -0
  105. package/dist/utils/logger.d.ts +2 -0
  106. package/dist/utils/logger.js +14 -0
  107. package/dist/utils/logger.js.map +1 -0
  108. package/docker-compose.yml +19 -0
  109. package/package.json +39 -0
  110. package/public/robots.txt +8 -0
  111. package/src/config.ts +140 -0
  112. package/src/converter/request.ts +207 -0
  113. package/src/converter/response.ts +85 -0
  114. package/src/converter/stream.ts +373 -0
  115. package/src/converter/types.ts +257 -0
  116. package/src/data/posts.ts +474 -0
  117. package/src/index.ts +219 -0
  118. package/src/middleware/api-key-auth.ts +82 -0
  119. package/src/middleware/quota-guard.ts +28 -0
  120. package/src/middleware/rate-limiter.ts +61 -0
  121. package/src/middleware/request-logger.ts +36 -0
  122. package/src/middleware/session-auth.ts +91 -0
  123. package/src/providers/aliyun.ts +16 -0
  124. package/src/providers/base-provider.ts +148 -0
  125. package/src/providers/deepseek.ts +14 -0
  126. package/src/providers/registry.ts +111 -0
  127. package/src/providers/types.ts +26 -0
  128. package/src/routes/admin.ts +169 -0
  129. package/src/routes/auth.ts +369 -0
  130. package/src/routes/blog.ts +28 -0
  131. package/src/routes/dashboard.ts +208 -0
  132. package/src/routes/messages.ts +346 -0
  133. package/src/routes/models.ts +37 -0
  134. package/src/routes/payment.ts +189 -0
  135. package/src/routes/sitemap.ts +40 -0
  136. package/src/services/alipay.ts +116 -0
  137. package/src/services/database.ts +187 -0
  138. package/src/services/health-checker.ts +115 -0
  139. package/src/services/mailer.ts +90 -0
  140. package/src/services/metrics.ts +104 -0
  141. package/src/services/remote-control.ts +226 -0
  142. package/src/services/remote-ws.ts +145 -0
  143. package/src/services/usage.ts +57 -0
  144. package/src/types/express.d.ts +46 -0
  145. package/src/utils/errors.ts +44 -0
  146. package/src/utils/logger.ts +8 -0
  147. package/tsconfig.json +17 -0
  148. package/views/pages/404.ejs +14 -0
  149. package/views/pages/admin.ejs +307 -0
  150. package/views/pages/blog-post.ejs +378 -0
  151. package/views/pages/blog.ejs +148 -0
  152. package/views/pages/dashboard.ejs +441 -0
  153. package/views/pages/docs.ejs +807 -0
  154. package/views/pages/index.ejs +416 -0
  155. package/views/pages/login.ejs +170 -0
  156. package/views/pages/orders.ejs +111 -0
  157. package/views/pages/pricing.ejs +379 -0
  158. package/views/pages/register.ejs +397 -0
  159. package/views/pages/remote.ejs +334 -0
  160. package/views/pages/settings.ejs +373 -0
  161. package/views/partials/header.ejs +70 -0
  162. package/views/partials/nav.ejs +140 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/services/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Request, Response } from 'express';
2
+ /**
3
+ * Check if the last user message contains a remote-control trigger phrase.
4
+ */
5
+ export declare function detectRemoteTrigger(body: any): boolean;
6
+ /**
7
+ * Handle remote-control trigger:
8
+ * 1. Check if user already has an active bridge -> return URL
9
+ * 2. If no active bridge -> create session in DB -> return Bash tool_use for installation
10
+ * 3. If no Bash tool available -> return text-only manual instructions
11
+ */
12
+ export declare function handleRemoteTrigger(req: Request, res: Response): Promise<void>;
13
+ /**
14
+ * API endpoint: browser fetches the API key for a remote session.
15
+ * Called by the remote page to get credentials for direct API access.
16
+ */
17
+ export declare function getRemoteSessionKey(sessionId: string, userId?: number): Promise<{
18
+ apiKey: string;
19
+ baseUrl: string;
20
+ } | null>;
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectRemoteTrigger = detectRemoteTrigger;
7
+ exports.handleRemoteTrigger = handleRemoteTrigger;
8
+ exports.getRemoteSessionKey = getRemoteSessionKey;
9
+ const uuid_1 = require("uuid");
10
+ const crypto_1 = __importDefault(require("crypto"));
11
+ const database_1 = require("./database");
12
+ const logger_1 = require("../utils/logger");
13
+ const remote_ws_1 = require("./remote-ws");
14
+ const TRIGGER_PHRASES = ['开启远程控制', '远程控制', 'enable remote', 'start remote', 'remote control', 'llmapi remote'];
15
+ // i18n
16
+ const ZH = {
17
+ alreadyActive: '🔗 远程控制已在运行中!',
18
+ openLink: '在手机或其他设备上打开以下链接:',
19
+ settingUp: '🔗 正在设置远程控制...',
20
+ sessionCreated: '🔗 远程控制会话已创建!',
21
+ manualInstruct: '请在终端中运行以下命令来启动守护进程:',
22
+ expires: '会话将在 2 小时后过期。',
23
+ };
24
+ const EN = {
25
+ alreadyActive: '🔗 Remote control is already active!',
26
+ openLink: 'Open this link on your phone or another device:',
27
+ settingUp: '🔗 Setting up remote control...',
28
+ sessionCreated: '🔗 Remote control session created!',
29
+ manualInstruct: 'Run this command in your terminal to start the daemon:',
30
+ expires: 'Session expires in 2 hours.',
31
+ };
32
+ function getLastUserText(body) {
33
+ if (!body?.messages?.length)
34
+ return '';
35
+ for (let i = body.messages.length - 1; i >= 0; i--) {
36
+ const msg = body.messages[i];
37
+ if (msg.role !== 'user')
38
+ continue;
39
+ if (typeof msg.content === 'string')
40
+ return msg.content;
41
+ if (Array.isArray(msg.content))
42
+ return msg.content.filter((b) => b.type === 'text').map((b) => b.text).join('');
43
+ }
44
+ return '';
45
+ }
46
+ /**
47
+ * Check if the last user message contains a remote-control trigger phrase.
48
+ */
49
+ function detectRemoteTrigger(body) {
50
+ if (!body || !Array.isArray(body.messages) || body.messages.length === 0)
51
+ return false;
52
+ let lastUserMsg = null;
53
+ for (let i = body.messages.length - 1; i >= 0; i--) {
54
+ if (body.messages[i].role === 'user') {
55
+ lastUserMsg = body.messages[i];
56
+ break;
57
+ }
58
+ }
59
+ if (!lastUserMsg)
60
+ return false;
61
+ let text = '';
62
+ if (typeof lastUserMsg.content === 'string') {
63
+ text = lastUserMsg.content;
64
+ }
65
+ else if (Array.isArray(lastUserMsg.content)) {
66
+ for (const block of lastUserMsg.content) {
67
+ if (block.type === 'text' && typeof block.text === 'string')
68
+ text += block.text + ' ';
69
+ }
70
+ }
71
+ text = text.trim().toLowerCase();
72
+ if (!text)
73
+ return false;
74
+ return TRIGGER_PHRASES.some(phrase => text.includes(phrase.toLowerCase()));
75
+ }
76
+ /**
77
+ * Handle remote-control trigger:
78
+ * 1. Check if user already has an active bridge -> return URL
79
+ * 2. If no active bridge -> create session in DB -> return Bash tool_use for installation
80
+ * 3. If no Bash tool available -> return text-only manual instructions
81
+ */
82
+ async function handleRemoteTrigger(req, res) {
83
+ const body = req.body;
84
+ const displayModel = body.model || 'claude-sonnet-4-6';
85
+ const userId = req.userId;
86
+ // Detect user language from the trigger message
87
+ const lastMsg = getLastUserText(body);
88
+ const isChinese = /[\u4e00-\u9fff]/.test(lastMsg);
89
+ const t = isChinese ? ZH : EN;
90
+ // Check if user already has an active bridge
91
+ const existingBridge = (0, remote_ws_1.hasActiveBridge)(userId);
92
+ if (existingBridge) {
93
+ const remoteUrl = `https://llmapi.pro/remote?s=${existingBridge.sessionId}`;
94
+ sendSSE(res, displayModel, [
95
+ { type: 'text', text: `${t.alreadyActive}\n\n${t.openLink}\n${remoteUrl}\n` },
96
+ ]);
97
+ logger_1.logger.info({ sessionId: existingBridge.sessionId, userId }, 'Remote control: reusing existing bridge');
98
+ return;
99
+ }
100
+ // Create new session
101
+ const sessionId = (0, uuid_1.v4)();
102
+ try {
103
+ const pool = (0, database_1.getPool)();
104
+ await pool.execute("INSERT INTO remote_sessions (session_id, user_id, status) VALUES (?, ?, 'pending')", [sessionId, userId]);
105
+ }
106
+ catch (err) {
107
+ logger_1.logger.error({ err }, 'Failed to create remote session');
108
+ res.status(500).json({ type: 'error', error: { type: 'api_error', message: 'Failed to create remote session' } });
109
+ return;
110
+ }
111
+ const remoteUrl = `https://llmapi.pro/remote?s=${sessionId}`;
112
+ // Check if Bash tool is available in the request
113
+ const hasBashTool = Array.isArray(body.tools) && body.tools.some((t) => t.name === 'bash' || t.name === 'Bash');
114
+ const bashCommand = `echo "╔══════════════════════════════════════════════════════════╗" && \\
115
+ echo "║ LLM API Remote Control ║" && \\
116
+ echo "║ ║" && \\
117
+ echo "║ This will: ║" && \\
118
+ echo "║ 1. Install llmapi-remote globally (one-time) ║" && \\
119
+ echo "║ 2. Start a background daemon on this machine ║" && \\
120
+ echo "║ 3. The daemon connects to llmapi.pro via WebSocket ║" && \\
121
+ echo "║ 4. You can then control Claude Code from your phone ║" && \\
122
+ echo "║ ║" && \\
123
+ echo "║ Data stored in: ~/.llmapi-remote/ ║" && \\
124
+ echo "║ To stop: llmapi-remote stop ║" && \\
125
+ echo "║ To remove: npm uninstall -g llmapi-remote ║" && \\
126
+ echo "║ rm -rf ~/.llmapi-remote ║" && \\
127
+ echo "╚══════════════════════════════════════════════════════════╝" && \\
128
+ npm install -g llmapi-remote@latest && \\
129
+ llmapi-remote start --session ${sessionId} --ws wss://llmapi.pro/remote-ws --key $ANTHROPIC_API_KEY`;
130
+ if (hasBashTool) {
131
+ sendSSE(res, displayModel, [
132
+ { type: 'text', text: `${t.settingUp}\n\n${t.openLink}\n${remoteUrl}\n` },
133
+ { type: 'tool_use', name: 'Bash', command: bashCommand },
134
+ ]);
135
+ }
136
+ else {
137
+ sendSSE(res, displayModel, [
138
+ { type: 'text', text: `${t.sessionCreated}\n\n${t.manualInstruct}\n\n\`\`\`bash\nnpm install -g llmapi-remote@latest && \\\nllmapi-remote start --session ${sessionId} --ws wss://llmapi.pro/remote-ws --key $ANTHROPIC_API_KEY\n\`\`\`\n\n${t.openLink}\n${remoteUrl}\n\n${t.expires}\n` },
139
+ ]);
140
+ }
141
+ logger_1.logger.info({ sessionId, userId, hasBashTool }, 'Remote control session created');
142
+ }
143
+ /**
144
+ * API endpoint: browser fetches the API key for a remote session.
145
+ * Called by the remote page to get credentials for direct API access.
146
+ */
147
+ async function getRemoteSessionKey(sessionId, userId) {
148
+ const pool = (0, database_1.getPool)();
149
+ const [rows] = await pool.execute("SELECT user_id, encrypted_key FROM remote_sessions WHERE session_id = ? AND status = 'active'", [sessionId]);
150
+ const session = rows[0];
151
+ if (!session || !session.encrypted_key)
152
+ return null;
153
+ const apiKey = decryptApiKey(session.encrypted_key, sessionId);
154
+ return { apiKey, baseUrl: process.env.SITE_URL || 'https://llmapi.pro' };
155
+ }
156
+ // Simple encryption: AES-256-CBC with session ID as key material
157
+ function encryptApiKey(apiKey, sessionId) {
158
+ const key = crypto_1.default.createHash('sha256').update(sessionId).digest();
159
+ const iv = crypto_1.default.randomBytes(16);
160
+ const cipher = crypto_1.default.createCipheriv('aes-256-cbc', key, iv);
161
+ let encrypted = cipher.update(apiKey, 'utf8', 'hex');
162
+ encrypted += cipher.final('hex');
163
+ return iv.toString('hex') + ':' + encrypted;
164
+ }
165
+ function decryptApiKey(encrypted, sessionId) {
166
+ const [ivHex, data] = encrypted.split(':');
167
+ const key = crypto_1.default.createHash('sha256').update(sessionId).digest();
168
+ const iv = Buffer.from(ivHex, 'hex');
169
+ const decipher = crypto_1.default.createDecipheriv('aes-256-cbc', key, iv);
170
+ let decrypted = decipher.update(data, 'hex', 'utf8');
171
+ decrypted += decipher.final('utf8');
172
+ return decrypted;
173
+ }
174
+ function sendSSE(res, displayModel, blocks) {
175
+ res.setHeader('Content-Type', 'text/event-stream');
176
+ res.setHeader('Cache-Control', 'no-cache');
177
+ res.setHeader('Connection', 'keep-alive');
178
+ res.flushHeaders();
179
+ const msgId = `msg_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 24)}`;
180
+ const send = (event, data) => {
181
+ res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
182
+ };
183
+ send('message_start', {
184
+ type: 'message_start',
185
+ message: { id: msgId, type: 'message', role: 'assistant', model: displayModel, content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } },
186
+ });
187
+ send('ping', { type: 'ping' });
188
+ let index = 0;
189
+ for (const block of blocks) {
190
+ if (block.type === 'text') {
191
+ send('content_block_start', { type: 'content_block_start', index, content_block: { type: 'text', text: '' } });
192
+ send('content_block_delta', { type: 'content_block_delta', index, delta: { type: 'text_delta', text: block.text } });
193
+ send('content_block_stop', { type: 'content_block_stop', index });
194
+ }
195
+ else if (block.type === 'tool_use') {
196
+ const toolId = `toolu_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 24)}`;
197
+ const input = { command: block.command, restart: false };
198
+ send('content_block_start', { type: 'content_block_start', index, content_block: { type: 'tool_use', id: toolId, name: block.name, input: {} } });
199
+ send('content_block_delta', { type: 'content_block_delta', index, delta: { type: 'input_json_delta', partial_json: JSON.stringify(input) } });
200
+ send('content_block_stop', { type: 'content_block_stop', index });
201
+ }
202
+ index++;
203
+ }
204
+ const stopReason = blocks.some(b => b.type === 'tool_use') ? 'tool_use' : 'end_turn';
205
+ send('message_delta', { type: 'message_delta', delta: { stop_reason: stopReason, stop_sequence: null }, usage: { output_tokens: 20 } });
206
+ send('message_stop', { type: 'message_stop' });
207
+ res.end();
208
+ }
209
+ //# sourceMappingURL=remote-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-control.js","sourceRoot":"","sources":["../../src/services/remote-control.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Server } from 'http';
2
+ export declare function initRemoteWs(server: Server): void;
3
+ export declare function hasActiveBridge(userId: number): {
4
+ sessionId: string;
5
+ } | null;
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initRemoteWs = initRemoteWs;
7
+ exports.hasActiveBridge = hasActiveBridge;
8
+ const ws_1 = require("ws");
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const database_1 = require("./database");
11
+ const logger_1 = require("../utils/logger");
12
+ const sessions = new Map();
13
+ function initRemoteWs(server) {
14
+ const wss = new ws_1.WebSocketServer({ server, path: '/remote-ws' });
15
+ wss.on('connection', async (ws, req) => {
16
+ const url = new URL(req.url || '', 'http://localhost');
17
+ const sessionId = url.searchParams.get('session');
18
+ const role = url.searchParams.get('role'); // 'bridge' or 'browser'
19
+ const token = url.searchParams.get('token');
20
+ if (!sessionId || !role) {
21
+ ws.close(4001, 'Missing params');
22
+ return;
23
+ }
24
+ // Authenticate
25
+ let userId;
26
+ try {
27
+ if (role === 'bridge') {
28
+ // Bridge sends API key as token
29
+ if (!token)
30
+ throw new Error('No token');
31
+ const keyPrefix = token.substring(0, 12);
32
+ const keyHash = crypto_1.default.createHash('sha256').update(token).digest('hex');
33
+ const pool = (0, database_1.getPool)();
34
+ const [keys] = await pool.execute('SELECT user_id FROM api_keys WHERE key_prefix = ? AND key_hash = ? AND status = ?', [keyPrefix, keyHash, 'active']);
35
+ const matched = keys[0];
36
+ if (!matched)
37
+ throw new Error('Invalid key');
38
+ userId = matched.user_id;
39
+ }
40
+ else {
41
+ // Browser: session ID itself is the auth (knowing the UUID = access)
42
+ const pool = (0, database_1.getPool)();
43
+ const [rows] = await pool.execute("SELECT user_id FROM remote_sessions WHERE session_id = ? AND status = 'active'", [sessionId]);
44
+ const row = rows[0];
45
+ if (!row)
46
+ throw new Error('Session not found');
47
+ userId = row.user_id;
48
+ }
49
+ }
50
+ catch (err) {
51
+ logger_1.logger.warn({ err: err.message, role, sessionId }, 'Remote WS auth failed');
52
+ ws.close(4003, 'Auth failed');
53
+ return;
54
+ }
55
+ // Get or create in-memory session
56
+ let session = sessions.get(sessionId);
57
+ if (!session) {
58
+ session = { bridge: null, browsers: new Set(), userId };
59
+ sessions.set(sessionId, session);
60
+ }
61
+ if (role === 'bridge') {
62
+ session.bridge = ws;
63
+ const pool = (0, database_1.getPool)();
64
+ await pool.execute("UPDATE remote_sessions SET status = 'active' WHERE session_id = ?", [sessionId]);
65
+ // Notify browsers
66
+ session.browsers.forEach(b => {
67
+ if (b.readyState === ws_1.WebSocket.OPEN)
68
+ b.send(JSON.stringify({ type: 'bridge_connected' }));
69
+ });
70
+ logger_1.logger.info({ sessionId, userId }, 'Remote bridge connected');
71
+ }
72
+ else {
73
+ session.browsers.add(ws);
74
+ // Notify bridge
75
+ if (session.bridge?.readyState === ws_1.WebSocket.OPEN) {
76
+ session.bridge.send(JSON.stringify({ type: 'browser_connected' }));
77
+ }
78
+ // Tell browser if bridge is already connected
79
+ if (session.bridge?.readyState === ws_1.WebSocket.OPEN) {
80
+ ws.send(JSON.stringify({ type: 'bridge_connected' }));
81
+ }
82
+ logger_1.logger.info({ sessionId }, 'Remote browser connected');
83
+ }
84
+ // Relay messages
85
+ ws.on('message', (raw) => {
86
+ const msg = raw.toString();
87
+ if (role === 'bridge') {
88
+ // Forward to all browsers
89
+ session.browsers.forEach(b => { if (b.readyState === ws_1.WebSocket.OPEN)
90
+ b.send(msg); });
91
+ }
92
+ else {
93
+ // Forward to bridge
94
+ if (session.bridge?.readyState === ws_1.WebSocket.OPEN)
95
+ session.bridge.send(msg);
96
+ }
97
+ });
98
+ ws.on('close', () => {
99
+ if (role === 'bridge') {
100
+ session.bridge = null;
101
+ session.browsers.forEach(b => {
102
+ if (b.readyState === ws_1.WebSocket.OPEN)
103
+ b.send(JSON.stringify({ type: 'bridge_disconnected' }));
104
+ });
105
+ logger_1.logger.info({ sessionId }, 'Remote bridge disconnected');
106
+ }
107
+ else {
108
+ session.browsers.delete(ws);
109
+ if (session.bridge?.readyState === ws_1.WebSocket.OPEN) {
110
+ session.bridge.send(JSON.stringify({ type: 'browser_disconnected' }));
111
+ }
112
+ }
113
+ // Cleanup empty sessions
114
+ if (!session.bridge && session.browsers.size === 0) {
115
+ sessions.delete(sessionId);
116
+ }
117
+ });
118
+ });
119
+ // Heartbeat
120
+ setInterval(() => {
121
+ wss.clients.forEach(ws => { if (ws.readyState === ws_1.WebSocket.OPEN)
122
+ ws.ping(); });
123
+ }, 30000);
124
+ // Cleanup old DB sessions every 5 min
125
+ setInterval(async () => {
126
+ try {
127
+ const pool = (0, database_1.getPool)();
128
+ await pool.execute("UPDATE remote_sessions SET status = 'closed', closed_at = NOW() WHERE status = 'active' AND created_at < NOW() - INTERVAL '2 hours'");
129
+ }
130
+ catch { }
131
+ }, 5 * 60000);
132
+ logger_1.logger.info('Remote WebSocket server initialized on /remote-ws');
133
+ }
134
+ // Check if a user has an active bridge connected
135
+ function hasActiveBridge(userId) {
136
+ for (const [sessionId, session] of sessions) {
137
+ if (session.userId === userId && session.bridge?.readyState === ws_1.WebSocket.OPEN) {
138
+ return { sessionId };
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+ //# sourceMappingURL=remote-ws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-ws.js","sourceRoot":"","sources":["../../src/services/remote-ws.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface UsageDetails {
2
+ inputTokens: number;
3
+ outputTokens: number;
4
+ thinkingTokens: number;
5
+ ttftMs: number;
6
+ tokensPerSec: number;
7
+ durationMs: number;
8
+ }
9
+ /**
10
+ * Record API usage and update subscription quota.
11
+ * Called after every successful request (async, non-blocking).
12
+ */
13
+ export declare function recordUsage(userId: number, apiKeyId: number | null, displayModel: string, providerName: string, backendModel: string, details: UsageDetails): Promise<void>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recordUsage = recordUsage;
4
+ const database_1 = require("./database");
5
+ const logger_1 = require("../utils/logger");
6
+ /**
7
+ * Record API usage and update subscription quota.
8
+ * Called after every successful request (async, non-blocking).
9
+ */
10
+ async function recordUsage(userId, apiKeyId, displayModel, providerName, backendModel, details) {
11
+ const pool = (0, database_1.getPool)();
12
+ try {
13
+ // Insert usage log
14
+ await pool.execute(`
15
+ INSERT INTO usage_logs (
16
+ user_id, api_key_id, model, provider_name,
17
+ input_tokens, output_tokens, thinking_tokens,
18
+ ttft_ms, tokens_per_sec, duration_ms, status
19
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'success')
20
+ `, [
21
+ userId, apiKeyId, displayModel, providerName,
22
+ details.inputTokens, details.outputTokens, details.thinkingTokens,
23
+ details.ttftMs, details.tokensPerSec, details.durationMs,
24
+ ]);
25
+ // Update subscription quota (input + output tokens count toward limit)
26
+ const totalTokens = details.inputTokens + details.outputTokens;
27
+ if (totalTokens > 0) {
28
+ await pool.execute('UPDATE subscriptions SET tokens_used = tokens_used + ? WHERE id = (SELECT id FROM subscriptions WHERE user_id = ? ORDER BY period_start DESC LIMIT 1)', [totalTokens, userId]);
29
+ }
30
+ // Update API key last_used_at
31
+ if (apiKeyId) {
32
+ await pool.execute('UPDATE api_keys SET last_used_at = NOW() WHERE id = ?', [apiKeyId]);
33
+ }
34
+ }
35
+ catch (err) {
36
+ logger_1.logger.error({ err, userId }, 'Failed to record usage');
37
+ }
38
+ }
39
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/services/usage.ts"],"names":[],"mappings":";;AAgBA,kCAwCC;AAxDD,yCAAqC;AACrC,4CAAyC;AAWzC;;;GAGG;AACI,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,QAAuB,EACvB,YAAoB,EACpB,YAAoB,EACpB,YAAoB,EACpB,OAAqB;IAErB,MAAM,IAAI,GAAG,IAAA,kBAAO,GAAE,CAAC;IAEvB,IAAI,CAAC;QACH,mBAAmB;QACnB,MAAM,IAAI,CAAC,OAAO,CAAC;;;;;;KAMlB,EAAE;YACD,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY;YAC5C,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,cAAc;YACjE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,UAAU;SACzD,CAAC,CAAC;QAEH,uEAAuE;QACvE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;QAC/D,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,OAAO,CAChB,uJAAuJ,EACvJ,CAAC,WAAW,EAAE,MAAM,CAAC,CACtB,CAAC;QACJ,CAAC;QAED,8BAA8B;QAC9B,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,OAAO,CAAC,uDAAuD,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,eAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,wBAAwB,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Error class that produces Anthropic API error format responses.
3
+ */
4
+ export declare class AnthropicError extends Error {
5
+ statusCode: number;
6
+ errorType: string;
7
+ constructor(statusCode: number, errorType: string, message: string);
8
+ toJSON(): {
9
+ type: string;
10
+ error: {
11
+ type: string;
12
+ message: string;
13
+ };
14
+ };
15
+ }
16
+ export declare class AuthenticationError extends AnthropicError {
17
+ constructor(message?: string);
18
+ }
19
+ export declare class PermissionError extends AnthropicError {
20
+ constructor(message?: string);
21
+ }
22
+ export declare class RateLimitError extends AnthropicError {
23
+ constructor(message?: string);
24
+ }
25
+ export declare class OverloadedError extends AnthropicError {
26
+ constructor(message?: string);
27
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OverloadedError = exports.RateLimitError = exports.PermissionError = exports.AuthenticationError = exports.AnthropicError = void 0;
4
+ /**
5
+ * Error class that produces Anthropic API error format responses.
6
+ */
7
+ class AnthropicError extends Error {
8
+ statusCode;
9
+ errorType;
10
+ constructor(statusCode, errorType, message) {
11
+ super(message);
12
+ this.statusCode = statusCode;
13
+ this.errorType = errorType;
14
+ this.name = 'AnthropicError';
15
+ }
16
+ toJSON() {
17
+ return {
18
+ type: 'error',
19
+ error: { type: this.errorType, message: this.message },
20
+ };
21
+ }
22
+ }
23
+ exports.AnthropicError = AnthropicError;
24
+ class AuthenticationError extends AnthropicError {
25
+ constructor(message = 'Invalid API key.') {
26
+ super(401, 'authentication_error', message);
27
+ }
28
+ }
29
+ exports.AuthenticationError = AuthenticationError;
30
+ class PermissionError extends AnthropicError {
31
+ constructor(message = 'Permission denied.') {
32
+ super(403, 'permission_error', message);
33
+ }
34
+ }
35
+ exports.PermissionError = PermissionError;
36
+ class RateLimitError extends AnthropicError {
37
+ constructor(message = 'Rate limit exceeded.') {
38
+ super(429, 'rate_limit_error', message);
39
+ }
40
+ }
41
+ exports.RateLimitError = RateLimitError;
42
+ class OverloadedError extends AnthropicError {
43
+ constructor(message = 'All backend providers are currently unavailable.') {
44
+ super(503, 'overloaded_error', message);
45
+ }
46
+ }
47
+ exports.OverloadedError = OverloadedError;
48
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,MAAa,cAAe,SAAQ,KAAK;IAE9B;IACA;IAFT,YACS,UAAkB,EAClB,SAAiB,EACxB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJR,eAAU,GAAV,UAAU,CAAQ;QAClB,cAAS,GAAT,SAAS,CAAQ;QAIxB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;IAED,MAAM;QACJ,OAAO;YACL,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;SACvD,CAAC;IACJ,CAAC;CACF;AAhBD,wCAgBC;AAED,MAAa,mBAAoB,SAAQ,cAAc;IACrD,YAAY,OAAO,GAAG,kBAAkB;QACtC,KAAK,CAAC,GAAG,EAAE,sBAAsB,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;CACF;AAJD,kDAIC;AAED,MAAa,eAAgB,SAAQ,cAAc;IACjD,YAAY,OAAO,GAAG,oBAAoB;QACxC,KAAK,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,0CAIC;AAED,MAAa,cAAe,SAAQ,cAAc;IAChD,YAAY,OAAO,GAAG,sBAAsB;QAC1C,KAAK,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,wCAIC;AAED,MAAa,eAAgB,SAAQ,cAAc;IACjD,YAAY,OAAO,GAAG,kDAAkD;QACtE,KAAK,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,0CAIC"}
@@ -0,0 +1,2 @@
1
+ import pino from 'pino';
2
+ export declare const logger: pino.Logger<never, boolean>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = void 0;
7
+ const pino_1 = __importDefault(require("pino"));
8
+ exports.logger = (0, pino_1.default)({
9
+ level: process.env.LOG_LEVEL || 'info',
10
+ transport: process.env.NODE_ENV === 'development'
11
+ ? { target: 'pino-pretty', options: { colorize: true } }
12
+ : undefined,
13
+ });
14
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAEX,QAAA,MAAM,GAAG,IAAA,cAAI,EAAC;IACzB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM;IACtC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa;QAC/C,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;QACxD,CAAC,CAAC,SAAS;CACd,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ services:
2
+ app:
3
+ build: .
4
+ container_name: llmapi
5
+ ports:
6
+ - "3103:3000"
7
+ env_file: .env
8
+ restart: unless-stopped
9
+ external_links:
10
+ - postgres:postgres
11
+ networks:
12
+ - default
13
+ - shared
14
+
15
+ networks:
16
+ default:
17
+ shared:
18
+ external: true
19
+ name: bridge
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "llmapi-v2",
3
+ "version": "2.1.0",
4
+ "description": "Claude Code API Relay - Anthropic API compatible proxy for Chinese LLM providers",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "dev": "tsx watch src/index.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "alipay-sdk": "^4.14.0",
14
+ "bcryptjs": "^2.4.3",
15
+ "cors": "^2.8.5",
16
+ "ejs": "^3.1.10",
17
+ "express": "^4.21.0",
18
+ "helmet": "^7.1.0",
19
+ "jsonwebtoken": "^9.0.2",
20
+ "pg": "^8.20.0",
21
+ "pino": "^9.6.0",
22
+ "pino-pretty": "^13.0.0",
23
+ "qrcode": "^1.5.4",
24
+ "uuid": "^10.0.0",
25
+ "ws": "^8.20.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/bcryptjs": "^2.4.6",
29
+ "@types/cors": "^2.8.17",
30
+ "@types/express": "^4.17.21",
31
+ "@types/jsonwebtoken": "^9.0.7",
32
+ "@types/node": "^20.14.0",
33
+ "@types/pg": "^8.20.0",
34
+ "@types/uuid": "^10.0.0",
35
+ "@types/ws": "^8.18.1",
36
+ "tsx": "^4.19.0",
37
+ "typescript": "^5.6.0"
38
+ }
39
+ }