coding-tool-x 3.8.6 → 3.8.8
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.
- package/dist/web/assets/{AgentsDrawer-Db9wAyaZ.js → AgentsDrawer-DRQuwi0m.js} +1 -1
- package/dist/web/assets/Analytics-Cf5KwEdj.js +1 -0
- package/dist/web/assets/AssetPathField-DyniM0pF.js +1 -0
- package/dist/web/assets/{ChatHistoryDrawer-CwzJHyNP.js → ChatHistoryDrawer-CrfS0KYV.js} +1 -1
- package/dist/web/assets/{CollapseItem-CTCfSiue.js → CollapseItem-ClMsJuUu.js} +1 -1
- package/dist/web/assets/{CommandsDrawer-vDghB1U9.js → CommandsDrawer-CzHelf56.js} +1 -1
- package/dist/web/assets/ConfigExportDrawer-BEZ8wTby.js +1 -0
- package/dist/web/assets/ConfigTemplates-Crg3xWe5.js +1 -0
- package/dist/web/assets/{ConfigTemplatesDrawer-CbsJp76X.js → ConfigTemplatesDrawer-BqM32Im7.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesPanel-liMSTh7s.js → ConfigTemplatesPanel-lTdutC8H.js} +1 -1
- package/dist/web/assets/{Divider-D28g1gwj.js → Divider-DsdbkHrG.js} +1 -1
- package/dist/web/assets/{FavoritesDrawer-D6QSiYaL.js → FavoritesDrawer-DDyWUBJR.js} +1 -1
- package/dist/web/assets/Forward-DLCL7aMY.js +1 -0
- package/dist/web/assets/{GatewayConvertDrawer-RvET6iNG.js → GatewayConvertDrawer-Rigtt_5J.js} +1 -1
- package/dist/web/assets/Home-tCSKmSHV.js +1 -0
- package/dist/web/assets/{MarkdownEditor-CYmLGYju.js → MarkdownEditor-BMMUufSc.js} +1 -1
- package/dist/web/assets/McpDrawer-CmE-d7VW.js +1 -0
- package/dist/web/assets/{McpFormDrawer-BcTlgSpN.js → McpFormDrawer-ByvS18od.js} +1 -1
- package/dist/web/assets/{PluginManager-Dh2o0xkZ.js → PluginManager-C4kJlpVX.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-deKQ5u3I.js → PluginsDrawer-05R4k15x.js} +1 -1
- package/dist/web/assets/PluginsPanel-BCoEs7Og.js +1 -0
- package/dist/web/assets/PluginsPanel-CzGMK-Ap.css +1 -0
- package/dist/web/assets/ProjectList-BJt1OIaI.js +1 -0
- package/dist/web/assets/ProjectList-DG3yu8xZ.css +1 -0
- package/dist/web/assets/PromptsDrawer-DnwBf_oW.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-B7d-38wH.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-yrAD4p1A.css +1 -0
- package/dist/web/assets/SessionCard-BUgjHQwX.js +1 -0
- package/dist/web/assets/SessionList-B-bXZRFk.js +1 -0
- package/dist/web/assets/SessionList-qG_XpPBJ.css +1 -0
- package/dist/web/assets/{SettingsDrawer-u_vwUuPS.js → SettingsDrawer-BITzjQLl.js} +1 -1
- package/dist/web/assets/SkillManager-D9jkNuRM.js +1 -0
- package/dist/web/assets/{SkillsDrawer-CkF3BpyC.js → SkillsDrawer-0Z4IefxC.js} +1 -1
- package/dist/web/assets/SkillsPanel-BKIVOj7n.js +1 -0
- package/dist/web/assets/{SkillsPanel-B3Nm5j32.css → SkillsPanel-C4ke56nF.css} +1 -1
- package/dist/web/assets/{SpeedTestDrawer-BgfG1Ked.js → SpeedTestDrawer-Bx1bYf5i.js} +1 -1
- package/dist/web/assets/Upload-CzC4rGI4.js +1 -0
- package/dist/web/assets/{WorkspaceDrawer-Br5CeCzL.js → WorkspaceDrawer-eujtWpoq.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-B2O8-qIw.js → WorkspaceManager-CQ-8Ej68.js} +1 -1
- package/dist/web/assets/{WorkspacePanel-5oDKit3c.js → WorkspacePanel-Cql5qE2R.js} +1 -1
- package/dist/web/assets/headers-BqDmkMjy.js +1 -0
- package/dist/web/assets/{icons-B2Vg-IOC.js → icons-DmFe4b5j.js} +1 -1
- package/dist/web/assets/index-Cmmwj3Fx.js +2 -0
- package/dist/web/assets/{index-Bi1MQHYX.css → index-Cnbe696C.css} +1 -1
- package/dist/web/assets/{light-Cp0t7v32.js → light-0eavxNvt.js} +1 -1
- package/dist/web/assets/platforms-BmzzwnSw.js +1 -0
- package/dist/web/assets/platforms-DWXgmy2w.js +1 -0
- package/dist/web/assets/session-visibility-BAmxh3cK.js +1 -0
- package/dist/web/assets/sessions-D4yQtfKb.js +1 -0
- package/dist/web/assets/sessions-DqlbocLJ.js +1 -0
- package/dist/web/assets/use-message-DqCwefG6.js +1 -0
- package/dist/web/assets/{useResponsiveDrawer-jbmqZuJs.js → useResponsiveDrawer-DZd5rNk5.js} +1 -1
- package/dist/web/index.html +7 -7
- package/package.json +1 -1
- package/src/commands/search.js +17 -2
- package/src/config/paths.js +2 -0
- package/src/platforms/driver-registry.js +9 -1
- package/src/platforms/drivers/claude/session-history-adapter.js +72 -1
- package/src/platforms/drivers/claude/sessions-implementation.js +49 -7
- package/src/platforms/drivers/claude/sessions.js +30 -6
- package/src/platforms/drivers/codex/parser.js +57 -1
- package/src/platforms/drivers/codex/session-history-adapter.js +8 -2
- package/src/platforms/drivers/codex/sessions-implementation.js +25 -3
- package/src/platforms/drivers/codex/sessions.js +18 -1
- package/src/platforms/drivers/dsh/api-operations.js +396 -0
- package/src/platforms/drivers/dsh/channels-implementation.js +458 -0
- package/src/platforms/drivers/dsh/channels.js +191 -0
- package/src/platforms/drivers/dsh/common.js +571 -0
- package/src/platforms/drivers/dsh/native-config.js +93 -0
- package/src/platforms/drivers/dsh/native-logs.js +116 -0
- package/src/platforms/drivers/dsh/plugin-manager.js +117 -0
- package/src/platforms/drivers/dsh/projects.js +23 -0
- package/src/platforms/drivers/dsh/proxy-implementation.js +260 -0
- package/src/platforms/drivers/dsh/proxy.js +23 -0
- package/src/platforms/drivers/dsh/resources.js +346 -0
- package/src/platforms/drivers/dsh/sessions.js +587 -0
- package/src/platforms/drivers/dsh/statistics-implementation.js +238 -0
- package/src/platforms/drivers/dsh/statistics.js +24 -0
- package/src/platforms/drivers/gemini/session-history-adapter.js +49 -1
- package/src/platforms/drivers/gemini/sessions-implementation.js +46 -1
- package/src/platforms/drivers/gemini/sessions.js +2 -1
- package/src/platforms/drivers/omp/session-history-adapter.js +42 -1
- package/src/platforms/drivers/omp/sessions-implementation.js +51 -3
- package/src/platforms/drivers/omp/sessions.js +33 -2
- package/src/platforms/drivers/opencode/sessions-implementation.js +89 -9
- package/src/platforms/drivers/opencode/sessions.js +37 -1
- package/src/platforms/manifest-schema.js +43 -3
- package/src/platforms/manifests/dsh.json +542 -0
- package/src/platforms/path-resolver.js +2 -0
- package/src/platforms/platform-path-context.js +12 -2
- package/src/platforms/registry.js +16 -1
- package/src/platforms/runtime.js +5 -0
- package/src/platforms/session-history-adapters.js +1 -0
- package/src/server/api/platform-api-config.js +23 -2
- package/src/server/api/platform-route-factory.js +9 -2
- package/src/server/api/platforms.js +19 -1
- package/src/server/api/plugins.js +108 -13
- package/src/server/services/config-registry-service.js +10 -1
- package/src/server/services/config-templates-service.js +1 -0
- package/src/server/services/plugins-service.js +474 -103
- package/src/server/services/session-history-index.js +900 -140
- package/src/server/services/session-history-worker.js +3 -1
- package/src/server/services/skill-service.js +214 -7
- package/src/server/services/usage-log-utils.js +1 -0
- package/src/server/services/workspace-service.js +39 -10
- package/src/shared/channel-driver.js +80 -5
- package/src/shared/driver-factories/api.js +22 -6
- package/src/shared/driver-factories/projects.js +8 -0
- package/dist/web/assets/Analytics-C4gMzv7g.js +0 -1
- package/dist/web/assets/AssetPathField-Bd_nMFu3.js +0 -1
- package/dist/web/assets/ConfigExportDrawer-C-XDvlHc.js +0 -1
- package/dist/web/assets/ConfigTemplates-CYY8XRED.js +0 -1
- package/dist/web/assets/Home-P8eseZcf.js +0 -1
- package/dist/web/assets/McpDrawer-Ch3bkKIq.js +0 -1
- package/dist/web/assets/PluginsPanel-BofsgQgj.js +0 -1
- package/dist/web/assets/PluginsPanel-D6ZqITcR.css +0 -1
- package/dist/web/assets/ProjectList-677RLZ8O.css +0 -1
- package/dist/web/assets/ProjectList-DhSWJHTA.js +0 -1
- package/dist/web/assets/PromptsDrawer-Cfb2FeeU.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-3eXamJhr.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +0 -1
- package/dist/web/assets/SessionCard-CW0eHURI.js +0 -1
- package/dist/web/assets/SessionList-BpYRz1cX.css +0 -1
- package/dist/web/assets/SessionList-DQfyDGWU.js +0 -1
- package/dist/web/assets/SkillManager-DHMprJLA.js +0 -1
- package/dist/web/assets/SkillsPanel-DWE4pFGx.js +0 -1
- package/dist/web/assets/Upload-CNwaGOws.js +0 -1
- package/dist/web/assets/headers-cf-QapUq.js +0 -1
- package/dist/web/assets/index-BMAUPq5K.js +0 -2
- package/dist/web/assets/platforms-CBkB45JP.js +0 -1
- package/dist/web/assets/platforms-FTpjxAT6.js +0 -1
- package/dist/web/assets/sessions-Cmy31slD.js +0 -1
- package/dist/web/assets/sessions-yC_oVVh8.js +0 -1
- package/dist/web/assets/use-message-DoLM8C1C.js +0 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
recordRequest: recordSharedRequest,
|
|
5
|
+
getStatistics: getSharedStatistics,
|
|
6
|
+
getDailyStatistics: getSharedDailyStatistics,
|
|
7
|
+
getTodayStatistics: getSharedTodayStatistics
|
|
8
|
+
} = require('../../../server/services/statistics-service');
|
|
9
|
+
const { normalizeUsageTokens, toNumber } = require('../../../server/services/usage-log-utils');
|
|
10
|
+
const { listArtifacts } = require('./sessions');
|
|
11
|
+
const { normalizeDshEvent } = require('./native-logs');
|
|
12
|
+
|
|
13
|
+
const TOOL_TYPE = 'dsh';
|
|
14
|
+
let configuredContext = {};
|
|
15
|
+
|
|
16
|
+
function configure(context = {}) {
|
|
17
|
+
configuredContext = context;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function initAggregateEntry(name = '') {
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
requests: 0,
|
|
24
|
+
tokens: { input: 0, output: 0, reasoning: 0, cached: 0, total: 0 },
|
|
25
|
+
cost: 0,
|
|
26
|
+
firstUsed: null,
|
|
27
|
+
lastUsed: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toLegacyEntryShape(entry = {}, includeName = false) {
|
|
32
|
+
const normalized = normalizeUsageTokens(TOOL_TYPE, entry.tokens || {});
|
|
33
|
+
const result = {
|
|
34
|
+
requests: toNumber(entry.requests),
|
|
35
|
+
tokens: {
|
|
36
|
+
input: normalized.input,
|
|
37
|
+
output: normalized.output,
|
|
38
|
+
reasoning: normalized.reasoning,
|
|
39
|
+
cached: normalized.cached,
|
|
40
|
+
total: normalized.total
|
|
41
|
+
},
|
|
42
|
+
cost: toNumber(entry.cost)
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (includeName) {
|
|
46
|
+
result.name = entry.name || '';
|
|
47
|
+
if (entry.firstUsed) result.firstUsed = entry.firstUsed;
|
|
48
|
+
if (entry.lastUsed) result.lastUsed = entry.lastUsed;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pickToolScope(sharedStats = {}) {
|
|
54
|
+
const byToolType = sharedStats.byToolType || {};
|
|
55
|
+
const toolScope = byToolType[TOOL_TYPE] || {};
|
|
56
|
+
const byChannel = toolScope.channels && typeof toolScope.channels === 'object'
|
|
57
|
+
? toolScope.channels
|
|
58
|
+
: Object.fromEntries(Object.entries(sharedStats.byChannel || {}).filter(([, value]) => value?.toolType === TOOL_TYPE));
|
|
59
|
+
const byModel = toolScope.models && typeof toolScope.models === 'object'
|
|
60
|
+
? toolScope.models
|
|
61
|
+
: Object.fromEntries(Object.entries(sharedStats.byModel || {}).filter(([, value]) => value?.toolType === TOOL_TYPE));
|
|
62
|
+
return { toolScope, byChannel, byModel };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildSummaryStatistics(sharedStats = {}) {
|
|
66
|
+
const { toolScope, byChannel, byModel } = pickToolScope(sharedStats);
|
|
67
|
+
const normalized = toLegacyEntryShape(toolScope);
|
|
68
|
+
return {
|
|
69
|
+
version: '1.0',
|
|
70
|
+
source: 'shared-stats',
|
|
71
|
+
lastUpdated: sharedStats.lastUpdated || new Date().toISOString(),
|
|
72
|
+
global: {
|
|
73
|
+
totalRequests: normalized.requests,
|
|
74
|
+
totalTokens: normalized.tokens.total,
|
|
75
|
+
totalCost: normalized.cost
|
|
76
|
+
},
|
|
77
|
+
byChannel: Object.fromEntries(Object.entries(byChannel).map(([key, value]) => [key, toLegacyEntryShape(value, true)])),
|
|
78
|
+
byModel: Object.fromEntries(Object.entries(byModel).map(([key, value]) => [key, toLegacyEntryShape(value)]))
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildDailyStatistics(sharedDaily = {}, fallbackDate) {
|
|
83
|
+
const toolScope = sharedDaily.byToolType?.[TOOL_TYPE] || {};
|
|
84
|
+
const normalized = toLegacyEntryShape(toolScope);
|
|
85
|
+
return {
|
|
86
|
+
date: sharedDaily.date || fallbackDate,
|
|
87
|
+
source: 'shared-stats',
|
|
88
|
+
summary: {
|
|
89
|
+
requests: normalized.requests,
|
|
90
|
+
tokens: normalized.tokens.total,
|
|
91
|
+
cost: normalized.cost
|
|
92
|
+
},
|
|
93
|
+
byChannel: Object.fromEntries(Object.entries(toolScope.channels || {}).map(([key, value]) => [key, toLegacyEntryShape(value, true)])),
|
|
94
|
+
byModel: Object.fromEntries(Object.entries(toolScope.models || {}).map(([key, value]) => [key, toLegacyEntryShape(value)]))
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function hasSummaryData(summary = {}) {
|
|
99
|
+
return Boolean(
|
|
100
|
+
summary.global?.totalRequests
|
|
101
|
+
|| summary.global?.totalTokens
|
|
102
|
+
|| summary.global?.totalCost
|
|
103
|
+
|| Object.keys(summary.byChannel || {}).length
|
|
104
|
+
|| Object.keys(summary.byModel || {}).length
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function hasDailyData(daily = {}) {
|
|
109
|
+
return Boolean(
|
|
110
|
+
daily.summary?.requests
|
|
111
|
+
|| daily.summary?.tokens
|
|
112
|
+
|| daily.summary?.cost
|
|
113
|
+
|| Object.keys(daily.byChannel || {}).length
|
|
114
|
+
|| Object.keys(daily.byModel || {}).length
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function eventDateKey(timestamp) {
|
|
119
|
+
const date = new Date(timestamp);
|
|
120
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
121
|
+
return new Date(date.getTime() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveDate(value) {
|
|
125
|
+
if (value && typeof value === 'object') {
|
|
126
|
+
return value.params?.date || value.query?.date || eventDateKey(Date.now());
|
|
127
|
+
}
|
|
128
|
+
return value || eventDateKey(Date.now());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readSessionUsage() {
|
|
132
|
+
try {
|
|
133
|
+
const artifacts = listArtifacts(configuredContext, { includeEvents: true });
|
|
134
|
+
return artifacts.flatMap(artifact => (artifact.events || [])
|
|
135
|
+
.map((event, index) => normalizeDshEvent(event, artifact.filePath, index))
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.map(event => ({ ...event, sessionId: artifact.header?.id || event.sessionId })));
|
|
138
|
+
} catch (_) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function addUsage(entry, event) {
|
|
144
|
+
const tokens = normalizeUsageTokens(TOOL_TYPE, event.tokens || {});
|
|
145
|
+
if (!tokens.total && !tokens.input && !tokens.output && !tokens.reasoning && !tokens.cached) return false;
|
|
146
|
+
entry.requests += 1;
|
|
147
|
+
entry.tokens.input += tokens.input;
|
|
148
|
+
entry.tokens.output += tokens.output;
|
|
149
|
+
entry.tokens.reasoning += tokens.reasoning;
|
|
150
|
+
entry.tokens.cached += tokens.cached;
|
|
151
|
+
entry.tokens.total += tokens.total;
|
|
152
|
+
entry.cost += toNumber(event.cost);
|
|
153
|
+
const iso = new Date(event.timestamp).toISOString();
|
|
154
|
+
if (!entry.firstUsed || iso < entry.firstUsed) entry.firstUsed = iso;
|
|
155
|
+
if (!entry.lastUsed || iso > entry.lastUsed) entry.lastUsed = iso;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildSessionDerivedStatistics(events = readSessionUsage()) {
|
|
160
|
+
const global = initAggregateEntry('DSH Sessions');
|
|
161
|
+
const byChannel = {};
|
|
162
|
+
const byModel = {};
|
|
163
|
+
events.forEach(event => {
|
|
164
|
+
if (!addUsage(global, event)) return;
|
|
165
|
+
const channel = event.channel || event.provider || 'dsh-session';
|
|
166
|
+
const model = event.model || 'unknown';
|
|
167
|
+
byChannel[channel] ||= initAggregateEntry(channel || 'DSH Session');
|
|
168
|
+
byModel[model] ||= initAggregateEntry(model);
|
|
169
|
+
addUsage(byChannel[channel], event);
|
|
170
|
+
addUsage(byModel[model], event);
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
version: '1.0',
|
|
174
|
+
source: global.requests ? 'sessions' : 'empty',
|
|
175
|
+
lastUpdated: global.lastUsed || new Date().toISOString(),
|
|
176
|
+
global: { totalRequests: global.requests, totalTokens: global.tokens.total, totalCost: global.cost },
|
|
177
|
+
byChannel: Object.fromEntries(Object.entries(byChannel).map(([key, value]) => [key, toLegacyEntryShape(value, true)])),
|
|
178
|
+
byModel: Object.fromEntries(Object.entries(byModel).map(([key, value]) => [key, toLegacyEntryShape(value)]))
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildSessionDerivedDailyStatistics(date, events = readSessionUsage()) {
|
|
183
|
+
const daily = buildSessionDerivedStatistics(events.filter(event => eventDateKey(event.timestamp) === date));
|
|
184
|
+
return {
|
|
185
|
+
date,
|
|
186
|
+
source: daily.source,
|
|
187
|
+
summary: {
|
|
188
|
+
requests: daily.global.totalRequests,
|
|
189
|
+
tokens: daily.global.totalTokens,
|
|
190
|
+
cost: daily.global.totalCost
|
|
191
|
+
},
|
|
192
|
+
byChannel: daily.byChannel,
|
|
193
|
+
byModel: daily.byModel
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function recordRequest(requestData = {}) {
|
|
198
|
+
const normalizedTokens = normalizeUsageTokens(TOOL_TYPE, requestData.tokens || {});
|
|
199
|
+
return recordSharedRequest({
|
|
200
|
+
...requestData,
|
|
201
|
+
toolType: TOOL_TYPE,
|
|
202
|
+
tokens: {
|
|
203
|
+
input: normalizedTokens.input,
|
|
204
|
+
output: normalizedTokens.output,
|
|
205
|
+
reasoning: normalizedTokens.reasoning,
|
|
206
|
+
cached: normalizedTokens.cached,
|
|
207
|
+
cacheCreation: normalizedTokens.cacheCreation,
|
|
208
|
+
cacheRead: normalizedTokens.cacheRead,
|
|
209
|
+
total: normalizedTokens.total
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getStatistics() {
|
|
215
|
+
const shared = buildSummaryStatistics(getSharedStatistics());
|
|
216
|
+
return hasSummaryData(shared) ? shared : buildSessionDerivedStatistics();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getDailyStatistics(date) {
|
|
220
|
+
const requestedDate = resolveDate(date);
|
|
221
|
+
const shared = buildDailyStatistics(getSharedDailyStatistics(requestedDate), requestedDate);
|
|
222
|
+
return hasDailyData(shared) ? shared : buildSessionDerivedDailyStatistics(requestedDate);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getTodayStatistics() {
|
|
226
|
+
const shared = buildDailyStatistics(getSharedTodayStatistics());
|
|
227
|
+
const date = shared.date || eventDateKey(Date.now());
|
|
228
|
+
return hasDailyData(shared) ? shared : buildSessionDerivedDailyStatistics(date);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = {
|
|
232
|
+
configure,
|
|
233
|
+
recordRequest,
|
|
234
|
+
getStatistics,
|
|
235
|
+
getDailyStatistics,
|
|
236
|
+
getTodayStatistics,
|
|
237
|
+
_test: { buildSessionDerivedStatistics, buildSessionDerivedDailyStatistics }
|
|
238
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createCapabilityDriver } = require('../../../shared/capability-driver');
|
|
4
|
+
|
|
5
|
+
function createDriver(context = {}) {
|
|
6
|
+
return createCapabilityDriver({
|
|
7
|
+
...context,
|
|
8
|
+
platform: 'dsh',
|
|
9
|
+
capability: 'statistics',
|
|
10
|
+
servicePath: './dsh/statistics-implementation',
|
|
11
|
+
localServicePath: '../platforms/drivers/dsh/statistics-implementation',
|
|
12
|
+
methods: {
|
|
13
|
+
summary: 'getStatistics',
|
|
14
|
+
daily: 'getDailyStatistics',
|
|
15
|
+
today: 'getTodayStatistics',
|
|
16
|
+
getStatistics: 'getStatistics',
|
|
17
|
+
getDailyStatistics: 'getDailyStatistics',
|
|
18
|
+
getTodayStatistics: 'getTodayStatistics',
|
|
19
|
+
recordRequest: 'recordRequest'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { createDriver };
|
|
@@ -298,6 +298,54 @@ async function inventory() {
|
|
|
298
298
|
return descriptors;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
async function summarize(descriptor) {
|
|
302
|
+
const header = readDescriptorMetadata(descriptor.filePath, descriptor.size);
|
|
303
|
+
let firstMessage = null;
|
|
304
|
+
let model = null;
|
|
305
|
+
try {
|
|
306
|
+
const fd = fs.openSync(descriptor.filePath, 'r');
|
|
307
|
+
try {
|
|
308
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
309
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
310
|
+
const lines = buffer.subarray(0, bytesRead).toString('utf8')
|
|
311
|
+
.split(/\r?\n/).filter(line => line.trim());
|
|
312
|
+
const records = [];
|
|
313
|
+
for (const line of lines) {
|
|
314
|
+
try { records.push(JSON.parse(line)); } catch (_) {}
|
|
315
|
+
}
|
|
316
|
+
const state = records.length === 1 && records[0]?.messages
|
|
317
|
+
? records[0]
|
|
318
|
+
: null;
|
|
319
|
+
const messages = state?.messages || records;
|
|
320
|
+
const firstUser = messages
|
|
321
|
+
.map(record => normalizeMessageRecord(record))
|
|
322
|
+
.find(message => message?.role === 'user' && message.content);
|
|
323
|
+
firstMessage = firstUser?.content || null;
|
|
324
|
+
model = messages.find(record => record?.model)?.model || null;
|
|
325
|
+
} finally {
|
|
326
|
+
fs.closeSync(fd);
|
|
327
|
+
}
|
|
328
|
+
} catch (_) {}
|
|
329
|
+
const projectPath = header.projectPath || descriptor.projectRoot || null;
|
|
330
|
+
const projectName = header.projectHash || descriptor.projectName
|
|
331
|
+
|| (projectPath ? getFilePathHash(projectPath) : 'unknown');
|
|
332
|
+
return {
|
|
333
|
+
session: {
|
|
334
|
+
sessionId: header.sessionId || descriptor.sessionId,
|
|
335
|
+
projectName,
|
|
336
|
+
projectDisplayName: projectPath ? path.basename(projectPath) : `Project ${String(projectName).slice(0, 8)}`,
|
|
337
|
+
projectFullPath: projectPath,
|
|
338
|
+
firstMessage,
|
|
339
|
+
gitBranch: null,
|
|
340
|
+
provider: null,
|
|
341
|
+
model,
|
|
342
|
+
startedAt: null,
|
|
343
|
+
updatedAt: descriptor.mtimeMs,
|
|
344
|
+
extraJson: JSON.stringify({ projectPath, storageName: path.basename(path.dirname(path.dirname(descriptor.filePath))) })
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
301
349
|
/**
|
|
302
350
|
* Parse a Gemini session file.
|
|
303
351
|
*/
|
|
@@ -341,4 +389,4 @@ async function parse(descriptor) {
|
|
|
341
389
|
return { session, messages };
|
|
342
390
|
}
|
|
343
391
|
|
|
344
|
-
module.exports = { configure, inventory, parse, extractContentText, normalizeMessageRecord, parseSessionContent };
|
|
392
|
+
module.exports = { configure, inventory, summarize, parse, extractContentText, normalizeMessageRecord, parseSessionContent };
|
|
@@ -649,7 +649,7 @@ function getProjectPath(projectHash, options = {}) {
|
|
|
649
649
|
* @returns {Array} 项目对象数组
|
|
650
650
|
*/
|
|
651
651
|
async function getProjects(options = {}) {
|
|
652
|
-
const projects = await getSessionHistoryIndex().
|
|
652
|
+
const projects = await getSessionHistoryIndex().getAllProjects('gemini', options);
|
|
653
653
|
const paths = buildPathMapping();
|
|
654
654
|
return projects.map(project => {
|
|
655
655
|
const fullPath = project.fullPath || paths.get(project.name) || null;
|
|
@@ -664,6 +664,25 @@ async function getProjects(options = {}) {
|
|
|
664
664
|
});
|
|
665
665
|
}
|
|
666
666
|
|
|
667
|
+
async function getProjectsPage(options = {}) {
|
|
668
|
+
const payload = await getSessionHistoryIndex().listProjectsPage('gemini', options);
|
|
669
|
+
const paths = buildPathMapping();
|
|
670
|
+
return {
|
|
671
|
+
...payload,
|
|
672
|
+
projects: payload.projects.map(project => {
|
|
673
|
+
const fullPath = project.fullPath || paths.get(project.name) || null;
|
|
674
|
+
return {
|
|
675
|
+
...project,
|
|
676
|
+
displayName: fullPath ? path.basename(fullPath) : project.displayName,
|
|
677
|
+
path: fullPath,
|
|
678
|
+
fullPath,
|
|
679
|
+
storageName: fullPath ? path.basename(fullPath) : project.name,
|
|
680
|
+
lastUpdated: project.lastUsed ? new Date(project.lastUsed).toISOString() : null
|
|
681
|
+
};
|
|
682
|
+
})
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
667
686
|
/**
|
|
668
687
|
* 获取指定项目的所有会话
|
|
669
688
|
* @param {string} projectHash - 项目 hash
|
|
@@ -690,6 +709,30 @@ async function getProjectSessions(projectHash, options = {}) {
|
|
|
690
709
|
}));
|
|
691
710
|
}
|
|
692
711
|
|
|
712
|
+
async function getProjectSessionsPage(projectHash, options = {}) {
|
|
713
|
+
const payload = await getSessionHistoryIndex().listSessionsPage('gemini', projectHash, options);
|
|
714
|
+
return {
|
|
715
|
+
...payload,
|
|
716
|
+
sessions: payload.sessions.map(session => ({
|
|
717
|
+
sessionId: session.sessionId,
|
|
718
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
719
|
+
size: session.size,
|
|
720
|
+
filePath: session.filePath,
|
|
721
|
+
gitBranch: null,
|
|
722
|
+
firstMessage: session.firstMessage,
|
|
723
|
+
forkedFrom: session.extra?.forkedFrom || null,
|
|
724
|
+
source: 'gemini',
|
|
725
|
+
tokens: session.tokens?.total ?? session.tokens ?? 0,
|
|
726
|
+
cost: session.extra?.cost || 0,
|
|
727
|
+
model: session.model || 'gemini-2.5-pro',
|
|
728
|
+
projectHash: session.projectName,
|
|
729
|
+
projectName: session.projectName,
|
|
730
|
+
storageName: session.extra?.storageName || session.projectName,
|
|
731
|
+
projectRoot: session.projectFullPath || null
|
|
732
|
+
}))
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
693
736
|
/**
|
|
694
737
|
* 获取单个会话的完整内容
|
|
695
738
|
* @param {string} sessionId - 会话 ID
|
|
@@ -936,7 +979,9 @@ module.exports = {
|
|
|
936
979
|
configure,
|
|
937
980
|
getAllSessions,
|
|
938
981
|
getProjects,
|
|
982
|
+
getProjectsPage,
|
|
939
983
|
getProjectSessions,
|
|
984
|
+
getProjectSessionsPage,
|
|
940
985
|
getSession,
|
|
941
986
|
getSessionById,
|
|
942
987
|
deleteSession,
|
|
@@ -11,9 +11,10 @@ function createDriver(context = {}) {
|
|
|
11
11
|
servicePath: './gemini/sessions-implementation',
|
|
12
12
|
localServicePath: '../platforms/drivers/gemini/sessions-implementation',
|
|
13
13
|
adapterLocalPath: '../platforms/drivers/gemini/session-history-adapter',
|
|
14
|
-
adapterMethods: { inventory: 'inventory', parse: 'parse' },
|
|
14
|
+
adapterMethods: { inventory: 'inventory', summarize: 'summarize', parse: 'parse' },
|
|
15
15
|
methods: {
|
|
16
16
|
listSessions: 'getProjectSessions',
|
|
17
|
+
listSessionsPage: 'getProjectSessionsPage',
|
|
17
18
|
recent: 'getRecentSessions',
|
|
18
19
|
getSessionById: 'getSessionById',
|
|
19
20
|
getAllSessions: 'getAllSessions',
|
|
@@ -194,6 +194,47 @@ async function inventory({ projectsDir } = {}) {
|
|
|
194
194
|
return descriptors;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
async function summarize(descriptor) {
|
|
198
|
+
let entries = [];
|
|
199
|
+
let fd;
|
|
200
|
+
try {
|
|
201
|
+
fd = fs.openSync(descriptor.filePath, 'r');
|
|
202
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
203
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
204
|
+
entries = buffer.subarray(0, bytesRead).toString('utf8')
|
|
205
|
+
.split(/\r?\n/).filter(line => line.trim()).map(line => {
|
|
206
|
+
try { return JSON.parse(line); } catch (_) { return null; }
|
|
207
|
+
}).filter(Boolean);
|
|
208
|
+
} catch (_) {
|
|
209
|
+
entries = [];
|
|
210
|
+
} finally {
|
|
211
|
+
if (fd !== undefined) {
|
|
212
|
+
try { fs.closeSync(fd); } catch (_) {}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const header = entries.find(entry => entry?.type === 'session') || {};
|
|
216
|
+
const messages = entries.map(convertOmpEntry).filter(Boolean);
|
|
217
|
+
const firstUser = messages.find(message => message.type === 'user' && message.content);
|
|
218
|
+
const modelChange = [...entries].reverse().find(entry => entry?.type === 'model_change');
|
|
219
|
+
const cwd = header.cwd || header.project?.cwd || '';
|
|
220
|
+
const sessionId = header.id || descriptor.sessionId || parseOmpSessionId(descriptor.filePath);
|
|
221
|
+
return {
|
|
222
|
+
session: {
|
|
223
|
+
sessionId,
|
|
224
|
+
projectName: cwd ? encodeProjectName(cwd) : 'unknown',
|
|
225
|
+
projectDisplayName: getDisplayName(cwd),
|
|
226
|
+
projectFullPath: cwd || null,
|
|
227
|
+
firstMessage: firstUser?.content || null,
|
|
228
|
+
gitBranch: header.gitBranch || null,
|
|
229
|
+
provider: modelChange?.provider || messages.find(message => message.provider)?.provider || null,
|
|
230
|
+
model: modelChange?.modelId || modelChange?.model || messages.find(message => message.model)?.model || null,
|
|
231
|
+
startedAt: header.timestamp ? new Date(header.timestamp).getTime() : null,
|
|
232
|
+
updatedAt: descriptor.mtimeMs,
|
|
233
|
+
extraJson: JSON.stringify({ cwd: cwd || null })
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
197
238
|
/**
|
|
198
239
|
* Parse an OMP session file.
|
|
199
240
|
*/
|
|
@@ -277,4 +318,4 @@ async function parse(descriptor) {
|
|
|
277
318
|
return { session, messages };
|
|
278
319
|
}
|
|
279
320
|
|
|
280
|
-
module.exports = { configure, inventory, parse, convertOmpEntry, parseUsage, encodeProjectName: encodeProjectName, getDisplayName, readJsonLines, extractMessageText, normalizeTextContent };
|
|
321
|
+
module.exports = { configure, inventory, summarize, parse, convertOmpEntry, parseUsage, encodeProjectName: encodeProjectName, getDisplayName, readJsonLines, extractMessageText, normalizeTextContent };
|
|
@@ -331,7 +331,7 @@ function scanSessionFiles(rootDir = getOmpSessionPaths().sessions) {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
async function getAllSessions(options = {}) {
|
|
334
|
-
const projects = await getSessionHistoryIndex().
|
|
334
|
+
const projects = await getSessionHistoryIndex().getAllProjects('omp', options);
|
|
335
335
|
const groups = await Promise.all(projects.map(project => getSessionHistoryIndex().listSessions('omp', project.name, options)));
|
|
336
336
|
return groups.flat().map(session => normalizeSession({
|
|
337
337
|
...session,
|
|
@@ -417,6 +417,24 @@ async function getProjects(options = {}) {
|
|
|
417
417
|
return projects;
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
async function getProjectsPage(options = {}) {
|
|
421
|
+
const payload = await getSessionHistoryIndex().listProjectsPage('omp', options);
|
|
422
|
+
return {
|
|
423
|
+
...payload,
|
|
424
|
+
projects: payload.projects.map(p => ({
|
|
425
|
+
name: p.name,
|
|
426
|
+
path: p.fullPath || p.path || '',
|
|
427
|
+
fullPath: p.fullPath || '',
|
|
428
|
+
displayName: p.displayName || p.name,
|
|
429
|
+
sessionCount: p.sessionCount || 0,
|
|
430
|
+
latestSession: p.latestSession || null,
|
|
431
|
+
mtime: p.lastUsed || null,
|
|
432
|
+
mtimeMs: p.lastUsed ? new Date(p.lastUsed).getTime() : 0,
|
|
433
|
+
source: 'omp'
|
|
434
|
+
}))
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
420
438
|
async function getSessionsByProject(projectName, options = {}) {
|
|
421
439
|
const idxSessions = await getSessionHistoryIndex().listSessions('omp', projectName, options);
|
|
422
440
|
const sessions = idxSessions.map(s => ({
|
|
@@ -450,6 +468,27 @@ async function getSessionsByProject(projectName, options = {}) {
|
|
|
450
468
|
return sessions.map(normalizeSession);
|
|
451
469
|
}
|
|
452
470
|
|
|
471
|
+
async function getSessionsPage(projectName, options = {}) {
|
|
472
|
+
const payload = await getSessionHistoryIndex().listSessionsPage('omp', projectName, options);
|
|
473
|
+
return {
|
|
474
|
+
...payload,
|
|
475
|
+
sessions: payload.sessions.map(s => normalizeSession({
|
|
476
|
+
sessionId: s.sessionId,
|
|
477
|
+
filePath: s.filePath,
|
|
478
|
+
firstMessage: s.firstMessage,
|
|
479
|
+
provider: s.provider,
|
|
480
|
+
model: s.model,
|
|
481
|
+
size: s.size,
|
|
482
|
+
mtime: new Date(s.mtime).toISOString(),
|
|
483
|
+
mtimeMs: s.mtime,
|
|
484
|
+
directory: s.projectFullPath,
|
|
485
|
+
cwd: s.projectFullPath,
|
|
486
|
+
messageCount: s.messageCount,
|
|
487
|
+
usage: s.tokens
|
|
488
|
+
}))
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
453
492
|
function normalizeSession(session) {
|
|
454
493
|
if (!session) return null;
|
|
455
494
|
return {
|
|
@@ -509,8 +548,15 @@ async function getRecentSessions(limit = 5) {
|
|
|
509
548
|
}));
|
|
510
549
|
}
|
|
511
550
|
|
|
512
|
-
async function searchSessions(keyword, contextLength = 35,
|
|
513
|
-
|
|
551
|
+
async function searchSessions(keyword, contextLength = 35, options = {}) {
|
|
552
|
+
const normalizedOptions = options && typeof options === 'object' && !Array.isArray(options)
|
|
553
|
+
? options
|
|
554
|
+
: { projectName: typeof options === 'string' ? options : null };
|
|
555
|
+
return getSessionHistoryIndex().searchSessions('omp', keyword, {
|
|
556
|
+
...normalizedOptions,
|
|
557
|
+
contextLength,
|
|
558
|
+
projectName: normalizedOptions.projectName || null
|
|
559
|
+
});
|
|
514
560
|
}
|
|
515
561
|
|
|
516
562
|
async function deleteSession(sessionId) {
|
|
@@ -611,10 +657,12 @@ module.exports = {
|
|
|
611
657
|
getOmpSessionPaths,
|
|
612
658
|
getProjectAndSessionCounts,
|
|
613
659
|
getProjects,
|
|
660
|
+
getProjectsPage,
|
|
614
661
|
getRecentSessions,
|
|
615
662
|
getSessionById,
|
|
616
663
|
getSessionMessages,
|
|
617
664
|
getSessionsByProject,
|
|
665
|
+
getSessionsPage,
|
|
618
666
|
isOmpInstalled: isOmpCliInstalled,
|
|
619
667
|
normalizeSession,
|
|
620
668
|
parseSessionFile,
|
|
@@ -11,12 +11,12 @@ function createDriver(context = {}) {
|
|
|
11
11
|
servicePath: './omp/sessions-implementation',
|
|
12
12
|
localServicePath: '../platforms/drivers/omp/sessions-implementation',
|
|
13
13
|
adapterLocalPath: '../platforms/drivers/omp/session-history-adapter',
|
|
14
|
-
adapterMethods: { inventory: 'inventory', parse: 'parse' },
|
|
14
|
+
adapterMethods: { inventory: 'inventory', summarize: 'summarize', parse: 'parse' },
|
|
15
15
|
methods: {
|
|
16
16
|
getProjects: 'getProjects',
|
|
17
17
|
listSessions: 'getSessionsByProject',
|
|
18
|
+
listSessionsPage: 'getSessionsPage',
|
|
18
19
|
recent: 'getRecentSessions',
|
|
19
|
-
search: 'searchSessions',
|
|
20
20
|
getSessionById: 'getSessionById',
|
|
21
21
|
messages: 'getSessionMessages',
|
|
22
22
|
delete: 'deleteSession',
|
|
@@ -25,6 +25,37 @@ function createDriver(context = {}) {
|
|
|
25
25
|
buildLaunchCommand: 'buildLaunchCommand',
|
|
26
26
|
status: 'getSessionStatus'
|
|
27
27
|
},
|
|
28
|
+
customMethods: {
|
|
29
|
+
search: (service, requestOrProjectName, keyword, contextLength, options = {}) => {
|
|
30
|
+
if (requestOrProjectName && typeof requestOrProjectName === 'object' && !Array.isArray(requestOrProjectName)) {
|
|
31
|
+
const request = requestOrProjectName;
|
|
32
|
+
const query = request.query || {};
|
|
33
|
+
return service.searchSessions(query.keyword || query.q || '', Number(query.context) || 15, {
|
|
34
|
+
...request,
|
|
35
|
+
...options,
|
|
36
|
+
projectName: request.params?.projectName || null,
|
|
37
|
+
limit: Number.parseInt(query.limit, 10) || 100
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return service.searchSessions(keyword, contextLength, {
|
|
41
|
+
...options,
|
|
42
|
+
projectName: requestOrProjectName
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
searchAcrossProjects: (service, requestOrKeyword, limit, options = {}) => {
|
|
46
|
+
if (requestOrKeyword && typeof requestOrKeyword === 'object' && !Array.isArray(requestOrKeyword)) {
|
|
47
|
+
const request = requestOrKeyword;
|
|
48
|
+
const query = request.query || {};
|
|
49
|
+
return service.searchSessions(query.keyword || query.q || '', Number(query.context) || 35, {
|
|
50
|
+
...request,
|
|
51
|
+
...options,
|
|
52
|
+
projectName: query.projectName || query.project || null,
|
|
53
|
+
limit: Number.parseInt(query.limit, 10) || 35
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return service.searchSessions(requestOrKeyword, Number(options.contextLength) || 35, { ...options, limit });
|
|
57
|
+
}
|
|
58
|
+
},
|
|
28
59
|
onSuccess: operation => {
|
|
29
60
|
if (['delete', 'fork', 'saveSessionOrder'].includes(operation)) {
|
|
30
61
|
context.sessionHistoryIndex?.invalidateSource('omp');
|