agentlytics 0.1.16 → 0.1.18

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/editors/codex.js CHANGED
@@ -376,9 +376,11 @@ function subtractRawUsage(current, previous) {
376
376
  }
377
377
 
378
378
  function convertToDelta(raw) {
379
+ const cacheRead = Math.min(raw.cached_input_tokens, raw.input_tokens);
380
+ const billableInput = Math.max(raw.input_tokens - cacheRead, 0);
379
381
  return {
380
- inputTokens: raw.input_tokens,
381
- cacheRead: Math.min(raw.cached_input_tokens, raw.input_tokens),
382
+ inputTokens: billableInput,
383
+ cacheRead,
382
384
  outputTokens: raw.output_tokens,
383
385
  totalTokens: raw.total_tokens > 0 ? raw.total_tokens : raw.input_tokens + raw.output_tokens,
384
386
  };
@@ -1,6 +1,7 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
3
  const os = require('os');
4
+ const Database = require('better-sqlite3');
4
5
 
5
6
  // OpenCode stores data in different locations depending on the platform
6
7
  // - Windows: %LOCALAPPDATA%\opencode\storage (not Roaming)
@@ -22,6 +23,118 @@ const SESSION_DIR = path.join(STORAGE_DIR, 'session');
22
23
  const MESSAGE_DIR = path.join(STORAGE_DIR, 'message');
23
24
  const PART_DIR = path.join(STORAGE_DIR, 'part');
24
25
 
26
+ // OpenCode also stores data in a SQLite database (older/primary store)
27
+ function getOpenCodeDbPath() {
28
+ const home = os.homedir();
29
+ switch (process.platform) {
30
+ case 'win32':
31
+ return path.join(home, 'AppData', 'Local', 'opencode', 'opencode.db');
32
+ case 'darwin':
33
+ case 'linux':
34
+ default:
35
+ return path.join(home, '.local', 'share', 'opencode', 'opencode.db');
36
+ }
37
+ }
38
+
39
+ const DB_PATH = getOpenCodeDbPath();
40
+
41
+ // ============================================================
42
+ // Query SQLite using better-sqlite3
43
+ // ============================================================
44
+
45
+ function queryDb(sql) {
46
+ if (!fs.existsSync(DB_PATH)) return [];
47
+ try {
48
+ const db = new Database(DB_PATH, { readonly: true });
49
+ const rows = db.prepare(sql).all();
50
+ db.close();
51
+ return rows;
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ function getSqliteSessions() {
58
+ return queryDb(
59
+ `SELECT s.id, s.title, s.directory, s.time_created, s.time_updated,
60
+ p.worktree, p.name as project_name,
61
+ (SELECT count(*) FROM message m WHERE m.session_id = s.id) as msg_count
62
+ FROM session s LEFT JOIN project p ON s.project_id = p.id
63
+ ORDER BY s.time_updated DESC`
64
+ );
65
+ }
66
+
67
+ function getSqliteMessages(sessionId) {
68
+ if (!fs.existsSync(DB_PATH)) return [];
69
+ try {
70
+ const db = new Database(DB_PATH, { readonly: true });
71
+ const messages = db.prepare(
72
+ `SELECT m.id as msg_id, m.data as msg_data, m.time_created
73
+ FROM message m WHERE m.session_id = ? ORDER BY m.time_created ASC`
74
+ ).all(sessionId);
75
+
76
+ const result = [];
77
+ for (const msg of messages) {
78
+ let msgData;
79
+ try { msgData = JSON.parse(msg.msg_data); } catch { continue; }
80
+
81
+ const role = msgData.role;
82
+ if (!role) continue;
83
+
84
+ const parts = db.prepare(
85
+ `SELECT data FROM part WHERE message_id = ? ORDER BY time_created ASC`
86
+ ).all(msg.msg_id);
87
+
88
+ const contentParts = [];
89
+ for (const part of parts) {
90
+ let partData;
91
+ try { partData = JSON.parse(part.data); } catch { continue; }
92
+ const type = partData.type;
93
+
94
+ if (type === 'text' && partData.text) {
95
+ contentParts.push(partData.text);
96
+ } else if (type === 'thinking' || type === 'reasoning') {
97
+ if (partData.text) contentParts.push(`[thinking] ${partData.text}`);
98
+ } else if (type === 'tool-call' || type === 'tool_use' || type === 'tool-use' || type === 'tool') {
99
+ const toolName = partData.name || partData.toolName || partData.tool || 'tool';
100
+ let argKeys = '';
101
+ try {
102
+ const input = typeof partData.input === 'string' ? JSON.parse(partData.input) : (partData.input || partData.args || partData.arguments || partData.state?.input || {});
103
+ argKeys = typeof input === 'object' ? Object.keys(input).join(', ') : '';
104
+ } catch {}
105
+ contentParts.push(`[tool-call: ${toolName}(${argKeys})]`);
106
+ } else if (type === 'tool-result' || type === 'tool_result') {
107
+ const preview = (partData.text || partData.output || partData.state?.output || '').substring(0, 500);
108
+ contentParts.push(`[tool-result] ${preview}`);
109
+ }
110
+ }
111
+
112
+ const content = contentParts.join('\n');
113
+ if (!content) continue;
114
+
115
+ let modelValue = null;
116
+ if (typeof msgData.modelID === 'string') {
117
+ modelValue = msgData.modelID;
118
+ } else if (msgData.model && typeof msgData.model === 'object' && msgData.model.modelID) {
119
+ modelValue = msgData.model.modelID;
120
+ } else if (typeof msgData.model === 'string') {
121
+ modelValue = msgData.model;
122
+ }
123
+
124
+ result.push({
125
+ role: role === 'user' ? 'user' : role === 'assistant' ? 'assistant' : role,
126
+ content,
127
+ _model: modelValue,
128
+ });
129
+ }
130
+
131
+ db.close();
132
+ return result;
133
+ } catch {
134
+ return [];
135
+ }
136
+ }
137
+
25
138
  // ============================================================
26
139
  // Scan JSON files from OpenCode storage
27
140
  // ============================================================
@@ -156,27 +269,64 @@ function getMessagesForSession(sessionId) {
156
269
  const name = 'opencode';
157
270
 
158
271
  function getChats() {
159
- const sessions = getAllSessions();
160
-
161
- return sessions.map(s => ({
162
- source: 'opencode',
163
- composerId: s.id,
164
- name: s.title || null,
165
- createdAt: s.time?.created || null,
166
- lastUpdatedAt: s.time?.updated || null,
167
- mode: s.mode || 'opencode',
168
- folder: s.directory || null,
169
- encrypted: false,
170
- bubbleCount: getMessageCount(s.id),
171
- _agent: s.agent,
172
- _model: s.modelID,
173
- _provider: s.providerID,
174
- _sessionData: s,
175
- })).sort((a, b) => (b.lastUpdatedAt || 0) - (a.lastUpdatedAt || 0));
272
+ const seen = new Set();
273
+ const chats = [];
274
+
275
+ // 1. JSON file-based sessions (newer storage format)
276
+ const fileSessions = getAllSessions();
277
+ for (const s of fileSessions) {
278
+ seen.add(s.id);
279
+ chats.push({
280
+ source: 'opencode',
281
+ composerId: s.id,
282
+ name: s.title || null,
283
+ createdAt: s.time?.created || null,
284
+ lastUpdatedAt: s.time?.updated || null,
285
+ mode: s.mode || 'opencode',
286
+ folder: s.directory || null,
287
+ encrypted: false,
288
+ bubbleCount: getMessageCount(s.id),
289
+ _agent: s.agent,
290
+ _model: s.modelID,
291
+ _provider: s.providerID,
292
+ _sessionData: s,
293
+ _storageType: 'file',
294
+ });
295
+ }
296
+
297
+ // 2. SQLite sessions (older/primary store) — add any not already found in files
298
+ const dbSessions = getSqliteSessions();
299
+ for (const row of dbSessions) {
300
+ if (seen.has(row.id)) continue;
301
+ seen.add(row.id);
302
+ chats.push({
303
+ source: 'opencode',
304
+ composerId: row.id,
305
+ name: cleanTitle(row.title),
306
+ createdAt: row.time_created || null,
307
+ lastUpdatedAt: row.time_updated || null,
308
+ mode: 'opencode',
309
+ folder: row.worktree || row.directory || null,
310
+ encrypted: false,
311
+ bubbleCount: row.msg_count || 0,
312
+ _storageType: 'sqlite',
313
+ });
314
+ }
315
+
316
+ return chats.sort((a, b) => (b.lastUpdatedAt || 0) - (a.lastUpdatedAt || 0));
317
+ }
318
+
319
+ function cleanTitle(title) {
320
+ if (!title) return null;
321
+ if (title.startsWith('New session - ')) return null;
322
+ return title.substring(0, 120) || null;
176
323
  }
177
324
 
178
325
  function getMessages(chat) {
179
- return getMessagesForSession(chat.composerId);
326
+ // Prefer file-based messages; fall back to SQLite
327
+ const fileMessages = getMessagesForSession(chat.composerId);
328
+ if (fileMessages.length > 0) return fileMessages;
329
+ return getSqliteMessages(chat.composerId);
180
330
  }
181
331
 
182
332
  const labels = { 'opencode': 'OpenCode' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlytics",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Comprehensive analytics dashboard for AI coding agents — Cursor, Windsurf, Claude Code, VS Code Copilot, Zed, Antigravity, OpenCode, Command Code",
5
5
  "main": "index.js",
6
6
  "bin": {
package/pricing.json CHANGED
@@ -6,7 +6,9 @@
6
6
  "OpenAI": "developers.openai.com/api/docs/pricing",
7
7
  "Google": "ai.google.dev/gemini-api/docs/pricing",
8
8
  "xAI": "docs.x.ai/developers/models",
9
- "DeepSeek": "api-docs.deepseek.com/quick_start/pricing"
9
+ "DeepSeek": "api-docs.deepseek.com/quick_start/pricing",
10
+ "Z.ai": "docs.z.ai/guides/overview/pricing",
11
+ "Zen": "opencode.ai/docs/zen/"
10
12
  },
11
13
  "lastVerified": "2026-03",
12
14
  "notes": {
@@ -818,5 +820,125 @@
818
820
  "output": 25,
819
821
  "cacheRead": 1.25,
820
822
  "cacheWrite": 0
823
+ },
824
+ "glm-5": {
825
+ "input": 1,
826
+ "output": 3.2,
827
+ "cacheRead": 0.2,
828
+ "cacheWrite": 0
829
+ },
830
+ "glm-5-code": {
831
+ "input": 1.2,
832
+ "output": 5,
833
+ "cacheRead": 0.3,
834
+ "cacheWrite": 0
835
+ },
836
+ "glm-4.7": {
837
+ "input": 0.6,
838
+ "output": 2.2,
839
+ "cacheRead": 0.11,
840
+ "cacheWrite": 0
841
+ },
842
+ "glm-4.7-flashx": {
843
+ "input": 0.07,
844
+ "output": 0.4,
845
+ "cacheRead": 0.01,
846
+ "cacheWrite": 0
847
+ },
848
+ "glm-4.6": {
849
+ "input": 0.6,
850
+ "output": 2.2,
851
+ "cacheRead": 0.11,
852
+ "cacheWrite": 0
853
+ },
854
+ "glm-4.5": {
855
+ "input": 0.6,
856
+ "output": 2.2,
857
+ "cacheRead": 0.11,
858
+ "cacheWrite": 0
859
+ },
860
+ "glm-4.5-x": {
861
+ "input": 2.2,
862
+ "output": 8.9,
863
+ "cacheRead": 0.45,
864
+ "cacheWrite": 0
865
+ },
866
+ "glm-4.5-air": {
867
+ "input": 0.2,
868
+ "output": 1.1,
869
+ "cacheRead": 0.03,
870
+ "cacheWrite": 0
871
+ },
872
+ "glm-4.5-airx": {
873
+ "input": 1.1,
874
+ "output": 4.5,
875
+ "cacheRead": 0.22,
876
+ "cacheWrite": 0
877
+ },
878
+ "glm-4-32b-0414-128k": {
879
+ "input": 0.1,
880
+ "output": 0.1,
881
+ "cacheRead": 0,
882
+ "cacheWrite": 0
883
+ },
884
+ "glm-4.7-flash": {
885
+ "input": 0,
886
+ "output": 0,
887
+ "cacheRead": 0,
888
+ "cacheWrite": 0
889
+ },
890
+ "glm-4.5-flash": {
891
+ "input": 0,
892
+ "output": 0,
893
+ "cacheRead": 0,
894
+ "cacheWrite": 0
895
+ },
896
+ "big-pickle": {
897
+ "input": 0,
898
+ "output": 0,
899
+ "cacheRead": 0,
900
+ "cacheWrite": 0
901
+ },
902
+ "minimax-m2.5-free": {
903
+ "input": 0,
904
+ "output": 0,
905
+ "cacheRead": 0,
906
+ "cacheWrite": 0
907
+ },
908
+ "minimax-m2.5": {
909
+ "input": 0.3,
910
+ "output": 1.2,
911
+ "cacheRead": 0.06,
912
+ "cacheWrite": 0.375
913
+ },
914
+ "minimax-m2.1": {
915
+ "input": 0.3,
916
+ "output": 1.2,
917
+ "cacheRead": 0.1,
918
+ "cacheWrite": 0
919
+ },
920
+ "kimi-k2.5": {
921
+ "input": 0.6,
922
+ "output": 3,
923
+ "cacheRead": 0.1,
924
+ "cacheWrite": 0
925
+ },
926
+ "kimi-k2-thinking": {
927
+ "input": 0.4,
928
+ "output": 2.5,
929
+ "cacheRead": 0,
930
+ "cacheWrite": 0
931
+ },
932
+ "kimi-k2": {
933
+ "input": 0.4,
934
+ "output": 2.5,
935
+ "cacheRead": 0,
936
+ "cacheWrite": 0
937
+ },
938
+ "qwen3-coder-480b": {
939
+ "input": 0.45,
940
+ "output": 1.5,
941
+ "cacheRead": 0,
942
+ "cacheWrite": 0
821
943
  }
822
944
  }