coding-tool-x 3.7.9 → 3.7.11

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 (33) hide show
  1. package/dist/web/assets/{Analytics-D14GGF2y.js → Analytics-Ck_00Brj.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-C0TeUSZg.js → ConfigTemplates-Cpw2vUa8.js} +1 -1
  3. package/dist/web/assets/Home-Bft3Kztl.js +1 -0
  4. package/dist/web/assets/Home-CHZJkkjH.css +1 -0
  5. package/dist/web/assets/{PluginManager-DPZvPrT4.js → PluginManager-2RlvEJCT.js} +1 -1
  6. package/dist/web/assets/{ProjectList-DUaiCvsF.js → ProjectList-_ecCngpl.js} +1 -1
  7. package/dist/web/assets/{SessionList-XdHNM61o.js → SessionList-DlWcw3hO.js} +1 -1
  8. package/dist/web/assets/{SkillManager-I4LFlsVN.js → SkillManager-CnY7IXkX.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-zMI7Lhzf.js → WorkspaceManager-3mqghi09.js} +1 -1
  10. package/dist/web/assets/{index-C5nLTbOk.css → index-BceXgSSe.css} +1 -1
  11. package/dist/web/assets/index-k5oP7c-A.js +2 -0
  12. package/dist/web/index.html +2 -2
  13. package/package.json +1 -1
  14. package/src/commands/toggle-proxy.js +7 -8
  15. package/src/commands/ui.js +11 -1
  16. package/src/config/default.js +1 -1
  17. package/src/index.js +41 -15
  18. package/src/server/api/omp-channels.js +16 -5
  19. package/src/server/api/omp-config.js +1 -1
  20. package/src/server/api/omp-proxy.js +13 -42
  21. package/src/server/index.js +3 -3
  22. package/src/server/omp-proxy-server.js +129 -10
  23. package/src/server/services/channel-scheduler.js +59 -9
  24. package/src/server/services/omp-auth-gateway-client.js +62 -0
  25. package/src/server/services/omp-channels.js +109 -12
  26. package/src/server/services/omp-gateway-routing.js +295 -0
  27. package/src/server/services/omp-gateway.js +919 -0
  28. package/src/server/services/omp-session-log-observer.js +138 -0
  29. package/src/server/services/omp-sessions.js +80 -0
  30. package/src/server/services/omp-settings-manager.js +358 -89
  31. package/dist/web/assets/Home-CUh_BsGm.js +0 -1
  32. package/dist/web/assets/Home-CqnBXFxj.css +0 -1
  33. package/dist/web/assets/index-CCP7sRMP.js +0 -2
