@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,301 @@
1
+ "use strict";
2
+ // Prompt Cache 模拟器
3
+ // 在反代侧追踪 cache_control 断点,模拟 Anthropic 的 prompt caching 行为
4
+ // 让 Claude Code 的 cache_control 字段产生实际效果的 usage 统计
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.promptCacheTracker = exports.PromptCacheTracker = void 0;
7
+ exports.buildCachedClaudeUsage = buildCachedClaudeUsage;
8
+ const crypto_1 = require("crypto");
9
+ const kiroApi_1 = require("./kiroApi");
10
+ // 常量
11
+ const DEFAULT_CACHE_TTL = 5 * 60 * 1000; // 5 分钟(Anthropic 默认 ephemeral TTL)
12
+ const ONE_HOUR_CACHE_TTL = 60 * 60 * 1000; // 1 小时
13
+ const DEFAULT_MIN_CACHEABLE_TOKENS = 1024; // 最小可缓存 token 数
14
+ const OPUS_MIN_CACHEABLE_TOKENS = 4096; // Opus 模型最小缓存阈值
15
+ const MAX_CACHE_RATIO = 0.85; // 最新内容不可能 100% 缓存命中
16
+ const MAX_ENTRIES_PER_ACCOUNT = 200; // 每个账号最大缓存条目数
17
+ const PRUNE_INTERVAL = 60 * 1000; // 清理间隔 1 分钟
18
+ // ============ Prompt Cache Tracker ============
19
+ class PromptCacheTracker {
20
+ entriesByAccount = new Map();
21
+ lastPrune = Date.now();
22
+ // 从 Claude 请求构建缓存 profile
23
+ buildClaudeProfile(system, messages, tools, totalInputTokens, model) {
24
+ const blocks = this.flattenCacheBlocks(system, messages, tools);
25
+ if (blocks.length === 0)
26
+ return null;
27
+ const hasher = (0, crypto_1.createHash)('sha256');
28
+ const breakpoints = [];
29
+ let cumulativeTokens = 0;
30
+ let activeTTL = 0;
31
+ for (const block of blocks) {
32
+ this.hashChunk(hasher, block.value);
33
+ cumulativeTokens += block.tokens;
34
+ // 确定断点 TTL
35
+ let breakpointTTL = 0;
36
+ if (block.ttl > 0) {
37
+ breakpointTTL = block.ttl;
38
+ activeTTL = block.ttl;
39
+ }
40
+ else if (block.isMessageEnd && activeTTL > 0) {
41
+ // 隐式断点:在有显式断点之后,每个消息结束都是一个断点
42
+ breakpointTTL = activeTTL;
43
+ }
44
+ if (breakpointTTL <= 0)
45
+ continue;
46
+ breakpoints.push({
47
+ fingerprint: hasher.copy().digest('hex'),
48
+ cumulativeTokens,
49
+ ttl: breakpointTTL
50
+ });
51
+ }
52
+ if (breakpoints.length === 0)
53
+ return null;
54
+ return {
55
+ breakpoints,
56
+ totalInputTokens: Math.max(totalInputTokens, cumulativeTokens),
57
+ model
58
+ };
59
+ }
60
+ // 计算缓存命中情况
61
+ compute(accountId, profile) {
62
+ if (!profile || profile.breakpoints.length === 0 || !accountId) {
63
+ return { cacheCreationInputTokens: 0, cacheReadInputTokens: 0, cacheCreation5mTokens: 0, cacheCreation1hTokens: 0 };
64
+ }
65
+ const minTokens = this.minCacheableTokens(profile.model);
66
+ const last = profile.breakpoints[profile.breakpoints.length - 1];
67
+ let lastTokens = Math.min(last.cumulativeTokens, profile.totalInputTokens);
68
+ const now = Date.now();
69
+ this.pruneIfNeeded(now);
70
+ const entries = this.entriesByAccount.get(accountId);
71
+ if (!entries || entries.size === 0) {
72
+ // 首次请求:全部是 creation
73
+ const effectiveCreation = lastTokens >= minTokens ? lastTokens : 0;
74
+ const [cache5m, cache1h] = this.computeTTLBreakdown(profile, 0);
75
+ return {
76
+ cacheCreationInputTokens: effectiveCreation,
77
+ cacheReadInputTokens: 0,
78
+ cacheCreation5mTokens: cache5m,
79
+ cacheCreation1hTokens: cache1h
80
+ };
81
+ }
82
+ // 上限 85%
83
+ const maxCacheable = Math.floor(profile.totalInputTokens * MAX_CACHE_RATIO);
84
+ if (lastTokens > maxCacheable)
85
+ lastTokens = maxCacheable;
86
+ // 从后往前匹配最长前缀
87
+ let matchedTokens = 0;
88
+ for (let i = profile.breakpoints.length - 1; i >= 0; i--) {
89
+ const bp = profile.breakpoints[i];
90
+ if (bp.cumulativeTokens < minTokens)
91
+ continue;
92
+ const entry = entries.get(bp.fingerprint);
93
+ if (!entry || entry.expiresAt < now)
94
+ continue;
95
+ // 命中:刷新过期时间
96
+ entry.expiresAt = now + entry.ttl;
97
+ matchedTokens = Math.min(bp.cumulativeTokens, profile.totalInputTokens);
98
+ if (matchedTokens > lastTokens)
99
+ matchedTokens = lastTokens;
100
+ break;
101
+ }
102
+ const creation = Math.max(lastTokens - matchedTokens, 0);
103
+ const [cache5m, cache1h] = this.computeTTLBreakdown(profile, matchedTokens);
104
+ return {
105
+ cacheCreationInputTokens: creation,
106
+ cacheReadInputTokens: matchedTokens,
107
+ cacheCreation5mTokens: cache5m,
108
+ cacheCreation1hTokens: cache1h
109
+ };
110
+ }
111
+ // 更新缓存条目(请求成功后调用)
112
+ update(accountId, profile) {
113
+ if (!profile || profile.breakpoints.length === 0 || !accountId)
114
+ return;
115
+ const minTokens = this.minCacheableTokens(profile.model);
116
+ const now = Date.now();
117
+ let entries = this.entriesByAccount.get(accountId);
118
+ if (!entries) {
119
+ entries = new Map();
120
+ this.entriesByAccount.set(accountId, entries);
121
+ }
122
+ for (const bp of profile.breakpoints) {
123
+ if (bp.cumulativeTokens < minTokens)
124
+ continue;
125
+ entries.set(bp.fingerprint, {
126
+ expiresAt: now + bp.ttl,
127
+ ttl: bp.ttl
128
+ });
129
+ }
130
+ // 限制每个账号的条目数
131
+ if (entries.size > MAX_ENTRIES_PER_ACCOUNT) {
132
+ const sorted = [...entries.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
133
+ const toDelete = sorted.slice(0, entries.size - MAX_ENTRIES_PER_ACCOUNT);
134
+ for (const [key] of toDelete)
135
+ entries.delete(key);
136
+ }
137
+ }
138
+ // 清除所有缓存
139
+ clear() {
140
+ const count = this.totalEntries();
141
+ this.entriesByAccount.clear();
142
+ return count;
143
+ }
144
+ totalEntries() {
145
+ let count = 0;
146
+ for (const entries of this.entriesByAccount.values())
147
+ count += entries.size;
148
+ return count;
149
+ }
150
+ // ============ 内部方法 ============
151
+ flattenCacheBlocks(system, messages, tools) {
152
+ const blocks = [];
153
+ // 工具定义
154
+ if (tools) {
155
+ for (const tool of tools) {
156
+ const value = this.canonicalize({ kind: 'tool', name: tool.name, description: tool.description, input_schema: tool.input_schema });
157
+ blocks.push({
158
+ value,
159
+ tokens: (0, kiroApi_1.estimateTokens)(value),
160
+ ttl: this.extractTTL(tool),
161
+ isMessageEnd: false
162
+ });
163
+ }
164
+ }
165
+ // System prompt
166
+ this.appendSystemBlocks(blocks, system);
167
+ // Messages
168
+ for (let i = 0; i < messages.length; i++) {
169
+ this.appendMessageBlocks(blocks, messages[i], i);
170
+ }
171
+ return blocks;
172
+ }
173
+ appendSystemBlocks(blocks, system) {
174
+ if (!system)
175
+ return;
176
+ if (typeof system === 'string') {
177
+ const value = this.canonicalize({ kind: 'system', type: 'text', text: system });
178
+ blocks.push({ value, tokens: (0, kiroApi_1.estimateTokens)(system), ttl: 0, isMessageEnd: false });
179
+ }
180
+ else if (Array.isArray(system)) {
181
+ for (const block of system) {
182
+ const obj = typeof block === 'string' ? { type: 'text', text: block } : block;
183
+ const value = this.canonicalize({ kind: 'system', block: obj });
184
+ const text = obj.text || '';
185
+ blocks.push({
186
+ value,
187
+ tokens: (0, kiroApi_1.estimateTokens)(text || JSON.stringify(obj)),
188
+ ttl: this.extractTTL(obj),
189
+ isMessageEnd: false
190
+ });
191
+ }
192
+ }
193
+ }
194
+ appendMessageBlocks(blocks, msg, messageIndex) {
195
+ const content = msg.content;
196
+ if (typeof content === 'string') {
197
+ const value = this.canonicalize({ kind: 'message', role: msg.role, index: messageIndex, type: 'text', text: content });
198
+ blocks.push({
199
+ value,
200
+ tokens: (0, kiroApi_1.estimateTokens)(content),
201
+ ttl: this.extractTTL(msg),
202
+ isMessageEnd: true
203
+ });
204
+ }
205
+ else if (Array.isArray(content)) {
206
+ const lastIdx = content.length - 1;
207
+ for (let i = 0; i < content.length; i++) {
208
+ const block = content[i];
209
+ const text = block.text || block.thinking || '';
210
+ const value = this.canonicalize({ kind: 'message', role: msg.role, index: messageIndex, blockIndex: i, block });
211
+ blocks.push({
212
+ value,
213
+ tokens: (0, kiroApi_1.estimateTokens)(text || JSON.stringify(block)),
214
+ ttl: this.extractTTL(block),
215
+ isMessageEnd: i === lastIdx
216
+ });
217
+ }
218
+ }
219
+ }
220
+ extractTTL(obj) {
221
+ if (!obj || typeof obj !== 'object')
222
+ return 0;
223
+ const record = obj;
224
+ const cacheControl = record.cache_control;
225
+ if (!cacheControl)
226
+ return 0;
227
+ if (String(cacheControl.type).toLowerCase() !== 'ephemeral')
228
+ return 0;
229
+ const ttlValue = cacheControl.ttl;
230
+ if (ttlValue === '1h' || ttlValue === '1H')
231
+ return ONE_HOUR_CACHE_TTL;
232
+ if (typeof ttlValue === 'number' && ttlValue > 0)
233
+ return ttlValue * 1000;
234
+ return DEFAULT_CACHE_TTL;
235
+ }
236
+ canonicalize(obj) {
237
+ return JSON.stringify(obj, Object.keys(obj).sort());
238
+ }
239
+ hashChunk(hasher, chunk) {
240
+ hasher.update(`${chunk.length}\0${chunk}\0`);
241
+ }
242
+ minCacheableTokens(model) {
243
+ return model.toLowerCase().includes('opus') ? OPUS_MIN_CACHEABLE_TOKENS : DEFAULT_MIN_CACHEABLE_TOKENS;
244
+ }
245
+ computeTTLBreakdown(profile, matchedTokens) {
246
+ let cache5m = 0;
247
+ let cache1h = 0;
248
+ let previous = matchedTokens;
249
+ for (const bp of profile.breakpoints) {
250
+ const current = Math.min(bp.cumulativeTokens, profile.totalInputTokens);
251
+ if (current <= previous)
252
+ continue;
253
+ const delta = current - previous;
254
+ if (bp.ttl >= ONE_HOUR_CACHE_TTL) {
255
+ cache1h += delta;
256
+ }
257
+ else {
258
+ cache5m += delta;
259
+ }
260
+ previous = current;
261
+ }
262
+ return [cache5m, cache1h];
263
+ }
264
+ pruneIfNeeded(now) {
265
+ if (now - this.lastPrune < PRUNE_INTERVAL)
266
+ return;
267
+ this.lastPrune = now;
268
+ for (const [accountId, entries] of this.entriesByAccount) {
269
+ for (const [fp, entry] of entries) {
270
+ if (entry.expiresAt < now)
271
+ entries.delete(fp);
272
+ }
273
+ if (entries.size === 0)
274
+ this.entriesByAccount.delete(accountId);
275
+ }
276
+ }
277
+ }
278
+ exports.PromptCacheTracker = PromptCacheTracker;
279
+ // 全局单例
280
+ exports.promptCacheTracker = new PromptCacheTracker();
281
+ // 构建带缓存 usage 的 Claude usage 对象
282
+ function buildCachedClaudeUsage(inputTokens, outputTokens, cacheUsage) {
283
+ const billed = Math.max(inputTokens - cacheUsage.cacheCreationInputTokens - cacheUsage.cacheReadInputTokens, 0);
284
+ const result = {
285
+ input_tokens: billed,
286
+ output_tokens: outputTokens
287
+ };
288
+ if (cacheUsage.cacheCreationInputTokens > 0) {
289
+ result.cache_creation_input_tokens = cacheUsage.cacheCreationInputTokens;
290
+ }
291
+ if (cacheUsage.cacheReadInputTokens > 0) {
292
+ result.cache_read_input_tokens = cacheUsage.cacheReadInputTokens;
293
+ }
294
+ if (cacheUsage.cacheCreation5mTokens > 0 || cacheUsage.cacheCreation1hTokens > 0) {
295
+ result.cache_creation = {
296
+ ...(cacheUsage.cacheCreation5mTokens > 0 ? { ephemeral_5m_input_tokens: cacheUsage.cacheCreation5mTokens } : {}),
297
+ ...(cacheUsage.cacheCreation1hTokens > 0 ? { ephemeral_1h_input_tokens: cacheUsage.cacheCreation1hTokens } : {})
298
+ };
299
+ }
300
+ return result;
301
+ }