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.
- package/README.md +1 -1
- package/dist/backend/analytics/analytics.module.js +8 -5
- package/dist/backend/analytics/controllers/agent-analytics.controller.js +5 -0
- package/dist/backend/analytics/controllers/agents.controller.js +5 -2
- package/dist/backend/analytics/controllers/messages.controller.js +6 -6
- package/dist/backend/analytics/services/aggregation.service.js +31 -108
- package/dist/backend/analytics/services/messages-query.service.js +152 -0
- package/dist/backend/analytics/services/query-helpers.js +15 -8
- package/dist/backend/analytics/services/timeseries-queries.service.js +31 -14
- package/dist/backend/common/common.module.js +12 -3
- package/dist/backend/common/constants/cache.constants.js +3 -2
- package/dist/backend/common/interceptors/agent-cache.interceptor.js +39 -0
- package/dist/backend/common/interceptors/user-cache.interceptor.js +5 -9
- package/dist/backend/common/services/tenant-cache.service.js +56 -0
- package/dist/backend/database/database-seeder.service.js +2 -2
- package/dist/backend/database/database.module.js +6 -0
- package/dist/backend/database/local-bootstrap.service.js +22 -22
- package/dist/backend/database/migrations/1771800000000-AddQualityScore.js +2 -2
- package/dist/backend/database/migrations/1771800100000-SeedQualityScores.js +2 -2
- package/dist/backend/database/migrations/1772843035514-AddPerformanceIndexes.js +56 -0
- package/dist/backend/entities/agent-message.entity.js +4 -1
- package/dist/backend/entities/cost-snapshot.entity.js +2 -1
- package/dist/backend/entities/security-event.entity.js +2 -1
- package/dist/backend/entities/user-provider.entity.js +5 -0
- package/dist/backend/main.js +3 -8
- package/dist/backend/model-prices/model-name-normalizer.js +3 -3
- package/dist/backend/model-prices/model-pricing-cache.service.js +5 -1
- package/dist/backend/notifications/services/limit-check.service.js +10 -0
- package/dist/backend/otlp/guards/otlp-auth.guard.js +8 -0
- package/dist/backend/otlp/services/log-ingest.service.js +7 -8
- package/dist/backend/otlp/services/metric-ingest.service.js +17 -20
- package/dist/backend/otlp/services/trace-ingest.service.js +85 -18
- package/dist/backend/routing/custom-provider.controller.js +10 -20
- package/dist/backend/routing/custom-provider.service.js +5 -2
- package/dist/backend/routing/proxy/proxy.controller.js +16 -1
- package/dist/backend/routing/proxy/stream-writer.js +4 -0
- package/dist/backend/routing/resolve-agent.service.js +62 -0
- package/dist/backend/routing/routing-cache.service.js +65 -0
- package/dist/backend/routing/routing.controller.js +16 -28
- package/dist/backend/routing/routing.module.js +5 -1
- package/dist/backend/routing/routing.service.js +40 -16
- package/dist/backend/telemetry/telemetry.service.js +33 -14
- package/dist/index.js +11 -11
- package/package.json +1 -1
- package/public/assets/index-XSLPss-g.js +7 -0
- package/public/index.html +1 -1
- package/dist/backend/common/services/cache-invalidation.service.js +0 -67
- package/dist/backend/routing/resolve-agent.util.js +0 -16
- 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-
|
|
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: [
|
|
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 {
|
|
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.
|
|
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
|
|
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
|
-
|
|
22
|
-
constructor(
|
|
23
|
-
this.
|
|
21
|
+
messagesQuery;
|
|
22
|
+
constructor(messagesQuery) {
|
|
23
|
+
this.messagesQuery = messagesQuery;
|
|
24
24
|
}
|
|
25
25
|
async getMessages(query, user) {
|
|
26
|
-
return this.
|
|
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", [
|
|
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 = [
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
38
|
-
sub
|
|
39
|
-
|
|
40
|
-
|
|
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
|
}
|