@@ -0,0 +1,138 @@
1
+ const { createOmpUsageEventCursor } = require('./omp-sessions');
2
+ const { getEnabledChannels } = require('./omp-channels');
3
+ const {
4
+ getManagedProviderId,
5
+ isManagedProviderId,
6
+ normalizeProviderId
7
+ } = require('./omp-settings-manager');
8
+ const { buildSuccessLogPayload, hasMeaningfulUsage } = require('./proxy-log-helper');
9
+ const { broadcastLog } = require('../websocket-server');
10
+
11
+ const DEFAULT_INTERVAL_MS = 5000;
12
+ const DEFAULT_MAX_SEEN_EVENTS = 10000;
13
+ let pollTimer = null;
14
+ let seenEventKeys = new Set();
15
+ let maxSeenEvents = DEFAULT_MAX_SEEN_EVENTS;
16
+ let usageEventCursor = null;
17
+
18
+ function getEventKey(event = {}) {
19
+ return String(event.key || event.id || '').trim();
20
+ }
21
+
22
+ function rememberEventKey(key) {
23
+ if (!key) return;
24
+ if (seenEventKeys.has(key)) {
25
+ seenEventKeys.delete(key);
26
+ }
27
+ seenEventKeys.add(key);
28
+ while (seenEventKeys.size > maxSeenEvents) {
29
+ seenEventKeys.delete(seenEventKeys.values().next().value);
30
+ }
31
+ }
32
+
33
+ function resolveChannel(event = {}, channels = getEnabledChannels()) {
34
+ const providerId = normalizeProviderId(event.provider || '');
35
+ const matched = (channels || []).find((channel) => {
36
+ const candidates = [
37
+ getManagedProviderId(channel),
38
+ channel.providerKey,
39
+ channel.provider,
40
+ channel.name,
41
+ channel.id
42
+ ].map(normalizeProviderId);
43
+ return candidates.includes(providerId);
44
+ });
45
+
46
+ return {
47
+ id: matched?.id || null,
48
+ name: matched?.name || event.provider || 'OMP'
49
+ };
50
+ }
51
+
52
+ function normalizeTimestamp(value) {
53
+ const timestamp = value ? new Date(value).getTime() : Date.now();
54
+ return Number.isFinite(timestamp) ? timestamp : Date.now();
55
+ }
56
+
57
+ function publishEvent(event, channels) {
58
+ if (isManagedProviderId(normalizeProviderId(event.provider || ''))) {
59
+ return false;
60
+ }
61
+ const channel = resolveChannel(event, channels);
62
+ const usage = event.usage || {};
63
+ broadcastLog(buildSuccessLogPayload({
64
+ source: 'omp',
65
+ requestId: event.id,
66
+ channel: channel.name,
67
+ model: event.model || '',
68
+ tokens: usage,
69
+ cost: Number(usage.cost) || 0,
70
+ timestamp: normalizeTimestamp(event.timestamp),
71
+ usageMissing: !hasMeaningfulUsage('omp', usage)
72
+ }));
73
+ return true;
74
+ }
75
+
76
+ function pollOmpSessionLogs() {
77
+ if (!pollTimer) return getOmpSessionLogObserverStatus();
78
+
79
+ const events = usageEventCursor.read();
80
+ const channels = getEnabledChannels();
81
+ events.forEach((event) => {
82
+ const key = getEventKey(event);
83
+ if (!key || seenEventKeys.has(key)) return;
84
+ rememberEventKey(key);
85
+ publishEvent(event, channels);
86
+ });
87
+ return getOmpSessionLogObserverStatus();
88
+ }
89
+
90
+ function startOmpSessionLogObserver(options = {}) {
91
+ if (pollTimer) return getOmpSessionLogObserverStatus();
92
+
93
+ usageEventCursor = createOmpUsageEventCursor();
94
+ maxSeenEvents = Math.max(1, Number(options.maxSeenEvents) || DEFAULT_MAX_SEEN_EVENTS);
95
+ seenEventKeys = new Set();
96
+ usageEventCursor.read().map(getEventKey).filter(Boolean).forEach(rememberEventKey);
97
+ const intervalMs = Math.max(100, Number(options.intervalMs) || DEFAULT_INTERVAL_MS);
98
+ pollTimer = setInterval(() => {
99
+ try {
100
+ pollOmpSessionLogs();
101
+ } catch (error) {
102
+ console.warn('[OMP Sessions] Failed to observe usage logs:', error.message);
103
+ }
104
+ }, intervalMs);
105
+ if (typeof pollTimer.unref === 'function') {
106
+ pollTimer.unref();
107
+ }
108
+ return getOmpSessionLogObserverStatus();
109
+ }
110
+
111
+ function stopOmpSessionLogObserver() {
112
+ if (pollTimer) {
113
+ clearInterval(pollTimer);
114
+ }
115
+ pollTimer = null;
116
+ seenEventKeys = new Set();
117
+ maxSeenEvents = DEFAULT_MAX_SEEN_EVENTS;
118
+ usageEventCursor?.reset?.();
119
+ usageEventCursor = null;
120
+ return getOmpSessionLogObserverStatus();
121
+ }
122
+
123
+ function getOmpSessionLogObserverStatus() {
124
+ return {
125
+ running: Boolean(pollTimer),
126
+ seenEvents: seenEventKeys.size
127
+ };
128
+ }
129
+
130
+ module.exports = {
131
+ startOmpSessionLogObserver,
132
+ stopOmpSessionLogObserver,
133
+ pollOmpSessionLogs,
134
+ getOmpSessionLogObserverStatus,
135
+ _test: {
136
+ resolveChannel
137
+ }
138
+ };
@@ -212,6 +212,42 @@ function parseSessionFile(filePath) {
212
212
  };
213
213
  }
214
214
 
