manifest 5.33.20 → 5.34.0

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 (49) hide show
  1. package/README.md +1 -0
  2. package/dist/backend/analytics/services/messages-query.service.js +14 -23
  3. package/dist/backend/common/utils/ttl-cache.js +59 -0
  4. package/dist/backend/main.js +3 -3
  5. package/dist/backend/model-discovery/provider-model-fetcher.service.js +64 -117
  6. package/dist/backend/notifications/notifications.module.js +2 -0
  7. package/dist/backend/notifications/services/limit-check.service.js +18 -44
  8. package/dist/backend/notifications/services/notification-cron.service.js +20 -47
  9. package/dist/backend/notifications/services/notification-log.service.js +75 -0
  10. package/dist/backend/otlp/guards/agent-key-auth.guard.js +72 -68
  11. package/dist/backend/routing/proxy/anthropic-adapter.js +9 -1
  12. package/dist/backend/routing/proxy/google-adapter.js +14 -9
  13. package/dist/backend/routing/proxy/proxy-message-recorder.js +6 -5
  14. package/dist/backend/routing/proxy/proxy-response-handler.js +24 -4
  15. package/dist/backend/routing/proxy/proxy.controller.js +19 -6
  16. package/dist/backend/routing/routing-core/provider.service.js +6 -5
  17. package/dist/index.js +1 -1
  18. package/dist/openclaw.plugin.json +2 -2
  19. package/openclaw.plugin.json +2 -2
  20. package/package.json +1 -1
  21. package/public/assets/Account-Bmij8ZrS.js +1 -0
  22. package/public/assets/Limits-CJay2wDy.js +1 -0
  23. package/public/assets/Login-B6PWrEGq.js +1 -0
  24. package/public/assets/MessageLog-Cb_ucG8H.js +1 -0
  25. package/public/assets/ModelPrices-CK2tG0zg.js +1 -0
  26. package/public/assets/Overview-DY7f_oAi.js +1 -0
  27. package/public/assets/{Register-DdOFjnan.js → Register-CbGDtQku.js} +1 -1
  28. package/public/assets/{ResetPassword-BeAVlNfR.js → ResetPassword-DFQ1lTig.js} +1 -1
  29. package/public/assets/Routing-Kn93Tqlz.js +6 -0
  30. package/public/assets/Settings-BZw5xW8c.js +1 -0
  31. package/public/assets/{SocialButtons-CeoNbddT.js → SocialButtons-DEIg2R3v.js} +1 -1
  32. package/public/assets/index-BCCvBxnZ.js +2 -0
  33. package/public/assets/model-display-C23X2Y_y.js +1 -0
  34. package/public/assets/overview-DGoLsOFJ.js +1 -0
  35. package/public/assets/routing-C26EmiVE.js +1 -0
  36. package/public/assets/routing-utils-DGg0JMgE.js +1 -0
  37. package/public/index.html +1 -1
  38. package/public/assets/Account-CYm-TXzW.js +0 -1
  39. package/public/assets/Limits-COjHmsgO.js +0 -1
  40. package/public/assets/Login-BKfeXNh1.js +0 -1
  41. package/public/assets/MessageLog-ByDZNXuf.js +0 -1
  42. package/public/assets/ModelPrices-CkDhB6td.js +0 -1
  43. package/public/assets/Overview-i14orjyX.js +0 -1
  44. package/public/assets/ProviderIcon-C4FJfEjA.js +0 -1
  45. package/public/assets/Routing-DuIyJZaX.js +0 -6
  46. package/public/assets/Settings-0NuwmyZQ.js +0 -1
  47. package/public/assets/index-gTrwEb5v.js +0 -2
  48. package/public/assets/model-display-BM7OhbwK.js +0 -1
  49. package/public/assets/overview-DqdTBNGG.js +0 -1
