sidekick-shared 0.21.2 → 0.21.4

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 (77) hide show
  1. package/README.md +60 -45
  2. package/dist/accountManager.js +9 -7
  3. package/dist/accountRegistry.d.ts +17 -0
  4. package/dist/accountRegistry.js +18 -14
  5. package/dist/accountStatus.js +7 -7
  6. package/dist/accounts.d.ts +13 -0
  7. package/dist/accounts.js +110 -25
  8. package/dist/aggregation/EventAggregator.d.ts +14 -11
  9. package/dist/aggregation/EventAggregator.js +68 -31
  10. package/dist/aggregation/PatternExtractor.js +5 -4
  11. package/dist/autoSwitch.js +6 -12
  12. package/dist/claudeProfiles.js +3 -3
  13. package/dist/codexProfiles.d.ts +15 -1
  14. package/dist/codexProfiles.js +96 -22
  15. package/dist/codexQuota.js +44 -16
  16. package/dist/codexQuotaWatcher.js +19 -4
  17. package/dist/context/composer.js +8 -6
  18. package/dist/context/sessionContext.js +12 -9
  19. package/dist/credentialIO.js +10 -8
  20. package/dist/extractors/sessionAssets.js +9 -1
  21. package/dist/extractors/sources/claudeAssets.js +7 -1
  22. package/dist/extractors/sources/codexAssets.js +14 -3
  23. package/dist/extractors/toolCall.js +1 -4
  24. package/dist/formatters/eventHighlighter.js +148 -28
  25. package/dist/formatters/noiseClassifier.js +3 -11
  26. package/dist/formatters/sessionDump.js +22 -13
  27. package/dist/formatters/toolSummary.js +1 -3
  28. package/dist/index.d.ts +26 -26
  29. package/dist/index.js +10 -4
  30. package/dist/modelContext.js +10 -8
  31. package/dist/modelInfo.js +11 -6
  32. package/dist/multiProviderQuotaService.d.ts +2 -2
  33. package/dist/multiProviderQuotaService.js +14 -10
  34. package/dist/parsers/codexParser.js +170 -103
  35. package/dist/parsers/debugLogParser.js +5 -4
  36. package/dist/parsers/openCodeParser.js +66 -48
  37. package/dist/parsers/planExtractor.js +3 -3
  38. package/dist/parsers/sessionActivityDetector.js +10 -6
  39. package/dist/parsers/sessionPathResolver.js +29 -34
  40. package/dist/parsers/subagentScanner.js +21 -20
  41. package/dist/parsers/subagentTraceParser.js +7 -4
  42. package/dist/phrases.js +11 -11
  43. package/dist/providerStatus.js +3 -4
  44. package/dist/providers/claudeCode.js +3 -1
  45. package/dist/providers/codex.js +35 -14
  46. package/dist/providers/codexDatabase.js +14 -5
  47. package/dist/providers/detect.js +10 -6
  48. package/dist/providers/openCode.js +91 -63
  49. package/dist/providers/openCodeDatabase.js +44 -17
  50. package/dist/quota.d.ts +19 -0
  51. package/dist/quota.js +50 -8
  52. package/dist/quotaHistory.js +10 -3
  53. package/dist/quotaSnapshots.js +2 -2
  54. package/dist/readers/decisions.js +1 -1
  55. package/dist/readers/notes.js +5 -3
  56. package/dist/readers/plans.js +43 -23
  57. package/dist/readers/tasks.js +1 -1
  58. package/dist/report/htmlHelpers.js +135 -16
  59. package/dist/report/htmlReportGenerator.js +38 -12
  60. package/dist/report/transcriptParser.js +8 -2
  61. package/dist/schemas/index.d.ts +4 -4
  62. package/dist/schemas/quota.js +2 -10
  63. package/dist/schemas/sessionEvent.js +20 -10
  64. package/dist/search/advancedFilter.js +3 -3
  65. package/dist/search/sessionSearch.js +10 -4
  66. package/dist/terminalSync.js +4 -2
  67. package/dist/turns/assistantTurn.js +5 -1
  68. package/dist/watchers/eventBridge.js +56 -26
  69. package/dist/watchers/factory.js +5 -2
  70. package/dist/watchers/jsonlTail.js +3 -1
  71. package/dist/watchers/jsonlWatcher.js +128 -52
  72. package/dist/watchers/providerReaderWatcher.js +3 -1
  73. package/dist/watchers/sqliteWatcher.js +52 -22
  74. package/dist/zaiQuota.js +6 -10
  75. package/dist/zaiQuotaApi.js +13 -5
  76. package/dist/zaiQuotaWatcher.js +1 -1
  77. package/package.json +5 -2
