@lightharu/krouter 1.8.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 (61) hide show
  1. package/LICENSE +679 -0
  2. package/README.md +238 -0
  3. package/dist-web/assets/index-CM4-0adf.css +1 -0
  4. package/dist-web/assets/index-DCslvfUR.js +139 -0
  5. package/dist-web/favicon.svg +9 -0
  6. package/dist-web/icon.svg +9 -0
  7. package/dist-web/index.html +19 -0
  8. package/out-server/main/kiroAuthSync.js +249 -0
  9. package/out-server/main/kproxy/certManager.js +262 -0
  10. package/out-server/main/kproxy/index.js +254 -0
  11. package/out-server/main/kproxy/mitmProxy.js +475 -0
  12. package/out-server/main/kproxy/types.js +23 -0
  13. package/out-server/main/proxy/accountPool.js +543 -0
  14. package/out-server/main/proxy/clientConfig.js +596 -0
  15. package/out-server/main/proxy/index.js +25 -0
  16. package/out-server/main/proxy/kiroApi.js +1996 -0
  17. package/out-server/main/proxy/logger.js +407 -0
  18. package/out-server/main/proxy/modelCatalog.js +75 -0
  19. package/out-server/main/proxy/promptCacheTracker.js +301 -0
  20. package/out-server/main/proxy/proxyServer.js +3543 -0
  21. package/out-server/main/proxy/selfSignedCert.js +179 -0
  22. package/out-server/main/proxy/systemProxy.js +250 -0
  23. package/out-server/main/proxy/tokenCounter.js +164 -0
  24. package/out-server/main/proxy/toolNameRegistry.js +57 -0
  25. package/out-server/main/proxy/translator.js +1084 -0
  26. package/out-server/main/proxy/types.js +3 -0
  27. package/out-server/main/registration/browser-identity.js +184 -0
  28. package/out-server/main/registration/chainProxy.js +349 -0
  29. package/out-server/main/registration/config.js +58 -0
  30. package/out-server/main/registration/email-service.js +801 -0
  31. package/out-server/main/registration/fingerprint.js +352 -0
  32. package/out-server/main/registration/http-utils.js +148 -0
  33. package/out-server/main/registration/jwe.js +74 -0
  34. package/out-server/main/registration/names.js +142 -0
  35. package/out-server/main/registration/proton-mail-window.js +339 -0
  36. package/out-server/main/registration/registrar.js +1715 -0
  37. package/out-server/main/registration/tlsClientPool.js +70 -0
  38. package/out-server/main/registration/xxtea.js +161 -0
  39. package/out-server/main/runtimePaths.js +19 -0
  40. package/out-server/main/utils/redact.js +95 -0
  41. package/out-server/server/index.js +1272 -0
  42. package/out-server/server/services/accountExtras.js +105 -0
  43. package/out-server/server/services/accountProfileHydration.js +95 -0
  44. package/out-server/server/services/authFlows.js +509 -0
  45. package/out-server/server/services/dashboardTunnel.js +315 -0
  46. package/out-server/server/services/diagnostics.js +326 -0
  47. package/out-server/server/services/kiroAccounts.js +431 -0
  48. package/out-server/server/services/kiroSettings.js +260 -0
  49. package/out-server/server/services/kproxyRuntime.js +264 -0
  50. package/out-server/server/services/localKiroCredentials.js +320 -0
  51. package/out-server/server/services/machineIdRuntime.js +327 -0
  52. package/out-server/server/services/protonBrowserRuntime.js +724 -0
  53. package/out-server/server/services/proxyRuntime.js +523 -0
  54. package/out-server/server/services/registrationRuntime.js +106 -0
  55. package/out-server/server/store.js +266 -0
  56. package/package.json +113 -0
  57. package/resources/tls-client-xgo-1.14.0-windows-amd64.dll +0 -0
  58. package/scripts/kiro-manager-cli.cjs +3 -0
  59. package/scripts/krouter-cli.cjs +509 -0
  60. package/src/renderer/src/assets/krouter-logo.svg +11 -0
  61. package/src/renderer/src/assets/krouter-mark.svg +9 -0
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ensureProxySelfSignedCert = ensureProxySelfSignedCert;
37
+ // 反代 HTTPS 自签证书生成器(不依赖外部 CA,单证书自签)
38
+ // 用于让用户一键启用反代 HTTPS,证书包含 SubjectAltName 覆盖常见绑定地址
39
+ const forge = __importStar(require("node-forge"));
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const crypto = __importStar(require("crypto"));
43
+ /**
44
+ * 在指定目录生成(或加载)反代用的自签证书
45
+ * - 文件位置:dataPath/proxy-tls/proxy.crt + proxy.key
46
+ * - 过期前 30 天自动续期
47
+ * - 默认 SAN 包含 localhost / 127.0.0.1 / ::1,以及调用方指定的 hostnames
48
+ *
49
+ * @param dataPath 应用数据目录(一般是 app.getPath('userData'))
50
+ * @param hostnames 额外的 DNS / IP 名称(如配置的 proxy host)
51
+ * @param forceRegen 强制重新生成
52
+ */
53
+ function ensureProxySelfSignedCert(dataPath, hostnames = [], forceRegen = false) {
54
+ const certDir = path.join(dataPath, 'proxy-tls');
55
+ const certPath = path.join(certDir, 'proxy.crt');
56
+ const keyPath = path.join(certDir, 'proxy.key');
57
+ if (!fs.existsSync(certDir))
58
+ fs.mkdirSync(certDir, { recursive: true });
59
+ // 复用已有证书(未过期且 SAN 覆盖了请求的 hostnames)
60
+ if (!forceRegen && fs.existsSync(certPath) && fs.existsSync(keyPath)) {
61
+ try {
62
+ const certPem = fs.readFileSync(certPath, 'utf8');
63
+ const keyPem = fs.readFileSync(keyPath, 'utf8');
64
+ const cert = forge.pki.certificateFromPem(certPem);
65
+ const now = new Date();
66
+ const renewBefore = new Date(cert.validity.notAfter.getTime() - 30 * 86400_000);
67
+ // SAN 覆盖检查
68
+ const existingAlt = extractAltNames(cert);
69
+ const requested = normalizeAltNames(hostnames);
70
+ const missing = requested.filter(n => !existingAlt.includes(n));
71
+ if (now < renewBefore && missing.length === 0) {
72
+ return {
73
+ cert: certPem,
74
+ key: keyPem,
75
+ fingerprint: computeFingerprint(certPem),
76
+ notBefore: cert.validity.notBefore.getTime(),
77
+ notAfter: cert.validity.notAfter.getTime(),
78
+ subject: cert.subject.getField('CN')?.value || 'localhost',
79
+ altNames: existingAlt
80
+ };
81
+ }
82
+ }
83
+ catch (err) {
84
+ console.warn('[ProxyTLS] Failed to load existing cert, regenerating:', err.message);
85
+ }
86
+ }
87
+ // 生成新证书
88
+ const keys = forge.pki.rsa.generateKeyPair(2048);
89
+ const cert = forge.pki.createCertificate();
90
+ cert.publicKey = keys.publicKey;
91
+ cert.serialNumber = generateSerialNumber();
92
+ cert.validity.notBefore = new Date();
93
+ cert.validity.notAfter = new Date();
94
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 2); // 2 年有效期
95
+ const subject = [
96
+ { name: 'commonName', value: 'Kiro Reverse Proxy' },
97
+ { name: 'organizationName', value: 'Krouter' },
98
+ { name: 'countryName', value: 'CN' }
99
+ ];
100
+ cert.setSubject(subject);
101
+ cert.setIssuer(subject);
102
+ // 构造 SubjectAltName:DNS / IP 都列入,覆盖常见客户端访问场景
103
+ const altNames = normalizeAltNames(hostnames);
104
+ // node-forge 的 GeneralName 在 type defs 中不公开,用结构化对象直接传入
105
+ const altList = [];
106
+ for (const name of altNames) {
107
+ if (isIPv4(name) || isIPv6(name)) {
108
+ altList.push({ type: 7, ip: name }); // type 7 = iPAddress
109
+ }
110
+ else {
111
+ altList.push({ type: 2, value: name }); // type 2 = dNSName
112
+ }
113
+ }
114
+ cert.setExtensions([
115
+ { name: 'basicConstraints', cA: false, critical: true },
116
+ {
117
+ name: 'keyUsage',
118
+ digitalSignature: true,
119
+ keyEncipherment: true,
120
+ critical: true
121
+ },
122
+ { name: 'extKeyUsage', serverAuth: true, clientAuth: true },
123
+ { name: 'subjectAltName', altNames: altList }
124
+ ]);
125
+ cert.sign(keys.privateKey, forge.md.sha256.create());
126
+ const certPem = forge.pki.certificateToPem(cert);
127
+ const keyPem = forge.pki.privateKeyToPem(keys.privateKey);
128
+ fs.writeFileSync(certPath, certPem);
129
+ fs.writeFileSync(keyPath, keyPem, { mode: 0o600 }); // 私钥仅 owner 可读
130
+ console.log(`[ProxyTLS] Generated self-signed cert: ${certPath} (SAN=${altNames.join(',')})`);
131
+ return {
132
+ cert: certPem,
133
+ key: keyPem,
134
+ fingerprint: computeFingerprint(certPem),
135
+ notBefore: cert.validity.notBefore.getTime(),
136
+ notAfter: cert.validity.notAfter.getTime(),
137
+ subject: 'Kiro Reverse Proxy',
138
+ altNames
139
+ };
140
+ }
141
+ /**
142
+ * 标准化 SAN 列表:去重,确保始终包含本地访问地址
143
+ */
144
+ function normalizeAltNames(extras) {
145
+ const set = new Set(['localhost', '127.0.0.1', '::1']);
146
+ for (const name of extras) {
147
+ const trimmed = name?.trim();
148
+ if (!trimmed)
149
+ continue;
150
+ // 0.0.0.0 不是有效 cert SAN(客户端永远不会真访问 0.0.0.0),跳过
151
+ if (trimmed === '0.0.0.0' || trimmed === '::')
152
+ continue;
153
+ set.add(trimmed);
154
+ }
155
+ return Array.from(set);
156
+ }
157
+ function extractAltNames(cert) {
158
+ const ext = cert.getExtension('subjectAltName');
159
+ if (!ext?.altNames)
160
+ return [];
161
+ return ext.altNames.map(a => a.ip || a.value || '').filter(Boolean);
162
+ }
163
+ function isIPv4(host) {
164
+ return /^(\d{1,3}\.){3}\d{1,3}$/.test(host);
165
+ }
166
+ function isIPv6(host) {
167
+ return host.includes(':') && !host.includes('.');
168
+ }
169
+ function generateSerialNumber() {
170
+ return crypto.randomBytes(16).toString('hex');
171
+ }
172
+ function computeFingerprint(certPem) {
173
+ // SHA-256 指纹:用 PEM body 的 DER 解码后哈希
174
+ const m = certPem.match(/-----BEGIN CERTIFICATE-----([\s\S]*?)-----END CERTIFICATE-----/);
175
+ if (!m)
176
+ return '';
177
+ const der = Buffer.from(m[1].replace(/\s/g, ''), 'base64');
178
+ return crypto.createHash('sha256').update(der).digest('hex').match(/.{2}/g).join(':').toUpperCase();
179
+ }
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ // 系统代理检测(Windows 注册表 / macOS scutil)+ 安全 ProxyAgent 工厂
3
+ // 含 SOCKS5/SOCKS4 代理支持(通过 socks 包 + undici Agent.connect 钩子)
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.getSystemProxy = getSystemProxy;
39
+ exports.safeCreateProxyAgent = safeCreateProxyAgent;
40
+ const undici_1 = require("undici");
41
+ const tls = __importStar(require("tls"));
42
+ let _cachedSystemProxy = null;
43
+ let _systemProxyCacheTime = 0;
44
+ const SYSTEM_PROXY_CACHE_TTL = 30_000; // 30秒缓存
45
+ /**
46
+ * 检查 URL 是否为 undici ProxyAgent 支持的协议(http / https)
47
+ */
48
+ function isHttpLikeProxyUrl(url) {
49
+ try {
50
+ const u = new URL(url);
51
+ return u.protocol === 'http:' || u.protocol === 'https:';
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ /**
58
+ * 解析 Windows ProxyServer 注册表值,仅返回 undici 可用的 http(s) 代理 URL
59
+ *
60
+ * 可能的格式:
61
+ * 1) "host:port" — 单一代理,应用于所有协议
62
+ * 2) "http=host:port;https=host:port;ftp=host:port;socks=host:port"
63
+ * — 按协议分别配置
64
+ * 3) "scheme://host:port" — 已带 scheme(http / https / socks5 ...)
65
+ *
66
+ * 不支持 socks/socks4/socks5/pac 等非 http(s) 协议,遇到时返回 null(回退直连)
67
+ */
68
+ function parseWindowsProxyServer(raw) {
69
+ const trimmed = raw.trim();
70
+ if (!trimmed)
71
+ return null;
72
+ // 形如 "http=host:port;https=host:port;socks=host:port" 的多协议格式
73
+ if (trimmed.includes('=')) {
74
+ const map = new Map();
75
+ for (const seg of trimmed.split(';')) {
76
+ const eq = seg.indexOf('=');
77
+ if (eq > 0) {
78
+ const k = seg.slice(0, eq).trim().toLowerCase();
79
+ const v = seg.slice(eq + 1).trim();
80
+ if (k && v)
81
+ map.set(k, v);
82
+ }
83
+ }
84
+ const https = map.get('https');
85
+ if (https)
86
+ return `http://${https}`;
87
+ const http = map.get('http');
88
+ if (http)
89
+ return `http://${http}`;
90
+ return null;
91
+ }
92
+ // 已带 scheme
93
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
94
+ return isHttpLikeProxyUrl(trimmed) ? trimmed : null;
95
+ }
96
+ // 裸的 host:port,按 http 处理
97
+ return `http://${trimmed}`;
98
+ }
99
+ function getSystemProxy() {
100
+ const now = Date.now();
101
+ if (_systemProxyCacheTime > 0 && now - _systemProxyCacheTime < SYSTEM_PROXY_CACHE_TTL) {
102
+ return _cachedSystemProxy;
103
+ }
104
+ try {
105
+ if (process.platform === 'win32') {
106
+ const { execSync } = require('child_process');
107
+ const result = execSync('reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable', { encoding: 'utf8', timeout: 3000, windowsHide: true });
108
+ if (result.includes('0x1')) {
109
+ const serverResult = execSync('reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer', { encoding: 'utf8', timeout: 3000, windowsHide: true });
110
+ const match = serverResult.match(/ProxyServer\s+REG_SZ\s+(.+)/);
111
+ if (match) {
112
+ const parsed = parseWindowsProxyServer(match[1]);
113
+ _cachedSystemProxy = parsed;
114
+ _systemProxyCacheTime = now;
115
+ return _cachedSystemProxy;
116
+ }
117
+ }
118
+ }
119
+ else if (process.platform === 'darwin') {
120
+ const { execSync } = require('child_process');
121
+ const result = execSync('scutil --proxy', { encoding: 'utf8', timeout: 3000 });
122
+ // 优先 HTTPS 代理,回退到 HTTP 代理(仅 undici 支持的 http/https 协议)
123
+ const httpsEnabled = /HTTPSEnable\s*:\s*1/.test(result);
124
+ if (httpsEnabled) {
125
+ const hostMatch = result.match(/HTTPSProxy\s*:\s*(\S+)/);
126
+ const portMatch = result.match(/HTTPSPort\s*:\s*(\d+)/);
127
+ if (hostMatch) {
128
+ const proxy = `http://${hostMatch[1]}${portMatch ? ':' + portMatch[1] : ''}`;
129
+ _cachedSystemProxy = proxy;
130
+ _systemProxyCacheTime = now;
131
+ return _cachedSystemProxy;
132
+ }
133
+ }
134
+ const httpEnabled = /HTTPEnable\s*:\s*1/.test(result);
135
+ if (httpEnabled) {
136
+ const hostMatch = result.match(/HTTPProxy\s*:\s*(\S+)/);
137
+ const portMatch = result.match(/HTTPPort\s*:\s*(\d+)/);
138
+ if (hostMatch) {
139
+ const proxy = `http://${hostMatch[1]}${portMatch ? ':' + portMatch[1] : ''}`;
140
+ _cachedSystemProxy = proxy;
141
+ _systemProxyCacheTime = now;
142
+ return _cachedSystemProxy;
143
+ }
144
+ }
145
+ // macOS 仅配 SOCKS 时 undici 不支持,静默回退直连(safeCreateProxyAgent 也会兜底)
146
+ }
147
+ }
148
+ catch { /* 检测失败静默回退直连 */ }
149
+ _cachedSystemProxy = null;
150
+ _systemProxyCacheTime = now;
151
+ return null;
152
+ }
153
+ /**
154
+ * 安全地创建 undici Dispatcher
155
+ *
156
+ * 支持协议:
157
+ * - http: / https: → undici 原生 ProxyAgent
158
+ * - socks5: / socks4: → 通过 socks 包 + undici Agent 的 connect 钩子实现 SOCKS 隧道
159
+ *
160
+ * URL 无效或协议无法支持时返回 undefined,让调用方回退直连,
161
+ * 而不会让异常向上传播阻塞业务流程。
162
+ */
163
+ function safeCreateProxyAgent(proxyUrl) {
164
+ if (!proxyUrl)
165
+ return undefined;
166
+ // 校验 URL
167
+ let u;
168
+ try {
169
+ u = new URL(proxyUrl);
170
+ }
171
+ catch {
172
+ console.warn(`[Proxy] 代理 URL 无效: ${proxyUrl}`);
173
+ return undefined;
174
+ }
175
+ const protocol = u.protocol;
176
+ // http / https 走原生 ProxyAgent
177
+ if (protocol === 'http:' || protocol === 'https:') {
178
+ try {
179
+ return new undici_1.ProxyAgent({ uri: proxyUrl, requestTls: { rejectUnauthorized: false } });
180
+ }
181
+ catch (err) {
182
+ console.warn(`[Proxy] 创建 HTTP ProxyAgent 失败,回退直连: ${proxyUrl}`, err);
183
+ return undefined;
184
+ }
185
+ }
186
+ // SOCKS 走自定义 connect
187
+ if (protocol === 'socks5:' || protocol === 'socks5h:' || protocol === 'socks4:' || protocol === 'socks4a:') {
188
+ try {
189
+ return createSocksDispatcher(u);
190
+ }
191
+ catch (err) {
192
+ console.warn(`[Proxy] 创建 SOCKS Agent 失败,回退直连: ${proxyUrl}`, err);
193
+ return undefined;
194
+ }
195
+ }
196
+ console.warn(`[Proxy] 忽略不支持的代理协议 (仅支持 http/https/socks5/socks4): ${proxyUrl}`);
197
+ return undefined;
198
+ }
199
+ /**
200
+ * 通过 undici Agent 的 connect 钩子实现 SOCKS5/4 隧道
201
+ * 流程:socks.createConnection 建立 TCP 隧道 → 如目标是 https 再 TLS 升级 → 把 socket 交给 undici
202
+ */
203
+ function createSocksDispatcher(u) {
204
+ const isSocks5 = u.protocol === 'socks5:' || u.protocol === 'socks5h:';
205
+ const type = isSocks5 ? 5 : 4;
206
+ const proxyHost = u.hostname;
207
+ const proxyPort = Number(u.port) || 1080;
208
+ const userId = u.username ? decodeURIComponent(u.username) : undefined;
209
+ const password = u.password ? decodeURIComponent(u.password) : undefined;
210
+ // undici Agent.connect callback 的类型签名是 (err: Error, socket: null) | (err: null, socket: Socket)
211
+ // 用宽松 any 包装避免严格类型不匹配,运行时行为完全正确
212
+ return new undici_1.Agent({
213
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
214
+ connect: ((options, callback) => {
215
+ const targetHost = options.hostname || options.host || '';
216
+ const targetPort = Number(options.port) || (options.protocol === 'https:' ? 443 : 80);
217
+ // 动态导入 socks 库
218
+ let SocksClient;
219
+ try {
220
+ SocksClient = require('socks').SocksClient;
221
+ }
222
+ catch (err) {
223
+ callback(err, null);
224
+ return;
225
+ }
226
+ void SocksClient.createConnection({
227
+ proxy: { host: proxyHost, port: proxyPort, type, userId, password },
228
+ command: 'connect',
229
+ destination: { host: targetHost, port: targetPort }
230
+ })
231
+ .then(({ socket }) => {
232
+ // HTTPS 需要 TLS 升级
233
+ if (options.protocol === 'https:') {
234
+ const tlsSocket = tls.connect({
235
+ socket,
236
+ servername: options.servername || targetHost,
237
+ rejectUnauthorized: options.rejectUnauthorized ?? false
238
+ });
239
+ tlsSocket.once('secureConnect', () => callback(null, tlsSocket));
240
+ tlsSocket.once('error', (err) => callback(err, null));
241
+ }
242
+ else {
243
+ callback(null, socket);
244
+ }
245
+ })
246
+ .catch((err) => callback(err, null));
247
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
248
+ })
249
+ });
250
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.countTokens = countTokens;
4
+ exports.setModelContextWindow = setModelContextWindow;
5
+ exports.getModelContextWindow = getModelContextWindow;
6
+ exports.getModelContextLength = getModelContextLength;
7
+ /**
8
+ * Token 计数工具:使用 js-tiktoken cl100k_base 编码精确计算,
9
+ * 失败时降级到字节系数估算;并提供按模型 ID 查询 context 窗口大小,
10
+ * 以便从 Kiro 后端的 contextUsagePercentage 反推真实 input tokens。
11
+ */
12
+ const js_tiktoken_1 = require("js-tiktoken");
13
+ let encoder = null;
14
+ let encoderInitFailed = false;
15
+ /** 懒加载 cl100k_base 编码器(GPT-4/Claude 通用近似) */
16
+ function getEncoder() {
17
+ if (encoder)
18
+ return encoder;
19
+ if (encoderInitFailed)
20
+ return null;
21
+ try {
22
+ encoder = (0, js_tiktoken_1.getEncoding)('cl100k_base');
23
+ return encoder;
24
+ }
25
+ catch (err) {
26
+ console.warn('[TokenCounter] Failed to load cl100k_base encoder:', err);
27
+ encoderInitFailed = true;
28
+ return null;
29
+ }
30
+ }
31
+ /**
32
+ * 使用 tiktoken cl100k_base 精确计算 token 数。
33
+ * 兜底:UTF-8 字节数 / 3.0(针对 payload JSON 经验值,误差 ±10%)。
34
+ */
35
+ function countTokens(text) {
36
+ if (!text)
37
+ return 0;
38
+ const enc = getEncoder();
39
+ if (enc) {
40
+ try {
41
+ return enc.encode(text).length;
42
+ }
43
+ catch (err) {
44
+ console.warn('[TokenCounter] encode failed, using fallback:', err);
45
+ }
46
+ }
47
+ return Math.ceil(Buffer.byteLength(text, 'utf-8') / 3.0);
48
+ }
49
+ // ============ 模型 context 窗口缓存 ============
50
+ // 由 proxyServer 在 fetchKiroModels 后通过 setModelContextWindow 填充
51
+ // modelId → maxInputTokens
52
+ const modelContextWindowCache = new Map();
53
+ function setModelContextWindow(modelId, maxInputTokens) {
54
+ if (modelId && maxInputTokens > 0) {
55
+ modelContextWindowCache.set(modelId, maxInputTokens);
56
+ }
57
+ }
58
+ function getModelContextWindow(modelId) {
59
+ return modelContextWindowCache.get(modelId);
60
+ }
61
+ /**
62
+ * 归一化 model ID 用于模糊匹配:
63
+ * claude-sonnet-4.5 → claudesonnet45
64
+ * CLAUDE_SONNET_4_5_20251001_V1_0 → claudesonnet45
65
+ * claude-3.7-sonnet → claude37sonnet
66
+ */
67
+ function normalizeModelId(id) {
68
+ return id
69
+ .toLowerCase()
70
+ .replace(/[-._]/g, '')
71
+ .replace(/\d{8}/g, '') // 移除日期 (20251001)
72
+ .replace(/v\d+$/g, '') // 移除尾部版本号 (v1)
73
+ .replace(/v\d+_\d+$/g, ''); // 移除 v1_0 形式
74
+ }
75
+ /**
76
+ * 从缓存中按模糊匹配查找 context window。
77
+ * 例如:用户传 alias `claude-sonnet-4.5`,但 cache 里存的是 CW 内部 ID
78
+ * `CLAUDE_SONNET_4_5_20251001_V1_0`,归一化后都是 `claudesonnet45`。
79
+ */
80
+ function guessContextFromCache(modelId) {
81
+ if (modelContextWindowCache.size === 0)
82
+ return undefined;
83
+ const queryNorm = normalizeModelId(modelId);
84
+ if (!queryNorm)
85
+ return undefined;
86
+ // 精确归一化匹配
87
+ for (const [id, ctx] of modelContextWindowCache) {
88
+ if (normalizeModelId(id) === queryNorm)
89
+ return ctx;
90
+ }
91
+ // 双向子串匹配(处理别名简短形式)
92
+ for (const [id, ctx] of modelContextWindowCache) {
93
+ const idNorm = normalizeModelId(id);
94
+ if (idNorm.includes(queryNorm) || queryNorm.includes(idNorm))
95
+ return ctx;
96
+ }
97
+ return undefined;
98
+ }
99
+ /**
100
+ * 根据 model ID 返回 context 窗口大小(用于 contextUsagePercentage 反推 inputTokens)。
101
+ *
102
+ * 优先级:
103
+ * 1. 直接命中 cache(Kiro 真实拉取的 maxInputTokens,最准确)
104
+ * 2. 模糊匹配 cache(处理 alias ↔ CW 内部 ID 映射)
105
+ * 3. 关键词匹配兜底(cache 未填充时)
106
+ */
107
+ function getModelContextLength(modelId) {
108
+ if (!modelId)
109
+ return 200000;
110
+ // 1. 优先用 Kiro 后端真实返回的 maxInputTokens
111
+ const cached = modelContextWindowCache.get(modelId);
112
+ if (cached && cached > 0)
113
+ return cached;
114
+ // 2. 模糊匹配 cache(alias ↔ CW 内部 ID)
115
+ const guessed = guessContextFromCache(modelId);
116
+ if (guessed && guessed > 0)
117
+ return guessed;
118
+ // 3. 关键词匹配兜底(首次请求 cache 未填充时使用)
119
+ const id = modelId.toLowerCase();
120
+ // Claude 系列(默认 200K)
121
+ if (id.includes('claude-opus-4') || id.includes('claude-sonnet-4') || id.includes('claude-haiku-4'))
122
+ return 200000;
123
+ if (id.includes('claude-3-7') || id.includes('claude-3.7'))
124
+ return 200000;
125
+ if (id.includes('claude-3-5') || id.includes('claude-3.5'))
126
+ return 200000;
127
+ if (id.includes('claude-3'))
128
+ return 200000;
129
+ if (id.includes('claude-2.1'))
130
+ return 200000;
131
+ if (id.includes('claude-2'))
132
+ return 100000;
133
+ if (id.includes('claude-instant'))
134
+ return 100000;
135
+ // GPT 系列
136
+ if (id.includes('gpt-4o') || id.includes('gpt-4-turbo'))
137
+ return 128000;
138
+ if (id.includes('gpt-4.1'))
139
+ return 1000000;
140
+ if (id.includes('gpt-4-32k'))
141
+ return 32768;
142
+ if (id.includes('gpt-4'))
143
+ return 8192;
144
+ if (id.includes('gpt-3.5-turbo-16k'))
145
+ return 16384;
146
+ if (id.includes('gpt-3.5'))
147
+ return 4096;
148
+ if (id.includes('o1') || id.includes('o3'))
149
+ return 128000;
150
+ // Gemini 系列
151
+ if (id.includes('gemini-2.5') || id.includes('gemini-2.0') || id.includes('gemini-1.5'))
152
+ return 1000000;
153
+ if (id.includes('gemini'))
154
+ return 32768;
155
+ // Amazon Titan / Nova 系列
156
+ if (id.includes('nova-pro') || id.includes('nova-lite'))
157
+ return 300000;
158
+ if (id.includes('nova-micro'))
159
+ return 128000;
160
+ if (id.includes('titan'))
161
+ return 8000;
162
+ // CodeWhisperer/Q Developer 内部模型一般跟 Claude 看齐
163
+ return 200000;
164
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToolNameRegistry = void 0;
4
+ class ToolNameRegistry {
5
+ originalToKiro = new Map();
6
+ kiroToOriginal = new Map();
7
+ toKiroName(name) {
8
+ const existing = this.originalToKiro.get(name);
9
+ if (existing)
10
+ return existing;
11
+ const baseName = name.length <= 64 ? name : this.shorten(name);
12
+ const kiroName = this.ensureUnique(baseName, name);
13
+ this.originalToKiro.set(name, kiroName);
14
+ this.kiroToOriginal.set(kiroName, name);
15
+ return kiroName;
16
+ }
17
+ toClientName(name) {
18
+ return this.kiroToOriginal.get(name) || name;
19
+ }
20
+ restoreToolUse(toolUse) {
21
+ return {
22
+ ...toolUse,
23
+ name: this.toClientName(toolUse.name)
24
+ };
25
+ }
26
+ restoreToolUses(toolUses) {
27
+ return toolUses.map(toolUse => this.restoreToolUse(toolUse));
28
+ }
29
+ ensureUnique(baseName, originalName) {
30
+ const existing = this.kiroToOriginal.get(baseName);
31
+ if (!existing || existing === originalName)
32
+ return baseName;
33
+ const hash = this.hash(originalName);
34
+ const suffix = `_${hash}`;
35
+ const candidate = baseName.substring(0, Math.max(1, 64 - suffix.length)) + suffix;
36
+ const candidateExisting = this.kiroToOriginal.get(candidate);
37
+ if (!candidateExisting || candidateExisting === originalName)
38
+ return candidate;
39
+ throw new Error(`Tool name collision after shortening: ${originalName}`);
40
+ }
41
+ shorten(name) {
42
+ const hash = this.hash(name);
43
+ const suffix = `_${hash}`;
44
+ const readable = name.replace(/[^a-zA-Z0-9_-]/g, '_');
45
+ const maxPrefixLength = 64 - suffix.length;
46
+ return readable.substring(0, maxPrefixLength) + suffix;
47
+ }
48
+ hash(value) {
49
+ let hash = 2166136261;
50
+ for (let index = 0; index < value.length; index++) {
51
+ hash ^= value.charCodeAt(index);
52
+ hash = Math.imul(hash, 16777619);
53
+ }
54
+ return (hash >>> 0).toString(36);
55
+ }
56
+ }
57
+ exports.ToolNameRegistry = ToolNameRegistry;