opencode-studio-server 1.0.13 → 1.1.2
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/index.js +108 -12
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -318,7 +318,6 @@ app.get('/api/plugins', (req, res) => {
|
|
|
318
318
|
|
|
319
319
|
for (const entry of entries) {
|
|
320
320
|
if (entry.isDirectory()) {
|
|
321
|
-
// Check for index.js or index.ts
|
|
322
321
|
const jsPath = path.join(pluginDir, entry.name, 'index.js');
|
|
323
322
|
const tsPath = path.join(pluginDir, entry.name, 'index.ts');
|
|
324
323
|
|
|
@@ -329,6 +328,12 @@ app.get('/api/plugins', (req, res) => {
|
|
|
329
328
|
enabled: true
|
|
330
329
|
});
|
|
331
330
|
}
|
|
331
|
+
} else if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.ts'))) {
|
|
332
|
+
plugins.push({
|
|
333
|
+
name: entry.name.replace(/\.(js|ts)$/, ''),
|
|
334
|
+
path: path.join(pluginDir, entry.name),
|
|
335
|
+
enabled: true
|
|
336
|
+
});
|
|
332
337
|
}
|
|
333
338
|
}
|
|
334
339
|
res.json(plugins);
|
|
@@ -350,6 +355,12 @@ app.get('/api/plugins/:name', (req, res) => {
|
|
|
350
355
|
} else if (fs.existsSync(path.join(dirPath, 'index.ts'))) {
|
|
351
356
|
content = fs.readFileSync(path.join(dirPath, 'index.ts'), 'utf8');
|
|
352
357
|
filename = 'index.ts';
|
|
358
|
+
} else if (fs.existsSync(path.join(pluginDir, name + '.js'))) {
|
|
359
|
+
content = fs.readFileSync(path.join(pluginDir, name + '.js'), 'utf8');
|
|
360
|
+
filename = name + '.js';
|
|
361
|
+
} else if (fs.existsSync(path.join(pluginDir, name + '.ts'))) {
|
|
362
|
+
content = fs.readFileSync(path.join(pluginDir, name + '.ts'), 'utf8');
|
|
363
|
+
filename = name + '.ts';
|
|
353
364
|
} else {
|
|
354
365
|
return res.status(404).json({ error: 'Plugin not found' });
|
|
355
366
|
}
|
|
@@ -384,6 +395,10 @@ app.delete('/api/plugins/:name', (req, res) => {
|
|
|
384
395
|
|
|
385
396
|
if (fs.existsSync(dirPath)) {
|
|
386
397
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
398
|
+
} else if (fs.existsSync(path.join(pluginDir, name + '.js'))) {
|
|
399
|
+
fs.unlinkSync(path.join(pluginDir, name + '.js'));
|
|
400
|
+
} else if (fs.existsSync(path.join(pluginDir, name + '.ts'))) {
|
|
401
|
+
fs.unlinkSync(path.join(pluginDir, name + '.ts'));
|
|
387
402
|
}
|
|
388
403
|
res.json({ success: true });
|
|
389
404
|
});
|
|
@@ -729,6 +744,7 @@ app.get('/api/debug/paths', (req, res) => {
|
|
|
729
744
|
|
|
730
745
|
app.get('/api/usage', async (req, res) => {
|
|
731
746
|
try {
|
|
747
|
+
const { projectId: filterProjectId, granularity = 'daily' } = req.query;
|
|
732
748
|
const home = os.homedir();
|
|
733
749
|
const candidatePaths = [
|
|
734
750
|
process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'opencode', 'storage', 'message') : null,
|
|
@@ -736,16 +752,60 @@ app.get('/api/usage', async (req, res) => {
|
|
|
736
752
|
path.join(home, '.opencode', 'storage', 'message')
|
|
737
753
|
].filter(p => p && fs.existsSync(p));
|
|
738
754
|
|
|
755
|
+
const getSessionDir = (messageDir) => {
|
|
756
|
+
return path.join(path.dirname(messageDir), 'session');
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const sessionProjectMap = new Map();
|
|
760
|
+
|
|
761
|
+
const loadProjects = (messageDir) => {
|
|
762
|
+
const sessionDir = getSessionDir(messageDir);
|
|
763
|
+
if (!fs.existsSync(sessionDir)) return;
|
|
764
|
+
|
|
765
|
+
try {
|
|
766
|
+
const projectDirs = fs.readdirSync(sessionDir);
|
|
767
|
+
|
|
768
|
+
for (const projDir of projectDirs) {
|
|
769
|
+
const fullProjPath = path.join(sessionDir, projDir);
|
|
770
|
+
if (!fs.statSync(fullProjPath).isDirectory()) continue;
|
|
771
|
+
|
|
772
|
+
const files = fs.readdirSync(fullProjPath);
|
|
773
|
+
for (const file of files) {
|
|
774
|
+
if (file.startsWith('ses_') && file.endsWith('.json')) {
|
|
775
|
+
const sessionId = file.replace('.json', '');
|
|
776
|
+
try {
|
|
777
|
+
const meta = JSON.parse(fs.readFileSync(path.join(fullProjPath, file), 'utf8'));
|
|
778
|
+
let projectName = 'Unknown Project';
|
|
779
|
+
if (meta.directory) {
|
|
780
|
+
projectName = path.basename(meta.directory);
|
|
781
|
+
} else if (meta.projectID) {
|
|
782
|
+
projectName = meta.projectID.substring(0, 8);
|
|
783
|
+
}
|
|
784
|
+
sessionProjectMap.set(sessionId, { name: projectName, id: meta.projectID || projDir });
|
|
785
|
+
} catch (e) {}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
} catch (e) {
|
|
790
|
+
console.error('Error loading projects:', e);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
for (const logDir of candidatePaths) {
|
|
795
|
+
loadProjects(logDir);
|
|
796
|
+
}
|
|
797
|
+
|
|
739
798
|
const stats = {
|
|
740
799
|
totalCost: 0,
|
|
741
800
|
totalTokens: 0,
|
|
742
801
|
byModel: {},
|
|
743
|
-
|
|
802
|
+
byTime: {},
|
|
803
|
+
byProject: {}
|
|
744
804
|
};
|
|
745
805
|
|
|
746
806
|
const processedFiles = new Set();
|
|
747
807
|
|
|
748
|
-
const processMessage = (filePath) => {
|
|
808
|
+
const processMessage = (filePath, sessionId) => {
|
|
749
809
|
if (processedFiles.has(filePath)) return;
|
|
750
810
|
processedFiles.add(filePath);
|
|
751
811
|
|
|
@@ -754,27 +814,62 @@ app.get('/api/usage', async (req, res) => {
|
|
|
754
814
|
const msg = JSON.parse(content);
|
|
755
815
|
|
|
756
816
|
const model = msg.modelID || (msg.model && (msg.model.modelID || msg.model.id)) || 'unknown';
|
|
817
|
+
const projectInfo = sessionProjectMap.get(sessionId) || { name: 'Unassigned', id: 'unknown' };
|
|
818
|
+
const projectId = projectInfo.id || 'unknown';
|
|
819
|
+
|
|
820
|
+
if (filterProjectId && filterProjectId !== 'all' && projectId !== filterProjectId) {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
757
823
|
|
|
758
824
|
if (msg.role === 'assistant' && msg.tokens) {
|
|
759
825
|
const cost = msg.cost || 0;
|
|
760
|
-
const
|
|
761
|
-
const
|
|
826
|
+
const inputTokens = msg.tokens.input || 0;
|
|
827
|
+
const outputTokens = msg.tokens.output || 0;
|
|
828
|
+
const tokens = inputTokens + outputTokens;
|
|
829
|
+
|
|
830
|
+
const timestamp = msg.time.created;
|
|
831
|
+
const dateObj = new Date(timestamp);
|
|
832
|
+
let timeKey;
|
|
833
|
+
|
|
834
|
+
if (granularity === 'hourly') {
|
|
835
|
+
timeKey = dateObj.toISOString().substring(0, 13) + ':00:00Z';
|
|
836
|
+
} else if (granularity === 'weekly') {
|
|
837
|
+
const day = dateObj.getDay();
|
|
838
|
+
const diff = dateObj.getDate() - day + (day === 0 ? -6 : 1);
|
|
839
|
+
const monday = new Date(dateObj.setDate(diff));
|
|
840
|
+
timeKey = monday.toISOString().split('T')[0];
|
|
841
|
+
} else {
|
|
842
|
+
timeKey = dateObj.toISOString().split('T')[0];
|
|
843
|
+
}
|
|
762
844
|
|
|
763
845
|
if (tokens > 0) {
|
|
764
846
|
stats.totalCost += cost;
|
|
765
847
|
stats.totalTokens += tokens;
|
|
766
848
|
|
|
767
849
|
if (!stats.byModel[model]) {
|
|
768
|
-
stats.byModel[model] = { name: model, cost: 0, tokens: 0 };
|
|
850
|
+
stats.byModel[model] = { name: model, cost: 0, tokens: 0, inputTokens: 0, outputTokens: 0 };
|
|
769
851
|
}
|
|
770
852
|
stats.byModel[model].cost += cost;
|
|
771
853
|
stats.byModel[model].tokens += tokens;
|
|
854
|
+
stats.byModel[model].inputTokens += inputTokens;
|
|
855
|
+
stats.byModel[model].outputTokens += outputTokens;
|
|
772
856
|
|
|
773
|
-
if (!stats.
|
|
774
|
-
stats.
|
|
857
|
+
if (!stats.byTime[timeKey]) {
|
|
858
|
+
stats.byTime[timeKey] = { date: timeKey, cost: 0, tokens: 0, inputTokens: 0, outputTokens: 0 };
|
|
859
|
+
}
|
|
860
|
+
stats.byTime[timeKey].cost += cost;
|
|
861
|
+
stats.byTime[timeKey].tokens += tokens;
|
|
862
|
+
stats.byTime[timeKey].inputTokens += inputTokens;
|
|
863
|
+
stats.byTime[timeKey].outputTokens += outputTokens;
|
|
864
|
+
|
|
865
|
+
const projectName = projectInfo.name;
|
|
866
|
+
if (!stats.byProject[projectId]) {
|
|
867
|
+
stats.byProject[projectId] = { id: projectId, name: projectName, cost: 0, tokens: 0, inputTokens: 0, outputTokens: 0 };
|
|
775
868
|
}
|
|
776
|
-
stats.
|
|
777
|
-
stats.
|
|
869
|
+
stats.byProject[projectId].cost += cost;
|
|
870
|
+
stats.byProject[projectId].tokens += tokens;
|
|
871
|
+
stats.byProject[projectId].inputTokens += inputTokens;
|
|
872
|
+
stats.byProject[projectId].outputTokens += outputTokens;
|
|
778
873
|
}
|
|
779
874
|
}
|
|
780
875
|
} catch (err) {
|
|
@@ -792,7 +887,7 @@ app.get('/api/usage', async (req, res) => {
|
|
|
792
887
|
const messages = fs.readdirSync(sessionDir);
|
|
793
888
|
for (const msgFile of messages) {
|
|
794
889
|
if (msgFile.endsWith('.json')) {
|
|
795
|
-
processMessage(path.join(sessionDir, msgFile));
|
|
890
|
+
processMessage(path.join(sessionDir, msgFile), session);
|
|
796
891
|
}
|
|
797
892
|
}
|
|
798
893
|
}
|
|
@@ -806,7 +901,8 @@ app.get('/api/usage', async (req, res) => {
|
|
|
806
901
|
totalCost: stats.totalCost,
|
|
807
902
|
totalTokens: stats.totalTokens,
|
|
808
903
|
byModel: Object.values(stats.byModel).sort((a, b) => b.cost - a.cost),
|
|
809
|
-
byDay: Object.values(stats.
|
|
904
|
+
byDay: Object.values(stats.byTime).sort((a, b) => a.date.localeCompare(b.date)),
|
|
905
|
+
byProject: Object.values(stats.byProject).sort((a, b) => b.cost - a.cost)
|
|
810
906
|
};
|
|
811
907
|
|
|
812
908
|
res.json(response);
|