@@ -63,18 +63,30 @@ function normalizeMessage(msg) {
63
63
  const model = data.modelID || undefined;
64
64
  if (role === 'user') {
65
65
  events.push({
66
- providerId: 'opencode', type: 'user', timestamp: ts,
67
- summary: content || '(user message)', model, raw: data,
66
+ providerId: 'opencode',
67
+ type: 'user',
68
+ timestamp: ts,
69
+ summary: content || '(user message)',
70
+ model,
71
+ raw: data,
68
72
  });
69
73
  }
70
74
  else if (role === 'assistant') {
71
75
  events.push({
72
- providerId: 'opencode', type: 'assistant', timestamp: ts,
73
- summary: content || '(thinking...)', tokens, cost, model, raw: data,
76
+ providerId: 'opencode',
77
+ type: 'assistant',
78
+ timestamp: ts,
79
+ summary: content || '(thinking...)',
80
+ tokens,
81
+ cost,
82
+ model,
83
+ raw: data,
74
84
  });
75
85
  }
76
86
  }
77
- catch { /* skip malformed */ }
87
+ catch {
88
+ /* skip malformed */
89
+ }
78
90
  return events;
79
91
  }
80
92
  function normalizePart(part) {
@@ -93,13 +105,15 @@ function normalizePart(part) {
93
105
  const toolInput = state?.input;
94
106
  const input = data.args ? truncate(JSON.stringify(data.args), 80) : '';
95
107
  // Include input in raw so PlanExtractor can access raw.input
96
- const toolRaw = eventType === 'tool_use'
97
- ? { ...data, input: toolInput || {} }
98
- : data;
108
+ const toolRaw = eventType === 'tool_use' ? { ...data, input: toolInput || {} } : data;
99
109
  events.push({
100
- providerId: 'opencode', type: eventType, timestamp: ts,
110
+ providerId: 'opencode',
111
+ type: eventType,
112
+ timestamp: ts,
101
113
  summary: input ? `${toolName} ${input}` : toolName,
102
- toolName, toolInput: input || undefined, raw: toolRaw,
114
+ toolName,
115
+ toolInput: input || undefined,
116
+ raw: toolRaw,
103
117
  });
104
118
  }
105
119
  else if (type === 'text') {
@@ -107,7 +121,9 @@ function normalizePart(part) {
107
121
  if (text) {
108
122
  // Wrap raw to include message.content for <proposed_plan> extraction
109
123
  events.push({
110
- providerId: 'opencode', type: 'assistant', timestamp: ts,
124
+ providerId: 'opencode',
125
+ type: 'assistant',
126
+ timestamp: ts,
111
127
  summary: truncate(text, 200),
112
128
  raw: { ...data, message: { content: [{ type: 'text', text }] } },
113
129
  });
@@ -116,7 +132,9 @@ function normalizePart(part) {
116
132
  if (direction) {
117
133
  const syntheticToolName = direction === 'enter' ? 'EnterPlanMode' : 'ExitPlanMode';
118
134
  events.push({
119
- providerId: 'opencode', type: 'tool_use', timestamp: ts,
135
+ providerId: 'opencode',
136
+ type: 'tool_use',
137
+ timestamp: ts,
120
138
  summary: syntheticToolName,
121
139
  toolName: syntheticToolName,
122
140
  raw: { input: { source: 'opencode_text_heuristic' } },
@@ -125,7 +143,9 @@ function normalizePart(part) {
125
143
  }
126
144
  }
127
145
  }
128
- catch { /* skip malformed */ }
146
+ catch {
147
+ /* skip malformed */
148
+ }
129
149
  return events;
130
150
  }
131
151
  function extractContent(data) {
@@ -158,7 +178,9 @@ class SqliteSessionWatcher {
158
178
  this.callbacks = callbacks;
159
179
  this.db = new openCodeDatabase_1.OpenCodeDatabase(path.dirname(dbPath));
160
180
  }
161
- get isActive() { return this._isActive; }
181
+ get isActive() {
182
+ return this._isActive;
183
+ }
162
184
  /** Seek to a timestamp position. Must be called before start(). */
163
185
  seekTo(position) {
164
186
  this.lastMessageTime = position;
@@ -186,8 +208,12 @@ class SqliteSessionWatcher {
186
208
  this.skipToEnd();
187
209
  }
188
210
  // Watch db and wal files for changes
189
- this.watchFile(this.dbPath, (w) => { this.fsWatcher = w; });
190
- this.watchFile(this.dbPath + '-wal', (w) => { this.walWatcher = w; });
211
+ this.watchFile(this.dbPath, (w) => {
212
+ this.fsWatcher = w;
213
+ });
214
+ this.watchFile(this.dbPath + '-wal', (w) => {
215
+ this.walWatcher = w;
216
+ });
191
217
  // Fallback polling
192
218
  this.pollTimer = setInterval(() => {
193
219
  if (this._isActive)
@@ -223,10 +249,14 @@ class SqliteSessionWatcher {
223
249
  const watcher = fs.watch(filePath, { persistent: false }, () => {
224
250
  this.debouncedPoll();
225
251
  });
226
- watcher.on('error', () => { });
252
+ watcher.on('error', () => {
253
+ /* ignore watch errors */
254
+ });
227
255
  setter(watcher);
228
256
  }
229
- catch { /* fs.watch not available */ }
257
+ catch {
258
+ /* fs.watch not available */
259
+ }
230
260
  }
231
261
  debouncedPoll() {
232
262
  if (this.debounceTimer)
@@ -239,10 +269,10 @@ class SqliteSessionWatcher {
239
269
  const messages = this.db.getMessagesForSession(this.sessionId);
240
270
  const parts = this.db.getPartsForSession(this.sessionId);
241
271
  if (messages.length > 0) {
242
- this.lastMessageTime = Math.max(...messages.map(m => m.time_created));
272
+ this.lastMessageTime = Math.max(...messages.map((m) => m.time_created));
243
273
  }
244
274
  if (parts.length > 0) {
245
- this.lastPartTime = Math.max(...parts.map(p => p.time_created));
275
+ this.lastPartTime = Math.max(...parts.map((p) => p.time_created));
246
276
  }
247
277
  }
248
278
  pollNewData() {
@@ -252,7 +282,7 @@ class SqliteSessionWatcher {
252
282
  const messages = this.db.getMessagesForSession(this.sessionId);
253
283
  const parts = this.db.getPartsForSession(this.sessionId);
254
284
  // Emit new messages
255
- const newMessages = messages.filter(m => m.time_created > this.lastMessageTime);
285
+ const newMessages = messages.filter((m) => m.time_created > this.lastMessageTime);
256
286
  for (const msg of newMessages) {
257
287
  const events = normalizeMessage(msg);
258
288
  for (const e of events)
@@ -261,7 +291,7 @@ class SqliteSessionWatcher {
261
291
  this.lastMessageTime = msg.time_created;
262
292
  }
263
293
  // Emit new parts
264
- const newParts = parts.filter(p => p.time_created > this.lastPartTime);
294
+ const newParts = parts.filter((p) => p.time_created > this.lastPartTime);
265
295
  for (const part of newParts) {
266
296
  const events = normalizePart(part);
267
297
  for (const e of events)
package/dist/zaiQuota.js CHANGED
@@ -132,18 +132,14 @@ function resolveZaiTier(configured, accumulated) {
132
132
  function inferZaiQuotaState(accumulated, tier, options = {}) {
133
133
  const budget = exports.ZAI_TIER_BUDGETS[tier];
134
134
  const capturedAt = options.capturedAt ?? new Date().toISOString();
135
- const fiveHourUtil = budget.fiveHour > 0
136
- ? Math.min((accumulated.fiveHourPrompts / budget.fiveHour) * 100, 200)
137
- : 0;
138
- const weeklyUtil = budget.weekly > 0
139
- ? Math.min((accumulated.weeklyPrompts / budget.weekly) * 100, 200)
140
- : 0;
141
- const fiveHourResetsAt = options.authoritativeFiveHourResetAt
142
- ?? (accumulated.fiveHourStartedAtMs !== null
135
+ const fiveHourUtil = budget.fiveHour > 0 ? Math.min((accumulated.fiveHourPrompts / budget.fiveHour) * 100, 200) : 0;
136
+ const weeklyUtil = budget.weekly > 0 ? Math.min((accumulated.weeklyPrompts / budget.weekly) * 100, 200) : 0;
137
+ const fiveHourResetsAt = options.authoritativeFiveHourResetAt ??
138
+ (accumulated.fiveHourStartedAtMs !== null
143
139
  ? new Date(accumulated.fiveHourStartedAtMs + FIVE_HOUR_MS).toISOString()
144
140
  : '');
145
- const sevenDayResetsAt = options.authoritativeWeeklyResetAt
146
- ?? (accumulated.weeklyStartedAtMs !== null
141
+ const sevenDayResetsAt = options.authoritativeWeeklyResetAt ??
142
+ (accumulated.weeklyStartedAtMs !== null
147
143
  ? new Date(accumulated.weeklyStartedAtMs + SEVEN_DAY_MS).toISOString()
148
144
  : '');
149
145
  const fiveHour = {
@@ -11,6 +11,7 @@ const fs_1 = __importDefault(require("fs"));
11
11
  const os_1 = __importDefault(require("os"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const quotaSnapshots_1 = require("./quotaSnapshots");
14
+ const quota_1 = require("./quota");
14
15
  const DEFAULT_ZAI_BASE_URL = 'https://api.z.ai/api/anthropic';
15
16
  const QUOTA_PATH = '/api/monitor/usage/quota/limit';
16
17
  const DEFAULT_ACCOUNT_ID = 'default';
@@ -39,7 +40,9 @@ function openCodeDataDirCandidates() {
39
40
  candidates.push(path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'opencode'));
40
41
  }
41
42
  else if (process.platform === 'win32') {
42
- candidates.push(path_1.default.join(process.env.LOCALAPPDATA || process.env.APPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'opencode'));
43
+ candidates.push(path_1.default.join(process.env.LOCALAPPDATA ||
44
+ process.env.APPDATA ||
45
+ path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'opencode'));
43
46
  }
44
47
  return Array.from(new Set(candidates));
45
48
  }
@@ -129,14 +132,14 @@ function quotaStateFromZaiQuotaLimitPayload(payload, capturedAt = new Date().toI
129
132
  const data = payload;
130
133
  const tokenLimits = tokenLimitEntries(data);
131
134
  const fiveHourLimit = tokenLimits.find(isFiveHourLimit) ?? tokenLimits[0];
132
- const weeklyLimit = tokenLimits.find(isWeeklyLimit) ?? tokenLimits.find(item => item !== fiveHourLimit);
135
+ const weeklyLimit = tokenLimits.find(isWeeklyLimit) ?? tokenLimits.find((item) => item !== fiveHourLimit);
133
136
  const level = data.data?.level;
134
137
  if (!fiveHourLimit || !weeklyLimit) {
135
138
  return unavailableZaiQuotaState('z.ai quota API returned no token quota windows.', {
136
139
  failureKind: 'unknown',
137
140
  }, capturedAt);
138
141
  }
139
- return {
142
+ return (0, quota_1.withQuotaProjections)({
140
143
  fiveHour: {
141
144
  utilization: fiveHourLimit.percentage ?? 0,
142
145
  resetsAt: isoFromEpochMs(fiveHourLimit.nextResetTime),
@@ -154,7 +157,11 @@ function quotaStateFromZaiQuotaLimitPayload(payload, capturedAt = new Date().toI
154
157
  planType: level,
155
158
  limitId: level ? `zai-${level}` : 'zai-coding-plan',
156
159
  limitName: displayPlanName(level),
157
- };
160
+ }, {
161
+ fiveHourWindowMs: quota_1.FIVE_HOUR_WINDOW_MS,
162
+ sevenDayWindowMs: quota_1.SEVEN_DAY_WINDOW_MS,
163
+ capturedAt,
164
+ });
158
165
  }
159
166
  function parseRetryAfterMs(retryAfter) {
160
167
  if (!retryAfter)
@@ -239,8 +246,9 @@ async function fetchZaiQuotaFromApi(options = {}) {
239
246
  }
240
247
  }
241
248
  function enrichZaiQuota(state) {
249
+ const withProjections = (0, quota_1.withQuotaProjections)(state);
242
250
  return {
243
- ...state,
251
+ ...withProjections,
244
252
  runtimeProvider: 'zai',
245
253
  providerId: 'zai',
246
254
  fiveHourLabel: state.fiveHourLabel ?? '5-Hour',
@@ -251,7 +251,7 @@ class ZaiQuotaWatcher {
251
251
  const cutoff = this.now() - SEVEN_DAY_MS;
252
252
  if (this.turns.length === 0)
253
253
  return;
254
- const next = this.turns.filter(turn => turn.timestampMs >= cutoff);
254
+ const next = this.turns.filter((turn) => turn.timestampMs >= cutoff);
255
255
  if (next.length !== this.turns.length) {
256
256
  this.turns = next;
257
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-shared",
3
- "version": "0.21.2",
3
+ "version": "0.21.4",
4
4
  "description": "Shared data access layer for Sidekick — readers, types, providers, credentials, quota",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -55,6 +55,8 @@
55
55
  "clean": "rm -rf dist",
56
56
  "lint": "eslint src/",
57
57
  "lint:fix": "eslint src/ --fix",
58
+ "format": "prettier --config ../prettier.config.cjs --ignore-path ../.prettierignore --write \"src/**/*.{ts,tsx,js,jsx,json,md,css,html,yml,yaml}\" \"*.{js,cjs,mjs,json,md,yml,yaml}\"",
59
+ "format:check": "prettier --config ../prettier.config.cjs --ignore-path ../.prettierignore --check \"src/**/*.{ts,tsx,js,jsx,json,md,css,html,yml,yaml}\" \"*.{js,cjs,mjs,json,md,yml,yaml}\"",
58
60
  "pretest": "npm run build",
59
61
  "test": "vitest run",
60
62
  "test:watch": "vitest",
@@ -64,9 +66,10 @@
64
66
  "@eslint/js": "^9.0.0",
65
67
  "@types/node": "^22.0.0",
66
68
  "eslint": "^9.0.0",
69
+ "prettier": "3.8.4",
67
70
  "typescript": "^5.7.0",
68
71
  "typescript-eslint": "^8.0.0",
69
- "vitest": "^3.0.0"
72
+ "vitest": "^4.1.9"
70
73
  },
71
74
  "license": "MIT",
72
75
  "dependencies": {