manifest 5.21.0 → 5.21.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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/dist/backend/analytics/analytics.module.js +8 -5
  3. package/dist/backend/analytics/controllers/agent-analytics.controller.js +5 -0
  4. package/dist/backend/analytics/controllers/agents.controller.js +5 -2
  5. package/dist/backend/analytics/controllers/messages.controller.js +6 -6
  6. package/dist/backend/analytics/services/aggregation.service.js +31 -108
  7. package/dist/backend/analytics/services/messages-query.service.js +152 -0
  8. package/dist/backend/analytics/services/query-helpers.js +15 -8
  9. package/dist/backend/analytics/services/timeseries-queries.service.js +31 -14
  10. package/dist/backend/common/common.module.js +12 -3
  11. package/dist/backend/common/constants/cache.constants.js +3 -2
  12. package/dist/backend/common/interceptors/agent-cache.interceptor.js +39 -0
  13. package/dist/backend/common/interceptors/user-cache.interceptor.js +5 -9
  14. package/dist/backend/common/services/tenant-cache.service.js +56 -0
  15. package/dist/backend/database/database-seeder.service.js +2 -2
  16. package/dist/backend/database/database.module.js +6 -0
  17. package/dist/backend/database/local-bootstrap.service.js +22 -22
  18. package/dist/backend/database/migrations/1771800000000-AddQualityScore.js +2 -2
  19. package/dist/backend/database/migrations/1771800100000-SeedQualityScores.js +2 -2
  20. package/dist/backend/database/migrations/1772843035514-AddPerformanceIndexes.js +56 -0
  21. package/dist/backend/entities/agent-message.entity.js +4 -1
  22. package/dist/backend/entities/cost-snapshot.entity.js +2 -1
  23. package/dist/backend/entities/security-event.entity.js +2 -1
  24. package/dist/backend/entities/user-provider.entity.js +5 -0
  25. package/dist/backend/main.js +3 -8
  26. package/dist/backend/model-prices/model-name-normalizer.js +3 -3
  27. package/dist/backend/model-prices/model-pricing-cache.service.js +5 -1
  28. package/dist/backend/notifications/services/limit-check.service.js +10 -0
  29. package/dist/backend/otlp/guards/otlp-auth.guard.js +8 -0
  30. package/dist/backend/otlp/services/log-ingest.service.js +7 -8
  31. package/dist/backend/otlp/services/metric-ingest.service.js +17 -20
  32. package/dist/backend/otlp/services/trace-ingest.service.js +85 -18
  33. package/dist/backend/routing/custom-provider.controller.js +10 -20
  34. package/dist/backend/routing/custom-provider.service.js +5 -2
  35. package/dist/backend/routing/proxy/proxy.controller.js +16 -1
  36. package/dist/backend/routing/proxy/stream-writer.js +4 -0
  37. package/dist/backend/routing/resolve-agent.service.js +62 -0
  38. package/dist/backend/routing/routing-cache.service.js +65 -0
  39. package/dist/backend/routing/routing.controller.js +16 -28
  40. package/dist/backend/routing/routing.module.js +5 -1
  41. package/dist/backend/routing/routing.service.js +40 -16
  42. package/dist/backend/telemetry/telemetry.service.js +33 -14
  43. package/dist/index.js +11 -11
  44. package/package.json +1 -1
  45. package/public/assets/index-XSLPss-g.js +7 -0
  46. package/public/index.html +1 -1
  47. package/dist/backend/common/services/cache-invalidation.service.js +0 -67
  48. package/dist/backend/routing/resolve-agent.util.js +0 -16
  49. package/public/assets/index-DP6qXsZ6.js +0 -7