215
+ function parseSessionUsageEvents(filePath) {
216
+ const entries = readJsonLines(filePath);
217
+ const header = entries.find(entry => entry?.type === 'session') || {};
218
+ const sessionId = header.id || parseOmpSessionId(filePath);
219
+ let activeProvider = '';
220
+ let activeModel = '';
221
+ const events = [];
222
+
223
+ entries.forEach((entry, index) => {
224
+ if (entry?.type === 'model_change') {
225
+ activeProvider = entry.provider || activeProvider;
226
+ activeModel = entry.modelId || entry.model || activeModel;
227
+ return;
228
+ }
229
+ if (entry?.type !== 'message') return;
230
+
231
+ const message = entry.message || {};
232
+ const role = message.role || entry.role;
233
+ if (role !== 'assistant') return;
234
+
235
+ const eventId = entry.id || message.id || `assistant-${index}`;
236
+ events.push({
237
+ key: `${filePath}:${eventId}`,
238
+ id: `${sessionId}:${eventId}`,
239
+ sessionId,
240
+ filePath,
241
+ provider: entry.provider || message.provider || activeProvider || '',
242
+ model: entry.model || message.model || activeModel || '',
243
+ timestamp: entry.timestamp || message.timestamp || header.timestamp || null,
244
+ usage: parseUsage(entry.usage || message.usage || {})
245
+ });
246
+ });
247
+
248
+ return events;
249
+ }
250
+
215
251
  function getOmpSessionPaths() {
216
252
  // Reading historical sessions normally only needs OMP's native directory
217
253
  // convention. Avoid starting the CLI here: project/count snapshot workers
@@ -264,6 +300,47 @@ function getAllSessions() {
264
300
  .sort((a, b) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
265
301
  }
266
302
 
303
+ function getOmpUsageEvents(rootDir = getOmpSessionPaths().sessions) {
304
+ return scanSessionFiles(rootDir).flatMap((filePath) => {
305
+ try {
306
+ return parseSessionUsageEvents(filePath);
307
+ } catch (error) {
308
+ console.warn('[OMP Sessions] Failed to parse usage events:', filePath, error.message);
309
+ return [];
310
+ }
311
+ });
312
+ }
313
+
314
+ function createOmpUsageEventCursor(rootDir = null) {
315
+ let signatures = new Map();
316
+
317
+ return {
318
+ read() {
319
+ const files = scanSessionFiles(rootDir || getOmpSessionPaths().sessions);
320
+ const nextSignatures = new Map();
321
+ const events = [];
322
+
323
+ files.forEach((filePath) => {
324
+ try {
325
+ const stat = fs.statSync(filePath);
326
+ const signature = `${stat.size}:${stat.mtimeMs}`;
327
+ nextSignatures.set(filePath, signature);
328
+ if (signatures.get(filePath) === signature) return;
329
+ events.push(...parseSessionUsageEvents(filePath));
330
+ } catch (error) {
331
+ console.warn('[OMP Sessions] Failed to read changed usage events:', filePath, error.message);
332
+ }
333
+ });
334
+
335
+ signatures = nextSignatures;
336
+ return events;
337
+ },
338
+ reset() {
339
+ signatures = new Map();
340
+ }
341
+ };
342
+ }
343
+
267
344
  function loadProjectOrder() {
268
345
  const data = safeReadJson(PROJECT_ORDER_FILE, { order: [] });
269
346
  return Array.isArray(data.order) ? data.order : [];
@@ -491,12 +568,14 @@ function isOmpCliInstalled() {
491
568
 
492
569
  module.exports = {
493
570
  buildLaunchCommand,
571
+ createOmpUsageEventCursor,
494
572
  decodeProjectName,
495
573
  deleteProject,
496
574
  deleteSession,
497
575
  encodeProjectName,
498
576
  forkSession,
499
577
  getAllSessions,
578
+ getOmpUsageEvents,
500
579
  getProjectAndSessionCounts,
501
580
  getProjects,
502
581
  getRecentSessions,
@@ -506,6 +585,7 @@ module.exports = {
506
585
  isOmpInstalled: isOmpCliInstalled,
507
586
  normalizeSession,
508
587
  parseSessionFile,
588
+ parseSessionUsageEvents,
509
589
  scanSessionFiles,
510
590
  searchSessions,
511
591
  saveProjectOrder,