govyn 0.0.1 → 0.2.5

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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -1
  3. package/configs/multi-provider.yaml +68 -0
  4. package/configs/openai-only.yaml +45 -0
  5. package/configs/team-setup.yaml +88 -0
  6. package/dist/action-logger.d.ts +128 -0
  7. package/dist/action-logger.js +356 -0
  8. package/dist/action-logger.js.map +1 -0
  9. package/dist/admin-cli.d.ts +2 -0
  10. package/dist/admin-cli.js +36 -0
  11. package/dist/admin-cli.js.map +1 -0
  12. package/dist/agents.d.ts +23 -0
  13. package/dist/agents.js +59 -0
  14. package/dist/agents.js.map +1 -0
  15. package/dist/alert-api.d.ts +14 -0
  16. package/dist/alert-api.js +355 -0
  17. package/dist/alert-api.js.map +1 -0
  18. package/dist/alert-manager.d.ts +77 -0
  19. package/dist/alert-manager.js +267 -0
  20. package/dist/alert-manager.js.map +1 -0
  21. package/dist/approval-api.d.ts +19 -0
  22. package/dist/approval-api.js +82 -0
  23. package/dist/approval-api.js.map +1 -0
  24. package/dist/approval-timeout.d.ts +29 -0
  25. package/dist/approval-timeout.js +45 -0
  26. package/dist/approval-timeout.js.map +1 -0
  27. package/dist/approval.d.ts +78 -0
  28. package/dist/approval.js +101 -0
  29. package/dist/approval.js.map +1 -0
  30. package/dist/auth.d.ts +47 -0
  31. package/dist/auth.js +335 -0
  32. package/dist/auth.js.map +1 -0
  33. package/dist/budget-api.d.ts +20 -0
  34. package/dist/budget-api.js +85 -0
  35. package/dist/budget-api.js.map +1 -0
  36. package/dist/budget-enforcer.d.ts +102 -0
  37. package/dist/budget-enforcer.js +294 -0
  38. package/dist/budget-enforcer.js.map +1 -0
  39. package/dist/cli.d.ts +15 -0
  40. package/dist/cli.js +200 -0
  41. package/dist/cli.js.map +1 -0
  42. package/dist/config.d.ts +15 -0
  43. package/dist/config.js +267 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/cost-aggregator.d.ts +69 -0
  46. package/dist/cost-aggregator.js +305 -0
  47. package/dist/cost-aggregator.js.map +1 -0
  48. package/dist/cost-api.d.ts +29 -0
  49. package/dist/cost-api.js +128 -0
  50. package/dist/cost-api.js.map +1 -0
  51. package/dist/database-url.d.ts +6 -0
  52. package/dist/database-url.js +47 -0
  53. package/dist/database-url.js.map +1 -0
  54. package/dist/db-retention.d.ts +53 -0
  55. package/dist/db-retention.js +82 -0
  56. package/dist/db-retention.js.map +1 -0
  57. package/dist/db-schema.d.ts +17 -0
  58. package/dist/db-schema.js +167 -0
  59. package/dist/db-schema.js.map +1 -0
  60. package/dist/db-writer.d.ts +55 -0
  61. package/dist/db-writer.js +115 -0
  62. package/dist/db-writer.js.map +1 -0
  63. package/dist/db.d.ts +33 -0
  64. package/dist/db.js +78 -0
  65. package/dist/db.js.map +1 -0
  66. package/dist/events.d.ts +77 -0
  67. package/dist/events.js +12 -0
  68. package/dist/events.js.map +1 -0
  69. package/dist/health.d.ts +14 -0
  70. package/dist/health.js +49 -0
  71. package/dist/health.js.map +1 -0
  72. package/dist/index.d.ts +7 -0
  73. package/dist/index.js +14 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/init-wizard.d.ts +12 -0
  76. package/dist/init-wizard.js +206 -0
  77. package/dist/init-wizard.js.map +1 -0
  78. package/dist/log-api.d.ts +20 -0
  79. package/dist/log-api.js +371 -0
  80. package/dist/log-api.js.map +1 -0
  81. package/dist/log-rotator.d.ts +55 -0
  82. package/dist/log-rotator.js +157 -0
  83. package/dist/log-rotator.js.map +1 -0
  84. package/dist/loop-detector.d.ts +71 -0
  85. package/dist/loop-detector.js +122 -0
  86. package/dist/loop-detector.js.map +1 -0
  87. package/dist/persistence-types.d.ts +165 -0
  88. package/dist/persistence-types.js +2 -0
  89. package/dist/persistence-types.js.map +1 -0
  90. package/dist/persistence.d.ts +185 -0
  91. package/dist/persistence.js +785 -0
  92. package/dist/persistence.js.map +1 -0
  93. package/dist/policy-api.d.ts +25 -0
  94. package/dist/policy-api.js +347 -0
  95. package/dist/policy-api.js.map +1 -0
  96. package/dist/policy-engine.d.ts +76 -0
  97. package/dist/policy-engine.js +835 -0
  98. package/dist/policy-engine.js.map +1 -0
  99. package/dist/policy-file.d.ts +10 -0
  100. package/dist/policy-file.js +52 -0
  101. package/dist/policy-file.js.map +1 -0
  102. package/dist/policy-parser.d.ts +21 -0
  103. package/dist/policy-parser.js +560 -0
  104. package/dist/policy-parser.js.map +1 -0
  105. package/dist/policy-types.d.ts +216 -0
  106. package/dist/policy-types.js +8 -0
  107. package/dist/policy-types.js.map +1 -0
  108. package/dist/policy-watcher.d.ts +54 -0
  109. package/dist/policy-watcher.js +116 -0
  110. package/dist/policy-watcher.js.map +1 -0
  111. package/dist/pricing.d.ts +69 -0
  112. package/dist/pricing.js +93 -0
  113. package/dist/pricing.js.map +1 -0
  114. package/dist/prompt.d.ts +6 -0
  115. package/dist/prompt.js +47 -0
  116. package/dist/prompt.js.map +1 -0
  117. package/dist/providers/anthropic.d.ts +18 -0
  118. package/dist/providers/anthropic.js +61 -0
  119. package/dist/providers/anthropic.js.map +1 -0
  120. package/dist/providers/custom.d.ts +19 -0
  121. package/dist/providers/custom.js +54 -0
  122. package/dist/providers/custom.js.map +1 -0
  123. package/dist/providers/openai.d.ts +17 -0
  124. package/dist/providers/openai.js +48 -0
  125. package/dist/providers/openai.js.map +1 -0
  126. package/dist/proxy.d.ts +57 -0
  127. package/dist/proxy.js +477 -0
  128. package/dist/proxy.js.map +1 -0
  129. package/dist/router.d.ts +23 -0
  130. package/dist/router.js +89 -0
  131. package/dist/router.js.map +1 -0
  132. package/dist/runtime.d.ts +1 -0
  133. package/dist/runtime.js +139 -0
  134. package/dist/runtime.js.map +1 -0
  135. package/dist/security.d.ts +64 -0
  136. package/dist/security.js +422 -0
  137. package/dist/security.js.map +1 -0
  138. package/dist/server.d.ts +33 -0
  139. package/dist/server.js +1147 -0
  140. package/dist/server.js.map +1 -0
  141. package/dist/sqlite-schema.d.ts +6 -0
  142. package/dist/sqlite-schema.js +134 -0
  143. package/dist/sqlite-schema.js.map +1 -0
  144. package/dist/streaming.d.ts +24 -0
  145. package/dist/streaming.js +63 -0
  146. package/dist/streaming.js.map +1 -0
  147. package/dist/tokens.d.ts +45 -0
  148. package/dist/tokens.js +237 -0
  149. package/dist/tokens.js.map +1 -0
  150. package/dist/types.d.ts +344 -0
  151. package/dist/types.js +5 -0
  152. package/dist/types.js.map +1 -0
  153. package/package.json +66 -2
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Log query API handler for the Govyn proxy server.
3
+ *
4
+ * Routes:
5
+ * GET /api/logs - List log entries with filtering and cursor-based pagination
6
+ * GET /api/logs/:id - Get a single log entry by ID
7
+ * GET /api/logs/:id/payload - Get stored payload content for a log entry
8
+ *
9
+ * Non-GET methods return 405. Logging disabled returns 503.
10
+ */
11
+ import type * as http from 'node:http';
12
+ import type { ActionLogger } from './action-logger.js';
13
+ /**
14
+ * Handle log API requests.
15
+ *
16
+ * @param req - The incoming HTTP request
17
+ * @param res - The outgoing HTTP response
18
+ * @param actionLogger - The ActionLogger instance (provides logDirectory and getPayloadPath)
19
+ */
20
+ export declare function handleLogApi(req: http.IncomingMessage, res: http.ServerResponse, actionLogger: ActionLogger): void;
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Log query API handler for the Govyn proxy server.
3
+ *
4
+ * Routes:
5
+ * GET /api/logs - List log entries with filtering and cursor-based pagination
6
+ * GET /api/logs/:id - Get a single log entry by ID
7
+ * GET /api/logs/:id/payload - Get stored payload content for a log entry
8
+ *
9
+ * Non-GET methods return 405. Logging disabled returns 503.
10
+ */
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ /**
14
+ * Send a JSON response.
15
+ */
16
+ function sendJson(res, statusCode, body) {
17
+ const bodyStr = JSON.stringify(body);
18
+ res.writeHead(statusCode, {
19
+ 'content-type': 'application/json',
20
+ 'content-length': Buffer.byteLength(bodyStr).toString(),
21
+ });
22
+ res.end(bodyStr);
23
+ }
24
+ /**
25
+ * Decode a cursor string to a file+line position.
26
+ * Cursor format: base64-encoded "{file}:{line}"
27
+ */
28
+ function decodeCursor(cursor) {
29
+ try {
30
+ const decoded = Buffer.from(cursor, 'base64').toString('utf8');
31
+ const colonIdx = decoded.lastIndexOf(':');
32
+ if (colonIdx === -1)
33
+ return null;
34
+ const file = decoded.slice(0, colonIdx);
35
+ const line = parseInt(decoded.slice(colonIdx + 1), 10);
36
+ if (isNaN(line))
37
+ return null;
38
+ return { file, line };
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ /**
45
+ * Encode a file+line position to a cursor string.
46
+ */
47
+ function encodeCursor(file, line) {
48
+ return Buffer.from(`${file}:${line}`).toString('base64');
49
+ }
50
+ /**
51
+ * Check whether a log entry matches the given filters.
52
+ */
53
+ function matchesFilters(entry, filters) {
54
+ if (filters.agent && entry.agent_id !== filters.agent)
55
+ return false;
56
+ if (filters.status !== undefined && entry.status !== filters.status)
57
+ return false;
58
+ if (filters.start && entry.timestamp < filters.start)
59
+ return false;
60
+ if (filters.end && entry.timestamp > filters.end)
61
+ return false;
62
+ if (filters.model && entry.model !== filters.model)
63
+ return false;
64
+ if (filters.provider && entry.provider !== filters.provider)
65
+ return false;
66
+ return true;
67
+ }
68
+ /**
69
+ * Read all JSONL files in the log directory, sorted newest-first by filename.
70
+ * Returns array of { filename, filepath } objects.
71
+ */
72
+ function getLogFiles(logDirectory) {
73
+ const resolvedDir = path.resolve(logDirectory);
74
+ if (!fs.existsSync(resolvedDir))
75
+ return [];
76
+ const files = fs.readdirSync(resolvedDir)
77
+ .filter((f) => f.endsWith('.jsonl'))
78
+ .sort((a, b) => b.localeCompare(a)); // Newest first (descending)
79
+ return files.map((filename) => ({
80
+ filename,
81
+ filepath: path.join(resolvedDir, filename),
82
+ }));
83
+ }
84
+ /**
85
+ * Parse JSONL file into an array of LogEntry objects.
86
+ * Skips malformed lines silently.
87
+ */
88
+ function parseJsonlFile(filepath) {
89
+ try {
90
+ const content = fs.readFileSync(filepath, 'utf8');
91
+ const lines = content.trim().split('\n').filter((l) => l.length > 0);
92
+ const entries = [];
93
+ for (const line of lines) {
94
+ try {
95
+ entries.push(JSON.parse(line));
96
+ }
97
+ catch {
98
+ // Skip malformed lines
99
+ }
100
+ }
101
+ return entries;
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ }
107
+ /**
108
+ * Handle log API requests.
109
+ *
110
+ * @param req - The incoming HTTP request
111
+ * @param res - The outgoing HTTP response
112
+ * @param actionLogger - The ActionLogger instance (provides logDirectory and getPayloadPath)
113
+ */
114
+ export function handleLogApi(req, res, actionLogger) {
115
+ const method = req.method ?? 'GET';
116
+ const rawUrl = req.url ?? '/api/logs';
117
+ // Handle DELETE for log purge
118
+ if (method === 'DELETE') {
119
+ handlePurge(req, res, actionLogger);
120
+ return;
121
+ }
122
+ // Only allow GET (and DELETE handled above)
123
+ if (method !== 'GET') {
124
+ sendJson(res, 405, {
125
+ error: {
126
+ type: 'method_not_allowed',
127
+ code: 'method_not_allowed',
128
+ message: 'Only GET and DELETE requests are supported for /api/logs',
129
+ details: { allowed_methods: ['GET', 'DELETE'] },
130
+ },
131
+ });
132
+ return;
133
+ }
134
+ // Parse URL
135
+ const parsedUrl = new URL(rawUrl, 'http://localhost');
136
+ const pathname = parsedUrl.pathname;
137
+ // Route: GET /api/logs/:id/payload
138
+ const payloadMatch = pathname.match(/^\/api\/logs\/([^/]+)\/payload$/);
139
+ if (payloadMatch) {
140
+ handleGetPayload(res, actionLogger, payloadMatch[1]);
141
+ return;
142
+ }
143
+ // Route: GET /api/logs/:id
144
+ const idMatch = pathname.match(/^\/api\/logs\/([^/]+)$/);
145
+ if (idMatch && idMatch[1] !== undefined) {
146
+ // Avoid matching the bare /api/logs path (idMatch[1] would be empty string with some regex variants)
147
+ const id = idMatch[1];
148
+ if (id.length > 0) {
149
+ handleGetById(res, actionLogger, id);
150
+ return;
151
+ }
152
+ }
153
+ // Route: GET /api/logs (list with filters)
154
+ handleList(res, actionLogger, parsedUrl.searchParams);
155
+ }
156
+ /**
157
+ * GET /api/logs - List log entries with filtering and cursor-based pagination.
158
+ */
159
+ function handleList(res, actionLogger, params) {
160
+ const logDir = actionLogger.logDirectory;
161
+ // Parse filters
162
+ const filters = {};
163
+ const agentParam = params.get('agent');
164
+ if (agentParam)
165
+ filters.agent = agentParam;
166
+ const statusParam = params.get('status');
167
+ if (statusParam) {
168
+ const statusNum = parseInt(statusParam, 10);
169
+ if (!isNaN(statusNum))
170
+ filters.status = statusNum;
171
+ }
172
+ const startParam = params.get('start');
173
+ if (startParam)
174
+ filters.start = startParam;
175
+ const endParam = params.get('end');
176
+ if (endParam)
177
+ filters.end = endParam;
178
+ const modelParam = params.get('model');
179
+ if (modelParam)
180
+ filters.model = modelParam;
181
+ const providerParam = params.get('provider');
182
+ if (providerParam)
183
+ filters.provider = providerParam;
184
+ // Parse pagination
185
+ const limitParam = params.get('limit');
186
+ let limit = 50;
187
+ if (limitParam) {
188
+ const parsed = parseInt(limitParam, 10);
189
+ if (!isNaN(parsed) && parsed > 0) {
190
+ limit = Math.min(parsed, 200);
191
+ }
192
+ }
193
+ const cursorParam = params.get('cursor');
194
+ const cursorPos = cursorParam ? decodeCursor(cursorParam) : null;
195
+ // Get log files (newest first)
196
+ const logFiles = getLogFiles(logDir);
197
+ const results = [];
198
+ let skipMode = cursorPos !== null;
199
+ let nextCursorFile = null;
200
+ let nextCursorLine = null;
201
+ let done = false;
202
+ for (const { filename, filepath } of logFiles) {
203
+ if (done)
204
+ break;
205
+ const entries = parseJsonlFile(filepath);
206
+ for (let lineIdx = 0; lineIdx < entries.length; lineIdx++) {
207
+ if (done)
208
+ break;
209
+ // If we have a cursor, skip until we reach the cursor position
210
+ if (skipMode) {
211
+ if (filename === cursorPos.file && lineIdx === cursorPos.line) {
212
+ skipMode = false;
213
+ // Fall through to process this entry (cursor points to first entry of next page)
214
+ }
215
+ else {
216
+ continue;
217
+ }
218
+ }
219
+ const entry = entries[lineIdx];
220
+ if (!matchesFilters(entry, filters))
221
+ continue;
222
+ if (results.length < limit) {
223
+ results.push(entry);
224
+ }
225
+ else {
226
+ // We have limit + 1 match — there's a next page
227
+ nextCursorFile = filename;
228
+ nextCursorLine = lineIdx;
229
+ done = true;
230
+ }
231
+ }
232
+ }
233
+ const hasMore = nextCursorFile !== null;
234
+ const cursor = hasMore ? encodeCursor(nextCursorFile, nextCursorLine) : null;
235
+ sendJson(res, 200, {
236
+ entries: results,
237
+ cursor,
238
+ has_more: hasMore,
239
+ });
240
+ }
241
+ /**
242
+ * GET /api/logs/:id - Get a single log entry by ID.
243
+ */
244
+ function handleGetById(res, actionLogger, id) {
245
+ const logDir = actionLogger.logDirectory;
246
+ const logFiles = getLogFiles(logDir);
247
+ for (const { filepath } of logFiles) {
248
+ const entries = parseJsonlFile(filepath);
249
+ for (const entry of entries) {
250
+ if (entry.id === id) {
251
+ sendJson(res, 200, entry);
252
+ return;
253
+ }
254
+ }
255
+ }
256
+ sendJson(res, 404, {
257
+ error: {
258
+ type: 'not_found',
259
+ code: 'log_entry_not_found',
260
+ message: `Log entry not found: ${id}`,
261
+ details: { id },
262
+ },
263
+ });
264
+ }
265
+ /**
266
+ * DELETE /api/logs?before=DATE - Purge log entries and payloads older than the given date.
267
+ */
268
+ function handlePurge(req, res, actionLogger) {
269
+ const rawUrl = req.url ?? '/api/logs';
270
+ const parsedUrl = new URL(rawUrl, 'http://localhost');
271
+ const beforeParam = parsedUrl.searchParams.get('before');
272
+ if (!beforeParam) {
273
+ sendJson(res, 400, {
274
+ error: {
275
+ type: 'invalid_request',
276
+ code: 'missing_parameter',
277
+ message: "Missing required 'before' query parameter",
278
+ details: { expected: 'ISO 8601 date string (e.g., 2024-01-15T00:00:00Z)' },
279
+ },
280
+ });
281
+ return;
282
+ }
283
+ const beforeDate = new Date(beforeParam);
284
+ if (isNaN(beforeDate.getTime())) {
285
+ sendJson(res, 400, {
286
+ error: {
287
+ type: 'invalid_request',
288
+ code: 'invalid_date',
289
+ message: `Invalid date format: '${beforeParam}'. Use ISO 8601 format.`,
290
+ details: { expected: 'ISO 8601 date string (e.g., 2024-01-15T00:00:00Z)' },
291
+ },
292
+ });
293
+ return;
294
+ }
295
+ const result = actionLogger.purgeBefore(beforeDate);
296
+ sendJson(res, 200, {
297
+ success: true,
298
+ deleted_logs: result.deletedLogs,
299
+ deleted_payloads: result.deletedPayloads,
300
+ });
301
+ }
302
+ /**
303
+ * GET /api/logs/:id/payload - Get stored payload for a log entry.
304
+ */
305
+ function handleGetPayload(res, actionLogger, id) {
306
+ const logDir = actionLogger.logDirectory;
307
+ const logFiles = getLogFiles(logDir);
308
+ // Find the log entry first
309
+ let entry = null;
310
+ for (const { filepath } of logFiles) {
311
+ const entries = parseJsonlFile(filepath);
312
+ for (const e of entries) {
313
+ if (e.id === id) {
314
+ entry = e;
315
+ break;
316
+ }
317
+ }
318
+ if (entry)
319
+ break;
320
+ }
321
+ if (!entry) {
322
+ sendJson(res, 404, {
323
+ error: {
324
+ type: 'not_found',
325
+ code: 'log_entry_not_found',
326
+ message: `Log entry not found: ${id}`,
327
+ details: { id },
328
+ },
329
+ });
330
+ return;
331
+ }
332
+ if (!entry.has_payload || !entry.payload_id) {
333
+ sendJson(res, 404, {
334
+ error: {
335
+ type: 'not_found',
336
+ code: 'no_payload',
337
+ message: 'No payload stored for this log entry',
338
+ details: { id },
339
+ },
340
+ });
341
+ return;
342
+ }
343
+ const payloadPath = actionLogger.getPayloadPath(entry.payload_id);
344
+ if (!fs.existsSync(payloadPath)) {
345
+ sendJson(res, 404, {
346
+ error: {
347
+ type: 'not_found',
348
+ code: 'payload_expired',
349
+ message: 'Payload file not found (may have been cleaned up by retention)',
350
+ details: { id, payload_id: entry.payload_id },
351
+ },
352
+ });
353
+ return;
354
+ }
355
+ try {
356
+ const content = fs.readFileSync(payloadPath, 'utf8');
357
+ const payload = JSON.parse(content);
358
+ sendJson(res, 200, payload);
359
+ }
360
+ catch {
361
+ sendJson(res, 500, {
362
+ error: {
363
+ type: 'internal_error',
364
+ code: 'payload_read_error',
365
+ message: 'Failed to read payload file',
366
+ details: { id, payload_id: entry.payload_id },
367
+ },
368
+ });
369
+ }
370
+ }
371
+ //# sourceMappingURL=log-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-api.js","sourceRoot":"","sources":["../src/log-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Log file rotation and retention for the Govyn proxy server.
3
+ *
4
+ * Handles:
5
+ * - Size-based rotation: rotates when a log file exceeds configured max size
6
+ * - Time-based rotation: rotates when a log file is older than the configured interval
7
+ * - Gzip compression: rotated files are compressed with zlib.gzipSync()
8
+ * - Retention cleanup: auto-deletes expired log files and payload files
9
+ *
10
+ * Design:
11
+ * - All rotation I/O is synchronous (runs inside flush() which is already on a timer)
12
+ * - Cleanup runs on a 1-hour unref'd interval (does not prevent process exit)
13
+ * - Cleanup failures never crash the process (wrapped in try/catch)
14
+ */
15
+ import type { LoggingConfig } from './types.js';
16
+ /**
17
+ * LogRotator manages log file rotation with gzip compression and
18
+ * configurable retention cleanup for both log files and payload files.
19
+ */
20
+ export declare class LogRotator {
21
+ private readonly config;
22
+ private cleanupInterval;
23
+ constructor(config: LoggingConfig);
24
+ /**
25
+ * Check whether the given log file should be rotated.
26
+ *
27
+ * @param currentFilePath - Path to the active JSONL log file
28
+ * @returns Whether rotation is needed and which trigger fired
29
+ */
30
+ checkRotation(currentFilePath: string): {
31
+ shouldRotate: boolean;
32
+ reason: 'size' | 'time' | null;
33
+ };
34
+ /**
35
+ * Rotate the given log file: compress with gzip, write to rotated path,
36
+ * and delete the original.
37
+ *
38
+ * @param currentFilePath - Path to the active JSONL log file to rotate
39
+ * @returns Path to a fresh log file (ActionLogger should create on next write)
40
+ */
41
+ rotate(currentFilePath: string): string;
42
+ /**
43
+ * Scan for and delete expired log files and payload files.
44
+ *
45
+ * - Log files (.jsonl.gz): deleted if older than retentionDays
46
+ * - Payload files (.json in payloads/): deleted if older than payloadRetentionDays
47
+ *
48
+ * Failures are logged to stderr but never crash the process.
49
+ */
50
+ cleanupExpired(): void;
51
+ /**
52
+ * Stop the periodic cleanup interval.
53
+ */
54
+ stop(): void;
55
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Log file rotation and retention for the Govyn proxy server.
3
+ *
4
+ * Handles:
5
+ * - Size-based rotation: rotates when a log file exceeds configured max size
6
+ * - Time-based rotation: rotates when a log file is older than the configured interval
7
+ * - Gzip compression: rotated files are compressed with zlib.gzipSync()
8
+ * - Retention cleanup: auto-deletes expired log files and payload files
9
+ *
10
+ * Design:
11
+ * - All rotation I/O is synchronous (runs inside flush() which is already on a timer)
12
+ * - Cleanup runs on a 1-hour unref'd interval (does not prevent process exit)
13
+ * - Cleanup failures never crash the process (wrapped in try/catch)
14
+ */
15
+ import * as fs from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import * as zlib from 'node:zlib';
18
+ /**
19
+ * LogRotator manages log file rotation with gzip compression and
20
+ * configurable retention cleanup for both log files and payload files.
21
+ */
22
+ export class LogRotator {
23
+ config;
24
+ cleanupInterval = null;
25
+ constructor(config) {
26
+ this.config = config;
27
+ // Run cleanup every hour; unref'd so it doesn't keep the process alive
28
+ this.cleanupInterval = setInterval(() => this.cleanupExpired(), 3600 * 1000);
29
+ this.cleanupInterval.unref();
30
+ }
31
+ /**
32
+ * Check whether the given log file should be rotated.
33
+ *
34
+ * @param currentFilePath - Path to the active JSONL log file
35
+ * @returns Whether rotation is needed and which trigger fired
36
+ */
37
+ checkRotation(currentFilePath) {
38
+ try {
39
+ const stat = fs.statSync(currentFilePath);
40
+ // Size check: file exceeds rotationMaxSizeMb
41
+ const maxBytes = this.config.rotationMaxSizeMb * 1024 * 1024;
42
+ if (stat.size > maxBytes) {
43
+ return { shouldRotate: true, reason: 'size' };
44
+ }
45
+ // Time check: file is older than rotationIntervalHours
46
+ const maxAge = this.config.rotationIntervalHours * 3600 * 1000;
47
+ const fileAge = Date.now() - stat.mtimeMs;
48
+ if (fileAge > maxAge) {
49
+ return { shouldRotate: true, reason: 'time' };
50
+ }
51
+ return { shouldRotate: false, reason: null };
52
+ }
53
+ catch {
54
+ // File doesn't exist or can't be read — no rotation needed
55
+ return { shouldRotate: false, reason: null };
56
+ }
57
+ }
58
+ /**
59
+ * Rotate the given log file: compress with gzip, write to rotated path,
60
+ * and delete the original.
61
+ *
62
+ * @param currentFilePath - Path to the active JSONL log file to rotate
63
+ * @returns Path to a fresh log file (ActionLogger should create on next write)
64
+ */
65
+ rotate(currentFilePath) {
66
+ const dir = path.dirname(currentFilePath);
67
+ const now = new Date();
68
+ const dateStr = now.toISOString().slice(0, 10);
69
+ const timeStr = now.toISOString().slice(11, 19).replace(/:/g, '');
70
+ // Rotated filename: govyn-YYYY-MM-DD-HHmmss.jsonl.gz
71
+ const rotatedName = `govyn-${dateStr}-${timeStr}.jsonl.gz`;
72
+ const rotatedPath = path.join(dir, rotatedName);
73
+ // Read, compress, write
74
+ const content = fs.readFileSync(currentFilePath);
75
+ const compressed = zlib.gzipSync(content);
76
+ fs.writeFileSync(rotatedPath, compressed);
77
+ // Delete original
78
+ fs.unlinkSync(currentFilePath);
79
+ // Return the path for the new current file (same date-based naming)
80
+ const freshDateStr = new Date().toISOString().slice(0, 10);
81
+ return path.join(dir, `govyn-${freshDateStr}.jsonl`);
82
+ }
83
+ /**
84
+ * Scan for and delete expired log files and payload files.
85
+ *
86
+ * - Log files (.jsonl.gz): deleted if older than retentionDays
87
+ * - Payload files (.json in payloads/): deleted if older than payloadRetentionDays
88
+ *
89
+ * Failures are logged to stderr but never crash the process.
90
+ */
91
+ cleanupExpired() {
92
+ try {
93
+ const logDir = path.resolve(this.config.directory);
94
+ let logsCleaned = 0;
95
+ let payloadsCleaned = 0;
96
+ // Clean up rotated log files (.jsonl.gz)
97
+ const maxLogAge = this.config.retentionDays * 24 * 3600 * 1000;
98
+ const now = Date.now();
99
+ if (fs.existsSync(logDir)) {
100
+ const files = fs.readdirSync(logDir);
101
+ for (const file of files) {
102
+ if (!file.endsWith('.jsonl.gz'))
103
+ continue;
104
+ const filePath = path.join(logDir, file);
105
+ try {
106
+ const stat = fs.statSync(filePath);
107
+ if (now - stat.mtimeMs > maxLogAge) {
108
+ fs.unlinkSync(filePath);
109
+ logsCleaned++;
110
+ }
111
+ }
112
+ catch {
113
+ // Skip individual file errors
114
+ }
115
+ }
116
+ }
117
+ // Clean up payload files (.json in payloads/)
118
+ const payloadsDir = path.join(logDir, 'payloads');
119
+ const maxPayloadAge = this.config.payloadRetentionDays * 24 * 3600 * 1000;
120
+ if (fs.existsSync(payloadsDir)) {
121
+ const payloadFiles = fs.readdirSync(payloadsDir);
122
+ for (const file of payloadFiles) {
123
+ if (!file.endsWith('.json'))
124
+ continue;
125
+ const filePath = path.join(payloadsDir, file);
126
+ try {
127
+ const stat = fs.statSync(filePath);
128
+ if (now - stat.mtimeMs > maxPayloadAge) {
129
+ fs.unlinkSync(filePath);
130
+ payloadsCleaned++;
131
+ }
132
+ }
133
+ catch {
134
+ // Skip individual file errors
135
+ }
136
+ }
137
+ }
138
+ if (logsCleaned > 0 || payloadsCleaned > 0) {
139
+ process.stderr.write(`[govyn] Cleaned up ${logsCleaned} expired log files, ${payloadsCleaned} expired payload files\n`);
140
+ }
141
+ }
142
+ catch (err) {
143
+ const message = err instanceof Error ? err.message : String(err);
144
+ process.stderr.write(`[govyn] Cleanup error: ${message}\n`);
145
+ }
146
+ }
147
+ /**
148
+ * Stop the periodic cleanup interval.
149
+ */
150
+ stop() {
151
+ if (this.cleanupInterval !== null) {
152
+ clearInterval(this.cleanupInterval);
153
+ this.cleanupInterval = null;
154
+ }
155
+ }
156
+ }
157
+ //# sourceMappingURL=log-rotator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-rotator.js","sourceRoot":"","sources":["../src/log-rotator.ts"],"names":[],"mappings":"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"}