package/README.md CHANGED
@@ -97,6 +97,7 @@ Works with 300+ models across these providers:
97
97
  | [Z.ai (Zhipu)](https://z.ai/) | `glm-5`, `glm-4.7`, `glm-4.5` + 5 more |
98
98
  | [OpenRouter](https://openrouter.ai/) | 300+ models from all providers |
99
99
  | [Ollama](https://ollama.com/) | Run any model locally (Llama, Gemma, Mistral, ...) |
100
+ | Custom providers | Any provider with an OpenAI-compatible API endpoint |
100
101
 
101
102
  ## Contributing
102
103
 
@@ -22,6 +22,7 @@ const query_helpers_1 = require("./query-helpers");
22
22
  const tenant_cache_service_1 = require("../../common/services/tenant-cache.service");
23
23
  const sql_dialect_1 = require("../../common/utils/sql-dialect");
24
24
  const provider_inference_1 = require("../../common/utils/provider-inference");
25
+ const ttl_cache_1 = require("../../common/utils/ttl-cache");
25
26
  const MODELS_CACHE_TTL_MS = 60_000;
26
27
  const COUNT_CACHE_TTL_MS = 30_000;
27
28
  const MAX_CACHE_ENTRIES = 5_000;
@@ -30,8 +31,14 @@ let MessagesQueryService = class MessagesQueryService {
30
31
  dataSource;
31
32
  tenantCache;
32
33
  dialect;
33
- modelsCache = new Map();
34
- countCache = new Map();
34
+ modelsCache = new ttl_cache_1.TtlCache({
35
+ maxSize: MAX_CACHE_ENTRIES,
36
+ ttlMs: MODELS_CACHE_TTL_MS,
37
+ });
38
+ countCache = new ttl_cache_1.TtlCache({
39
+ maxSize: MAX_CACHE_ENTRIES,
40
+ ttlMs: COUNT_CACHE_TTL_MS,
41
+ });
35
42
  constructor(turnRepo, dataSource, tenantCache) {
36
43
  this.turnRepo = turnRepo;
37
44
  this.dataSource = dataSource;
@@ -104,7 +111,7 @@ let MessagesQueryService = class MessagesQueryService {
104
111
  }
105
112
  }
106
113
  const cachedCount = params.cursor ? this.countCache.get(countCacheKey) : undefined;
107
- const countHit = cachedCount && cachedCount.expiresAt > Date.now();
114
+ const countHit = cachedCount !== undefined;
108
115
  const [countResult, rows, allModels] = await Promise.all([
109
116
  countHit ? null : countQb.getRawOne(),
110
117
  dataQb
@@ -116,11 +123,11 @@ let MessagesQueryService = class MessagesQueryService {
116
123
  ]);
117
124
  let totalCount;
118
125
  if (countHit) {
119
- totalCount = cachedCount.count;
126
+ totalCount = cachedCount;
120
127
  }
121
128
  else {
122
129
  totalCount = Number(countResult?.total ?? 0);
123
- this.setCountCache(countCacheKey, totalCount);
130
+ this.countCache.set(countCacheKey, totalCount);
124
131
  }
125
132
  const hasMore = rows.length > params.limit;
126
133
  const items = rows.slice(0, params.limit);
@@ -149,11 +156,8 @@ let MessagesQueryService = class MessagesQueryService {
149
156
  async getDistinctModels(userId, range, tenantId, agentName) {
150
157
  const cacheKey = `${userId}:${agentName ?? ''}:${range ?? 'all'}`;
151
158
  const cached = this.modelsCache.get(cacheKey);
152
- if (cached && cached.expiresAt > Date.now()) {
153
- return cached.models;
154
- }
155
159
  if (cached)
156
- this.modelsCache.delete(cacheKey);
160
+ return cached;
157
161
  const cutoff = range ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(range)) : undefined;
158
162
  const modelsQb = this.turnRepo
159
163
  .createQueryBuilder('at')
@@ -166,12 +170,7 @@ let MessagesQueryService = class MessagesQueryService {
166
170
  (0, query_helpers_1.addTenantFilter)(modelsQb, userId, agentName, tenantId);
167
171
  const modelsResult = await modelsQb.orderBy('at.model', 'ASC').getRawMany();
168
172
  const models = modelsResult.map((r) => String(r['model']));
169
- if (this.modelsCache.size >= MAX_CACHE_ENTRIES && !this.modelsCache.has(cacheKey)) {
170
- const firstKey = this.modelsCache.keys().next().value;
171
- if (firstKey !== undefined)
172
- this.modelsCache.delete(firstKey);
173
- }
174
- this.modelsCache.set(cacheKey, { models, expiresAt: Date.now() + MODELS_CACHE_TTL_MS });
173
+ this.modelsCache.set(cacheKey, models);
175
174
  return models;
176
175
  }
177
176
  buildCountCacheKey(params) {
@@ -185,14 +184,6 @@ let MessagesQueryService = class MessagesQueryService {
185
184
  params.cost_max ?? '',
186
185
  ].join(':');
187
186
  }
188
- setCountCache(key, count) {
189
- if (this.countCache.size >= MAX_CACHE_ENTRIES && !this.countCache.has(key)) {
190
- const firstKey = this.countCache.keys().next().value;
191
- if (firstKey !== undefined)
192
- this.countCache.delete(firstKey);
193
- }
194
- this.countCache.set(key, { count, expiresAt: Date.now() + COUNT_CACHE_TTL_MS });
195
- }
196
187
  };
197
188
  exports.MessagesQueryService = MessagesQueryService;
198
189
  exports.MessagesQueryService = MessagesQueryService = __decorate([
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TtlCache = void 0;
4
+ class TtlCache {
5
+ store = new Map();
6
+ maxSize;
7
+ ttlMs;
8
+ constructor(options) {
9
+ this.maxSize = options.maxSize;
10
+ this.ttlMs = options.ttlMs;
11
+ }
12
+ get(key) {
13
+ const entry = this.store.get(key);
14
+ if (!entry)
15
+ return undefined;
16
+ if (entry.expiresAt <= Date.now()) {
17
+ this.store.delete(key);
18
+ return undefined;
19
+ }
20
+ return entry.value;
21
+ }
22
+ set(key, value) {
23
+ if (this.store.size >= this.maxSize && !this.store.has(key)) {
24
+ const firstKey = this.store.keys().next().value;
25
+ if (firstKey !== undefined)
26
+ this.store.delete(firstKey);
27
+ }
28
+ this.store.set(key, { value, expiresAt: Date.now() + this.ttlMs });
29
+ }
30
+ delete(key) {
31
+ return this.store.delete(key);
32
+ }
33
+ has(key) {
34
+ const entry = this.store.get(key);
35
+ if (!entry)
36
+ return false;
37
+ if (entry.expiresAt <= Date.now()) {
38
+ this.store.delete(key);
39
+ return false;
40
+ }
41
+ return true;
42
+ }
43
+ clear() {
44
+ this.store.clear();
45
+ }
46
+ get size() {
47
+ return this.store.size;
48
+ }
49
+ evictExpired() {
50
+ const now = Date.now();
51
+ for (const [key, entry] of this.store) {
52
+ if (entry.expiresAt <= now) {
53
+ this.store.delete(key);
54
+ }
55
+ }
56
+ }
57
+ }
58
+ exports.TtlCache = TtlCache;
59
+ //# sourceMappingURL=ttl-cache.js.map
@@ -102,8 +102,8 @@ async function bootstrap() {
102
102
  });
103
103
  }
104
104
  else {
105
- const rateLimit = require('express-rate-limit');
106
- const loginLimiter = rateLimit.default({
105
+ const { default: rateLimit } = await Promise.resolve().then(() => __importStar(require('express-rate-limit')));
106
+ const loginLimiter = rateLimit({
107
107
  windowMs: 15 * 60 * 1000,
108
108
  max: 20,
109
109
  standardHeaders: true,
@@ -111,7 +111,7 @@ async function bootstrap() {
111
111
  message: { error: 'Too many login attempts. Try again later.' },
112
112
  });
113
113
  expressApp.use('/api/auth/sign-in', loginLimiter);
114
- const { toNodeHandler } = require('better-auth/node');
114
+ const { toNodeHandler } = await Promise.resolve().then(() => __importStar(require('better-auth/node')));
115
115
  expressApp.all('/api/auth/*splat', toNodeHandler(auth_instance_1.auth));
116
116
  }
117
117
  expressApp.use(express.json({ limit: '1mb' }));
@@ -17,57 +17,47 @@ const DEFAULT_CONTEXT_WINDOW = 128000;
17
17
  const ANTHROPIC_DEFAULT_CONTEXT = 200000;
18
18
  const GEMINI_DEFAULT_CONTEXT = 1000000;
19
19
  const MINIMAX_SUBSCRIPTION_MODELS_URL = 'https://api.minimax.io/anthropic/v1/models?limit=100';
20
- function parseOpenAI(body, provider) {
21
- const data = body?.data;
22
- if (!Array.isArray(data))
23
- return [];
24
- return data
25
- .filter((m) => {
26
- const entry = m;
27
- return typeof entry.id === 'string' && entry.id.length > 0;
28
- })
29
- .map((m) => {
30
- const entry = m;
31
- return {
32
- id: entry.id,
33
- displayName: entry.id,
34
- provider,
35
- contextWindow: DEFAULT_CONTEXT_WINDOW,
36
- inputPricePerToken: null,
37
- outputPricePerToken: null,
38
- capabilityReasoning: false,
39
- capabilityCode: false,
40
- qualityScore: 3,
41
- };
42
- });
20
+ function createModelParser(config) {
21
+ return (body, provider) => {
22
+ const arr = body?.[config.arrayKey];
23
+ if (!Array.isArray(arr))
24
+ return [];
25
+ return arr
26
+ .filter((m) => config.filter(m))
27
+ .map((m) => {
28
+ const entry = m;
29
+ const id = config.getId(entry);
30
+ const ctxVal = config.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
31
+ return {
32
+ id,
33
+ displayName: config.getDisplayName(entry, id),
34
+ provider,
35
+ contextWindow: typeof ctxVal === 'function' ? ctxVal(entry) : ctxVal,
36
+ inputPricePerToken: config.inputPricePerToken ?? null,
37
+ outputPricePerToken: config.outputPricePerToken ?? null,
38
+ capabilityReasoning: false,
39
+ capabilityCode: config.capabilityCode ?? false,
40
+ qualityScore: config.qualityScore ?? 3,
41
+ };
42
+ });
43
+ };
43
44
  }
45
+ const parseOpenAI = createModelParser({
46
+ arrayKey: 'data',
47
+ filter: (entry) => typeof entry.id === 'string' && entry.id.length > 0,
48
+ getId: (entry) => entry.id,
49
+ getDisplayName: (_entry, id) => id,
50
+ });
44
51
  function bearerHeaders(key) {
45
52
  return { Authorization: `Bearer ${key}` };
46
53
  }
47
- function parseAnthropic(body, provider) {
48
- const data = body?.data;
49
- if (!Array.isArray(data))
50
- return [];
51
- return data
52
- .filter((m) => {
53
- const entry = m;
54
- return typeof entry.id === 'string' && entry.type === 'model';
55
- })
56
- .map((m) => {
57
- const entry = m;
58
- return {
59
- id: entry.id,
60
- displayName: entry.display_name || entry.id,
61
- provider,
62
- contextWindow: ANTHROPIC_DEFAULT_CONTEXT,
63
- inputPricePerToken: null,
64
- outputPricePerToken: null,
65
- capabilityReasoning: false,
66
- capabilityCode: false,
67
- qualityScore: 3,
68
- };
69
- });
70
- }
54
+ const parseAnthropic = createModelParser({
55
+ arrayKey: 'data',
56
+ filter: (entry) => typeof entry.id === 'string' && entry.type === 'model',
57
+ getId: (entry) => entry.id,
58
+ getDisplayName: (entry, id) => entry.display_name || id,
59
+ contextWindow: ANTHROPIC_DEFAULT_CONTEXT,
60
+ });
71
61
  function parseGemini(body, provider) {
72
62
  const models = body?.models;
73
63
  if (!Array.isArray(models))
@@ -128,76 +118,33 @@ function parseOpenRouter(body, provider) {
128
118
  };
129
119
  });
130
120
  }
131
- function parseOllama(body, provider) {
132
- const models = body?.models;
133
- if (!Array.isArray(models))
134
- return [];
135
- return models
136
- .filter((m) => typeof m.name === 'string')
137
- .map((m) => {
138
- const entry = m;
139
- const id = entry.name.replace(/:latest$/, '');
140
- return {
141
- id,
142
- displayName: id,
143
- provider,
144
- contextWindow: DEFAULT_CONTEXT_WINDOW,
145
- inputPricePerToken: 0,
146
- outputPricePerToken: 0,
147
- capabilityReasoning: false,
148
- capabilityCode: false,
149
- qualityScore: 2,
150
- };
151
- });
152
- }
153
- function parseOpenaiSubscription(body, provider) {
154
- const data = body?.models;
155
- if (!Array.isArray(data))
156
- return [];
157
- return data
158
- .filter((m) => {
159
- const entry = m;
160
- return typeof entry.slug === 'string' && entry.visibility === 'list';
161
- })
162
- .map((m) => {
163
- const entry = m;
164
- return {
165
- id: entry.slug,
166
- displayName: entry.display_name || entry.slug,
167
- provider,
168
- contextWindow: entry.context_window ?? 200000,
169
- inputPricePerToken: 0,
170
- outputPricePerToken: 0,
171
- capabilityReasoning: false,
172
- capabilityCode: true,
173
- qualityScore: 3,
174
- };
175
- });
176
- }
177
- function parseCopilot(body, provider) {
178
- const data = body?.data;
179
- if (!Array.isArray(data))
180
- return [];
181
- return data
182
- .filter((m) => {
183
- const entry = m;
184
- return typeof entry.id === 'string' && entry.id.length > 0;
185
- })
186
- .map((m) => {
187
- const entry = m;
188
- return {
189
- id: `copilot/${entry.id}`,
190
- displayName: entry.id,
191
- provider,
192
- contextWindow: DEFAULT_CONTEXT_WINDOW,
193
- inputPricePerToken: 0,
194
- outputPricePerToken: 0,
195
- capabilityReasoning: false,
196
- capabilityCode: false,
197
- qualityScore: 3,
198
- };
199
- });
200
- }
121
+ const parseOllama = createModelParser({
122
+ arrayKey: 'models',
123
+ filter: (entry) => typeof entry.name === 'string',
124
+ getId: (entry) => entry.name.replace(/:latest$/, ''),
125
+ getDisplayName: (_entry, id) => id,
126
+ inputPricePerToken: 0,
127
+ outputPricePerToken: 0,
128
+ qualityScore: 2,
129
+ });
130
+ const parseOpenaiSubscription = createModelParser({
131
+ arrayKey: 'models',
132
+ filter: (entry) => typeof entry.slug === 'string' && entry.visibility === 'list',
133
+ getId: (entry) => entry.slug,
134
+ getDisplayName: (entry, id) => entry.display_name || id,
135
+ contextWindow: (entry) => entry.context_window ?? 200000,
136
+ inputPricePerToken: 0,
137
+ outputPricePerToken: 0,
138
+ capabilityCode: true,
139
+ });
140
+ const parseCopilot = createModelParser({
141
+ arrayKey: 'data',
142
+ filter: (entry) => typeof entry.id === 'string' && entry.id.length > 0,
143
+ getId: (entry) => `copilot/${entry.id}`,
144
+ getDisplayName: (entry) => entry.id,
145
+ inputPricePerToken: 0,
146
+ outputPricePerToken: 0,
147
+ });
201
148
  exports.PROVIDER_CONFIGS = {
202
149
  openai: {
203
150
  endpoint: 'https://api.openai.com/v1/models',
@@ -13,6 +13,7 @@ const notification_rules_service_1 = require("./services/notification-rules.serv
13
13
  const notification_cron_service_1 = require("./services/notification-cron.service");
14
14
  const notification_email_service_1 = require("./services/notification-email.service");
15
15
  const email_provider_config_service_1 = require("./services/email-provider-config.service");
16
+ const notification_log_service_1 = require("./services/notification-log.service");
16
17
  const limit_check_service_1 = require("./services/limit-check.service");
17
18
  let NotificationsModule = class NotificationsModule {
18
19
  };
@@ -25,6 +26,7 @@ exports.NotificationsModule = NotificationsModule = __decorate([
25
26
  notification_cron_service_1.NotificationCronService,
26
27
  notification_email_service_1.NotificationEmailService,
27
28
  email_provider_config_service_1.EmailProviderConfigService,
29
+ notification_log_service_1.NotificationLogService,
28
30
  limit_check_service_1.LimitCheckService,
29
31
  ],
30
32
  exports: [limit_check_service_1.LimitCheckService],
@@ -12,38 +12,33 @@ var LimitCheckService_1;
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.LimitCheckService = void 0;
14
14
  const common_1 = require("@nestjs/common");
15
- const typeorm_1 = require("typeorm");
16
- const uuid_1 = require("uuid");
17
15
  const notification_rules_service_1 = require("./notification-rules.service");
18
16
  const notification_email_service_1 = require("./notification-email.service");
19
17
  const email_provider_config_service_1 = require("./email-provider-config.service");
18
+ const notification_log_service_1 = require("./notification-log.service");
20
19
  const ingest_event_bus_service_1 = require("../../common/services/ingest-event-bus.service");
21
20
  const manifest_runtime_service_1 = require("../../common/services/manifest-runtime.service");
22
21
  const period_util_1 = require("../../common/utils/period.util");
23
- const sql_dialect_1 = require("../../common/utils/sql-dialect");
24
- const local_mode_constants_1 = require("../../common/constants/local-mode.constants");
25
22
  const CACHE_TTL_MS = 60_000;
26
23
  const MAX_CACHE_SIZE = 10_000;
27
24
  let LimitCheckService = LimitCheckService_1 = class LimitCheckService {
28
- ds;
29
25
  rulesService;
30
26
  emailService;
31
27
  emailProviderConfig;
32
28
  ingestBus;
33
29
  runtime;
30
+ notificationLog;
34
31
  logger = new common_1.Logger(LimitCheckService_1.name);
35
- dialect;
36
32
  rulesCache = new Map();
37
33
  consumptionCache = new Map();
38
34
  ingestSub;
39
- constructor(ds, rulesService, emailService, emailProviderConfig, ingestBus, runtime) {
40
- this.ds = ds;
35
+ constructor(rulesService, emailService, emailProviderConfig, ingestBus, runtime, notificationLog) {
41
36
  this.rulesService = rulesService;
42
37
  this.emailService = emailService;
43
38
  this.emailProviderConfig = emailProviderConfig;
44
39
  this.ingestBus = ingestBus;
45
40
  this.runtime = runtime;
46
- this.dialect = (0, sql_dialect_1.detectDialect)(ds.options.type);
41
+ this.notificationLog = notificationLog;
47
42
  }
48
43
  onModuleInit() {
49
44
  this.ingestSub = this.ingestBus.all().subscribe(() => {
@@ -53,9 +48,6 @@ let LimitCheckService = LimitCheckService_1 = class LimitCheckService {
53
48
  onModuleDestroy() {
54
49
  this.ingestSub?.unsubscribe();
55
50
  }
56
- sql(query) {
57
- return (0, sql_dialect_1.portableSql)(query, this.dialect);
58
- }
59
51
  async checkLimits(tenantId, agentName) {
60
52
  const rules = await this.getCachedRules(tenantId, agentName);
61
53
  if (rules.length === 0)
@@ -88,12 +80,11 @@ let LimitCheckService = LimitCheckService_1 = class LimitCheckService {
88
80
  }
89
81
  }
90
82
  async notifyLimitExceeded(rule, actual, periodStart, periodEnd) {
91
- const alreadySent = await this.ds.query(this.sql(`SELECT 1 FROM notification_logs WHERE rule_id = $1 AND period_start = $2`), [rule.id, periodStart]);
92
- if (alreadySent.length > 0)
83
+ if (await this.notificationLog.hasAlreadySent(rule.id, periodStart))
93
84
  return;
94
- const now = new Date().toISOString().replace('T', ' ').replace('Z', '').slice(0, 19);
85
+ const now = (0, notification_log_service_1.formatNotificationTimestamp)();
95
86
  const providerConfig = await this.emailProviderConfig.getFullConfig(rule.user_id);
96
- const email = await this.resolveUserEmail(rule.user_id, providerConfig);
87
+ const email = await this.notificationLog.resolveUserEmail(rule.user_id, providerConfig?.notificationEmail);
97
88
  let emailSent = false;
98
89
  if (email) {
99
90
  emailSent = await this.emailService.sendThresholdAlert(email, {
@@ -109,34 +100,17 @@ let LimitCheckService = LimitCheckService_1 = class LimitCheckService {
109
100
  }, providerConfig ?? undefined);
110
101
  }
111
102
  if (emailSent || !email) {
112
- await this.ds.query(this.sql(`INSERT INTO notification_logs
113
- (id, rule_id, period_start, period_end, actual_value, threshold_value, metric_type, agent_name, sent_at)
114
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`), [
115
- (0, uuid_1.v4)(),
116
- rule.id,
103
+ await this.notificationLog.insertLog({
104
+ ruleId: rule.id,
117
105
  periodStart,
118
106
  periodEnd,
119
- actual,
120
- rule.threshold,
121
- rule.metric_type,
122
- rule.agent_name,
123
- now,
124
- ]);
125
- }
126
- }
127
- async resolveUserEmail(userId, fullConfig) {
128
- if (fullConfig?.notificationEmail)
129
- return fullConfig.notificationEmail;
130
- if (this.runtime.isLocalMode()) {
131
- const configEmail = (0, local_mode_constants_1.readLocalNotificationEmail)();
132
- if (configEmail)
133
- return configEmail;
107
+ actualValue: actual,
108
+ thresholdValue: rule.threshold,
109
+ metricType: rule.metric_type,
110
+ agentName: rule.agent_name,
111
+ sentAt: now,
112
+ });
134
113
  }
135
- const rows = await this.ds.query(this.sql(`SELECT email FROM "user" WHERE id = $1`), [userId]);
136
- const email = rows[0]?.email ?? null;
137
- if (email === local_mode_constants_1.LOCAL_EMAIL)
138
- return null;
139
- return email;
140
114
  }
141
115
  async getCachedRules(tenantId, agentName) {
142
116
  const key = `${tenantId}:${agentName}`;
@@ -179,11 +153,11 @@ let LimitCheckService = LimitCheckService_1 = class LimitCheckService {
179
153
  exports.LimitCheckService = LimitCheckService;
180
154
  exports.LimitCheckService = LimitCheckService = LimitCheckService_1 = __decorate([
181
155
  (0, common_1.Injectable)(),
182
- __metadata("design:paramtypes", [typeorm_1.DataSource,
183
- notification_rules_service_1.NotificationRulesService,
156
+ __metadata("design:paramtypes", [notification_rules_service_1.NotificationRulesService,
184
157
  notification_email_service_1.NotificationEmailService,
185
158
  email_provider_config_service_1.EmailProviderConfigService,
186
159
  ingest_event_bus_service_1.IngestEventBusService,
187
- manifest_runtime_service_1.ManifestRuntimeService])
160
+ manifest_runtime_service_1.ManifestRuntimeService,
161
+ notification_log_service_1.NotificationLogService])
188
162
  ], LimitCheckService);
189
163
  //# sourceMappingURL=limit-check.service.js.map
@@ -13,30 +13,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.NotificationCronService = void 0;
14
14
  const common_1 = require("@nestjs/common");
15
15
  const schedule_1 = require("@nestjs/schedule");
16
- const typeorm_1 = require("typeorm");
17
- const uuid_1 = require("uuid");
18
16
  const notification_rules_service_1 = require("./notification-rules.service");
19
17
  const notification_email_service_1 = require("./notification-email.service");
20
18
  const email_provider_config_service_1 = require("./email-provider-config.service");
19
+ const notification_log_service_1 = require("./notification-log.service");
21
20
  const manifest_runtime_service_1 = require("../../common/services/manifest-runtime.service");
22
- const sql_dialect_1 = require("../../common/utils/sql-dialect");
23
21
  const period_util_1 = require("../../common/utils/period.util");
24
- const local_mode_constants_1 = require("../../common/constants/local-mode.constants");
25
22
  let NotificationCronService = NotificationCronService_1 = class NotificationCronService {
26
- ds;
27
23
  rulesService;
28
24
  emailService;
29
25
  emailProviderConfigService;
30
26
  runtime;
27
+ notificationLog;
31
28
  logger = new common_1.Logger(NotificationCronService_1.name);
32
- dialect;
33
- constructor(ds, rulesService, emailService, emailProviderConfigService, runtime) {
34
- this.ds = ds;
29
+ constructor(rulesService, emailService, emailProviderConfigService, runtime, notificationLog) {
35
30
  this.rulesService = rulesService;
36
31
  this.emailService = emailService;
37
32
  this.emailProviderConfigService = emailProviderConfigService;
38
33
  this.runtime = runtime;
39
- this.dialect = (0, sql_dialect_1.detectDialect)(ds.options.type);
34
+ this.notificationLog = notificationLog;
40
35
  }
41
36
  async onModuleInit() {
42
37
  try {
@@ -49,9 +44,6 @@ let NotificationCronService = NotificationCronService_1 = class NotificationCron
49
44
  this.logger.error(`Startup catch-up failed: ${err}`);
50
45
  }
51
46
  }
52
- sql(query) {
53
- return (0, sql_dialect_1.portableSql)(query, this.dialect);
54
- }
55
47
  async checkThresholds(userId) {
56
48
  const rules = userId
57
49
  ? await this.rulesService.getActiveRulesForUser(userId)
@@ -103,16 +95,15 @@ let NotificationCronService = NotificationCronService_1 = class NotificationCron
103
95
  }
104
96
  async evaluateRule(rule, actual) {
105
97
  const { periodStart, periodEnd } = (0, period_util_1.computePeriodBoundaries)(rule.period);
106
- const alreadySent = await this.ds.query(this.sql(`SELECT 1 FROM notification_logs WHERE rule_id = $1 AND period_start = $2`), [rule.id, periodStart]);
107
- if (alreadySent.length > 0)
98
+ if (await this.notificationLog.hasAlreadySent(rule.id, periodStart))
108
99
  return false;
109
100
  if (actual < rule.threshold)
110
101
  return false;
111
- const now = new Date().toISOString().replace('T', ' ').replace('Z', '').slice(0, 19);
112
- const email = await this.resolveUserEmail(rule.user_id);
102
+ const now = (0, notification_log_service_1.formatNotificationTimestamp)();
103
+ const fullConfig = await this.emailProviderConfigService.getFullConfig(rule.user_id);
104
+ const email = await this.notificationLog.resolveUserEmail(rule.user_id, fullConfig?.notificationEmail);
113
105
  let emailSent = false;
114
106
  if (email) {
115
- const providerConfig = await this.emailProviderConfigService.getFullConfig(rule.user_id);
116
107
  emailSent = await this.emailService.sendThresholdAlert(email, {
117
108
  agentName: rule.agent_name,
118
109
  metricType: rule.metric_type,
@@ -122,46 +113,28 @@ let NotificationCronService = NotificationCronService_1 = class NotificationCron
122
113
  timestamp: now,
123
114
  agentUrl: `${this.runtime.getAuthBaseUrl()}/agents/${encodeURIComponent(rule.agent_name)}`,
124
115
  alertType: 'soft',
125
- }, providerConfig ?? undefined);
116
+ }, fullConfig ?? undefined);
126
117
  }
127
118
  else {
128
119
  this.logger.warn(`No email found for user ${rule.user_id}, skipping alert for rule ${rule.id}`);
129
120
  }
130
121
  if (emailSent || !email) {
131
- await this.ds.query(this.sql(`INSERT INTO notification_logs
132
- (id, rule_id, period_start, period_end, actual_value, threshold_value, metric_type, agent_name, sent_at)
133
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`), [
134
- (0, uuid_1.v4)(),
135
- rule.id,
122
+ await this.notificationLog.insertLog({
123
+ ruleId: rule.id,
136
124
  periodStart,
137
125
  periodEnd,
138
- actual,
139
- rule.threshold,
140
- rule.metric_type,
141
- rule.agent_name,
142
- now,
143
- ]);
126
+ actualValue: actual,
127
+ thresholdValue: rule.threshold,
128
+ metricType: rule.metric_type,
129
+ agentName: rule.agent_name,
130
+ sentAt: now,
131
+ });
144
132
  }
145
133
  else {
146
134
  this.logger.warn(`Failed to send alert for rule ${rule.id}, will retry next cron run`);
147
135
  }
148
136
  return emailSent || !email;
149
137
  }
150
- async resolveUserEmail(userId) {
151
- const fullConfig = await this.emailProviderConfigService.getFullConfig(userId);
152
- if (fullConfig?.notificationEmail)
153
- return fullConfig.notificationEmail;
154
- if (this.runtime.isLocalMode()) {
155
- const configEmail = (0, local_mode_constants_1.readLocalNotificationEmail)();
156
- if (configEmail)
157
- return configEmail;
158
- }
159
- const rows = await this.ds.query(this.sql(`SELECT email FROM "user" WHERE id = $1`), [userId]);
160
- const email = rows[0]?.email ?? null;
161
- if (email === local_mode_constants_1.LOCAL_EMAIL)
162
- return null;
163
- return email;
164
- }
165
138
  };
166
139
  exports.NotificationCronService = NotificationCronService;
167
140
  __decorate([
@@ -172,10 +145,10 @@ __decorate([
172
145
  ], NotificationCronService.prototype, "checkThresholds", null);
173
146
  exports.NotificationCronService = NotificationCronService = NotificationCronService_1 = __decorate([
174
147
  (0, common_1.Injectable)(),
175
- __metadata("design:paramtypes", [typeorm_1.DataSource,
176
- notification_rules_service_1.NotificationRulesService,
148
+ __metadata("design:paramtypes", [notification_rules_service_1.NotificationRulesService,
177
149
  notification_email_service_1.NotificationEmailService,
178
150
  email_provider_config_service_1.EmailProviderConfigService,
179
- manifest_runtime_service_1.ManifestRuntimeService])
151
+ manifest_runtime_service_1.ManifestRuntimeService,
152
+ notification_log_service_1.NotificationLogService])
180
153
  ], NotificationCronService);
181
154
  //# sourceMappingURL=notification-cron.service.js.map