package/README.md CHANGED
@@ -129,7 +129,7 @@ Works with **300+ models** across these providers:
129
129
  | [OpenAI](https://platform.openai.com/) | `gpt-5.3`, `gpt-4.1`, `o3`, `o4-mini` + 54 more |
130
130
  | [Anthropic](https://www.anthropic.com/) | `claude-opus-4-6`, `claude-sonnet-4.5`, `claude-haiku-4.5` + 14 more |
131
131
  | [Google Gemini](https://ai.google.dev/) | `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3-pro` + 19 more |
132
- | [DeepSeek](https://www.deepseek.com/) | `deepseek-v3`, `deepseek-r1` + 11 more |
132
+ | [DeepSeek](https://www.deepseek.com/) | `deepseek-chat`, `deepseek-reasoner` + 11 more |
133
133
  | [xAI](https://x.ai/) | `grok-4`, `grok-3`, `grok-3-mini` + 8 more |
134
134
  | [Mistral AI](https://mistral.ai/) | `mistral-large`, `codestral`, `devstral` + 26 more |
135
135
  | [Qwen (Alibaba)](https://www.alibabacloud.com/en/solutions/generative-ai/qwen) | `qwen3-235b`, `qwen3-coder`, `qwq-32b` + 42 more |
@@ -15,6 +15,7 @@ const tenant_entity_1 = require("../entities/tenant.entity");
15
15
  const otlp_module_1 = require("../otlp/otlp.module");
16
16
  const aggregation_service_1 = require("./services/aggregation.service");
17
17
  const timeseries_queries_service_1 = require("./services/timeseries-queries.service");
18
+ const messages_query_service_1 = require("./services/messages-query.service");
18
19
  const agent_analytics_service_1 = require("./services/agent-analytics.service");
19
20
  const overview_controller_1 = require("./controllers/overview.controller");
20
21
  const tokens_controller_1 = require("./controllers/tokens.controller");
@@ -27,10 +28,7 @@ let AnalyticsModule = class AnalyticsModule {
27
28
  exports.AnalyticsModule = AnalyticsModule;
28
29
  exports.AnalyticsModule = AnalyticsModule = __decorate([
29
30
  (0, common_1.Module)({
30
- imports: [
31
- typeorm_1.TypeOrmModule.forFeature([agent_message_entity_1.AgentMessage, agent_entity_1.Agent, tenant_entity_1.Tenant]),
32
- otlp_module_1.OtlpModule,
33
- ],
31
+ imports: [typeorm_1.TypeOrmModule.forFeature([agent_message_entity_1.AgentMessage, agent_entity_1.Agent, tenant_entity_1.Tenant]), otlp_module_1.OtlpModule],
34
32
  controllers: [
35
33
  overview_controller_1.OverviewController,
36
34
  tokens_controller_1.TokensController,
@@ -39,7 +37,12 @@ exports.AnalyticsModule = AnalyticsModule = __decorate([
39
37
  agents_controller_1.AgentsController,
40
38
  agent_analytics_controller_1.AgentAnalyticsController,
41
39
  ],
42
- providers: [aggregation_service_1.AggregationService, timeseries_queries_service_1.TimeseriesQueriesService, agent_analytics_service_1.AgentAnalyticsService],
40
+ providers: [
41
+ aggregation_service_1.AggregationService,
42
+ timeseries_queries_service_1.TimeseriesQueriesService,
43
+ messages_query_service_1.MessagesQueryService,
44
+ agent_analytics_service_1.AgentAnalyticsService,
45
+ ],
43
46
  })
44
47
  ], AnalyticsModule);
45
48
  //# sourceMappingURL=analytics.module.js.map
@@ -14,10 +14,13 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.AgentAnalyticsController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
+ const cache_manager_1 = require("@nestjs/cache-manager");
17
18
  const public_decorator_1 = require("../../common/decorators/public.decorator");
18
19
  const otlp_auth_guard_1 = require("../../otlp/guards/otlp-auth.guard");
19
20
  const agent_analytics_service_1 = require("../services/agent-analytics.service");
20
21
  const range_query_dto_1 = require("../../common/dto/range-query.dto");
22
+ const agent_cache_interceptor_1 = require("../../common/interceptors/agent-cache.interceptor");
23
+ const cache_constants_1 = require("../../common/constants/cache.constants");
21
24
  let AgentAnalyticsController = class AgentAnalyticsController {
22
25
  analytics;
23
26
  constructor(analytics) {
@@ -55,6 +58,8 @@ exports.AgentAnalyticsController = AgentAnalyticsController = __decorate([
55
58
  (0, common_1.Controller)('api/v1/agent'),
56
59
  (0, public_decorator_1.Public)(),
57
60
  (0, common_1.UseGuards)(otlp_auth_guard_1.OtlpAuthGuard),
61
+ (0, common_1.UseInterceptors)(agent_cache_interceptor_1.AgentCacheInterceptor),
62
+ (0, cache_manager_1.CacheTTL)(cache_constants_1.DASHBOARD_CACHE_TTL_MS),
58
63
  __metadata("design:paramtypes", [agent_analytics_service_1.AgentAnalyticsService])
59
64
  ], AgentAnalyticsController);
60
65
  //# sourceMappingURL=agent-analytics.controller.js.map
@@ -55,7 +55,10 @@ let AgentsController = class AgentsController {
55
55
  email: user.email,
56
56
  });
57
57
  (0, product_telemetry_1.trackCloudEvent)('agent_created', user.id, { agent_name: slug });
58
- return { agent: { id: result.agentId, name: slug, display_name: displayName }, apiKey: result.apiKey };
58
+ return {
59
+ agent: { id: result.agentId, name: slug, display_name: displayName },
60
+ apiKey: result.apiKey,
61
+ };
59
62
  }
60
63
  async getAgentKey(user, agentName) {
61
64
  const keyData = await this.apiKeyGenerator.getKeyForAgent(user.id, agentName);
@@ -93,7 +96,7 @@ exports.AgentsController = AgentsController;
93
96
  __decorate([
94
97
  (0, common_1.Get)('agents'),
95
98
  (0, common_1.UseInterceptors)(user_cache_interceptor_1.UserCacheInterceptor),
96
- (0, cache_manager_1.CacheTTL)(cache_constants_1.DASHBOARD_CACHE_TTL_MS),
99
+ (0, cache_manager_1.CacheTTL)(cache_constants_1.AGENT_LIST_CACHE_TTL_MS),
97
100
  __param(0, (0, current_user_decorator_1.CurrentUser)()),
98
101
  __metadata("design:type", Function),
99
102
  __metadata("design:paramtypes", [Object]),
@@ -15,15 +15,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.MessagesController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const messages_query_dto_1 = require("../dto/messages-query.dto");
18
- const aggregation_service_1 = require("../services/aggregation.service");
18
+ const messages_query_service_1 = require("../services/messages-query.service");
19
19
  const current_user_decorator_1 = require("../../auth/current-user.decorator");
20
20
  let MessagesController = class MessagesController {
21
- aggregation;
22
- constructor(aggregation) {
23
- this.aggregation = aggregation;
21
+ messagesQuery;
22
+ constructor(messagesQuery) {
23
+ this.messagesQuery = messagesQuery;
24
24
  }
25
25
  async getMessages(query, user) {
26
- return this.aggregation.getMessages({
26
+ return this.messagesQuery.getMessages({
27
27
  range: query.range,
28
28
  userId: user.id,
29
29
  status: query.status,
@@ -48,6 +48,6 @@ __decorate([
48
48
  ], MessagesController.prototype, "getMessages", null);
49
49
  exports.MessagesController = MessagesController = __decorate([
50
50
  (0, common_1.Controller)('api/v1'),
51
- __metadata("design:paramtypes", [aggregation_service_1.AggregationService])
51
+ __metadata("design:paramtypes", [messages_query_service_1.MessagesQueryService])
52
52
  ], MessagesController);
53
53
  //# sourceMappingURL=messages.controller.js.map
@@ -20,25 +20,30 @@ const agent_message_entity_1 = require("../../entities/agent-message.entity");
20
20
  const agent_entity_1 = require("../../entities/agent.entity");
21
21
  const range_util_1 = require("../../common/utils/range.util");
22
22
  const query_helpers_1 = require("./query-helpers");
23
+ const tenant_cache_service_1 = require("../../common/services/tenant-cache.service");
23
24
  const sql_dialect_1 = require("../../common/utils/sql-dialect");
24
25
  let AggregationService = class AggregationService {
25
26
  turnRepo;
26
27
  agentRepo;
27
28
  dataSource;
29
+ tenantCache;
28
30
  dialect;
29
- constructor(turnRepo, agentRepo, dataSource) {
31
+ constructor(turnRepo, agentRepo, dataSource, tenantCache) {
30
32
  this.turnRepo = turnRepo;
31
33
  this.agentRepo = agentRepo;
32
34
  this.dataSource = dataSource;
35
+ this.tenantCache = tenantCache;
33
36
  this.dialect = (0, sql_dialect_1.detectDialect)(this.dataSource.options.type);
34
37
  }
35
38
  async hasAnyData(userId, agentName) {
39
+ const tenantId = await this.tenantCache.resolve(userId);
36
40
  const qb = this.turnRepo.createQueryBuilder('at').select('1').limit(1);
37
- (0, query_helpers_1.addTenantFilter)(qb, userId, agentName);
41
+ (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId ?? undefined);
38
42
  const row = await qb.getRawOne();
39
43
  return row != null;
40
44
  }
41
45
  async getTokenSummary(range, userId, agentName) {
46
+ const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
42
47
  const interval = (0, range_util_1.rangeToInterval)(range);
43
48
  const prevInterval = (0, range_util_1.rangeToPreviousInterval)(range);
44
49
  const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
@@ -47,21 +52,21 @@ let AggregationService = class AggregationService {
47
52
  .createQueryBuilder('at')
48
53
  .select('COALESCE(SUM(at.input_tokens + at.output_tokens), 0)', 'total')
49
54
  .where('at.timestamp >= :cutoff', { cutoff });
50
- (0, query_helpers_1.addTenantFilter)(currentQb, userId, agentName);
55
+ (0, query_helpers_1.addTenantFilter)(currentQb, userId, agentName, tenantId);
51
56
  const currentRow = await currentQb.getRawOne();
52
57
  const prevQb = this.turnRepo
53
58
  .createQueryBuilder('at')
54
59
  .select('COALESCE(SUM(at.input_tokens + at.output_tokens), 0)', 'total')
55
60
  .where('at.timestamp >= :prevCutoff', { prevCutoff })
56
61
  .andWhere('at.timestamp < :cutoff', { cutoff });
57
- (0, query_helpers_1.addTenantFilter)(prevQb, userId, agentName);
62
+ (0, query_helpers_1.addTenantFilter)(prevQb, userId, agentName, tenantId);
58
63
  const prevRow = await prevQb.getRawOne();
59
64
  const detailQb = this.turnRepo
60
65
  .createQueryBuilder('at')
61
66
  .select('COALESCE(SUM(at.input_tokens), 0)', 'inp')
62
67
  .addSelect('COALESCE(SUM(at.output_tokens), 0)', 'out')
63
68
  .where('at.timestamp >= :cutoff', { cutoff });
64
- (0, query_helpers_1.addTenantFilter)(detailQb, userId, agentName);
69
+ (0, query_helpers_1.addTenantFilter)(detailQb, userId, agentName, tenantId);
65
70
  const detail = await detailQb.getRawOne();
66
71
  const current = Number(currentRow?.total ?? 0);
67
72
  const previous = Number(prevRow?.total ?? 0);
@@ -78,6 +83,7 @@ let AggregationService = class AggregationService {
78
83
  };
79
84
  }
80
85
  async getCostSummary(range, userId, agentName) {
86
+ const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
81
87
  const interval = (0, range_util_1.rangeToInterval)(range);
82
88
  const prevInterval = (0, range_util_1.rangeToPreviousInterval)(range);
83
89
  const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
@@ -87,20 +93,21 @@ let AggregationService = class AggregationService {
87
93
  .createQueryBuilder('at')
88
94
  .select(`COALESCE(SUM(${safeCost}), 0)`, 'total')
89
95
  .where('at.timestamp >= :cutoff', { cutoff });
90
- (0, query_helpers_1.addTenantFilter)(currentQb, userId, agentName);
96
+ (0, query_helpers_1.addTenantFilter)(currentQb, userId, agentName, tenantId);
91
97
  const currentRow = await currentQb.getRawOne();
92
98
  const prevQb = this.turnRepo
93
99
  .createQueryBuilder('at')
94
100
  .select(`COALESCE(SUM(${safeCost}), 0)`, 'total')
95
101
  .where('at.timestamp >= :prevCutoff', { prevCutoff })
96
102
  .andWhere('at.timestamp < :cutoff', { cutoff });
97
- (0, query_helpers_1.addTenantFilter)(prevQb, userId, agentName);
103
+ (0, query_helpers_1.addTenantFilter)(prevQb, userId, agentName, tenantId);
98
104
  const prevRow = await prevQb.getRawOne();
99
105
  const current = Number(currentRow?.total ?? 0);
100
106
  const previous = Number(prevRow?.total ?? 0);
101
107
  return { value: current, trend_pct: (0, query_helpers_1.computeTrend)(current, previous) };
102
108
  }
103
109
  async getMessageCount(range, userId, agentName) {
110
+ const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
104
111
  const interval = (0, range_util_1.rangeToInterval)(range);
105
112
  const prevInterval = (0, range_util_1.rangeToPreviousInterval)(range);
106
113
  const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
@@ -109,14 +116,14 @@ let AggregationService = class AggregationService {
109
116
  .createQueryBuilder('at')
110
117
  .select('COUNT(*)', 'total')
111
118
  .where('at.timestamp >= :cutoff', { cutoff });
112
- (0, query_helpers_1.addTenantFilter)(currentQb, userId, agentName);
119
+ (0, query_helpers_1.addTenantFilter)(currentQb, userId, agentName, tenantId);
113
120
  const currentRow = await currentQb.getRawOne();
114
121
  const prevQb = this.turnRepo
115
122
  .createQueryBuilder('at')
116
123
  .select('COUNT(*)', 'total')
117
124
  .where('at.timestamp >= :prevCutoff', { prevCutoff })
118
125
  .andWhere('at.timestamp < :cutoff', { cutoff });
119
- (0, query_helpers_1.addTenantFilter)(prevQb, userId, agentName);
126
+ (0, query_helpers_1.addTenantFilter)(prevQb, userId, agentName, tenantId);
120
127
  const prevRow = await prevQb.getRawOne();
121
128
  const current = Number(currentRow?.total ?? 0);
122
129
  const previous = Number(prevRow?.total ?? 0);
@@ -174,106 +181,21 @@ let AggregationService = class AggregationService {
174
181
  .set(agentUpdate)
175
182
  .where('id = :id', { id: agent.id })
176
183
  .execute();
177
- const tables = ['agent_messages', 'notification_rules', 'notification_logs', 'token_usage_snapshots', 'cost_snapshots'];
178
- for (const table of tables) {
179
- await manager
180
- .createQueryBuilder()
181
- .update(table)
182
- .set({ agent_name: newName })
183
- .where('agent_name = :currentName', { currentName })
184
- .execute();
185
- }
184
+ const tables = [
185
+ 'agent_messages',
186
+ 'notification_rules',
187
+ 'notification_logs',
188
+ 'token_usage_snapshots',
189
+ 'cost_snapshots',
190
+ ];
191
+ await Promise.all(tables.map((table) => manager
192
+ .createQueryBuilder()
193
+ .update(table)
194
+ .set({ agent_name: newName })
195
+ .where('agent_name = :currentName', { currentName })
196
+ .execute()));
186
197
  });
187
198
  }
188
- async getMessages(params) {
189
- const cutoff = params.range
190
- ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(params.range))
191
- : undefined;
192
- const baseQb = this.turnRepo.createQueryBuilder('at');
193
- if (cutoff) {
194
- baseQb.where('at.timestamp >= :cutoff', { cutoff });
195
- }
196
- (0, query_helpers_1.addTenantFilter)(baseQb, params.userId);
197
- if (params.status)
198
- baseQb.andWhere('at.status = :status', { status: params.status });
199
- if (params.service_type)
200
- baseQb.andWhere('at.service_type = :serviceType', { serviceType: params.service_type });
201
- if (params.model)
202
- baseQb.andWhere('at.model = :model', { model: params.model });
203
- if (params.cost_min !== undefined)
204
- baseQb.andWhere('at.cost_usd >= :costMin', { costMin: params.cost_min });
205
- if (params.cost_max !== undefined)
206
- baseQb.andWhere('at.cost_usd <= :costMax', { costMax: params.cost_max });
207
- if (params.agent_name)
208
- baseQb.andWhere('at.agent_name = :filterAgent', { filterAgent: params.agent_name });
209
- const countQb = baseQb.clone().select('COUNT(*)', 'total');
210
- const countResult = await countQb.getRawOne();
211
- const totalCount = Number(countResult?.total ?? 0);
212
- const costExpr = (0, sql_dialect_1.sqlCastFloat)((0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd'), this.dialect);
213
- const dataQb = baseQb.clone()
214
- .select('at.id', 'id')
215
- .addSelect('at.timestamp', 'timestamp')
216
- .addSelect('at.agent_name', 'agent_name')
217
- .addSelect('at.model', 'model')
218
- .addSelect('at.description', 'description')
219
- .addSelect('at.service_type', 'service_type')
220
- .addSelect('at.input_tokens', 'input_tokens')
221
- .addSelect('at.output_tokens', 'output_tokens')
222
- .addSelect('at.status', 'status')
223
- .addSelect('at.input_tokens + at.output_tokens', 'total_tokens')
224
- .addSelect(costExpr, 'cost')
225
- .addSelect('at.routing_tier', 'routing_tier')
226
- .addSelect('at.routing_reason', 'routing_reason')
227
- .addSelect('at.cache_read_tokens', 'cache_read_tokens')
228
- .addSelect('at.cache_creation_tokens', 'cache_creation_tokens')
229
- .addSelect('at.duration_ms', 'duration_ms')
230
- .addSelect('at.error_message', 'error_message');
231
- if (params.cursor) {
232
- const sepIdx = params.cursor.indexOf('|');
233
- if (sepIdx !== -1) {
234
- const cursorTs = params.cursor.substring(0, sepIdx);
235
- const cursorId = params.cursor.substring(sepIdx + 1);
236
- dataQb.andWhere(new typeorm_2.Brackets((sub) => {
237
- sub
238
- .where('at.timestamp < :cursorTs', { cursorTs })
239
- .orWhere(new typeorm_2.Brackets((inner) => {
240
- inner
241
- .where('at.timestamp = :cursorTs2', { cursorTs2: cursorTs })
242
- .andWhere('at.id < :cursorId', { cursorId });
243
- }));
244
- }));
245
- }
246
- }
247
- const rows = await dataQb
248
- .orderBy('at.timestamp', 'DESC')
249
- .addOrderBy('at.id', 'DESC')
250
- .limit(params.limit + 1)
251
- .getRawMany();
252
- const hasMore = rows.length > params.limit;
253
- const items = rows.slice(0, params.limit);
254
- const lastItem = items[items.length - 1];
255
- const ts = lastItem?.['timestamp'];
256
- const tsStr = ts instanceof Date ? (0, query_helpers_1.formatTimestamp)(ts) : String(ts ?? '');
257
- const lastId = lastItem?.['id'];
258
- const nextCursor = hasMore && lastItem
259
- ? `${tsStr}|${String(lastId)}` : null;
260
- const modelsQb = this.turnRepo
261
- .createQueryBuilder('at')
262
- .select('DISTINCT at.model', 'model')
263
- .where('at.model IS NOT NULL')
264
- .andWhere("at.model != ''");
265
- if (cutoff) {
266
- modelsQb.andWhere('at.timestamp >= :cutoff', { cutoff });
267
- }
268
- (0, query_helpers_1.addTenantFilter)(modelsQb, params.userId);
269
- const modelsResult = await modelsQb.orderBy('at.model', 'ASC').getRawMany();
270
- return {
271
- items,
272
- next_cursor: nextCursor,
273
- total_count: totalCount,
274
- models: modelsResult.map((r) => String(r['model'])),
275
- };
276
- }
277
199
  };
278
200
  exports.AggregationService = AggregationService;
279
201
  exports.AggregationService = AggregationService = __decorate([
@@ -282,6 +204,7 @@ exports.AggregationService = AggregationService = __decorate([
282
204
  __param(1, (0, typeorm_1.InjectRepository)(agent_entity_1.Agent)),
283
205
  __metadata("design:paramtypes", [typeorm_2.Repository,
284
206
  typeorm_2.Repository,
285
- typeorm_2.DataSource])
207
+ typeorm_2.DataSource,
208
+ tenant_cache_service_1.TenantCacheService])
286
209
  ], AggregationService);
287
210
  //# sourceMappingURL=aggregation.service.js.map
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.MessagesQueryService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const typeorm_1 = require("@nestjs/typeorm");
18
+ const typeorm_2 = require("typeorm");
19
+ const agent_message_entity_1 = require("../../entities/agent-message.entity");
20
+ const range_util_1 = require("../../common/utils/range.util");
21
+ const query_helpers_1 = require("./query-helpers");
22
+ const tenant_cache_service_1 = require("../../common/services/tenant-cache.service");
23
+ const sql_dialect_1 = require("../../common/utils/sql-dialect");
24
+ const MODELS_CACHE_TTL_MS = 60_000;
25
+ const MAX_CACHE_ENTRIES = 5_000;
26
+ let MessagesQueryService = class MessagesQueryService {
27
+ turnRepo;
28
+ dataSource;
29
+ tenantCache;
30
+ dialect;
31
+ modelsCache = new Map();
32
+ constructor(turnRepo, dataSource, tenantCache) {
33
+ this.turnRepo = turnRepo;
34
+ this.dataSource = dataSource;
35
+ this.tenantCache = tenantCache;
36
+ this.dialect = (0, sql_dialect_1.detectDialect)(this.dataSource.options.type);
37
+ }
38
+ async getMessages(params) {
39
+ const tenantId = (await this.tenantCache.resolve(params.userId)) ?? undefined;
40
+ const cutoff = params.range ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(params.range)) : undefined;
41
+ const baseQb = this.turnRepo.createQueryBuilder('at');
42
+ if (cutoff) {
43
+ baseQb.where('at.timestamp >= :cutoff', { cutoff });
44
+ }
45
+ (0, query_helpers_1.addTenantFilter)(baseQb, params.userId, undefined, tenantId);
46
+ if (params.status)
47
+ baseQb.andWhere('at.status = :status', { status: params.status });
48
+ if (params.service_type)
49
+ baseQb.andWhere('at.service_type = :serviceType', { serviceType: params.service_type });
50
+ if (params.model)
51
+ baseQb.andWhere('at.model = :model', { model: params.model });
52
+ if (params.cost_min !== undefined)
53
+ baseQb.andWhere('at.cost_usd >= :costMin', { costMin: params.cost_min });
54
+ if (params.cost_max !== undefined)
55
+ baseQb.andWhere('at.cost_usd <= :costMax', { costMax: params.cost_max });
56
+ if (params.agent_name)
57
+ baseQb.andWhere('at.agent_name = :filterAgent', { filterAgent: params.agent_name });
58
+ const countQb = baseQb.clone().select('COUNT(*)', 'total');
59
+ const countResult = await countQb.getRawOne();
60
+ const totalCount = Number(countResult?.total ?? 0);
61
+ const costExpr = (0, sql_dialect_1.sqlCastFloat)((0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd'), this.dialect);
62
+ const dataQb = baseQb
63
+ .clone()
64
+ .select('at.id', 'id')
65
+ .addSelect('at.timestamp', 'timestamp')
66
+ .addSelect('at.agent_name', 'agent_name')
67
+ .addSelect('at.model', 'model')
68
+ .addSelect('at.description', 'description')
69
+ .addSelect('at.service_type', 'service_type')
70
+ .addSelect('at.input_tokens', 'input_tokens')
71
+ .addSelect('at.output_tokens', 'output_tokens')
72
+ .addSelect('at.status', 'status')
73
+ .addSelect('at.input_tokens + at.output_tokens', 'total_tokens')
74
+ .addSelect(costExpr, 'cost')
75
+ .addSelect('at.routing_tier', 'routing_tier')
76
+ .addSelect('at.routing_reason', 'routing_reason')
77
+ .addSelect('at.cache_read_tokens', 'cache_read_tokens')
78
+ .addSelect('at.cache_creation_tokens', 'cache_creation_tokens')
79
+ .addSelect('at.duration_ms', 'duration_ms')
80
+ .addSelect('at.error_message', 'error_message');
81
+ if (params.cursor) {
82
+ const sepIdx = params.cursor.indexOf('|');
83
+ if (sepIdx !== -1) {
84
+ const cursorTs = params.cursor.substring(0, sepIdx);
85
+ const cursorId = params.cursor.substring(sepIdx + 1);
86
+ dataQb.andWhere(new typeorm_2.Brackets((sub) => {
87
+ sub.where('at.timestamp < :cursorTs', { cursorTs }).orWhere(new typeorm_2.Brackets((inner) => {
88
+ inner
89
+ .where('at.timestamp = :cursorTs2', { cursorTs2: cursorTs })
90
+ .andWhere('at.id < :cursorId', { cursorId });
91
+ }));
92
+ }));
93
+ }
94
+ }
95
+ const rows = await dataQb
96
+ .orderBy('at.timestamp', 'DESC')
97
+ .addOrderBy('at.id', 'DESC')
98
+ .limit(params.limit + 1)
99
+ .getRawMany();
100
+ const hasMore = rows.length > params.limit;
101
+ const items = rows.slice(0, params.limit);
102
+ const lastItem = items[items.length - 1];
103
+ const ts = lastItem?.['timestamp'];
104
+ const tsStr = ts instanceof Date ? (0, query_helpers_1.formatTimestamp)(ts) : String(ts ?? '');
105
+ const lastId = lastItem?.['id'];
106
+ const nextCursor = hasMore && lastItem ? `${tsStr}|${String(lastId)}` : null;
107
+ const models = await this.getDistinctModels(params.userId, params.range, tenantId);
108
+ return {
109
+ items,
110
+ next_cursor: nextCursor,
111
+ total_count: totalCount,
112
+ models,
113
+ };
114
+ }
115
+ async getDistinctModels(userId, range, tenantId) {
116
+ const cacheKey = `${userId}:${range ?? 'all'}`;
117
+ const cached = this.modelsCache.get(cacheKey);
118
+ if (cached && cached.expiresAt > Date.now()) {
119
+ return cached.models;
120
+ }
121
+ if (cached)
122
+ this.modelsCache.delete(cacheKey);
123
+ const cutoff = range ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(range)) : undefined;
124
+ const modelsQb = this.turnRepo
125
+ .createQueryBuilder('at')
126
+ .select('DISTINCT at.model', 'model')
127
+ .where('at.model IS NOT NULL')
128
+ .andWhere("at.model != ''");
129
+ if (cutoff) {
130
+ modelsQb.andWhere('at.timestamp >= :cutoff', { cutoff });
131
+ }
132
+ (0, query_helpers_1.addTenantFilter)(modelsQb, userId, undefined, tenantId);
133
+ const modelsResult = await modelsQb.orderBy('at.model', 'ASC').getRawMany();
134
+ const models = modelsResult.map((r) => String(r['model']));
135
+ if (this.modelsCache.size >= MAX_CACHE_ENTRIES && !this.modelsCache.has(cacheKey)) {
136
+ const firstKey = this.modelsCache.keys().next().value;
137
+ if (firstKey !== undefined)
138
+ this.modelsCache.delete(firstKey);
139
+ }
140
+ this.modelsCache.set(cacheKey, { models, expiresAt: Date.now() + MODELS_CACHE_TTL_MS });
141
+ return models;
142
+ }
143
+ };
144
+ exports.MessagesQueryService = MessagesQueryService;
145
+ exports.MessagesQueryService = MessagesQueryService = __decorate([
146
+ (0, common_1.Injectable)(),
147
+ __param(0, (0, typeorm_1.InjectRepository)(agent_message_entity_1.AgentMessage)),
148
+ __metadata("design:paramtypes", [typeorm_2.Repository,
149
+ typeorm_2.DataSource,
150
+ tenant_cache_service_1.TenantCacheService])
151
+ ], MessagesQueryService);
152
+ //# sourceMappingURL=messages-query.service.js.map
@@ -13,8 +13,6 @@ function computeTrend(current, previous) {
13
13
  const EPS = 1e-6;
14
14
  if (Math.abs(previous) < EPS)
15
15
  return 0;
16
- if (Math.abs(current) < EPS && Math.abs(previous) < EPS)
17
- return 0;
18
16
  const pct = Math.round(((current - previous) / previous) * 100);
19
17
  return Math.max(-999, Math.min(999, pct));
20
18
  }
@@ -33,12 +31,21 @@ function downsample(data, targetLen) {
33
31
  }
34
32
  return result;
35
33
  }
36
- function addTenantFilter(qb, userId, agentName) {
37
- qb.andWhere(new typeorm_1.Brackets((sub) => {
38
- sub
39
- .where('at.tenant_id IN (SELECT id FROM tenants WHERE name = :userId)', { userId })
40
- .orWhere('at.user_id = :userId', { userId });
41
- }));
34
+ function addTenantFilter(qb, userId, agentName, tenantId) {
35
+ if (tenantId) {
36
+ qb.andWhere(new typeorm_1.Brackets((sub) => {
37
+ sub
38
+ .where('at.tenant_id = :tenantId', { tenantId })
39
+ .orWhere('at.user_id = :userId', { userId });
40
+ }));
41
+ }
42
+ else {
43
+ qb.andWhere(new typeorm_1.Brackets((sub) => {
44
+ sub
45
+ .where('at.tenant_id IN (SELECT id FROM tenants WHERE name = :userId)', { userId })
46
+ .orWhere('at.user_id = :userId', { userId });
47
+ }));
48
+ }
42
49
  if (agentName) {
43
50
  qb.andWhere('at.agent_name = :agentName', { agentName });
44
51
  }